1. 配置文件的基本结构
1.1. Logback配置文件的语法非常灵活。正因为灵活, 所以无法用DTD或XML schema进行定义。尽管如此, 可以这样描述配置文件的基本结构: 以<configuration>开头, 后面有零个或多个<appender>元素, 有零个或多个<logger>元素, 有最多一个<root>元素。
2. 配置logger或<logger>元素
2.1. Logger是用<logger>元素配置的。<logger>元素有且仅有一个name属性、一个可选的level属性和一个可选的additivity属性。Level属性的值大小写无关, 其值为: TRACE、DEBUG、INFO、 WARN、ERROR、ALL和OFF中的一个值。还可以是一个特殊的字符串"INHERITED"或其同义词"NULL", 表示强制继承上级的级别。
2.2. <logger>元素可以包含零个或多个<appender-ref>元素, 表示这个appender会被添加到该logger。强调一下, 每个用<logger>元素声明的logger, 首先会移除所有的appender, 然后添加引用的appender, 所以如果logger没有引用任何appender, 就会失去所有appender。
2.3. 例子
2.3.1. 新建一个名为LogbackCfgGrammer的Java项目, 同时添加相关jar包
2.3.2. 编辑LoggerCfg.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class LoggerCfg {
private static final Logger logger = LoggerFactory.getLogger(LoggerCfg.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/logger.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.info("配置logger或<logger>元素");
logger.debug("配置logger或<logger>元素");
}
}
2.3.3. 新建cfg文件夹, 在该文件夹下编辑logger.xml
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.zr.cfg.LoggerCfg" level="info">
<appender-ref ref="stdout" />
</logger>
</configuration>
2.3.4. 只会打印info和高于info级别的日志
3. 配置根logger或<root>元素
3.1. <root>元素配置根logger。该元素有一个level属性。没有name属性, 因为已经被命名为"ROOT"。Level属性的值大小写无关, 其值为: TRACE、DEBUG、INFO、 WARN、ERROR、ALL和OFF中的一个值。注意不能设置为"INHERITED"或"NULL"。
3.2. <logger>元素可以包含零个或多个<appender-ref>元素。与<logger>元素类似, 声明<root>元素后, 会先关闭然后移除全部当前appender, 只引用声明的appender。如果root元素没有引用任何appender, 就会失去所有appender。
3.3. 基本选择规则依赖于被调用的logger的有效级别, 而不是appender所关联的logger的级别。Logback首先判断记录语句是否被启用, 如果启用, 则调用logger等级里的appender, 同时如果有继承的话, 还会调用祖先的appender, 无视logger的级别。
3.4. 例子
3.4.1. 编辑RootLoggerCfg.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class RootLoggerCfg {
private static final Logger logger = LoggerFactory.getLogger(RootLoggerCfg.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/rootlogger.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.info("配置根logger或<root>元素");
logger.debug("配置根logger或<root>元素");
}
}
3.4.2. 在cfg文件夹下编辑rootlogger.xml
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.zr.cfg.RootLoggerCfg" level="info"></logger>
<root level="off">
<appender-ref ref="stdout" />
</root>
</configuration>
3.4.3. 各个logger的级别
3.4.4. 配置里唯一的appender"stdout", 被关联到级别为OFF的根logger, 根logger的级别不起任何作用。com.zr.cfg.RootLoggerCfg类的INFO级别是启用的, 因此会输出com.zr.cfg.RootLoggerCfg类INFO级别及其更高级别的日志:
4. 配置Appender
4.1. Appender用<appender>元素配置, 该元素必要属性name和class。name属性指定appender的名称, class属性指定appender类的全限定名。
4.2. <appender>元素可以包含零个或一个<layout>元素或者零个或一个<encoder>元素和零个或多个<filter>元素。除了这三个常用元素之外, 还可以包含任意数量的javabean属性。
4.3. 下图演示了常用结构, 注意对javabean属性的支持在图中不可见。
4.4. <layout>元素的class属性是必要的, 表示将被实例化的layout类的全限定名。因为太常用了, 所以当layout是PatternLayout 时, 可以省略class属性。
4.5. <encoder>元素class属性是必要的, 表示将被实例化的encoder类的全限定名。因为太常用了, 所以当encoder是PatternLayoutEncoder时, 可以省略class属性。
4.6. 记录输出到多个appender很简单, 先定义各种appender, 然后在logger里进行引用就行了。
4.7. 注意每个appender都有自己的encoder。Encoder通常不能被多个appender共享, layout也是。
4.8. 例子
4.8.1. 编辑AppenderCfg.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class AppenderCfg {
private static final Logger logger = LoggerFactory.getLogger(AppenderCfg.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/appender.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.info("Appender用<appender>元素配置, 该元素必要属性name和class。");
logger.debug("name属性指定appender的名称, class属性指定appender类的全限定名。");
}
}
4.8.2. 在cfg文件夹下编辑appender.xml
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>log/my.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="file" />
<appender-ref ref="stdout" />
</root>
</configuration>
4.8.3. 运行结果
4.9. Appender累积
4.9.1. 默认情况下, appender是可累积的: logger会把记录输出到它自身的appender和它所有祖先的appender。因此, 把同一appender关联到多个logger会导致重复输出。
4.9.2. Appender的叠加性对新手来说并不是陷阱, 反而是非常方便的。举例来说, 你可以让某些系统里所有logger的记录信息出现在控制台, 却让某些特定logger的记录信息发到一个特定的appender。
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>log/error.log</file>
<append>false</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.zr.cfg.AccumulateCfg" level="error">
<appender-ref ref="file" />
</logger>
<root>
<appender-ref ref="stdout" />
</root>
</configuration>
4.9.3. 如果你觉得默认的累积行为不合适, 可以设置叠加性标识为false以关闭它。这样的话, logger树里的某个分支可以输出到与其他logger不同的appender。
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<appender name="file" class="ch.qos.logback.core.FileAppender">
<file>log/error.log</file>
<append>false</append>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.zr.cfg.AccumulateCfg" level="error" additivity="false">
<appender-ref ref="file" />
</logger>
<root>
<appender-ref ref="stdout" />
</root>
</configuration>
4.9.4. 创建AccumulateCfg.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class AccumulateCfg {
private static final Logger logger = LoggerFactory.getLogger(AccumulateCfg.class);
private static final Logger root = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/accumulate.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.error("默认情况下, appender是可累积的: logger会把记录输出到它自身的appender和它所有祖先的appender。");
logger.error("因此, 把同一appender关联到多个logger会导致重复输出。");
logger.error("Appender的叠加性对新手来说并不是陷阱, 反而是非常方便的。");
root.warn("举例来说, 你可以让某些系统里所有logger的记录信息出现在控制台, 却让某些特定logger的记录信息发到一个特定的appender。");
root.info("如果你觉得默认的累积行为不合适, 可以设置叠加性标识为false以关闭它。");
root.debug("这样的话, logger树里的某个分支可以输出到与其他logger不同的appender。");
}
}
4.9.5. 创建accumulate.xml, 使用步骤4.9.2.的配置文件
4.9.6. 运行AccumulateCfg.java
4.9.7. 修改accumulate.xml配置, 使用步骤4.9.3.的配置文件, 再次运行AccumulateCfg.java
5. 设置上下文名称
5.1. 每个logger都关联到logger上下文。默认情况下, logger上下文名为"default"。但是你可以借助配置指令<contextName>设置成其他名字。注意一旦设置logger上下文名称后, 不能再改。设置上下文名称后, 可以方便地区分来自不同应用程序的记录。
5.2. 例子
5.2.1. 编辑ContextName.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class ContextName {
private static final Logger logger = LoggerFactory.getLogger(ContextName.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/contextName.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.info("每个logger都关联到logger上下文。");
logger.debug("你可以借助配置指令<contextName>设置上下文名字。");
}
}
5.2.2. 在cfg文件夹下编辑contextName.xml
<configuration debug="true">
<contextName>日志服务器</contextName>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="stdout" />
</root>
</configuration>
5.2.3. 运行结果
6. 变量替换
6.1. 原则上, 指定变量的地方就能够发生变量替换。变量替换的语法与Unix shell中的变量替换相似。位于"${"与"}"之间的字符串是键(key), 取代键的值可以在同一配置文件里指定, 也可以在外部文件或通过系统属性进行指定。
6.2. 属性被插入logger上下文
6.2.1. 注意通过<property>元素定义的值实际上会被插入logger上下文。换句话说, 这些值变成了logger上下文的属性。所以, 它们对所有记录事件都可用, 包括通过序列化方式被发送到远程主机的记录事件。
6.2.2. 下面的例子在配置文件的开头声明了一个变量又名替换属性, 它代表输出文件的位置, 然后在后面的配置文件里使用它。
<property name="fileName" value="my.log" />
6.3. 属性文件
6.3.1. 当需要很多变量时, 更方便的做法是在一个单独的文件里声明所有变量。
<property file="variables.properties" />
6.3.2. 还可以不引用文件, 而是引用class path上的资源。
<property resource="variables.properties" />
6.3.3. variables.properties文件内容类似于:
pathPre=/log
fileName=my.log
fullPath=/log/my.log
6.4. 嵌套变量替换
6.4.1. Logback支持嵌套变量替换。这里的嵌套是指变量的值里包含对其他变量的引用。
pathPre=/log
fileName=my.log
fullPath=${pathPre}/${fileName}
6.5. 变量的默认替换值
6.5.1. 在某些特定情况下, 最好给变量一个默认值, 以免变量未被声明或值为null。Bash shell用":-"指定默认值。例如, 假设"appName"未被声明, 那么"${appName:-zrApp}"将被解释为"zrApp"。
6.6. Logback自动定义了一个常用变量"${HOSTNAME}"。
7. 配置文件里的条件化处理
7.1. 开发者经常需要针对不同的环境在不同的配置文件里换来换去, 比如开发、测试和生产环境。这些配置文件大同小异。为避免重复劳动, logback支持在配置文件里进行条件化处理, 用<if>、<then>和<else>这些元素可以让一个配置文件适用于多个环境。
7.2. 条件语句一般格式如下
<configuration>
<!-- if-then form -->
<if condition="some conditional expression">
<then> ... </then>
</if>
<!-- if-then-else form -->
<if condition="some conditional expression">
<then> ... </then>
<else> ... </else>
</if>
</configuration
7.3. 其中"condition"是java表达式, 只允许访问上下文属性和系统属性。对于作为参数传入的键, property()方法或其等价的p()方法将返回属性的字符串值。例如, 想访问属性键为"k"的值, 你可以用property("k")或等价的 p("k")。如果键为"k"的属性未被定义, property方法将返回空字符串而不是null, 这样避免了检查null值。
7.4. 例子
7.4.1. 条件语句需要两个额外的包commons-compiler-3.1.3.jar和janino-3.1.3.jar
7.4.2. 编辑Condition.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class Condition {
private static final Logger logger = LoggerFactory.getLogger(Condition.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/condition.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.error("错误信息");
logger.warn("警告信息");
logger.info("信息");
logger.debug("测试信息");
}
}
7.4.3. 在cfg文件夹下编辑condition.xml
<configuration>
<property name="development" value="dev" />
<contextName>${pathPre:-zrApp}</contextName>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<if condition='p("development").contains("test")'>
<then>
<root level="debug">
<appender-ref ref="stdout" />
</root>
</then>
<else>
<root level="warn">
<appender-ref ref="stdout" />
</root>
</else>
</if>
</configuration>
7.4.4. 运行结果
8. 从JNDI获取变量
8.1. 在某些特定情况下, 你也许利用JNDI里存储的env项, <insertFromJNDI>指令会从JNDI里取得env项, 然后用as属性把它们作为变量。
8.2. 新建一个名为JNDIGetVariable的动态Web工程, 同时添加相关jar包
8.3. 创建JNDIAction.java
package com.fj;
import java.io.IOException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JNDIAction extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(JNDIAction.class);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
InitialContext ctx = new InitialContext();
String contextName = (String) ctx.lookup("java:comp/env/logback/context-name");
logger.error("从JNDI获取变量: {}", contextName);
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
8.4. 在src目录下添加logback.xml
<configuration>
<insertFromJNDI env-entry-name="java:comp/env/logback/context-name" as="context-name" />
<contextName>${context-name}</contextName>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%contextName %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="stdout" />
</root>
</configuration>
8.5. 配置web.xml
8.6. 运行项目
9. 文件包含
9.1. Joran支持在配置文件里包含其他文件。方法是声明<include>元素, 被包含的内容可以是文件、资源或URL。
9.2. 作为文件, 用"file"属性包含一个文件。可以用相对路径, 但是需要注意, 当前目录是由应用程序决定的, 与配置文件的路径必要的联系。
<include file="includedConfig.xml" />
9.3. 作为资源, 用"resource"属性包含一个资源, 也就是在class path上的文件。
<include resource="includedConfig.xml" />
9.4. 作为URL, 用"url"属性包括一个URL。
<include url="http://some.host.com/includedConfig.xml" />
9.5. 被包含的文件必须把它的元素嵌套在<included>元素里。请注意<included>元素是必需的。
9.6. 例子
9.6.1. 编辑IncludeCfg.java
package com.zr.cfg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
public class IncludeCfg {
private static final Logger logger = LoggerFactory.getLogger(IncludeCfg.class);
public static void main(String[] args) {
// 上下文
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
// Joran配置文件处理类
JoranConfigurator cfg = new JoranConfigurator();
cfg.setContext(lc);
// 上下文已经读取配置文件或使用默认配置文件, 这里进行重置操作。
lc.reset();
try {
cfg.doConfigure("cfg/containingcfg.xml");
} catch (JoranException e) {
e.printStackTrace();
}
logger.error("错误信息");
logger.warn("警告信息");
logger.info("信息");
logger.debug("测试信息");
}
}
9.6.2. 在cfg文件夹下编辑containingcfg.xml
<included>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
</included>
9.6.3. 在cfg文件夹下编辑includecfg.xml
<configuration>
<include file="cfg/includecfg.xml"></include>
<root>
<appender-ref ref="stdout" />
</root>
</configuration>
9.6.4. 运行结果