logback 随笔 写入日志

Posted 肯尼思布赖恩埃德蒙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了logback 随笔 写入日志相关的知识,希望对你有一定的参考价值。

0. 整个宇宙 浩瀚无边的尽头 ~

比较详细的源码走读
异步日志接入kafka
异步日志配置


日志写入的话,就从Logger开始吧

1. Logger 调用 Appender 插入日志

package ch.qos.logback.classic;

public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable 
    public static final String FQCN = ch.qos.logback.classic.Logger.class.getName();

	// loggerName
    private String name;
	// 父、子层级的Logger相互持有对方的引用
    transient private Logger parent;
    transient private List<Logger> childrenList;
    final transient LoggerContext loggerContext;
	// 构造
    Logger(String name, Logger parent, LoggerContext loggerContext) 
        this.name = name;
        this.parent = parent;
        this.loggerContext = loggerContext;
    

    transient private Level level;
	// 从父层级继承下来的层级
    transient private int effectiveLevelInt;

    public void info(String format, Object... argArray) 
		// 俩null分别是 marker、throwable
        filterAndLog_0_Or3Plus(FQCN, null, Level.INFO, format, argArray, 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) 
			// 如果 当前记录的层级 小于 日志记录生效的层级
			// no-op
            if (effectiveLevelInt > level.levelInt) 
                return;
            
		// 	如果建议被否定,也一样 no-op
         else if (decision == FilterReply.DENY) 
            return;
        

		// step into ...
		// 那逻辑只能在这里了
        buildLoggingEventAndAppend(localFQCN, marker, level, msg, params, t);
    
	
	private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
                    final Throwable t) 
		// step into ...
		// 看看封装而成的 log事件 的构造
        LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
		// 入参的marker是null
        le.setMarker(marker);
		// 这么看log.info() 就是 调用appenders(event)
        callAppenders(le);
    
	
	public void callAppenders(ILoggingEvent event) 
        int writes = 0;
		// 向上递归以传播事件
        for (Logger l = this; l != null; l = l.parent) 
			// 将当前事件追加到 aai
            writes += l.appendLoopOnAppenders(event);
			// 如果当前logger不可追加,退出循环
            if (!l.additive) 
                break;
            
        
		// 抛出1个警告:没有appender绑定到当前的logger
        // No appenders in hierarchy
        if (writes == 0) 
            loggerContext.noAppenderDefinedWarning(this);
        
    
	
	// 这个impl指的是真正干事的、配置文件中能看到的 Appender
	// 比如,我们熟悉的 ConsoleAppender、FileAppender
	// 源码中出现的 AsyncAppender 等Appender作用只是委托给内部维护的aai
	transient private AppenderAttachableImpl<ILoggingEvent> aai;
    private int appendLoopOnAppenders(ILoggingEvent event) 
        if (aai != null) 
			// step into ...
			// AppenderAttachableImpl.appendLoopOnAppenders()
            return aai.appendLoopOnAppenders(event);
         else 
            return 0;
        
    


-------------

package ch.qos.logback.core.spi;

public class AppenderAttachableImpl<E> implements AppenderAttachable<E> 

    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++) 
			// step into ...
			// Appender.doAppend()
            appenderArray[i].doAppend(e);
            size++;
        
        return size;
    
	

2. 找个简单的Appender速通一下先

package ch.qos.logback.core;

