如何使用 SLF4J 和 Log4j2 记录 FATAL(或任何自定义日志级别)
How to log FATAL (or any custom log level) with SLF4J and Log4j2
我有那些具体的要求:
- 需要能够登录FATAL级别
- 需要使用SLF4J
- 需要使用Log4j2
现在,这是我的实现:
final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
final Marker marker = MarkerFactory.getMarker("FATAL");
logger.error(marker, "!!! Fatal World !!!");
这是我的 PatternLayout (在 yaml 中):
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %-5level %marker [%t] %logger{3.} - %msg%n"
这是我的日志输出:
20150506T155705,158 ERROR FATAL [main] - !!! Fatal World !!!
你知道如何有效地从日志输出中删除 "ERROR" 吗?
非常感谢
Marker
并不是您真正想要的。 Marker
用于 "enriching" 日志消息,使它们更易于搜索。您正在尝试更改日志 level/priority,这有点不同。
您正在使用 logger.error()
,它将消息记录为 ERROR
级别。
如果没有预定义的FATAL
级别(通常有,例如logger.fatal()
),则使用允许您指定日志级别的通用logger.log()
。
logger.fatal(yourMessage);
或
logger.log(priorityLevel, yourMessage);
更新:
来自 SLF4J 网站:
The Marker interface, part of the org.slf4j package, renders the FATAL level largely redundant. If a given error requires attention beyond that allocated for ordinary errors, simply mark the logging statement with a specially designated marker which can be named "FATAL" or any other name to your liking.
http://www.slf4j.org/faq.html#fatal
因此,使用 SLF4J,不可能有 FATAL
日志级别。我非常不同意这个决定背后的理由,但事实就是如此。
到目前为止我找到的唯一解决方案是使用 5 个标记:
final Marker traceMarker = MarkerFactory.getMarker("TRACE");
final Marker debugMarker = MarkerFactory.getMarker("DEBUG");
final Marker infoMarker = MarkerFactory.getMarker("INFO");
final Marker warnMarker = MarkerFactory.getMarker("WARN");
final Marker errorMarker = MarkerFactory.getMarker("ERROR");
final Marker fatalMarker = MarkerFactory.getMarker("FATAL");
并记录每次都通过标记:
logger.info(infoMarker, "!!! INFO World !!!");
logger.error(errorMarker, "!!! ERROR World !!!");
logger.error(fatalMarker, "!!! FATAL World !!!");
并修改 PatternLayout 以完全删除 LogLevel 并始终记录标记,例如:
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %marker [%t] %logger{3.} - %msg%n"
我有点认为这个解决方案是 hack...它还会以正确的方式使用 LogLevel 删除任何外部库的日志级别。
总结:这个解决方案不是一个好的解决方案。
UPDATE:我尝试了另一种解决方案,编写了一个 RewritePolicy:
public class FatalRewritePolicy implements RewritePolicy {
public static final String FATAL = "FATAL";
@Override
public LogEvent rewrite(final LogEvent logEvent) {
final Marker marker = logEvent.getMarker();
if (marker == null)
return logEvent;
// Log Level is final in the LogEvent, there's no way we can modify it.
Level level = logEvent.getLevel();
return null;
}
}
似乎无法更改 Log4j2 中 LogEvent 的 LogLevel(这很有意义)。
总结:仍然没有解决方案。
这是我和一些同事一起得出的最接近工作的解决方案:
- 使用SLF4J标记创建致命标记class。
- 使用 RoutingAppender,使用 Marker 作为路由模式:"$${marker:}"
- 配置一个 Fatal-specific appender,它有自己的 PatternLayout,它不包含 LogLevel 但包含硬编码的 FATAL 级别。
这是 Java 样本:
Marker fatal = MarkerFactory.getMarker("FATAL");
// Usage example
final Logger logger = LoggerFactory.getLogger(FatalLogger.class);
logger.log(fatal, "this is a fatal message");
// Log sample :
20150514T115144,279 FATAL [main] FatalLogger - this is a fatal message
这是 YAML 示例:
Configuration:
status: debug
Appenders:
RandomAccessFile:
- name: APPLICATION_APPENDER
fileName: logs/application.log
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %-5level %msg%n"
- name: FATAL_APPENDER
fileName: logs/application.log
PatternLayout:
Pattern: "%d{ISO8601_BASIC} FATAL %msg%n"
Routing:
name: ROUTING_APPENDER
Routes:
pattern: "$${marker:}"
Route:
- key: FATAL
ref: FATAL_APPENDER
- ref: APPLICATION_APPENDER #DefaultRoute
Loggers:
Root:
level: trace
AppenderRef:
- ref: ROUTING_APPENDER
这是我为 log4j 做的,我也推荐给 log4j2...
编写您自己的自定义 slf4j 静态绑定程序又名桥。这需要一些工作,但由于各种复杂的原因,这是非常值得的 1 我大概有一天会写博客。
这是您的工作。
- 您在此处复制此代码:https://github.com/apache/logging-log4j2/tree/master/log4j-slf4j-impl
- 然后您想要编辑
Log4jLogger
class 并更改标记方法(跟踪、错误、警告、信息等)以适当地分派。即if (marker.contains("FATAL")) fatal(....);
- 从您的项目中排除原始
log4j-slf4j-impl
并使用您的新代码。
老实说,我认为 slf4j 存在严重缺陷,因为
- 它非常 difficult/impossible 覆盖硬核静态初始化以及不提供
logger.fatal(...)
。
- 不需要标记,标记本身就很复杂:
- 非常非常非常少的项目使用标记。我实际上 looked/grepped 开源项目和标记使用率接近于零低。
- 使用标记的是因为缺少
fatal
。这不是 80/20。标记用于 1%,fatal
用于 99%。
- 许多开发人员认为使用标记 "fatal" 会以某种方式将其映射到致命的。
- Few know what a detached marker is including myself.
- 与 MDC 上下文提供的内容重叠。给定面向事件维度的数据库(elasticsearch、druid 等),MDC 上下文更优越(name/value 对)。
1 其中之一能够真正成为日志框架启动过程的一部分,而不是难以确定几乎任意的静态初始化
我知道问题是针对 log4j 的。我在查看与 logback 相关的内容时找到了此页面。以下是 sl4j 的建议:https://www.slf4j.org/faq.html#fatal。
您可以在邮件开头添加 "FATAL"。例如:
LOGGER.error("FATAL: database connection lost.");
您确实丢失了一些东西,比如基于级别的过滤,但这对很多人来说可能没问题,特别是因为您不太可能过滤掉 FATAL 语句(当然是调试和跟踪)。
我有那些具体的要求:
- 需要能够登录FATAL级别
- 需要使用SLF4J
- 需要使用Log4j2
现在,这是我的实现:
final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
final Marker marker = MarkerFactory.getMarker("FATAL");
logger.error(marker, "!!! Fatal World !!!");
这是我的 PatternLayout (在 yaml 中):
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %-5level %marker [%t] %logger{3.} - %msg%n"
这是我的日志输出:
20150506T155705,158 ERROR FATAL [main] - !!! Fatal World !!!
你知道如何有效地从日志输出中删除 "ERROR" 吗?
非常感谢
Marker
并不是您真正想要的。 Marker
用于 "enriching" 日志消息,使它们更易于搜索。您正在尝试更改日志 level/priority,这有点不同。
您正在使用 logger.error()
,它将消息记录为 ERROR
级别。
如果没有预定义的FATAL
级别(通常有,例如logger.fatal()
),则使用允许您指定日志级别的通用logger.log()
。
logger.fatal(yourMessage);
或
logger.log(priorityLevel, yourMessage);
更新:
来自 SLF4J 网站:
The Marker interface, part of the org.slf4j package, renders the FATAL level largely redundant. If a given error requires attention beyond that allocated for ordinary errors, simply mark the logging statement with a specially designated marker which can be named "FATAL" or any other name to your liking.
http://www.slf4j.org/faq.html#fatal
因此,使用 SLF4J,不可能有 FATAL
日志级别。我非常不同意这个决定背后的理由,但事实就是如此。
到目前为止我找到的唯一解决方案是使用 5 个标记:
final Marker traceMarker = MarkerFactory.getMarker("TRACE");
final Marker debugMarker = MarkerFactory.getMarker("DEBUG");
final Marker infoMarker = MarkerFactory.getMarker("INFO");
final Marker warnMarker = MarkerFactory.getMarker("WARN");
final Marker errorMarker = MarkerFactory.getMarker("ERROR");
final Marker fatalMarker = MarkerFactory.getMarker("FATAL");
并记录每次都通过标记:
logger.info(infoMarker, "!!! INFO World !!!");
logger.error(errorMarker, "!!! ERROR World !!!");
logger.error(fatalMarker, "!!! FATAL World !!!");
并修改 PatternLayout 以完全删除 LogLevel 并始终记录标记,例如:
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %marker [%t] %logger{3.} - %msg%n"
我有点认为这个解决方案是 hack...它还会以正确的方式使用 LogLevel 删除任何外部库的日志级别。
总结:这个解决方案不是一个好的解决方案。
UPDATE:我尝试了另一种解决方案,编写了一个 RewritePolicy:
public class FatalRewritePolicy implements RewritePolicy {
public static final String FATAL = "FATAL";
@Override
public LogEvent rewrite(final LogEvent logEvent) {
final Marker marker = logEvent.getMarker();
if (marker == null)
return logEvent;
// Log Level is final in the LogEvent, there's no way we can modify it.
Level level = logEvent.getLevel();
return null;
}
}
似乎无法更改 Log4j2 中 LogEvent 的 LogLevel(这很有意义)。
总结:仍然没有解决方案。
这是我和一些同事一起得出的最接近工作的解决方案:
- 使用SLF4J标记创建致命标记class。
- 使用 RoutingAppender,使用 Marker 作为路由模式:"$${marker:}"
- 配置一个 Fatal-specific appender,它有自己的 PatternLayout,它不包含 LogLevel 但包含硬编码的 FATAL 级别。
这是 Java 样本:
Marker fatal = MarkerFactory.getMarker("FATAL");
// Usage example
final Logger logger = LoggerFactory.getLogger(FatalLogger.class);
logger.log(fatal, "this is a fatal message");
// Log sample :
20150514T115144,279 FATAL [main] FatalLogger - this is a fatal message
这是 YAML 示例:
Configuration:
status: debug
Appenders:
RandomAccessFile:
- name: APPLICATION_APPENDER
fileName: logs/application.log
PatternLayout:
Pattern: "%d{ISO8601_BASIC} %-5level %msg%n"
- name: FATAL_APPENDER
fileName: logs/application.log
PatternLayout:
Pattern: "%d{ISO8601_BASIC} FATAL %msg%n"
Routing:
name: ROUTING_APPENDER
Routes:
pattern: "$${marker:}"
Route:
- key: FATAL
ref: FATAL_APPENDER
- ref: APPLICATION_APPENDER #DefaultRoute
Loggers:
Root:
level: trace
AppenderRef:
- ref: ROUTING_APPENDER
这是我为 log4j 做的,我也推荐给 log4j2...
编写您自己的自定义 slf4j 静态绑定程序又名桥。这需要一些工作,但由于各种复杂的原因,这是非常值得的 1 我大概有一天会写博客。
这是您的工作。
- 您在此处复制此代码:https://github.com/apache/logging-log4j2/tree/master/log4j-slf4j-impl
- 然后您想要编辑
Log4jLogger
class 并更改标记方法(跟踪、错误、警告、信息等)以适当地分派。即if (marker.contains("FATAL")) fatal(....);
- 从您的项目中排除原始
log4j-slf4j-impl
并使用您的新代码。
老实说,我认为 slf4j 存在严重缺陷,因为
- 它非常 difficult/impossible 覆盖硬核静态初始化以及不提供
logger.fatal(...)
。 - 不需要标记,标记本身就很复杂:
- 非常非常非常少的项目使用标记。我实际上 looked/grepped 开源项目和标记使用率接近于零低。
- 使用标记的是因为缺少
fatal
。这不是 80/20。标记用于 1%,fatal
用于 99%。 - 许多开发人员认为使用标记 "fatal" 会以某种方式将其映射到致命的。
- Few know what a detached marker is including myself.
- 与 MDC 上下文提供的内容重叠。给定面向事件维度的数据库(elasticsearch、druid 等),MDC 上下文更优越(name/value 对)。
1 其中之一能够真正成为日志框架启动过程的一部分,而不是难以确定几乎任意的静态初始化
我知道问题是针对 log4j 的。我在查看与 logback 相关的内容时找到了此页面。以下是 sl4j 的建议:https://www.slf4j.org/faq.html#fatal。
您可以在邮件开头添加 "FATAL"。例如:
LOGGER.error("FATAL: database connection lost.");
您确实丢失了一些东西,比如基于级别的过滤,但这对很多人来说可能没问题,特别是因为您不太可能过滤掉 FATAL 语句(当然是调试和跟踪)。