首页 > 编程语言 >Java 日志

Java 日志

时间:2024-06-01 22:10:27浏览次数:9  
标签:输出 traceId Java slf4j 日志 logback Logger

概述

Java日志系统中,有两个组件协同工作

  • 一个是负责日志输出的框架,如Logback、Log4j2
  • 一个是日志接口,提供统一的日志记录接口,如slf4j、Apache commons-logging

日志接口必须与日志输出框架集成后才能正常工作,集成时,需要相应的桥接包,以slf4j为例,

集成log4j2时需要如下包

  • slf4j-api
  • log4j-api、log4j-core、log4j-slf4j-impl(集成包)

集成logback时需要如下包

  • slf4j-api
  • logback-core、logback-classic(集成包)

除了日志接口与输出框架的集成,日志接口之间也可以进行转接。

例如将commons-logging接口转接到slf4j,再由slf4j集成到logback。

  • jcl-over-slf4j(集成包,不再需要commons-logging)
  • slf4j-api、logback-core、logback-classic(集成包)

以常用的Slf4j+Logback的组合来说,我们在程序中进行日志打印时,调用的是Slf4j中的Log对象进行日志记录,但是真正进行日志输出的其实是Logback,如输出到控制台、文件。

日志系统

使用Slf4j作为统一的日志接口,集成日志输出框架Logback,同时转接commons-logging接口,包依赖如下

  • jcl-over-slf4j
  • slf4j-api、logback-core、logback-classic

Slf4j

引入slf4j-api包以后,有两种使用方式,一是在类中定义一个Slf4j Logger对象,二是直接使用lombok的@Slf4j注解。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld{
  private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
  public static void main(String[] args) {
    logger.info("hello rachel !");
  }
}

Logback

引入logback-core、logback-classic包后,slf4j会自动与其集成。springboot可以自动识别加载logback配置,但要符合命令规则,如logback.xml,logback-spring.xml等。

编写配置文件logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!--引入spring boot默认的logback配置文件,使用其中定义的日志打印格式-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <!--标准输出:控制台日志-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- spring property:自定义属性,引用spring上下文中的值 -->
<springProperty scope="context" name="LOG_PATH" source="logging.file.path" defaultValue="logs"/>
    <springProperty scope="context" name="LOG_LEVEL" source="logging.level" defaultValue="INFO"/>
    <springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="zhisou"/>

    <!--文件输出:追加日志到文件中-->
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--实时日志文件的存放路径和名称-->
<file>${LOG_PATH}/${APP_NAME}.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--滚动切割产生的历史日志文件的存放路径和名称-->
            <!--%d{yyyy-MM-dd}:按天进行日志滚动-->
            <!--%i:当文件大小超过maxFileSize时,按照i进行文件滚动-->
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--历史日志保存最大天数-->
<MaxHistory>15</MaxHistory>
            <!--当日志文件超过指定的大小时,根据FileNamePattern标签里提到的%i进行日志文件滚动-->
<maxFileSize>100MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,配置INFO表示只会记录自身和比高于自身级别的日志-->
    <!--开发调试阶段打开控制台输出,日志级别设置为INFO;生产环境关闭控制台输出,只需要将WARN及以上级别的日志记录到文件中-->
    <!--root为根logger配置(其余logger都是它的子类),用于定义要记录的日志级别和日志输出目的地-->
<root level="${LOG_LEVEL}">
        <!--定义日志输出,控制台和日志文件-->
<appender-ref ref="STDOUT"/>
        <appender-ref ref="LOG_FILE"/>
    </root>

    <!-- 定义子logger,针对org.springframework包下产生的日志只记录WARN及以上级别 -->
    <!--    <logger name="org.springframework" level="WARN"/>-->

</configuration>

问题

logback-spring.xml中引用spring上下文内容时,如果该配置在本地,可以在加载logback-spring.xml时引用配置值。但如果该配置项在配置中心上,会无法引用,只能使用默认值。

因为使用默认的日志文件命名时(logback.xml和logback-spring.xml),spring在获取远程配置之前就已经加载logback.xml,而此时要引用的spring配置还没加载。

<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="zhisou"/>

解决方法:使用自定义的日志文件名(spring无法自动加载),如logback-custom.xml。然后在远程配置中指定使用该配置文件,这样就可以在远程配置加载后再加载logback配置。

# 这里classpath指resources目录下
logging.config: classpath:logback-custom.xml

Logback深入

结构

logback主要由appender和logger两部分构成

  • appender用于负责日志输出,例如,输出到控制台或者文件、定制输出格式。
  • logger用于指定某一个包或者类中的日志输出级别,以及指定appender。root代表的是根logger,其余的logger皆继承root中的配置。

Appender

appender标签中,需要指定一个名字和appender类。根据appender类的不同,有不同的行为,例如输出到控制台或者文件。

控制台

ConsoleAppender就是用于输出日志到控制台的,日志输出到控制台时是一种滚动输出的方式,旧的日志很快会被新的覆盖。在appender中定义一个encoder对象,确定日志输出格式和字符编码即可。

