百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

6000字讲明白大型网站架构技术细节:后端架构规整化java日志框架

ccwgpt 2024-10-01 08:12 37 浏览 0 评论

日志

对于一个大型网站系统而言,日志是后端应用程序必须要引入的模块,日志有利于追查Bug和记录用户操作。每个编程语言都有很多现成的日志框架,而Java的日志框架也有很多,如log4j2、Logback、SLF4J等,这些日志框架都可以让后端应用程序按照一定的规则输出日志文件。

在大型网站系统当中,仅仅把日志记录下来是远远不够的。大型网站系统需要一个完整的日志系统,日志系统除了需要收集日志并将其记录下来以外,还需要做日志筛选、用户行为记录追溯及风险预警等工作。

不过,在项目中前期,或者单独调试某个后端应用程序时,仍然需要使用日志模块。这里以log4j2为例,通过日志模块引入和规范化日志记录两个方面对日志模块的使用进行介绍。

1.日志模块引入

引入日志模块的具体操作步骤如下:

(1)引入log4j2依赖包。需要在工程配置文件(build.gradle)中添加log4j2依赖包,如代码4.32所示。需要注意的是,如果不去除Spring Boot中原有日志模块的话,那么新引入的日志模块与原有日志模块会产生冲突。

代码4.32 build.gradle中添加log4j2依赖包

configurations {

//去除Spring Boot中原有的日志模块

all*.exclude group: 'org.springframework.boot', module: 'spring-boot

starter-logging'

}

dependencies {

//dependencies中添加log4j2的依赖包

implementation 'org.springframework.boot:spring-boot-starter-log4j2'

…}

(2)同步工程配置。修改完build.gradle文件后,log4j2的依赖包在同步工程配置后才会被下载和引入。在IntelliJ IDEA中单击“同步”按钮即可同步工程配置,如图4.59所示。

(3)创建log4j2的配置文件log4j2.xml,其内容及设置说明如代码4.33所示,更详细的说明请参考官方说明(https://logging.apache.org/log4j/2.x/manual/index.html)。另外,日志配置文件一般与后端应用程序的配置文件放在一起,如图4.60所示。

说明:图4.60中的“配置文件目录位置”和“后端应用程序配置文件名”都不是默认设置。关于“配置文件目录位置”和“后端应用程序配置文件名”的设置可参考前面小节中的讲解。

代码4.33 log4j2.xml文件的内容及其配置

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

<!-- monitorInterval:检查更新的时间间隔,单位为s。

在程序运行期间,log4j2能够自动检测日志配置文件是否有更新,如果有更新则自动加载新设

置-->

<configuration monitorInterval="1800">

<!--配置变量,变量会被后续设置使用-->

<properties>

<!-- 设置日志格式的变量:%d:获取日期时间;

%level:日志等级;

%msg:日志消息,如ERROR、INFO、DEBUG等

%n:换行符-->

<property name="LOG_PATTERN"

value="[%d{yyyy-MM-dd}][%d{HH:mm:ss}][%level]%msg%n" />

<!-- 设置日志存储路径的变量 -->

<property name="FILE_PATH" value="D:/logs/backend/demo" />

</properties>

<!-- 设置日志输出源,如设置日志输出格式、设置日志文件名等 -->

<appenders>

<!-- 设置Console(控制台)输出日志格式,一般在开发工具调试时使用 -->

<console name="Console" target="SYSTEM_OUT">

<!--输出日志的格式,采用properties中设置的LOG_PATTERN变量-->

<patternLayout pattern="${LOG_PATTERN}"/>

</console>

<!-- 设置记录INFO和DEBUG日志等级的日志文件,当符合存档策略时在(<policies></policies>中设置),则会自动压缩并另存为存档文件。

fileName:日志文件名,使用properties中设置的FILE_PATH变量。在此例中,输出文件名为D:/logs/backend/demo/web-info.log。

immediateFlush:接收到日志后,是否立即输出到文件中。这个一般设置为false,设置为true会严重影响接口的并发能力。

filePattern:存档文件名,在此例中,归档文件名为(以2020-2-23为例)D:/logs/backend/demo/web-info/web-info-2020-2-23_1.log.gz-->

<rollingFile name="RollingFileInfo" fileName="${FILE_PATH}/web

info.log"

immediateFlush="false"

filePattern="${FILE_PATH}/web-info/web-info-%d{yyyy-MM

dd}_%i.log.gz">

