Logback日志框架-日志打印過(guò)程及Logger繼承特性源碼分析

摘要

上一篇《Logback日志框架初始化全過(guò)程源碼解析》分析了Logback的整個(gè)初始化過(guò)程以及Logger的創(chuàng)建您觉,這篇文章將繼續(xù)分析Logger的打印過(guò)程,上篇文章沒(méi)有介紹Logback的架構(gòu)熬荆,所以我們先會(huì)簡(jiǎn)單介紹下Logback的基礎(chǔ)組成模塊心傀;然后再介紹下logback.xml 配置文件中的主要元素和他們之間的協(xié)作關(guān)系,重點(diǎn)說(shuō)明下Logger的繼承特性爆捞;接著梳理下整個(gè)日志打印過(guò)程炎疆,帶著這些概念進(jìn)入源碼去一一驗(yàn)證卡骂,最后做個(gè)簡(jiǎn)單的總結(jié)。

正文

Logback基礎(chǔ)組成模塊

Logback主要分為3個(gè)模塊:

  • logback-core

    core模塊是其他兩個(gè)模塊的基礎(chǔ)

  • logback-classic

    classic模塊依賴core模塊形入,她是其實(shí)是log4j的升級(jí)版全跨,logback-classic天然實(shí)現(xiàn)了SLF4J API,所以我們可以在Log4j亿遂、Jul等日志框架之間隨意切換而不需要改動(dòng)我們的業(yè)務(wù)代碼浓若。

  • logback-access

    access模塊主要用于和Servlet容器結(jié)合提供記錄HTTP請(qǐng)求日志的功能,通常不使用蛇数。

一般使用Logback時(shí)我們會(huì)引用slf4j-api挪钓、logback-core和logback-classic等3個(gè)依賴。

Logback配置

logback 提供多種配置方式:

配置文件其實(shí)也提供2類:logback.grovy和logback.xml

兩者大同小異绍赛,只是語(yǔ)法不同而已,在這里我們選擇比較熟悉和常用的logback.xml進(jìn)行簡(jiǎn)單解釋辑畦。

<configuration>
    <property name="pattern" value="%date %level [%thread] %logger{10} [%file : %line] %msg%n"/>
    
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>${pattern}</Pattern>
        </layout>
    </appender>
    
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${fileName:-demo.log}</file>
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    
    <logger name="com" level="warn" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </logger>

    <logger name="com.cw" level="info" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </logger>
    
    <logger name="com.cw.demo" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </logger>

    <root level="info">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

logback.xml中我們主要看6個(gè)xml元素:

appender

appender主要用來(lái)指定日志信息最終輸出到什么位置,console腿倚、file或者socket等纯出。

layout

layout用來(lái)根據(jù)pattern規(guī)則格式化日志內(nèi)容,將日志event轉(zhuǎn)換為String類型,然后通過(guò)java.io.Writer輸出暂筝。

encoder

encoder是0.9.19版本之后出現(xiàn)的箩言,她和layout作用本質(zhì)上是相同的,都會(huì)通過(guò)pattern規(guī)則將日志內(nèi)容格式化焕襟,不同點(diǎn)是陨收,encoder將日志event轉(zhuǎn)化為byte數(shù)組,然后寫(xiě)到OutputStream鸵赖。

0.9.19版本之后官方推薦encoder來(lái)代替layout务漩,具體原因,官方給出如下解釋:

Why the breaking change?

Layouts, as discussed in detail in the next chapter, are only able to transform an event into a String. Moreover, given that a layout has no control over when events get written out, layouts cannot aggregate events into batches. Contrast this with encoders which not only have total control over the format of the bytes written out, but also control when (and if) those bytes get written out.

意思是說(shuō)它褪,Layouts只能將日志event轉(zhuǎn)化為String饵骨,不能夠控制何時(shí)將日志event寫(xiě)出,無(wú)法將多個(gè)event集合到一組茫打。而encoder不僅能控制日志bytes數(shù)據(jù)的格式居触,還能控制是否以及何時(shí)寫(xiě)出數(shù)據(jù)。簡(jiǎn)單來(lái)講就是encoder比layout牛逼老赤,能用encoder就不用layout轮洋。當(dāng)然對(duì)于之前的使用layout的老代碼,官方也提供一些適配class例如LayoutWrappingEncoder