<!--标准输出:控制台日志-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
文件输出

相较于控制台输出,输出到文件中就复杂多了。

例如,是始终输出到一个文件呢?还是按照一定的规则,输出到不同的文件中?

输出到一个文件中,会导致文件越来越庞大,不是一个好的选择,所以一般选择是输出到不同文件中。

输出到不同文件中时,按照什么规则进行文件分割呢?文件大小?时间?又或者是两者结合?

分割规则一般基于时间和文件大小的结合,只要有一个符合条件,就新建一个日志文件用于存储日志。

具体的实现是通过滚动输出类RollingFileAppender,结合基于大小和时间的滚动规则使用SizeAndTimeBasedRollingPolicy。

<!--文件输出:追加日志到文件中-->
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--实时日志文件的存放路径和名称-->
<file>${LOG_PATH}/${APP_NAME}.log</file>
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--滚动切割产生的历史日志文件的存放路径和名称-->
            <!--%d{yyyy-MM-dd}:按天进行日志滚动-->
            <!--%i:当文件大小超过maxFileSize时,按照i进行文件滚动-->
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--历史日志保存最大天数-->
<MaxHistory>30</MaxHistory>
            <!--当日志文件超过指定的大小时,根据FileNamePattern标签里提到的%i进行日志文件滚动-->
<maxFileSize>100MB</maxFileSize>
        </rollingPolicy>
    </appender>

Logger

这里的Logger相当于我们在类中定义的Logger对象,我们可以单独为每一个类中的Logger对象指定一个Appender,但这可能会累死人,并且没有收益。

更好的方法是通过root对象,root代表的是一个根Logger对象,其余的Logger对象都是它的子类,所以只要在root中定义了Appender,就相当于对所有Logger设置了。

如果对某些包中进行单独的配置,只需要再定义一个Logger,重写配置即可。

<!--日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,配置INFO表示只会记录自身和比高于自身级别的日志-->
    <!--开发调试阶段打开控制台输出,日志级别设置为INFO;生产环境关闭控制台输出,只需要将WARN及以上级别的日志记录到文件中-->
    <!--root为根logger配置(其余logger都是它的子类),用于定义要记录的日志级别和日志输出目的地-->
<root level="${LOG_LEVEL}">
        <!--定义日志输出,控制台和日志文件-->
<appender-ref ref="STDOUT"/>
        <appender-ref ref="LOG_FILE"/>
    </root>

    <!-- 定义子logger,针对org.springframework包下产生的日志只记录WARN及以上级别 -->
    <logger name="org.springframework" level="WARN"/>

高级应用

异步输出

cache和buffer的区别

  • cache :设备之间有速度差,高速设备访问低速设备会造成高速设备等待,导致使用率降低,为了减少低速设备对高速设备的影响,在两者之间加入cache,通过加快访问速度,以提升高速设备的使用效率。
  • buffer :通俗来说就是化零为整,把少量多次变成多量少次;具体来说就是进行流量整形,把突发的大数量较小规模的 I/O 整理成平稳的小数量较大规模的 I/O,以减少响应次数

logback中的FileAppender就是一种buffer级的输出方案,其内部有一个buffer。在进行日志写入时,如果buffer还未写满就写入buffer中,如果buffer满了,就先将buffer同步写入磁盘,再写buffer。

logback中还提供了一种cache级的方案AsyncAppender,其适用场景为高并发环境下。AsyncAppender内部有一个阻塞队列,每次日志写入时,就将其放入到阻塞队列中;由一个后台线程,从阻塞队列中取数据,放入下游的FileAppender。

<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <discardingThreshold>0</discardingThreshold>
    <queueSize>1000</queueSize>
    <!-- 绑定一个FileAppender -->
    <appender-ref ref="FILE_APPEND" />
    <!-- 与MDC搭配使用,复制MDC中的信息到异步线程-->
    <includeCallerData>true</includeCallerData>
  </appender>

链路追踪

微服务中,一个请求在多个服务中日志如何串联起来分析?

一个方法是为每个请求生成一个traceId,然后放入request对象中,在不同的服务中进行日志输出时,都输出这个traceId。这个traceId可以是一个UUID,也可以根据服务名+请求IP+时间戳进行生成,只要唯一即可。

有了traceId,那怎么做到每次日志输出时,都输出这个traceId呢?

Logback提供了一种上下文信息存储机制MDC(Mapped Diagnostic Context),可以在日志输出时动态添加一些额外的上下文信息。例如,将traceId放入MDC中,然后在日志输出格式中引用traceId即可。因为MDC基于ThreadLocal实现,可以线程执行的任意地方获取到MDC中的信息。

一个可行的链路追踪方案是,在每个服务中都定义一个ServletFilter,检测request对象中是否存在一个约定好的traceId字段,如果存在,放入MDC中;如果不存在,则生成一个,放入request对象中,也放入MDC中。

TraceFilter

