logback.groovy配置

第十二章:Groovy 配置

領域特定語言或者 DSL 更加普遍。logback 基於 XML 的配置可以看做 DSL 的實例。由於 XML 的本質,基於 XML 的配置文件變得非常的囉嗦以及臃腫。另外,logback 中的 Joran 有一個相對龐大的代碼,用來專門處理基於 XML 的配置文件。Joran 支持一些非常好的特性,例如變量替換,條件處理,以及動態擴展。但是,不但 Joran 非常複雜,而且給用戶的體驗非常的不好,或者至少不直觀。

本章敘述基於 Groovy 的 DSL 致力於一致性,直觀性,以及非常強大。任何你可以使用 XML 配置的文件,你都可以用更加簡短的符號使用 Groovy 來實現。爲了幫助你遷移到 Groovy 風格的配置,我們開發了一個工具

常規建議

一般來說,logback.groovy 文件是 Groovy 程序。因爲 Groovy 是 Java 的超集,所以無論你在 Java 執行什麼配置操作,你都可以在 logback.groovy 文件中做同樣的事情。但是,在 Java 中,使用變成的方式配置 logback 有點笨重,所以我們增加了一些 logback 特有的擴展來減輕你的負擔。我們嘗試限制 logback 特有的拓展符號儘量的少。如果你已經熟悉了 Groovy,那麼你應該更加容易去讀,去理解甚至去寫你自己的 logback.groovy 文件。那麼不熟悉 Groovy 的人依然會發現 logback.groovy 中的語法比 logback.xml 中的語法更加容易使用。

logback.groovy 文件是 Groovy 程序,具有最小的 logback 特定的拓展。所有常用的 groovy 結構,例如類的導入,變量定義,字符串 (GString) 中包含 ${..} 評估表達式,以及 if-else 語句在 logback.grooby 文件中都是可用的。

自動導入

1.0.10 版本以後 爲了減少不必要的引用,一些共同的類以及包會被自動導入。因此,只要你只是配置了內置的 appender,layout 等等,你不需要在你的腳本中添加相對應的導入語句。當然,對於默認導入不會涉及到類,你需要自己導入。

下面是默認導入的列表:

  • import ch.qos.logback.core.*;
  • import ch.qos.logback.core.encoder.*;
  • import ch.qos.logback.core.read.*;
  • import ch.qos.logback.core.rolling.*;
  • import ch.qos.logback.core.status.*;
  • import ch.qos.logback.classic.net.*;
  • import ch.qos.logback.classic.encoder.PatternLayoutEncoder;

另外,ch.qos.logback.classic.Level 中的所有常量 (大寫) 都會被靜態導入,以及小寫的別名。也就是說在你的腳本中可以引用 INFO 以及 info,而不需要使用靜態導入語句。

不再支持 SiftingAppender

1.0.12 版本以後 在 groovy 配置文件中不再支持 SiftingAppender。但是,如果有需要,可以重新引進。

logback.groovy 特定的拓展

本質上,logback.groovy 語法包含以下所說的六個方法;按照它們習慣上相反的順序出現。嚴格來說,這些方法的調用順序並重要,但是有一個例外:appender 附加到 logger 之前必須**被定義。

  • root(Level level, List<String> appenderNames = [])

root 方法可以用來設置 root logger 的日誌級別。第二個可選參數的類型爲 List<String>,可以用來添加之前定義的 appender 的名字。如果你不想指定 appenderNames,那麼就是一個空 (empty) 的列表。在 Groovy 中,用 [] 表示一個空的列表。

設置 root logger 的級別爲 WARN,你可以這樣寫:

 

root(WARN)

設置 root logger 的級別爲 INFO,並且將名爲 "CONSOLE" 與 "FILE" 的 appender 附加到 root 上,你可以這樣寫:

 

root(INFO, ["CONSOLE", "FILE"])

在前面的例子中,假設名爲 "CONSOLE" 與 "FILE" 的 appender 已經被定義好了。很快將會討論有關 appender 的定義。

  • logger(String name, Level level, List<String> appenderNames = [], Boolean additivity = null)

logger() 方法接收四個參數,最後兩個是可選的。第一個參數表示配置 logger 的名字。第二參數表示指定 logger 的級別。設置 logger 的級別爲 null 將強制它從它最近的祖先那裏繼承級別。第三個參數的類型爲 List<String>,是可選的,默認爲空列表。列表中 appender 會被附加到指定的 logger 上去。第四個參數的類型爲 Boolean,也是可選的,用來控制疊加性。如果忽略,默認值爲 null

例如,下面這個腳本設置 "com.foo" 這個 logger 的級別爲 INFO:

 

logger("com.foo", INFO)

下個腳本設置 "com.foo" 這個 logger 的級別爲 DEBUG,並且將名爲 "CONSOLE" 的 appender 附加到其上:

 