pattern

pattern主要用來(lái)定義日志的輸出格式等抬旺,詳見(jiàn)PatternLayout弊予。

logger

logger可以與我代碼中LoggerFactory.getLogger產(chǎn)生的Logger對(duì)象相對(duì)應(yīng),她有三個(gè)屬性:

  • name : 日志名稱嚷狞,用于Logger對(duì)象初始化的時(shí)候定位到配置文件中具體的配置块促。

  • level:標(biāo)識(shí)日志的有效等級(jí),日志等級(jí)具有繼承性床未。

    例如:

    logback.xml中name="com.cw"的logger(取別名Lc)等級(jí)是info竭翠, name="com.cw.demo"的logger(取別名為L(zhǎng)d),Lc是Ld的父級(jí)薇搁,而Ld沒(méi)有設(shè)置日志等級(jí)斋扰,那么她的等級(jí)就繼承子父級(jí)Lc的info,

    如果Lc也沒(méi)有等級(jí)啃洋,那么會(huì)繼續(xù)往上找到name="com"這個(gè)祖先传货,繼承這個(gè)祖先的日志等級(jí)warn

  • additivity:標(biāo)識(shí)該日志對(duì)象是否要將日志event向父級(jí)輸出宏娄,默認(rèn)為true问裕,一般我們會(huì)設(shè)置為false,防止一段日志信息被重復(fù)打印到多個(gè)日志文件中孵坚,無(wú)形中增加了日志文件的大小粮宛,稍后會(huì)結(jié)合源碼做分析窥淆。

appender-ref

appender-ref作用是將一個(gè)logger對(duì)象和具體的appender綁定,指定該logger將通過(guò)哪個(gè)appender將日志輸出到何處巍杈。一個(gè)logger可存在多個(gè)appender-ref忧饭,也就是說(shuō)可以同時(shí)綁定多個(gè)appender。

root

root其實(shí)是個(gè)特殊的logger筷畦,看名字就知道她是一個(gè)根節(jié)點(diǎn)词裤,是所有l(wèi)ogger的祖先,配置文件中必須得有一個(gè)root鳖宾。

日志打印源碼分析

過(guò)濾和創(chuàng)建LogEvent

這里我們以我們常用的logger.info() 為入口吼砂,info()中實(shí)際是調(diào)用filterAndLog_1()方法,

這個(gè)方法做2件事攘滩,過(guò)濾和創(chuàng)建LoggingEvent并推送給實(shí)際綁定的Appender對(duì)象

public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {
   
    public void info(String format, Object arg) {
        filterAndLog_1(FQCN, null, Level.INFO, format, arg, null);
    }

    private void filterAndLog_1(final String localFQCN, final Marker marker, final Level         level, final String msg, final Object param, final Throwable t) {

        /**
         * 這里先經(jīng)過(guò)一串過(guò)濾器處理帅刊,根據(jù)傳入的marker滿足的條件不同返回不同的結(jié)果,
         * FilterReply.NEUTRAL 表示中立的意思
         */
        final FilterReply decision = loggerContext.getTurboFilterChainDecision_1(marker, this, level, msg, param, t);

        /**
         * 過(guò)濾后返回NEUTRAL則進(jìn)一步判斷當(dāng)前想要打印的日志等級(jí)"INFO"是否高于或者等于有效日志級(jí)別漂问。
         * 如果小于有效日志級(jí)別赖瞒,則表示該日志無(wú)效,直接返回蚤假,不做具體輸出動(dòng)作栏饮。
         */
        if (decision == FilterReply.NEUTRAL) {
            if (effectiveLevelInt > level.levelInt) {
                return;
            }
        } else if (decision == FilterReply.DENY) {
            return;
        }
        
        buildLoggingEventAndAppend(localFQCN, marker, level, msg, new Object[] { param }, t);
    }

    /**
      * 創(chuàng)建日志Event, 推送到與logger綁定的所有appender中
      */
    private void buildLoggingEventAndAppend(
        final String localFQCN, final Marker marker, 
        final Level level, final String msg, 
        final Object[] params, final Throwable t) {

        // 創(chuàng)建日志Event
        LoggingEvent le = new LoggingEvent(localFQCN, this, level, msg, t, params);
        le.setMarker(marker);
        
        //推送到與logger綁定的所有appender中
        callAppenders(le);
    }
    
    public void callAppenders(ILoggingEvent event) {
        int writes = 0;

        /**
         * 這里有個(gè)熟悉的單詞additive,上面我們介紹logger的時(shí)候說(shuō)到磷仰,
         * 她是logger的一個(gè)屬性袍嬉,默認(rèn)為true
         * 結(jié)合這段代碼我們看下,for循環(huán)中灶平,先將event, 推給當(dāng)前l(fā)ogger的Appenders中伺通,并且累加
         * writes,緊接著判斷additive逢享,
         * 如果為true則罐监,取logger的父級(jí),繼續(xù)將event推給父級(jí)logger的Appender中瞒爬,
         * 以此類推弓柱,直至遇到某個(gè)父級(jí)或者祖先的additive為false,則break跳出循環(huán)侧但,
         * 或者 送到root日志根節(jié)點(diǎn)為止矢空。
         * 這幾解釋了additive的作用了。
         */
        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);
        }
    }
    
    private int appendLoopOnAppenders(ILoggingEvent event) {
        if (aai != null) {
            return aai.appendLoopOnAppenders(event);
        } else {
            return 0;
        }
    }
    
    /**
     * appenderList是與Logger綁定的Appender
     * 例如上述logback.xml中name="FILE"的logger綁定的
     * Appender 就是ch.qos.logback.core.FileAppender類禀横。
     * 因?yàn)槊總€(gè)logger可以同時(shí)綁定多個(gè)Appender所以這里用數(shù)組appenderList來(lái)存儲(chǔ)這些Appender屁药。
     */
    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;
    }
}