<!-- 输出日志的格式,采用properties中设置的LOG_PATTERN变量 -->

<patternLayout pattern="${LOG_PATTERN}"/>

<!-- 筛选接收的日志等级,接收INFO和DEBUG等级的日志 -->

<filters>

<thresholdFilter level="error" onMatch="DENY" onMismatch=

"NEUTRAL"/>

<thresholdFilter level="info" onMatch="ACCEPT" onMismatch=

"DENY"/>

<thresholdFilter level="debug" onMatch="ACCEPT" onMismatch=

"DENY"/>

</filters>

<!-- 设置存档策略,此例为:每天自动存档,日志文件超过20MB也会存档 -->

<policies>

<!-- 设置时间的存档策略,interval的时间精度与filePattern的时间精

度一致,因为filePattern只设置到日期,所以这里的interval="1"指的是1天-->

<timeBasedTriggeringPolicy interval="1"/><!-- 设置文件大小的存档策略-->

<sizeBasedTriggeringPolicy size="20MB"/>

</policies>

<!-- 设置保留多少个日志文件,日志文件个数超过max的值会自动覆盖 -->

<defaultRolloverStrategy max="15"/>

</rollingFile>

<!-- 设置记录error日志等级的日志文件,配置格式与上面“设置记录INFO和DEBUG

日志等级的日志文件”相同,这里不展开介绍。

在此例子中,ERROR日志的日志文件名为D:/logs/backend/demo/web-error.log,归档文件名为(以2020-2-23为例)

D:/logs/backend/demo/web-error/web-error-2020-2-23_1.log.gz-->

<rollingFile name="RollingFileError" fileName="${FILE_PATH}/web

error.log"

immediateFlush="false"

filePattern="${FILE_PATH}/web-error/web-error-%d{yyyy-MM

dd}_%i.log.gz">

<patternLayout pattern="${LOG_PATTERN}"/>

<filters>

<thresholdFilter level="error" onMatch="ACCEPT" onMismatch=

"DENY"/>

</filters>

<policies>

<timeBasedTriggeringPolicy interval="1"/>

<sizeBasedTriggeringPolicy size="20MB"/>

</policies>

<defaultRolloverStrategy max="15"/>

</rollingFile>

</appenders>

<!-- 设置日志源,需要在这里关联日志输出源(<appenders></appenders>)才能输出到对

应文件当中 -->

<loggers>

<!-- 设置输出日志等级,默认情况下,不会输出比该日志等级低的日志。

在此例中,只输出FATAL、ERROR、WARN、INFO的日志。

日志级别以及优先级排序为OFF > FATAL > ERROR > WARN > INFO > DEBUG >

TRACE > ALL,其中OFF是不输出所有日志-->

<root level="info">

<!-- 关联输出源,其中ref中的值需要与<rollingFile></rollingFile>中

的name对应 -->

<!-- 在非调试环境下,需要关闭控制台的日志输出(去掉下面第一行就可以了) -->

<AppenderRef ref="Console" />

<AppenderRef ref="RollingFileInfo" />

<AppenderRef ref="RollingFileError" />

</root>

</loggers>

</configuration>

(4)引入日志配置文件。在后端应用程序配置文件中指定日志配置文件路径后,才能生效。在如图4.60所示的工程目录结构中,需要在demo.properties文件中添加日志配置文件的路径,如代码4.34所示,其中,classpath:log4j2.xml为具体的路径。

代码4.34 在后端应用程序的配置文件中添加日志配置文件的路径

#设置日志配置文件的路径

logging.config=classpath:log4j2.xml

(5)程序中记录日志,如代码4.35所示。对应的日志输出结果如代码4.36所示,其中,由于log4j2.xml(日志配置文件)设置的日志输出等级为INFO,所以DEBUG等级的日志没有被记录下来。

说明:日志配置文件的相关说明请参照代码4.33,其中,设置输出日志等级的位置为<root level="info">…</root>。

代码4.35 代码中记录日志

//需要引入的日志依赖类

import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

