log4j2源码分析

目录

1.概述
1.1.组件概览
1.2.灵活的配置
1.2.1.插件发现机制
1.2.2.插件装配机制
1.2.3.配置文件基本元素与对象的映射关系
2.属性占位符
2.1.概述
2.2.Interpolator插值器
2.3.默认属性配置
3.Logger
3.1.配置示例
3.2.配置详解
3.3.Logger继承机制
4.Appender
4.1.概述
4.2.框架支持的Appender实现
4.3.常用Appender详解
4.3.1.ConsoleAppender
4.3.2.RollingFileAppender
5.Layout
5.1.概述
5.2.PatternLayout
5.2.1.模式字符串
6.Manager
7.Filter

1.概述

1.1.组件概览

在log4j2中,LogManager就是日志的门面,相当于slf4j-api中的LoggerFactory.
框架为每个类加载分配了一个单独的LoggerContext,用于管理所有创建出来的Logger实例.
ContextSelector则负责管理类加载器到对应的LoggerContext实例之间的映射关系.
log4j2中,有5个关键概念:

  • LoggerConfig:日志配置,用于整合多个Appender,进行日志打印.
  • Appender:追加器,用于操作Layout和Manager,往单一目的地进行日志打印.
  • Layout:布局,用于把LogEvent日志事件序列化成字节序列,不同Layout实现具有不同的序列化方式.
  • Manager:管理器,用于管理输出目的地,如:RollingFileManager用于管理文件滚动以及将字节序列写入到指定文件中.
  • Filter:过滤器,用于对LogEvent日志事件加以过滤,LoggerConfig和Appender都可以配置过滤器,也就是说日志事件会经过一总一分两层过滤.

组件架构如下:


组件架构

1.2.灵活的配置

1.2.1.插件发现机制

在log4j2中,一切皆插件,框架通过PluginRegistry扫描并发现插件配置.

PluginRegistry支持两种扫描方式

  • 一种是使用指定的ClassLoader读取classpath下所有的META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat文件,产生PluginType;
  • 另一种是扫描classpath下指定的packageName,内省带有@Plugin注解的类文件,产生PluginType.

插件配置以PluginType的形式保存在插件注册表中,PluginType的作用类似于spring中BeanDefinition,定义了如何创建插件实例.
插件类通过@PluginFactory注解或者@PluginBuilderFactory注解配置插件实例的实例化和属性注入方式.

1.2.2.插件装配机制

log4j2知道如何实例化插件后,我们就可以通过编写配置文件(如:log4j2.xml),进行插件的实例化和属性注入了.
Configuration全局配置对象负责保存所有解析到的配置.
通过ConfigurationFactory.getConfiguration()可以使用不同的工厂生产不同的配置对象,不同的Configuration实现可以解析不同格式的配置,如:xml,yaml,json等.

以xml文件为例,文件中每个元素都会最终对应一个插件实例,元素名称实际就是PluginType中的name,实例的属性可以从子元素对应的实例获取,也可以从自身元素的属性配置获取.

因此,xml中dom树的元素嵌套关系,也就是log4j组件实例的引用嵌套关系.

xml,yaml,json格式文件都可以描述这种嵌套关系,因此log4j2中定义了与文件格式无关的数据结构,Node来抽象配置.

AbstractConfiguration.setup()负责提取配置,形成Node树.
AbstractConfiguration.doConfigure()负责根据Node树,进行插件实例化和属性注入.

1.2.3.配置文件基本元素与对象的映射关系

序号 xml元素 工厂方法(类名.方法名) 对象类型
1 <Properties> PropertiesPlugin.configureSubstitutor() StrLookup
2 <Property> Property.createProperty() Property
3 <Loggers> LoggersPlugin.createLoggers() Loggers
4 <Logger> LoggerConfig.createLogger() LoggerConfig
5 <Root> RootLogger.createLogger() LoggerConfig
6 <AppenderRef> AppenderRef.createAppenderRef() AppenderRef
7 <Filters> CompositeFilter.createFilters() CompositeFilter
8 <Appenders> AppendersPlugin.createAppenders() ConcurrentMap<String, Appender>

2.属性占位符

2.1.概述

在log4j2中,环境变量信息(键值对)被封装为StrLookup对象,该对象作用类似于spring框架中的PropertySource.

在配置文件中,基本上所有的值的配置都可以通过参数占位符引用环境变量信息,格式为:${prefix:key}.

2.2.Interpolator插值器

Interpolator内部以Map<String,StrLookup>的方式,封装了很多StrLookuo对象,key则对应参数占位符${prefix:key}中的prefix.

同时,Interpolator内部还保存着一个没有prefix的StrLookup实例,被称作默认查找器,它的键值对数据来自于log4j2.xml配置文件中的<Properties>元素的配置.

当参数占位符${prefix:key}带有prefix前缀时,Interpolator会从指定prefix对应的StrLookup实例中进行key查询,

当参数占位符${key}没有prefix时,Interpolator则会从默认查找器中进行查询.

Interpolator中默认支持的StrLookup查找方式如下(StrLookup查找器实现类均在org.apache.logging.log4j.core.lookup包下):

序号 prefix 插件类型 描述
1(*) sys SystemPropertiesLookup 从jvm属性中查找
2(*) env EnvironmentLookup 从操作系统环境变量中获取value
3 marker MarkerLookup 判断以指定key为名称的Marker标签是否存在,存在则返回key,否则返回null
4 jvmrunargs JmxRuntimeInputArgumentsLookup 获取jmx的运行时输入参数
5 bundle ResourceBundleLookup 通过ResourceBundle查找value,格式:${prefix:bundleName:bundleKey}
6 java JavaLookup 获取jvm进程信息,只指定固定的key值,包括:(1)version:jdk版本(2)runtime:运行环境信息(3)vm:虚拟机名称(4)os:操作系统信息(5)hw:硬件信息(6)locale:地区信息
7 main MainMapLookup 暂未启用
8 log4j Log4jLookup 只支持两个key:(1)configLocation:获取log4j2配置文件的绝对路径(2)configParentLocation:获取配置文件所在目录的绝对路径
9 date DateLookup 以指定格式,获取当前系统时间或LogEvent的时间戳,通过key来指定日期格式字符串
10 sd StructuredDataLookup 从LogEvent中引用的StructuredDataMessage中获取value
11 ctx ContextMapLookup 从ThreadContext中获取value
12 map MapLookup 暂未启用
13 jndi JndiLookup 使用jndi(javax.naming)获取value

2.3.默认属性配置

注意:Properties元素一定要配置在最前面,否则不生效.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <Property name="customKey_1">customValue_1</Property>
        <Property name="customKey_2">customValue_2</Property>
    </Properties>
</Configuration>

3.Logger

3.1.配置示例

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">

    <Appenders>
        <Console name="console">
            <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root additivity="true" level="error" includeLocation="true" >
            <AppenderRef ref="console" level="info">
                <ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
            </AppenderRef>
            <Property name="customeKey">customeValue</Property>
            <ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
        </Root>
        <Logger name="com.lixin" additivity="true" level="info" includeLocation="true">
            <AppenderRef ref="console" level="info">
                <ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
            </AppenderRef>
            <Property name="customeKey">customeValue</Property>
            <ThresholdFilter level="warn" onMatch="NEUTRAL" onMismatch="DENY"/>
        </Logger>
    </Loggers>

</Configuration>