logger("com.foo", DEBUG, ["CONSOLE"])

下個腳本跟上一個類似,只是這個還設置了 "com.foo" 這個 logger 的疊加性爲 false:

 

logger("com.foo", DEBUG, ["CONSOLE"],false)
  • appender(String name, Class clazz, Closure closure = null)

appender 方法的第一個參數接收 appender 的名字進行配置。第二個參數是強制的,表示 appender 實例化的類。第三個參數包含更多的配置信息。如果忽略,默認爲 null。

大部分 appender 都需要設置屬性,並且注入子組件才能正常工作。屬性通過 '=' 進行設置。子組件的注入通過調用以屬性命名的方法,並且將實例化的類作爲參數傳遞給該方法。這個約定可以被遞歸的應用到配置的屬性以及任何 appender 子組件的子組件中。這個方法是 logback.groovy 的核心,可能是唯一需要去學習的約定。

例如,接下來的腳本實例化一個 FileAppender 命名爲 "FILE",設置它的 file 屬性爲 "testFile.log",以及它的 append 屬性設置爲 false。類型爲 PatternLayoutEncoder 的 encoder 被注入到這個 appender 中。encoder 的模式屬性設置爲 "%level %logger - %msg%n"。然後將這個 appender 附加到 root logger 上。

 

appender("FILE", FileAppender) {
    file = "testFile.log"
    append = false
    encoder(PatternLayoutEncoder) {
        pattern = "%level %logger - %msg%n"
    }
}

root(DEBUG, ["FILE"])
  • timestamp(String datePattern, long timeReference = -1)

timestamp() 方法根據 datePatterntimeReference 參數格式化,返回一個對應的字符串。datePattern 參數應該尊村 SimpleDateFormat 中定義的約定。如果 timeReference 沒有指定,那麼默認爲 -1。在這種情況下,當解析配置文件時,當前時間作爲 timeReference 參數的值。

在下個例子中,bySecond 變量表示被 "yyyyMMdd'T'HHmmss" 格式化之後的當前時間。之後,"bySecond" 變量被用於 file 屬性的定義中。

 

def bySecond = timestamp("yyyyMMdd'T'HHmmss")

appender("FILE", FileAppender) {
    file = "log-${bySecond}.txt"
    encoder(PatternLayoutEncoder) {
        pattern = "%logger{35} - %msg%n"
    }
}

root(DEBUG, ["FILE"])
  • conversionRule(String conversionWord, Class converterClass)

在創建了你自己的轉換說明符之後,你需要通知 logback 它的存在。下面這個簡單的 logback.groovy 文件告訴 logback 在遇到 %sample 轉換字符時使用 MySampleConverter。

 

import chapters.layouts.MySampleConverter

conversionRule("sample", MySampleConverter)
appender("STDOUT", ConsoleAppender) {
    encoder(PatternLayoutEncoder) {
        pattern = "%-4relative [%thread] %sample - %msg%n"
    }
}

root(DEBUG, ["STDOUT"])
  • scan(String scanPeriod = null)

調用 scan() 方法告訴 logback 週期性的掃描 logback.groovy 文件的變化。當檢測到變化時,logback.groovy 文件會被重新加載。

 

scan()

默認情況下,一分鐘掃描一次配置文件。你可以通過 "scanPeriod" 來指定一個不同的掃描週期。它的值可以被指定以 milliseconds, seconds, minutes 或者 hours 位單位。例如:

 

scan("30 seconds")

如果沒有指定時間單位,那麼默認的時間單位爲 milliseconds,但是通常來說是不合適的 (既然不合適,爲什麼默認還是毫秒,費解🤔)。如果你更改了默認的掃描週期,記得要指定時間單位。更多關於掃描工作的細節,請查看自動加載部分。

  • statusListener(Class listenerClass)

你可以通過調用 statusListener 方法,並給該方法傳遞一個監聽器類,來添加一個狀態監聽器。例:

 

import chapters.layouts.MySampleConverter

// 強烈建議在最後一個導入語句之後,其它所有語句之前添加狀態監聽器
statusListener(OnConsoleStatusListener)

關於狀態監聽器請查看之前的章節。

  • jmxConfigurator(String name)

你可以通過該方法註冊一個 JMXConfigurator MBean。無參調用將會使用 logback 默認的對象名 (ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator) 去註冊 MBean。

 

jmxConfigurator()

要改變 Name 鍵的值,而不是 "default",僅僅只需要給 jmxConfigurator 方法傳遞一個不同的名字參數就可以了。

 

jmxConfigurator('MyName')

如果你想要完整的定義對象名,可以使用同樣的語法,但是需要傳遞一個有效的對象名字符串作爲參數:

 

jmxConfigurator('myApp:type=LoggerManager')

該方法首先會去嘗試將該參數作爲對象名,如果它不表示一個有效的對象名,則會把它當作 "Name" 鍵的值。

