Ведение журнала Java с использованием getHead() с добавлением

Мы разрабатываем веб-приложение, и я реализую регистрацию событий на стороне сервера. Я решил, что хорошим подходом будет расширение API ведения журналов Java для регистрации событий в файле 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-файла. Обратите внимание, что XMLFormatter собственного языка Java делает нечто подобное, возвращая строку заголовка XML-файла в методе getHead(). Как оказалось, ведение журнала Java не учитывает флаг добавления при вызове getHead(). Флаг добавления в основном говорит регистратору повторно открыть существующий файл и продолжить запись в него при запуске. Поскольку наш тестовый сервер довольно часто отскакивает, большинство сгенерированных CSV-файлов имеют имена заголовков столбцов в нескольких местах внутри файлов, а не только вверху.

Я не думаю, что есть какой-то способ обойти это, поскольку почти весь код обработчика ведения журнала Java имеет частные поля или методы области действия пакета. Итак, я даже не могу написать свой собственный обработчик для работы здесь. Это ошибка и я SOL? Должен ли я просто использовать другой API ведения журналов (например, Log4J)?


person MiseryMachine    schedule 18.12.2015    source источник
comment
Быстрый и грязный обходной путь: не переопределяйте getHead(), вместо этого создайте исходный файл журнала с требуемым заголовком и просто добавьте к нему. Не очень приятно, но, возможно, лучше, чем менять всю стратегию ведения журнала, если вы уже используете j.u.l.   -  person Klitos Kyriacou    schedule 18.12.2015


Ответы (1)


RFE FileHandler, связанные с этой проблемой: JDK-4629315: добавление файлов журнала XML не объединяется новые записи и JDK-5036335: укажите метод для получения имен файлов журналов ) и пути.

Хитрость в выполнении этой работы заключается в том, что вы должны иметь возможность запрашивать текущий файл журнала при запросе заголовка. Если текущая длина файла журнала равна нулю, модуль форматирования должен вернуть заголовки.

В вашем пользовательском форматере вы можете использовать метод getHead, чтобы попытаться найти открытый файл и запросить длину, используя либо java.io, либо java.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;
}

В противном случае, если вы хотите сделать какой-то взлом, вы можете прибегнуть к использованию отражения.

@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;
}
person jmehrens    schedule 18.12.2015
comment
Спасибо за ваш вклад. Я нашел решение, основанное на вашем первом ответе. Я не мог использовать его напрямую, потому что использую собственный регистратор, но вы указали мне правильное направление. - person MiseryMachine; 18.12.2015