当前位置: 首页 > news >正文

【log4j2】log4j2插件挂载变更msg格式(工作实战,原理详解)

在这里插入图片描述

文章目录

  • 日志改造(某银行1)
  • 日志改造(某银行2)
  • 日志改造(某银行3)
  • 日志改造(某银行4)
  • 日志改造(某银行5)

更多相关内容可查看

业务背景:在已成型很多年的项目的基础上,进行日志改造,例如在程序中已经通过lg4j2打印了很多日志了,通过改每一条输出日志不显示,可以通过插件挂载,动态读取的方式来改造日志

对log4j2日志不了解的可查看主页log4j2相关文章,此篇不做详细log4j2框架讲解

日志改造(某银行1)

插件类 这个类是比如一个日志改造的要求有很多字段,但你的系统未必有这些字段,在做的时候,在请求或者交易进来的时候通过MDC或者ThreadContext放入线程中覆盖map的value值,log4j会自动读取到,插件的作用就是将原来的日志格式重新组装

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.util.ReadOnlyStringMap;import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedHashMap;
import java.util.Map;@Plugin(name = "ServicePattern", category = "Converter")
@ConverterKeys({"service"})
public class ServicePatternConverter extends LogEventPatternConverter {private static final Map<String, String> FIELD_DEFAULTS = new LinkedHashMap<>();static {FIELD_DEFAULTS.put("srcSysId", "");FIELD_DEFAULTS.put("srcHost", "");FIELD_DEFAULTS.put("srcApi", "");FIELD_DEFAULTS.put("globalSeqNo", "");FIELD_DEFAULTS.put("innerSeqNo", "");FIELD_DEFAULTS.put("sysId", "05620001");FIELD_DEFAULTS.put("esfServiceID", "");FIELD_DEFAULTS.put("transCode", "");FIELD_DEFAULTS.put("host", "");FIELD_DEFAULTS.put("api", "");FIELD_DEFAULTS.put("fileName", "");FIELD_DEFAULTS.put("fileLine", "");FIELD_DEFAULTS.put("startTime", "");FIELD_DEFAULTS.put("endTime", "");FIELD_DEFAULTS.put("duration", "");FIELD_DEFAULTS.put("time2", "");FIELD_DEFAULTS.put("time3", "");FIELD_DEFAULTS.put("returnCode", "");FIELD_DEFAULTS.put("returnMessage", "");FIELD_DEFAULTS.put("returnStatus", "0");FIELD_DEFAULTS.put("callStack", "[]");FIELD_DEFAULTS.put("traceId", "");FIELD_DEFAULTS.put("spanId", "");FIELD_DEFAULTS.put("parentSpanId", "");FIELD_DEFAULTS.put("businessId", "");FIELD_DEFAULTS.put("userId", "");FIELD_DEFAULTS.put("amount", "");FIELD_DEFAULTS.put("currency", "");FIELD_DEFAULTS.put("extendTag", "");}public ServicePatternConverter() {super("service", "SERVICE");}public static ServicePatternConverter newInstance(final String[] options) {return new ServicePatternConverter();}@Overridepublic void format(LogEvent event, StringBuilder toAppendTo) {try {//数据准备ReadOnlyStringMap contextData = event.getContextData();FIELD_DEFAULTS.put("host", getCurrentIP());String methodName = event.getSource().getMethodName();if (StringUtil.isNotEmpty(methodName)) FIELD_DEFAULTS.put("api", methodName);String className = event.getSource().getClassName();if (StringUtil.isNotEmpty(className)) FIELD_DEFAULTS.put("fileName", className);int lineNumber = event.getSource().getLineNumber();FIELD_DEFAULTS.put("fileLine", String.valueOf(lineNumber));toAppendTo.append("{");boolean firstField = true;for (Map.Entry<String, String> entry : FIELD_DEFAULTS.entrySet()) {String fieldName = entry.getKey();String defaultValue = entry.getValue();String value = contextData.getValue(fieldName);if (value == null) {value = defaultValue;}if (!firstField) {toAppendTo.append(",");}firstField = false;toAppendTo.append("\"").append(fieldName).append("\":");if (value.startsWith("{") || value.startsWith("[")) {// 已经是JSON格式的值toAppendTo.append(value);} else {toAppendTo.append("\"").append(escapeJson(value)).append("\"");}}toAppendTo.append("}");} catch (Exception e) {LoggerManager.exceptionLogger.error("SUDB Error Monitor log format error, detail: {}", ExceptionUtil.getStackTreeInfo(e));}}private static String getCurrentIP() {try {return InetAddress.getLocalHost().getHostAddress();} catch (UnknownHostException e) {throw new RuntimeException(e);}}private String escapeJson(String value) {return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");}
}

log4j调用这个插件的样例

