审计日志通用格式


=Start=

缘由:

好久没有写文章了,也不是不想写,好像也不是太忙,可能就是变懒了吧。。。结合现状,简单记录一篇,希望自己还能不断坚持学习、记录。

正文:

整理一套通用的审计日志格式,用于指导/督促业务方在设计/实现时进行参考。

基本要求:

【什么人】在【什么时间】从【什么途径】通过【什么设备】使用【什么方式】访问了【什么内容】,【访问结果】如何。

什么人
在什么时间点
从什么地方来
进入什么系统
以什么身份
对谁
进行什么操作
操作结果
……
who when from where admin/operator/guest target what success/fail ……
可选增强内容:

什么人(who):它的账号类型是什么,属于哪个组,是否是超级管理员等等。

用什么设备(device):设备的类型、操作系统版本、设备ID、设备属主是什么。

从什么地方来(from):比如连接IP属于内网直连还是通过VPN接入;还有就是网络类型属于WiFi还是4G,网络信息是什么(SSID/BSSID)。

对谁(target):对哪个页面(它的页面title是什么,创建者owner是谁,内容密级security_level是多少)。

操作类型(op_type):增/删/改/查。

操作结果(op_return):是否成功,原因如何等等。

=END=


《“审计日志通用格式”》 有 2 条评论

  1. 日志格式规范
    https://blog.csdn.net/lk142500/article/details/80424945
    `
    1 简介
    1.1 日志的作用

    一般程序日志出自下面几个方面的需求:
    1、 记录用户操作的审计日志,甚至有的时候就是监管部门的要求;
    2、 快速定位问题的根源;
    3、 追踪程序执行的过程;
    4、 追踪数据的变化;
    5、 数据统计和性能分析;
    6、 采集运行环境数据;

    1.2 撰写日志的要求
    1.2.1 日志的可读性
    1.2.2 日志的性能
    1.2.3 占用磁盘空间
    1.2.4 日志的时效性
    1.2.5 日志级别(FATAL,ERROR,EARN,INFO,DEBUG,TRACE)
    1.2.6 日志内容
    1.2.7 日志格式(常见的日志格式中对于每一条日志应含有的信息包括日期、时间、日志级别、代码位置、日志内容、错误码等信息。)

    2 日志级别和含义

    2.1 Log4j的组成
    Log4j由三个重要的组成构成:日志记录器(Loggers),输出端(Appenders)和日志格式化器(Layout)。
    2.1.1 Logger
    控制要启用或禁用哪些日志记录语句,并对日志信息进行级别限制
    2.1.2 Appenders
    指定了日志将打印到控制台还是文件中.
    2.1.3 Layout
    控制日志信息的显示格式。

    2.2 日志级别
    2.2.1 TRACE
    2.2.2 DEBUG
    2.2.3 INFO
    2.2.4 WARN
    2.2.5 ERROR
    2.2.6 FATAL

    2.3 日志级别大小关系(DEBUG<INFO<WARN<ERROR)

    3 日志规范示例
    模仿,抄写是比较好的学习方式,借鉴前人撰写日志的良好风格以形成自己的风格是不错的方式。下面是一些不错的日志记录。

    3.1 TRACE日志记录示例
    3.2 INFO日志记录示例
    3.3 DEBUG日志记录示例
    3.4 WARN日志记录示例
    3.5 ERROR日志记录示例

    怎么样写出好的日志来?
    **其实写好日志并不难,只要我们能在写代码的时候能体会到后面的维护工作的压力和艰辛,多点关注和理解就一定能做好这件事。**
    `

  2. 日志导致线程Block的这些坑,你不得不防
    https://tech.meituan.com/2022/07/29/tips-for-avoiding-log-blocking-threads.html
    `
    1. 前言
    日志对程序的重要性不言而喻。它很“大”,我们在项目中经常通过日志来记录信息和排查问题,相关代码随处可见。它也很“小”,作为辅助工具,日志使用简单、上手快,我们通常不会花费过多精力耗在日志上。但看似不起眼的日志也隐藏着各种各样的“坑”,如果使用不当,它不仅不能帮助我们,反而还可能降低服务性能,甚至拖垮我们的服务。

    日志导致线程Block的问题,相信你或许已经遇到过,对此应该深有体会;或许你还没遇到过,但不代表没有问题,只是可能还没有触发而已。本文主要介绍美团统一API网关服务Shepherd(参见《百亿规模API网关服务Shepherd的设计与实现》一文)在实践中所踩过的关于日志导致线程Block的那些“坑”,然后再分享一些避“坑”经验。

    2. 背景
    API网关服务Shepherd基于Java语言开发,使用业界大名鼎鼎的Apache Log4j2作为主要日志框架,同时使用美团内部的XMD-Log SDK和Scribe-Log SDK对日志内容进行处理,日志处理整体流程如下图1所示。业务打印日志时,日志框架基于Logger配置来决定把日志交给XMDFile处理还是Scribe处理。其中,XMDFile是XMD-Log内部提供的日志Appender名称,负责输出日志到本地磁盘,Scribe是Scribe-Log内部提供的日志Appender名称,负责上报日志到远程日志中心。

    随着业务的快速增长,日志导致的线程Block问题愈发频繁。比如调用后端RPC服务超时,导致调用方大量线程Block;再比如,业务内部输出异常日志导致服务大量线程Block等,这些问题严重影响着服务的稳定性。因此,我们结合项目在过去一段时间暴露出来的各种由于日志导致的线程Block问题,对日志框架存在的稳定性风险因素进行了彻底的排查和修复,并在线下、线上环境进行全方位验证。在此过程中,我们总结了一些日志使用相关的实践经验,希望分享给大家。

    3. 踩过的坑
    本章节主要记录项目过去一段时间,我们所遇到的一系列日志导致的线程Block问题,并逐个深入分析问题根因。

    3.1 日志队列满导致线程Block
    日志量过大导致AsyncAppender日志队列被打满,新的日志事件无法入队,进而由ErrorHandler处理日志,同时由于ErrorHandler存在线程安全问题,导致大量日志输出到了Console,而Console在输出日志到PrintStream输出流时,存在synchronized同步代码块,所以在高并发场景下导致线程Block。

    3.2 AsyncAppender导致线程Block
    Log4j2打印异常日志时,AsyncAppender会先创建日志事件快照,并进一步触发解析、加载异常堆栈类。JVM通过生成字节码的方式优化反射调用性能,但该动态生成的类无法被WebAppClassLoader类加载器加载,因此当大量包含反射调用的异常堆栈被输出到日志时,会频繁地触发类加载,由于类加载过程是synchronized同步加锁的,且每次加载都需要读取文件,速度较慢,从而导致线程Block。

    3.3 Lambda表达式导致线程Block
    Log4j2打印异常日志时,AsyncAppender会先创建日志事件快照,并进一步触发解析、加载异常堆栈类。JDK 8低版本中使用Lambda表达式所生成的异常堆栈类无法被WebAppClassLoader类加载器加载,因此,当大量包含Lambda表达式调用的异常堆栈被输出到日志时,会频繁地触发类加载,由于类加载过程是synchronized同步加锁的,且每次加载都需要读取文件,速度较慢,从而导致了线程Block。

    3.4 AsyncLoggerConfig导致线程Block
    Log4j2打印异常日志时,AsyncLoggerConfig会初始化Disruptor RingBuffer日志元素字段,并进一步触发解析、加载异常堆栈类。JVM通过生成字节码的方式优化反射调用性能,但该动态生成的类无法被WebAppClassLoader类加载器加载,因此当大量包含反射调用的异常堆栈被输出到日志时,会频繁地触发类加载,由于类加载过程是synchronized同步加锁的,且每次加载都需要读取文件,速度较慢,从而导致线程Block。

    4. 避坑指南
    本章节主要对上述案例中导致线程Block的原因进行汇总分析,并给出相应的解决方案。

    4.1 问题总结
    日志异步处理流程整体步骤如下:

    业务线程组装日志事件对象,如创建日志快照或者初始化日志字段等。
    日志事件对象入队,如BlockingQueue队列或Disruptor RingBuffer队列等。
    日志异步线程从队列获取日志事件对象,并输出至目的地,如本地磁盘文件或远程日志中心等。
    对应地,Log4j2导致线程Block的主要潜在风险点如下:

    如上图标号①所示,日志事件对象在入队前,组装日志事件时触发了异常堆栈类解析、加载,从而引发线程Block。
    如上图标号②所示,日志事件对象在入队时,由于队列满,无法入队,从而引发线程Block。
    如上图标号③所示,日志事件对象在出队后,对日志内容进行格式化处理时触发了异常堆栈类解析、加载,从而引发线程 Block。
    从上述分析不难看出:

    标号①和②处如果发生线程Block,那么会直接影响业务线程池内的所有线程。
    标号③出如果发生线程Block,那么会影响日志异步线程,该线程通常为单线程。
    标号①和②处发生线程Block的影响范围远比标号③更大,因此核心是要避免日志事件在入队操作完成前触发线程Block。其实日志异步线程通常是单线程,因此对于单个Appender来说,不会出现Block现象,至多会导致异步线程处理速度变慢而已,但如果存在多个异步Appender,那么多个日志异步线程仍然会出现彼此Block的现象。

    4.2 对症下药
    搞清楚了日志导致线程Block的原因后,问题也就不难解决,解决方案主要从日志事件“入队前”、“入队时”和“出队后”三方面展开。

    4.2.1 入队前避免线程Block
    1. 日志事件入队前避免触发异常堆栈类解析、加载操作。
    2. 禁用JVM反射调用优化。
    3. 升级JDK版本修复Lambda类Bug。

    先说方案结论:
    1. 自定义Appender实现,创建日志事件快照时避免触发异常堆栈类解析、加载,美团内部Scribe-Log提供的AsyncScribeAppender即是如此。
    2. 日志配置文件中不使用标签,可以使用标签来代替。

    4.2.2 入队时避免线程Block
    结合上文分析的“日志队列满导致线程Block”案例,日志事件入队时避免线程Block的解决方案可从如下几方面考虑:

    1. 日志队列满时,Appender忽略该日志。
    2. Appender使用自定义的ErrorHandler实现处理日志。
    3. 关闭StatusConfigListener日志输出。

    先说方案结论:自定义Appender实现,日志事件入队失败时忽略错误日志,美团内部Scribe-Log提供的AsyncScribeAppender即是如此。

    4.2.3 出队后避免线程Block
    日志事件出队后会按照用户配置的输出样式,对日志内容进行格式化转换,此时仍然可能触发解析、加载异常堆栈类。因此,日志出队后避免线程Block的根本解决方法是在异常格式化转换时避免解析、加载异常堆栈类。

    先说方案结论:显式配置日志输出样式%ex来代替默认的%xEx,避免对日志内容格式化时解析、加载异常堆栈类。

    5. 最佳实践
    本章节主要结合项目在日志使用方面的一系列踩坑经历和实践经验,总结了一份关于日志配置的最佳实践,供大家参考。

    建议日志配置文件中对所有Appender的PatternLayout都增加%ex配置,因为如果没有显式配置%ex,则异常格式化输出的默认配置是%xEx,此时会打印异常的扩展信息(JAR名称和版本),可能导致业务线程Block。
    不建议日志配置文件中使用AsyncAppender,建议自定义Appender实现,因为AsyncAppender是日志框架默认提供的,目前最新版本中仍然存在日志事件入队前就触发加载异常堆栈类的问题,可能导致业务线程Block。
    不建议生产环境使用ConsoleAppender,因为输出日志到Console时有synchronized同步操作,高并发场景下非常容易导致业务线程Block。
    不建议在配置文件中使用标签,因为日志事件元素在入队前就会触发加载异常堆栈类,可能导致业务线程Block。如果希望使用Log4j2提供的异步日志AsyncLogger,建议配置Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector参数,开启异步日志。
    `

发表评论

您的电子邮箱地址不会被公开。