日志

46 阅读6分钟

日志

代码

日志级别:越低输出越详细

TRACE < DEBUG < INFO < WARN < ERROR 

log4j2日志

依赖

        <!--日志-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--导入log4j2,需要排除boot自带的日志-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <!-- 排除自带的logback依赖 -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

日志输出模板配置

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <!--<Configuration status="WARN" monitorInterval="30"> -->
    <properties>
        <property name="LOG_HOME">./service-logs</property>
    </properties>
    <Appenders>
        <!--*********************控制台日志***********************-->
        <Console name="consoleAppender" target="SYSTEM_OUT">
            <!--设置日志格式及颜色-->
            <PatternLayout
                    pattern="%style{%d{ISO8601}}{bright,green} %highlight{%-5level} [%style{%t}{bright,blue}] %style{%C{}}{bright,yellow}: %msg%n%style{%throwable}{red}"
                    disableAnsi="false" noConsoleNoAnsi="false"/>
        </Console>
​
        <!--*********************文件日志***********************-->
        <!--all级别日志-->
        <RollingFile name="allFileAppender"
                     fileName="${LOG_HOME}/all.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log.gz">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
​
        <!--debug级别日志-->
        <RollingFile name="debugFileAppender"
                     fileName="${LOG_HOME}/debug.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉info及更高级别日志-->
                <ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
​
        <!--info级别日志-->
        <RollingFile name="infoFileAppender"
                     fileName="${LOG_HOME}/info.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉warn及更高级别日志-->
                <ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <!--<DefaultRolloverStrategy max="100"/>-->
        </RollingFile>
​
        <!--warn级别日志-->
        <RollingFile name="warnFileAppender"
                     fileName="${LOG_HOME}/warn.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--过滤掉error及更高级别日志-->
                <ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
​
        <!--error及更高级别日志-->
        <RollingFile name="errorFileAppender"
                     fileName="${LOG_HOME}/error.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>%d %p %C{} [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <!--<OnStartupTriggeringPolicy/>-->
                <!--设置日志基础文件大小,超过该大小就触发日志文件滚动更新-->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <!--设置日志文件滚动更新的时间,依赖于文件命名filePattern的设置-->
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置日志的文件个数上限,不设置默认为7个,超过大小后会被覆盖;依赖于filePattern中的%i-->
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
​
        <!--json格式error级别日志-->
        <RollingFile name="errorJsonAppender"
                     fileName="${LOG_HOME}/error-json.log"
                     filePattern="${LOG_HOME}/error-json-%d{yyyy-MM-dd}-%i.log.gz">
            <JSONLayout compact="true" eventEol="true" locationInfo="true"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
            </Policies>
        </RollingFile>
    </Appenders>
​
    <Loggers>
        <!-- 根日志设置,没有被特殊配置输出都会按照根日志配置进行输出:设置了多个appenderRef,且配置有过滤,日志会被区分存放在不同的日志文档中 -->
        <Root level="debug">
            <AppenderRef ref="allFileAppender" level="all"/>  <!-- 所有日志放一起-->
            <AppenderRef ref="consoleAppender" level="debug"/> <!-- 只输出debug级别日志-->
            <AppenderRef ref="debugFileAppender" level="debug"/>
            <AppenderRef ref="infoFileAppender" level="info"/>
            <AppenderRef ref="warnFileAppender" level="warn"/>
            <AppenderRef ref="errorFileAppender" level="error"/>
            <AppenderRef ref="errorJsonAppender" level="error"/>
        </Root>
​
         <!-- 单独配置路径日志输出级别:可以使用 双标签配置 AppenderRef 去个性化输出 -->
        <!--spring日志-->
        <Logger name="org.springframework" level="debug"/>
        <!--druid数据源日志-->
        <Logger name="druid.sql.Statement" level="debug"/>
        <!-- mybatis日志 -->
        <!--        <Logger name="com.mybatis" level="debug"/>-->
        <Logger name="org.hibernate" level="warn"/>
        <Logger name="com.zaxxer.hikari" level="info"/>
        <Logger name="org.quartz" level="info"/>
        <Logger name="com.andya.demo" level="debug"/>
