logback核心组件
- Logger:
- Appender
- Layout
Logger
package org.slf4j;
public interface Logger {
// Printing methods:
public void trace(String message);
public void debug(String message);
public void info(String message);
public void warn(String message);
public void error(String message);
}
Logger的level继承逻辑
每个logger肯定都会对应一个level,如果自己没有level则继承父级的level,根Logger的名字为Root,Root默认的level为DEBUG.
Logger的appender继承逻辑
logger会继承所有父类的appender
logback执行流程
- 首先检查是否配置了TurboFilters, turboFilter会在loggingEvent创建之前就执行。
- 检查此Logger是否有可以处理此level的logging,如果可以则创建loggingEvent
- Logger遍历自己的Appenders,每个appender调用自己的layout或者encode格式化输出
配置
开启调试信息
debug=true,会打印logback的启动信息,前提是配置文件语法正确并能被加载。
<configuration debug="true">
...
</configuration>
动态更新
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
配置例子
<configuration>
<!--设置logcontext的name,当多个应用的日志打印到同一个文件时,可以区分不同的应用 -->
<contextName>myAppName</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--自定义变量,可以引用系统变量,文件,类 -->
<property resource="resource1.properties" />
<property file="src/main/java/chapters/configuration/variables1.properties" />
<property name="USER_HOME" value="/home/sebastien" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<!--additivity = false表示此logger只会执行自己的appender,不会继承父类的appender -->
<logger name="chapters.configuration.Foo" additivity="false">
<appender-ref ref="FILE" />
</logger>
<root level="debug">
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
自定义变量的scope
<property scope="context" name="nodeId" value="firstNode" />
scope分为三种local, context, system,默认为local,只能在配置文件中使用,context的范围是logcontext,system会把变量设置到jvm的系统变量中。
给变量默认值语法
“${aName:-golden}”
不同环境使用不同配置
<!-- 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>
<!-- 被包含的文件内容也要包含在include标签中才行-->
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://some.host.com/includedConfig.xml"/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
appender
RollingFileAppender
RollingPolicy
这个类负责日志文件的移动和重命名
TimeBasedRollingPolicy
这个类同时实现了 RollingPolicy 和 TriggeringPolicy ,负责触发日志文件并且转移和重命名日志文件。
fileNamePattern的写法
日志文件支持自动压缩
例子
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logFile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- keep 30 days' worth of history capped at 3GB total size -->
<!--maxHistory日志文件的最大数量 -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
SizeAndTimeBasedRollingPolicy
这个类可以控制每个日志文件的大小
注意%i和%d都是必须的,i从0开始,每生成一个文件就递增。
<configuration>
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!-- each file should be at most 100MB, keep 60 days worth of history, but at most 20GB -->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="ROLLING" />
</root>
</configuration>
其它的appender
SocketAppender and SSLSocketAppender
The appenders covered thus far are only able to log to local resources. In contrast, the SocketAppender is designed to log to a remote entity by transmitting serialized ILoggingEvent instances over the wire. When using SocketAppender logging events on the wire are sent in the clear. However, when using SSLSocketAppender, logging events are delivered over a secure channel.
ServerSocketAppender and SSLServerSocketAppender
The SocketAppender component (and its SSL-enabled counterpart) discussed previously are designed to allow an application to connect to a remote logging server over the network for the purpose of delivering logging events to the server. In some situations, it may be inconvenient or infeasible to have an application initiate a connection to a remote logging server. For these situations, Logback offers ServerSocketAppender.
Instead of initiating a connection to a remote logging server, ServerSocketAppender passively listens on a TCP socket awaiting incoming connections from remote clients. Logging events that are delivered to the appender are distributed to each connected client. Logging events that occur when no client is connected are summarily discarded.
In addition to the basic ServerSocketAppender, Logback offers SSLServerSocketAppender, which distributes logging events to each connected client using a secure, encrypted channel. Moreover, the SSL-enabled appender fully supports mutual certificate-based authentication, which can be used to ensure that only authorized clients can connect to the appender to receive logging events.
SMTPAppender
DBAppender
SyslogAppender
SiftingAppender
AsyncAppender
编写自己的appender
继承AppenderBase并且覆写append方法
package chapters.appenders;
import java.io.IOException;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
public class CountingConsoleAppender extends AppenderBase<ILoggingEvent> {
static int DEFAULT_LIMIT = 10;
//定义的属性只要有setter方法,就能在配置文件中设置,会在logback启动时被读取
int counter = 0;
int limit = DEFAULT_LIMIT;
PatternLayoutEncoder encoder;
public void setLimit(int limit) {
this.limit = limit;
}
public int getLimit() {
return limit;
}
//start方法会在logback启动时自动执行
@Override
public void start() {
if (this.encoder == null) {
addError("No encoder set for the appender named ["+ name +"].");
return;
}
try {
encoder.init(System.out);
} catch (IOException e) {
}
super.start();
}
public void append(ILoggingEvent event) {
if (counter >= limit) {
return;
}
// output the events as formatted by our layout
try {
this.encoder.doEncode(event);
} catch (IOException e) {
}
// prepare for next event
counter++;
}
public PatternLayoutEncoder getEncoder() {
return encoder;
}
public void setEncoder(PatternLayoutEncoder encoder) {
this.encoder = encoder;
}
}
Encoder
0.9.19版本之前都是使用layout转换logevent为字符串并且输出到目标文件,此版本后都是使用encoder.
layout不能控制字节写入到流的过程,而enocder可以控制什么时候把内容写入到流,可以批量处理logenvents.
LayoutWrappingEncoder
因为之前有很多人在使用0.9.19版本,很多使用layout,所以用这个类来包装layout,提供encoder和layout之间的桥梁。
package ch.qos.logback.core.encoder;
public class LayoutWrappingEncoder<E> extends EncoderBase<E> {
protected Layout<E> layout;
private Charset charset;
// encode a given event as a byte[]
public byte[] encode(E event) {
String txt = layout.doLayout(event);
return convertToBytes(txt);
}
private byte[] convertToBytes(String s) {
if (charset == null) {
return s.getBytes();
} else {
return s.getBytes(charset);
}
}
PatternLayoutEncoder
由于以前PatternLayout是最常使用的layout,为了兼容这种layout,所以推出了这个encoder.0.9.19版本版本之后,如果使用PatternLayout会报错,必须使用PatternLayoutEncoder才可以。
layout
layout的作用是把logevent格式化成字符串。
编写自己的layout
package chapters.layouts;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
public class MySampleLayout2 extends LayoutBase<ILoggingEvent> {
//属性只要有set方法就可以在配置文件中设置
String prefix = null;
boolean printThreadName = true;
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public void setPrintThreadName(boolean printThreadName) {
this.printThreadName = printThreadName;
}
public String doLayout(ILoggingEvent event) {
StringBuffer sbuf = new StringBuffer(128);
if (prefix != null) {
sbuf.append(prefix + ": ");
}
sbuf.append(event.getTimeStamp() - event.getLoggerContextVO().getBirthTime());
sbuf.append(" ");
sbuf.append(event.getLevel());
if (printThreadName) {
sbuf.append(" [");
sbuf.append(event.getThreadName());
sbuf.append("] ");
} else {
sbuf.append(" ");
}
sbuf.append(event.getLoggerName());
sbuf.append(" - ");
sbuf.append(event.getFormattedMessage());
sbuf.append(LINE_SEP);
return sbuf.toString();
}
}
配置自己的layout
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="chapters.layouts.MySampleLayout2">
<prefix>MyPrefix</prefix>
<printThreadName>false</printThreadName>
</layout>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
PatternLayout
新的版本无法直接使用这个layout, 只能使用PatternLayoutEncoder, pattern属性用来表示转换表达式,表达式写法参考官方文档。
package chapters.layouts;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
public class PatternSample {
static public void main(String[] args) throws Exception {
Logger rootLogger = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
LoggerContext loggerContext = rootLogger.getLoggerContext();
// we are not interested in auto-configuration
loggerContext.reset();
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
encoder.setPattern("%-5level [%thread]: %message%n");
encoder.start();
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
appender.setContext(loggerContext);
appender.setEncoder(encoder);
appender.start();
rootLogger.addAppender(appender);
rootLogger.debug("Message 1");
rootLogger.warn("Message 2");
}
}
HTMLLayout
Filter
filter包含regular filters和turbo filters. regular filters在logevent生成之后才处理,turbo filters生成logevent之前就会执行。
实现自己的filter
FilterRepl是一个枚举,有三个值 DENY, NEUTRAL or ACCEPT.
DENY:删除logevent
NEUTRAL: 继续执行后面的filter.
ACCEPT:直接处理logevent,不需要执行后面的filter
package chapters.filters;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class SampleFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("sample")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.NEUTRAL;
}
}
}
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="chapters.filters.SampleFilter" />
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>
LevelFilter
匹配具体的log level, 当匹配或者不匹配时怎么处理,根据onMatch和onMismatch属性处理。
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
ThresholdFilter
当处理的log level大于或等于配置的leve时,decide()方法返回 NEUTRAL
<configuration>
<appender name="CONSOLE"
class="ch.qos.logback.core.ConsoleAppender">
<!-- deny all events with a level below INFO, that is TRACE and DEBUG -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
EvaluatorFilter
这个filter是用来判断logevent是否符合设置的表达式,判断会更加灵活。EvaluatorFilter是一个抽象基类。
GEventEvaluator
表达式是使用groovy语言编写的
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
<expression>
e.level.toInt() >= WARN.toInt() && <!-- Stands for && in XML -->
!(e.mdc?.get("req.userAgent") =~ /Googlebot|msnbot|Yahoo/ )
</expression>
</evaluator>
<OnMismatch>DENY</OnMismatch>
<OnMatch>NEUTRAL</OnMatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
JaninoEventEvaluator
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
<expression>return message.contains("billing");</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
表达式可以使用java语言编写
<evaluator>
<expression>
if(logger.startsWith("org.apache.http"))
return true;
if(mdc == null || mdc.get("entity") == null)
return false;
String payee = (String) mdc.get("entity");
if(logger.equals("org.apache.http.wire") && <!-- & encoded as & -->
payee.contains("someSpecialValue") &&
!message.contains("someSecret")) {
return true;
}
return false;
</expression>
</evaluator>
TurboFilters
public class SampleTurboFilter extends TurboFilter {
String marker;
Marker markerToAccept;
@Override
public FilterReply decide(Marker marker, Logger logger, Level level,
String format, Object[] params, Throwable t) {
if (!isStarted()) {
return FilterReply.NEUTRAL;
}
if ((markerToAccept.equals(marker))) {
return FilterReply.ACCEPT;
} else {
return FilterReply.NEUTRAL;
}
}
public String getMarker() {
return marker;
}
public void setMarker(String markerStr) {
this.marker = markerStr;
}
@Override
public void start() {
if (marker != null && marker.trim().length() > 0) {
markerToAccept = MarkerFactory.getMarker(marker);
super.start();
}
}
}
<configuration>
<turboFilter class="chapters.filters.SampleTurboFilter">
<Marker>sample</Marker>
</turboFilter>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>
内置turboFilter
- MDCFilter checks the presence of a given value in the MDC
- DynamicThresholdFilter allows filtering based on MDC key/level threshold associations.
- MarkerFilter checks for the presence of a specific marker associated with the logging request.
- DuplicateMessageFilter 去重的filter
<configuration>
<turboFilter class="ch.qos.logback.classic.turbo.MDCFilter">
<MDCKey>username</MDCKey>
<Value>sebastien</Value>
<OnMatch>ACCEPT</OnMatch>
</turboFilter>
<turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
<Marker>billing</Marker>
<OnMatch>DENY</OnMatch>
</turboFilter>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console" />
</root>