本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

一、前言

本文以一个简单的项目为例,一步步展示logback的同步和异步配置方法,并且配置的日志要求满足阿里巴巴Java开发手册-日志规约 ,因为对于线上服务,日志对于排查问题有至关重要的作用,规范的日志格式配合shell脚本可以快速定位问题。

最开始使用Java日志系统,最大的疑惑就是分不清楚log4jslf4jlogback等日志库之间的关系,不过网上有不少文章介绍这部分相关知识,比如理解Java日志体系Java日志框架那些事儿混乱的 Java 日志体系,可以作为提前阅读。

二、配置同步日志

2.1 日志要求

首先项目的整体结构如下图所示:

一共有两个packageutilhttp,下面分别有两个类Util.javaHttp.java,我们的日志要求是:

  • http目录下的文件产生的日志全部记录到:~/logs/${appname}/http.log,级别:INFO
  • util目录下的文件产生的日志全部记录到:~/logs/${appname}/util.log,级别:DEBUG
  • 其余日志文件均记录到:~/logs/${appname}/${appname}.log,级别:INFO
  • 要求所有的日志均至少保存15天
  • 如果保存15天,内容太大可能造成磁盘风险,则最大保存10GB的日志。

2.2 添加POM

使用logback+slf4j的组合,需要依赖的pom如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId>
<artifactId>logback-test</artifactId>
<version>1.0-SNAPSHOT</version> <dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.10</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>

2.3 添加logback.xml

logback查找配置的顺序如下所示:

  • 在系统配置文件System Properties中寻找是否有logback.configurationFile对应的value
  • 在classpath下寻找是否有logback.groovy(即logback支持groovy与xml两种配置方式)
  • 在classpath下寻找是否有logback-test.xml
  • 在classpath下寻找是否有logback.xml

resources目录下添加logback.xml,目前内容为空,如下所示:

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

2.4 配置appender

appender用来配置日志的文件名、日志的写入策略,滚动策略等,我们按照2.1的要求配置appender如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="APP_NAME" value="logbacktest" />
<property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" /> <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--指定日志文件名称-->
<file>${LOG_NAME}</file>
<encoder>
<!--指定日志内容格式-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--日志最大保存15天-->
<maxHistory>15</maxHistory>
<!--日志最大的文件大小-->
<maxFileSize>100MB</maxFileSize>
<!--日志最大保存10GB-->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender> </configuration>

property可以用来定义变量,我们定义APP_NAMElogbacktest,后面可以用${APP_NAME}来使用这个变量,其它的配置见注释。

2.5 配置root

上面我们定义了appender,定义了日志文件名,日志写入策略,但是现在还有一个问题就是:哪个路径下的日志写入上面定义的appender?是Main.java下的,还是Util.java下的呢?

当然,logback有配置专门去配置路径,这里我们先配置root,即:默认表示所有路径。我们在2.1中的要求是其余日志文件均记录到:~/logs/${appname}/${appname}.log,级别:INFO,我们目前可以通过配置root来满足这个需求,如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="APP_NAME" value="logbacktest" />
<property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" /> <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
...
</appender> <root level="INFO">
<!--ref表示具体的appender name-->
<appender-ref ref="APP_LOG" />
</root> </configuration>

这样配置之后,所有的日志都会以root的level,即INFO去使用APP_LOG这个appender打印。

2.6 打印日志

编辑Util.java如下所示,打印5个级别的日志:

package com.test.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* @author bodong.ybd
* @date 2019/6/17
*/
public class Util {
private static final Logger log = LoggerFactory.getLogger(Util.class); public static void loginfo() {
log.trace("trace");
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
}
}

编辑Main.java文件内容如下所示:

package com.test;

import com.test.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* @author bodong.ybd
* @date 2019/6/17
*/
public class Main {
private static final Logger log = LoggerFactory.getLogger(Main.class); private static void loginfo() {
log.trace("trace");
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
} public static void main(String[] args) {
loginfo();
Util.loginfo();
}
}

运行Main.java,打印日志,效果如下:

➜  ~ ls ~/logs/logbacktest
logbacktest.log
➜ ~ cat ~/logs/logbacktest/logbacktest.log
2019-06-19 19:33:26 [main] INFO com.test.Main - info
2019-06-19 19:33:26 [main] WARN com.test.Main - warn
2019-06-19 19:33:26 [main] ERROR com.test.Main - error
2019-06-19 19:33:26 [main] INFO com.test.util.Util - info
2019-06-19 19:33:26 [main] WARN com.test.util.Util - warn
2019-06-19 19:33:26 [main] ERROR com.test.util.Util - error

可以看到产生了一个日志文件logbacktest.log,并且Main.javaUtil.java的日志都打印在了这个文件中,日志级别是root配置的INFO,但是这样并不满足要求,因为2.1要求:util目录下的文件产生的日志全部记录到:~/logs/${appname}/util.log,级别:DEBUG,这个问题需要配置logger来解决。

2.7 配置logger

logger用来设置某个包或者类具体日志的打印级别,下面我们把Util.java配置拆出去。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="APP_NAME" value="logbacktest" />
<property name="LOG_NAME" value="${user.home}/logs/${APP_NAME}/${APP_NAME}.log" />
<property name="UTIL_NAME" value="${user.home}/logs/${APP_NAME}/util.log" /> <appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
...
</appender> <appender name="UTIL_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--指定日志文件名称-->
<file>${UTIL_NAME}</file>
<encoder>
<!--指定日志内容格式-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf8</charset>
</encoder>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${UTIL_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--日志最大保存15天-->
<maxHistory>15</maxHistory>
<!--日志最大的文件大小-->
<maxFileSize>100MB</maxFileSize>
<!--日志最大保存10GB-->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
</appender> <!--com.test.util目录下的文件产生的日志全部记录到util.log-->
<!--默认的日志级别是DEBUG-->
<!--additivity=false表示如果能匹配到这条规则就不用往上继续查找到root节点去-->
<logger name="com.test.util" level="DEBUG" additivity="false" >
<appender-ref ref="UTIL_LOG"/>
</logger> <root level="INFO">
<!--ref表示具体的appender-->
<appender-ref ref="APP_LOG" />
</root> </configuration>

这样配置完成后就可以把Util.javaMain.java的日志分开了,效果如下所示:

➜  ~ ls ~/logs/logbacktest
logbacktest.log util.log
➜ ~ cat ~/logs/logbacktest/logbacktest.log
2019-06-19 19:42:51 [main] INFO com.test.Main - info
2019-06-19 19:42:51 [main] WARN com.test.Main - warn
2019-06-19 19:42:51 [main] ERROR com.test.Main - error
➜ ~ cat ~/logs/logbacktest/util.log
2019-06-19 19:42:51 [main] DEBUG com.test.util.Util - debug
2019-06-19 19:42:51 [main] INFO com.test.util.Util - info
2019-06-19 19:42:51 [main] WARN com.test.util.Util - warn
2019-06-19 19:42:51 [main] ERROR com.test.util.Util - error

好了,看到这里,详细Http.java相关的日志你肯定也会配置了,试试看。

三、配置异步日志

写入方式 优点 缺点
同步 一般使用O_SYNC标志打开文件,即每条日志会至少写入磁盘缓存,安全。 慢,每条都会刷盘。
异步 将内容写入到内存即可返回(不同异步库可能用不同数据结构),速度快。更多可参考:https://logging.apache.org/log4j/2.x/manual/async.html 不安全,如果内存数据结构满了或者机器断电,可能造成数据丢失。

使用logback配置异步日志可以使用appender:ch.qos.logback.classic.AsyncAppender,将上面的Util.java的log配置为异步如下所示:

<appender name ="ASYNC_UTIL_LOG" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<neverBlock>true</neverBlock>
<appender-ref ref="UTIL_LOG"/>
</appender> <logger name="com.test.util" level="DEBUG" additivity="false" >
<appender-ref ref="ASYNC_UTIL_LOG"/>
</logger>

discardingThreshold:默认情况下,当阻塞队列剩余20%的容量时,它将丢弃级别TRACE,DEBUG和INFO的事件,仅保留级别WARN和ERROR的事件。要保留所有事件,请将discardingThreshold设置为0。
queueSize:阻塞队列的最大容量。默认情况下,queueSize设置为256。
neverBlock:如果为false(默认值),appender将阻塞在添加队列的接口处。设置为true,appender将删除消息,不会阻止您的应用程序。
这几个参数更加详细的解释见: https://logback.qos.ch/manual/appenders.html

logback还有另一种异步日志配置方式,即使用disruptor,可参考这里配置。

四、总结

本文以一个例子说明了logback+slf4配置同步和异步日志的方法,使用异步日志除了可以提高程序性能之外,还可以防止部分磁盘IO Hang导致的问题,水平有限,如有不足,请指出。

[完]

Java Logback简易教程的更多相关文章

  1. LogBack简易教程

    1.简介 LogBack是一个日志框架,它与Log4j可以说是同出一源,都出自Ceki Gülcü之手.(log4j的原型是早前由Ceki Gülcü贡献给Apache基金会的) 1.1 LogBac ...

  2. Spark-Mllib中各分类算法的java实现(简易教程)

    一.简述 Spark是当下非常流行的数据分析框架,而其中的机器学习包Mllib也是其诸多亮点之一,相信很多人也像我那样想要快些上手spark.下面我将列出实现mllib分类的简明代码,代码中将简述训练 ...

  3. JavaScript简易教程(转)

    原文:http://www.cnblogs.com/yanhaijing/p/3685304.html 这是我所知道的最完整最简洁的JavaScript基础教程. 这篇文章带你尽快走进JavaScri ...

  4. Ant 简易教程

    转载:http://www.cnblogs.com/jingmoxukong/p/4433945.html Ant 简易教程 Apache Ant,是一个将软件编译.测试.部署等步骤联系在一起加以自动 ...

  5. Intellj IDEA 简易教程

    Intellj IDEA 简易教程 目录 JDK 安装测试 IDEA 安装测试 调试 单元测试 重构 Git Android 其他 参考资料 Java开发IDE(Integrated Developm ...

  6. Android开发简易教程

    Android开发简易教程 Android 开发因为涉及到代码编辑.UI 布局.打包等工序,有一款好用的IDE非常重要.Google 最早提供了基于 Eclipse 的 ADT 作为开发工具,后来在2 ...

  7. 移动开发之【微信小程序】的原理与权限问题以及相关的简易教程

    这几天圈子里到处都在传播着这样一个东西,微信公众平台提供了一种新的开放能力,开发者可以快速开发一个小程序,取名曰:微信公众平台-小程序 据说取代移动开发安卓和苹果,那这个东东究竟是干吗用的?但很多人觉 ...

  8. JavaScript简易教程

    这是我所知道的最完整最简洁的JavaScript基础教程. 这篇文章带你尽快走进JavaScript的世界——前提是你有一些编程经验的话.本文试图描述这门语言的最小子集.我给这个子集起名叫做“Java ...

  9. Android实战简易教程-第三十九枪(第三方短信验证平台Mob和验证码自己主动填入功能结合实例)

    用户注冊或者找回password时通常会用到短信验证功能.这里我们使用第三方的短信平台进行验证实例. 我们用到第三方短信验证平台是Mob,地址为:http://mob.com/ 一.注冊用户.获取SD ...

随机推荐

  1. 配置阿里云gradle

    build.gradle buildscript { ext { springBootVersion = '1.5.15.BUILD-SNAPSHOT' } repositories { // mav ...

  2. P5488-差分与前缀和【NTT,生成函数】

    正题 题目链接:https://www.luogu.com.cn/problem/P5488 题目大意 求一个长度为$n$的序列的$k$阶差分/前缀和. 解题思路 先考虑前缀和怎么做 搞出来生成函数就 ...

  3. CF755G-PolandBall and Many Other Balls【倍增FFT】

    正题 题目链接:https://www.luogu.com.cn/problem/CF755G 题目大意 \(n\)个东西排成一排,每个组可以选择一个单独的物品或者两个连续的物品,一个物品不同同时在两 ...

  4. 2017第二届广东省强网杯线上赛:WEB phone number (SQL注入)

    目录 解题思路 总结 解题思路 拿到题目的时候,只有一个登录界面 拿到登录界面,而且还伴随着有注册界面,联想到SQL的二次注入漏洞 尝试注册admin'#,并使用admin登录,发现登录失败,说明可能 ...

  5. Java(一)——基础知识

    引言 之前一直对 Java 怀有固执的偏见,以为 Java 是编写前端的语言,作为一个机械生,非常抗拒去学它. 但是最近接触一点以后,完全改观了先前的看法,于是开启了对 Java 的大学习. 一.数据 ...

  6. 最详细STL(一)vector

    vector的本质还是数组,但是可以动态的增加和减少数组的容量(当数组空间内存不足时,都会执行: 分配新空间-复制元素-释放原空间),首先先讲讲vector和数组的具体区别 一.vector和数组的区 ...

  7. 踩坑系列《十三》解决时间戳long转换int溢出(即转换值为负数)

    最近业务需求,需要使用到 int 类型的时间戳,所以在使用时间戳的时候,由于java自带的 System.currentTimeMillis() 返回的类型是long,强行转换一波的话,是会出现数据溢 ...

  8. 用Flask 实现文件服务器(包含docker版本)

    最近有了公司局域网内共享axure原型的需求,所以用Flask开发了一款文件上传/查看工具,记录一下其中的问题和解决方案 这个工具参照了一位大神的uploads工具 https://zhuanlan. ...

  9. Python代码阅读(第12篇):初始化二维数组

    Python 代码阅读合集介绍:为什么不推荐Python初学者直接看项目源码 本篇阅读的代码实现了二维数组的初始化功能,根据给定的宽高初始化二维数组. 本篇阅读的代码片段来自于30-seconds-o ...

  10. 初步认识HCIA,什么是计算机网络,拓扑,网络的发展,交换机,路由器,IP,光纤,带宽,广播,ARP......

    HCIA ---- 华为认证初级网络工程师 云技术 --- 云存储 云计算 计算机技术 : ​ --- 抽象语言 -- 电线号的转换 抽象语言 -- 编码 ---- 应用层 编码 --- 二进制 -- ...