內置 DSL

logback.groovy 是一個內置 DSL 的意思是,它的內容可以作爲 Groovy 腳本執行。因此,所有常用的 Groovy 指令,例如類的導入,GString,變量的定義,包含字符串 (GString) 的 ${..} 評估表達式,if-else 語句這些在 logback.groovy 文件中都是可用的。在接下來的討論中,我們將會展示 Groovy 指令在 logback.groovy 文件中的典型用法。

變量定義與 GString

你可以在 logback.groovy 文件中的任何地方定義變量,然後在 GString 中使用該變量。例如:

 

def USER_HOME = System.getProperty("user.home")

appender("FILE", FileAppender) {
    // 使用 USER_HOME 變量
    file = "${USER_HOME}/myApp.log"
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
root(DEBUG, ["FILE"])

在控制檯打印

通過調用 Groovy 的 println 方法在控制檯進行打印。例如:

 

def USER_HOME = System.getProperty("user.home");
println "USER_HOME=${USER_HOME}"

appender("FILE", FileAppender) {
    println "Setting [file] property to [${USER_HOME}/myApp.log]"
    file = "${USER_HOME}/myApp.log"
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
root(DEBUG, ["FILE"])

自動輸出字段

'hostname' 變量

'hostname' 變量包含當前 host 的名字。但是由於作用域規則,所以作者不能完全解釋清楚 (😓)。'hostname' 變量只在最上層的作用域中有效,但是在內部的作用域中無效。下面的例子應該可以解釋這一點:

 

// 如果當前 host 的名字爲 x,那麼將會輸出 "hostname is x"
println "hostname is ${hostname}"

appender("STDOUT", ConsoleAppender) {
    // 將會輸出 "hostname is null"
    println "hostname is ${hostname}"
}

如果你想要在所有的作用域中使用 hostname 變量。那麼你需要定義一個變量,並將 'hostname' 的值賦給它。如下:

 

// 將 hostname 的值賦給 HOSTNAME
def HOSTNAME = hostname

// 如果當前 host 的名字爲 x,那麼將會輸出 "hostname is x"
println "hostname is ${HOSTNAME}"

appender("STDOUT", ConsoleAppender) {
    // 如果當前 host 的名字爲 x,那麼將會輸出 "hostname is x"
    println "hostname is ${HOSTNAME}"
}

任何對於當前上下文的引用都是上下文感知的

logback.groovy 腳本是在 ContextAware 對象的範圍內執行完成的。因此,在當前上下文的範圍內,你可以使用 'context',並且可以通過 addInfo()addWarn()、與 addError() 方法將狀態信息發送給上下文的 StatusManager

 

// 添加一個控制檯轉態監聽器總是沒錯的
statusListener(OnConsoleStatusListener)

// 設置上下文的名字爲 wombat
context.name = 'wombat'

// 添加一個關於上下文名字的狀態信息
addInfo("Context name has been set to ${context_name}")

def USER_HOME = System.getProperty("user.home")

// 添加關於 USRE_HOME 的狀態信息
addInfo("USER_HOME=${USER_HOME}")

appender("FILE", FileAppender) {
    addInfo("Setting [file] property to [${USER_NAME}/myApp.log]")
    file = "${USER_HOME}/myApp.log"
    encoder(PatternLayoutEncoder) {
        pattern = "%msg%n"
    }
}
root(DEBUG, ["FILE"])

條件配置

由於 Groovy 是一種完全成熟的編程語言,條件語句允許單一的 logback.groovy 文件用來適用不同的環境,例如開發,測試以及生產。

在下個腳本中,console appender 根據 host 來激活,而不是我們的生產環境 pixie 或 orion。rolling file appender 的輸出目錄也是根據 host 來確定。

 

statusListener(OnConsoleStatusListener)

def appenderList = ["ROLLING"]
def WEBAPP_DIR = "."
def consoleAppender = true;

// hostname 是否匹配 pixie 或 orion
if (hostname =~ /pixie|orion/) {
    WEBAPP_DIR = "/opt/myapp"
    consoleAppender = false
} else {
    appenderList.add("CONSOLE")
}

if (consoleAppender) {
    appender("CONSOLE", ConsoleAppender) {
        encoder(PatternLayoutEncoder) {
            pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
        }
    }
}

appender("ROLLING", RollingFileAppender) {
    encoder(PatternLayoutEncoder) {
        Pattern = "%d %level %thread %mdc %logger - %m%n"
    }
    rollingPolicy(TimeBasedRollingPolicy) {
        FileNamePattern = "${WEBAPP_DIR}/log/translator-%d{yyyy-MM}.zip"
    }
}

root(INFO, appenderList)



作者:ChinaXieShuai
鏈接:https://www.jianshu.com/p/902a9a2a2a49
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章