Log4j2的應(yīng)用和原理

系列

簡(jiǎn)介

?Log4j2是apache在Log4j的基礎(chǔ)上,參考logback架構(gòu)實(shí)現(xiàn)的一套新的日志系統(tǒng)。本文基于Log4j-2.12.1的版本進(jìn)行源碼分析惩系。
?本文的目的在于梳理Log4j2的Logger對(duì)象生成過(guò)程讹挎,借此理順Log4j2各組件之間的聯(lián)系。

Log4j2的用法

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" strict="true">

    <Properties>
        <Property name="filename">target/test.log</Property>
    </Properties>
    <Filter type="ThresholdFilter" level="trace"/>

    <Appenders>
        <Appender type="Console" name="STDOUT">
            <Layout type="PatternLayout" pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Filters>
                <Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
                <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
            </Filters>
        </Appender>
        <Appender type="Console" name="FLOW">
            <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
            <Filters>
                <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
                <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </Appender>
        <Appender type="File" name="File" fileName="${filename}">
            <Layout type="PatternLayout">
                <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
            </Layout>
        </Appender>
    </Appenders>

    <Loggers>
        <Logger name="test1" level="debug" additivity="false">
            <Filter type="ThreadContextMapFilter">
                <KeyValuePair key="test" value="123"/>
            </Filter>
            <AppenderRef ref="STDOUT"/>
        </Logger>

        <Logger name="test2" level="debug" additivity="false">
            <AppenderRef ref="File"/>
        </Logger>

        <Root level="trace">
            <AppenderRef ref="STDOUT"/>
        </Root>
    </Loggers>

</Configuration>
  • 根節(jié)點(diǎn)Configuration設(shè)置Log4j2本身的屬性躁锡。
  • Appenders設(shè)置多個(gè)Appender對(duì)象午绳,Appender內(nèi)部包含Layout和Filters。
  • Loggers設(shè)置Logger對(duì)象映之,包含Logger和Root兩個(gè)子節(jié)點(diǎn)拦焚。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jDemo {

    private static Logger logger= 
       LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            // 記錄trace級(jí)別的信息
            logger.trace("log4j2日志輸出:This is trace message.");
            // 記錄debug級(jí)別的信息
            logger.debug("log4j2日志輸出:This is debug message.");
            // 記錄info級(jí)別的信息
            logger.info("log4j2日志輸出:This is info message.");
            // 記錄error級(jí)別的信息
            logger.error("log4j2日志輸出:This is error message.");
        }
    }
}
  • 通過(guò)LogManager.getLogger()來(lái)獲取Logger對(duì)象進(jìn)行日志輸出。

