【问题标题】:Log4j2 JSONLayout timestamp patternLog4j2 JSONLayout 时间戳模式
【发布时间】:2017-05-05 14:20:18
【问题描述】:

显然,log4j2 中的JSONLayout 不支持时间戳模式。通常它只有 JSON 格式选项,但没有 pattern 选项。

{
  "configuration": {
    "name": "logggg",
    "packages" : "logger.savemyjob",
    "appenders": {
      "RollingFile": {
        "name": "rollingStone",
        "fileName": "async_rolled.log",
        "filePattern": "async_rolled-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz",
        "immediateFlush" : false,
         "JSONLayout": {
            "complete": true,
            "compact": false,
            "eventEol": true
         },
        "SizeBasedTriggeringPolicy": {
          "size": "10 MB"
        },
        "DefaultRolloverStrategy": {
          "max": "10"
        }
      }
    },
    "loggers": {
      "root": {
        "level": "debug",
        "appender-ref": {
          "ref": "rollingStone"
        }
      }
    }
  }
}

日志示例,

{
  "timeMillis" : 1482231551081,
  "thread" : "main",
  "level" : "debug",
  "endOfBatch" : false,
  "threadId" : 1,
  "threadPriority" : 5, 
  "message" : "log4j might suck"
}

当我查看他们的 API 时,它看起来过于冗长,并且看不到添加时间戳字段的更简单方法。

JsonLayout 插件似乎是我需要覆盖的插件,因为它的 final 甚至无法扩展,否则我必须复制整个依赖类。

@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public final class JsonLayout extends AbstractJacksonLayout {

protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties,
            final boolean encodeThreadContextAsList,
            final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
            final String footerPattern, final Charset charset) {
        super(config, new JacksonFactory.JSON(encodeThreadContextAsList).newWriter(locationInfo, properties, compact),
                charset, compact, complete, eventEol,
                PatternLayout.createSerializer(config, null, headerPattern, DEFAULT_HEADER, null, false, false),
                PatternLayout.createSerializer(config, null, footerPattern, DEFAULT_FOOTER, null, false, false));
    }

}