public class XX{

//获取日志对象

private static final Logger LOGGER = LogManager.getLogger();

public void Function(){

//输出INFO级别的日志

LOGGER.info("info log test.");

//输出ERROR级别的日志

LOGGER.error("error log test.");

//输出DEBUG级别的日志

LOGGER.debug("debug log test.");

}

}

代码4.36 日志输出结果

[2020-04-20][17:46:17][INFO]info log test.

[2020-04-20][17:46:17][ERROR]error log test.

(6)如果实现了4.3.3小节中的后端应用程序与配置文件分离,那么日志配置文件也应该从后端应用程序中分离出来。按常理来说,只要在配置文件中设置日志配置文件路径就可以了(如logging.config=/home/tomcat/appconfig/log4j2.xml),但是由于某种冲突,这样的配置是不起作用的。

因此,要想实现后端应用程序引用外部的日志配置文件,需要通过“添加启动参数”和“修改代码(ServletInitializer.java)”才能实现。具体做法是,“添加启动参数”需要在Tomcat目录下的/conf/catalina.properties文件中添加如代码4.37所示的设置,修改后的代码如代码4.38所示,其中,xxx_log4j2.xml为日志配置文件名。最终,配置文件和日志配置文件集中管理的效果如图4.61所示。

代码4.37 设置日志配置文件所在目录

#后端应用程序的外部配置文件所在目录,详见4.3.3节的介绍

spring.config.location=${catalina.home}/appconfig/

#新增代码,设置日志配置文件所在目录,一般与后端应用程序的外部配置文件目录相同

logging.config=${catalina.home}/appconfig/

代码4.38 修改后的ServletInitializer.java文件

public class ServletInitializer extends SpringBootServletInitializer {

@Override

protected SpringApplicationBuilder configure(SpringApplicationBuilder

application) {

//新增代码,补全启动参数(logging.config)的文件名

String loggingConfig = System.getProperty("logging.config");

if(!loggingConfig.isEmpty()) {

System.setProperty("logging.config", loggingConfig+"xxx_

log4j2.xml");

}

//设置后端应用程序的外部配置文件名,详见4.3.3小节的介绍

return application

.properties("spring.config.name=xxx")

.sources(xxApplication.class);

//初始工程的代码,需要去除

//return application.sources(xxApplication.class);

}

}

2.规范化日志记录

日志记录是一个十分开放的行为,原则上,只要记录的日志能“定位问题发生的位置”和“记录某些重要的用户操作”就可以了。但是,在实际项目当中,由于日志对功能的实现是不产生影响的,所以日志通常都是通过一次次问题修复而得到补充的。而这种“补丁型”日志,通常是混乱的。混乱的日志对于“定位问题发生的位置”和“追查某些重要的用户操作”都不能起到很好的作用,因此,日志记录需要规范化。

注意:日志记录的规则需要在项目前期定好,在开发过程中吸收规整化日志的工作量。项目后期再整理日志是很不理智的行为,因为后期整理日志需要花费大量的时间去梳理和理解业务代码,而这部分工作量是很难预估且十分枯燥的,最后整理日志的结果往往也是不尽人意的。

规范化日志记录可以从限制日志等级、明确日志记录位置、添加日志跟踪码等方面进行考虑。

(1)限制日志等级。日志模块一般都有等级划分,以log4j2为例,其日志等级有6种,分别为TRACE(追踪调试)、DEBUG(调试)、INFO(信息)、WARNING(警告)、ERROR(错误)和FATAL(致命错误)。每个日志等级看上去都有相对明确的分工和含义,但是在实际应用当中,这些日志等级的具体用途其实相当模糊,很多时候,都很难界定一个日志应该归类为哪一个等级。一旦出现这种模糊规则,就会出现一人一个样的做法,最后导致“五花八门”的日志等级划分原则。

因此,规整化日志需要限制日志等级。一般情况下,后端应用程序使用DEBUG、INFO和ERROR三个日志等级就足够了,这三个日志等级的分工和协助如图4.62所示。

其中,DEBUG日志在运行时不生效,需要打开调试模式(修改日志输出等级)后,才能记录DEBUG日志。

(2)明确日志记录位置。在限制了日志等级后,需要解决“补丁式日志”的问题,避免日志不够全面的情况。而解决“补丁式日志”的关键,是明确日志记录位置。但是,明确日志记录位置是一件很难实现的事情,因为接口程序与接口程序之间很难找到共性。

不过,如果实现了4.3.2小节中介绍的“限制函数调用层级”“公共模块”和“错误机制”,那么接口程序会变成流水线式的处理方式。在流水线式的接口程序中明确日志记录位置是相对容易的,如图4.63所示。

其中,每个接口只需要在“接收请求”“数据库操作”和“返回结果”这三部分添加日志就可以了,其余日志都在公共模块里,而且公共模块里的日志是一次添加全局有效的。

说明:Dao层(数据库操作)其实也可以做成一个公共模块,这样可以省掉一些日志工作量。另外,虽然数据库本身可以自动记录日志,但是数据库自身的日志不能包含用户身份信息,即不能追溯用户操作,所以Dao层(数据库操作)的日志是有必要记录的。

(3)添加日志跟踪码。即使日志被记录得十分详细,分析日志也是一件很麻烦的事情。同一时刻,后端应用程序可能会同时处理多个请求,以至于多个请求的日志是混合在一起的,在不经过特殊处理的情况下,根本没法分辨哪几条日志是属于同一个请求的。像这种无法区分请求的日志,被记录下来也是浪费资源。

因此,需要在每条日志中添加日志跟踪码,标记同一请求的日志。跟踪码的本质,就是同一请求输出日志时,都多加一个相同的字符串。如果使用的是log4j2日志模块,可以在不改变原有日志输出代码的前提下,添加日志跟踪码。

首先,需要修改日志配置文件中的日志输出格式,如代码4.39所示,其中[%X{requestId}]为新增的跟踪码格式。

代码4.39 修改日志输出格式

<!-- 此段设置截取自代码4.33的,单独设置是不起作用的 -->

<!-- 设置日志格式的变量:

%d:获取日期时间;

%level:日志等级;

%X{requestId}:跟踪码;

%msg:日志消息,如ERROR、INFO、DEBUG等;

%n:换行符-->

<property name="LOG_PATTERN"

value="[%d{yyyy-MM-dd}][%d{HH:mm:ss}][%level][%X{requestId}]

%msg%n" />

修改完日志配置文件之后,需要在每个接口程序中添加“生成跟踪码”的代码,如代码4.40所示,其中,代码中的函数为Controller层中的接口的入口函数,requestId对应代码4.39中的跟踪码标识。

代码4.40 添加“生成跟踪码”的代码

@Controller

@RequestMapping(value="…",method = RequestMethod.POST)

@ResponseBody

public JSONObject XXX(@RequestBody String requestParam, HttpServlet

Response response) {

//在每个接口的入口函数都需要添加以下“生成跟踪码”代码

ThreadContext.put("requestId", UUID.randomUUID().toString());

}

…修改日志跟踪码后,能清晰地识别不同请求的日志,日志输出结果如代码4.41所示,其中,62e3300c-e0a0-40cd-be80-4320d40ddc2c和00000000-0000-0000-0000-000000000000是日志追踪码。

代码4.41 添加“日志跟踪码”后的日志输出结果

[2020-04-20][17:46:17][INFO][62e3300c-e0a0-40cd-be80-4320d40ddc2c]info

log test_1.

[2020-04-20][17:46:17][INFO][00000000-0000-0000-0000-000000000000]info

log test_1.

[2020-04-20][17:46:17][INFO][00000000-0000-0000-0000-000000000000]info

log test_2.

[2020-04-20][17:46:17][INFO][62e3300c-e0a0-40cd-be80-4320d40ddc2c]info

log test_2.

[2020-04-20][17:46:17][INFO][62e3300c-e0a0-40cd-be80-4320d40ddc2c]info

log test_3.

本文给大家讲解的内容是大型网站架构的技术细节:后端架构规整化java日志框架

