Вход в Java и в целом: лучшие практики? - PullRequest
61 голосов
/ 25 мая 2009

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

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

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

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

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

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

Ответы [ 8 ]

37 голосов
/ 25 мая 2009

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

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

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

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

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

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

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

8 голосов
/ 21 января 2018

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

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

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

  1. В настоящее время я избегаю использования SLF4J по следующим причинам:
    • У него есть некоторые проблемы параллелизма с маркерами, которые я хочу использовать для управления регистрацией операторов SQL ( Маркеры не такие мощные, как slf4j - см. Первый комментарий Ральфа Гоерса)
    • Он не поддерживает лямбду Java 8, которую я снова хочу использовать для повышения производительности ( Поддержка лямбда-выражения в 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>
7 голосов
/ 17 октября 2015

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

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

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

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

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

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

6 голосов
/ 14 ноября 2012

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

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

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

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

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

4 голосов
/ 12 мая 2016

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

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 определяет приложение-сокет с резервным файлом-приложение. И консоль-аппендер. Иногда я использую маркеры log4j2, когда этого требует ситуация.

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

4 голосов
/ 25 мая 2009

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

Снижает риск перепутывания регистраторов при копировании и рефакторинге ^ 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());
}

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

4 голосов
/ 25 мая 2009

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

2 голосов
/ 20 декабря 2012

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...