SpringBoot使用log4j2将日志记录到文件及自定义数据库
目录
一、环境说明
二、进行配置
1、pom.xml
2、log4j2.xml
3、CustomDataSourceProperties
4、ConfigReader
5、ConnectionFactory 连接工厂类,用于管理数据库连接
三、进行简单测试配置
1、LogUtils
2、LoginUserInfoHelper
3、LoginLogUtils
4、写日志
一、环境说明
Spring Boot2+MyBatis-Plus+Log4j2
二、进行配置
1、pom.xml
由于Spring Boot内置的日志框架是logback,会导致和log4j2冲突,所以要先排除项目中logback的依赖。同时引入log4j2。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions> <!-- 去掉springboot默认配置 --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></dependency><dependency> <!-- 引入log4j2依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency>
2、log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration 标签用于配置 Log4j2,其中 status 属性设置为 "WARN",表示仅记录警告级别的日志用于运行时状态。monitorInterval 属性设置为 "30",表示每隔 30 秒自动检查配置文件更改。
-->
<Configuration status="WARN" monitorInterval="30"><Properties><!--定义名为 "baseDir" 的属性,用于指定日志文件的基本目录。其他地方可以使用 "${baseDir}" 引用这个值。--><Property name="baseDir">./logs</Property><!--定义名为 "logPattern" 的属性,用于指定日志的格式,以便在后续配置中引用。--><Property name="logPattern">%d{yyyy-MM-dd HH:mm:ss} %highlight{%6p} %style{%5pid}{bright,magenta} --- [%15.15t]%style{%-40.40logger{39}}{bright,cyan} : %m%n</Property><!-- 文件输出的格式 --><Property name="fileLayout">%d{yyyy-MM-dd HH:mm:ss} %p --- [%t] %logger : %m%n"</Property><!-- <Property name="logPattern">%highlight{%d{HH:mm:ss:SSS} [%-5p] - %l - %m%n}{FATAL=red, ERROR=red, WARN=yellow, INFO=default, DEBUG=default}</Property> --><!--定义名为 "fileSize" 的属性,用于指定日志文件的最大大小。其他地方可以使用 "${fileSize}" 引用这个值。--><Property name="fileSize">10MB</Property></Properties><!-- OFF 0 --><!-- FATAL 100 --><!-- ERROR 200 --><!-- WARN 300 --><!-- INFO 400 --><!-- DEBUG 500 --><!-- TRACE 600 --><!-- ALL Integer.MAX_VALUE --><CustomLevels><CustomLevel name="CUSTOM_LOG" intLevel="90"/><CustomLevel name="EXCEPTION_LOG" intLevel="91"/><CustomLevel name="OPERATION_LOG" intLevel="92"/><CustomLevel name="LOGIN_LOG" intLevel="93"/></CustomLevels><Appenders><!-- Console Appender 用于将日志输出到控制台。--><Console name="Console" target="SYSTEM_OUT"><!-- 只输出 DEBUG 级别及以上的日志 --><ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/><!-- 使用定义的日志格式 --><PatternLayout pattern="${logPattern}"/></Console><!-- DEBUG 级别的日志文件输出 --><RollingFile name="DebugAppender" fileName="${baseDir}/app_debug.log"filePattern="${baseDir}/debug_%d{yyyy-MM-dd}_%i.log"><!-- 仅接受 DEBUG 级别的日志 --><Filters><ThresholdFilter level="DEBUG"/><ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><!-- 使用定义的日志格式 --><PatternLayout pattern="${fileLayout}"/><Policies><!-- 每隔 1 天滚动一次日志,"modulate" 表示即使没有日志产生,也会在下一天创建新文件 --><TimeBasedTriggeringPolicy interval="1" modulate="true"/><!-- 当日志文件大小达到指定大小时触发滚动 --><SizeBasedTriggeringPolicy size="${fileSize}"/></Policies></RollingFile><!-- INFO 级别的日志文件输出 --><RollingFile name="InfoAppender" fileName="${baseDir}/app_info.log"filePattern="${baseDir}/info_%d{yyyy-MM-dd}_%i.log"><Filters><!-- 仅接受 INFO 级别的日志 --><!-- onMatch="ACCEPT" onMismatch="DENY" --><ThresholdFilter level="INFO"/><ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><!-- 使用定义的日志格式 --><PatternLayout pattern="${fileLayout}"/><Policies><!-- 每隔 1 天滚动一次日志,"modulate" 表示即使没有日志产生,也会在下一天创建新文件 --><TimeBasedTriggeringPolicy interval="1" modulate="true"/><!-- 当日志文件大小达到指定大小时触发滚动 --><SizeBasedTriggeringPolicy size="${fileSize}"/></Policies></RollingFile><!--处理 WARN 级别的日志--><RollingFile name="WarnAppender" fileName="${baseDir}/app_warn.log"filePattern="${baseDir}/info_%d{yyyy-MM-dd}_%i.log"><Filters><ThresholdFilter level="WARN"/><ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><!-- 使用定义的日志格式 --><PatternLayout pattern="${fileLayout}"/><Policies><!-- 每隔 1 天滚动一次日志,"modulate" 表示即使没有日志产生,也会在下一天创建新文件 --><TimeBasedTriggeringPolicy interval="1" modulate="true"/><!-- 当日志文件大小达到指定大小时触发滚动 --><SizeBasedTriggeringPolicy size="${fileSize}"/></Policies></RollingFile><!-- ERROR 级别的日志文件输出 --><RollingFile name="ErrorAppender" fileName="${baseDir}/app_error.log"filePattern="${baseDir}/error_%d{yyyy-MM-dd}_%i.log"><!-- 仅接受 ERROR 级别的日志 --><Filters><ThresholdFilter level="ERROR"/><ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><!-- 使用定义的日志格式 --><PatternLayout pattern="${fileLayout}"/><Policies><!-- 每隔 1 天滚动一次日志,"modulate" 表示即使没有日志产生,也会在下一天创建新文件 --><TimeBasedTriggeringPolicy interval="1" modulate="true"/><!-- 当日志文件大小达到指定大小时触发滚动 --><SizeBasedTriggeringPolicy size="${fileSize}"/></Policies></RollingFile><!-- 配置 HikariSqlFile Appender,将 SQL 日志输出到文件的日志记录追加器 --><!-- <RollingFile name="HikariSqlAppender" fileName="${baseDir}/app_sql.log" --><!-- filePattern="${baseDir}/app_sql_%d{yyyy-MM-dd}_%i.log"> --><!-- <!– 使用定义的日志格式 –> --><!-- <PatternLayout pattern="${fileLayout}"/> --><!-- <Policies> --><!-- <!– 每隔 1 天滚动一次日志,"modulate" 表示即使没有日志产生,也会在下一天创建新文件 –> --><!-- <TimeBasedTriggeringPolicy interval="1" modulate="true"/> --><!-- <!– 当日志文件大小达到指定大小时触发滚动 –> --><!-- <SizeBasedTriggeringPolicy size="${fileSize}"/> --><!-- </Policies> --><!-- </RollingFile> --><!-- 配置JDBC Appender --><!-- 登录日志表 --><JDBC name="LoginDatabase" tableName="sys_log_login"><ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/><Filters><ThresholdFilter level="LOGIN_LOG"/><ThresholdFilter level="OPERATION_LOG" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><Column name="user_id" pattern="%X{user_id}"/><Column name="user_name" pattern="%X{user_name}"/><Column name="client_ip" pattern="%X{client_ip}"/><Column name="device_info" pattern="%X{device_info}"/><Column name="remarks" pattern="%X{remarks}"/><Column name="login_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/></JDBC><!-- 操作日志表 --><JDBC name="OperationDatabase" tableName="sys_log_operation"><ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/><Filters><ThresholdFilter level="OPERATION_LOG"/><ThresholdFilter level="EXCEPTION_LOG" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><Column name="user_id" pattern="%X{user_id}"/><Column name="user_name" pattern="%X{user_name}"/><Column name="v_before" pattern="%X{v_before}"/><Column name="v_after" pattern="%X{v_after}"/><Column name="remarks" pattern="%X{remarks}"/><Column name="operation_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/></JDBC><!-- 异常日志表 --><JDBC name="ExceptionDatabase" tableName="sys_log_exception"><ConnectionFactory class="com.cj.blog.common.logs.ConnectionFactory" method="getDatabaseConnection"/><Filters><ThresholdFilter level="EXCEPTION_LOG"/><ThresholdFilter level="CUSTOM_LOG" onMatch="DENY" onMismatch="NEUTRAL"/></Filters><Column name="user_id" pattern="%X{user_id}"/><Column name="user_name" pattern="%X{user_name}"/><Column name="request_mode" pattern="%X{request_mode}"/><Column name="absolute_uri" pattern="%X{absolute_uri}"/><Column name="form_data" pattern="%X{form_data}"/><Column name="source" pattern="%X{source}"/><Column name="message" pattern="%X{message}"/><!-- <Column name="stack_trace" pattern="%X{stack_trace}"/> --><Column name="exception_time" pattern="%d{yyyy-MM-dd HH:mm:ss}"/></JDBC></Appenders><Loggers><Root level="DEBUG"><!-- 控制台 --><AppenderRef ref="Console" level="INFO"/><!-- 文件 --><AppenderRef ref="ErrorAppender" level="ERROR"/><AppenderRef ref="WarnAppender" level="WARN"/><AppenderRef ref="InfoAppender" level="INFO"/><AppenderRef ref="DebugAppender" level="DEBUG"/><!-- 数据库源 --><AppenderRef ref="LoginDatabase" level="LOGIN_LOG"/><AppenderRef ref="OperationDatabase" level="OPERATION_LOG"/><AppenderRef ref="ExceptionDatabase" level="EXCEPTION_LOG"/></Root><!-- <Logger name="com.cj.config.logs.ConnectionFactory" level="INFO"> --><!-- <AppenderRef ref="DatabaseAppender" level="INFO"/> --><!-- </Logger> --><!-- <Root level="TRACE"> --><!-- <!– 引用控制台输出和文件输出的 appender –> --><!-- <AppenderRef ref="Console"/> --><!-- <AppenderRef ref="DebugAppender"/> --><!-- <AppenderRef ref="InfoAppender"/> --><!-- <AppenderRef ref="WarnAppender"/> --><!-- <AppenderRef ref="ErrorAppender"/> --><!-- <AppenderRef ref="LoginDatabase"/> --><!-- <AppenderRef ref="OperationDatabase" /> --><!-- <!– <AppenderRef ref="ExceptionDatabase" level="Exception"/> –> --><!-- </Root> --><Logger name="org.apache.logging.log4j" level="DEBUG" additivity="false"><AppenderRef ref="Console"/></Logger><!--额外配置的logger--><!--log4j2 自带过滤日志--><!-- Apache Tomcat Web服务器配置加载的日志 --><Logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"><!-- 仅记录错误级别的日志,通常用于记录严重的配置错误 --></Logger><!-- 与 Apache Tomcat 相关的组件生命周期信息 --><Logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"><!-- 仅记录错误级别的信息 --></Logger><!-- Apache Tomcat HTTP 协议处理器的日志记录 --><Logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"><!-- 记录警告级别及更高级别的信息,通常用于记录一般警告信息 --></Logger><!-- Apache SSHD(SSH Server)库的日志记录 --><Logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"><!-- 记录警告级别及更高级别的信息,通常用于记录一般警告信息 --></Logger><!-- 与 Apache Tomcat 相关的 NIO 选择器池的日志记录 --><Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><!-- 与 CRaSH(Common Reusable SHell)相关的库和 SSH 支持的日志记录 --><Logger name="org.crsh.plugin" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><Logger name="org.crsh.ssh" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><!-- Eclipse Jetty Web服务器的组件生命周期的日志记录 --><Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"><!-- 仅记录错误级别的信息 --></Logger><!-- Hibernate Validator相关日志 --><Logger name="org.hibernate.validator.internal.util.Version" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><!-- 与 Spring Boot Actuator 相关的日志记录 --><Logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><Logger name="org.springframework.boot.actuate.endpoint.jmx" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger><!-- Thymeleaf模板引擎的日志记录 --><Logger name="org.thymeleaf" level="WARN"><!-- 记录警告级别及更高级别的信息 --></Logger></Loggers></Configuration>
3、CustomDataSourceProperties
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
@Scope("singleton")
public class CustomDataSourceProperties {public String url;public String username;public String password;public String driverClassName;
}
4、ConfigReader
import com.cj.blog.model.base.CustomDataSourceProperties;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;import java.io.InputStream;
import java.util.Map;/*** 用于从YAML文件中读取配置属性的实用工具类。*/
public class ConfigReader {private static final Logger logger = LogManager.getLogger(ConfigReader.class);/*** 从YAML配置文件中获取数据源属性。** @return CustomDataSourceProperties对象,包含数据源属性,如果未找到属性则返回null。*/public static CustomDataSourceProperties getDataSourceProperties() {String defaultActive = "dev"; // 默认活动配置String defaultConfigFile = "/application.yml"; // 默认配置文件名// 从默认配置文件中获取Spring属性Map<String, Object> properties = getSpringProperties(defaultActive, defaultConfigFile);if (properties != null) {Map<String, Object> springProperties = (Map<String, Object>) properties.get("spring");if (springProperties != null) {Map<String, Object> datasourceProperties = (Map<String, Object>) springProperties.get("datasource");if (datasourceProperties != null) {CustomDataSourceProperties dataSourceProperties = new CustomDataSourceProperties();dataSourceProperties.setUrl((String) datasourceProperties.get("url"));dataSourceProperties.setUsername((String) datasourceProperties.get("username"));dataSourceProperties.setPassword((String) datasourceProperties.get("password"));dataSourceProperties.setDriverClassName((String) datasourceProperties.get("driver-class-name"));logger.info("获取数据源属性成功!");logger.info("数据源属性:" + dataSourceProperties);return dataSourceProperties;}}}return null;}/*** 从指定的配置文件中获取Spring属性。** @param active 要使用的活动配置。* @param configFile 配置文件的路径。* @return 包含Spring属性的Map,如果未找到属性则返回null。*/private static Map<String, Object> getSpringProperties(String active, String configFile) {// 读取配置文件Map<String, Object> data = readConfigFile(configFile);if (data != null) {Map<String, Object> springProperties = (Map<String, Object>) data.get("spring");if (springProperties != null) {Map<String, Object> applicationProperties = (Map<String, Object>) springProperties.get("profiles");if (applicationProperties != null) {active = (String) applicationProperties.get("active");System.out.println("spring.application.active: " + active);}}}logger.info("spring.application.active: " + active);// 读取活动配置的配置文件return readConfigFile("/application-" + active + ".yml");}/*** 读取指定的YAML配置文件并将其解析为Map。** @param fileName YAML文件的路径。* @return 包含解析后的YAML数据的Map,如果未找到文件或无法解析则返回null。*/private static Map<String, Object> readConfigFile(String fileName) {try {// 创建用于解析YAML文件的ObjectMapper对象ObjectMapper mapper = new ObjectMapper(new YAMLFactory());// 使用类加载器加载YAML文件InputStream inputStream = ConfigReader.class.getResourceAsStream(fileName);// 读取YAML文件并解析为Map对象return mapper.readValue(inputStream, new TypeReference<Map<String, Object>>() {});} catch (Exception e) {e.printStackTrace();return null;}}}
5、ConnectionFactory 连接工厂类,用于管理数据库连接
import com.cj.blog.common.utils.ConfigReader;
import com.cj.blog.model.base.CustomDataSourceProperties;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;/*** 连接工厂类,用于管理数据库连接。通过Apache Commons DBCP提供数据库连接池功能。*/
@Component
public class ConnectionFactory {// 数据源,用于获取数据库连接private final DataSource dataSource;/*** 构造函数,初始化数据库连接池配置,并创建数据源。** @param dataSourceProperties 数据源属性对象,包含数据库连接信息*/public ConnectionFactory(CustomDataSourceProperties dataSourceProperties) {// 初始化数据库连接池配置Properties properties = new Properties();properties.setProperty("user", dataSourceProperties.getUsername());properties.setProperty("password", dataSourceProperties.getPassword());// 创建基于DriverManager的连接工厂和连接池GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<>();pool.setMaxActive(10); // 设置最大连接数pool.setMinIdle(2); // 设置最小空闲连接数DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(dataSourceProperties.getUrl(), properties);// 创建可池化的数据库连接工厂new PoolableConnectionFactory(connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED);// 创建数据源this.dataSource = new PoolingDataSource(pool);}/*** 获取数据库连接。** @return 数据库连接* @throws SQLException 如果获取连接时发生错误*/public static Connection getDatabaseConnection() throws SQLException {return Singleton.INSTANCE.dataSource.getConnection();}/*** 单例类,确保只有一个连接工厂实例。*/private static class Singleton {static ConnectionFactory INSTANCE;static {// TODO 这里需要修改,在创建数据库连接工厂时,需要从配置文件中读取数据源属性// 从配置文件中读取数据源属性,并创建连接工厂实例CustomDataSourceProperties dataSourceProperties = ConfigReader.getDataSourceProperties();dataSourceProperties.setDriverClassName(dataSourceProperties.getDriverClassName());dataSourceProperties.setUrl(dataSourceProperties.getUrl());dataSourceProperties.setUsername(dataSourceProperties.getUsername());dataSourceProperties.setPassword(dataSourceProperties.getPassword());INSTANCE = new ConnectionFactory(dataSourceProperties);}}}
三、进行简单测试配置
1、LogUtils
import org.apache.logging.log4j.ThreadContext;/*** 日志工具类*/
public class LogUtils {/*** 清除登录日志上下文信息,以便在日志记录操作结束后不保留上下文。*/public static void clearLogContext() {ThreadContext.clearMap();}
}
2、LoginUserInfoHelper
/*** 用于存储和获取用户信息的线程局部存储辅助类。*/
public class LoginUserInfoHelper {// 使用 ThreadLocal 来存储用户ID和用户名private static final ThreadLocal<Long> userId = new ThreadLocal<>();private static final ThreadLocal<String> userName = new ThreadLocal<>();/*** 获取当前线程的用户ID。** @return 用户ID*/public static Long getUserId() {return userId.get();}/*** 设置当前线程的用户ID。** @param _userId 用户ID*/public static void setUserId(Long _userId) {userId.set(_userId);}/*** 从当前线程中移除用户ID。*/public static void removeUserId() {userId.remove();}/*** 获取当前线程的用户名。** @return 用户名*/public static String getUsername() {return userName.get();}/*** 设置当前线程的用户名。** @param _username 用户名*/public static void setUsername(String _username) {userName.set(_username);}/*** 从当前线程中移除用户名。*/public static void removeUsername() {userName.remove();}
}
3、LoginLogUtils
import com.cj.blog.common.utils.RequestUtils;
import com.cj.blog.model.auth.LoginUserInfoHelper;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.Optional;/*** 登录日志工具类。*/
public class LoginLogUtils {private static final Logger logger = LogManager.getLogger(LoginLogUtils.class);/*** 设置用户登录日志,根据HttpServletRequest设置日志上下文信息。** @param request 请求对象,用于获取远程地址和用户代理信息。* @param remarks 登录日志的备注信息。*/public static void setLogLogin(HttpServletRequest request, String remarks) {// 使用 LoginUserInfoHelper 中的用户信息设置登录日志setLogLogin(LoginUserInfoHelper.getUserId().toString(),LoginUserInfoHelper.getUsername(),request.getRemoteAddr(),RequestUtils.getUserAgent(request),remarks);}/*** 设置用户登录日志,根据指定的客户端IP、设备信息和备注信息。** @param clientIp 客户端IP地址。* @param deviceInfo 设备信息。* @param remarks 登录日志的备注信息。*/public static void setLogLogin(String clientIp, String deviceInfo, String remarks) {// 使用 LoginUserInfoHelper 中的用户信息设置登录日志setLogLogin(LoginUserInfoHelper.getUserId().toString(),LoginUserInfoHelper.getUsername(),clientIp,deviceInfo,remarks);}/*** 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。** @param userId 用户ID。* @param userName 用户名。* @param clientIp 客户端IP地址。* @param deviceInfo 设备信息。* @param remarks 登录日志的备注信息。*/public static void setLogLogin(String userId, String userName, String clientIp, String deviceInfo, String remarks) {logLogin(userId, userName, clientIp, deviceInfo, remarks);}/*** 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。** @param userId 用户ID。* @param userName 用户名。* @param clientIp 客户端IP地址。* @param deviceInfo 设备信息。* @param remarks 登录日志的备注信息。*/public static void logLogin(String userId, String userName, String clientIp, String deviceInfo, String remarks) {try {// 设置上下文信息,这些信息将与日志消息关联ThreadContext.put("user_id", Objects.toString(userId, "0"));ThreadContext.put("user_name", Objects.toString(userName, "0"));ThreadContext.put("client_ip", clientIp);ThreadContext.put("device_info", deviceInfo);ThreadContext.put("remarks", remarks);// 使用自定义的 Login 级别记录消息logger.log(Level.getLevel("LOGIN_LOG"), remarks);} finally {LogUtils.clearLogContext(); // 在try块之后清除上下文,确保上下文信息不泄漏}}/*** 设置用户登录日志,包含用户ID、用户名、客户端IP、设备信息和备注信息。** @param clientIp 客户端IP地址。* @param deviceInfo 设备信息。* @param remarks 登录日志的备注信息。* @param userParams 用户ID、用户名。*/public static void setLogLogin(String clientIp, String deviceInfo, String remarks, String... userParams) {String userId = Optional.ofNullable(userParams.length > 0 ? userParams[0] : null).orElse("0");String userName = Optional.ofNullable(userParams.length > 1 ? userParams[1] : null).orElse("0");try {// 设置上下文信息,这些信息将与日志消息关联ThreadContext.put("user_id", userId);ThreadContext.put("user_name", userName);ThreadContext.put("client_ip", clientIp);ThreadContext.put("device_info", deviceInfo);ThreadContext.put("remarks", remarks);// 使用自定义的 Login 级别记录消息logger.log(Level.getLevel("LOGIN_LOG"), remarks);} finally {LogUtils.clearLogContext(); // 在try块之后清除上下文,确保上下文信息不泄漏}}}
4、写日志
LoginLogUtils.setLogLogin(customUser.getSysUser().getUserId().toString(),customUser.getUsername(),request.getRemoteAddr(),RequestUtils.getUserAgent(request),"登录成功");
相关文章:
SpringBoot使用log4j2将日志记录到文件及自定义数据库
目录 一、环境说明 二、进行配置 1、pom.xml 2、log4j2.xml 3、CustomDataSourceProperties 4、ConfigReader 5、ConnectionFactory 连接工厂类,用于管理数据库连接 三、进行简单测试配置 1、LogUtils 2、LoginUserInfoHelper 3、LoginLogUtils 4、…...

vue+elementUI用户修改密码的前端验证
用户登录后修改密码,密码需要一定的验证规则。旧密码后端验证是否正确;前端验证新密码的规范性,新密码规范为:6-16位,至少含数字/字母/特殊字符中的两种;确认密码只需要验证与新密码是否一致; 弹…...

微信小程序问题定位——sourcemap文件
使用sourceMap在微信小程序中进行线上问题定位,主要可以通过以下步骤实现: 下载微信开发者工具首先,确保已经安装了微信开发者工具,这是进行小程序开发和调试的基础。登录微信公众平台并下载sourceMap文件:登录微信小…...
Photoshop_00000
简介 Adobe官网:https://www.adobe.com Adobe中文官网:https://www.adobe.com/cn Adobe中国服务商:http://adobe.sxbyu.cn/adobe/adobe_index?flag800&bd_vid5593893117402635109# Photoshop安装 基础操作 文件的打开和新建 打开文…...

rviz上不显示机器人模型(模型只有白色)
文档中的是base_footprint,需要根据自己所设的坐标系更改,我的改为base_link 如何查看自己设的坐标系: 这些parent父坐标系就是 同时打开rviz后需要更改成base_link...

Android 录屏操作
Android 录屏操作 本文主要介绍android中如何通过MediaRecorder实现录屏操作的. 1: 申请权限 <uses-permission android:name"android.permission.RECORD_AUDIO" /> <uses-permission android:name"android.permission.WRITE_EXTERNAL_STORAGE"…...

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的血细胞智能检测与计数(深度学习模型+UI界面代码+训练数据集)
摘要:开发血细胞智能检测与计数系统对于疾病的预防、诊断和治疗具有关键作用。本篇博客详细介绍了如何运用深度学习构建一个血细胞智能检测与计数系统,并提供了完整的实现代码。该系统基于强大的YOLOv8算法,并对比了YOLOv7、YOLOv6、YOLOv5&a…...

Selenium 学习(0.20)——软件测试之单元测试
我又(浪完)回来了…… 很久没有学习了,今天忙完终于想起来学习了。没有学习的这段时间,主要是请了两个事假(5工作日和10工作日)放了个年假(13天),然后就到现在了。 看了下…...

章六、集合(1)—— 概念、API、List 接口及实现类、集合迭代
零、 关闭IDEA调试时自动隐藏空元素 一、 集合的概念 存储一个班学员信息,假定一个班容纳20名学员 当我们需要保存一组一样(类型相同)的元素的时候,我们应该使用一个容器来存储,数组就是这样一个容器。 数组有什么缺…...
原生js html5 canvas制作flappy bird压扁小鸟游戏
可以以电脑浏览器的手机模式打开,也可以在手机浏览器中直接打开 游戏运用了Canvas的drawImage,translate,rotate,save,restore,fillRect等API。 采用中介者模式,Game类统领全局,负责…...

服务器集群 -- nginx配置tcp负载均衡
当面临高流量、高可用性、水平扩展、会话保持或跨地域流量分发等需求时,单台服务器受限于硬件资源、性能有限不能满足应用场景的并发需求量时,引入负载均衡器部署多个服务器共同处理客户端的并发请求,可以帮助优化系统架构,提高系…...

现代DevOps如何改变软件开发格局
在软件开发的早期,该过程通常是开发人员编写代码,再将其交给质量保证(QA)进行测试。这种瀑布开发方法可能会导致质量问题和延迟,因为问题是在周期后期发现的。 一、了解DevOps和测试左移 DevOps是Development和Opera…...

如何在WordPress网站上设置多语言展示
在今天的全球化世界中,拥有多语言网站对于吸引更广泛的受众至关重要。前不就我们遇到Hostease的客户咨询我们的在线客服,他想要对他的wordpress网站支持多语言。我们提供给客户可以尝试以下的插件来支持多语言。 在本教程中,我们将逐步介绍如…...
Pinia和Vuex有什么区别?
API设计: Pinia的API设计更加简洁和直观。它采用了类似于Vue Composition API的风格,使用了更加现代化的语法和概念。相比之下,Vuex的API设计较为传统,使用了基于对象和字符串的方式来定义和访问状态。 TypeScript支持ÿ…...

普林斯顿算法讲义(二)
原文:普林斯顿大学算法课程 译者:飞龙 协议:CC BY-NC-SA 4.0 2.2 归并排序 原文:algs4.cs.princeton.edu/22mergesort 译者:飞龙 协议:CC BY-NC-SA 4.0 我们在本节中考虑的算法基于一种简单的操作ÿ…...

JVM 面试——G1和ZGC的区别
ZGC是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器ZGC的目标主要有4个 支持TB量级的堆。我们生产环境的硬盘还没有上TB呢,这应该可以满足未来十年内,所有JAVA应用的需求了吧。最大GC停顿时间不超10ms。目前一般线上环境运行良好的JAVA应用Minor …...

当“新质生产力”遇上“CAE仿真”,将激起什么样的火花?
在刚刚闭幕的全国两会上,新质生产力无疑是最为“滚烫”的热词。发展新质生产力是推动高质量发展的内在要求和重要着力点,此次更被列为2024年政府重点任务的第一条。 新质生产力是创新起主导作用,摆脱传统经济增长方式、生产力发展路径&#…...

使用 ChatGPT 写高考作文
写作文,很简单,但写一篇好的作文,是非常有难度的。 想要写一篇高分作文,需要对作文题目有正确的理解,需要展现独到的观点和深入的思考,需要具备清晰的逻辑结构,需要准确而得体的语言表达。 正…...

mac输入su命令报错如何重置密码
diannao1xiejiandeMacBook-Air ~ % su Password: su: Sorry输入 sudo passwd 命令重置密码即可。...
KY211 特殊排序
描述: 输入一系列整数,将其中最大的数挑出(如果有多个,则挑出一个即可),并将剩下的数进行排序,如果无剩余的数,则输出-1。 输入描述: 输入第一行包括1个整数N,1<N<1000&#x…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...