  1. 下篇文章给大家讲解的内容是大型网站架构的技术细节:后端架构规整化自研框架Once
  2. 感谢大家的支持!

相关推荐

Java七大热门技术框架源码解析(25章) 完结

获课》aixuetang.xyz/5699/Hibernate与MyBatis源码级PK:ORM框架的两种哲学在Java持久层框架领域,Hibernate与MyBatis代表了两种截然不同的设计哲学。...

【25章】Java七大热门技术框架源码解析

获课》aixuetang.xyz/5699/Java高级面试:七大框架源码精讲与实战解析在当今Java技术生态中,对主流框架源码的深入理解已成为高级开发者面试的核心竞争力。掌握Spring、MyBat...

饿了么董事长吴泽明兼任CEO,韩鎏分管即时物流中心

饿了么调整组织架构。2月11日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明...

饿了么100%迁至阿里云,快速扩容可支持1亿人同时点单

来源:环球网6月17日,记者获悉,饿了么已完成100%上云,所有业务系统、数据库设施等均已迁移至阿里云。高峰期,饿了么可在阿里云上快速扩容,可以支持1亿人同时在线点单,这意味着饿了么的服务能力再次全面...

饿了么组织架构调整:董事长吴泽明兼任CEO 韩鎏专注即时物流中心管理

近日,饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴泽明汇报。吴泽明在内部信中表示,考虑即时物...

饿了么组织架构调整:董事长吴泽明兼任CEO

Tech星球2月11日消息,据新浪科技报道,今日饿了么董事长吴泽明(花名:范禹)通过公司全员信宣布饿了么最新组织调整:即日起,吴泽明将兼任饿了么CEO,韩鎏(花名:昊宸)专注分管即时物流中心,继续向吴...

饿了么又调整了组织架构,董事长吴泽明兼任CEO

2月11日,饿了么董事长,花名为范禹的吴泽明,通过公司全员信宣布最新组织调整:从即日起,吴泽明将兼任饿了么CEO。公司原CEO,花名为昊宸的韩鎏今后专注分管即时物流中心,继续向吴泽明汇报。在内部信中,...

SpringBoot项目快速开发框架JeecgBoot——Web处理!

Web处理JeecgBoot框架主要用于Web开发领域。下面介绍JeecgBoot在Web开发中的常用功能,如控制器、登录、系统菜单、权限模块的角色管理和用户管理。首先启动后台项目,将其导入IDE...

腾讯即将开源Kuikly:基于Kotlin的纯原生跨端解决方案

IT之家3月4日消息,腾讯日前在端服务网站发布预告,即将开源Kuikly跨端开发框架。预告海报介绍称,Kuikly是基于KotlinKMM技术、客户端开发友好的全新跨端解决方案,可...

Python构建MCP服务器完整教程:5步打造专属AI工具调用系统

模型控制协议(ModelControlProtocol,MCP)是一种专为实现AI代理与工具解耦而设计的通信协议,为AI驱动应用程序的开发提供了高度的灵活性和模块化架构。通过MCP服务器,AI代...

Python3使用diagrams生成架构图(python模块制作)

目录技术背景diagrams的安装基础逻辑关系图组件簇的定义总结概要参考链接技术背景对于一个架构师或者任何一个软件工程师而言,绘制架构图都是一个比较值得学习的技能。这就像我们学习的时候整理的一些Xmi...

Python 失宠!Hugging Face 用 Rust 新写了一个 ML框架,现已低调开源

大数据文摘受权转载自AI前线整理|褚杏娟近期,HuggingFace低调开源了一个重磅ML框架:Candle。Candle一改机器学习惯用Python的做法,而是Rust编写,重...

Python Web 框架(Python Web 框架)

Tornado、Flask、Django三个PythonWeb框架的主要区别和适用场景:特点/框架TornadoFlaskDjango类型异步非阻塞Web服务器和框架轻量级微框架全功能...

构建并发布你的自定义 Python 包(python如何创建自定义模块)

Python让你可以重用代码,并将代码分享给他人以节省时间和精力。所以,当你编写了一些方便的脚本,希望你的同事或其他人也能使用时,接下来该怎么做呢?这篇文章就来解决打包和分发的问题。我们将专注于将你...

Python 应用开发框架 BeeWare 简明实用教程

1.BeeWare简介BeeWare是一个Python框架,用于开发跨平台原生应用。它支持Android、iOS、Windows、macOS和Linux,并提供原生用户体验。2.安装B...

取消回复欢迎 发表评论: