日志技术简介
java日志体系
-
日志的主流体系
-
日志的用途
- 软件开发中,我们经常需要去调试程序,做一些信息,状态的输出便于我们查询程序的运行状况。为了让我们能够更加灵活和方便的控制这些调试的信息,所以我们需要专业的日志技术。java 中寻找bug 会需要重现。调试也就是debug 可以在程序运行中暂停程序运行,可以查看程序在运行中的情况。日志主要是为了更加方便的去重现问题。
- 类似于我们sout到控制台在没有日志框架之前确实是这么做的 (加上IO技术永久保存)
-
日志的配置
-
目前使用的是logback(猜想log4j log4j12也可以用 出自同一个人)
-
工作环境是:关闭springboot默认的日志配置 只向控制台输出debug和info级别的日志
<?xml version="1.0" encoding="UTF-8"?> <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="true" scanPeriod="10 seconds"> <!-- 设置字符集 --> <property name="file.charset" value="UTF-8" /> <!-- DEBUG日志 --> <appender name="DEBUG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss} %green([%-5level]) %logger{10}.%method %msg%n</pattern> <charset>${file.charset}</charset> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>debug</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- INFO日志 --> <appender name="INFO_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss} %green([%-5level]) %logger{10}.%M - %msg%n</pattern> <charset>${file.charset}</charset> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>info</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <logger name="com.itheima" level="debug" additivity="false"> <appender-ref ref="INFO_CONSOLE"/> <appender-ref ref="DEBUG_CONSOLE"/> </logger> <!-- 关闭所有日志 --> <root level="off"> </root> </configuration>
-
标签的含义和用途(请具备xml基础的知识)
-
root: 根目录
- 功能:本身也是一个logger顶级包的上级
- 有level属性 指定要记录的日志级别 >=INFO 级别的被记录(级别不区分大小写)
- 根标签有且仅有一个
- 在没有logger的情况下 所有包/类的日志级别 都继承自根标签
-
logger
-
功能:对特定包或类设置要被记录的日志
-
name :具体包或类
-
level:日志级别(>=debug)的生效
-
additivity:是否向上级传递
(如果要记录的包中有日志要被记录,且additivity的值为true(默认值),在有logger的情况下日志将会被记录两次)
-
appender-ref:具体由那种appender来记录
-
-
子标签
-
encoder:要被记录的日志的格式进行设置
- pattern:遵循java.text.Format接口规范
- charset:字符编码
-
filter:对日志进筛选
-
class:具体执行的类 要配套使用
-
LevelFilter
需要指定 level级别和onMatch(>=level)策略 onMismatch(<level)策略
-
onMatch策略和onMismatch策略
ACCEPT:接受并处理
DENY:拒绝并抛弃
NEUTRAL:不处理传递给下一个filter
-
ThresholdFilter:对>=level的筛选
只需要指定 level级别
-
-
-
更加细致的控制请看这篇博客:
logback logback.xml常用配置详解(一)
and - AUB - ITeye博客 记录到file中请看
-
-
appender
- 功能:日志记录被什么样的类执行
- class:
- ConsoleAppender 效果:输出到控制台
- FileAppender 效果:输出文件
- ...
- name:指定该appender叫什么 被logger/root的appender-ref:引用
-
-
日志使用的思考
- 不同的框架使用的日志技术可能不一样,对框架的日志技术的实现统一使用一种日志技术
- 独立使用日志的配置文件 设定出适合自己的日志记录
-
最佳实践
- 总是使用 Log Facade,而不是具体的 Log Implementation
- 只添加一个 Log Implementation 依赖
- 具体的日志依赖应该设置为 optional,并使用 runtime scope
设为optional,依赖不会传递,这样如果你是个lib项目,然后别的项目使用了你这个lib,不会被引入不想要的Log Implementation 依赖;
Scope设置为runtime,是为了防止开发人员在项目中直接使用Log Implementation中的类,而不使用Log Facade中的类。 - 如果有必要, 排除依赖的第三方库中的Log Impementation依赖
这是很常见的一个问题,第三方库的开发者未必会把具体的日志实现或者桥接器的依赖设置为optional,然后你的项目继承了这些依赖——具体的日志实现未必是你想使用的,比如他依赖了Log4j,你想使用Logback,这时就很尴尬。另外,如果不同的第三方依赖使用了不同的桥接器和Log实现,也极容易形成环。
这种情况下,推荐的处理方法,是使用exclude来排除所有的这些Log实现和桥接器的依赖,只保留第三方库里面对Log Facade的依赖。
最佳实践的配置
<!-- ================================================= --> <!-- 日志及相关依赖(用slf4j+logback代替jcl+log4j) --> <!-- ================================================= --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${version}</version> </dependency> <!-- 强制使用 logback的绑定 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>${version}</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${version}</version> </dependency> <!-- 强制使用 logback的绑定,这里去除对log4j 的绑定 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>99.0-does-not-exist</version> </dependency> <!-- slf4j 的桥接器,将第三方类库对 log4j 的调用 delegate 到 slf api 上 --> <!-- 这个桥接器是自己做的,主要是我们依赖的类库存在很多硬编码的引用 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${version}</version> </dependency> <!-- 强制排除 log4j 的依赖,全部 delegate 到 log4j-over-slf4j 上 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>99.0-does-not-exist</version> </dependency> <dependency> <groupId>apache-log4j</groupId> <artifactId>log4j</artifactId> <version>999-not-exist</version> </dependency> <!-- slf4j + logback 配置结束 -->
2020-7-27
自定义配置遇到的问题:
1.<root level="off"></root>
这样关闭会导致 别人的代码中(框架)的日志消息全部不显示
个人理解的一个误区: 日志像异常一样 在我的包中发生的层层上抛 我的配置可以管理到 ×
框架中的日志输出根据实现的不同,可以通过日志技术写入控制台,也可以通过标准输出到控制台
2.根据1来推出
- 1.框架中的日志是都丢给了root 以下是证明
结论是:我们不能更加具体的控制框架 什么包输入什么包不输出日志
猜想:1.在root 关闭的基础上 可以通过全局异常捕获(aop)来 抓异常自己输出到控制台(这个网上有类似解决方案)
2.学习代理技术 来解决
- 2.日志实现可以不通过log.info()...的方式输出到控制台
结论:日志写入控制台的方式表面上看 可以有两种 调用log.info()...和 System.out(err)
猜想:写入控制台的本质都是 调用了System.out/err()
logback.xml的配置中<target>System.out</target>
application.yml的配置中使用org.apache.ibatis.logging.stdout.StdOutImpl
这中日志实现时
无论root 是否关闭,其都会在控制台输出! logback.xml的配置是对log.info()...这些方式的管理