public class TraceFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String traceId = request.getHeader("x-trace-id");
        if (StringUtils.isEmpty(traceId)) {
            // 更好的实现是基于服务名+请求IP+时间戳生成traceId
            traceId = UUID.randomUUID().toString();    
        }
        
        MDC.put("x-trace-id", traceId);
        filterChain.doFilter(request, response);
        MDC.remove("x-trace-id");
    }
}

日志格式定义

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%X{traceId}]-[%d]-[%-5p]-[%t]-[%c:%L]</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

配置含义为

  • [%X{traceId}]: 输出MDC中名为traceId的值。
  • [%d]: 输出日志事件的时间戳,默认使用ISO8601格式。
  • [%-5p]: 输出日志事件的级别,左对齐并固定宽度为5个字符。级别包括TRACE、DEBUG、INFO、WARN、ERROR等。
  • [%t]: 输出产生日志事件的线程名称。
  • [%c:%L]: 输出产生日志事件的类名和行号。%c表示类名,%L表示行号。
  • %m: 输出日志事件的消息。
  • %n: 输出一个换行符。
  • %wEx: 输出日志事件的异常信息,包括异常类名、异常消息和堆栈跟踪。

信息传递

需要注意的问题是,在服务中涉及到线程切换或者接口调用时,需要将traceId信息复制到子线程或者请求对象中。

例如,在线程池中传递traceId,通过Feign Client调用其它服务时需要将traceId放入request header中。

标签:输出,traceId,Java,slf4j,日志,logback,Logger
From: https://www.cnblogs.com/cd-along/p/18226483

相关文章

  • 【Redis】 使用Java操作Redis的客户端
    文章目录......
  • Java项目:springBoot汽车销售管理系统(计算机毕业设计)
    作者主页:Java毕设网 简介:Java领域优质创作者、Java项目、学习资料、技术互助文末获取源码一、项目介绍本项目基于springboot以及Vue开发,为前后端分离的项目。针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能,提供经理和销售两种角色进行管......
  • 01:Java概述及基本语法
    1、Java是什么?是SUN(StanfordUniversityNetwork,斯坦福大学网络公司)1995年推出的一门高级编程语言2、Java技术体系平台JavaSE(JavaStandardEdition)标准版JavaEE(JavaEnterpriseEdition)企业版JavaME(JavaMicroEdition)小型版3、Java主要特性面向对象......
  • 初学者springboot启动报错Caused by: java.lang.IllegalArgumentException: Invalid v
    本人第一次接触springboot框架本来想用mybatis连接数据库,引入mybatisplus配置就启动报错packagecom.hu.springboot_mybatis.dao;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.hu.springboot_mybatis.pojo.UserPojo;importorg.apache.ibatis.ann......
  • Spire.Doc for Java 12.5.1 -2024-05-30
    Spire.DocforJavaisaprofessionalWordAPIthatempowersJavaapplicationstocreate,convert,manipulateandprintWorddocumentswithoutdependencyonMicrosoftWord.Byusingthismultifunctionallibrary,developersareabletoprocesscopioustasks......
  • 大三学生第一次Java面试记录
    前言分享一下第一次面试的经历吧,希望对焦虑害怕的大学生有帮助吧,其实我也很慌~~一、2024年5月8号这天我在牛客app上找的校招网申,然后看着跟开发有关的公司就都投了,主打一个广撒网,因为我觉得在校大学生也没有实习经验,学校也不是双一流知名学校,校招的企业应该要求会宽容一点......
  • spdlog日志库源码:日志记录器logger类
    特性一个logger类对象代表一个日志记录器,为用户提供日志记录接口。每个logger对象都有一个唯一的名称,用于标识该logger。logger对象维护一个日志等级(如DEBUG、INFO、WARN、ERROR等)。只有当日志消息的等级高于或等于logger的当前等级时,消息才会被记录下来。logger......
  • spdlog日志库源码:输出通道sink
    概述在spdlog日志库中,sinks并不是一个单独的类,而是一系列类的集合,这些类以基类-派生类的形式组织,每一个sink派生类代表了一种输出日志消息的方式。输出目标可以是普通文件、标准输出(stdout)、标准错误输出(stderr)、系统日志(syslog)等等。其文件位于include/spd......
  • java如何取得拼音的首字母
    packagecom.junfun.pms;importnet.sourceforge.pinyin4j.PinyinHelper;publicclassPinyinUtils{publicstaticStringgetFirstLetter(Stringinput){StringBuilderresult=newStringBuilder();for(charc:input.toCharArray()){......
  • java8,频繁old gc,通过修改g1回收器之后,效果很好
    原配置:-Xms15360m-Xmx15360m-XX:NewSize=4096m-XX:MaxNewSize=4096m-XX:MetaspaceSize=512m-XX:MaxDirectMemorySize=1024m-XX:+UseG1GC-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/data/logs/skynet-\${DAOKEAPPUK}/\${DAOKEAPPUK}_heapDump.hprof-XX:+UseC......