使用附加的getHead()进行Java日志记录

问题描述 投票:3回答:1

我们正在开发一个Web应用程序,我正在服务器端实现事件日志记录。我决定一种很好的方法是扩展Java日志API以将事件记录到CSV文件。我创建了一个java.util.logging.Formatter类如下:

public class EventLogCsvFormatter extends Formatter {
private static final SimpleDateFormat SDF = new SimpleDateFormat("MM/dd/yyyy h:mm a");
private static final String[] COL_HEADERS = {"Date/Time", "Source", "Event Type", "Application Context", "Description", "Fields"};

public EventLogCsvFormatter() {
}

@Override
public String getHead(Handler h) {
    return String.join(",", COL_HEADERS);
}

@Override
public String format(LogRecord record) {
    if (record instanceof EventLogRecord) {
        EventLogRecord customLogRecord = (EventLogRecord) record;
        String[] fields = customLogRecord.getFields();
        List<String> textList = new ArrayList<>();

        textList.add(SDF.format(new Date(record.getMillis())));
        textList.add(customLogRecord.getSource() != null ? customLogRecord.getSource() : "Not Given");
        textList.add(customLogRecord.getEventType() != null ? customLogRecord.getEventType().toString() : "Not Given");
        textList.add(customLogRecord.getEventContext() != null ? customLogRecord.getEventContext().toString() : "Not Given");
        textList.add(customLogRecord.getMessage());

        if (fields != null && fields.length > 0) {
            for (String field : fields) {
                textList.add(field);
            }
        }

        String retVal = "\n" + String.join(",", textList);
        return retVal;
    }

    return "";
}

}

实现getHead()方法的想法是为每个CSV文件提供列标题。请注意,Java自己的XMLFormatter在其getHead()方法中返回XML文件头字符串时做了类似的事情。事实证明,Java日志记录在调用getHead()时没有考虑append标志。 append标志基本上告诉记录器重新打开现有文件并在启动时继续记录到它。由于我们的测试服务器反弹很多,因此生成的大多数CSV文件都在文件中的多个位置具有列标题名称,而不仅仅是在顶部。

我认为没有办法解决这个问题,因为几乎所有Java日志处理程序代码都有私有或包范围的字段和方法。所以,我甚至不能编写自己的自定义处理程序来工作。这是一个错误,我是SOL吗?我应该继续使用不同的日志记录API(例如Log4J)吗?

java java.util.logging
1个回答
2
投票

与此问题相关的FileHandler RFE是JDK-4629315: Appending of XML Logfiles doesn't merge new recordsJDK-5036335: Provide method to obtain log file name(s) and path(s)

使这项工作的诀窍是,您必须能够在请求标头时查询当前日志文件。如果当前日志文件长度为零,则格式化程序必须返回标头。

在自定义格式化程序中,您可以使用getHead方法尝试找到打开的文件,并使用java.iojava.nio查询长度。

@Override
public String getHead(Handler h) {
    boolean writeHeader = true;
    try {
        if (h instanceof FileHandler) {
            writeHeader = lengthOpen((FileHandler) h).longValue() == 0L;
        }
    } catch (SecurityException ignore) {
    }

    if (writeHeader) {
        return ""; //TODO: Insert your CSV headers.
    } else {
        return super.getHead(h); //Skip headers.
    }
}

private Number lengthOpen(Handler h) {
    if (h instanceof FileHandler) {
        String p = h.getClass().getName();
        LogManager manager = LogManager.getLogManager();
        p = manager.getProperty(p.concat(".pattern"));
        //TODO: Deal with FileHandler patterns.
        if (p != null) {
            File f = new File(p);
            //TODO: Implement file listing and filtering.
            return f.length();
        }
    }
    return 0L;
}

否则,如果你想做一些hackery,你可以使用反射。

@Override
public String getHead(Handler h) {
    boolean writeHeader = true;
    try {
        if (h instanceof FileHandler) {
            writeHeader = lengthFrom((FileHandler) h).longValue() == 0L;
        }
    } catch (SecurityException ignore) {
    }

    if (writeHeader) {
        return ""; //TODO: Insert your CSV headers here.
    } else {
        return super.getHead(h); //Skip headers.
    }
}

private Number lengthFrom(FileHandler h) {
    try {
        Field f = StreamHandler.class.getDeclaredField("output");
        f.setAccessible(true);
        OutputStream out = (OutputStream) f.get(h);
        f = out.getClass().getDeclaredField("written");
        f.setAccessible(true);
        return (Number) f.get(out);
    } catch (ReflectiveOperationException roe) {
        h.getErrorManager().error(null, roe, ErrorManager.FORMAT_FAILURE);
    }
    return 0L;
}
© www.soinside.com 2019 - 2024. All rights reserved.