All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.evitadb.server.log.AppLogJsonLayout Maven / Gradle / Ivy

The newest version!
/*
 *
 *                         _ _        ____  ____
 *               _____   _(_) |_ __ _|  _ \| __ )
 *              / _ \ \ / / | __/ _` | | | |  _ \
 *             |  __/\ V /| | || (_| | |_| | |_) |
 *              \___| \_/ |_|\__\__,_|____/|____/
 *
 *   Copyright (c) 2023-2024
 *
 *   Licensed under the Business Source License, Version 1.1 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   https://github.com/FgForrest/evitaDB/blob/master/LICENSE
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package io.evitadb.server.log;

import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import ch.qos.logback.core.util.CachingDateFormatter;
import io.evitadb.api.observability.trace.TracingContext;
import io.evitadb.utils.StringUtils;
import lombok.Setter;

import javax.annotation.Nonnull;


/**
 * Logs events as JSON objects for easier digestion by log aggregators like Loki. We construct the JSON manually
 * so we don't slow down the logging process by using Jackson.
 *
 * @author Lukáš Hornych, FG Forrest a.s. (c) 2023
 */
public class AppLogJsonLayout extends LayoutBase {

	private static final String[] ESCAPED_CHARS = new String[] { "\r\n", "\n", "\r", "\f", "\b", "\\", "\"" };
	private static final String[] REPLACEMENTS_FOR_ESCAPED_CHARS = new String[] { "\\r\\n", "\\n", "\\r", "\\f", "\\b", "\\\\", "\\\"" };

	private final CachingDateFormatter cachingDateFormatter = new CachingDateFormatter("yyyy-MM-dd'T'HH:mm:ss.SSSZ", null);
	private final MessageConverter messageConverter = new MessageConverter();
	private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter();

	@Setter private boolean logTimestamp = true;

	@Override
	public void start() {
		messageConverter.start();
		throwableProxyConverter.start();
		super.start();
	}

	@Override
	public String doLayout(ILoggingEvent event) {
		final StringBuilder buf = new StringBuilder(512);

		buf.append("{");

		if (logTimestamp) {
			buf.append("\"timestamp\":\"");
			buf.append(cachingDateFormatter.format(event.getTimeStamp()));
			buf.append("\"");

			buf.append(",");
		}

		buf.append("\"level\":\"");
		buf.append(event.getLevel().toString());
		buf.append("\"");

		buf.append(",");

		String completeMessage = messageConverter.convert(event);
		if (event.getThrowableProxy() != null) {
			completeMessage += "\n" + throwableProxyConverter.convert(event);
		}
		buf.append("\"message\":\"");
		buf.append(escapeMessage(completeMessage));
		buf.append("\"");

		buf.append(",");

		final String clientId = event.getMDCPropertyMap().get(TracingContext.MDC_CLIENT_ID_PROPERTY);
		buf.append("\"client_id\":");
		if (clientId == null) {
			buf.append("null");
		} else {
			buf.append("\"");
			buf.append(clientId);
			buf.append("\"");
		}

		buf.append(",");

		final String traceId = event.getMDCPropertyMap().get(TracingContext.MDC_TRACE_ID_PROPERTY);
		buf.append("\"trace_id\":");
		if (traceId == null) {
			buf.append("null");
		} else {
			buf.append("\"");
			buf.append(traceId);
			buf.append("\"");
		}

		buf.append("}");
		buf.append(CoreConstants.LINE_SEPARATOR);

		return buf.toString();
	}

	/**
	 * Escapes special characters in a given message by replacing them with their corresponding escape sequences.
	 * The escape sequences are defined in the {@link AppLogJsonLayout} class.
	 *
	 * @param message the message to escape
	 * @return the escaped message
	 */
	private static String escapeMessage(@Nonnull String message) {
		return StringUtils.replaceEach(message, ESCAPED_CHARS, REPLACEMENTS_FOR_ESCAPED_CHARS);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy