需求: 将日志按照不同的模块和日志级别输入到不同的日志文件里.
    实现方式1:
        最初的想法是用 LoggerFactory.getLogger(logName),然后为不同的logName 定义不同的logger指向不同的FileAppender
        缺点:由于Logger 的名字改变,不再能根据每个类的名字动态调整日志级别,对错误排查影响较大
    
    
    实现方式2:
        利用Log4j2的Lookup功能动态构建文件名 参考https://logging.apache.org/log4j/2.x/manual/lookups.html
        缺点是这个只能在Logger 定义之前指定一个模块:如
        static {
            System.setProperty("module", "module1");
        }
        public static final Logger logger = LoggerFactory.getLogger(Module1.class);

        
        没办法在运行时动态切换日志文件
    
    实现方式3:
        按利用log4j2 的路由功能 (RoutingAppender)可以根据上下文变量将日志动态路由到的不同的文件
        具体配置如下:
        <RollingFile name="RollingFilePre" fileName="test-module1.log" filePattern="test-module1.%d{yyyy-MM-dd}.%i.log" append="true">
            <DynaJsonLayout compact="true" eventEol="true"/>    
            <Policies>
                <SizeBasedTriggeringPolicy size="1048576"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="200"/>
        </RollingFile>     
        <RollingFile name="RollingFileRule" fileName="test-module2.log" filePattern="test-module2.%d{yyyy-MM-dd}.%i.log" append="true">
            <DynaJsonLayout compact="true" eventEol="true"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="1048576"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <DefaultRolloverStrategy max="200"/>
        </RollingFile>     
        <Routing name="Routing" >
            <Routes pattern="${sys:module}">
                <Route key="val1" ref="RollingFilePre">
                </Route>
                <Route key="val2" ref="RollingFileRule">
                </Route>
            </Routes>
        </Routing>

        
        这样可以在程序中随时调用 System.setProperty() 来切换日志文件, 也可以通过其他选项比如ThreadContext property 来动态切换
    
        注意:如果需要程序中动态切换文件,route 和 异步日志 async 会有一定的冲突,切换时可能数据还在内存队列里,这样可能打不到正确的文件里
        
        按日志级别分文件则是另外一种思路,利用ThresholdFilter 将日志同时输出到A,B两个Appender, 再根据日志级别过滤. 比如A只接收Error 级别的日志
        B只接收Error以下级别的日志,奇怪的是Log4j2 没有把两者统一起来
        <root level="debug">  
            <appender-ref ref="Console"></appender-ref>
            <appender-ref ref="RoutingFileAsync"/>  
        </root>
        <Async name="RoutingFileAsync" bufferSize="300000">
            <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            <AppenderRef ref="Routing"/>
            <ArrayBlockingQueue/>
        </Async>
        <Console name="Console" target="SYSTEM_OUT">  
            <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="ACCEPT"/>
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>  
        </Console>      

    
LOGSTASH采集:
     利用Log4j2   JsonLayout,程序中会有一个工具类将错误日志格式变成JSON的,类似于 {"errorCode":xxx, "errorMessage":xxx}
     但是JsonLayout 会把日志报文统一放到message 属性中,这样在日志记录进入Elasticsearch 仍然无法直接被搜索,我们的期望是将
     JSON消息报文中的属性展开成多个顶级属性而不是嵌套在message中
     一种思路是在日志从KAFKA到Elasticsearch 的时候做一些转换,不确定Logstash 的mutate是否支持
     另一种思路是定制一个自己的JsonLayout 重载toSerializable方法来自定义输出的格式和内容, 具体实现步骤如下
     1.定义 DynaLogEvent 继承了log4j 原生的 LogEvent, 增加了一个Map 类型的 attributes 属性,如果原生Log4jEvent 中的Message是Json格式则就把解析出的键值对存到
     attributes 中  
    
     2. 定义了 DynaJsonLayout 通过 Plugin Annotation 注册到Log4j2 中,同时log4j2.xml 中需要声明 <configuration packages="org.apache.logging.log4j.core.layout">
     来指定扫描插件的路径 DynaJsonLayout 主要就是重载了 toSerializable 方法,将原生的 LogEvent 转化为 DynaLogEvent 并通过 json ObjectWriter 输出成json 格式
     ObjectWriter 使用了定制的 DynaLog4jJsonObjectMapper, 通过工厂类DynaJacksonFactory来构建,DynaJsonLayout 初始化时将 DynaLog4jJsonObjectMapper
     注入ObjectWriter 中
    
     3. 实践中发现attributes 并不能被Log4jJsonObjectMapper正确的序列化 (注:Log4j2的Log4jJsonObjectMapper使用了大量jackson的高级技巧来定制LogEvent的序列化,
     如Module, Mixin 等,大家有兴趣可以阅读下代码,这里不详细展开了), 所以为DynaLogEvent中的attributes属性添加了JsonIgnore Annotation 避免它被自动序列化,
     并定制了DynaLogEventSerializer 来自定义序列化 attributes, DynaLogEventSerializer通过 DynaLog4jJsonModule 注册到 DynaLog4jJsonObjectMapper 中
    
     源代码:https://github.com/wispershadow/myopensources/tree/master/jsonlogger