 <PatternLayout><pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level]->%service%n</pattern></PatternLayout>

日志改造(某银行2)

这个插件可以作用于,比如你的原来的错误码表需要大批量改动,或者某些以原来格式输出的码表大批量改动,通过映射的方式来改造日志

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
import org.apache.logging.log4j.core.time.Instant;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@Plugin(name = "MonitorLogConverter", category = "Converter")
@ConverterKeys({"monitorLog"})
public class MonitorLogConverter extends LogEventPatternConverter {private static final Map<String, String> SERVICE_ID_MAP = new ConcurrentHashMap<>();private static final Map<String, String> ERROR_TYPE_MAP = new ConcurrentHashMap<>();private static final Pattern TX_PATTERN = Pattern.compile("tx id: (\\d+)");private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("errorCode: (\\d+)");private static final Pattern ERROR_DETAIL_PATTERN = Pattern.compile("errorDetail\\s*:\\s*([^,]+?)\\s*(?=,|$)");private static String cachedIp;static {try {//加载日志映射文件loadMappings();//获取当前服务器ip地址cachedIp = InetAddress.getLocalHost().getHostAddress();} catch (Exception e) {cachedIp = "";}}protected MonitorLogConverter(String name, String style) {super(name, style);}public static MonitorLogConverter newInstance(final String[] options) {return new MonitorLogConverter("monitorLog", "MonitorLog");}@Overridepublic void format(LogEvent event, StringBuilder toAppendTo) {String message = event.getMessage().getFormattedMessage();// 提取tx idString txNumber = extractPattern(message, TX_PATTERN);// 提取serviceIdString serviceId = SERVICE_ID_MAP.getOrDefault(txNumber, "");//格式化tx idif (StringUtil.isNotEmpty(txNumber)){txNumber = "TX" + txNumber;}// 提取error codeString errorCode = extractPattern(message, ERROR_CODE_PATTERN);errorCode = StringUtil.isEmpty(errorCode) ? "0" : errorCode;// 提取error typeString errorType = ERROR_TYPE_MAP.getOrDefault(errorCode, "NA");//成功了为0 失败则为错误信息message = "0".equals(errorCode) ? "success" : extractPattern(message, ERROR_DETAIL_PATTERN);// 构建完整日志格式toAppendTo.append(String.format("%s|%s|NA|individual|%s|%s|%s|%s|%s|%s|%s%n",serviceId,                     // SERVICE_IDtxNumber,                      // INTERFACE_MAPevent.getContextData().getValue("msgId"), // MSG_UIDcachedIp,                      // CLIENT_IPerrorCode,                     // STATUS_CODEerrorType,                     // ERROR_TYPEevent.getContextData().getValue("responseTime"), // RESPONSE_TIMEevent.getContextData().getValue("msgId"), // Txn_ReferenceNumbermessage                        // LOG_DETAIL));}private static synchronized void loadMappings() {Path configPath = Paths.get("logMappings.properties");try (InputStream is = Files.newInputStream(configPath)) {Properties props = new Properties();props.load(is);SERVICE_ID_MAP.clear();ERROR_TYPE_MAP.clear();props.stringPropertyNames().stream().filter(key -> key.matches("\\d{4}")).forEach(key -> SERVICE_ID_MAP.put(key, props.getProperty(key)));props.stringPropertyNames().stream().filter(key -> key.matches("\\d{6}")).forEach(key -> ERROR_TYPE_MAP.put(key, props.getProperty(key)));} catch (NoSuchFileException e) {LoggerManager.systemLogger.warn("Config file not found: {}", configPath);} catch (IOException e) {LoggerManager.systemLogger.error("Failed to load log mappings, using defaults. Detail: {}",ExceptionUtil.getStackTreeInfo(e));}}private String extractPattern(String message, Pattern pattern) {Matcher matcher = pattern.matcher(message);return matcher.find() ? matcher.group(1) : "";}}

log4j调用这个插件的样例

  <PatternLayout><pattern>[Performance]%d{yyyy-MM-dd HH:mm:ss.SSS}|${DBS_Name}|%monitorLog</pattern></PatternLayout>

日志改造(某银行3)

这个插件是一个相对简易版的日志格式的变更

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;/*** 插件*/
@Plugin(name = "CommPatternConverter", category = "Converter")
@ConverterKeys({"comm", "COMM"})
public class CommPatternConverter extends LogEventPatternConverter {protected CommPatternConverter() {super("COMM", "COMM");}@Overridepublic void format(LogEvent event, StringBuilder output) {try {String message = event.getMessage().getFormattedMessage();if (message == null) {message = "";}if (output == null) {output = new StringBuilder();}output.append("#COMM:").append("RS=R,PEER=null,LEN=").append(message.length()).append(",EMSG=null").append(",MSGDTA=").append(message);} catch (Exception e) {LoggerManager.exceptionLogger.error("#COMM:RS=E,PEER=null,LEN=0,EMSG=FORMAT_ERROR,MSGDTA=Citic Comm log format error, detail: {}", ExceptionUtil.getStackTreeInfo(e));if (output != null) {output.setLength(0);output.append("#COMM:RS=E,PEER=null,LEN=0,EMSG=FORMAT_ERROR,MSGDTA=null");}}}public static CommPatternConverter newInstance(final String[] options) {return new CommPatternConverter();}
}

log4j调用这个插件的样例

  <PatternLayout><Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]|%level|${APP_Name}|%X{UUID}|%X{GlobalSeqNo}|%pid|%thread|%class{1.}:%line|%X{NestedLevel}|%comm%n</Pattern></PatternLayout>

日志改造(某银行4)

这个插件是对错误日志的格式变更,通过正则来扫描对应的错误日志,改造成对应的日志格式

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 插件*/
@Plugin(name = "ErrorPatternConverter", category = "Converter")
@ConverterKeys({"errorfmt", "ERRORFMT"})
public class ErrorPatternConverter extends LogEventPatternConverter {//    // 匹配简体中文和繁体中文的错误格式
//    private static final Pattern ERROR_CODE_PATTERN = Pattern.compile(
//            "(?:错误码|錯誤碼)\\s*[,,]\\s*(\\d*)\\s*[,,]?\\s*(?:错误描述|錯誤描述)\\s*[::]\\s*(.*?)(?:\\s*\\n|$)"
//    );
//
//    // 匹配无错误码的纯错误描述(简繁体)
//    private static final Pattern PLAIN_ERROR_PATTERN = Pattern.compile(
//            "(?:错误描述|錯誤描述)\\s*[::]\\s*(.*)"
//    );// 匹配英文错误格式private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("(?:Error code)\\s*[,,]\\s*(\\d*)\\s*[,,]?\\s*(?:Error description)\\s*[::]\\s*(.*?)(?:\\s*\\n|$)",Pattern.CASE_INSENSITIVE);// 匹配无错误码的纯错误描述(英文)private static final Pattern PLAIN_ERROR_PATTERN = Pattern.compile("(?:Error description)\\s*[::]\\s*(.*)",Pattern.CASE_INSENSITIVE);protected ErrorPatternConverter() {super("ErrorFmt", "errorfmt");}@Overridepublic void format(LogEvent event, StringBuilder output) {try {String message = event.getMessage().getFormattedMessage();if (message == null) {message = "";}Matcher matcher = ERROR_CODE_PATTERN.matcher(message);if (output == null) {output = new StringBuilder();}output.append("#EX_ERR:");if (matcher.find()) {// 标准格式:错误码,错误描述String errorCode = matcher.group(1).trim();String errorDesc = matcher.group(2).trim();formatError(output, errorCode, errorDesc, event);} else {// 尝试匹配无错误码的情况Matcher plainMatcher = PLAIN_ERROR_PATTERN.matcher(message);if (plainMatcher.find()) {formatError(output, "", plainMatcher.group(1).trim(), event);} else {// 非标准格式的fallback处理formatError(output, "", message, event);}}} catch (Exception e) {LoggerManager.exceptionLogger.error("#EX_ERR:[POS= ErrorPatternConverter],ERR =null,EMSG =Citic Error log format error, detail: {}", ExceptionUtil.getStackTreeInfo(e));if (output != null) {output.setLength(0);output.append("#EX_ERR:[POS= ErrorPatternConverter],ERR =null,EMSG =Citic Error log format error!");}}}private void formatError(StringBuilder output, String errorCode,String errorDesc, LogEvent event) {// 添加位置信息(如果可用)String position = getErrorPosition(event);if (!position.isEmpty()) {output.append("[POS=").append(position).append("],");}// 处理错误码output.append("ERR=");if (!errorCode.isEmpty()) {output.append(errorCode);}// 处理错误描述output.append(",EMSG=").append(sanitizeMessage(errorDesc));}private String getErrorPosition(LogEvent event) {if (event.getSource() != null) {StackTraceElement source = event.getSource();return String.format("%s.%s(%s:%d)",shortenClassName(source.getClassName()),source.getMethodName(),source.getFileName(),source.getLineNumber());}return "";}private String shortenClassName(String fullClassName) {String[] parts = fullClassName.split("\\.");if (parts.length > 0) {return parts[parts.length - 1];}return fullClassName;}private String sanitizeMessage(String message) {// 替换换行和特殊字符return message.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t").replace(",", "\\,");}public static ErrorPatternConverter newInstance(final String[] options) {return new ErrorPatternConverter();}
}

log4j调用这个插件的样例

 <PatternLayout><Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]|%level|${APP_Name}|%X{UUID}|%X{GlobalSeqNo}|%pid|%thread|%class{1.}:%line|%X{NestedLevel}|%errorfmt%n</Pattern></PatternLayout>

日志改造(某银行5)

这个日志是一个对错误日志的格式改造

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 插件*/
@Plugin(name = "ProcPatternConverter", category = "Converter")
@ConverterKeys({"proc", "PROC"})
public class ProcPatternConverter extends LogEventPatternConverter {public ProcPatternConverter() {super("PROC", "PROC"); // name -> 在Pattern中使用的关键字}private static final Pattern pattern = Pattern.compile("Error code,(\\d+),Error description:(.*)");@Overridepublic void format(LogEvent event, StringBuilder output) {try {String message = event.getMessage().getFormattedMessage();if (message == null) {message = "";}if (output == null) {output = new StringBuilder();}if (message.contains("Error code") || message.contains("Error description")) {// 提取错误详情部分Matcher matcher = pattern.matcher(message);String errCode = "";String errMsg = "";if (matcher.find()) {errCode = matcher.group(1).trim();errMsg = matcher.group(2).trim();}output.append("#TRAN}:TRANID=null,ERR=").append(errCode).append(",EMSG=").append(errMsg);} else if (message.contains("done")) {// 成功交易格式:提取交易码(如 "6001 done, use 536 millisecond")String transId = extractNumberFromStart(message.split(" ")[0]);// 提取时间信息(use xxx millisecond)String timeInfo = extractTimeInfo(message);output.append("#TRAN}:TRANID=").append(transId).append(",ERR=0,EMSG= 交易结束").append(timeInfo != null ? " " + timeInfo : "");} else if (message.contains("request message")) {String transId = extractValue(message);output.append("#TRAN{:TCSQ=null").append(",TRANID=").append(transId);} else {// 默认格式output.append("#PROC{:@XXX,").append(event.getLevel()).append("=").append(message).append("}");}} catch (Exception e) {LoggerManager.exceptionLogger.error("#PROC{:@XXX error =Citic Proc log format error, detail: {}", ExceptionUtil.getStackTreeInfo(e));if (output != null) {output.setLength(0);output.append("#PROC{:@XXX error =null}");}}}// 辅助方法:从消息中提取时间信息(use xxx millisecond)private String extractTimeInfo(String message) {java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("use\\s+(\\d+)\\s+millisecond"  // 匹配 "use 数字 millisecond").matcher(message);return matcher.find() ? matcher.group(0) : message; // 返回完整匹配的字符串}private String extractValue(String message) {java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("the\\s+(\\d+)\\s+request message"  // 匹配 "the 数字 request message").matcher(message);return matcher.find() ? matcher.group(1) : "message";}private String extractNumberFromStart(String str) {java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?:.*?:)?(\\d+)").matcher(str);return matcher.find() ? matcher.group(1) : "0"; // 默认返回0或抛异常}public static ProcPatternConverter newInstance(final String[] options) {return new ProcPatternConverter();}
}

log4j调用这个插件的样例

