Logging: 将日志与正在创建的对象关联

huangapple go评论75阅读模式
英文:

Logging: Associate logs with object being created

问题

我有一个使用了多个辅助方法和其他“微服务”的MainService的Spring Boot项目,用于创建FinalObject,最终使用Hibernate/JPA进行持久化。这些方法和服务可能会记录多条消息,我希望将这些消息与在记录事件发生时正在创建的对象关联起来。

问题在于,辅助方法和微服务无法访问finalObject实例,因此尽管一切都被记录下来,但只有捕获的异常被保存为finalObject的属性 - 警告消息或其他日志不会被保存:

class FinalObject {
    private int value;
    private int price;
    private List<String> logs;
    ...
}
class MainService {
    @Autowired ValueService valueService;  // + 其他服务

    void createFinalObject() {  // 主要方法
        FinalObject o1 = new FinalObject();
        try {
            o1.setValue(valueService.getValue("some argument"));
        }
        catch (Exception e) {
            log.error(e.toString());  // 使用Log4j2进行日志记录
            o1.addLog(e.toString());  // 如果出现异常,我可以轻松地将其记录到o1属性中。
        }
        o1.setPrice(calculatePrice(o1.getValue()));
        ...
    }
    int calculatePrice(int value) {  // 辅助方法
        if (value > getMarketPrice())
            log.info("This is very valuable!");  // 我需要一种将此与o1关联的方法!
        ...
        return price;
    }
}
// ValueService.java
int getValue(String arg) {
    if (arg.matches("\\d$"))
        log.warn("arg ends with a number");  // 也必须保存到o1中!
    ...
    return value;
}

解决方案1:在各处传递o1

