Памятка по логированию в Scala и вообще мире Java
12 августа 2015
Ни одно серьезное приложение не обходится без логирования, будь то сайтик, GUI или даже что-нибудь под Android. В мире Java ситуация с логированием на первый взгляд несколько запутанная. Есть какие-то SLF4J, какой-то Logback, зачем-то нужно создавать XML-файлы, вот это все. Но, как это часто бывает, если сесть, и не спеша во всем разобраться, на самом деле все оказывается очень просто и логично.
Итак, есть штука под названием SLF4J. Это что-то вроде интерфейса, который должны поддерживать все библиотеки для логирования. То есть, интерфейс один, а реализаций много. Есть java.util.logging (JUL), Common Logging (JCL), log4j, Logback. Наибольшей популярностью, похоже, пользуется Logback. Так вот, авторы библиотек пишут все логи через SLF4J и ничего не знают о том, какая конкретная реализация используется в том или ином приложении. Авторы программ выбирают конкретную реализацию. SLF4J как-то там сам ее находит, в результате чего весь проект использует эту одну реализацию. Правда, круто?
В случае со Scala есть заслуживающая внимания библиотека scala-logging, являющаяся оберткой над SLF4J. Используя макросы Scala, она добавляет проверку перед записью строки в лог, будет ли при текущих настройках эта строка на самом деле куда-то записана.
То есть, вы пишете:
… а после развертывания макроса это превращается в:
В результате, если отладочные логи отключены, не будет выделяться и освобождаться память под строку, не будет выполняться интерполяция, и так далее. Таким образом получаем существенно большую производительность.
Давайте попробуем прикрутить логирование к нашему REST-сервису на базе Finagle. В build.sbt дописываем зависимости:
"com.typesafe.scala-logging" %% "scala-logging" % "3.1.0"
В конструкторе класса FinagleServiceExample создаем логер:
import org.slf4j.LoggerFactory
// ...
private val logger = Logger(LoggerFactory.getLogger(this.getClass))
После чего берем и просто пишем логи:
logger.debug(s"reading $key")
logger.error(s"bad request: $req")
Существуют следующие уровни логирования, от меньшего к большему: TRACE, DEBUG, INFO, WARN, ERROR. В настройках, речь о которых пойдет ниже, выбирается эффективный уровень. При выборе определенного эффективного уровня в лог попадают все сообщения этого уровня и выше. Например, если выбран эффективный уровень INFO, в логах вы увидите все INFO, WARN и ERROR сообщения. Кроме того, есть особый эффективный уровень OFF, который отключает все логирование.
Logback ищет файл с настройками по следующим правилам:
- Если в classpath есть logback.groovy, используется он;
- Иначе в classpath ищется файл logback-test.xml;
- Если нет и его, пробуем найти logback.xml;
- Если и он не найден, используем настройки по умолчанию, то есть, пишем просто в консоль;
Давайте положим в src/test/resources файл logback-test.xml следующего содержания:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{HH:mm:ss.SSS} %-5level [%thread] %logger{128} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Как несложно догадаться, здесь мы говорим Logback писать все сообщения уровня DEBUG и выше в STDOUT, используя заданный формат. Если теперь запустить тесты, в выводе вы действительно увидите все DEBUG, INFO и ERROR логи. Можете заменить уровень в строке <root level="DEBUG">
на INFO или ERROR и убедиться, что логов становится меньше.
В каталоге src/main/resources создадим logback.xml немного иного содержания:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%date %-5level [%thread] %logger{128} - %msg%n
</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>finagle-example.log</file>
<encoder>
<pattern>
%date %-5level [%thread] %logger{128} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
Эти настройки тоже в особых пояснениях не нуждаются. Мы говорим писать как в консоль, так и в файл, используя немного другой формат логов. Можно собрать standalone jar, используя команду:
… затем запустить его, сказав:
… и проверить, что Logback успешно подхватывает настройки.
Можно переопределить настройки без пересборки jar, сказав:
-jar ./target/scala-2.11/finagle-example-assembly-0.1.jar
Можно заставить Logback автоматически перечитывать документацию, изменив первую строчку в logback.xml примерно так:
Можно фильтровать логи по классам и пакетам, при этом относительно иерархий работает интуитивная семантика:
В appender-секциях можно указывать минимальный уровень сообщений:
<level>INFO</level>
</filter>
Логи конкретных пакетов и классов можно направлять в заданные аппендеры:
<appender-ref ref="FILE" />
</logger>
Можно настроить ротацию и архивацию логов:
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/path/to/application.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%date %-5level [%thread] %logger{128} - %msg%n</pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<FileNamePattern>/path/to/application.%i.log.zip</FileNamePattern>
<MinIndex>1</MinIndex>
<MaxIndex>10</MaxIndex>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>2MB</MaxFileSize>
</triggeringPolicy>
</appender>
Альтернативный вариант:
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/path/to/application.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%date %-5level [%thread] %logger{128} - %msg%n</pattern>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>
/path/to/application-%d{yyyy-MM-dd}.log.gz
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
</appender>
Как видите, все очень гибко, очень мощно и не сказать, чтобы супер сложно.
Ссылки по теме:
- Официальный мануал по SLF4J;
- На сайте Logback есть отличная документация;
- Исходники к этой заметке на GitHub;
А чем вы пишете логи?
Метки: Scala, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.