第十二章:Groovy 配置
領(lǐng)域特定語言或者 DSL 更加普遍。logback 基于 XML 的配置可以看做 DSL 的實例厂榛。由于 XML 的本質(zhì)粤策,基于 XML 的配置文件變得非常的啰嗦以及臃腫。另外湿故,logback 中的 Joran 有一個相對龐大的代碼蟆炊,用來專門處理基于 XML 的配置文件稽莉。Joran 支持一些非常好的特性,例如變量替換涩搓,條件處理污秆,以及動態(tài)擴展。但是昧甘,不但 Joran 非常復(fù)雜良拼,而且給用戶的體驗非常的不好,或者至少不直觀充边。
本章敘述基于 Groovy 的 DSL 致力于一致性庸推,直觀性,以及非常強大浇冰。任何你可以使用 XML 配置的文件贬媒,你都可以用更加簡短的符號使用 Groovy 來實現(xiàn)。
常規(guī)建議
一般來說肘习,logback.groovy 文件是 Groovy 程序际乘。因為 Groovy 是 Java 的超集,所以無論你在 Java 執(zhí)行什么配置操作漂佩,你都可以在 logback.groovy 文件中做同樣的事情脖含。但是,在 Java 中投蝉,使用變成的方式配置 logback 有點笨重养葵,所以我們增加了一些 logback 特有的擴展來減輕你的負(fù)擔(dān)。我們嘗試限制 logback 特有的拓展符號盡量的少瘩缆。如果你已經(jīng)熟悉了 Groovy关拒,那么你應(yīng)該更加容易去讀,去理解甚至去寫你自己的 logback.groovy 文件。那么不熟悉 Groovy 的人依然會發(fā)現(xiàn) logback.groovy 中的語法比 logback.xml 中的語法更加容易使用夏醉。
logback.groovy 文件是 Groovy 程序,具有最小的 logback 特定的拓展涌韩。所有常用的 groovy 結(jié)構(gòu)畔柔,例如類的導(dǎo)入,變量定義臣樱,字符串 (GString) 中包含 ${..} 評估表達式靶擦,以及 if-else 語句在 logback.grooby 文件中都是可用的。
自動導(dǎo)入
1.0.10 版本以后
為了減少不必要的引用雇毫,一些共同的類以及包會被自動導(dǎo)入玄捕。因此,只要你只是配置了內(nèi)置的 appender棚放,layout 等等枚粘,你不需要在你的腳本中添加相對應(yīng)的導(dǎo)入語句。當(dāng)然飘蚯,對于默認(rèn)導(dǎo)入不會涉及到類馍迄,你需要自己導(dǎo)入。
下面是默認(rèn)導(dǎo)入的列表:
- 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
中的所有常量 (大寫) 都會被靜態(tài)導(dǎo)入,以及小寫的別名峦甩。也就是說在你的腳本中可以引用 INFO 以及 info赘来,而不需要使用靜態(tài)導(dǎo)入語句。
不再支持 SiftingAppender
1.0.12 版本以后
在 groovy 配置文件中不再支持 SiftingAppender
凯傲。但是犬辰,如果有需要,可以重新引進泣洞。
logback.groovy 特定的拓展
本質(zhì)上忧风,logback.groovy 語法包含以下所說的六個方法;按照它們習(xí)慣上相反的順序出現(xiàn)球凰。嚴(yán)格來說狮腿,這些方法的調(diào)用順序并不重要,但是有一個例外:appender 附加到 logger 之前必須**被定義呕诉。
-
root(Level level, List<String> appenderNames = [])
root
方法可以用來設(shè)置 root logger 的日志級別缘厢。第二個可選參數(shù)的類型為 List<String>
,可以用來添加之前定義的 appender 的名字甩挫。如果你不想指定 appenderNames贴硫,那么就是一個空 (empty) 的列表。在 Groovy 中,用 []
表示一個空的列表英遭。
設(shè)置 root logger 的級別為 WARN间护,你可以這樣寫:
root(WARN)
設(shè)置 root logger 的級別為 INFO,并且將名為 "CONSOLE" 與 "FILE" 的 appender 附加到 root 上挖诸,你可以這樣寫:
root(INFO, ["CONSOLE", "FILE"])
在前面的例子中汁尺,假設(shè)名為 "CONSOLE" 與 "FILE" 的 appender 已經(jīng)被定義好了。很快將會討論有關(guān) appender 的定義多律。
-
logger(String name, Level level, List<String> appenderNames = [], Boolean additivity = null)
logger()
方法接收四個參數(shù)痴突,最后兩個是可選的。第一個參數(shù)表示配置 logger 的名字狼荞。第二參數(shù)表示指定 logger 的級別辽装。設(shè)置 logger 的級別為 null
將強制它從它最近的祖先那里繼承級別。第三個參數(shù)的類型為 List<String>
相味,是可選的拾积,默認(rèn)為空列表。列表中 appender 會被附加到指定的 logger 上去攻走。第四個參數(shù)的類型為 Boolean
殷勘,也是可選的,用來控制疊加性昔搂。如果忽略玲销,默認(rèn)值為 null
。
例如摘符,下面這個腳本設(shè)置 "com.foo" 這個 logger 的級別為 INFO:
logger("com.foo", INFO)
下個腳本設(shè)置 "com.foo" 這個 logger 的級別為 DEBUG贤斜,并且將名為 "CONSOLE" 的 appender 附加到其上:
logger("com.foo", DEBUG, ["CONSOLE"])
下個腳本跟上一個類似,只是這個還設(shè)置了 "com.foo" 這個 logger 的疊加性為 false:
logger("com.foo", DEBUG, ["CONSOLE"]逛裤,false)
-
appender(String name, Class clazz, Closure closure = null)
appender 方法的第一個參數(shù)接收 appender 的名字進行配置瘩绒。第二個參數(shù)是強制的,表示 appender 實例化的類带族。第三個參數(shù)包含更多的配置信息锁荔。如果忽略,默認(rèn)為 null蝙砌。
大部分 appender 都需要設(shè)置屬性阳堕,并且注入子組件才能正常工作。屬性通過 '=' 進行設(shè)置择克。子組件的注入通過調(diào)用以屬性命名的方法恬总,并且將實例化的類作為參數(shù)傳遞給該方法。這個約定可以被遞歸的應(yīng)用到配置的屬性以及任何 appender 子組件的子組件中肚邢。這個方法是 logback.groovy 的核心壹堰,可能是唯一需要去學(xué)習(xí)的約定拭卿。
例如,接下來的腳本實例化一個 FileAppender
命名為 "FILE"贱纠,設(shè)置它的 file
屬性為 "testFile.log"峻厚,以及它的 append
屬性設(shè)置為 false。類型為 PatternLayoutEncoder
的 encoder 被注入到這個 appender 中谆焊。encoder 的模式屬性設(shè)置為 "%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()
方法根據(jù) datePattern
將 timeReference
參數(shù)格式化懊渡,返回一個對應(yīng)的字符串。datePattern
參數(shù)應(yīng)該尊村SimpleDateFormat中定義的約定军拟。如果 timeReference
沒有指定剃执,那么默認(rèn)為 -1。在這種情況下懈息,當(dāng)解析配置文件時肾档,當(dāng)前時間作為 timeReference
參數(shù)的值。
在下個例子中辫继,bySecond
變量表示被 "yyyyMMdd'T'HHmmss" 格式化之后的當(dāng)前時間怒见。之后,"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)
在創(chuàng)建了你自己的轉(zhuǎn)換說明符之后遣耍,你需要通知 logback 它的存在。下面這個簡單的 logback.groovy 文件告訴 logback 在遇到 %sample
轉(zhuǎn)換字符時使用 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)
調(diào)用 scan() 方法告訴 logback 周期性的掃描 logback.groovy 文件的變化舵变。當(dāng)檢測到變化時,logback.groovy 文件會被重新加載瘦穆。
scan()
默認(rèn)情況下纪隙,一分鐘掃描一次配置文件。你可以通過 "scanPeriod" 來指定一個不同的掃描周期扛或。它的值可以被指定以 milliseconds, seconds, minutes 或者 hours 位單位绵咱。例如:
scan("30 seconds")
如果沒有指定時間單位,那么默認(rèn)的時間單位為 milliseconds熙兔,但是通常來說是不合適的 (既然不合適悲伶,為什么默認(rèn)還是毫秒,費解??)黔姜。如果你更改了默認(rèn)的掃描周期拢切,記得要指定時間單位。更多關(guān)于掃描工作的細(xì)節(jié)秆吵,請查看自動加載部分淮椰。
-
statusListener(Class listenerClass)
你可以通過調(diào)用 statusListener
方法,并給該方法傳遞一個監(jiān)聽器類,來添加一個狀態(tài)監(jiān)聽器主穗。例:
import chapters.layouts.MySampleConverter
// 強烈建議在最后一個導(dǎo)入語句之后泻拦,其它所有語句之前添加狀態(tài)監(jiān)聽器
statusListener(OnConsoleStatusListener)
關(guān)于狀態(tài)監(jiān)聽器請查看之前的章節(jié)。
-
jmxConfigurator(String name)
你可以通過該方法注冊一個 JMXConfigurator
MBean忽媒。無參調(diào)用將會使用 logback 默認(rèn)的對象名 (ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator
) 去注冊 MBean争拐。
jmxConfigurator()
要改變 Name
鍵的值,而不是 "default"晦雨,僅僅只需要給 jmxConfigurator
方法傳遞一個不同的名字參數(shù)就可以了架曹。
jmxConfigurator('MyName')
如果你想要完整的定義對象名,可以使用同樣的語法闹瞧,但是需要傳遞一個有效的對象名字符串作為參數(shù):
jmxConfigurator('myApp:type=LoggerManager')
該方法首先會去嘗試將該參數(shù)作為對象名绑雄,如果它不表示一個有效的對象名,則會把它當(dāng)作 "Name" 鍵的值奥邮。
內(nèi)置 DSL
logback.groovy 是一個內(nèi)置 DSL 的意思是万牺,它的內(nèi)容可以作為 Groovy 腳本執(zhí)行。因此洽腺,所有常用的 Groovy 指令脚粟,例如類的導(dǎo)入,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"])
在控制臺打印
通過調(diào)用 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' 變量包含當(dāng)前 host 的名字霹购。但是由于作用域規(guī)則佑惠,所以作者不能完全解釋清楚 (??)。'hostname' 變量只在最上層的作用域中有效齐疙,但是在內(nèi)部的作用域中無效膜楷。下面的例子應(yīng)該可以解釋這一點:
// 如果當(dāng)前 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
// 如果當(dāng)前 host 的名字為 x轿塔,那么將會輸出 "hostname is x"
println "hostname is ${HOSTNAME}"
appender("STDOUT", ConsoleAppender) {
// 如果當(dāng)前 host 的名字為 x特愿,那么將會輸出 "hostname is x"
println "hostname is ${HOSTNAME}"
}
任何對于當(dāng)前上下文的引用都是上下文感知的
logback.groovy 腳本是在 ContextAware 對象的范圍內(nèi)執(zhí)行完成的仲墨。因此,在當(dāng)前上下文的范圍內(nèi)揍障,你可以使用 'context
'目养,并且可以通過 addInfo()
、addWarn()
毒嫡、與 addError()
方法將狀態(tài)信息發(fā)送給上下文的 StatusManager
癌蚁。
// 添加一個控制臺轉(zhuǎn)態(tài)監(jiān)聽器總是沒錯的
statusListener(OnConsoleStatusListener)
// 設(shè)置上下文的名字為 wombat
context.name = 'wombat'
// 添加一個關(guān)于上下文名字的狀態(tài)信息
addInfo("Context name has been set to ${context_name}")
def USER_HOME = System.getProperty("user.home")
// 添加關(guān)于 USRE_HOME 的狀態(tài)信息
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 文件用來適用不同的環(huán)境兜畸,例如開發(fā)努释,測試以及生產(chǎn)。
在下個腳本中咬摇,console appender 根據(jù) host 來激活洽洁,而不是我們的生產(chǎn)環(huán)境 pixie 或 orion。rolling file appender 的輸出目錄也是根據(jù) 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)