配置文件解析

  1. 根節(jié)點(diǎn)Configuration有兩個(gè)屬性:status和monitorinterval,有兩個(gè)子節(jié)點(diǎn):Appenders和Loggers(表明可以定義多個(gè)Appender和Logger)杠输。
    • status:用來(lái)指定log4j本身的打印日志的級(jí)別赎败。
    • monitorinterval:用于指定log4j自動(dòng)重新配置的監(jiān)測(cè)間隔時(shí)間,單位是s,最小是5s抬伺。
  2. Appenders節(jié)點(diǎn)螟够,常見(jiàn)的有三種子節(jié)點(diǎn):Console、RollingFile峡钓、File妓笙。
    • Console節(jié)點(diǎn)用來(lái)定義輸出到控制臺(tái)的Appender。

      • name:指定Appender的名字能岩。
      • target:SYSTEM_OUT 或 SYSTEM_ERR,一般只設(shè)置默認(rèn):SYSTEM_OUT寞宫。
      • PatternLayout:輸出格式,不設(shè)置默認(rèn)為:%m%n拉鹃。
    • File節(jié)點(diǎn)用來(lái)定義輸出到指定位置的文件的Appender辈赋。

      • name:指定Appender的名字膏燕。
      • fileName:指定輸出日志的目的文件帶全路徑的文件名。
      • PatternLayout:輸出格式坝辫,不設(shè)置默認(rèn)為:%m%n。
    • RollingFile節(jié)點(diǎn)用來(lái)定義超過(guò)指定大小自動(dòng)刪除舊的創(chuàng)建新的Appender竭业。

      • name:指定Appender的名字。
      • fileName:指定輸出日志的目的文件帶全路徑的文件名未辆。
      • PatternLayout:輸出格式,不設(shè)置默認(rèn)為:%m%n咐柜。
      • filePattern:指定新建日志文件的名稱(chēng)格式兼蜈。
      • Policies:指定滾動(dòng)日志的策略,就是什么時(shí)候進(jìn)行新建日志文件輸出日志炕桨。
      • TimeBasedTriggeringPolicy:Policies子節(jié)點(diǎn)饭尝,基于時(shí)間的滾動(dòng)策略,interval屬性用來(lái)指定多久滾動(dòng)一次献宫,默認(rèn)是1 hour钥平。modulate=true用來(lái)調(diào)整時(shí)間:比如現(xiàn)在是早上3am,interval是4姊途,那么第一次滾動(dòng)是在4am涉瘾,接著是8am,12am...而不是7am捷兰。
      • SizeBasedTriggeringPolicy:Policies子節(jié)點(diǎn)立叛,基于指定文件大小的滾動(dòng)策略,size屬性用來(lái)定義每個(gè)日志文件的大小贡茅。
      • DefaultRolloverStrategy:用來(lái)指定同一個(gè)文件夾下最多有幾個(gè)日志文件時(shí)開(kāi)始刪除最舊的秘蛇,創(chuàng)建新的(通過(guò)max屬性),默認(rèn)是7個(gè)文件。
  3. Loggers節(jié)點(diǎn)顶考,常見(jiàn)的有兩種:Root和Logger赁还。
    • Root節(jié)點(diǎn)用來(lái)指定項(xiàng)目的根日志,如果沒(méi)有單獨(dú)指定Logger驹沿,那么就會(huì)默認(rèn)使用該Root日志輸出艘策。
      • level:日志輸出級(jí)別,共有8個(gè)級(jí)別渊季,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF朋蔫。
    • AppenderRef:Root的子節(jié)點(diǎn),用來(lái)指定該日志輸出到哪個(gè)Appender却汉。
      • Logger節(jié)點(diǎn)用來(lái)單獨(dú)指定日志的形式驯妄,比如要為指定包下的class指定不同的日志級(jí)別等。
      • level:日志輸出級(jí)別合砂,共有8個(gè)級(jí)別赎懦,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF励两。
      • name:用來(lái)指定該Logger所適用的類(lèi)或者類(lèi)所在的包全路徑,繼承自Root節(jié)點(diǎn)当悔。
      • AppenderRef:Logger的子節(jié)點(diǎn)盲憎,用來(lái)指定該日志輸出到哪個(gè)Appender,如果沒(méi)有指定饼疙,就會(huì)默認(rèn)繼承自Root.如果指定了,那么會(huì)在指定的這個(gè)Appender和Root的Appender中都會(huì)輸出慕爬,此時(shí)我們可以設(shè)置Logger的additivity="false"只在自定義的Appender中進(jìn)行輸出。
  4. 關(guān)于日志level.
    • 共有8個(gè)級(jí)別磅甩,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF卷要。
    • All:最低等級(jí)的僧叉,用于打開(kāi)所有日志記錄彪标。
    • Trace:是追蹤捞烟,就是程序推進(jìn)以下题画,你就可以寫(xiě)個(gè)trace輸出苍息。
    • Debug:指出細(xì)粒度信息事件對(duì)調(diào)試應(yīng)用程序是非常有幫助的表谊。
    • Info:消息在粗粒度級(jí)別上突出強(qiáng)調(diào)應(yīng)用程序的運(yùn)行過(guò)程爆办。
    • Warn:輸出警告及warn以下級(jí)別的日志距辆。
    • Error:輸出錯(cuò)誤信息日志跨算。
    • Fatal:輸出每個(gè)嚴(yán)重的錯(cuò)誤事件將會(huì)導(dǎo)致應(yīng)用程序的退出的日志.
    • OFF:最高等級(jí)的诸蚕,用于關(guān)閉所有日志記錄挫望。