通過(guò)FileAppender打印到文件

logback.xml中name="FILE"的logger綁定的 Appender 是ch.qos.logback.core.FileAppender類,那么我們就以FileAppender為例分析下日志寫(xiě)入到文件的整個(gè)流程柏锄。

FIleAppender類的繼承關(guān)系圖:

FileAppender.png

整個(gè)流程如下:

FileAppender 指定日志打印的目標(biāo)文件者祖,并指定文件的輸出流OutputStream給OutputStreamAppender立莉,

目標(biāo)文件的指定動(dòng)作是發(fā)生在logger的初始化過(guò)程中绢彤,通過(guò)加載解析logback.xml找到name="FILE"的<appender>元素七问,讀取子元素<file>的值來(lái)獲取文件;

OutputStreamAppender 提供一個(gè)基礎(chǔ)服務(wù)茫舶,她繼承UnsynchronizedAppenderBase抽象類并實(shí)現(xiàn)其中的

append(E eventObject)抽象方法械巡,進(jìn)行具體的日志格式化和打印動(dòng)作,其實(shí)就是向FileAppender提供的輸出流中輸入格式化后的日志饶氏;

通過(guò)名稱可看出UnsynchronizedAppenderBase并未做線程安全方面的措施讥耗,而是將線程安全方面的問(wèn)題交給子類自己去處理,OutputStreamAppender確實(shí)也自己處理了疹启,她通過(guò)在writeBytes(byte[] byteArray)方法中加入 ReentrantLock重入鎖來(lái)保證日志寫(xiě)入的線程安全古程。

接下來(lái)我們從下到上依次來(lái)分析下幾個(gè)類中重要的方法:

FileAppender

public class FileAppender<E> extends OutputStreamAppender<E> {
    ...
    
    /**
     * 
     */
    public void start() {
        int errors = 0;
        if (getFile() != null) {
            ...

            /**
             * 檢查該日志文件是否以及被其他Appender綁定了,如果沒(méi)被綁定則返回false
             */
            if (checkForFileCollisionInPreviousFileAppenders()) {
                addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
                addError(MORE_INFO_PREFIX + COLLISION_WITH_EARLIER_APPENDER_URL);
                errors++;
            } else {
               
                try {
                    /**
                      * 打開(kāi)未被其他Appender綁定的文件輸出流給OutputStreamAppender,
                      * 用于后續(xù)的日志輸入喊崖。
                      */
                    openFile(getFile());
                } catch (java.io.IOException e) {
                    errors++;
                    addError("openFile(" + fileName + "," + append + ") call failed.", e);
                }
            }
        } else {
            errors++;
            addError("\"File\" property not set for appender named [" + name + "].");
        }
        if (errors == 0) {
            super.start();
        }
    }
    
