精华内容
下载资源
问答
  • 即插即用 logback-spring.xml 放在resources目录下即可 ...-- 日志根目录--> <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="D:\projectLog\logs"/> <!-

    即插即用

    logback-spring.xml
    放在resources目录下即可

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    
        <!-- 日志根目录-->
        <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="D:\projectLog\logs"/>
    	<!-- <springProperty scope="context" name="LOG_HOME" source="logging.path" defaultValue="/home/logs"/> -->
    
        <!-- 日志级别 -->
        <springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="info"/>
    
        <!--  标识这个"STDOUT" 将会添加到这个logger -->
        <springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>
    
        <!-- 日志文件名称-->
        <property name="LOG_PREFIX" value="log" />
    
        <!-- 日志文件编码-->
        <property name="LOG_CHARSET" value="UTF-8" />
    
        <!-- 日志文件路径+日期-->
        <property name="LOG_DIR" value="${LOG_HOME}/%d{yyyyMMdd}" />
    
        <!--对日志进行格式化-->
        <property name="LOG_MSG" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n"/>
    
        <!--文件大小,默认10MB-->
        <property name="MAX_FILE_SIZE" value="50MB" />
    
        <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
        <property name="MAX_HISTORY" value="10"/>
    
        <!--输出到控制台-->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <!-- 输出的日志内容格式化-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${LOG_MSG}</pattern>
            </encoder>
        </appender>
    
        <!-- 定义 ALL 日志的输出方式:-->
        <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日志文件路径,日志文件名称-->
            <File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>
    
            <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    
                <!--日志文件路径,新的 ALL 日志文件名称,“ i ” 是个变量 -->
                <FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}%i.log</FileNamePattern>
    
                <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
                <MaxHistory>${MAX_HISTORY}</MaxHistory>  
    
                <!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>         
    
            </rollingPolicy>
    
            <!-- 输出的日志内容格式化-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${LOG_MSG}</pattern>
            </encoder>
        </appender>
    
        <!-- 定义 ERROR 日志的输出方式:-->
        <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!-- 下面为配置只输出error级别的日志 -->
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <OnMismatch>DENY</OnMismatch>
                <OnMatch>ACCEPT</OnMatch>
            </filter>
            <!--日志文件路径,日志文件名称-->
            <File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>
    
            <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    
                <!--日志文件路径,新的 ERR 日志文件名称,“ i ” 是个变量 -->
                <FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}%i.log</FileNamePattern>
    
                <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
                <MaxHistory>${MAX_HISTORY}</MaxHistory>
    
                <!--当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB-->
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
    
            <!-- 输出的日志内容格式化-->
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>${LOG_MSG}</pattern>
            </encoder>
        </appender>
    
        <logger name="org.springframework"     level="ERROR" />
        <logger name="org.apache.commons"      level="ERROR" />
        
        <root level="${LOG_ROOT_LEVEL}">
            <appender-ref ref="${STDOUT}"/> <!-- 是否在控制臺輸出日誌 -->
            <appender-ref ref="FILE_ALL"/>
            <appender-ref ref="FILE_ERROR"/>
        </root>
    
    </configuration>
    
    
    展开全文
  • 日志打印规范

    千次阅读 2020-11-28 13:31:55
    文章目录打印日志的目的日志级别ERRORWARNINFODEBUG如何打印日志打印日志的位置打印输出哪些内容告警告警分级谁来处理告警拾遗空指针异常入参校验分布式链路追踪 灵魂之问:撸了那么多年代码,你真的会打日志吗? ...

    正文在下面,先打个广告:
    在这里插入图片描述

    灵魂之问:撸了那么多年代码,你真的会打日志吗?

    打印日志的目的

    不仅是初学者,很多老码农也不能很好的打印日志(注意 : 技术大佬不是本文的目标读者),本质的原因是没有意识到为什么要打印日志,打印日志的目的是什么,这里我就为大家介绍一下。

    • 最重要的原则:在确保日志数量有限的情况下,可以快速定位问题。你仔细想想,在你遇到问题的时候,尤其是生产环境,是不是经常无法快速定位问题,明明打印了日志,却没有上下文,不知道哪个请求,哪个用户,输入参数是什么,然后只能去跟领导说:我要多打一些日志,发布一些才能定位问题。
    • 跟踪应用的BUG/警告。日志可以帮助开发者和软件测试人员快速跟踪程序崩溃的原因
      -跟踪性能下降的问题范围。很多时候,性能问题,很难在开发过程中暴露出来,这需要进行全方位的测试跟踪,而通过日志提供的详细执行时间记录可以很方便的找出应用的性能瓶颈
    • 跟踪操作流程。通过对日志跟踪,你可以获取操作发生的具体环境,操作的结果
    • 排查疑难杂症。很多问题可能开发测试无法发现,或者由于环境不同,请求量不同,导致一些问题在前期开发测试期间很难被发现,此时日志就发挥重要作用了。
    • 了解项目的运行状态;了解一些长流程的执行情况
    • 商业用途。很多大的企业,都会根据日志做一些技术分析,有一些是技术上的,如分析性能瓶颈,热点业务热点接口,是否被爬虫爬取,网站的TPS,响应耗时等等。有一些是产品或商业层面的,如分析用户喜好,用户习惯,AB测试等等

    无论做什么事情,都要搞明白为什么要做,才能做的更好!思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

    日志级别

    日志打印通常有四种级别,从高到低分别是:ERROR、WARN、INFO、DEBUG。

    ERROR

    ERROR:该级别的错误需要马上被处理,当ERROR错误发生时,已经影响了用户的正常访问,是需要马上介入并处理的。常见的ERROR异常有:空指针异常,数据库不可用,服务不可用,关键用例无法继续执行等。

    ERROR是错误的意思,但不代表出现异常的地方就该打ERROR。
    ERROR是相对程序正确运行来说的,如果出现了ERROR那就代表出问题了,开发人员必须要查一下原因,或许是程序问题,或许是环境问题,或许是理论上不该出错的地方出错了。总之,如果你觉得某个地方出问题时需要解决,就打ERROR,如果不需要解决就不要打ERROR。

    如,接口入参检查就不应该使用ERROR级别日志,甚至都不应该打印日志,而是直接返回错误码,交由上层调用方处理。
    否则频繁的告警会使运维,开发对错误告警麻木,无效的告警会将真正有问题的错误淹没掉。

    哪些错误应该打印ERROR级别?

    • 读写配置文件失败
    • 配置信息错误
    • 网络断线
    • 数据库不可用(这不是绝对的,后面会讲报警升级)
    • 所有第三方对接的异常(包括第三方返回错误码)
    • 所有影响核心功能使用的异常

    WARN

    WARN:不应该出现但是不影响程序、当前请求正常运行的异常情况。对于WARN级别的日志,虽然不需要管理员马上处理,但是也需要引起重视。
    WARN日志有两种级别:一个是解决方案存在明显的问题(例如Try Catch语句中由于不确定会出现什么异常,而用Exception统一捕获抛出的问题),另一个是潜在的问题和建议(例如系统性能可能会伴随着时间的迁移逐渐不能满足服务需要,或者非核心接口的偶尔超时)。应用程序可以容忍这些信息,不过它们应该被及时地检查及修复。

    如果WARN出现的过于频繁或次数太多,那就代表你要检查一下程序或环境或依赖程序是否真的出问题了。此时应该监控warn日志的次数,达到阈值告警并处理。

    哪些错误应该打印WARN级别?

    • 异常:由于在程序运行之前不能明确异常引发的原因,异常只进行了简单的捕获抛出,需要将这种笼统处理的异常打印为WARN格式的日志,提醒管理员干预处理。不过如果是核心业务,则需要打印ERROR级别
    • 有容错机制的时候出现的错误情况
    • 找不到配置文件,但是系统能自动创建配置文件(或使用默认配置)
    • 性能即将接近临界值的时候(也要看是否是核心系统,核心业务,如果是,则需要打印ERROR,报警,并自动或人工扩容)
    • 业务异常的记录,危险操作
    • 限流,降级,熔断的操作,都需要WARN级别日志打印
    • 出于提醒目的的日志。比如2B的业务中,用户数据不完善, 但不至于严重到系统(接口)不可用程度,此时就应该打印warn级别的日志,然后通知业务方进行数据补全。

    INFO

    INFO:主要用于记录系统运行状态、核心用例执行结果、用户的操作行为等信息。该日志级别,常用于反馈系统当前状态给最终用户。

    需要打印的INFO级别的日志:

    • 接口请求与响应。通常情况下,无论是dubbo等RPC框架,还是http/https等restful风格的访问,只要涉及到微服务间的访问,都要打印入参与出参。
    • 一些需要观测执行情况的行为:如定时任务(尤其是分布式任务)等/大批量数据执行进度(批处理)/耗时操作等等
    • 不符合业务逻辑预期(尤其核心业务):打印关键的参数,要能从这些参数中清楚地看出,谁的操作与预期不符,为什么与预期不符。并且唯一定位到这条日志,要包含用户id或者流水号
    • 系统模块的入口与出口处:可以是重要方法级或模块级,记录它的输入与输出,方便定位
    • 非预期执行:为程序在“有可能”执行到的地方打印日志(通常与业务挂钩,打印内容要看得出为什么走到这个分支 )
      • switch case语句块中的default
      • if…else if…else中很少出现的else情况
      • try catch语句块中catch分支。
    • 服务状态变化(尽可能记录线索):程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场,推断程序运行过程
    • 程序运行耗时:通过它可以跟踪为什么系统响应变慢或者太快。再配合链路追踪系统,可以方便的定位到那个系统哪个方法耗时长

    DEBUG

    DEBUG:该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。通过该种日志,可以查看某一个操作每一步的执行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。可以保证在不重现错误的情况下,也可以通过DEBUG级别的日志记录对问题进行诊断。
    一般来说,在生产环境中,不会输出该级别的日志。

    如何打印日志

    打印日志的位置

    • 程序入口:入口包括方法入口,对外暴露接口等。在入口打印日志是因为这个时候传递进来的参数没有经过任何处理,将它打印在日志文件中能一眼就知道程序的原始数据是否符合我们的预期,是不是传递进来的原始数据就出现 的问题。
    • 计算结果:测试关心的程序的输出结果是否符合预期,那么对于计算过程不应该关心,仅给出计算结果就能判断是否符合预期。
    • 重要信息(或关键步骤):这一点可能很宽泛,因为不同的业务逻辑重点可能并不一样,例如在有的重要参数不能为空,此时就需要判断是否为空,如果为空则记录到日志中;还有的例如传递进来的参数经过一系列的算法处理过后,此时也需要打印日志来查看是否计算正确。但切记,尽量不要直接在for循环中打印日志,特别是for循环特别大时,这样你的日志可能分分钟被冲得不见踪迹,甚至带来性能上的影响。
    • 异常捕获:在异常打印出详细的日志能让你快速定位错误在哪里,例如在程序抛出异常捕获时,在平时我们经常就是直接在控制台打印出堆栈信息e.printStackTrace(),但在实际的生产环境更加艰苦,更别说有IDE来让你查看控制台信息,此时就需要我们将堆栈信息记录在日志中,以便发生异常时我们能准确定位程序在哪里出错。
    • 分支:无论是if-else还是switch,需要打印相关的日志,可以快速的看到根据什么参数走到了哪个分支。
    • 批量操作:涉及到数量的操作要打印log,比如查询数据库和批量拷贝文件、上传下载、批量格式转换等批量操作
    • 调用其他系统接口的前后:打印所调用接口的系统名称/接口名称和传入参数/响应参数,这样能方便做问题定界,通过这两条日志可以清楚地看出是否是所调用的系统出现了问题

    打印输出哪些内容

    • 通用信息
      • 级别,例如 warn、info 和 error
      • 时间
      • 进程ID
      • 线程ID:线程ID相当重要,在解决多线程问题的时候,这一项信息是必不可少的
      • 模块:哪个类,假如是分布式服务的话,需要指明是哪个服务的哪个类
      • Filter:主要用于查找某一类LOG时候方便,这个Filter是开发人员在开发过程中自己定义的,可以是方法名/业务模块名称/功能名称等等。
      • 请求id: 一个请求到来会经过很多服务的很多方法,一个唯一的请求id可以将该请求的所有日志串联起来
      • 耗时:可以通过拦截器或AOP来打印某服务或某核心方法的耗时,可以使用一些agent来拦截方法执行时间。

    如某公司通用日志打印

    [INFO] 2020-11-26 14:06:59.330[ConsumeMessageThread_56][2011280MD13623573890648168927232] c.w.w.r.h.SyncRequestHandler:108
    
    • 入参出参
      入参和出参不是每个地方都要打印的,只在服务对外暴露的API/核心方法提供即可。注意打印的参数一定要是原始参数,而不是经过转换后的参数,防止你参数转换的代码有问题。
    • 会话标识。能知道是哪个客户端或者是哪个用户触发、登陆账号、seesion信息等。
    • 场景信息(谁,什么功能等);
    • 状态信息(开始,中断,结束)以及重要参数;
    • 日志格式:日志格式统一使用主语+谓语+宾语+状语的格式,日志打印应该遵从人类的自然语言,任何没有开发经验或是外行人都能从你的日志中捕获到有用的信息。使用 [] 进行参数变量隔离,这样的格式写法,可读性更好,对于排查问题更有帮助

    日志框架

    java生态中有很多日志框架可供选择,如log4j, log4j2, logback等等。他们各自的特点不是本文重点,请读者自行google。
    这里需要强调一点:
    应用中不可直接使用日志系统( Log4j、 Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory; 
    private static final Logger logger = LoggerFactory.getLogger(Abc.class);
    

    告警

    告警分级

    任何系统都有很多的接口和方法,首先要根据业务划分优先级,指定不同的业务级别。

    除了上面降到的4种基本的告警级别外,还要根据业务级别划分不同的告警级别。

    举个例子,比如接口超时,如果是核心业务,需要打印ERROR日志,并且出现一次就要报警;如果不是核心业务,那么可能只需要打印warn日志,然后配置在一定时间窗口内出现N次才报警处理。

    谁来处理告警

    • 客服
      当一个产品比较完善的时候,系统中可能出现的所有异常都已经暴露的差不多了,那么此时需要将各种异常进行分类汇总,并给出解决方案,交由客服。当有用户反馈时,客服会拦截处理一部分异常。
      客服可以处理的异常通常会提供管理控制台来操作。
    • 运维
      客服无法处理的异常,可能就需要运维人员根据用户信息和日志信息协助排查,定位问题,如果运维可以解决,比如系统扩容,硬件升级,那么运维解决,否则交由开发解决
    • 开发
      如果到了开发同学这里,说明问题已经比较严重了,需要根据日志信息仔细排查,看是否是系统未发现的BUG。

    拾遗

    • 要特别注意打印日志本身抛出的异常。一是正确的空值校验,二是也可以try-cache。
    • 外部交互,统一错误码,便于沟通与排查问题。
    • 控制日志保存期限及大小,避免磁盘打爆。日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点【阿里开发手册规约中有强调】
    • 日志可以进行分类,分文件打印,可以根据不同都日志级别,或不同都业务,便于排查。命名时需要注意:见名知意。如 appName_logType_logName.log。 logType:日志类型,推荐分类有 stats/desc/monitor/visit
      等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
    • 异步打印日志,避免日志成为系统性能负担
    • 如果明确知道是什么原因导致的错误,那么堆栈可以不打印,打印堆栈也是一个比较大的开销
    • 通常不再循环里打印日志
    • 写完功能,进行测试时,尽量不用编辑器的调试,而是通过日志来查看功能实现和进行异常定位、分析。当然这个要求对于开发者要求比较高。
    • 避免重复打印,务必在日志配置文件中设置additivity=false。
    • 一个类中通常只有一个静态的log对象
    • 日志中如果英文描述不清,可以用中文。不过用于定位错误信息的关键字推荐用英文。

    空指针异常

    • 基本每个使用到的对象,返回值,都要考虑是否会存在空都情况,做出正确判断。
    • 对于一些情况,当返回空时,可以采用返回非空对象的方式来代替。比如查询数据返回空集合Collections.emptyList()getOrDefault方法。
    • 使用 if(null == obj) 而不是 if(obj == null) 来避免不必要的空指针异常

    入参校验

    • 服务端不可以相信前端任何输入。需要对入参做严格都校验。
    • 微服务间,下层服务也需要对上层服务都入参做校验,不要以为调用方都是一个部门或公司的就忽略了
    • 对于批量操作,务必对请求都数据量做校验,避免大SQL或内存撑爆

    安全

    日志的打印需要对用户的身份证,密码等敏感信息脱敏。

    分布式链路追踪

    微服务系统需要配合链路追踪和ELK等工具。

    spring boot 全局统一日志打印

    spring boot 全局统一日志打印

    spring boot 2.1学习笔记【十九】使用spring validation实现全局参数校验

    spring boot 2.1学习笔记【十九】使用spring validation实现全局参数校验

    案例

    • 反例:日志冗余
      在这里插入图片描述
      如下, 已经有抛出异常了,那么没必要再打印一次error日志。但是,要注意将具体的错误信息封装成自然语言抛出异常,并且上层要捕获并打印error日志。
    try {
    	//do sth	
    } catch (Exception e) {
    	logger.error('XX 发生异常', e);
    	throw new IOException();
    }
    
    • 反例:未使用占位符
      log.error("具体业务信息,入参:"+sceneId);
    • 反例:日志没有有效信息,没有堆栈信息
    try {
        throw new Exception("测试日志");
    }catch (Exception e){
        logger.info(e.getMessage());//错误
        logger.info("输入日志1",e.getMessage());//错误
        logger.info("输入日志2",e);//正确
    }
    

    如下:没有打印status的值。如果分支比较复杂,最好在status=1和2两个分支打印一下走向。当日在外层打印也行,只要能快速定位到走的哪个分支流程。

    if(status=1){
    	//do sth
    }else if(status=2){
    	//do sth
    }else{    
    	log.warn("status 有误");
    }
    
    • 反例:日志未能准确表达错误信息
    Connection connection =ConnectionFactory.getConnection();
    if (connection == null) {
    	log.warn("System initialized unsuccessfully"); 
    } 
    
    • 反例:由于打印日志,导致新的异常。通常是空指针异常
    log.debug("Processing request with id: {}", request.getId());
    
    • 反例:将数据库中的数据全部打印出来
      有时候只需要打印数据量大小即可,如果打印了很多无用的数据,不仅性能损耗,占用额外的磁盘空间,还会给日志查看带来困扰
    • 反例:错误的打印对象
      如下,如果parama是对象,那么可能打印的是对象的hashcode值。可以重写tostring方法或者转为json打印,推荐json
    logger.error("calculate user fee error. parama=[{}], paramb=[{}]", parama, paramb, e)
    

    正例:

    logger.debug("UserName is:[{}] and age is : [{}] ", name, age);
    
    logger.error("calculate user fee error. parama=[{}], paramb=[{}]", json(parama), paramb, e)
    
    展开全文
  • Java程序日志打印规范

    千次阅读 2020-05-02 11:17:04
    日志技术框架一览 JUL:JDK中的日志记录工具,也常称为JDKLog、jdk-logging。 LOG4J1:一个具体的日志实现框架。 LOG4J2:一个具体的日志实现框架,是LOG4J1的下一个版本。 LOGBACK:一个具体的日志实现框架...

    日志技术框架一览

    • JUL:JDK中的日志记录工具,也常称为JDKLog、jdk-logging。

    • LOG4J1:一个具体的日志实现框架。

    • LOG4J2:一个具体的日志实现框架,是LOG4J1的下一个版本。

    • LOGBACK:一个具体的日志实现框架,但其性能更好。

    • JCL:一个日志门面,提供统一的日志记录接口,也常称为commons-logging。

    • SLF4J:一个日志门面,与JCL一样提供统一的日志记录接口,可以方便地切换看具体的实现框架。

      注意:JUL、LOG4J1、LOG4J2、LOGBACK是日志实现框架,而JCL、SLF4J是日志实现门面(接口)

    日志系统切换

    在实际的日志转换过程中,SLF4J其实是充当一个中介的角色。例如当我们一个项目原来是使用LOG4J进行日志记录,但是我们要换成LogBack进行日志记录。

    此时我们需要先将LOG4J转换成SLF4J日志系统,再从SLF4J日志系统转成LogBack日志系统。

    • 从日志框架转SLF4J(如下图)

      jul-to-slf4j:jdk-logging到slf4j的桥梁
      log4j-over-slf4j:log4j1到slf4j的桥梁
      jcl-over-slf4j:commons-logging到slf4j的桥梁

    • 从SLF4J转具体的日志框架(如下图)

      slf4j-jdk14:slf4j到jdk-logging的桥梁
      slf4j-log4j12:slf4j到log4j1的桥梁
      log4j-slf4j-impl:slf4j到log4j2的桥梁
      logback-classic:slf4j到logback的桥梁
      slf4j-jcl:slf4j到commons-logging的桥梁

    例如我们一开始使用的是 Log4J 日志框架,现在我们希望转成 LogBack 框架,那么我们首先需要加入 log4j-over-slf4j.jar 将 Log4J 转成 SLF4J,之后再加入 logback-classic.jar 将 SLF4J 转成 LogBack。
    在这里插入图片描述

    Java常用的日志框架

    JDKLog:日志小刀(功能太简单,不支持占位符,扩展差,很少有人用)

    Logger logger = Logger.getLogger("JDKLog");
    logger.info("Hello World");
    

    Log4J:日志大炮(分为1.X和2.X两个版本,功能强大,扩展好,性能有待提升)

    在前几年的系统中经常使用log4j,所以现在很多公司需要将log4j切换到logback。肯定是不可能一行行修改的,需要通过下面的slf4j适配器进行转换。

    使用姿势

    1. 使用 Log4J 框架首先需要引入依赖的包:
    <!-- Log4J -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.6.2</version>
    </dependency>
    
    1. 增加配置文件 log4j2.xml 放在 resource 目录下:
    <?xml version="1.0" encoding="utf-8"?>
    <Configuration status="WARN"> 
      <Appenders> 
        <Console name="Console" target="SYSTEM_OUT"> 
          <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> 
        </Console> 
      </Appenders>  
      <Loggers> 
        <Root level="info"> 
          <AppenderRef ref="Console"/> 
        </Root> 
      </Loggers> 
    </Configuration>
    

    其中节点的 level 属性表示输出的最低级别。

    1. 最后编写一个测试类:
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    /****
     ** Log4J Demo
     **/
    public class Log4jLog {
        public static void main(String[] args) {
            Logger logger = LogManager.getLogger(Log4jLog.class);
            logger.debug("Debug Level");
            logger.info("Info Level");
            logger.warn("Warn Level");
            logger.error("Error Level");
        }
    }
    
    1. 运行测试类输出结果:
    10:16:08.279 [main] INFO com.chanshuyi.Log4jLog - Info Level
    10:16:08.280 [main] WARN com.chanshuyi.Log4jLog - Warn Level
    10:16:08.280 [main] ERROR com.chanshuyi.Log4jLog - Error Level
    
    1. 如果没有配置 log4j2.xml 配置文件,那么LOG4J将自动启用类似于下面的的配置文件:
    <?xml version="1.0" encoding="utf-8"?>
    
    <Configuration status="WARN"> 
      <Appenders> 
        <Console name="Console" target="SYSTEM_OUT"> 
          <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> 
        </Console> 
      </Appenders>  
      <Loggers> 
        <Root level="error"> 
          <AppenderRef ref="Console"/> 
        </Root> 
      </Loggers> 
    </Configuration>
    
    1. 使用默认配置文件的输出结果:
      11:40:07.377 [main] ERROR com.chanshuyi.Log4jLog - Error Level
      从上面的使用步骤可以看出 Log4J 的使用稍微复杂一些,但是条理还是很清晰的。而且因为 Log4J 有多个分级(DEBUG/INFO/WARN/ERROR)记录级别,所以可以很好地记录不同业务问题。因为这些优点,所以在几年前几乎所有人都使用 Log4J 作为日志记录框架,群众基础可谓非常深厚。

    但 Log4J 本身也存在一些缺点,比如不支持使用占位符,不利于代码阅读等缺点。但是相比起 JDKLog,Log4J 可以说是非常好的日志记录框架了。

    LogBack:日志火箭

    1. 直接使用logback框架。

    LogBack 其实可以说是 Log4J 的进化版,因为它们两个都是同一个人(Ceki Gülcü)设计的开源日志组件。LogBack 除了具备 Log4j 的所有优点之外,还解决了 Log4J 不能使用占位符的问题。

    使用 LogBack 需要首先引入依赖:

    <!-- LogBack -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.7</version>
    </dependency>
    

    配置 logback.xml 配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <layout class="ch.qos.logback.classic.PatternLayout">
                <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
            </layout>
        </appender>
        <logger name="com.chanshuyi" level="TRACE"/>
     
        <root level="debug">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    

    LogBack 的日志级别区分可以细分到类或者包,这样就可以使日志记录变得更加灵活。之后在类文件中引入Logger类,并进行日志记录:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
     
    /****
     ** LogBack Demo
     **/
    public class LogBack {
        static final Logger logger = LoggerFactory.getLogger(LogBack.class);
        public static void main(String[] args) {
            logger.trace("Trace Level.");
            logger.debug("Debug Level.");
            logger.info("Info Level.");
            logger.warn("Warn Level.");
            logger.error("Error Level.");
        }
    }
    

    输出结果:

    14:34:45.747 [main] TRACE com.chanshuyi.LogBack - Trace Level.
    14:34:45.749 [main] DEBUG com.chanshuyi.LogBack - Debug Level.
    14:34:45.749 [main] INFO  com.chanshuyi.LogBack - Info Level.
    14:34:45.749 [main] WARN  com.chanshuyi.LogBack - Warn Level.
    14:34:45.749 [main] ERROR com.chanshuyi.LogBack - Error Level.
    

    LogBack 解决了 Log4J 不能使用占位符的问题,这使得阅读日志代码非常方便。除此之外,LogBack 比 Log4J 有更快的运行速度,更好的内部实现。并且 LogBack 内部集成了 SLF4J 可以更原生地实现一些日志记录的实现。

    SLF4J:适配器

    因为在实际的项目应用中,有时候可能会从一个日志框架切换到另外一个日志框架的需求,这时候往往需要在代码上进行很大的改动。为了避免切换日志组件时要改动代码,这时候一个叫做 SLF4J(Simple Logging Facade for Java,即Java简单日志记录接口集)的东西出现了。如下图中的日志接口。
    在这里插入图片描述
    SLF4J(Simple Logging Facade for Java,即Java简单日志记录接口集)是一个日志的接口规范,它对用户提供了统一的日志接口,屏蔽了不同日志组件的差异。这样我们在编写代码的时候只需要看 SLF4J 这个接口文档即可,不需要去理会不同日之框架的区别。而当我们需要更换日志组件的时候,我们只需要更换一个具体的日志组件Jar包就可以了。

    而整合 SLF4J 和日志框架使用也是一件很简单的事情。

    SLF4J+JDKLog

    SLF4J + JDKLog 需要在 Maven 中导入以下依赖包:

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.21</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-jdk14</artifactId>
      <version>1.7.21</version>
    </dependency>
    

    编写测试类:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
     
    /****
     ** SLF4J + JDKLog
     **/
    public class Slf4jJDKLog {
        final static Logger logger = LoggerFactory.getLogger(Slf4jJDKLog.class);
        public static void main(String[] args) {
            logger.trace("Trace Level.");
            logger.info("Info Level.");
            logger.warn("Warn Level.");
            logger.error("Error Level.");
        }
    }
    

    输出结果:

    七月 15, 2016 3:30:02 下午 com.chanshuyi.slf4j.Slf4jJDKLog main
    信息: Info Level.
    七月 15, 2016 3:30:02 下午 com.chanshuyi.slf4j.Slf4jJDKLog main
    警告: Warn Level.
    七月 15, 2016 3:30:02 下午 com.chanshuyi.slf4j.Slf4jJDKLog main
    严重: Error Level.
    

    SLF4J+LOG4J

    需要依赖的 Jar 包:slf4j-api.jar、slf4j-412.jar、log4j.jar,导入Maven依赖:

    <!-- 2.SLF4J + Log4J -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.21</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.21</version>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    

    配置 log4j.xml 文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
     
    <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/' >
     
        <appender name="myConsole" class="org.apache.log4j.ConsoleAppender">
            <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern"
                       value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
            </layout>
            <!--过滤器设置输出的级别-->
            <filter class="org.apache.log4j.varia.LevelRangeFilter">
                <param name="levelMin" value="debug" />
                <param name="levelMax" value="error" />
                <param name="AcceptOnMatch" value="true" />
            </filter>
        </appender>
     
        <!-- 根logger的设置-->
        <root>
            <priority value ="debug"/>
            <appender-ref ref="myConsole"/>
        </root>
    </log4j:configuration>
    

    我们还是用上面的代码,无需做改变,运行结果为:

    [15 16:04:06,371 DEBUG] [main] slf4j.SLF4JLog - Debug Level.
    [15 16:04:06,371 INFO ] [main] slf4j.SLF4JLog - Info Level.
    [15 16:04:06,371 WARN ] [main] slf4j.SLF4JLog - Warn Level.
    [15 16:04:06,371 ERROR] [main] slf4j.SLF4JLog - Error Level.
    

    SLF4J+LogBack

    导入依赖:

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.21</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.7</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.1.7</version>
    </dependency>
    

    配置 logback.xml 文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <layout class="ch.qos.logback.classic.PatternLayout">
                <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
            </layout>
        </appender>
        <logger name="com.chanshuyi" level="TRACE"/>
     
        <root level="warn">
            <appender-ref ref="STDOUT" />
        </root>
    </configuration>
    

    我们还是用上面的代码,无需做改变,运行结果为:

    16:08:01.040 [main] TRACE com.chanshuyi.slf4j.SLF4JLog - Trace Level.
    16:08:01.042 [main] DEBUG com.chanshuyi.slf4j.SLF4JLog - Debug Level.
    16:08:01.043 [main] INFO  com.chanshuyi.slf4j.SLF4JLog - Info Level.
    16:08:01.043 [main] WARN  com.chanshuyi.slf4j.SLF4JLog - Warn Level.
    16:08:01.043 [main] ERROR com.chanshuyi.slf4j.SLF4JLog - Error Level.
    

    LogBack日志框架

    经过上面的介绍,相信大家对 Java 常用的日志框架都有了一定认识。

    那么在实际使用中到底选择哪种日志框架合适呢?

    按笔者理解,现在最流的日志框架解决方案莫过于SLF4J + LogBack。原因有下面几点:

    • LogBack 自身实现了 SLF4J 的日志接口,不需要 SLF4J 去做进一步的适配。
    • LogBack 自身是在 Log4J 的基础上优化而成的,其运行速度和效率都比 LOG4J 高。
    • SLF4J + LogBack 支持占位符,方便日志代码的阅读,而 LOG4J 则不支持。

    从上面几点来看,SLF4J + LogBack是一个较好的选择。

    LogBack 被分为3个组件:logback-core、logback-classic 和 logback-access。

    • logback-core 提供了 LogBack 的核心功能,是另外两个组件的基础。
    • logback-classic 则实现了 SLF4J 的API,所以当想配合 SLF4J 使用时,需要将 logback-classic 引入依赖中。
    • logback-access 是为了集成Servlet环境而准备的,可提供HTTP-access的日志接口。

    LogBack的日志记录数据流是从 Class(Package)到 Logger,再从Logger到Appender,最后从Appender到具体的输出终端。

    img

    LogBack配置文件可以分为几个节点,其中 Configuration 是根节点,Appender、Logger、Root是Configuration的子节点。

    appender节点

    是的子节点,是负责写日志的组件。appender有两个必要属性name、class 。name指定appender的名称,class指定appender的全限定名
    class,主要包括:

    • ch.qos.logback.core.ConsoleAppender 控制台输出
    • ch.qos.logback.core.FileAppender 文件输出
    • ch.qos.logback.core.RollingFileAppender 文件滚动输出
    <?xml version="1.0" encoding="utf-8"?> 
    <configuration debug="true" scan="true" scanPeriod="2">
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
        </appender>
    
        <!-- conf file out -->
        <appender name="file_out" class="ch.qos.logback.core.FileAppender">
        </appender>
        
        <!-- conf file out -->
        <appender name="file_out" class="ch.qos.logback.core.RollingFileAppender">
        </appender>
    
        <root></root>
        <logger></logger>
    </configuration>
    

    ConsoleAppender

    把日志添加到控制台,有如下节点:

    • `` : 对日志进行格式化。
    • `` : 字符串System.out 或者 System.err, 默认 System.out;
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
      <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%date [%thread] %-5level %logger - %message%newline</pattern>
            </encoder>
      </appender>
    
      <root level="INFO">             
        <appender-ref ref="console_out" />   
      </root>     
    </configuration>
    

    FileAppender

    把日志添加到文件,有如下节点:

    • ``:被写入的文件名,可以是相对目录 , 也可以是绝对目录 , 如果目录不存在则会自动创建。
    • ``:如果是true , 日志被追加到文件结尾 , 如果是false,清空现存文件 , 默认是true。
    • ``:对日志进行格式化 [具体的转换符说明请参见官网.]
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <appender name="file_out" class="ch.qos.logback.core.FileAppender">
            <file>logs/debug.log</file>
            <encoder>
                <pattern>%date [%thread] %-5level %logger - %message%newline</pattern>
            </encoder>
        </appender>
    </configuration>
    

    rollingFileAppender

    滚动纪录文件,先将日志记录到指定文件,当符合某种条件时,将日志记录到其他文件,有如下节点:

    • ``:被写入的文件名,可以是相对目录,也可以解决目录,如果目录不存在则自动创建。
    • ``:如果是true,日志被追加到文件结尾,如果是false,清空现存文件,默认是true。
    • ``:对日志进行格式化。
    • ``:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。

    rollingPolicy

    • TimeBaseRollingPolicy :最常用的滚动策略,根据时间来制定滚动策略,即负责滚动也负责触发滚动。有如下节点;
      • ``:必要节点,包含文件及“%d” 转换符,“%d”可以包含一个java.text.SimpleDateFormat 制定的时间格式,如:%d{yyyy-MM},如果直接使用%d ,默认格式是 yyyy-MM-dd。
      • ``:可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,且 是 6,则只保存最近6个月的文件,删除之前的旧文件,注意:删除旧文件是哪些为了归档而创建的目录也会被删除。
      • ``:必须包含“%i” 例如:设置最小值,和最大值分别为1和2,命名模式为 log%i.log,会产生归档文件log1.log和log2.log,还可以指定文件压缩选项,例如:log%i.log.gz 或者 log%i.log.zip
    • triggeringPolicy:告知RollingFileAppender,激活RollingFileAppender滚动。
    <!-- 03:conf errorAppender out -->
    <appender name="errorAppender" class="ch.qos.logback.core.RollingFileAppender">
        <file>logs/error.log</file>
        <!-- 设置滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <!--设置日志命名模式--> 
            <fileNamePattern>errorFile.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--最多保留30天log-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <!-- 超过150MB时,触发滚动策略 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>150</maxFileSize>
        </triggeringPolicy>
        <encoder>
            <pattern>%d [%p] %-5level %logger - %msg%newline</pattern>
        </encoder>
    </appender>
    

    logger节点

    logger是的子节点,来设置某一个包或者具体的某一个类的日志打印级别,以及指定。logger仅有一个name属性,两个可选属性 level/addtivity。

    • name:用来指定受此loger约束的某一个包或者具体的某一个类。
    • level:用来设置打印级别,大小写无关。可选值有TRACE、DEBUG、INFO、WARN、ERROR、ALL和OFF。还有一个特俗值INHERITED 或者 同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。
    • addtivity:是否向上级logger传递打印信息,默认为true;

    可以包含零个或多个元素,表示这个appender将会添加到logger。

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <!-- 过滤掉非INFO级别 -->
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <!--  conf infoAppender out -->
        <appender name="infoAppender" class="ch.qos.logback.core.RollingFileAppender">
            <file>logs/info.log</file>
            <!-- 设置滚动策略 -->
            <rollingPoliy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!--设置日志命名模式--> 
                <fileNamePattern>infoFile.%d{yyyy-MM-dd}.log</fileNamePattern>
                <!--最多保留30天log-->
                <maxHistory>30</maxHistory>
            </rollingPoliy>
            <!-- 超过150MB时,触发滚动策略 -->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <maxFileSize>150</maxFileSize>
            </triggeringPolicy>
            <encoder>
                <pattern>%d [%p] %-5level %logger - %msg%newline</pattern>
            </encoder>
        </appender>
     
        <!-- 添加两个appender节点 -->
        <logger name="logback.olf.log" level="info">
            <appender-ref ref = "console_out"/>
            <appender-ref ref = "infoAppender"/>
        </logger>
    </configuration>
    

    root节点

    元素配置根logger。该元素有一个level属性,没有name属性,因为已经被命名 为root。Level属性的值大小写无关,其值为下面其中一个字符串:TRACE、DEBUG、INFO、 WARN、ERROR、ALL 和 OFF。如果 root 元素没 有引用任何 appender,就会失去所有 appender。

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <!-- 过滤掉非INFO级别 -->
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
        </appender>
    
        <!-- 01:conf infoAppender out -->
        <appender name="infoAppender" class="ch.qos.logback.core.RollingFileAppender">
            
            <file>logs/info.log</file>
            <!-- 设置滚动策略 -->
            <rollingPoliy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!--设置日志命名模式--> 
                <fileNamePattern>infoFile.%d{yyyy-MM-dd}.log</fileNamePattern>
                <!--最多保留30天log-->
                <maxHistory>30</maxHistory>
            </rollingPoliy>
            <!-- 超过150MB时,触发滚动策略 -->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <maxFileSize>150</maxFileSize>
            </triggeringPolicy>
            <encoder>
                <pattern>%d [%p] %-5level %logger - %msg%newline</pattern>
            </encoder>
        </appender>
    
        <!-- 02:conf debugAppender out -->
        <appender name="debugAppender" class="ch.qos.logback.core.RollingFileAppender">
            <file>logs/debug.log</file>
            <!-- 设置滚动策略 -->
            <rollingPoliy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!--设置日志命名模式--> 
                <fileNamePattern>debugFile.%d{yyyy-MM-dd}.log</fileNamePattern>
                <!--最多保留30天log-->
                <maxHistory>30</maxHistory>
            </rollingPoliy>
            <!-- 超过150MB时,触发滚动策略 -->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <maxFileSize>150</maxFileSize>
            </triggeringPolicy>
            <encoder>
                <pattern>%d [%p] %-5level %logger - %msg%newline</pattern>
            </encoder>
        </appender>
    
        <!-- 03:conf errorAppender out -->
        <appender name="errorAppender" class="ch.qos.logback.core.RollingFileAppender">
            <file>logs/error.log</file>
            <!-- 设置滚动策略 -->
            <rollingPoliy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!--设置日志命名模式--> 
                <fileNamePattern>errorFile.%d{yyyy-MM-dd}.log</fileNamePattern>
                <!--最多保留30天log-->
                <maxHistory>30</maxHistory>
            </rollingPoliy>
            <!-- 超过150MB时,触发滚动策略 -->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <maxFileSize>150</maxFileSize>
            </triggeringPolicy>
            <encoder>
                <pattern>%d [%p] %-5level %logger - %msg%newline</pattern>
            </encoder>
        </appender>
     
        <root level="ALL">
            <appender-ref ref="infoAppender"/>
            <appender-ref ref="debugAppender"/>
            <appender-ref ref="errorAppender"/>
        </root>
    </configuration>
    

    filter过滤节点

    级别过滤器(LevelFilter)

    LevelFilter 根据记录级别对记录事件进行过滤。如果事件的级别等于配置的级别,过滤 器会根据 onMatch 和 onMismatch 属性接受或拒绝事件。下面是个配置文件例子:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <!-- 过滤掉非INFO级别 -->
                <level>INFO</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            
            <encoder>
                <pattern>%-4relative [%thread] %-5level %logger{30} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="DEBUG">
            <appender-ref ref="console_out" />
        </root>
    </configuration>
    

    临界值过滤器(ThresholdFilter)

    ThresholdFilter过滤掉低于指定临界值的事件。

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">  
            <!-- 过滤掉TRACE和DEBUG级别的日志 -->
                <level>INFO</level> 
            </filter>
            
            <encoder>
                <pattern>%-4relative [%thread] %-5level %logger{30} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="DEBUG">
            <appender-ref ref="console_out" />
        </root>
    </configuration>
    

    求值过滤器(EvaluatorFilter)

    评估是否符合指定的条件

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.EvaluatorFilter">  
                 <evaluator>
                 <!--过滤掉所有日志中不包含hello字符的日志-->
                    <expression>
                        message.contains("hello")
                    </expression>
                    <onMatch>NEUTRAL</onMatch>
                    <onMismatch>DENY</onMismatch>
                 </evaluator>
            </filter>
            
            <encoder>
                <pattern>%-4relative [%thread] %-5level %logger{30} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="DEBUG">
            <appender-ref ref="console_out" />
        </root>
    </configuration>
    

    匹配器(Matchers)

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <!-- conf consoel out -->
        <appender name ="console_out" class="ch.qos.logback.core.ConsoleAppender">
            <filter class="ch.qos.logback.classic.filter.EvaluatorFilter">  
                 <evaluator> 
                    <matcher>
                        <Name>odd</Name>
                        <!-- 过滤掉序号为奇数的语句-->
                        <regex>statement [13579]</regex>
                    </matcher>
                    <expression>odd.matches(formattedMessage)</expression>
                    <onMatch>NEUTRAL</onMatch>
                    <onMismatch>DENY</onMismatch>
                 </evaluator>
            </filter>
            
            <encoder>
                <pattern>%-4relative [%thread] %-5level %logger{30} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="DEBUG">
            <appender-ref ref="console_out" />
        </root>
    </configuration>
    

    下面是一个我常用的logback.xml配置文件,供大家参考:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <configuration debug="true" scan="true" scanPeriod="30 seconds"> 
    
      <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
        <!-- encoders are  by default assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%level] - %m%n</pattern>
            
            <!-- 常用的Pattern变量,大家可打开该pattern进行输出观察 -->
            <!-- 
              <pattern>
                  %d{yyyy-MM-dd HH:mm:ss} [%level] - %msg%n
                  Logger: %logger
                  Class: %class
                  File: %file
                  Caller: %caller
                  Line: %line
                  Message: %m
                  Method: %M
                  Relative: %relative
                  Thread: %thread
                  Exception: %ex
                  xException: %xEx
                  nopException: %nopex
                  rException: %rEx
                  Marker: %marker
                  %n
                  
              </pattern>
               -->
        </encoder>
      </appender>
      
      <!-- 按日期区分的滚动日志 -->
      <appender name="ERROR-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>logs/error.log</file>
          
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
        </encoder>
          
          <filter class="ch.qos.logback.classic.filter.LevelFilter">
          <level>ERROR</level>
          <onMatch>ACCEPT</onMatch>
          <onMismatch>DENY</onMismatch>
        </filter>
          
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <!-- daily rollover -->
          <fileNamePattern>error.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
    
          <!-- keep 30 days' worth of history -->
          <maxHistory>30</maxHistory>
        </rollingPolicy>
      </appender>
      
      <!-- 按文件大小区分的滚动日志 -->
      <appender name="INFO-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>logs/info.log</file>
          
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
        </encoder>
        
          <filter class="ch.qos.logback.classic.filter.LevelFilter">
          <level>INFO</level>
          <onMatch>ACCEPT</onMatch>
          <onMismatch>DENY</onMismatch>
        </filter>
          
          <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
          <fileNamePattern>info.%i.log</fileNamePattern>
          <minIndex>1</minIndex>
          <maxIndex>3</maxIndex>
        </rollingPolicy>
        
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
          <maxFileSize>5MB</maxFileSize>
        </triggeringPolicy>
        
      </appender>
      
      
      <!-- 按日期和大小区分的滚动日志 -->
      <appender name="DEBUG-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>logs/debug.log</file>
    
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
        </encoder>
          
          <filter class="ch.qos.logback.classic.filter.LevelFilter">
          <level>DEBUG</level>
          <onMatch>ACCEPT</onMatch>
          <onMismatch>DENY</onMismatch>
        </filter>
          
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <!-- rollover daily -->
          <fileNamePattern>debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
          
          <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- or whenever the file size reaches 100MB -->
            <maxFileSize>100MB</maxFileSize>
          </timeBasedFileNamingAndTriggeringPolicy>
          
        </rollingPolicy>
        
      </appender>
      
      
       <!-- 级别阀值过滤 -->
      <appender name="SUM-OUT" class="ch.qos.logback.core.rolling.RollingFileAppender">
          <file>logs/sum.log</file>
    
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%class:%line] - %m%n</pattern>
        </encoder>
          
        <!-- deny all events with a level below INFO, that is TRACE and DEBUG -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          <level>INFO</level>
        </filter>
    
          
          <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
          <!-- rollover daily -->
          <fileNamePattern>debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
          
          <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
            <!-- or whenever the file size reaches 100MB -->
            <maxFileSize>100MB</maxFileSize>
          </timeBasedFileNamingAndTriggeringPolicy>
          
        </rollingPolicy>
        
      </appender>
      
      
      <root level="debug">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="ERROR-OUT" />
        <appender-ref ref="INFO-OUT" />
        <appender-ref ref="DEBUG-OUT" />
        <appender-ref ref="SUM-OUT" />
      </root>
    </configuration>
    

    如何进行日志系统转换?

    在实际的日志转换过程中,SLF4J其实是充当了一个中介的角色。例如当我们一个项目原来是使用LOG4J进行日志记录,但是我们要换成LogBack进行日志记录。

    此时我们需要先将LOG4J转换成SLF4J日志系统,再从SLF4J日志系统转成LogBack日志系统。

    推荐的日志编写方式

    以下是错误方式。因为系统中如果存在大量类似的代码,同时系统只输出 info 及 info 以上级别的日志,那么,在输出 debug 日志时会产生大量的字符串,而并不会输出 debug 日志,最后造成字符串不停地拼接,浪费系统性能。此时,SLF4J 就可以使用占位符的功能编写日志,通过这样的形式,SLF4J 就可以根据日志等级判断,只对符合要求的日志进行数据拼接和打印。

    //错误方式
    logger.debug("用户" + userId + "开始下单:" + orderNo + ",请求信息:" + Gson.toJson(req));
    //推荐方式
    logger.debug("用户{}开始下单:{},请求信息:", userId, orderNo,  Gson.toJson(req));
    

    有些时候日志输出需要进行数值计算,或者 JSON 转换,此时就需要一定的计算任务。但方法调用如果被当作参数传递的话一样会被执行,所以 Java8 中 SLF4J 还可以通过 Supplier 来传递。如下所示:

    logger.debug("用户{}开始下单:{},请求信息:", userId, orderNo, () -> Gson.toJson(req))
    
    

    日志编写推荐2个方向。
    在这里插入图片描述

    日志编写位置

    1. 系统/应用启动和参数变更。当系统启动时,可以将相关的参数信息进行打印,以便出现问题时,更准确地查询原因;参数信息可能并不存储在本地,需要通过配置中心获取,而参数信息有变更时,也需要将变更后的内容输出在日志中。
    2. 关键操作节点。最典型的就是在关键位置添加日志,记录用户进行的某个操作。
    3. 大型任务进度上报。当系统在处理某个比较大型的任务时,可以在这个过程中增加相关的日志来表明任务处理的进度,防止因为长时间没有处理而无法得知程序执行的状态,比如在文件下载时,可以按照百分比来定时/定次地上报数据。
    4. 异常。如果是通过 try-catch 处理,你需要注意日志编写的位置。如果你需要将日志在本层抛出,则不需要进行日志记录,否则会出现日志重复的问题。如果你除了异常以外还需要记录其他的内容,则可以通过定制异常信息来实现。

    写入性能

    1. 日志编写位置:日志编写的位置在程序中十分重要,如果在 for 循环中编写,因为这个循环会持续很多次,那么就会产生大量的日志记录。此时可以考虑一下,这个日志的记录是否有必要。
    2. 日志数量:如果你大量地编写日志,那么日志的质量一定会降低。
    3. 日志编写等级:我在上一节中讲过,日志等级很容易被滥用,不正确的日志等级会导致我们查询问题的时间增加。
    4. 日志输出级别:这里指的是对于配置日志输出级别的选择。在线上环境,不建议使用 debug 级别,因为线上一直有请求,debug 级别会输出大量的基础和请求信息,极其浪费资源,因此建议开启 info 或者以上。
    5. 用输出参数:不对大字段、无用字段输出,因为这很影响程序执行效率和日志的内容。比如大段的Html,或者大段的对象信息,甚至有见到整个图片流信息。

    占位符

    你为什么要用占位符:

    1. 节约性能。在生成较高级别的日志时,低级别的日志会不停叠加字符串而占用过多的内存、CPU 资源,导致性能浪费。
    2. 便于编写。先确认日志所想要表达的内容,再确认你所需要编写的参数,这样在写日志时,目的也会更加明确。
    3. 便于查看。在代码 review 时,更方便查看日志想表达的意思,而不会被日志的参数打乱。

    可读性

    我总结了几点在日志中容易遗漏的信息:

    1. 会话标识:当前操作的用户和与当前请求相关的信息,类似 Session。当出现问题/查看行为时,可以根据这个值来快速识别到相关的日志。
    2. 请求标识:每个请求都拥有一个唯一的标识,这样在查看问题时,我们只需要查看这一个请求中的所有日志即可。一般我们会配合链路追踪系统一同使用,因为后者可以实现跨应用的日志追踪,从而帮助我们过滤掉不相关的信息。例如MDC。
    3. 参数信息:在日志中增加参数信息能帮你了解到,是什么情况下产生的问题,这样你也很容易复现问题,以及辨别错误的原因。
    4. 发生数据的结果:和参数信息相互对应,一个是执行前,一个是执行后。发生数据的结果可以帮你了解程序执行的结果,出错时也是很重要的参考条件。

    关键信息隐蔽

    对于关键的信息不显示或者进行掩码显示,以免信息被盗取后出现数据内容泄漏。推特在 2018 年曾将用户的密码打印在日志中,这一行为泄露了 3.3 亿人的密码。

    减少代码位置信息的输出

    如果不是必要,尽量不要在日志格式中输出当前日志所在的代码行和方法名称信息。因为这是通过获取当前线程堆栈快照信息来进行实现的,这种实现方式会极大地影响程序执行的效率。
    在 log4j 的文档中有这样一段话:“使用同步方式进行获取位置信息会慢 1.3 到 5 倍,如果是使用异步日志,因为会涉及跨线程获取位置信息,会慢 30 到 100 倍。

    文件分类

    将不同的业务逻辑按照不同的日志文件来分类。这个在微服务中不存在这个问题,但是针对同一个微服务,也需要将正常日志和错误日志,业务日志和非业务日志区分开来(非必须)。

    日志 review

    好的日志不是一次就能写好的,而是通过代码评审、不断迭代等方式进行完善的

    日志格式

    日志的格式布局会影响运维人员将这些日志内容收集与管理的效率。如果编写者和管理者能够通过协商,规定出一套完整的日志格式,这样就能在排查问题时事半功倍。

    我会简单介绍几点在日志编写时需要注意的事项:

    1. 系统之间格式保持一致:每个应用在日志格式上尽量保持统一,这样,运维人员在进行日志收集时,就可以采用统一的日志模板来收集和格式化内容,减少双方的沟通成本。
    2. 不编写多行的日志内容:除了异常堆栈信息,不对日志内容进行多行的输出,多行的内容十分不便于数据解析,可能会出现生成多条日志的情况。
    3. 不要使用日志中的常见内容来分割:如果采用日志中常见的内容来分割,会在格式解析时出现问题,比如用户 ID 中的空格就是比较常见的内容。

    日志归档

    日志归档是一件很重要的事情。一般情况下,我们会对日志按照日期来归档,每天生成一个日志文件,这样在日志备份和恢复时,可以按照日期来进行。如果感觉天级别的日志仍然太大了,可以考虑按照小时细分。

    参考

    https://www.cnblogs.com/chanshuyi/p/something_about_java_log_framework.html

    展开全文
  • 行业分类-物理装置-一种基于拦截的接口日志打印的方法及装置.zip
  • 日志打印的正确姿势

    2019-12-27 15:24:53
    日志 日志是什么? 日志,维基百科的定义是记录服务器等电脑设备或软件的运作。 日志文件提供精确的系统记录,根据日志最终定位到错误详情和根源。日志的特点是,它描述一些离散的(不连续的)事件。 例如:应用...

    日志

    日志是什么?
    日志,维基百科的定义是记录服务器等电脑设备或软件的运作。
    日志文件提供精确的系统记录,根据日志最终定位到错误详情和根源。日志的特点是,它描述一些离散的(不连续的)事件。 例如:应用通过一个滚动的文件输出 INFO 或 ERROR 信息,并通过日志收集系统,存储到一些存储引擎(Elasticsearch)中方便查询。

    日志有什么用?

    在上文中我们解释了日志的作用是提供精准的系统记录方便根因分析。那么具体在哪些具体方面它可以发挥作用?

    打印调试:即可以用日志来记录变量或者某一段逻辑。记录程序运行的流程,即程序运行了哪些代码,方便排查逻辑问题。

    问题定位:程序出异常或者出故障时快速的定位问题,方便后期解决问题。因为线上生产环境无法 debug,在测试环境去模拟一套生产环境,费时费力。所以依靠日志记录的信息定位问题,这点非常重要。还可以记录流量,后期可以通过 ELK(包括 EFK 进行流量统计)。

    用户行为日志:记录用户的操作行为,用于大数据分析,比如监控、风控、推荐等等。这种日志,一般是给其他团队分析使用,而且可能是多个团队,因此一般会有一定的格式要求,开发者应该按照这个格式来记录,便于其他团队的使用。当然,要记录哪些行为、操作,一般也是约定好的,因此,开发者主要是执行的角色。

    根因分析(甩锅必备):即在关键地方记录日志。方便在和各个终端定位问题时,别人说时你的程序问题,你可以理直气壮的拿出你的日志说,看,我这里运行了,状态也是对的。这样,对方就会乖乖去定位他的代码,而不是互相推脱。

    什么时候记录日志?

    上文说了日志的重要性,那么什么时候需要记录日志。

    系统初始化:系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录 INFO 日志,打印出参数以及启动完成态服务表述。
    编程语言提示异常:如今各类主流的编程语言都包括异常机制,业务相关的流行框架有完整的异常模块。这类捕获的异常是系统告知开发人员需要加以关注的,是质量非常高的报错。应当适当记录日志,根据实际结合业务的情况使用 WARN 或者 ERROR 级别。
    业务流程预期不符:除开平台以及编程语言异常之外,项目代码中结果与期望不符时也是日志场景之一,简单来说所有流程分支都可以加入考虑。取决于开发人员判断能否容忍情形发生。常见的合适场景包括外部参数不正确,数据处理问题导致返回码不在合理范围内等等。
    系统核心角色,组件关键动作:系统中核心角色触发的业务动作是需要多加关注的,是衡量系统正常运行的重要指标,建议记录 INFO 级别日志,比如电商系统用户从登录到下单的整个流程;微服务各服务节点交互;核心数据表增删改;核心组件运行等等,如果日志频度高或者打印量特别大,可以提炼关键点 INFO 记录,其余酌情考虑 DEBUG 级别。
    第三方服务远程调用:微服务架构体系中有一个重要的点就是第三方永远不可信,对于第三方服务远程调用建议打印请求和响应的参数,方便在和各个终端定位问题,不会因为第三方服务日志的缺失变得手足无措。

    日志打印

    Slf4j & Logback
    Slf4j 英文全称为 “ Simple Logging Facade for Java ”,为 Java 提供的简单日志门面。Facade 门面,更底层一点说就是接口。它允许用户以自己的喜好,在工程中通过 Slf4j 接入不同的日志系统。
    Logback 是 Slf4j 的原生实现框架,同样也是出自 Log4j 一个人之手,但拥有比 Log4j 更多的优点、特性和更做强的性能,Logback 相对于 Log4j 拥有更快的执行速度。基于我们先前在 Log4j 上的工作,Logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上 10 倍。在保证 Logback 的组件更加快速的同时,同时所需的内存更加少。
    日志文件
    日志文件放置于固定的目录中,按照一定的模板进行命名,推荐的日志文件名称:
    当前正在写入的日志文件名:<应用名>[-<功能名>].log
    如:example-server-book-service-access.log
    已经滚入历史的日志文件名:<应用名>[-<功能名>].yyyy-MM-dd-hh.[滚动号].log
    复制代码如:example-server-book-service-access.2019-12-01-10.1.log
    日志变量定义
    推荐使用 lombok(代码生成器) 注解 @lombok.extern.slf4j.Slf4j 来生成日志变量实例。

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
    </dependency>
    

    复制代码代码示例

    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class LogTest {
        public static void main(String[] args) {
            log.info("this is log test");
        }
    }
    

    复制代码日志配置
    日志记录采用分级记录,级别与日志文件名相对应,不同级别的日志信息记录到不同的日志文件中。如有特殊格式日志,如 access log,单独使用一个文件,请注意避免重复打印(可使用 additivity**=“false”** 避免 )。
    参数占位格式
    使用参数化形式 {} 占位,[] 进行参数隔离,这样的好处是可读性更高,而且只有真正准备打印的时候才会处理参数。
    // 正确示例,必须使用参数化信息的方式
    log.debug(“order is paying with userId:[{}] and orderId : [{}]”,userId, orderId);
    // 错误示例,不要进行字符串拼接,那样会产生很多 String 对象,占用空间,影响性能。及日志级别高于此级别也会进行字符串拼接逻辑。
    log.debug("order is paying with userId: " + userId + " and orderId: " + orderId);
    复制代码日志的基本格式
    日志时间
    作为日志产生的日期和时间,这个数据非常重要,一般精确到毫秒。
    yyyy-MM-dd HH:mm:ss.SSS
    复制代码日志级别
    日志的输出都是分级别的,不同的设置不同的场合打印不同的日志。
    主要使用如下的四个级别:

    DEBUG:DEUBG 级别的主要输出调试性质的内容,该级别日志主要用于在开发、测试阶段输出。该级别的日志应尽可能地详尽,开发人员可以将各类详细信息记录到 DEBUG 里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等等,便于在开发、测试阶段出现问题或者异常时,对其进行分析。
    INFO:INFO 级别的主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,开发人员可以将初始化系统配置、业务状态变化信息,或者用户业务流程中的核心处理记录到INFO日志中,方便日常运维工作以及错误回溯时上下文场景复现。建议在项目完成后,在测试环境将日志级别调成 INFO,然后通过 INFO 级别的信息看看是否能了解这个应用的运用情况,如果出现问题后是否这些日志能否提供有用的排查问题的信息。
    WARN:WARN 级别的主要输出警告性质的内容,这些内容是可以预知且是有规划的,比如,某个方法入参为空或者该参数的值不满足运行该方法的条件时。在 WARN 级别的时应输出较为详尽的信息,以便于事后对日志进行分析。
    ERROR:ERROR 级别主要针对于一些不可预知的信息,诸如:错误、异常等,比如,在 catch 块中抓获的网络通信、数据库连接等异常,若异常对系统的整个流程影响不大,可以使用 WARN 级别日志输出。在输出 ERROR 级别的日志时,尽量多地输出方法入参数、方法执行过程中产生的对象等数据,在带有错误、异常对象的数据时,需要将该对象一并输出。

    DEBUG / INFO 的选择

    DEBUG 级别比 INFO 低,包含调试时更详细的了解系统运行状态的东西,比如变量的值等等,都可以输出到 DEBUG 日志里。 INFO 是在线日志默认的输出级别,反馈系统的当前状态给最终用户看的。输出的信息,应该对最终用户具有实际意义的。从功能角度上说,INFO 输出的信息可以看作是软件产品的一部分,所以需要谨慎对待,不可随便输出。如果这条日志会被频繁打印或者大部分时间对于纠错起不到作用,就应当考虑下调为 DEBUG 级别。

    由于 DEBUG 日志打印量远大于 INFO,出于前文日志性能的考虑,如果代码为核心代码,执行频率非常高,务必推敲日志设计是否合理,是否需要下调为 DEBUG 级别日志。
    注意日志的可读性,不妨在写完代码 review 这条日志是否通顺,能否提供真正有意义的信息。
    日志输出是多线程公用的,如果有另外一个线程正在输出日志,上面的记录就会被打断,最终显示输出和预想的就会不一致。

    WARN / ERROR 的选择

    当方法或者功能处理过程中产生不符合预期结果或者有框架报错时可以考虑使用,常见问题处理方法包括:

    增加判断处理逻辑,尝试本地解决:增加逻辑判断吞掉报警永远是最优选择抛出异常,交给上层逻辑解决
    抛出异常,交给上层逻辑解决
    记录日志,报警提醒
    使用返回码包装错误做返回

    一般来说,WARN 级别不会短信报警,ERROR 级别则会短信报警甚至电话报警,ERROR 级别的日志意味着系统中发生了非常严重的问题,必须有人马上处理,比如数据库不可用,系统的关键业务流程走不下去等等。错误的使用反而带来严重的后果,不区分问题的重要程度,只要有问题就error记录下来,其实这样是非常不负责任的,因为对于成熟的系统,都会有一套完整的报错机制,那这个错误信息什么时候需要发出来,很多都是依据单位时间内 ERROR 日志的数量来确定的。
    强调ERROR报警

    ERROR 级别的日志打印通常伴随报警通知。ERROR的报出应该伴随着业务功能受损,即上面提到的系统中发生了非常严重的问题,必须有人马上处理。

    ERROR日志目标

    给处理者直接准确的信息:ERROR 信息形成自身闭环。

    问题定位:

    发生了什么问题,哪些功能受到影响
    获取帮助信息:直接帮助信息或帮助信息的存储位置
    通过报警知道解决方案或者找何人解决

    线程名称
    输出该日志的线程名称,一般在一个应用中一个同步请求由同一线程完成,输出线程名称可以在各个请求产生的日志中进行分类,便于分清当前请求上下文的日志。
    opentracing 标识
    在分布式应用中,用户的一个请求会调用若干个服务完成,这些服务可能还是嵌套调用的,因此完成一个请求的日志并不在一个应用的日志文件,而是分散在不同服务器上不同应用节点的日志文件中。该标识是为了串联一个请求在整个系统中的调用日志。

    唯一字符串(trace id)
    调用层级(span id)

    通过搜索 trace id 就可以查到这个 trace id 标识的请求在整个系统中流转(处理)过程中产生的所有日志。
    biz 标识
    在业务开发中,我们的日志都是和业务相关联的,有时候是需要根据用户或者业务做聚类的,因此一次请求如果可以通过某项标识做聚类的时候,可以将聚类标识打印到日志中。

    用户标识(user id)
    业务标识(biz id)

    日志记录器名称
    日志记录器名称一般使用类名,日志文件中可以输出简单的类名即可,看实际情况是否需要使用包名和行号等信息。主要用于看到日志后到哪个类中去找这个日志输出,便于定位问题所在。
    日志内容
    禁用 System.out.println 和 System.err.println
    变参替换日志拼接
    输出日志的对象,应在其类中实现快速的 toString 方法,以便于在日志输出时仅输出这个对象类名和 hashCode
    预防空指针:不要在日志中调用对象的方法获取值,除非确保该对象肯定不为 null,否则很有可能会因为日志的问题而导致应用产生空指针异常。
    异常堆栈
    异常堆栈一般会出现在 ERROR 或者 WARN 级别的日志中,异常堆栈含有方法调用链的系统,以及异常产生的根源。异常堆栈的日志属于上一行日志的,在日志收集时需要将其划至上一行中。
    最佳实践
    日志格式

    2019-12-01 00:00:00.000|pid|log-level|[svc-name,trace-id,span-id,user-id,biz-id]|thread-name|package-name.class-name : log message
    

    复制代码

    时间
    pid,pid
    log-level,日志级别
    svc-name,应用名称
    trace-id,调用链标识
    span-id,调用层级标识
    user-id,用户标识
    biz-id,业务标识
    thread-name,线程名称
    package-name.class-name,日志记录器名称
    log message,日志消息体
    
    日志模块扩展

    日志模块是基于以下技术点做扩展的。

    Slf4j MDC 实现原理(暂不开展详解,如有兴趣私下沟通)
    Opentracing Scope 原理(暂不开展详解,如有兴趣私下沟通)

    在每个 tracing 链路中,将 Opentracing Scope 中的上下文信息放置 MDC 中,根据 Spring Boot Logging 扩展接口扩展的取值逻辑 **logging.pattern.level **的取值逻辑。

    修改 logback 配置文件中每个 appender 的 pattern 为以下默认值即可实现标准化。

    %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:- }|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n
    

    复制代码logback.xml 节选

    <configuration>
    <property name="LOG_PATH"
              value="${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}"/>
    
    <springProperty scope="context" name="APP_NAME"
                    source="spring.application.name" defaultValue="spring-boot-fusion"/>
    <!-- 全局统一 pattern -->
    <property name="LOG_PATTERN"
              value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}|${PID:- }|%level|${LOG_LEVEL_PATTERN:-%5p}|%t|%-40.40logger{39}: %msg%n"/>
    <!-- 输出模式 file,滚动记录文件,先将日志文件指定到文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。-->
        <file>${LOG_PATH}/${APP_NAME}-info.log</file>
        <!--滚动策略  基于时间的分包策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- yyyy-MM-dd 时间策略则为一天一个文件 -->
            <FileNamePattern>${LOG_PATH}/${APP_NAME}-info.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern>
            <!--日志文件保留小时数-->
            <MaxHistory>48</MaxHistory>
            <maxFileSize>1GB</maxFileSize>
            <totalSizeCap>20GB</totalSizeCap>
        </rollingPolicy>
        <!--  layout 负责把事件转换成字符串,格式化的日志信息的输出 -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>${LOG_PATTERN}</pattern>
        </layout>
        <!--级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--设置过滤级别-->
            <level>INFO</level>
            <!--用于配置符合过滤条件的操作-->
            <onMatch>ACCEPT</onMatch>
            <!--用于配置不符合过滤条件的操作-->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    </configuration>
    

    复制代码代码使用示例:

    @Override
    public Result<PagingObject<SimpleResponse>> page(@RequestParam(value = "page-num", defaultValue = "1") int pageNum,
                                                     @RequestParam(value = "page-size", defaultValue = "10") int pageSize) {
        LogStandardUtils.putUserId("userId123");
        LogStandardUtils.putBizId("bizId321");
        producerService.sendMsg("xxx");
        simpleClient.page(pageNum, pageSize);
        return new Result<>(simpleService.page(pageNum, pageSize));
    }
    

    复制代码日志记录

    2019-12-04 16:29:08.223|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.u.concurrent.ShutdownEnabledTimer   : Shutdown hook installed for: NFLoadBalancer-PingTimer-example-server-order-service
    2019-12-04 16:29:08.224|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.netflix.loadbalancer.BaseLoadBalancer : Client: example-server-order-service instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
    2019-12-04 16:29:08.234|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.l.DynamicServerListLoadBalancer     : Using serverListUpdater PollingServerListUpdater
    2019-12-04 16:29:08.247|43546|INFO|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.n.l.DynamicServerListLoadBalancer     : DynamicServerListLoadBalancer for client example-server-order-service initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null}
    2019-12-04 16:29:08.329|43546|WARN|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.l.ctl.common.rule.StrategyRule    : No up servers available from load balancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null}
    2019-12-04 16:29:08.334|43546|WARN|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.l.ctl.common.rule.StrategyRule    : No up servers available from load balancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=example-server-order-service,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:ConsulServerList{serviceId='example-server-order-service', tag=null}
    2019-12-04 16:29:08.342|43546|ERROR|[example-server-book-service,ac613cff04bac8b1,4a9adc10fdf0eb5,userId123,bizId321]|XNIO-1 task-4|c.p.f.w.c.advice.ExceptionHandlerAdvice : 当前程序进入到异常捕获器,出错的 url 为:[ http://127.0.0.1:10011/simples ],出错的参数为:[ {"querystring":"{}","payload":""} ]
    java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: example-server-order-service
    

    复制代码日志服务
    SLS日志服务
    日志服务(简称 SLS)是针对日志类数据的一站式服务,在阿里巴巴集团经历大量大数据场景锤炼而成。您无需开发就能快捷完成日志数据采集、消费、投递以及查询分析等功能,提升运维、运营效率,建立 DT 时代海量日志处理能力。
    project

    项目、管理日志基础单元,服务日志建议一个环境建为一个 Project,这样日志记录是整体一个闭环,日志记录随整个环境内的服务调用产生。

    logstore

    日志库,日志库建议按照日志类型分为不同的,如特定格式的 access 日志,以及 info / warn / error 日志,特定格式可以配置更为方面的索引以及告警设置。

    注意:请勿按照应用服务区分为不同的 logstore,在微服务架构中,一次请求交叉了多个应用服务,日志是散落在各个应用服务中的,按照服务区分 logstore,需要开发同学十分了解应用运行状况和调用拓扑图,这点往往是不具备的。
    实时采集与消费
    功能:

    通过ECS、容器、移动端、开源软件、JS等接入实时日志数据(例如Metric、Event、BinLog、TextLog、Click等)。
    提供实时消费接口,与实时计算及服务对接。

    用途:数据清洗(ETL)、流计算(Stream Compute)、监控与报警、 机器学习与迭代计算。

    查询分析
    实时索引、查询分析数据。

    查询:关键词、模糊、上下文、范围。
    统计:SQL聚合等丰富查询手段。
    可视化:Dashboard + 报表功能。
    对接:Grafana、JDBC/SQL92。

    用途:DevOps / 线上运维,日志实时数据分析,安全诊断与分析,运营与客服系统。

    消费投递
    稳定可靠的日志投递。将日志中枢数据投递至存储类服务进行存储。支持压缩、自定义Partition、以及行列等各种存储方式。
    用途:数据仓库 + 数据分析、审计、推荐系统与用户画像。

    告警
    日志服务的告警功能基于仪表盘中的查询图表实现。在日志服务控制台查询页面或仪表盘页面设置告警规则,并指定告警规则的配置、检查条件和通知方式。设置告警后,日志服务定期对仪表盘的查询结果进行检查,检查结果满足预设条件时发送告警通知,实现实时的服务状态监控。

    ELK 通用日志解决方案
    ELK 是 Elasticsearch、Logstash、Kibana 三大开源框架首字母大写简称。市面上也被成为 Elastic Stack。其中 Elasticsearch 是一个基于 Lucene、分布式、通过 Restful 方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用 Elasticsearch 作为底层支持框架,可见 Elasticsearch 提供的搜索能力确实强大,市面上很多时候我们简称 Elasticsearch 为 es。Logstash 是 ELK 的中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/ MQ / Redis / Elasticsearch / Kafka 等)。Kibana 可以将 Elasticsearch 的数据通过友好的页面展示出来,提供实时分析的功能。
    实践说明
    普通格式日志

    2019-11-26 15:01:03.332|1543|INFO|[example-server-book-service,28f019d57b8336ab,630697c7f34ca4fa,105,45982043|XNIO-1 task-42]|c.p.f.w.pay.PayServiceImpl     : order is paying with userId: 105 and orderId: 45982043
    

    复制代码普通日志前缀是固定的,可以固定分词索引,方便更快的查询分析。
    特定格式日志
    以 access 日志为例

    2019-11-26 15:01:03.332|1543|INFO|[example-server-book-service,28f019d57b8336ab,630697c7f34ca4fa,105,45982043|XNIO-1 task-42]|c.p.f.w.logging.AccessLoggingFilter     : 
    > url: http://liweichao.com:10011/actuator/health
    > http-method: GET
    > request-header: [Accept:"text/plain, text/*, */*", Connection:"close", User-Agent:"Consul Health Check", Host:"liweichao.com:10011", Accept-Encoding:"gzip"]
    > request-time: 2019-11-26 15:01:03.309
    > querystring: -
    > payload: -
    > extra-param: -
    
    < response-time: 2019-11-26 15:01:03.332
    < take-time: 23
    < http-status: 200
    < response-header: [content-type:"application/vnd.spring-boot.actuator.v2+json;charset=UTF-8", content-size:"15"]
    < response-data: {"status":"UP"}
    

    复制代码特定格式日志可按格式创建索引更方便聚焦查询分析和告警,如根据 take-time,http-status,biz-code 等值。
    参考文献
    [ 别在 Java 代码里乱打日志了,这才是打印日志的正确姿势!
    转自链接: https://juejin.im/post/5e01a184e51d45581e44178a.

    展开全文
  • 日志打印的规范以及实现方式

    千次阅读 多人点赞 2019-01-08 18:00:16
    为什么要做日志? 原因1:跟踪应用的警告和错误,标识程序运行中的危险操作、错误操作,进而便于在出现问题时排查问题 原因2:跟踪崩溃bug,在开发过程中,日志可以帮助开发者和软件测试人员跟踪程序崩溃的原因 ...
  • 因为公司业务需要,需要把性能日志和业务日志分开打印,用elk收集处理,所以需要对不同的业务的日志打印到不同文件。 使用的是spring boot自带的logback。 首先在yml文件配置logback.xml文件,默认会从...
  • java中常用的日志分类有以下几种 1. JUL (java.util.Logging) Java自带的日志,可以直接使用 package site.uuyy.log; import java.util.logging.Logger; public class JUL { public static void main...
  • 代码中日志打印的正确使用

    千次阅读 2017-06-06 15:15:32
    1,工欲善其事,必先利其器 很多程序员可能都忘了记录应用程序的行为和当前活动是多么重要。有的人很高兴的就在代码里加上了这么句: ...我认为,slf4j是最好的日志API,最主要是因为它支持一个很棒
  • -- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> < configuration scan = "true" > < contextName > logback contextName > <!--定义日志...
  • description方法是NSObject...对于一个Person类,如果没有重写description方法,NSLog(@“%@”,p),输出的是p的地址,而我们想要的效果是打印出person的成员变量,所以我们可以在Person类里重写description方法。 ...
  • 关于异常日志打印的问题

    千次阅读 2018-11-28 22:49:14
    在开发中看到系统大量使用try catch(Exception e)包裹业务代码,然后打印信息。让我不禁想到这样包裹真的好么??又因为代码中我发现一些问题就算是因为这样的包裹没有及时处理异常导致报错却没有打印信息,很难发现...
  • 首先呢springboot 使用的是 logback做为默认的日志记录方式,但是如果我想用log4j记录日志怎么办呢 第一步:将logback依赖排除,因为springboot-starter-web 会自动依赖logback 包 <dependency> <...
  • 日志信息分类

    千次阅读 2018-12-05 16:14:01
    1.等级由低到高: ...info 用于打印程序应该出现的正常状态信息, 便于追踪定位; warn 表明系统出现轻微的不合理但不影响运行和使用; error 表明出现了系统错误和异常,无法正常完成目标操作。
  • 日志那些事之一—java日志框架分类

    千次阅读 2018-03-06 19:56:19
    本系列日志文章主要是针对项目上遇到的日志问题,发散下思维,比较广的介绍下java中常见以及常用的日志框架,并通过实例代码演示。系列文章主要分为三大部分: 了解日志的作用,了解java中常用的日志框架; ...
  • 第一段代码抛出的异常是在标准Java类库【InputFileStream】中抛出的,这首先就提升了问题定位的难度,不过幸好stacktrace中也打印出了前面的调用链,我们可以在标准类库的调用者身上查找问题(可以定位到Test.java的...
  • 在增加了上面的配置之后重启应用,便可以看到如下的日志打印: 2020-02-11 15:36:09.787 TRACE 49215 --- [main] s.w.s.m.m.a.RequestMappingHandlerMapping : c.d.c.UserController: {PUT /users/{id}}: putUser...
  • 打印日志是一门艺术,但长期被开发同学所忽视。日志就像车辆保险,没人愿意为保险付钱,但是一旦出了问题都又想有保险可用。我们打印日志的时候都很随意,可是用的时候会吐槽各种 SB 包括自己!写好每一条日志吧,与...
  • 日志用于引擎及时反馈给我们运行时刻数据和信息。强大的用途不限于如下: 函数是否被调用 算法使用的什么数据 上报错误给开发组或者用户 特定时刻强制运行致命错误(如断言错误)以停止执行程序。 本章将介绍...
  • Springboot中使用logback分类输出日志

    千次阅读 2018-11-15 14:36:35
    1、logback配置  Springboot2.0默认集成了logback,无需再引入logback包。springboot会自动启用resources下的logback-spring.xml...官方配置的日志输出格式:%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %log...
  • 小程序 打印日志

    千次阅读 2021-11-12 15:34:56
    实时日志”进入插件端日志查询页面,进而查看开发者打印日志信息。 1、小程序/小游戏端 (1)log.js文件 var log = wx.getRealtimeLogManager ? wx.getRealtimeLogManager() : null module.exports = { ...
  • log4j打印彩色日志

    千次阅读 2020-04-02 09:30:09
    5p]} [%c:%L] %highlight{%m%n} rootLogger.level=all rootLogger.appenderRef.stdout.ref=STDOUT 使用%highlight{}即可打印出彩色日志,但是自log4j 2.10版本以后该功能默认是关闭的,所以要给它打开。 在 VM ...
  • 我们使用log4j2的时候,一般都需要不同的日志分类打印不同的日志等级。 如下面的配置:这个配置的目标是 日志输出使用root,而root的级别是error,希望com.hmmy包下面的日志输出debug级别,结果在生成日志文件中重复...
  • 编程中打印日志的作用: 追踪程序运行过程,快速定位问题 如何运用日志 1、程序开始一般都会加上入口日志,后面每一步流程都有记录开始与结束日志,一直到最后程序结束输出所有数据信息。所以一般程序有报错的时候看...
  • Java知识体系最强总结(2021版)

    万次阅读 多人点赞 2019-12-18 10:09:56
    https://thinkwon.blog.csdn.net/article/details/102571883 IO流 待整理: File、递归 字节流、字节缓冲流 编码表、编码方式、转换流、序列化、序列化流、打印流、commons-io 网络编程 网络概述、网络模型 Socket...
  • HTTP接口设计及日志打印

    千次阅读 2017-11-21 15:14:11
    日志记录、性能监控的三种实现方式 博客分类:  Spring   一、需解决的问题 部分API有签名参数(signature),Passport首先对签名进行校验,校验通过才会执行实现方法。   第一种实现...
  • 日志打印的10个小建议

    千次阅读 2014-03-01 14:24:26
    1,工欲善其事,必先利其器 很多程序员可能都忘了记录应用程序的行为和当前活动是多么重要。有的人很高兴的就在代码里加上了这么句: log.info("Happy and ...我认为,slf4j是最好的日志API,最主要是因为它支持一个
  • logback 配置打印 JPA SQL日志到文件

    万次阅读 2018-07-16 22:46:44
    Logback 输出 Hibernate SQL日志 到文件 使用Spring Boot 配置 JPA 时可以指定如下配置在控制台查看执行的SQL语句 ...#llogging.level后跟要打印日志的包名或类的全限定名,设置打印级别 # 日志级别...
  • Android studio 日志分类

    2015-08-15 17:23:24
    运行app,编辑器都会跳出 Android 这个窗口,没有的话,自己找到6Android 图标,如下如所示这就是所有的日志,一看就眼花,很多都是我们不需要的,所以我们要进行过滤: Android studio 自动过滤掉了其他程序的...
  • 打印日志的10个建议

    万次阅读 2014-03-07 15:30:46
    1,工欲善其事,必先利其器 很多程序员可能都忘了记录应用程序的行为和当前活动是多么重要。有的人很高兴的就在代码里加上了这么句: ...我认为,slf4j是最好的日志API,最主要是因为它支持一个很棒的模

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 38,559
精华内容 15,423
关键字:

日志打印分类