非静态Slf4j MDC模式

时间:2016-02-16 09:31:51

标签: logging slf4j

在我的系统中,我有一系列能够管理自己状态的复杂角色。为了便于说明,我们假装它是一辆汽车:

class Car {
    private String registrationPlate;
    private int gear;

    public void gearUp() {
       int oldGear = gear++;
       log.info("Changed gear from {} to {} for vehicle {}", oldGear, gear, registrationPlate);
    }
}

每当我换档(或任何其他状态)时,我想在日志文件中添加一些注释。鉴于我的系统中有许多汽车,受影响的汽车的登记牌是用任何日志条目写出的,这一点至关重要。

上述代码假定开发人员始终记得在日志条目中包含注册牌。实际上,开发人员常常忘记这一点,我们最终省略了这个细节的日志条目,这使得在日志中跟踪事件变得不可能 - 因为你不知道哪个" car"采取了记录的行动。

映射诊断上下文的概念似乎完全符合我的要求;我想在写入日志的每一行附加一些额外的元数据,太棒了!

然而,实际上,实施对我来说并不是非常有用。假设MDC是一些静态线程特定的上下文(由ThreadLocal内部促进)。

鉴于我的系统是多线程的,并且每辆Car最终可能会对池中的任意数量的线程执行操作。因此,开发人员的负担刚刚增加:

    public void gearUp() {
       int oldGear = gear++;
       MDC.put("registrationPlate", registrationPlate);
       log.info("Changed gear from {} to {}", oldGear, gear);
       MDC.clear();
    }

天堂禁止他们忘记调用MDC.clear(),否则下一次从该线程调用记录器可能会附加到错误的汽车上(如果下一个日志记录语句也忘记了MDC.put("registrationPlate", ...)

我真正需要的是一个特定于MDC的记录器。这将是我对API的一种幻想,可以促进:

private final Logger logger;

public Car(String registrationPlate) {
    this.registrationPlate = registrationPlate;

    this.logger = LoggerFactory.buildLogger(this)
        .withContext("registrationPlate", registrationPlate)
        .build();
}

真的而不是自己构建它,它似乎已经是我应该可以使用的功能了。我忽略了哪些替代方案?

1 个答案:

答案 0 :(得分:1)

MDC实际上是为了给出基于线程的某些内容的上下文(就像网站服务的用户请求一样),所以尽管你可以让它做你想做的事情(而且我有时会扭曲它来处理那些并非真正设计的东西),我认为这不是你想要的。

看起来您实际上正在寻找让每个类的实例都拥有自己的记录器。好吧,一种方法实际上就是这样做,实际名称实际上用作记录器名称的一部分:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Car {
    private final Logger logger;
    private final String registrationNumber;

    public Car(final String registrationNumber) {
        this.registrationNumber = registrationNumber;
        logger = LoggerFactory.getLogger("example.car." + registrationNumber);
    }

    public void logSomething() {
        logger.info("Logging something here");
    }
}

这有点奇怪,因为我们习惯于记录器名称始终与正在记录的类相同,虽然这是一个有用的约定,但我认为如果您正在查找特定的内容,可以调整它记录不太相同的方式排队。如果您使用缩写的记录器名称进行日志记录,则可能无法正常工作(如果您为%c提供长度参数,则使用Logback,其中除最后一个之外的段可以缩写为单个字符),但如果您使用日志配置设置为使用完整的记录器名称,这应该为您提供我认为您正在寻找的内容。

我认为唯一可行的方法是使用Markers做一些事情,在那里为每个类实例创建一个新标记。这可能同样有效,并且Logback至少允许您将Marker作为模式的一部分包含在内,尽管它需要开发人员记住在每个日志条目上使用Marker。作为另一种选择可能值得探索。

(我在这个答案中的代码,例如,我特此致力于公共领域。)