log4j2核心組件

public class LoggerContext extends AbstractLifeCycle
        implements org.apache.logging.log4j.spi.LoggerContext, 
        AutoCloseable, Terminable, ConfigurationListener,
        LoggerContextShutdownEnabled {
    // 保存Logger對(duì)象的LoggerRegistry對(duì)象
    private final LoggerRegistry<Logger> loggerRegistry = 
                                          new LoggerRegistry<>();
    // 保存配置的Configuration對(duì)象
    private volatile Configuration configuration = new DefaultConfiguration();
}
  • LoggerContext通過(guò)LoggerRegistry保存Logger對(duì)象。
public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
    // PrivateConfig包含LoggerConfig 
    protected volatile PrivateConfig privateConfig;
    // LoggerContext對(duì)象 
    private final LoggerContext context;

    protected class PrivateConfig {
        // config fields are public to make them visible to Logger subclasses
        /** LoggerConfig to delegate the actual logging to. */
        public final LoggerConfig loggerConfig; // SUPPRESS CHECKSTYLE
        /** The current Configuration associated with the LoggerConfig. */
        public final Configuration config; // SUPPRESS CHECKSTYLE
        private final Level loggerConfigLevel;
        private final int intLevel;
        private final Logger logger;
        private final boolean requiresLocation;
    }
}
  • Logger通過(guò)PrivateConfig來(lái)保存LoggerConfig對(duì)象蛉幸。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}

public class LoggerConfig extends AbstractFilterable implements LocationAware {

    private List<AppenderRef> appenderRefs = new ArrayList<>();
    private final AppenderControlArraySet appenders = 
                new AppenderControlArraySet();
    private LoggerConfig parent;
    private Map<Property, Boolean> propertiesMap;
    private final List<Property> properties;
    private final Configuration config;
}

public class AppenderControl extends AbstractFilterable {

    private final ThreadLocal<AppenderControl> recursive = new ThreadLocal<>();
    private final Appender appender;
    private final Level level;
    private final int intLevel;
    private final String appenderName;
}
  • LoggerConfig通過(guò)AppenderControlArraySet保存AppenderControl。
  • AppenderControl保存Appender匹层。
  • LoggerConfig父類(lèi)包含F(xiàn)ilter對(duì)象升筏。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}

public abstract class AbstractAppender extends AbstractFilterable 
                              implements Appender, LocationAware {
    private final String name;
    private final boolean ignoreExceptions;
    private final Layout<? extends Serializable> layout;
}
  • Appender對(duì)象包含F(xiàn)ilter對(duì)象您访。
  • Appender對(duì)象包含Layout對(duì)象檀训。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}


public abstract class AbstractConfiguration 
      extends AbstractFilterable implements Configuration {

    protected final List<String> pluginPackages = new ArrayList<>();
    protected PluginManager pluginManager;
    private String name;
    private ConcurrentMap<String, Appender> appenders 
                                = new ConcurrentHashMap<>();
    private ConcurrentMap<String, LoggerConfig> loggerConfigs 
                              = new ConcurrentHashMap<>();
    private LoggerConfig root = new LoggerConfig();
    private final WeakReference<LoggerContext> loggerContext;
}
  • Configuration包含Appender對(duì)象峻凫。
  • Configuration包含LoggerConfig對(duì)象蔚晨。
  • Configuration包含F(xiàn)ilter對(duì)象。

Log4j2初始化流程

LogManager

  • LogManager是Log4J啟動(dòng)的入口多糠,后續(xù)的LoggerContext以及Logger都是通過(guò)調(diào)用LogManager的getLogger靜態(tài)方法獲得夹孔。
  • LogManager的static代碼塊初始化LoggerContextFactor對(duì)象 搭伤。
public class LogManager {
    public static String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
    private static volatile LoggerContextFactory factory;