​
    </Loggers></Configuration>

applicaiton.yaml

logging:
  config:  classpath:log4j2.xml # 配置文件位置
  # 也能控制日志输出级别,只不过功能没有xml强大
  level:
    root: INFO
    javax.activation: info
    org.apache.catalina: INFO
    org.apache.commons.beanutils.converters: INFO
    org.apache.coyote.http11.Http11Processor: INFO
    org.apache.http: INFO
    org.apache.tomcat: INFO
    org.springframework: INFO
    com.chinamobile.cmss.bdpaas.resource.monitor: DEBUG
    #单独配置特定包的SQL日志==>调试用
    com.yang.home.cipherkey.mapper: DEBUG
    # mybatis核心日志
    org.mybatis: DEBUG

MDC实现日志链路追踪

spring有现成的日志链路追踪的解决方案:Spring Cloud Sleuth

MDC : Mapped Diagnostic Context (映射诊断上下文)

  • 基于threadlocal实现,基本的api就是put和remove,执行结束完毕需要进行remove

通过log4j2实现轻量级日志链路追踪,在输入日志过程中携带一个流水号,方便定位和追踪。

需要考虑的问题:

  • 流水号怎么生成,如何保证唯一性
  • 如何为每个接口都设置流水号
  • 解决多线程下流水号共享问题
  • 服务间调用解决流水号共享问题==>调用时传递参数即可

流水号唯一性

UUID
  • 其标准形式由32个十六进制数字组成 : 7fcf758e-af8a-41d8-80ba-9dc5e5151c37
  • 重复概率极低,日志系统即使重复影响不会太大,Java提供现成的api接口,默认采用随机数生成
UUID.randomUUID().toString();
雪花算法

Twitter 提出的

原理:

  • 雪花算法生成的id是一个64bit的 long 型的数值 首位固定为0,之后41位时间戳+5位机器id+5位服务id+12位序列号
  • 41位毫米级时间戳意味着可以连续使用至少69年不重复=>是一个相对于某个时间的时间戳
  • 机器id+服务id共可以配置1024个节点
  • 序列号确保同一毫秒内生成不同,可以4096个值超过等下一秒

雪花算法的时间回拨问题:系统同步时间时,或者操作人员设置时间导致时间回拨,从而导致雪花算法生成重复的id

解决方案:缓存上次获取到的时间戳

  1. 保存上一次获取到的时间戳,检查到回拨问题直接异常
  2. 等待一段时间再获取
  3. 减少机器码位数,用于记录出现回拨问题次数,发现回拨问题,就+1

解决了回拨问题,就能完全保证分布式系统的序列号的唯一性,但是每次后加锁,性能消耗在日志序列号获取上有些得不偿失!

数据库自增

redis/mysql数据库自增值用于来做唯一id

AOP/拦截器设置请求MDC

还有一个过滤器也能实现类似的功能

给所有controller统一配置拦截器或者AOP

  • 执行controller前设置用MDC设置序列号
  • 执行controller后清除MDC序列号

多线程问题

创建线程的方式

基本都3种

  • 继承thread 重写 run方法,调用start
  • 实现runable接口 重写run方法,构建thread传入实现的类,调用start
  • future:实现Callable接口->用callable实例创建FutureTask->用futuretask创建thread

扩展

  • 线程池 ThreadPoolExecutor
  • CompletableFuture : 能够执行异步任务
  • spring提供@Async
解决方案
  • 重新包装新的runable,callable,Supplier ,在执行最终的方法前进行一个设置MDC,执行完毕后清楚MDC,利用拉姆达表达式的闭包提供的功能完成跨线程的设置
  • 统一使用线程池,使用线程装饰器(用springboot封装的线程池类)==>但是只能解决runable接口的任务(callable也会被处理),spring提供@Async也能被处理,CompletableFuture的Supplier额外处理