目录
Layout是什么
Layout在logback中就是一个负责将Event
转化为String
的组件,就是这个纯粹简单,但是这个转化过程中涉及到转化格式
,这是个很有趣的东西,接口签名如下:
public interface Layout<E> extends ContextAware, LifeCycle {
String doLayout(E event);
String getFileHeader();
String getPresentationHeader();
String getFileFooter();
String getPresentationFooter();
String getContentType();
}
自定义Layout
自定义一个简单的Layout,需要继承 LayoutBase < IoggingEvent >
package chapters.layouts;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
public class MySampleLayout extends LayoutBase<ILoggingEvent> {
public String doLayout(ILoggingEvent event) {
StringBuffer sbuf = new StringBuffer(128);
sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime());
sbuf.append(" ");
sbuf.append(event.getLevel());
sbuf.append(" [");
sbuf.append(event.getThreadName());
sbuf.append("] ");
sbuf.append(event.getLoggerName();
sbuf.append(" - ");
sbuf.append(event.getFormattedMessage());
sbuf.append(CoreConstants.LINE_SEP);
return sbuf.toString();
}
}
配置文件中如何使用
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="chapters.layouts.MySampleLayout" />
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
encoder
标签内的layout
标签class
属性指定该实现类即可。
带参数选项的自定义Layout
怎么使用了,稍微加工一下就出来了。
package chapters.layouts;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
public class MySampleLayout2 extends LayoutBase<ILoggingEvent> {
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();
}
}
配置文件使用方式
<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>
转换词符Conversion Word
转换说明符(conversion specifier) 都以%开头,后跟可选的格式修饰符
, 转换词
,和大括号之间的可选参数
.
例如%-4relative [%thread] %-5level - %msg%n
格式修饰符 Format modifiers
%-4relative [%thread] %-5level %logger{35} - %msg%n
你直到%
与relative
之间的-4
是什么意思吗?它就是格式修饰符
,一般位于%
与Conversion Word
之间。
这里的-4
中的-
代表左对齐(也就是右填充),4
代表最少四个字符,如果数据项不足4个字符,就右填充空格。
主要看RelativeTimeConverter
和FormatInfo
类,可以看到更多代码细节。
默认是左填充(右对齐),左截断(当数据项超过最大字符数时从左删除字符)。
public class FormatInfo {
private int min = Integer.MIN_VALUE;
private int max = Integer.MAX_VALUE;
//默认左填充,左截断
private boolean leftPad = true;
private boolean leftTruncate = true;
......
/**
* This method is used to parse a string such as "5", ".7", "5.7" or "-5.7" into
* a FormatInfo.
*
* @param str A String to convert into a FormatInfo object
* @return A newly created and appropriately initialized FormatInfo object.
* @throws IllegalArgumentException
*/
public static FormatInfo valueOf(String str) throws IllegalArgumentException {
if (str == null) {
throw new NullPointerException("Argument cannot be null");
}
FormatInfo fi = new FormatInfo();
int indexOfDot = str.indexOf('.');
String minPart = null;
String maxPart = null;
//如果存在句点,句点左侧不分作为minPart, 右侧作为maxPart
if (indexOfDot != -1) {
minPart = str.substring(0, indexOfDot);
if (indexOfDot + 1 == str.length()) {
throw new IllegalArgumentException("Formatting string [" + str + "] should not end with '.'");
} else {
maxPart = str.substring(indexOfDot + 1);
}
} else {
minPart = str;
}
//如果minPart部分不为空,
//当minPart为负数时,则不再左填充,最少字符个数为minPart的绝对值
if (minPart != null && minPart.length() > 0) {
int min = Integer.parseInt(minPart);
if (min >= 0) {
fi.min = min;
} else {
fi.min = -min;
fi.leftPad = false;
}
}
//如果maxPart部分不为空
//如果maxPart为负数,则不再左截断,最多字符个数为maxPart的绝对值
if (maxPart != null && maxPart.length() > 0) {
int max = Integer.parseInt(maxPart);
if (max >= 0) {
fi.max = max;
} else {
fi.max = -max;
fi.leftTruncate = false;
}
}
return fi;
}
........
}
举几个例子
更多案例
%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n
将时间和线程名 组合起来输出日志时,一起加起来的字符个数最少为30个,不足进行右填充(左对齐),输出的日志效果可能是
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Classload hashcode is 13995234
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Initializing for ServletContext
13:09:30 [main] DEBUG c.q.logback.demo.ContextListener - Trying platform Mbean server
13:09:30 [pool-1-thread-1] INFO ch.qos.logback.demo.LoggingTask - Howdydy-diddly-ho - 0
13:09:38 [btpool0-7] INFO c.q.l.demo.lottery.LotteryAction - Number: 50 was tried.
13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Beginning to factor.
13:09:40 [btpool0-7] DEBUG c.q.l.d.prime.NumberCruncherImpl - Trying 2 as a factor.
13:09:40 [btpool0-7] INFO c.q.l.d.prime.NumberCruncherImpl - Found factor 2
<configuration>
<evaluator name="DISP_CALLER_EVAL">
<expression>logger.contains("chapters.layouts") && \
message.contains("who calls thee")</expression>
</evaluator>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%-4relative [%thread] %-5level - %msg%n%caller{2, DISP_CALLER_EVAL}
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
只在loggername包含chapters.layouts
且日志内容包含who calls thee
的日志时间中才输出调用信息。
package chapters.layouts;
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;
import ch.qos.logback.core.util.StatusPrinter;
public class CallerEvaluatorExample {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(CallerEvaluatorExample.class);
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
configurator.doConfigure(args[0]);
} catch (JoranException je) {
// StatusPrinter will handle this
}
StatusPrinter.printInCaseOfErrorsOrWarnings(lc);
for (int i = 0; i < 5; i++) {
if (i == 3) {
//输出调用信息
logger.debug("who calls thee?");
} else {
logger.debug("I know me " + i);
}
}
}
}
<configuration>
<evaluator name="DISPLAY_EX_EVAL">
<expression>throwable != null && throwable instanceof \
chapters.layouts.TestException</expression>
</evaluator>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%msg%n%ex{full, DISPLAY_EX_EVAL}</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
存在throwable且是TestException类型才符合评估条件,但%ex
表示的是评估条件不满足
的条件下,才输出异常堆栈。
自定义Conversion Specifier
第一步: 继承ClassicConverter
public class MySampleConverter extends ClassicConverter {
long start = System.nanoTime();
@Override
public String convert(ILoggingEvent event) {
long nowInNanos = System.nanoTime();
return Long.toString(nowInNanos-start);
}
}
第二步:配置文件使用
<configuration>
<conversionRule conversionWord="nanos"
converterClass="chapters.layouts.MySampleConverter" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-6nanos [%thread] - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>