    static {
        // step1 通過(guò)特定配置文件的配置信息獲取loggerContextFactory
        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
        final String factoryClassName = managerProps.getStringProperty(
                                                  FACTORY_PROPERTY_NAME);
        if (factoryClassName != null) {
            try {
                factory = LoaderUtil.newCheckedInstanceOf(
                  factoryClassName, LoggerContextFactory.class);
            } catch (final ClassNotFoundException cnfe) {
            } catch (final Exception ex) {
            }
        }

        if (factory == null) {
            final SortedMap<Integer, LoggerContextFactory> factories = 
                                                           new TreeMap<>();
            // step2 ProviderUtil中的getProviders()方法載入providers
            if (ProviderUtil.hasProviders()) {
                for (final Provider provider : ProviderUtil.getProviders()) {
                    final Class<? extends LoggerContextFactory> factoryClass 
                                = provider.loadLoggerContextFactory();
                    if (factoryClass != null) {
                        try {
                            factories.put(provider.getPriority(), 
                                        factoryClass.newInstance());
                        } catch (final Exception e) {
                        }
                    }
                }

                if (factories.isEmpty()) {
                    factory = new SimpleLoggerContextFactory();
                } else if (factories.size() == 1) {
                    factory = factories.get(factories.lastKey());
                } else {
                    factory = factories.get(factories.lastKey());
                }
            } else {
                // step3 默認(rèn)SimpleLoggerContextFactory
                factory = new SimpleLoggerContextFactory();
            }
        }
    }
}
  • 1.首先通過(guò)PropertiesUtil.getProperties()根據(jù)特定配置文件的配置信息獲取loggerContextFactory的類(lèi)邓尤。
  • 2.如果沒(méi)有找到對(duì)應(yīng)的Factory則通過(guò)ProviderUtil中的getProviders()方法載入providers季稳,通過(guò)provider的loadLoggerContextFactory方法載入LoggerContextFactory的實(shí)現(xiàn)類(lèi)澈魄。
  • 3.如果provider中沒(méi)有獲取到LoggerContextFactory的實(shí)現(xiàn)類(lèi)或provider為空铛漓,則使用SimpleLoggerContextFactory作為L(zhǎng)oggerContextFactory票渠。

根據(jù)配置文件加載Factory

public final class PropertiesUtil {

    private static String LOG4J_PROPERTIES_FILE_NAME = 
                                        "log4j2.component.properties";
    private static final PropertiesUtil LOG4J_PROPERTIES = 
                    new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);

    public static PropertiesUtil getProperties() {
        return LOG4J_PROPERTIES;
    }

    public PropertiesUtil(final String propertiesFileName) {
        this.environment = new Environment(
                  new PropertyFilePropertySource(propertiesFileName));
    }
}

public class PropertyFilePropertySource extends PropertiesPropertySource {

    public PropertyFilePropertySource(final String fileName) {
        super(loadPropertiesFile(fileName));
    }

    // 查找指定文件內(nèi)容并加載至Properties當(dāng)中
    private static Properties loadPropertiesFile(final String fileName) {
        final Properties props = new Properties();
        for (final URL url : LoaderUtil.findResources(fileName)) {
            try (final InputStream in = url.openStream()) {
                props.load(in);
            } catch (final IOException e) {
            }
        }
        return props;
    }
}
  • PropertiesUtil.getProperties()負(fù)責(zé)加載log4j2.component.properties文件。
  • log4j2.component.properties內(nèi)包含log4j2.loggerContextFactory的配置肠骆。

通過(guò)ProviderUtil加載Factory

public final class ProviderUtil {

    static String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";

    private ProviderUtil() {
        // 通過(guò)SPI機(jī)制進(jìn)行加載
        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
            try {
                loadProviders(classLoader);
            } catch (final Throwable ex) {
                
            }
        }
        // 查找log4j-provider.properties文件
        for (final LoaderUtil.UrlResource resource : 
                   LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
            loadProvider(resource.getUrl(), resource.getClassLoader());
        }
    }

    protected static void loadProviders(final ClassLoader classLoader) {
        // 通過(guò)SPI方式加載Provider
        final ServiceLoader<Provider> serviceLoader = 
                  ServiceLoader.load(Provider.class, classLoader);
        for (final Provider provider : serviceLoader) {
            if (validVersion(provider.getVersions()) 
                                && !PROVIDERS.contains(provider)) {
                PROVIDERS.add(provider);
            }
        }
    }
}
  • loadProviders的通過(guò)ServiceLoader實(shí)現(xiàn)SPI的加載制定的Factory。