架构看起来比我预期的要复杂 :(,我从 Logger 追踪。

我也考虑过改变LogEvent本身,

public interface LogEvent extends Serializable {

    @Deprecated
    Map<String, String> getContextMap();

    ReadOnlyStringMap getContextData();

    ThreadContext.ContextStack getContextStack();

    String getLoggerFqcn();

    Level getLevel();

    String getLoggerName();

    Marker getMarker();

    Message getMessage();

    long getTimeMillis();

    StackTraceElement getSource();

    String getThreadName();

    long getThreadId();

    int getThreadPriority();

    Throwable getThrown();

    ThrowableProxy getThrownProxy();

    boolean isEndOfBatch();

    boolean isIncludeLocation();

    void setEndOfBatch(boolean endOfBatch);

    void setIncludeLocation(boolean locationRequired);

    long getNanoTime();

    String getTimestamp();
}

还有MutableLogEvent

public class MutableLogEvent implements LogEvent, ReusableMessage {

    public void initFrom(final LogEvent event) {

        SimpleDateFormat standardDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        this.timestamp = standardDateFormat.format(new Date(event.getTimeMillis()));
    }
}

我猜它可能会起作用,尽管它破坏了几个核心 log4j-core 测试。我基本上想知道以最小变化添加额外 json 字段的技巧。

我很少看到像 JSONEventLayoutV1 这样的其他 impl,这似乎与 log4j json api 完全不同,后者在性能方面非常出色。

这是我失败的覆盖尝试,LogEventhttps://github.com/prayagupd/sell-peace/blob/custom_timestamp/supply-peace/src/main/java/org/apache/logging/log4j/core/DnLogEvent.java

问题越来越长,我基本上想知道我覆盖log4j2 api时不要错过的重要事情。

【问题讨论】:

    标签: java logging log4j2


    【解决方案1】:

    如果这只是添加一个包含时间戳的新字段,除了默认提供的 timeMillis,为什么不尝试在新的自定义字段上使用 Lookups。

    JsonLayout 配置可能如下所示:

    <JsonLayout>
        <KeyValuePair key="timestamp" value="$${date:yyyy-MM-dd'T'HH:mm:ss.SSSZ}" />
    </JsonLayout>
    

    $$是Lookupdate:后面的字符是java SimpleDateFormat可以接受的格式

    【讨论】:

    • 谢谢彼得!这应该被标记为可接受的解决方案,它是最简单和最优雅的解决方案。
    • 一个小提示:我应用此设置来生成与 DataDog 兼容的日志。令我惊讶的是,他们不接受 4 位时区格式(如+0100),它必须在内部有: 分隔符(如+01:00)。为此,请将Z 模式字母替换为XXX
    【解决方案2】:

    所以,简而言之,我需要编写 7 个对象。流程如下

    CustomLogEvent 
        -> LogEventToCustomLogEventConverter 
           -> CustomLogEventMixIn 
               -> CustomLog4jJsonModule 
                      -> CustomLog4jJsonObjectMapper 
                          -> CustomJacksonFactory 
                              -> CustomJSONLayout
    

    CustomJSONLayout 是我将在我的log4j2.json 中使用的插件,它支持将参数作为配置。

    所以,我最终同时为LogEvent 使用了继承和组合。

    public class JsonLogEvent implements LogEvent{
    
        static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss";
        static final DateFormat isoDateFormat = new SimpleDateFormat(TIMESTAMP_FORMAT);
    
        private LogEvent wrappedLogEvent;
    
        public JsonLogEvent(LogEvent wrappedLogEvent) {
            this.wrappedLogEvent = wrappedLogEvent;
        }
    
        public String getTimestamp() {
            return isoDateFormat.format(new Date(this.getTimeMillis()));
        }
    }
    

    还有CustomLogEventMixIn,它以timestamp 作为键。

    @JsonSerialize(converter = JsonLogEvent.LogEventToCustomLogEventConverter.class)
    @JsonRootName(XmlConstants.ELT_EVENT)
    @JsonFilter("org.apache.logging.log4j.core.impl.Log4jLogEvent")
    @JsonPropertyOrder({"timestamp", "threadName", "level", "loggerName", "marker", "message", "thrown",
            XmlConstants.ELT_CONTEXT_MAP, JsonConstants.ELT_CONTEXT_STACK, "loggerFQCN", "Source", "endOfBatch", "timeMillis" })
    abstract class CustomLogEventMixIn extends LogEventMixIn {
    
        @JsonProperty("timestamp")
        public abstract String getTimestamp();
    
        private static final long serialVersionUID = 1L;
    
    }
    
    public static class LogEventToCustomLogEventConverter extends StdConverter<LogEvent, JsonLogEvent> {
    
        @Override
        public JsonLogEvent convert(LogEvent value) {
            return new JsonLogEvent(value);
        }
    }
    

    LogEventMixInLog4jJsonModule 使用

    public class CustomLog4jJsonModule extends Log4jJsonModule {
    
        private static final long serialVersionUID = 1L;
    
        CustomLog4jJsonModule() {
            super();
        }
    
        @Override
        public void setupModule(final SetupContext context) {
            super.setupModule(context);
    
            context.setMixInAnnotations(LogEvent.class, CustomLogEventMixIn.class);
        }
    }
    
    public class CustomLog4jJsonObjectMapper extends ObjectMapper {
    
        public CustomLog4jJsonObjectMapper() {
            this.registerModule(new CustomLog4jJsonModule());
            this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        }
    
    }
    

    追踪JsonLayout 的使用方式非常有帮助。

    【讨论】:

      【解决方案3】:

      首先要做的是在Log4j2 JIRA issue tracker 上提出功能请求。这听起来可以使许多用户受益,因此值得尝试在 Log4j 本身中修复它。

      同时,让我们看一下自定义解决方案。我不会更改 LogEvent,这将导致解决方案脆弱(例如,可能不适用于 Async Loggers 和 AsyncAppender)。此外,当您想升级到更高版本的 Log4j2 时,您可能会遇到麻烦。 LogEvent 已经有你需要的数据 (timeMillis),它只需要格式化。

      官方的方式是创建一个自定义的 Json 布局插件。您可以进行重写,也可以从复制代码开始。 (JIRA 票中要提出的另一个主题。)要更改的关键类可能是LogEventJsonMixIn

      Log4j2 使用 Jackson 来生成 json 字符串。您可能需要将 LogEventJsonMixIn 替换为提供格式化日期而不是原始毫秒的版本。 Jackson 可能已经为此提供了 Deserializer,否则您需要自己编写。 Log4j 社区或许也能提供更多的想法。

      【讨论】:

      • 谢谢 Remko。我可能会创建一个 JIRA 问题。我最终像你说的那样,在LogEvent 周围创建了Wrapper JsonLogEvent,它返回格式化的时间。而CustomLogEventMixIn extending LogEventMixIn 具有timestamp json 键。
      • 可能对其他人也有用。如果留在 StackOverflow 上,每个人都需要将其作为自定义解决方案来实现。合并到 Log4j 中可能会很好。
      【解决方案4】:

      您可以使用下面的代码

      <KeyValuePair key="@timestamp" value="$${date:dd-MM-yyyy HH:mm:ss}"/>
      

      log4j2.xml中的所有配置

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration>
          <Properties>
              <Property name="activeProfile">${sys:spring.profiles.active}</Property>
          </Properties>
          <Appenders>
              <Console name="Console" target="SYSTEM_OUT">
                  <JSONLayout eventEol="true" complete="false" locationInfo="true" includeTimeMillis="true">
                      <KeyValuePair key="@timestamp" value="$${date:dd-MM-yyyy HH:mm:ss}"/>
                      <KeyValuePair key="requestId" value="$${ctx:X-Request-Id}"/>
                  </JSONLayout>
              </Console>
          </Appenders>
          <Loggers>
              <Root level="info">
                  <AppenderRef ref="Console" />
              </Root>
          </Loggers>
      </Configuration>
      

      【讨论】:

        猜你喜欢
        • 2016-01-17
        • 1970-01-01
        • 1970-01-01
        • 2016-09-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多