Rest API 日志记录实践问题
RestAPI logging practice issue
我在记录应用程序事件时遇到了这个问题。
以下是要求。
- 记录正在发生的基本应用程序事件、方法调用、它们的参数、returns、进程计时等。
- 但只有日志文件必须包含每个请求的 3 行日志。
- 第一行:记录请求详细信息,当请求进来时。
- 第二行:记录所有应用程序事件(这是问题所在)
- 第三行:记录请求销毁事件,当请求完成它的生命周期时。
这三个请求由命中请求时创建的事务 ID 标识,此 ID 放入 slf4j
的 MDC
。
现在我被告知使用 StringBuilder
来附加日志记录详细信息,它应该在控制器方法中初始化,将它的引用传递给每个被调用的方法,记录 StringBuilder
的内容在 finally
控制器方法块中。
此实现使整个代码库变得丑陋。
因此,我尝试使用带有 StringBuilder
的 ThreadLocal
var 来存储日志记录详细信息,并在请求被销毁时清理它以避免内存泄漏。相反,我尝试使用如下所示的实现(伪)。
用初始值初始化 ThreadLocal
。
public static ThreadLocal<StringBuilder> log = new ThreadLocal<StringBuilder>() {
@Override protected StringBuilder initialValue() {
return new StringBuilder("|Internal");
}
};
将详细信息附加到此 ThreadLocal
ed StringBuilder
SomeClass.log.get().append("|").append(whatever);
所有处理完成后,在controller 方法的finally 块中将此内容写入日志文件作为日志的第二行,然后将其清理。
但是我的队友对此很怀疑
- 你能告诉我这种情况下的实现有什么问题吗(这是一个好方法)吗?
- 是否存在内存泄漏等问题?
非常感谢对此发表任何评论。
直接解决您的问题
从技术上讲,我发现这种方法有两个问题:
在 all 控制器方法中需要清理线程本地。这足以让某个地方的某个人忘记将这个 finally 块放在某个控制器中 - 事情将开始崩溃。
如果不清理 StringBuilder,也会发生内存泄漏。因此,对于通过 "leaking" 控制器的所有线程,数据将越来越多地累积。
如果出于某种原因 BL 在另一个线程中生成/执行,代码将中断。
现在谈谈功能本身。对于这样的要求,我看到了两个可能的理由:
- 审计
- 测光
如果我们谈论计量,鉴于您已经在使用 spring 启动,您可以使用 Dropwizard 指标(spring 启动 1.x) 或 Micrometer(spring 引导 2.x 具有到 1 的可用后向端口。5.x)
如果我们谈论的是审计,那么与支持清理所有控制器中的内容的日志记录的耦合可能会很脆弱,正如我上面所说的那样。
我想提醒您的最后一件事是“3 行”要求。一般来说,一起检查 3 条消息并不容易,通常人们只处理 1 行日志(搜索、计数、grep 等等),而不是同时处理 3 行。我提出这个问题,因为也许它可以指出它也可以将请求日志记录和审计应用程序业务事件的要求分开。在这种情况下,也许可以使用一种技术记录请求(过滤器、tomcat 阀门,还有很多东西),而审计本身可以使用其他技术甚至技术来完成。
如果您必须使用现有的解决方案,一种有趣的方法可能是将日志记录重构到某些 AOP 方面/过滤器或其他一些众所周知的点,这样所有控制器就不必处理代码你描述过
我在记录应用程序事件时遇到了这个问题。 以下是要求。
- 记录正在发生的基本应用程序事件、方法调用、它们的参数、returns、进程计时等。
- 但只有日志文件必须包含每个请求的 3 行日志。
- 第一行:记录请求详细信息,当请求进来时。
- 第二行:记录所有应用程序事件(这是问题所在)
- 第三行:记录请求销毁事件,当请求完成它的生命周期时。
这三个请求由命中请求时创建的事务 ID 标识,此 ID 放入 slf4j
的 MDC
。
现在我被告知使用 StringBuilder
来附加日志记录详细信息,它应该在控制器方法中初始化,将它的引用传递给每个被调用的方法,记录 StringBuilder
的内容在 finally
控制器方法块中。
此实现使整个代码库变得丑陋。
因此,我尝试使用带有 StringBuilder
的 ThreadLocal
var 来存储日志记录详细信息,并在请求被销毁时清理它以避免内存泄漏。相反,我尝试使用如下所示的实现(伪)。
用初始值初始化 ThreadLocal
。
public static ThreadLocal<StringBuilder> log = new ThreadLocal<StringBuilder>() {
@Override protected StringBuilder initialValue() {
return new StringBuilder("|Internal");
}
};
将详细信息附加到此 ThreadLocal
ed StringBuilder
SomeClass.log.get().append("|").append(whatever);
所有处理完成后,在controller 方法的finally 块中将此内容写入日志文件作为日志的第二行,然后将其清理。 但是我的队友对此很怀疑
- 你能告诉我这种情况下的实现有什么问题吗(这是一个好方法)吗?
- 是否存在内存泄漏等问题?
非常感谢对此发表任何评论。
直接解决您的问题
从技术上讲,我发现这种方法有两个问题:
在 all 控制器方法中需要清理线程本地。这足以让某个地方的某个人忘记将这个 finally 块放在某个控制器中 - 事情将开始崩溃。 如果不清理 StringBuilder,也会发生内存泄漏。因此,对于通过 "leaking" 控制器的所有线程,数据将越来越多地累积。
如果出于某种原因 BL 在另一个线程中生成/执行,代码将中断。
现在谈谈功能本身。对于这样的要求,我看到了两个可能的理由:
- 审计
- 测光
如果我们谈论计量,鉴于您已经在使用 spring 启动,您可以使用 Dropwizard 指标(spring 启动 1.x) 或 Micrometer(spring 引导 2.x 具有到 1 的可用后向端口。5.x)
如果我们谈论的是审计,那么与支持清理所有控制器中的内容的日志记录的耦合可能会很脆弱,正如我上面所说的那样。
我想提醒您的最后一件事是“3 行”要求。一般来说,一起检查 3 条消息并不容易,通常人们只处理 1 行日志(搜索、计数、grep 等等),而不是同时处理 3 行。我提出这个问题,因为也许它可以指出它也可以将请求日志记录和审计应用程序业务事件的要求分开。在这种情况下,也许可以使用一种技术记录请求(过滤器、tomcat 阀门,还有很多东西),而审计本身可以使用其他技术甚至技术来完成。
如果您必须使用现有的解决方案,一种有趣的方法可能是将日志记录重构到某些 AOP 方面/过滤器或其他一些众所周知的点,这样所有控制器就不必处理代码你描述过