    /**
     * 檢查該日志文件是否以及被其他Appender綁定了挣磨,如果是的話就返回true
     * 否則記錄appender的名稱和日志文件名稱,表示當(dāng)前文件已經(jīng)被Appender綁定了荤懂,
     * 不能再和別的Appender綁定茁裙,然后返回false
     */
    protected boolean checkForFileCollisionInPreviousFileAppenders() {
        boolean collisionsDetected = false;
        if (fileName == null) {
            return false;
        }
        @SuppressWarnings("unchecked")
        Map<String, String> map = (Map<String, String>) context.getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
        if (map == null) {
            return collisionsDetected;
        }
        for (Entry<String, String> entry : map.entrySet()) {
            if (fileName.equals(entry.getValue())) {
                addErrorForCollision("File", entry.getValue(), entry.getKey());
                collisionsDetected = true;
            }
        }
        if (name != null) {
            map.put(getName(), fileName);
        }
        return collisionsDetected;
    }
    /**
     * 創(chuàng)建個(gè)ResilientFileOutputStream輸出流,             
     * 給OutputStreamAppender中的节仿,outputStream成員變量晤锥;
     * 文件必須是可寫(xiě)的;
     * 雖然該方法是public的但是不要直接調(diào)用廊宪,
     * 而是通過(guò)start方法一步步設(shè)置還成員變量的后再調(diào)用
     */
    public void openFile(String file_name) throws IOException {
        lock.lock();
        try {
            File file = new File(file_name);
            boolean result = FileUtil.createMissingParentDirectories(file);
            if (!result) {
                addError("Failed to create parent directories for [" + file.getAbsolutePath() + "]");
            }
            ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
            resilientFos.setContext(context);
            setOutputStream(resilientFos);
        } finally {
            lock.unlock();
        }
    }
  ...
}

OutputStreamAppender

public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
    protected Encoder<E> encoder;
    protected final ReentrantLock lock = new ReentrantLock(false);
    private OutputStream outputStream;

    boolean immediateFlush = true;

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

    /**
       * 真正的的寫(xiě)入操作
       * <p>
       * Most subclasses of <code>WriterAppender</code> will need to override this
       * method.
       */
    protected void subAppend(E event) {
        if (!isStarted()) {
            return;
        }
        try {
            // this step avoids LBCLASSIC-139
            if (event instanceof DeferredProcessingAware) {
                ((DeferredProcessingAware) event).prepareForDeferredProcessing();
            }
            // 格式化日志文件
            byte[] byteArray = this.encoder.encode(event);
            // 寫(xiě)入日志到特定的輸出流中矾瘾,使用ReentrantLock保證線程安全
            writeBytes(byteArray);
        } catch (IOException ioe) {
            this.started = false;
            addStatus(new ErrorStatus("IO failure in appender", this, ioe));
        }
    }
 
    /**
    * 傳入一個(gè)已經(jīng)打開(kāi)的outputStream,
    * 例如FileAppender 中打開(kāi)的ResilientFileOutputStream
    */
    public void setOutputStream(OutputStream outputStream) {
        lock.lock();
        try {
            // close any previously opened output stream
            closeOutputStream();
            this.outputStream = outputStream;
            if (encoder == null) {
                addWarn("Encoder has not been set. Cannot invoke its init method.");
                return;
            }
            encoderInit();
        } finally {
            lock.unlock();
        }
    }

    ...

    /**
     * 寫(xiě)入日志到特定的輸出流中箭启,使用ReentrantLock保證線程安全
     */
    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();
        }
    }
}

UnsynchronizedAppenderBase

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

    protected boolean started = false;
    
    /**
     * guard 用來(lái)防止appender 重復(fù)調(diào)用自己的doAppend方法
     */
    private ThreadLocal<Boolean> guard = new ThreadLocal<Boolean>();

    /**
     * Appenders are named.
     */
    protected String name;

    private FilterAttachableImpl<E> fai = new FilterAttachableImpl<E>();

    private int statusRepeatCount = 0;
    private int exceptionCount = 0;

    static final int ALLOWED_REPEATS = 3;

    public void doAppend(E eventObject) {
        
        // 判斷當(dāng)前線程是否正在打印日志壕翩,如果不是才進(jìn)行打印動(dòng)作。
        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;
            }

            /**
              * append是個(gè)抽象方法册烈,所以此處會(huì)調(diào)用append方法的具體實(shí)現(xiàn)
              * 例如OutputStreamAppender類中實(shí)現(xiàn)的該方法
              */
            this.append(eventObject);

        } catch (Exception e) {
            if (exceptionCount++ < ALLOWED_REPEATS) {
                addError("Appender [" + name + "] failed to append.", e);
            }
        } finally {
            guard.set(Boolean.FALSE);
        }
    }

    abstract protected void append(E eventObject);

