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

时间:2013-03-21 02:59:55

标签: java json logging slf4j logback

我在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 ......)

7 个答案:

答案 0 :(得分:3)

如果你有一个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模式将取消您的消息。

答案 1 :(得分:2)

使用RawJsonAppendingMarker:

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

答案 2 :(得分:2)

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

解决了这个问题
<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);

答案 3 :(得分: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"])

答案 4 :(得分:1)

Logback对JSON没有任何异常。它只是一个正常记录的字符串。逃避可能发生在你的最后,除非你在谈论某种以这种格式写出来的JSON Appender。我很确定Logback本身没有这样的东西,所以如果那是你的问题,那么你想看看你从哪里得到Appender。 SSCCE有助于进一步排查故障。

答案 5 :(得分:1)

我自己过来了,发现了一篇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>

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

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

答案 6 :(得分:0)

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

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));