int calculatePrice(int value, FinalObject o1) {
    if (value > getMarketPrice()) {
        o1.addLog("This is very valuable!");  // 现在我在这里可以访问o1
        log.info("This is very valuable!");
    }
    ...

解决方案2:将o1logs属性作为可修改的列表传递:

o1.setPrice(calculatePrice(o1.getValue(), o1.getLogs()));
...
int calculatePrice(int value, List<String> finalObjectLogs) {
    if (value > getMarketPrice()) {
        finalObjectLogs.add("This is very valuable!");  // 直接修改o1的logs属性
        log.info("This is very valuable!");
    }
    ...

解决方案3:添加一个log4j2数据库appender

更优雅的解决方案可能是向log4j2添加一个数据库appender。这个方法的一个挑战是如何将这些日志与o1关联起来。FinalObject的id仅在createFinalObject()的最后阶段在保存到数据库时生成,因此在执行日志语句时我没有一个id。

问题:

有没有比我上面提到的方法更优雅的方式?

或者:

如果解决方案3听起来是一个好的方法,我如何实现它?

英文:

I have Spring Boot project with a MainService that uses several helper methods and other "microservices" to create a FinalObject, that's eventually persisted using Hibernate/JPA. The methods and services may log several messages, and I want these to be associated with the object that was being created when the logged event occurred.

The problem is that the helper methods and microservices don't have access to the finalObject instance, so even though everything is logged, only caught exceptions get saved as a finalObject attribute - not warning messages or other logs:

class FinalObject {
    private int value;
    private int price;
    private List&lt;String&gt; logs;
    ...
}
class MainService {
    @Autowired ValueService valueService;  // + other services

    void createFinalObject() {  // Main method
        FinalObject o1 = new FinalObject();
        try {
            o1.setValue(valueService.getValue(&quot;some argument&quot;));
        }
        catch (Exception e) {
            log.error(e.toString());  // Logging using Log4j2
            o1.addLog(e.toString());  // If there&#39;s an exception, I can easily log it to a o1 attribute.
        }
        o1.setPrice(calculatePrice(o1.getValue()));
        ...
    }
    int calculatePrice(int value) {  // Helper method
        if (value &gt; getMarketPrice())
            log.info(&quot;This is very valuable!&quot;);  // I need a way to associate this with o1!
        ...
        return price;
    }
}
// ValueService.java
int getValue(String arg) {
    if (arg.matches(&quot;\\d$&quot;))
        log.warn(&quot;arg ends with a number&quot;);  // Must also be saved to o1!
    ...
    return value;
}

Solution 1: Passing o1 around everywhere:

int calculatePrice(int value, FinalObject o1) {
    if (value &gt; getMarketPrice()) {
        o1.addLog(&quot;This is very valuable!&quot;);  // Now I have access to o1 here
        log.info(&quot;This is very valuable!&quot;);
    }
    ...

Solution 2: Pass o1's logs attribute around as a modifiable list:

o1.setPrice(calculatePrice(o1.getValue(), o1.getLogs()));
...
int calculatePrice(int value, List&lt;String&gt; finalObjectLogs) {
    if (value &gt; getMarketPrice()) {
        finalObjectLogs.add(&quot;This is very valuable!&quot;);  // Directly modify o1&#39;s logs attribute
        log.info(&quot;This is very valuable!&quot;);
    }
    ...

Solution 3: Add a log4j2 database appender

A more elegant solution may be do add a database appender to log4j2. A challenge with this is how I can relate these logs to o1. The FinalObject id only gets generated at the very end of createFinalObject(), when it is saved to the database, so I don't have an id when the log statements are executed.

Question:

How can I do this more elegantly than the ways I mentioned above?

Or:

If solution 3 seems like a good approach, how do I implement it?

答案1

得分: 1

你可以使用 Spring Sleuth 和日志过滤器来记录端点。查阅 Spring Sleuth 文档:

https://docs.spring.io/spring-cloud-sleuth/docs/2.2.4.RELEASE/reference/html/

你可以创建一个过滤器来记录每个端点,或者记录其中一个。

有了这些日志记录器,你可以记录你想要的类型。

英文:

You can use spring sleuth and logging filters to log the endpoints.
Check the Spring Sleuth Docs;

https://docs.spring.io/spring-cloud-sleuth/docs/2.2.4.RELEASE/reference/html/

You can create a filter do log every endpoint, or log one of them.

And with this loggers you can log the types that you want.

答案2

得分: 1

我很好奇。在你的示例中,计算价格的方法似乎与任何对象都没有关联,那么为什么你要在其中包含关于特定对象的信息呢?

另一方面,如果你想将其与单个请求中执行的其他操作相关联,我建议你查看Log4j-Audit中描述的RequestContext(请求上下文)。你不必使用Log4j Audit来实现这样的功能。它简单地利用了Log4j的ThreadContext,并定义了特定的键来使用。然后,在请求的开始时在ThreadContext中初始化这些值,并在请求结束时将其清除。你可以根据需要向ThreadContext中添加项目。然后,可以配置Log4j以在每个日志事件中包含特定的键。

我应该指出,Spring Cloud Sleuth正在做我多年来一直在做的事情。为了将请求信息从一个服务传播到下一个服务,只需在调用服务时将它们转换为HTTP头,服务启动时再进行相反的操作。这就是为什么Log4j-Audit示例中的RequestContext显示了将属性分类为ClientServer、Local或Chained的注释。然后它提供了实现这一目标所需的组件。

英文:

I am curious. In your example the method calculate price doesn't seem to relate to any object so why would you want to include information about a particular object in it?

On the other hand, if you want to correlate it with other operations being performed in a single Request I would suggest you look at the RequestContext that Log4j-Audit describes. You don't have to use Log4j Audit to implement something like this. It simply leverages the Log4j ThreadContext and defines specific keys that are used. You then initialized the values in the ThreadContext and the beginning of the request and clear it at the end of the Request. You can add items to the ThreadContext as needed. Log4j can then be configured to include specific keys in every log event.

I should note that Spring Cloud Sleuth is doing something that I have been doing for years. To propagate the Request information from one service to the next they simply need to be converted to and from HTTP headers when calling a service and when the service starts. That is why the Log4j-Audit example RequestContext shows annotations to classify the attributes as ClientServer, Local, or Chained. It then provides the components needed to accomplish this.

huangapple
  • 本文由 发表于 2020年8月15日 00:25:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/63416571.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定