至此整個(gè)日志打印過(guò)程結(jié)束了戈泼。

總結(jié)

本文主要通過(guò)FileAppender類為例詳細(xì)分析了整個(gè)日志的打印流程,除了FileAppender 還有一些我們常用的Appender赏僧,例如:

ConsoleAppender: 打印日志到控制臺(tái)
RollingFileAppender: 可以根據(jù)一定的規(guī)則對(duì)日志進(jìn)行切割備份大猛,如:日期、日志大小等規(guī)則

文中就不一一介紹了淀零,他們的流程都大同小異挽绩,留個(gè)感興趣的同學(xué)自己去分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末驾中,一起剝皮案震驚了整個(gè)濱河市唉堪,隨后出現(xiàn)的幾起案子模聋,更是在濱河造成了極大的恐慌,老刑警劉巖唠亚,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件链方,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灶搜,警方通過(guò)查閱死者的電腦和手機(jī)祟蚀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)割卖,“玉大人前酿,你說(shuō)我怎么就攤上這事∨羲荩” “怎么了罢维?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丙挽。 經(jīng)常有香客問(wèn)我肺孵,道長(zhǎng)取试,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任初婆,我火速辦了婚禮,結(jié)果婚禮上猿棉,老公的妹妹穿的比我還像新娘磅叛。我一直安慰自己萨赁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布杖爽。 她就那樣靜靜地躺著敲董,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慰安。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天萄窜,我揣著相機(jī)與錄音,去河邊找鬼键兜。 笑死穗泵,一個(gè)胖子當(dāng)著我的面吹牛普气,可吹牛的內(nèi)容都是我干的棋电。 我是一名探鬼主播苇侵,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼企锌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了陡鹃?” 一聲冷哼從身側(cè)響起抖坪,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤擦俐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蚯瞧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體埋合,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蜜猾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了振诬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棠笑,死狀恐怖禽绪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情循捺,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布念赶,位于F島的核電站恰力,受9級(jí)特大地震影響踩萎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜香府,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一企孩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勿璃,春花似錦、人聲如沸闻葵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嬉橙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間霞扬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工萤彩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斧拍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓愚墓,卻偏偏與公主長(zhǎng)得像昂勉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子议经,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在應(yīng)用程序中添加日志記錄總的來(lái)說(shuō)基于三個(gè)目的:監(jiān)視代碼中變量的變化情況,周期性的記錄到文件中供其他應(yīng)用進(jìn)行統(tǒng)計(jì)分析...
    時(shí)待吾閱讀 4,985評(píng)論 0 6
  • 轉(zhuǎn)自:http://www.cnblogs.com/warking/p/5710303.html 一嗓袱、logbac...
    chenzhong_閱讀 1,501評(píng)論 0 3
  • 在logback學(xué)習(xí)筆記(上)中我們介紹了logback中的一些核心概念渠抹,在這篇文章中我們共同來(lái)學(xué)習(xí)一下如何利用配...
    阿龍的學(xué)與思閱讀 1,676評(píng)論 0 3
  • 在應(yīng)用程序中添加日志記錄總的來(lái)說(shuō)基于三個(gè)目的:監(jiān)視代碼中變量的變化情況闪萄,周期性的記錄到文件中供其他應(yīng)用進(jìn)行統(tǒng)計(jì)分析...
    時(shí)待吾閱讀 5,049評(píng)論 1 13
  • 這幾天生活很靜败去,靜的你都體會(huì)不到這個(gè)世界的變化,自己一個(gè)人圆裕,帶著手機(jī)拿著書(shū)去畫(huà)室或者圖書(shū)館或者經(jīng)信走廊以及校園的某...
    小場(chǎng)面閱讀 263評(píng)論 0 0