3.2.配置详解

  • additivity:日志可加性,如果配置为true,则在日志打印时,会通过Logger继承关系递归调用父Logger引用的Appender进行日志打印.
    注意:该属性默认为true.在递归打印日志时,会忽略父Logger的level配置
  • level:用于控制允许打印的日志级别上线,在配置示例中,只有级别<=info的LogEvent才会被放行,级别优先级顺序为OFF<FATAL<ERROR<WARN<INFO<DEBUG<TRACE<ALL
    注意:level属性的配置时可选的,在获取level时会通过Logger继承关系递归获取,RootLogger的级别默认为error,其他默认为null.也就是说,如果全都不配置level的话,则所有Logger级别都默认为error.
  • includeLocation:如果配置为true,则打印日志时可以附带日志点源码位置信息输出.同步日志上下文默认为true,异步默认为false.
  • LoggerConfig元素下可以单独配置Property元素,添加属性键值对,这些属性会在每次打印日志时,被追加到LogEvent的contextData中
  • LoggerConfig支持配置过滤器,在判断是否打印日志时,先过滤器判断过滤,然后再级别判断过滤.
  • AppenderRef:顾名思义,就是配置当前Logger引用的Appender.同时,AppenderRef也支持配置level和Filter,进行更细粒度的日志过滤
  • LoggerConfig等于总开关,AppenderRef则为各个子开关,两个开关都通过才能打印日志

3.3.Logger继承机制

log4j2框架会根据LoggerConfig的name建立对象之间的继承关系.这种继承机制与java的package很像,name以点进行名称空间分割,子名称空间继承父名称空间.
名称空间可以是全限定类名,也可以是报名.整个配置树的根节点就是RootLogger.
举例:假如我们的配置的Logger如下:

<Root/>
<Logger name="com"/>
<Logger name="com.lixin.DemoClass"/>
<Logger name="org"/>
<Logger name="org.springframework"/>
配置树

当通过LogManager.getLogger(name)获取Logger实例时,会根据name逐级递归直到找到匹配的LoggerConfig,或者递归到Root根节点为止.

4.Appender

4.1.概述

追加器,负责控制Layout进行LogEvent的序列化,以及控制Manager对序列化后的字节序列进行输出.

在log4j2.xml配置文件中,配置方式如下:

<Appenders>
    <具体的Appender插件名称>
    </具体的Appender插件名称>
</Appenders>

4.2.框架支持的Appender实现

序号 工厂方法 xml元素 具体类 作用
1 NullAppender.createAppender <Null> NullAppender
2 ConsoleAppender.newBuilder <Console> ConsoleAppender 控制台输出
3 FileAppender.newBuilder <File> FileAppender 往一个固定文件,流式追加日志
5 RollingFileAppender.newBuilder <RollingFile> RollingFileAppender 日志文件可滚动,滚动策略可配置,可按时间,文件大小等方式.流式追加日志
6 AsyncAppender.newBuilder <Async> AsyncAppender 内部引用一组appender,通过异步线程+队列方式调用这些appender
7 RollingRandomAccessFileAppender.newBuilder <RollingRandomAccessFile> RollingRandomAccessFileAppender 日志文件可滚动,使用RandomAccessFile追加日志
8 RandomAccessFileAppender.newBuilder <RandomAccessFile> RandomAccessFileAppender 往一个固定文件,使用RandomAccessFile追加日志
8 OutputStreamAppender.newBuilder <OutputStream> OutputStreamAppender
9 MemoryMappedFileAppender.newBuilder <MemoryMappedFile> MemoryMappedFileAppender 比RandomAccessFile性能高
10 JdbcAppender.newBuilder <JDBC> JdbcAppender
11 JpaAppender.createAppender <JPA> JpaAppender
12 JeroMqAppender.createAppender <JeroMQ> JeroMqAppender
13 KafkaAppender.newBuilder <Kafka> KafkaAppender
14 JmsAppender.newBuilder <JMS>
<JMSQueue>
<JMSTopic>
JmsAppender
15 Rewrite.createAppender <Rewrite> RewriteAppender
16 RoutingAppender.newBuilder <Routing> RoutingAppender 路由追加器
可根据pattern模式字符串,路由到内部管理的其他Appender实例,
支持LogEvent事件重写和Appender定期清理
17 CountingNoOpAppender.createAppender <CountingNoOp> CountingNoOpAppender
18 FailoverAppender.createAppender <Failover> FailoverAppender
19 ScriptAppenderSelector <ScriptAppenderSelector>
20 SmtpAppender.createAppender <SMTP> SmtpAppender
21 SocketAppender.newBuilder <Socket> SocketAppender
22 SyslogAppender.newBuilder <Syslog> SyslogAppender
23 WriterAppender.newBuilder <Writer> WriterAppender

