Java:如何将原始JSON记录为JSON,并避免在使用logback / slf4j进行日志记录期间转义

2022-09-03 06:38:10

我在 JAX-RS 应用程序中使用带有 Logback 的 SLF4J...我想以这样的方式登录到JSON,即我的消息不会再次编码,而是原始打印到日志文件中:

目前它看起来像这样:

{"@timestamp":1363834123012,"@message":"{\"text\":\"From MLK to Barack 
Ob...\n\"}"

但我想拥有这个:

  {"@timestamp":1363834123012,"@message": { "text ": "From MLK to Barack 
Ob...\n\}

原因是我想再次解析JSON,并希望避免数据的去维化。

我已经编写了一个自定义的logback编码器,但我发现没有办法避免转义。是否可以传递对象以重新登录并根据对象的类型更改设置?

编辑:我找到了一种方法 - 不完全优雅 - 按照SSCE的要求:

在我的应用程序中

// SLF4J Logger
private static Logger logger = LoggerFactory.getLogger(MyClass.class);
// A logback? Marker
private Marker foo = MarkerFactory.getMarker("foo");
// Jackson ObjectMapper()
ObjectMapper mapper = new ObjectMapper();

// Log something... 
logger.info(foo, mapper.writeValueAsString(json));

我使用了 Logstash-Encoder 的变体,可以在这里找到:https://github.com/logstash/logstash-logback-encoder

package my.package;

import static org.apache.commons.io.IOUtils.*;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;

import org.codehaus.jackson.JsonGenerator.Feature;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import org.slf4j.Marker;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.encoder.EncoderBase;

public class JsonEncoder extends EncoderBase<ILoggingEvent> {

    private static final ObjectMapper MAPPER = new ObjectMapper().configure(
        Feature.ESCAPE_NON_ASCII, true);
    private static Marker M;

    private boolean immediateFlush = true;

@Override
public void doEncode(ILoggingEvent event) throws IOException {

    M = event.getMarker();

    ObjectNode eventNode = MAPPER.createObjectNode();

    eventNode.put("@timestamp", event.getTimeStamp());
    //
    if (M != null) {
        if (M.getName().equals("foo")) {
            JsonNode j = MAPPER.readTree(event.getFormattedMessage());
            eventNode.put("@foo", j);
        }
    } else {
        eventNode.put("@message", event.getFormattedMessage());
    }
    eventNode.put("@fields", createFields(event));

    write(MAPPER.writeValueAsBytes(eventNode), outputStream);
    write(CoreConstants.LINE_SEPARATOR, outputStream);

    if (immediateFlush) {
        outputStream.flush();
    }

}

private ObjectNode createFields(ILoggingEvent event) {
         // not important here
    return fieldsNode;

}

@Override
public void close() throws IOException {
    write(LINE_SEPARATOR, outputStream);
}

public boolean isImmediateFlush() {
    return immediateFlush;
}

public void setImmediateFlush(boolean immediateFlush) {
    this.immediateFlush = immediateFlush;
}
}

它现在工作!是的!但我想这不是最好的方法(序列化,反序列化JSON...)


答案 1

如果你有一个Json格式的消息,上面的解决方案可以工作,但不是那么好,因为你不想调用logshtash特定的代码,每次你在代码中使用你的记录器。

只需添加一个

net.logstash.logback.encoder.LogstashEncoder

是不够的,因为信息本身保持逃避。要解决此问题,请在日志备份中尝试以下操作.xml:

<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
  <providers>
    <timestamp/>
    <version/>
    <loggerName/>
    <pattern>
      <pattern>
        {
          "jsonMessage": "#asJson{%message}"
        }
      </pattern>
    </pattern>
  </providers>
</encoder>

#asJson模式将疏远您的信息。


更新:

现在有一个解析选项,它将消息转换为 json,如果解析失败,请保持字符串不变。(请参阅文档tryJson)

<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
  <providers>
    <timestamp />
      <pattern>
        <pattern>
          {
            "message": "#tryJson{%message}"
          }
        </pattern>
      </pattern>
    <loggerName/>
  </providers>
</encoder>

这将用记录的字符串替换默认属性,如果可能,可以选择转换为 json。message


答案 2

使用 RawJsonAppendingMarker:

log.trace(net.logstash.logback.marker.Markers.appendRaw("jsonMessage", jsonString), null);