Содержание

Логирование

Сведения

:!: Logback

Logback - одна из наиболее используемых библиотек ведения журнала, замена предшественника Log4j
Состоит из трех классов:
Logger:
Контекст для сообщений журнала. Класс с которым взаимодействуют приложения для создания сообщений

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

Layout:
Форматирование сообщений

Maven зависимости:(?)

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.3.5</version>
</dependency>
 
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.4</version>
    <scope>test</scope>
</dependency>
 
# И еще возможно это
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.3.5</version>
</dependency>

Минимальный пример:

# Файл logback.xml
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
 
  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Файл программы:

public class Example {
 
    private static final Logger logger 
      = LoggerFactory.getLogger(Example.class);
 
    public static void main(String[] args) {
        logger.info("Example log from {}", Example.class.getSimpleName());
    }
}
 
  # Еще такой пример есть, пока неясно зачем приведение и что ето за класс
ch.qos.logback.classic.Logger logger = 
  (ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.baeldung.logback");
logger.debug("Hi there!");
:!: Уровни
  • TRACE — самый низкий уровень информации, в основном используемый для очень глубокой отладки кода, обычно не включаемый в производственные журналы.
  • DEBUG — информация низкого уровня, используемая для целей отладки, обычно не включаемая в производственные журналы.
  • INFO — серьезность журнала, содержащая информацию, например, о начавшейся или завершенной операции.
  • WARN – уровень журнала, информирующий о событии, которое может потребовать нашего внимания, но не является критическим и может быть ожидаемым.
  • ОШИБКА — уровень журнала, сообщающий об ошибке, ожидаемой или неожиданной, обычно означающей, что часть системы работает неправильно.

Библиотека «logback-core» содержит следующие классы:

  • ConsoleAppender — добавляет события журнала в System.out или System.err.
  • OutputStreamAppender — добавляет события журнала в java.io.Outputstream, предоставляя базовые услуги для других приложений.
  • FileAppender — добавляет события журнала в файл.
  • RollingFileAppender — добавляет события журнала в файл с возможностью автоматического переключения файлов.

«Logback-classic» расширяет перечень:

  • SocketAppender — добавляет события журнала в сокет
  • SSLSocketAppender — добавляет события журнала в сокет, используя безопасное соединение.
  • SMTPAppender — накапливает данные в пакетах и ​​отправляет содержимое пакета на указанный пользователем адрес электронной почты после наступления указанного пользователем события.
  • DBAppender — добавляет данные в таблицы базы данных
  • SyslogAppender — добавляет данные в место назначения, совместимое с Syslog.
  • SiftingAppender — приложение, которое может разделять ведение журнала в соответствии с заданным атрибутом времени выполнения.
  • AsyncAppender — асинхронно добавляет события журнала.

Конфигурация

:!: Конфигурация

Журнал отладки конфигурации:

<configuration debug="true">
  ...
</configuration>
 
  # Либо такой метод
<configuration>
    <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  
    ...
</configuration>

Автоматическая перезагрузка конфигурации

<configuration scan="true">
  ...
</configuration>
 
  # По умолчанию 60 секунд, можно изменить
<configuration scan="true" scanPeriod="15 seconds">
  ...
</configuration>

Конфигурация логгеров

  # Уровень логирования можно задать каждому персонально
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.baeldung.logback" level="INFO" /> 
    <logger name="com.baeldung.logback.tests" level="WARN" /> 
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>
 
  # Далее использование:
Logger foobar = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.foobar");
Logger logger = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");
Logger testslogger = 
  (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback.tests");
 
foobar.debug("This is logged from foobar");
logger.debug("This is not logged from logger");
logger.info("This is logged from logger");
testslogger.info("This is not logged from tests");
testslogger.warn("This is logged from tests");

Переменные

<property name="LOG_DIR" value="/var/log/application" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>${LOG_DIR}/tests.log</file>
    <append>true</append>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender>
 
  # Значение объявлено в первой строке, либо можно указать в аргументах запуска джарки (возможно и через переменные окружения, но это не точно)
$ java -DLOG_DIR=/var/log/application com.baeldung.logback.LogbackTests
:!: Appenders

Файловые аппендеры

<configuration debug="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>tests.log</file>
        <append>true</append>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>
 
    <logger name="com.baeldung.logback" level="INFO" /> 
    <logger name="com.baeldung.logback.tests" level="WARN"> 
        <appender-ref ref="FILE" /> 
    </logger> 
 
    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

Ротируемый аппендер

<property name="LOG_FILE" value="LogFile" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_FILE}.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- daily rollover -->
        <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
 
        <!-- keep 30 days' worth of history capped at 3GB total size -->
        <maxHistory>30</maxHistory>
        <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
        <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
</appender> 

Использование

:!: Приемы/детали

Отдельный класс
Для того чтобы перенаправить определенные сообщение в какой либо файл, создаем ему отдельный логгер, указав любое имя, создаем ему аппендер, с нужной конфигурацией, и в логере указываем additivity=«false» чтобы сообщение не передавалось дальше

  # Параметризованные сообщения
  # фигурные скобки используют "toString()" при возможности/необходимости
log.debug("Current count is " + count);
log.debug("Current count is {}", count);
logger.debug("Going to divide {} by {}", 42, zero);
:!: Макеты

Например

<encoder>
    <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
  • %d{HH:mm:ss.SSS} – метка времени с часами, минутами, секундами и миллисекундами
  • [%thread] — имя потока, создающего сообщение журнала, заключенное в квадратные скобки.
  • %-5level — уровень события логирования, дополненный до 5 символов
  • %logger{36} – имя логгера , усеченное до 35 символов.
  • %msg%n — сообщения журнала, за которыми следует символ-разделитель строк, зависящий от платформы.

Перечень

:!: Итого

root-логер является корневым, перехватывает все, с заданным уровнем
В апендерах можно настроить фильтрацию, в основном по уровню, но как то особо не развернешься с одними только ими

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

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 
    <property name="LOGS" value="./logs" />
 
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{dd-MM-yyyy HH:mm:ss.SSS} %magenta([%thread]) %highlight(%-5level) %logger{36}.%M - %msg%n
            </pattern>
        </encoder>
    </appender>
 
    <appender name="SystemLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOGS}/system.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily and when the file reaches 10 MegaBytes -->
            <fileNamePattern>${LOGS}/archived/system_%d{yyyy-MM-dd}_%i.log.zip
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
 
    <appender name="ErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOGS}/error.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily and when the file reaches 10 MegaBytes -->
            <fileNamePattern>${LOGS}/archived/error_%d{yyyy-MM-dd}_%i.log.zip
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
 
    <appender name="WebappLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOGS}/webapp.log</file>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- rollover daily and when the file reaches 10 MegaBytes -->
            <fileNamePattern>${LOGS}/archived/webapp_%d{yyyy-MM-dd}_%i.log.zip
            </fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>
 
    <logger name="com.baeldung.logback" level="INFO">
        <appender-ref ref="webapp" />
    </logger>
 
    <root level="info">
        <appender-ref ref="SystemLog" />
        <appender-ref ref="Console" />
        <appender-ref ref="ErrorLog"/>
    </root>
</configuration>
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final Logger waLogger = LoggerFactory.getLogger("com.baeldung.logback");
 
    logger.info("Запись в лог");
    waLogger.info("(WA) Запись в лог");
:!: Еще пример
<appender name="Commands" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>./logs/commands/commands.log</file>
    <append>true</append>
    <encoder>
        <pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} %-5level - %msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <!-- daily rollover -->
        <fileNamePattern>./logs/commands/commands-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
        <maxHistory>50</maxHistory>
    </rollingPolicy>
