项目上线时,需要对项目做安全检查,其中有两项是对输出日志进行分类和过滤掉日志中敏感字段。
项目使用Log4j日志系统,下面简单介绍下这两项要求的实现方式。

  1. 对日志进行分类,要求调用其他服务的API日志按照格式单独输出到一个文件。
    方式: 除根Logger外,再额外增加一个apiLogger,如下,
<!-- api logger的设置-->
 <logger name="log4j.logger.apiLogger" additivity="false">
        <level value ="INFO"/>
        <appender-ref ref="apiConsoleAppender"/>
        <appender-ref ref="apiMsgOutGoingAppender"/>
 </logger>
 
 <!-- 根logger的设置-->
 <root>
        <level value ="INFO"/>
        <appender-ref ref="ConsoleAppender"/>
        <appender-ref ref="DailyRollingFileAppender"/>
 </root>

注: (1) additivity设为false,则root中的配置就失效了。即使用root配置的日志不会在apiLogger的文件中出现;
(2) 每种logger指定两个appender,分别是在debug console和Linux 服务器日志文件中显示。
根日志的ConsoleAppender和DailyRollingFileAppender的配置如下:

<appender name="ConsoleAppender" class="org.apache.log4j.ConsoleAppender">
    <!-- 设置日志输出的样式 -->
    <layout class="org.apache.log4j.PatternLayout">
        <!-- 设置日志输出的格式 -->
        <param name="ConversionPattern" value="**** [%p] [%d{yyyy/MM/dd HH:mm:ss:SSS}] ******************************%n[Thread] %t%n[Class] %C%n[Method] %M%n[Message] %m%n%n" />
    </layout>
</appender>
<appender name="DailyRollingFileAppender" class="org.apache.log4j.DailyRollingFileAppender">
    <!-- 设置日志信息输出文件全路径名 -->
    <param name="File" value="/var/log/tomcat/hpc.log" />
    <!-- 设置日志多久回滚一次,即产生一个新的日志文件 -->
    <param name="DatePattern" value="'_'yyyy-MM-dd" />
    <!--日志编码格式-->
    <param name="Encoding" value="UTF-8" />
    <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
    <param name="Append" value="true" />
    <!-- 设置日志输出的样式 -->
    <layout class="org.apache.log4j.PatternLayout">
        <!-- 设置日志输出的格式 -->
        <param name="ConversionPattern" value="**** [%p] [%d{yyyy/MM/dd HH:mm:ss:SSS}] *************************%n[Thread] %t%n[Class] %C%n[Method] %M%n[Message] %m%n%n" />
    </layout>

    <!-- 日志过滤: adminPass, X-Auth-Token; 包含该字符串的信息不输出 -->
    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="adminPass" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="X-Auth-Token" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="PW" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="pwd" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

    <!-- password Password-->
    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="assword" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

    <!-- 过滤pstmt-, 所有的数据库操作均包含此字符串-->
    <filter class="org.apache.log4j.varia.StringMatchFilter">
        <param name="StringToMatch" value="pstmt-" />
        <param name="AcceptOnMatch" value="false" />
    </filter>

</appender>

apiLogger的apiConsoleAppender和apiMsgOutGoingAppender设置如下:

<!-- 将日志信息输出到文件,可以配置多久产生一个新的日志信息文件 -->
<appender name="apiConsoleAppender" class="org.apache.log4j.ConsoleAppender">
    <!-- 设置日志输出的样式 -->
    <layout class="org.apache.log4j.PatternLayout">
        <!-- 设置日志输出的格式 -->
        <param name="ConversionPattern" value='[%-5p] [Outgoing] %X{og.serverIp} [%d{yyyy-MM-dd HH:mm:ss,SSS z}] %X{og.respTime} "%X{og.visitUri}" %X{og.statusCode} %X{og.reqLen} %X{og.respLen} %n' />
    </layout>
</appender>
<appender name="apiMsgOutGoingAppender" class="org.apache.log4j.RollingFileAppender">
    <!-- 设置日志信息输出文件全路径名 -->
    <param name="File" value="/var/log/tomcat/interface.log" />
    <!--日志编码格式-->
    <param name="Encoding" value="UTF-8" />
    <!-- 设置是否在重新启动服务时,在原有日志的基础添加新日志 -->
    <param name="Append" value="true" />
    <!-- 设置文件最大size -->
    <param name="MaxFileSize" value="30MB" />
    <!-- 保留日志文件数的最大值 -->
    <param name="MaxBackupIndex" value="100" />
    <!-- 设置日志输出的样式 -->
    <layout class="org.apache.log4j.PatternLayout">
        <!-- 设置日志输出的格式 -->
        <param name="ConversionPattern" value='[%-5p] [Outgoing] %X{og.serverIp} [%d{yyyy-MM-dd HH:mm:ss,SSS z}] %X{og.respTime} "%X{og.visitUri}" %X{og.statusCode} %X{og.reqLen} %X{og.respLen} %n'/>
    </layout>
</appender>

注:(1) Log4j系统Appender组件负责日志的输出配置,包括输出目录,最大输出大小,输出文件数等;Layout组件负责日志输出的格式。
(2)JAVA代码在使用日志系统时,默认用根日志的配置输出,若想使用apiLogger,使用:
private static Logger log = Logger.getLogger("log4j.logger.apiLogger"); (3)日志layout中传入自定义参数,使用MDC类:

public static void configAndPrintAPILogger(Logger logger, int statusCode, String method, String url,
        long responseTime,
        int requestLength, int responseLength)
    {
        String endpoint = url.split("/")[2];
        MDC.put("og.serverIp", endpoint);
        MDC.put("og.respTime", responseTime);
        //截取 https://endpoint之后的
        MDC.put("og.visitUri", method + " " + url.substring(8 + endpoint.length()));
        MDC.put("og.statusCode", statusCode);
        MDC.put("og.reqLen", requestLength);
        MDC.put("og.respLen", responseLength);
        logger.info("");
    }

经上述配置后,日志的输出如下:

java日志全局脱敏且无代码入侵的代码实现 log4j配置日志脱敏_日志系统


2. 日志中过滤敏感字符,如password, PW, adminPass等。如DailyRollingFileAppender中所示,使用StringMatchFilter:

<!-- 过滤password Password-->
        <filter class="org.apache.log4j.varia.StringMatchFilter">
            <param name="StringToMatch" value="assword" />
            <param name="AcceptOnMatch" value="false" />
        </filter>

StringMatchFilter中使用msg.indexOf(StringToMatch)参数判断是否过滤,即该条日志包含指定字段,若AcceptOnMatch设置为false,则不输出。
需要注意的是,log4j日志系统采用xml格式时才能用该种方式过滤,properties格式时不能用此方法。