abstract public class UnsynchronizedAppenderBase<E> extends ContextAwareBase implements Appender<E> 

    private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();
    public void doAppend(E eventObject) 
        if (Boolean.TRUE.equals(guard.get())) 
            return;
        

        try 
            guard.set(Boolean.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;
            

			// step into ...
			// 实现类的append()
            // 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.set(Boolean.FALSE);
        
    



---------------

package ch.qos.logback.core;

public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> 

    protected Encoder<E> encoder;
    protected final ReentrantLock lock = new ReentrantLock(false);
    private OutputStream outputStream;

    @Override
    protected void append(E eventObject) 
        if (!isStarted()) 
            return;
        

        subAppend(eventObject);
    

    /**
     * Actual writing occurs here.
     * <p>
     * Most subclasses of <code>WriterAppender</code> will need to override this
     * method.
     * 
     * @since 0.9.0
     */
    protected void subAppend(E event) 
        if (!isStarted()) 
            return;
        
        try 
            // this step avoids LBCLASSIC-139
            if (event instanceof DeferredProcessingAware) 
                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
            
            // the synchronization prevents the OutputStream from being closed while we
            // are writing. It also prevents multiple threads from entering the same
            // converter. Converters assume that they are in a synchronized block.
            // lock.lock();

            byte[] byteArray = this.encoder.encode(event);
            writeBytes(byteArray);

         catch (IOException ioe) 
            // as soon as an exception occurs, move to non-started state
            // and add a single ErrorStatus to the SM.
            this.started = false;
            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
        
    
	
	private void writeBytes(byte[] byteArray) throws IOException 
        if(byteArray == null || byteArray.length == 0)
            return;
        
        lock.lock();
        try 
        	// 到这就不谈噢
            this.outputStream.write(byteArray);
            if (immediateFlush) 
                this.outputStream.flush();
            
         finally 
            lock.unlock();
        
    


3. 既然支持异步,那么看看线程模型

  • 工作中遇到过接入kafka的场景,于是引起了注意(异步日志也是log4j2性能真正优秀的地方)

  • 如果要接入外部异步队列,需要依赖logback-appender-kafka支持,具体参考顶部链接

package ch.qos.logback.core;

public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> 

    BlockingQueue<E> blockingQueue;

    @Override
    protected void append(E eventObject) 
        if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) 
            return;
        
        preprocess(eventObject);
        put(eventObject);
    

    private void put(E eventObject) 
        if (neverBlock) 
            blockingQueue.offer(eventObject);
         else 
            putUninterruptibly(eventObject);
        
    

    private void putUninterruptibly(E eventObject) 
        boolean interrupted = false;
        try 
            while (true) 
                try 
                    blockingQueue.put(eventObject);
                    break;
                 catch (InterruptedException e) 
                    interrupted = true;
                
            
         finally 
            if (interrupted) 
                Thread.currentThread().interrupt();
            
        
    

	// 消费者
    class Worker extends Thread 

        public void run() 
            AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
            AppenderAttachableImpl<E> aai = parent.aai;

            // loop while the parent is started
            while (parent.isStarted()) 
                try 
                    E e = parent.blockingQueue.take();
                    aai.appendLoopOnAppenders(e);
                 catch (InterruptedException ie) 
                    break;
                
            

            addInfo("Worker thread will flush remaining events before exiting. ");

            for (E e : parent.blockingQueue) 
                aai.appendLoopOnAppenders(e);
                parent.blockingQueue.remove(e);
            

            aai.detachAndStopAllAppenders();
        
    


------

package ch.qos.logback.classic;

public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> 
// 没啥可考的东东,掠过...

4. Appender 的生命周期初始化方法(start()方法)调用位置

  • 从调用栈输出的结果,可以看出——是在 静态绑定 LoggerFactory 时,解析配置文件过程中调用的
  • 像 FileAppender 创建文件、父级目录都是在start()中创建的,因此可以认为程序运行过程如果没有文件,后续的日志将丢失,需要重启程序
start:76, ConsoleAppender (ch.qos.logback.core)
end:90, AppenderAction (ch.qos.logback.core.joran.action)
callEndAction:309, Interpreter (ch.qos.logback.core.joran.spi)
endElement:193, Interpreter (ch.qos.logback.core.joran.spi)
endElement:179, Interpreter (ch.qos.logback.core.joran.spi)
play:62, EventPlayer (ch.qos.logback.core.joran.spi)
doConfigure:165, GenericConfigurator (ch.qos.logback.core.joran)
doConfigure:152, GenericConfigurator (ch.qos.logback.core.joran)
doConfigure:110, GenericConfigurator (ch.qos.logback.core.joran)
doConfigure:53, GenericConfigurator (ch.qos.logback.core.joran)
configureByResource:75, ContextInitializer (ch.qos.logback.classic.util)
autoConfig:150, ContextInitializer (ch.qos.logback.classic.util)
init:84, StaticLoggerBinder (org.slf4j.impl)
<clinit>:55, StaticLoggerBinder (org.slf4j.impl)
bind:150, LoggerFactory (org.slf4j)
performInitialization:124, LoggerFactory (org.slf4j)
getILoggerFactory:417, LoggerFactory (org.slf4j)
getLogger:362, LoggerFactory (org.slf4j)
createLocationAwareLog:130, LogAdapter$Slf4jAdapter (org.apache.commons.logging)
createLog:91, LogAdapter (org.apache.commons.logging)
getLog:67, LogFactory (org.apache.commons.logging)
getLog:59, LogFactory (org.apache.commons.logging)
<clinit>:196, SpringApplication (org.springframework.boot)
main:10, AngelMicroServiceSampleApplication (cn.angel.project.angelmicroservicesample)

logback 有时不写入日志文件,有时不滚动日志文件

【中文标题】logback 有时不写入日志文件,有时不滚动日志文件【英文标题】:Logback sometimes does not write to the log file, and sometimes does not roll the log file 【发布时间】:2011-04-03 18:14:41 【问题描述】:

有时当我启动我的 java 应用程序时,logback 拒绝向我的日志文件写入任何内容。有时它还拒绝在午夜(或午夜后的第一个日志事件)滚动日志文件,这会导致日志事件丢失到空白处。当我在 logbacks 无法滚动日志时查看我的主日志文件时,它将有一个像 23:59 这样的时间,昨天的日期,并且在那之后的任何和所有日志记录语句都将无法挽回地丢失。我有一个相当简单的配置文件,它看起来是正确的。它肯定应该是正确的,因为它在大多数情况下都有效。

这是我的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!--See http://logback.qos.ch/manual/appenders.html#RollingFileAppender-->
    <!--and http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy-->
    <!--for further documentation-->
    <append>true</append>
    <File>aggregator.log</File>
    <encoder>
        <!-- was: %dyyyy-MM-dd HH:mm:ss%5p [%t] (%F:%L) - %msg%n -->
      <pattern>%dyyyy-MM-dd HH:mm:ss %-5level [%thread] \(%class25:%line\) - %msg%n</pattern>
    </encoder>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- By setting the name to .gz here, we get free compression. -->
      <fileNamePattern>aggregator.log.%dyyyy-MM-dd.gz</fileNamePattern>
    </rollingPolicy>
  </appender>
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%dyyyy-MM-dd HH:mm:ss %-5level [%thread] \(%class25:%line\) - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="file"/>
    <appender-ref ref="console"/>
  </root>
</configuration>

不幸的是,我无法可靠地重现此错误,因此调试它有点困难。有人可以告诉我我做错了什么,或者还有什么问题?如果有任何帮助,我将 STDOUT 和 STDERR 重定向到 /dev/null (我在 linux 上运行,顺便说一句)。

【问题讨论】:

我刚刚更新到最新的点发布。希望这会有所帮助。 【参考方案1】:

要调试问题,请使用&lt;configuration debug="true"&gt; 并且不要重定向标准输出。 Logback 会在解析配置以及出现问题时打印消息。

【讨论】:

我意识到我可以做到这一点,但由于我无法重现此行为,因此即使有所有信息,也很难排除故障。但是,我会尝试... 我用调试运行并查看了输出,并且 logback 没有抱怨。这是意料之中的,因为配置在大多数情况下都能正常工作。 运行它并等待它中断。然后,您应该会在标准输出上看到一个错误。【参考方案2】:

事实证明,这与 logback 几乎没有关系。问题是我有一个 .policy 文件,它没有为应用程序指定适当的权限。我认为我可以旋转文件的时候是我手动移动或删除以前的文件的时候。我通过确保 logback 有足够的权限来轮换自己的日志来解决这个问题。

【讨论】:

以上是关于logback 随笔 写入日志的主要内容,如果未能解决你的问题,请参考以下文章

Logback-android:日志未写入文件

logback日志写入数据库(mysql)配置

logback KafkaAppender 写入Kafka队列,集中日志输出.

logback 异步日志配置

logback的配置和使用

logback的使用和logback.xml详解