</appender>
(...)
<logger name="com.gmware.lib.commands" level="INFO" additivity="false">
    <appender-ref ref="Commands"/>
</logger>
:!: Рабочий пример, есть Json
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false" scanPeriod="1 seconds" debug="false">
 
    <appender name="ConsoleAppender" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <encoder>
            <pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
 
    <appender name="FileMultiplicatorDefaultAppender"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>./log/FileMultiplicator.log</file>
        <append>true</append>
        <encoder>
            <pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>log/FileMultiplicator-%d{yyyy-MM-dd}.%i.zip</fileNamePattern>
            <!-- each file should be at most 100MB, keep 7 days worth of history, but at most 20GB -->
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
    </appender>
 
    <appender name="FileMultiplicatorJsonAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>log/json/FileMultiplicator.log</file>
        <append>true</append>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <pattern>
                    <!-- the pattern that defines what to include -->
                    <pattern>
                        {
                        "timestamp": "%date{yyyy-MM-dd'T'HH:mm:ss}",
                        "logger": "%logger{36}",
                        "message": "%message",
                        "level": "%level"
                        }
                    </pattern>
                </pattern>
                <arguments/>
                <logstashMarkers/>
                <stackTrace>
                    <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter">
                        <maxDepthPerThrowable>30</maxDepthPerThrowable>
                        <maxLength>2048</maxLength>
                        <shortenedClassNameLength>20</shortenedClassNameLength>
                        <exclude>^sun\.reflect\..*\.invoke</exclude>
                        <exclude>^net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude>
                        <rootCauseFirst>true</rootCauseFirst>
                    </throwableConverter>
                </stackTrace>
            </providers>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- rollover daily -->
            <fileNamePattern>log/json/FileMultiplicator-%d{yyyy-MM-dd}.%i.zip</fileNamePattern>
            <!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
    </appender>
 
 
    <appender name="Commands" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>./log/commands/commands.log</file>
        <append>true</append>
        <encoder>
            <pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} %-5level - %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- daily rollover -->
            <fileNamePattern>./log/commands/commands-%d{yyyy-MM-dd}.log.zip</fileNamePattern>
            <maxHistory>50</maxHistory>
        </rollingPolicy>
    </appender>
 
    <root level="INFO">
