ログバックは主に3つのjarファイルで構成されています
- slf4j-api
- ログバックコア
- logback-クラシック
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Main.class);
logger.debug("hello world");
}
}
复制代码
デフォルト設定がない場合、LogbackはConsoleAppenderをルートロガーに追加します
Logbackは、StatusPrinterおよびStatusManagerを介して内部ステータス情報を印刷できます
private static void test2(){
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusManager statusManager = loggerContext.getStatusManager();
StatusPrinter.print(statusManager);
}
复制代码
次のように印刷します
21:20:30,270 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
21:20:30,270 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
21:20:30,271 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
21:20:30,274 |-INFO in ch.qos.logback.classic.BasicConfigurator@6d8a00e3 - Setting up default configuration.
复制代码
3つのデフォルト構成ファイルが見つかりません
- logback-test.xml
- logback.groovy
- logback.xml
デフォルト構成のみを使用して、ログをコンソールに出力できます。
出力先としてのアペンダー
- コンソール
- ファイル
- db
- SQM
エラーが発生した場合、Logbackは自身の内部状態をコンソールに出力します
3つのコンポーネント
- ロガー
- アペンダー
- Layous
logback-classicモジュールの一部としてのLogger、logback-coreの一部としてのAppenderおよびLayoutsインターフェース、一般的なモジュールとして、logback-coreにはLoggerの概念がありません。
LoggerContext
ロガーを生成するためのslf4jのILoggerFactoryインターフェイスを実装します
LoggerContextのメインメンバー変数
- 1つ目はルートロガーです
- 2つ目はキャッシュであるため、ロガーの名前では大文字と小文字が区別されます
- グローバルフィルターとしてのTurboFilter
階層
Logbackのロガーは階層構造.
としてます
親ロガーがロガーであり、あるcom.demo.Main
という名前のロガーはcom.demo
com
root
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
private static final long serialVersionUID = 5454405123156820674L; // 8745934908040027998L;
public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();
private String name;
transient private Level level;
transient private int effectiveLevelInt;
transient private Logger parent;
transient private List<Logger> childrenList;
复制代码
字段 parent
就是用来存放父级 logger 的、而 childrenList
则是存放子 logger 的、当父 logger 的等级发生改变的时候就是依靠该字段去通知子 logger 。
有效等级
我们知道如果我们 logger 设置的等级大于调用的方法、那么该日志不会被输出、显然这种等值的比较、可以依靠数值来实现。而这个值在 Logger 类中就是 effectiveLevelInt
这个字段
public static final int OFF_INT = Integer.MAX_VALUE;
public static final int ERROR_INT = 40000;
public static final int WARN_INT = 30000;
public static final int INFO_INT = 20000;
public static final int DEBUG_INT = 10000;
public static final int TRACE_INT = 5000;
public static final int ALL_INT = Integer.MIN_VALUE;
public static final Level OFF = new Level(OFF_INT, "OFF");
public static final Level ERROR = new Level(ERROR_INT, "ERROR");
public static final Level WARN = new Level(WARN_INT, "WARN");
public static final Level INFO = new Level(INFO_INT, "INFO");
public static final Level DEBUG = new Level(DEBUG_INT, "DEBUG");
public static final Level TRACE = new Level(TRACE_INT, "TRACE");
public static final Level ALL = new Level(ALL_INT, "ALL");
复制代码
我们可以看到
public LoggerContext() {
super();
this.loggerCache = new ConcurrentHashMap<String, Logger>();
this.loggerContextRemoteView = new LoggerContextVO(this);
this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
this.root.setLevel(Level.DEBUG);
loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
initEvaluatorMap();
size = 1;
this.frameworkPackages = new ArrayList<String>();
}
复制代码
默认情况下 root logger 的 level 为 debug、effectiveLevelInt
的值为 10000
默认情况下、如果我们创建使用的 logger 没有指定 level 的话、它会继承它的父类的 effectiveLevelInt
Logger createChildByName(final String childName) {
........
Logger childLogger;
childLogger = new Logger(childName, this, this.loggerContext);
childrenList.add(childLogger);
childLogger.effectiveLevelInt = this.effectiveLevelInt;
return childLogger;
}
复制代码
this
就是父 logger
当 父 logger level 发生变化时、会调用方法通知子 logger
Logger createChildByName(final String childName) {
int i_index = LoggerNameUtil.getSeparatorIndexOf(childName, this.name.length() + 1);
if (i_index != -1) {
throw new IllegalArgumentException("For logger [" + this.name + "] child name [" + childName
+ " passed as parameter, may not include '.' after index" + (this.name.length() + 1));
}
if (childrenList == null) {
childrenList = new CopyOnWriteArrayList<Logger>();
}
Logger childLogger;
childLogger = new Logger(childName, this, this.loggerContext);
childrenList.add(childLogger);
childLogger.effectiveLevelInt = this.effectiveLevelInt;
return childLogger;
}
复制代码
demo 如下
private static void test3(){
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("x.y");
logger.setLevel(Level.INFO);
logger.info("info");
logger.debug("debug");
ch.qos.logback.classic.Logger childLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("x.y.z");
childLogger.info("info");
childLogger.debug("debug");
logger.setLevel(Level.TRACE);
logger.trace("trace");
logger.debug("debug");
childLogger.trace("trace");
childLogger.debug("debug");
}
复制代码
输出的话、你自己想想
Appender
一个 Logger 可以有多个 Appender、比如说日志既可以输出到控制台也可以到日志文件
// Logger 类中使用 AppenderAttachableImpl<ILoggingEvent> aai 保存、内部使用 COWList 保存
public synchronized void addAppender(Appender<ILoggingEvent> newAppender) {
if (aai == null) {
aai = new AppenderAttachableImpl<ILoggingEvent>();
}
aai.addAppender(newAppender);
}
复制代码
Appender 默认也是具有继承的特性、比如说 root 的 Appender 是 Console、而子 logger 的 Appender 是 file、那么使用子 logger 打印日志、日志即会输出到日志文件也会输出到 console 中
// Logger
public void callAppenders(ILoggingEvent event) {
int writes = 0;
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
复制代码
// AppenderAttachableImpl
public int appendLoopOnAppenders(E e) {
int size = 0;
final Appender<E>[] appenderArray = appenderList.asTypedArray();
final int len = appenderArray.length;
for (int i = 0; i < len; i++) {
appenderArray[i].doAppend(e);
size++;
}
return size;
}
复制代码
先调用自己的 Appender 然后调用父类的。
可以将 additive 设置为 false、那么子 logger 不再对父 logger 有继承 Appender
流程
过滤链
public void info(String msg) {
filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, msg, null, null);
}
private void filterAndLog_0_Or3Plus(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
final FilterReply decision = loggerContext.getTurboFilterChainDecision_0_3OrMore(marker, this, level, msg, params, t);
if (decision == FilterReply.NEUTRAL) {
if (effectiveLevelInt > level.levelInt) {
return;
}
} else if (decision == FilterReply.DENY) {
return;
}
buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
}
复制代码
从 LoggerContext 中获取 TurboFilterList
final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
final Object[] params, final Throwable t) {
if (turboFilterList.size() == 0) {
return FilterReply.NEUTRAL;
}
return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
}
复制代码
默认情况是没有 turboFilter 、所以会返回 NEUTRAL
final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
final Object[] params, final Throwable t) {
if (turboFilterList.size() == 0) {
return FilterReply.NEUTRAL;
}
return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
}
复制代码
public FilterReply getTurboFilterChainDecision(final Marker marker, final Logger logger, final Level level, final String format, final Object[] params,
final Throwable t) {
final int size = size();
// if (size == 0) {
// return FilterReply.NEUTRAL;
// }
if (size == 1) {
try {
TurboFilter tf = get(0);
return tf.decide(marker, logger, level, format, params, t);
} catch (IndexOutOfBoundsException iobe) {
return FilterReply.NEUTRAL;
}
}
Object[] tfa = toArray();
final int len = tfa.length;
for (int i = 0; i < len; i++) {
// for (TurboFilter tf : this) {
final TurboFilter tf = (TurboFilter) tfa[i];
final FilterReply r = tf.decide(marker, logger, level, format, params, t);
if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
return r;
}
}
return FilterReply.NEUTRAL;
}
复制代码
如果存在多个、如果明确是 DENY 或者 ACCPET 的话、直接返回、如果不确定、那么遍历到最后返回 NEUTRAL
リターンがニュートラルの場合、呼び出されたメソッドのレベルがロガーによって設定されたレベルよりも大きいかどうかが判断され、それよりも小さい場合、直接印刷およびリターンされません。
同様に、DENYに戻ることも直接の戻りであり、ACCEPTのみがダウンし続けることができます
LoggingEventオブジェクト
private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
final Throwable t) {
LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
le.setMarker(marker);
callAppenders(le);
}
复制代码
LoggingEventオブジェクト。このオブジェクトには、ログリクエスト、リクエストされたロガー、ログリクエストのレベル、ログ情報、ログとともに渡される例外情報、現在の時刻、現在のスレッド、およびのさまざまな情報に関連するすべてのパラメータが含まれます。現在のクラスとMDC。
アペンダーに電話する
public void callAppenders(ILoggingEvent event) {
int writes = 0;
for (Logger l = this; l != null; l = l.parent) {
writes += l.appendLoopOnAppenders(event);
if (!l.additive) {
break;
}
}
// No appenders in hierarchy
if (writes == 0) {
loggerContext.noAppenderDefinedWarning(this);
}
}
复制代码
これは上で述べました
public int appendLoopOnAppenders(E e) {
int size = 0;
final Appender<E>[] appenderArray = appenderList.asTypedArray();
final int len = appenderArray.length;
for (int i = 0; i < len; i++) {
appenderArray[i].doAppend(e);
size++;
}
return size;
}
复制代码
public synchronized void doAppend(E eventObject) {
// WARNING: The guard check MUST be the first statement in the
// doAppend() method.
// prevent re-entry.
if (guard) {
return;
}
try {
guard = true;
if (!this.started) {
if (statusRepeatCount++ < ALLOWED_REPEATS) {
addStatus(new WarnStatus("Attempted to append to non started appender [" + name + "].", this));
}
return;
}
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
// ok, we now invoke derived class' implementation of append
this.append(eventObject);
} catch (Exception e) {
if (exceptionCount++ < ALLOWED_REPEATS) {
addError("Appender [" + name + "] failed to append.", e);
}
} finally {
guard = false;
}
}
复制代码
ここにはカスタムフィルターが含まれますgetFilterChainDecision
フォーマットされた出力
// SyslogAppenderBase
@Override
protected void append(E eventObject) {
if (!isStarted()) {
return;
}
try {
String msg = layout.doLayout(eventObject);
if (msg == null) {
return;
}
if (msg.length() > maxMessageSize) {
msg = msg.substring(0, maxMessageSize);
}
sos.write(msg.getBytes(charset));
sos.flush();
postProcess(eventObject, sos);
} catch (IOException ioe) {
addError("Failed to send diagram to " + syslogHost, ioe);
}
}
复制代码
これにより、Layoutオブジェクトを使用してEventObjectが文字列に変換されます。
ただし、MQやSocketへの出力など、フォーマットする必要のないアペンダーもあります。
private void dispatchEvents(ObjectWriter objectWriter) throws InterruptedException, IOException {
while (true) {
E event = deque.takeFirst();
postProcessEvent(event);
Serializable serializableEvent = getPST().transform(event);
try {
objectWriter.write(serializableEvent);
} catch (IOException e) {
tryReAddingEventToFrontOfQueue(event);
throw e;
}
}
}
复制代码
それを出力ストリームにシリアル化する方法
public Serializable transform(ILoggingEvent event) {
if (event == null) {
return null;
}
if (event instanceof LoggingEvent) {
return LoggingEventVO.build(event);
} else if (event instanceof LoggingEventVO) {
return (LoggingEventVO) event;
} else {
throw new IllegalArgumentException("Unsupported type " + event.getClass().getName());
}
}
}
复制代码
LoggingEventを送信します
ログイベントが完全にフォーマットされると、各アペンダーを介して特定の宛先に送信されます。