【问题标题】:Java: How to log raw JSON as JSON and avoid escaping during logging with logback / slf4jJava:如何将原始 JSON 记录为 JSON 并避免在使用 logback / slf4j 进行记录时转义
【发布时间】:2013-03-10 10:35:17
【问题描述】:

我在 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 编码器,但我没有办法避免转义。我可以将对象传递给 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 ......)

【问题讨论】:

    标签: java json logging slf4j logback


    【解决方案1】:

    如果您有 Json 格式的消息,则上面的解决方案可以工作,但不是很好,因为您不想在每次在代码中使用记录器时调用特定于 logstash 的代码。

    添加一个

    net.logstash.logback.encoder.LogstashEncoder
    

    还不够,因为 消息本身一直被转义。要解决此问题,请在 logback.xml 中尝试以下操作:

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

    #asJson 模式将使您的消息转义。

    【讨论】:

      【解决方案2】:

      这是一个更新的 (2016) groovy logback 配置,它将您的日志以 json 格式转储到一个文件中,并在控制台中调试行。花了我一整天才弄清楚,所以我想我会更新线程。

          import ch.qos.logback.classic.encoder.PatternLayoutEncoder
      import ch.qos.logback.core.ConsoleAppender
      import ch.qos.logback.core.rolling.FixedWindowRollingPolicy
      import ch.qos.logback.core.rolling.RollingFileAppender
      import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy
      import net.logstash.logback.encoder.LogstashEncoder
      
      import static ch.qos.logback.classic.Level.INFO
      import static ch.qos.logback.classic.Level.WARN
      
      def PROJECT_ID = "com.foo"
      
          appender("file", RollingFileAppender) {
              file = "/tmp/logs/${PROJECT_ID}.json"
              encoder(LogstashEncoder)
              rollingPolicy(FixedWindowRollingPolicy) {
                  maxIndex = 1
                  fileNamePattern = "logs/${PROJECT_ID}.json.%i"
              }
              triggeringPolicy(SizeBasedTriggeringPolicy) {
                  maxFileSize = "1MB"
              }
          }
      
      
          appender("STDOUT", ConsoleAppender) {
              encoder(PatternLayoutEncoder) {
                  pattern = "%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"
              }
          }
      
          logger("com.foo", INFO, ["STDOUT", "file"], false)
      
          root(WARN, ["STDOUT", "file"])
      

      【讨论】:

        【解决方案3】:

        我自己刚刚过来,发现一篇带有few recommandations on logging的文章。

        如果你使用 maven 把这个依赖放到pom.xml

        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>3.4</version>
        </dependency>
        

        然后把这样的东西放到logback.xml

        <configuration>
            <property name="PROJECT_ID" value="example"/>
            <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <File>logs/${PROJECT_ID}.json</File>
                <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
                <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
                    <maxIndex>1</maxIndex>
                    <FileNamePattern>logs/${PROJECT_ID}.json.%i</FileNamePattern>
                </rollingPolicy>
                <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                    <MaxFileSize>1MB</MaxFileSize>
                </triggeringPolicy>
            </appender>
            <logger name="eu.kielczewski" additivity="false" level="INFO">
                <appender-ref ref="file"/>
            </logger>
            <root level="WARN">
                <appender-ref ref="file"/>
            </root>
        </configuration>
        

        这会在logs/ 下创建example.json 文件。文件大小达到 1MB 时滚动一次。

        LOGGER.debug(append("object", someObject), "log message");
        

        【讨论】:

          【解决方案4】:

          我遇到了同样的问题。我用

          解决了
          <encoder
           class="net.logstash.logback.encoder.LogstashEncoder">          
          </encoder
          

          而不是

          <encoder
           class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
          

          在我使用的 java 代码中:

          SRV_PERF_LOGGER.info(net.logstash.logback.marker.Markers.appendRaw("message", jackson.writeValueAsString(dto)), null);
          

          【讨论】:

            【解决方案5】:

            使用 RawJsonAppendingMarker:

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

            【讨论】:

              【解决方案6】:

              我看不到导致您的问题的原始代码,但我怀疑它可能看起来像这样

              JsonNode logOutput;
              String messageJSONAsString;
              

              ...

              logOutput.put("@message", messageJSONAsString);
              logger.info(objectMapper.writeValueAsString(logOutput);
              

              这将在您的输出中生成转义的 JSON,因为当您将消息放入输出 JsonNode 时,Jackson 会为您重新转义它以确保输出是有效的 JSON。

              这里的解决方案是将消息作为 ObjectNode 而不是字符串放在输出中。通常您已经可以将对象作为 Object 访问,在这种情况下您可以这样做

              ObjectNode jsonObject = objectMapper.valueToTree(messageObject);
              logOutput.put("@message", jsonObject)
              

              否则,如果您的消息是 JSON 字符串,则对其进行解析并将其添加到输出中

              logoutput.put("@message", objectMapper.readTree(messageJSONAsString));
              

              【讨论】:

                【解决方案7】:

                Logback 不会对 JSON 做任何不寻常的事情。它只是一个正常记录的字符串。转义可能发生在您的最后,除非您正在谈论某种以该格式写出的 JSON Appender。我很确定 Logback 本身没有类似的东西,所以如果这是你的问题,你会想看看你从哪里得到 Appender。 SSCCE 将有助于进一步排除故障。

                【讨论】:

                • 您好,感谢您抽出宝贵时间来查看问题。我找到了一个有点 hacky 的解决方案,并添加了一些应该类似于 SSCCE 的东西!
                猜你喜欢
                • 2016-10-11
                • 1970-01-01
                • 2017-05-06
                • 1970-01-01
                • 1970-01-01
                • 2012-03-15
                • 2023-04-03
                • 1970-01-01
                • 2020-08-07
                相关资源
                最近更新 更多