<!--        <appender-ref ref="ConsoleAppender"/>-->
        <appender-ref ref="FileMultiplicatorDefaultAppender"/>
	<appender-ref ref="FileMultiplicatorJsonAppender"/>	
    </root>
 
    <logger name="com.gmware.lib.commands" level="INFO" additivity="false">
        <appender-ref ref="Commands"/>
    </logger>	
</configuration>

Ротирование логов

Аппендер класса «RollingFileAppender» расширяет класс «FileAppender», добавляя возможность ротирования файлов

Два важных подкомпонента: RollingPolicy («что делать») и TriggeringPolicy («когда это делать»)
Первый может реализовать интерфейс для второго, тогда достаточно указать только его

RollingPolicy

Внутри аппендера создается xml компонент политики, имеются разные классы, определяющие поведение

:!: Примеры классов для RollingPolicy

TimeBasedRollingPolicy - На основе времени, один обязательный аргумент «fileNamePattern», так же есть «MaxHistory»
Шаблон имени должен сдержать спецификатор «%d», тот может содержать шаблон даты/времени, по умолчанию «гггг-ММ-дд». Период ротирования выводится из значения в fileNamePattern
т.е. в данном случае в полночь, если время не указано

Сжатие - для этого нужно добавить расширение «gz/zip» к имени файла

SizeAndTimeBaseRollingPolicy - собсна по дате и размеру. Здесь добавляется еще один обязательный токен именования - «%i», обозначающий порядковый номер

Пример конфигурации

<configuration>
  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>mylog.txt</file>
    <append>true</append>
 
	<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <!-- rollover daily -->
      <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
       <maxFileSize>100MB</maxFileSize>    
       <maxHistory>60</maxHistory>
       <totalSizeCap>20GB</totalSizeCap>
    </rollingPolicy>
 
    <encoder>
      <pattern>%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
      </pattern>
    </encoder>
  </appender>
 
 
  <root level="DEBUG">
    <appender-ref ref="ROLLING" />
  </root>
 
</configuration>