生成默認(rèn)Factory

factory = new SimpleLoggerContextFactory();
  • 默認(rèn)的工廠(chǎng)為SimpleLoggerContextFactory莉钙。

LoggerContext

LoggerContext 包含了配置信息磁玉,并能創(chuàng)建log4j-api定義的Logger接口實(shí)例蚊伞。

public class LogManager {

    public static LoggerContext getContext(final boolean currentContext) {
        try {
            return factory.getContext(FQCN, null, null, 
                      currentContext, null, null);
        } catch (final IllegalStateException ex) {
            return new SimpleLoggerContextFactory().
              getContext(FQCN, null, null, currentContext, null, null);
        }
    }
  • 通過(guò)Log4jContextFactory的getContext來(lái)獲取LoggerContext對(duì)象时迫。
public class Log4jContextFactory implements LoggerContextFactory {

    private static ContextSelector createContextSelector() {
        // 省略其他代碼

        return new ClassLoaderContextSelector();
    }

    public LoggerContext getContext(final String fqcn, 
                                    final ClassLoader loader, 
                                    final Object externalContext,
                                    final boolean currentContext, 
                                    final URI configLocation, 
                                    final String name) {
        // 通過(guò)selector=ClassLoaderContextSelector來(lái)獲取ctx
        final LoggerContext ctx = 
          selector.getContext(fqcn, loader, currentContext, configLocation);
        if (externalContext != null && ctx.getExternalContext() == null) {
            ctx.setExternalContext(externalContext);
        }
        if (name != null) {
            ctx.setName(name);
        }
        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
            if (configLocation != null || name != null) {
                ContextAnchor.THREAD_CONTEXT.set(ctx);
                final Configuration config = ConfigurationFactory.getInstance()
                     .getConfiguration(ctx, name, configLocation);
                ctx.start(config);
                ContextAnchor.THREAD_CONTEXT.remove();
            } else {
                ctx.start();
            }
        }
        return ctx;
    }
}
  • 通過(guò)ClassLoaderContextSelector的getContext來(lái)獲取LoggerContext。
  • ctx.start()負(fù)責(zé)配置的解析碳想,這部分待后續(xù)進(jìn)行分析胧奔。
public class ClassLoaderContextSelector 
    implements ContextSelector, LoggerContextShutdownAware {

    public LoggerContext getContext(final String fqcn, 
            final ClassLoader loader, 
            final boolean currentContext,
            final URI configLocation) {
        if (currentContext) {
            final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
            if (ctx != null) {
                return ctx;
            }
            return getDefault();
        } else if (loader != null) {
            return locateContext(loader, configLocation);
        } else {
            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
            if (clazz != null) {
                return locateContext(clazz.getClassLoader(), configLocation);
            }
            final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
            if (lc != null) {
                return lc;
            }
            return getDefault();
        }
    }

    private LoggerContext locateContext(final ClassLoader loaderOrNull, 
                                         final URI configLocation) {
        final ClassLoader loader = loaderOrNull != null ? 
           loaderOrNull : ClassLoader.getSystemClassLoader();
        final String name = toContextMapKey(loader);
        AtomicReference<WeakReference<LoggerContext>> ref = 
                                      CONTEXT_MAP.get(name);
        if (ref == null) {
            if (configLocation == null) {
                ClassLoader parent = loader.getParent();
                while (parent != null) {
                    ref = CONTEXT_MAP.get(toContextMapKey(parent));
                    if (ref != null) {
                        final WeakReference<LoggerContext> r = ref.get();
                        final LoggerContext ctx = r.get();
                        if (ctx != null) {
                            return ctx;
                        }
                    }
                    parent = parent.getParent();
            
                }
            }
            LoggerContext ctx = createContext(name, configLocation);
            final AtomicReference<WeakReference<LoggerContext>> r = 
                                            new AtomicReference<>();
            r.set(new WeakReference<>(ctx));
            CONTEXT_MAP.putIfAbsent(name, r);
            ctx = CONTEXT_MAP.get(name).get().get();
            return ctx;
        }
        final WeakReference<LoggerContext> weakRef = ref.get();
        LoggerContext ctx = weakRef.get();
        if (ctx != null) {
            if (ctx.getConfigLocation() == null && configLocation != null) {
                ctx.setConfigLocation(configLocation);
            } 
            return ctx;
        }
        ctx = createContext(name, configLocation);
        ref.compareAndSet(weakRef, new WeakReference<>(ctx));
        return ctx;
    }

    protected LoggerContext createContext(final String name, 
                                          final URI configLocation) {
        return new LoggerContext(name, null, configLocation);
    }
}
  • 按照getContext岩遗、locateContext宿礁、createContext的邏輯創(chuàng)建LoggerContext梆靖。
  • CONTEXT_MAP通過(guò)線(xiàn)程安全的putIfAbsent保存新建的LoggerContext返吻。
  • ClassLoaderContextSelector負(fù)責(zé)LoggerContext的創(chuàng)建街佑。