    <PatternLayout><Pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]|%level|${APP_Name}|%X{UUID}|%X{GlobalSeqNo}|%pid|%thread|%class{1.}:%line|%X{NestedLevel}|%proc%n</Pattern></PatternLayout>

根据上述的几个案例,因为我做了日志改造的项目也不少,除了一些在程序中的改造外,基于插件的改造大概都是照猫画虎即可,重点在于log4j插件支持的这些api,以及配置文件能动态配置的一些写法,其余的赋值查询系统变量类似的代码,就是一些独立的方法了

http://www.dtcms.com/a/394710.html

相关文章:

  • MVCC(多版本并发控制):InnoDB 高并发的核心技术
  • 决策树习题
  • PHP-ThinkPhp漏洞学习-MVC模型路由访问模版渲染安全写法版本漏洞(2024小迪安全Day31)
  • [已修复] iTunes 无法识别您的 iPhone
  • EC2 实例的操作系统 (OS) 未能成功从 AWS 的网络服务 (DHCP) 中获取到分配给它的私有 IPv4 地址
  • Vercel、Netlify、AWS 与 Cloudflare:前端部署与边缘计算平台全景对比
  • 【Threejs】【工具类】Raycaster实现 3D 交互(如鼠标拾取、碰撞检测)的核心工具
  • Fay数字人QA功能详解
  • NETSTAT命令详解
  • FFmpeg 5.x 编译 so 文件的记录
  • 以开启https的nginx转发流量到minio
  • StarRocks 各类索引以及存储位置详解
  • PromptPilot 技术深解,工程化提示词开发如何让大模型准确率大大提高
  • RTX4090:AI与深度学习应用实践的革命性推动者
  • Cursor Multi-Root Workspace 新特性深度实战:一次打开 React 前端 + Go 后端,AI 自动跨项目跳转、联调、写代码!
  • 如何处理单位换算的问题
  • Docker部署 MySQL+Mycat
  • RTX4090:极致性能探索与硬核评测
  • DM物理存储结构及内存结构
  • ASP.NET Core MVC 路由逻辑初探
  • 【Python办公】文字转视频(可自定义颜色、字体大小)
  • 数据洪流时代,如何挑选一款面向未来的时序数据库?IoTDB 的答案
  • python+django/flask的吉安红色旅游平台 红色教育景区展示+图文资讯发布+评论互动功能系统java+nodejs
  • 设计模式(C++)详解——代理模式 (Proxy Pattern)(2)
  • 详解 Kubernetes 命令:kubectl exec -it nginx -- bash 及实战场景
  • Android挂机短信模板和多台手机同步短信模板
  • Solid Edge 转换为 IGS 全流程技术指南:含迪威模型网在线转换方案
  • 设计模式(C++)详解——代理模式 (Proxy Pattern)(1)
  • 聊聊AI agents MCP 开发
  • 【C++进阶】智能指针的使用及其原理