Статьи

Log4J Groovy Way

Во время развития, сейчас или потом, кто-то хочет распечатать что-то на консоли его IDE. Ну, с Groovy это легко: println «Hello World!». Но когда дело доходит до серьезной разработки и ведения журнала, вы должны соответствовать следующим требованиям:

  • сохраняйте лог-операторы в коде для более быстрого их включения
  • временные метки для профилирования
  • log log … кто входит?
  • файл журнала для отслеживания ошибок
  • ну и так далее …

Одно из многих хороших лог-решений: log4j

На работе наша компания использует log4j как часть сервера приложений JBoss, поэтому я к этому привык. Некоторое время назад, прежде чем я начал работать с Groovy, я наткнулся на статью в grovvy zone: Groovy и Log4j

Фактически, эта статья заставила меня наконец начать работать с Groovy, после нескольких месяцев молчаливого следования сообществу. Ну, я немного развивался, каждый день с новыми классными и «Groovy» функциями, но вскоре после этого я запустил еще один Groovy-проект, и это стало второстепенным со вчерашнего дня, когда я собирался внедрить ведение журнала для своего проекта. Итак, я выкопал свой код и немного отполировал его, и вот, вот мое решение с log4j.

Почему я говорю вам использовать log4j? Я не. Я не хочу показывать никаких плюсов и минусов. Я просто хочу предоставить простое и отличное решение для удовлетворения требований к журналированию, для этого примера с log4j. Так что снабдите ваш проект log4j и запустите ваши двигатели, дамы и господа …

Groovy путь

Из упомянутой статьи я впервые придумал это решение для тестирования log4j:

package log4jimport org.apache.log4j.*class HelloWorldNoLog {  public static String PATTERN = "%d{ABSOLUTE} %-5p [%c{1}] %m%n"  static void main(args) {    def simple = new PatternLayout(PATTERN)    BasicConfigurator.configure(new ConsoleAppender(simple))    LogManager.rootLogger.level = Level.INFO    Logger log = Logger.getInstance(HelloWorldNoLog.class)    def name = "World"    log.info "Hello $name!"    log.warn "Groovy " + "logging " + "ahead..."    def x = 42    log.setLevel Level.DEBUG    if (log.isDebugEnabled()) {      log.debug "debug statement for var x: " + x    }  }}

дает тебе:

23:01:40,062 INFO  [HelloWorldNoLog] Hello World!23:01:40,078 WARN  [HelloWorldNoLog] Groovy logging ahead...23:01:40,078 DEBUG [HelloWorldNoLog] debug statement for var x: 42

Хороший, простой, но не очень хороший ИМХО, на самом деле здесь есть более-менее некрасивые вещи:

  • У вас есть код начальной загрузки, но это обычно не реальная проблема
  • Вам нужен экземпляр объекта Logger, и вы должны предоставить свой класс
  • Лучше проверить, включен ли уровень для трассировки, отладки (и информации), прежде чем регистрировать
  • Вы + присоединили строки вместо GString, как в первом примере кода для нескольких параметров

Настоящий заводной способ

Ну, разве не было бы проще иметь:

package log4jclass HelloWorldLog {   static void main(args) {      def name = "World"      Log.info "Hello $name!"      Log.warn "Groovy ", "logging ", "arrived!"      def x = 42      Log.setLevel "debug"      Log.debug "debug statement for var x: ", x   }}

 

чтобы дать вам:

23:10:46,359 INFO  [HelloWorldLog] Hello World!23:10:46,359 WARN  [HelloWorldLog] Groovy logging arrived!23:10:46,375 DEBUG [HelloWorldLog] debug statement for var x: 42

Довольно классно, а? Но что здесь произошло? Где весь код? Давайте проясним некоторые вещи: поскольку «Log.info …» начинается с заглавной буквы, это должен быть класс в том же пакете. Имея этот класс в ваших пакетах, вы устраняете необходимость:

  • Код шаблона
  • Получение экземпляра Logger
  • Предоставление класса 😉
  • Проверка, включен ли уровень
  • Перечисление для изменения уровня и, следовательно, не требуется импорт log4j

Для этого действительно нужны только три вещи: библиотека log4j в classpath, вспомогательный класс Log и импорт при записи чего-либо (также может быть статический импорт …). Хорошо, хорошо, на следующей странице появляется магический класс …

Log.groovy

package log4jimport org.apache.log4j.*class Log {  public static String PATTERN = "%d{ABSOLUTE} %-5p [%c{1}] %m%n"  public static Level LEVEL = Level.INFO  private static boolean initialized = false  private static Logger logger() {    def caller = Thread.currentThread().stackTrace.getAt(42)    if (!initialized) basic()    return Logger.getInstance(caller.className)  }  static basic() {    def simple = new PatternLayout(PATTERN)    BasicConfigurator.configure(new ConsoleAppender(simple))    LogManager.rootLogger.level = LEVEL    initialized = true  }  static setLevel(level) {    def Level l = null    if (level instanceof Level) {      l = level    } else if (level instanceof String) {      l = (Level."${level.toUpperCase()}")?: null    }    if (l) LogManager.rootLogger.level = l  }  static trace(Object... messages) { log("Trace", null, messages) }  static trace(Throwable t, Object... messages) { log("Trace", t, messages) }    static debug(Object... messages) { log("Debug", null, messages) }  static debug(Throwable t, Object... messages) { log("Debug", t, messages) }  static info(Object... messages) { log("Info", null, messages) }  static info(Throwable t, Object... messages) { log("Info", t, messages) }  static warn(Object... messages) { log("Warn", null, messages) }  static warn(Throwable t, Object... messages) { log("Warn", t, messages) }  static error(Object... messages) { log("Error", null, messages) }  static error(Throwable t, Object... messages) { log("Error", t, messages) }  static fatal(Object... messages) { log("Fatal", null, messages) }  static fatal(Throwable t, Object... messages) { log("Fatal", t, messages) }  private static log(String level, Throwable t, Object... messages) {    if (messages) {      def log = logger()      if (level.equals("Warn") || level.equals("Error") || level.equals("Fatal") || log."is${level}Enabled" ()) {        log."${level.toLowerCase()}" (messages.join(), t)      }    }  }}

Когда вы вызываете любой из методов trace, debug и т. Д. (С Throwable или без него), вызывается общий метод log () с уровнем, начинающимся с заглавной строки в виде заглавной буквы (также обратите внимание на использование varargs):

private static log(String level, Throwable t, Object... messages) {  if (messages) {    def log = logger()    if (level.equals("Warn") || level.equals("Error") || level.equals("Fatal") || log."is${level}Enabled" ()) {      log."${level.toLowerCase()}" (messages.join(), t)    }  }}
  • Метод сначала получает сам регистратор (все еще не нужен класс 😉
  • Затем он проверяет, является ли уровень предупреждением, ошибкой или фатальным для прямой регистрации
  • В противном случае вызывается метод isTraceEnabled, isDebugEnable или isInfoEnabled (поэтому первая буква заглавная)
  • Само ведение журнала происходит с нижним регистром как динамический вызов метода
  • Сообщения объединяются автоматически (вы можете использовать запятую здесь для поддержки нескольких способов регистрации)

Теперь для объекта Logger этот метод выполняет всю магию:

  private static Logger logger() {    def caller = Thread.currentThread().stackTrace.getAt(42)    if (!initialized) basic()    return Logger.getInstance(caller.className)  }

Заметки:

  • Чтобы получить класс для логгера log4j, используйте трассировку стека текущего потока
  • Поскольку путь стека к этому методу не изменяется внутренне, вызывающий абонент может быть найден в фиксированной позиции.
  • Таким образом, экземпляр Logger был создан с использованием простого имени класса

Остальное здесь установлено только на уровне. Так долго, так здорово.

Что дальше

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

  • Поддержка log4j.xml для конфигурации (из classpath или из каталога пользователя)
  • Разрешить Appenders, Категории и остальное
  • Или создайте DSL для конфигурации, подобной этой (только что написал, не проверял!):
def log4jDSL = {  configuration(debug: false) {    appender(name: "file", type: "DailyRolling") {      errorHandler (type: "OnlyOnce")      params() {        File "groovy.log"        Append false        DatePattern ".yyyy-MM-dd"        Threshold "debug"        layout (type: "PatternLayout") {          param (type: "ConversationPattern", value: "%d %-5p [%c] %m%n")        }      }    }    category(name: "org.codehaus") {      priority (value: "debug")    }    root() {      appenders() {        file()      }    }  }}

Вывод

Для меня это чистый подход, и его можно расширять и использовать везде, где Groovy и регистрация ведутся. Приложив немного больше усилий, вы можете прочитать разметку xmls или groovy и в итоге получить надежное, но настраиваемое решение для вашего пакета утилит Groovy (ну, вы можете использовать его и в качестве категории, и в качестве статической …)

Надеюсь, вам понравился мой первый пост так же, как мне понравилось писать этот совет или трюк!

Обо мне

Я из Германии, и у меня Groovy зависимость, так как я попробовал это месяц назад, как уже упоминалось. Я работаю в средней компании по разработке программного обеспечения J2EE в Людвигсбурге, недалеко от Штутгарта, и в то же время я представил Groovy, который помог там успешно решить некоторые задачи по разработке 😉

редактировать

Основываясь на комментарии Рональда, я обновил соответствующие части этой истории (подробности см. В комментариях)