Подтвердить что ты не робот

Регистрация в Java и в целом: лучшие практики?

Иногда, когда я вижу свой код регистрации, я задаюсь вопросом, правильно ли я делаю это. На это не может быть окончательного ответа, но у меня есть следующие проблемы:

Библиотечные классы

У меня есть несколько классов библиотек, которые могут регистрировать некоторые сообщения INFO. Исключение составляют ошибки со смертельным исходом. В настоящее время у меня есть статический экземпляр журнала в моих классах с именем класса в качестве имени журнала. (Log4j's: Logger.getLogger(MyClass.class))

Правильно ли это? Возможно, пользователь этого класса библиотеки не хочет отправлять какие-либо сообщения из моей реализации или хочет перенаправить их в конкретный журнал приложений. Должен ли я разрешить пользователю устанавливать регистратор из "внешнего мира"? Как вы справляетесь с такими случаями?

Общие журналы

В некоторых приложениях мои классы могут захотеть записать сообщение журнала в конкретный журнал, не идентифицированный именем класса. (I.e.: HTTP Request log) Каков наилучший способ сделать такую ​​вещь? Служба поиска приходит в голову...

4b9b3361

Ответ 1

Ваши соглашения довольно стандартны и довольно хороши (imho).

Единственное, что нужно посмотреть - это фрагментация памяти из чрезмерных незавершенных отладочных вызовов, поэтому с Log4J (и большинством других фреймворков Java) вы получите что-то вроде этого:

if (log.isDebugEnabled()) {
  log.debug("...");
}

поскольку построение этого сообщения журнала (которое вы, вероятно, не используете) может быть дорогостоящим, особенно если это делается тысячи или миллионы раз.

Уровень регистрации уровня INFO не должен быть слишком "частым" (и, как вы говорите, похоже, что это не так). Сообщения INFO должны быть в целом значимыми и значительными, например, запуск и остановка приложения. Вещи, которые вы, возможно, захотите узнать, если у вас возникла проблема. Журналирование отладки/точного уровня больше используется, когда у вас действительно есть проблема, которую вы пытаетесь диагностировать. Отладка/точная регистрация обычно включается только тогда, когда это необходимо. Информация обычно выполняется постоянно.

Если кто-то не хочет получать специальные сообщения INFO из ваших классов, они, конечно, могут изменить вашу конфигурацию log4j, чтобы не получить их. Log4j прекрасно разбирается в этом отделе (в отличие от регистрации Java 1.4).

Что касается вашей HTTP-вещи, я, как правило, не обнаружил, что это проблема с ведением журнала Java, потому что обычно один класс отвечает за то, что вас интересует, поэтому вам нужно всего лишь разместить его в одном месте. В (по моему опыту), когда вам нужны общие сообщения журнала через кажущиеся несвязанные классы, просто поставьте некоторый токен, который можно легко выполнить grepped for.

Ответ 2

Ниже приведены рекомендации, которым я следую во всех своих проектах, чтобы обеспечить хорошую производительность. Я пришел, чтобы сформировать этот набор руководящих принципов на основе материалов из различных источников в Интернете.

Как и на сегодняшний день, я считаю, что Log4j 2 - лучший вариант для входа в Java.

Тесты доступны здесь. Практика, которой я следую, чтобы добиться максимальной производительности, заключается в следующем:

  1. Я избегаю использования SLF4J на данный момент по следующим причинам:
    • У него есть некоторые проблемы параллелизма с маркерами, которые я хочу использовать для управления регистрацией операторов SQL (маркеры не такие мощные, как slf4j - см. Первый комментарий Ральфа Гоерса)
    • Он не поддерживает Java 8 Lambda, который, опять же, я хочу использовать для повышения производительности (поддержка лямбда-выражения в Logger)
  2. Делайте все обычные журналы, используя асинхронный регистратор для лучшей производительности
  3. Регистрируйте сообщения об ошибках в отдельном файле, используя синхронный регистратор, потому что мы хотим видеть сообщения об ошибках, как только они появляются
  4. Не используйте информацию о местоположении, такую как имя файла, имя класса, имя метода, номер строки в регулярной регистрации, потому что для получения этой информации платформа берет снимок стека и просматривает его. Это влияет на производительность. Следовательно, используйте информацию о местоположении только в журнале ошибок, а не в обычном журнале
  5. В целях отслеживания отдельных запросов, обрабатываемых отдельными потоками, рассмотрите возможность использования контекста потока и случайного UUID, как описано здесь
  6. Поскольку мы регистрируем ошибки в отдельном файле, очень важно, чтобы мы регистрировали контекстную информацию также в журнале ошибок. Например, если приложение обнаружило ошибку при обработке файла, напечатайте имя файла и обрабатываемую запись файла в файле журнала ошибок вместе с трассировкой стека.
  7. Файл журнала должен быть понятным и понятным. Например, если приложение обрабатывает записи клиентов в нескольких файлах, каждое сообщение журнала должно быть таким, как показано ниже:
12:01:00,127 INFO FILE_NAME=file1.txt - Processing starts
12:01:00,127 DEBUG FILE_NAME=file1.txt, CUSTOMER_ID=756
12:01:00,129 INFO FILE_NAME=file1.txt - Processing ends
  1. Зарегистрируйте все операторы SQL, используя маркер SQL, как показано ниже, и используйте фильтр, чтобы включить или отключить его:
private static final Marker sqlMarker = 
  MarkerManager.getMarker("SQL");

private void method1() {
    logger.debug(sqlMarker, "SELECT * FROM EMPLOYEE");
}
  1. Зарегистрируйте все параметры, используя Java 8 Lambdas. Это спасет приложение от форматирования сообщения, когда данный уровень журнала отключен:
int i=5, j=10;
logger.info("Sample output {}, {}", ()->i, ()->j);
  1. Не используйте конкатенацию строк. Используйте параметризованное сообщение, как показано выше

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

  3. Не используйте printStackTrace() или System.out.println()

  4. Приложение должно закрыть регистратор перед выходом:

LogManager.shutdown();
  1. Наконец, для каждой ссылки, я использую следующую конфигурацию:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorinterval="300" status="info" strict="true">
    <Properties>
        <Property name="filePath">${env:LOG_ROOT}/SAMPLE</Property>
        <Property name="filename">${env:LOG_ROOT}/SAMPLE/sample
        </Property>
        <property name="logSize">10 MB</property>
    </Properties>
    <Appenders>
        <RollingFile name="RollingFileRegular" fileName="${filename}.log"
            filePattern="${filePath}/sample-%d{yyyy-dd-MM}-%i.log">
            <Filters>
                <MarkerFilter marker="SQL" onMatch="DENY"
                    onMismatch="NEUTRAL" />
            </Filters>
            <PatternLayout>
                <Pattern>%d{HH:mm:ss,SSS} %m%n
                </Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy
                    interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy
                    size="${logSize}" />

            </Policies>
        </RollingFile>
        <RollingFile name="RollingFileError" 
            fileName="${filename}_error.log"
            filePattern="${filePath}/sample_error-%d{yyyy-dd-MM}-%i.log"
            immediateFlush="true">
            <PatternLayout>
                <Pattern>%d{HH:mm:ss,SSS} %p %c{1.}[%L] [%t] %m%n
                </Pattern>
            </PatternLayout>
            <Policies>
                <TimeBasedTriggeringPolicy
                    interval="1" modulate="true" />
                <SizeBasedTriggeringPolicy
                    size="${logSize}" />
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <AsyncLogger name="com"
            level="trace">
            <AppenderRef ref="RollingFileRegular"/>
        </AsyncLogger>
        <Root includeLocation="true" level="trace">
            <AppenderRef ref="RollingFileError" level="error" />
        </Root>
    </Loggers>
</Configuration>
  1. Требуемые зависимости Maven находятся здесь:
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.6</version>
</dependency>
<!-- (Optional)To be used when working 
with the applications using Log4j 1.x -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-1.2-api</artifactId>
    <version>2.8.1</version>
</dependency>

Ответ 3

В ответе @cletus он написал о проблеме

if (log.isDebugEnabled()) {
  log.debug("val is " + value);
}

который можно преодолеть, используя SL4J. Он предоставляет помощь в форматировании

log.debug("val is {}", value);

где сообщение создается, только если уровень отлаживается.

Итак, теперь, используя SL4J и его сопутствующий регистратор, Logback, рекомендуется по соображениям производительности и стабильности.

Ответ 4

Что касается экземпляров регистраторов, я имел некоторый успех, используя шаблон Java Eclipse для настройки моих регистраторов:

private static Logger log = Logger.getLogger(${enclosing_type}.class);

Это позволяет избежать проблемы с удалением JVM со своей трассировкой стека и, в конечном счете, уменьшить (тривиально, возможно) накладные расходы от создания трассировки стека.

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

Похоже, IntelliJ поддерживает ту же концепцию для переменной шаблона, представляющей имя закрывающего типа. Я не вижу способ сделать это легко в NetBeans.

Ответ 5

Предпочтительным вариантом для конфигурации log4j, которую вы описываете, является использование конфигурационного файла log4j. Это позволяет пользователям вашей реализации делать именно то, что вы просите, поскольку они могут переопределить вашу конфигурацию позже с чем-то более подходящим для их собственной реализации. См. здесь для очень тщательного праймера.

Ответ 6

Я, вероятно, украл это где-то, но это приятно.

Это уменьшает риск смешивания регистраторов при копировании и переустройстве pasti ^ h ^ h ^ h, и это меньше, чем тип.

В вашем коде:

private final static Logger logger = LoggerFactory.make();

... и в LoggerFactory:

public static Logger make() {
    Throwable t = new Throwable();
    StackTraceElement directCaller = t.getStackTrace()[1];
    return Logger.getLogger(directCaller.getClassName());
}

(Обратите внимание, что stackdump выполняется во время инициализации. Stacktrace, вероятно, не будет оптимизирован JVM, но на самом деле нет гарантий)

Ответ 7

Я просматриваю log-уровни приложения, и в настоящее время я обнаруживаю шаблон:

private static final Logger logger = Logger.getLogger(Things.class)

public void bla() {
  logger.debug("Starting " + ...)
  // Do stuff
  ...
  logger.debug("Situational")

  // Algorithms
  for(Thing t : things) {
    logger.trace(...)
  }

  // Breaking happy things
  if(things.isEmpty){
    logger.warn("Things shouldn't be empty!")
  }

  // Catching things
  try {
    ...
  } catch(Exception e) {
    logger.error("Something bad happened")
  }

  logger.info("Completed "+...)
}

Файл log4j2 определяет сокет-appender с файлом-приложением fail-over. И консоль-приложение. Иногда я использую маркеры log4j2, когда это требует ситуация.

Думал, что дополнительная перспектива может помочь.

Ответ 8

В качестве дополнения я считаю важным понимать Simple Logging Facade для Java (SLF4J) (http://www.slf4j.org/). Из-за некоторых проблем использования различных протоколов каротажа в самых разнообразных частях большого проекта SLF4J является фактическим стандартом для решения проблемы для успешного управления этими частями, не так ли?

Второе: кажется, что некоторые задачи старой школы могут быть заменены на Аспектно-ориентированное программирование, Spring frmwrk имеет реализация, AOP-подход для ведения журнала считается здесь в StackOverflow и здесь в блоге Spring.