Java: как регистрировать raw JSON как JSON и избегать экранирования при регистрации с помощью logback/slf4j
Я использую SLF4J с Logback в приложении JAX-RS... Я хочу войти в JSON таким образом, что мое сообщение не закодировано снова, а напечатано в файле журнала:
В настоящий момент это выглядит так:
{"@timestamp":1363834123012,"@message":"{\"text\":\"From MLK to Barack
Ob...\n\"}"
Но я хочу иметь это:
{"@timestamp":1363834123012,"@message": { "text ": "From MLK to Barack
Ob...\n\}
Причина в том, что я хочу снова разобрать JSON и хочу избежать неэквивации данных.
Я написал собственный кодер регистрации, но не нашел способа избежать экранирования. Могу ли я передать объект для регистрации и изменить настройки в зависимости от типа объекта?
Изменить: я нашел способ - не совсем элегантный - по просьбе 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. Это просто строка, которая регистрируется как обычно. Ускорение, вероятно, происходит на вашем конце, если вы не говорите о каком-то JSON Appender, который записывает его в этом формате. Я почти уверен, что у Logback нет ничего подобного, поэтому вы бы хотели посмотреть, откуда у вас есть Appender, если это ваша проблема. SSCCE поможет в дальнейшем устранении неполадок.
Ответ 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
здесь приведен обновленный (2016) groovy logback config, который выгружает ваши журналы в формате 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
Если у вас есть отформатированные сообщения Json, верхние решения работают, но они не так хороши, так как вы не хотите вызывать конкретный код logstash, каждый раз, когда вы используете свой регистратор в коде.
Просто добавив
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 отменит ваше сообщение.
Ответ 5
Используйте RawJsonAppendingMarker:
log.trace(net.logstash.logback.marker.Markers.appendRaw("jsonMessage", jsonString), null);
Ответ 6
Только что наткнулся на это сам и нашел статью с несколькими рекомендациями по протоколированию.
Если вы используете 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/
. Файл сканируется один раз, когда он достигает размера 1 МБ.
LOGGER.debug(append("object", someObject), "log message");
Ответ 7
Я не вижу исходного кода, который вызывает вашу проблему, но я подозреваю, что это может выглядеть так.
JsonNode logOutput;
String messageJSONAsString;
...
logOutput.put("@message", messageJSONAsString);
logger.info(objectMapper.writeValueAsString(logOutput);
Это приведет к выходу JSON на выходе, потому что, когда вы помещаете сообщение в выходной JsonNode, Джексон снова убежит от него, чтобы убедиться, что выход действителен JSON.
Решением здесь является размещение сообщения в вашем представлении как ObjectNode, а не как строка. Обычно у вас уже есть доступ к объекту как к объекту, и в этом случае вы можете сделать
ObjectNode jsonObject = objectMapper.valueToTree(messageObject);
logOutput.put("@message", jsonObject)
В противном случае, если ваше сообщение является строкой JSON, затем проанализируйте его и добавьте в выходной файл
logoutput.put("@message", objectMapper.readTree(messageJSONAsString));