4.3.常用Appender详解

4.3.1.ConsoleAppender

控制台追加器,用于把日志输出到控制台,一般本地调试时使用.
配置示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">

    <Appenders>
        <!-- follow和direct不能同时为true,如果follow为true则会跟随底层输出流的变化,direct为true则固定指向输出流 -->
        <Console name="console" target="SYSTEM_OUT" follow="false" direct="true">
            <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root additivity="true" level="error" includeLocation="true" >
            <AppenderRef ref="console" level="info"/>
        </Root>
    </Loggers>

</Configuration>

4.3.2.RollingFileAppender

文件滚动追加器,用于向本地磁盘文件中追加日志,同时可以通过触发策略(TriggeringPolicy)和滚动策略(RolloverStrategy)控制日志文件的分片,避免日志文件过大.
线上环境常用.

常用的触发策略包含两种:

  • TimeBasedTriggeringPolicy:基于时间周期性触发滚动,一般按天滚动
  • SizeBasedTriggeringPolicy:基于文件大小触发滚动,可以控制单个日志文件的大小上限

滚动策略的实现包含两种:

  • DefaultRolloverStrategy:默认滚动策略
    该策略内部维护一个最小索引和最大索引,每次滚动时,会删除历史文件,之后剩余文件全部进行一轮重命名,最后创建新的不带有索引后缀的文件进行日志追加


    默认策略
  • DirectWriteRolloverStrategy:直接写滚动策略
    该策略内部会维护一个一直自增的文件索引,每次滚动时直接创建新的带有索引后缀的文件进行日志追加,同步清理历史的文件.


    直接写策略

配置示例如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" dest="err" verbose="false">

    <Properties>
        <Property name="logDir">/Users/lixin46/workspace/demo/logdemo/logs</Property>
        <Property name="pattern">%date{yyyy-MM-dd HH:mm:ss.SSS} %C.%M %message%n</Property>
    </Properties>
    <Appenders>
        <Console name="console" >
            <PatternLayout pattern="${pattern}"/>
        </Console>
        <!-- 使用DirectWriteRolloverStrategy策略时,不需要配置fileName -->
        <RollingFile name="fileAppender" fileName="${logDir}/request.log" filePattern="${logDir}/request.log-%d-%i">
            <PatternLayout pattern="${pattern}"/>
            <!-- 所有策略中,只要任意策略满足就会触发滚动 -->
            <Policies>
                <!-- 滚动时间周期,只有数量,单位取决于filePattern中%d的配置 -->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10b"/>
            </Policies>
            <!-- 限制最多保留5个文件,索引自增 -->
            <!--<DirectWriteRolloverStrategy maxFiles="5"/>-->
            <!-- 限制最多保留5个文件,索引从2到6 -->
            <DefaultRolloverStrategy fileIndex="max" min="2" max="6"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <Root level="info">
            <AppenderRef ref="console" level="info"/>
            <AppenderRef ref="fileAppender" level="info" />
        </Root>
    </Loggers>

</Configuration>

5.Layout

5.1.概述

布局对象,职责是把指定的LogEvent转换成可序列化对象(如:String),或者直接序列化成字节数组.

log4j2支持很多的序列化格式,如:普通模式字符串,JSON字符串,yaml字符串,XML格式字符串,HTML字符串等等.

类体系如下:


layout类体系

5.2.PatternLayout

模式布局是我们最常使用的,它通过PatternProcessor模式解析器,对模式字符串进行解析,得到一个List<PatternConverter>转换器列表和List<FormattingInfo>格式信息列表.

在PatternLayout序列化时,会遍历每个PatternConverter,从LogEvent中取不同的值进行序列化输出.

5.2.1.模式字符串

模式字符串由3部分组成,格式为:%(格式信息)(转换器名称){选项1}{选项2}...

  • 格式信息
    数据结构如下:
public final class FormattingInfo {

    // 默认配置,右对齐,长度不限,左侧截断
    private static final FormattingInfo DEFAULT = new FormattingInfo(false, 0, Integer.MAX_VALUE, true);
    // 字段最小长度
    private final int minLength;
    // 字段最大长度
    private final int maxLength;
    // 是否左对齐,默认为false,长度过短时,左侧填充空白
    private final boolean leftAlign;
    // 是否左侧截断,默认为true,长度过长时,删除左侧内容
    private final boolean leftTruncate;
}

模式字符串的格式为:
%-(minLength).-(maxLength)(转换器名称){选项字符串}
minLength代表字段的最小长度限制,当字段内容长度小于最小限制时,会进行空格填充.
minLength前面的-负责控制对齐方式,默认为右对齐(左边空格填充),如果加上-,则会切换为左对齐方式(右边空格填充)
maxLength代表字段的最大长度限制,当字段内容长度大于最大限制时,会进行内容阶段
maxLength前面的-负责控制阶段方向,默认为左侧阶段,如果加上-,则会切换为右侧阶段
minLength和maxLength之间用点分隔.
格式信息中所有属性都是可选的,不配置,则使用默认值

  • 转换器名称

log4j2会通过PluginManager收集所有类别为Converter的插件,同时分析插件类上的@ConverterKeys注解,获取转换器名称,并建立名称到插件实例的映射关系.
PatternParser识别到转换器名称的时候,会查找映射.

框架支持的所有转换器如下:

序号 名称 类型 描述
1 d
date
DatePatternConverter 日志的时间戳
2 p
level
LevelPatternConverter 日志级别
3 m
msg
message
MessagePatternConverter 日志中的消息内容
4 C
class
ClassNamePatternConverter 日志打印点所在类的类名
注意:需要给LoggerincludeLocation="true"属性开启位置
5 M
method
MethodLocationPatternConverter 日志打印点所在方法的方法名
注意:需要给LoggerincludeLocation="true"属性开启位置
6 c
logger
LoggerPatternConverter Logger实例的名称
7 n LineSeparatorPatternConverter 专门追加换行符
8 properties Log4j1MdcPatternConverter
9 ndc Log4j1NdcPatternConverter
10 enc
encode
EncodingPatternConverter
11 equalsIgnoreCase EqualsIgnoreCaseReplacementConverter
12 equals EqualsReplacementConverter
13 xEx
xThroable
xException
ExtendedThrowablePatternConverter
14 F
file
FileLocationPatternConverter 注意:需要给LoggerincludeLocation="true"属性开启位置
15 l
location
FullLocationPatternConverter 相当于%C.%M(%F:%L)
注意:需要给LoggerincludeLocation="true"属性开启位置
16 highlight HighlightConverter
17 L
line
LineLocationPatternConverter 日志打印点的代码行数
注意:需要给LoggerincludeLocation="true"属性开启位置
18 K
map
MAP
MapPatternConverter
19 marker MarkerPatternConverter 打印完整标记,格式如:标记名[父标记名[祖父标记名]],一个标记可以有多个父标记
20 markerSimpleName MarkerSimpleNamePatternConverter 只打印标记的名称
21 maxLength
maxLen
MaxLengthConverter
22 X
mdc
MDC
MdcPatternConverter LogEvent.getContextData()映射诊断上下文
23 N
nano
NanoTimePatternConverter
24 x
NDC
NdcPatternConverter LogEvent.getContextStack()嵌套诊断上下文
25 replace RegexReplacementConverter
26 r
relative
RelativeTimePatternConverter
27 rEx
rThrowable
rException
RootThrowablePatternConverter
28 style StyleConverter
29 T
tid
threadId
ThreadIdPatternConverter 线程id
30 t
tn
thread
threadName
ThreadNamePatternConverter 线程名称
31 tp
threadPriority
ThreadPriorityPatternConverter 线程优先级
32 ex
throwable
Exception
ThrowablePatternConverter 异常
33 u
uuid
UuidPatternConverter 生成一个uuid,随日志一起打印,用于唯一标识一条日志
34 notEmpty
varsNotEmpty
variablesNotEmpty
VariablesNotEmptyReplacementConverter
  • 选项字符串

有时我们需要对特定的转换器进行特殊的配置,如:给DatePatternConverter配置时间格式,这个时候需要通过选项字符串配置.
PatternParser会提取模式字符串中的所有选项,保存在一个List<String>中,每个{}包裹的内容作为一个选项.
当创建转换器时,框架会自动扫描转换器类中声明的静态工厂方法newInstance,同时支持两种可选的形参,一种是Configuration,另一种String[]则会注入选项列表.
选项列表的识别由不同的转换器各自定义.

最后,以一个实际的例子解释配置:
日志会输出时间,类名,方法名,消息以及一个换行符.
同时,我们给DatePatternConverter指定了了时间格式,并且限制全限定类名最小长度为5,右截断,最大为10,左对齐.

<PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %-5.-10C.%M %message%n"/>

6.Manager

管理器的职责主要是控制目标输出流,以及把保存在ByteBuffer字节缓冲区中的日志序列化结果,输出到目标流中.
如:RollingFileManager需要在每次追加日志之前,进行滚动检查,如果触发滚动还会创建新的文件输出流.
manager继承体系如下:


manager继承体系

7.Filter

过滤器的核心职责就是对LogEvent日志事件进行匹配,匹配结果分为匹配和不匹配,结果值有3种:接受,拒绝,中立.可由用户自定义匹配和不匹配的行为结果.

所有实现了Filterable接口的组件都可以引用一个过滤器进行事件过滤,包含LoggerConfigAppenderControl等.

框架实现的过滤器如下:

序号 工厂方法(类名.方法名) xml元素 作用
1 BurstFilter.newBuilder <BurstFilter>
2 DynamicThresholdFilter.createFilter <DynamicThresholdFilter>
3 LevelRangeFilter.createFilter <LevelRangeFilter>
4 MapFilter.createFilter <MapFilter>
5 MarkerFilter.createFilter <MarkerFilter>
6 RegexFilter.createFilter <RegexFilter>
7 ScriptFilter.createFilter <ScriptFilter>
8 StructuredDataFilter.createFilter <StructuredDataFilter>
9 ThreadContextMapFilter.createFilter <ThreadContextMapFilter><ContextMapFilter>
10 ThresholdFilter.createFilter <ThresholdFilter> 根据LogEvent的级别进行过滤,如果LogEvent.level<=ThresholdFilter.level,则返回匹配的结果,否则返回不匹配的结果.如:过滤器为info,日志为error,则error<=info返回匹配结果
11 TimeFilter.createFilter <TimeFilter> 判断日志时间是否在指定的时间区间内
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 4,979评论 0 6
  • 在应用程序中添加日志记录总的来说基于三个目的:监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析...
    时待吾阅读 5,019评论 1 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • 拆书部分 《如何听如何说》,151页 举例时应该精挑细选,目的是把你的概括性陈述说得更生动、更易懂一些。许多人感到...
    听风过而阅读 249评论 0 0
  • 生活中到处存在着经济学,经济学渗透在生活的方方面面。只是我们混然不觉而已。 改变习惯思维,善于观察身边的事物并学会...
    灵魂兽者阅读 859评论 0 0