配置解析過(guò)程

  • LoggerContext的start過(guò)程就是配置文件的解析過(guò)程,這部分邏輯后續(xù)補(bǔ)充捍靠。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沐旨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子榨婆,更是在濱河造成了極大的恐慌磁携,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纲辽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡璃搜,警方通過(guò)查閱死者的電腦和手機(jī)拖吼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)唾糯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人葡秒,你說(shuō)我怎么就攤上這事学少】勰遥” “怎么了盒至?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵酒唉,是天一觀(guān)的道長(zhǎng)网沾。 經(jīng)常有香客問(wèn)我醋旦,道長(zhǎng)咧最,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上宪迟,老公的妹妹穿的比我還像新娘意荤。我一直安慰自己捐寥,他們只是感情好睡互,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布壳炎。 她就那樣靜靜地躺著铲球,像睡著了一般掖鱼。 火紅的嫁衣襯著肌膚如雪褐墅。 梳的紋絲不亂的頭發(fā)上猾封,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天阵难,我揣著相機(jī)與錄音朱庆,去河邊找鬼。 笑死剧罩,一個(gè)胖子當(dāng)著我的面吹牛栓拜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纽门,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蝙搔!你這毒婦竟也來(lái)了勤晚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤产舞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后攘已,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體炮赦,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年样勃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吠勘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡峡眶,死狀恐怖剧防,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辫樱,我是刑警寧澤峭拘,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站狮暑,受9級(jí)特大地震影響棚唆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜心例,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一宵凌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧止后,春花似錦瞎惫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至歉糜,卻和暖如春乘寒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匪补。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伞辛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夯缺。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓蚤氏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親踊兜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子竿滨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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

  • 目錄 1.概述1.1.組件概覽1.2.靈活的配置1.2.1.插件發(fā)現(xiàn)機(jī)制1.2.2.插件裝配機(jī)制1.2.3.配置文...
    拉風(fēng)小野驢閱讀 8,133評(píng)論 0 4
  • 背景: 業(yè)務(wù)上面的代碼,肯定都是需要打印日志的,不論是之后定位問(wèn)題還是其他什么的于游。最基礎(chǔ)的訴求就是能夠?qū)⒉煌?jí)別的...
    蘇922閱讀 606評(píng)論 2 0
  • 1 最簡(jiǎn)單的log4j2的配置 log4j2在不配置的情況下,是把error和fatal級(jí)別的信息輸出到控制臺(tái),形...
    BinaryBang閱讀 674評(píng)論 0 0
  • 關(guān)注:CodingTechWork毁葱,一起學(xué)習(xí)進(jìn)步。 引言 ??對(duì)于一個(gè)線(xiàn)上程序或者服務(wù)而言贰剥,重要的是要有日志輸出头谜,...
    Hughman閱讀 1,051評(píng)論 0 3
  • log4j是一個(gè)被廣泛使用的Java日志記錄框架,通過(guò)使用該框架鸠澈,我們可以在自己的項(xiàng)目中根據(jù)自身需求靈活配置日志輸...
    LilacZiyun閱讀 5,929評(píng)論 0 7