net.logstash.logback.layout.CompositeJsonLayout Maven / Gradle / Ivy
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 net.logstash.logback.layout;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Objects;
import net.logstash.logback.composite.AbstractCompositeJsonFormatter;
import net.logstash.logback.composite.JsonProviders;
import net.logstash.logback.decorate.JsonFactoryDecorator;
import net.logstash.logback.decorate.JsonGeneratorDecorator;
import net.logstash.logback.encoder.CompositeJsonEncoder;
import net.logstash.logback.encoder.SeparatorParser;
import net.logstash.logback.util.ReusableByteBuffer;
import net.logstash.logback.util.ThreadLocalReusableByteBuffer;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.LayoutBase;
import ch.qos.logback.core.pattern.PatternLayoutBase;
import ch.qos.logback.core.spi.DeferredProcessingAware;
public abstract class CompositeJsonLayout extends LayoutBase {
private boolean immediateFlush = true;
private Layout prefix;
private Layout suffix;
/**
* Separator to use between events.
*
* By default, this is null (for backwards compatibility), indicating no separator.
* Note that this default is different than the default of {@link CompositeJsonEncoder#lineSeparator}.
* In a future major release, the default will likely change to be the same as {@link CompositeJsonEncoder#lineSeparator}.
*/
private String lineSeparator;
/**
* The minimum size of the byte buffer used when encoding events.
*
* The buffer automatically grows above the {@code #minBufferSize} when needed to
* accommodate with larger events. However, only the first {@code minBufferSize} bytes
* will be reused by subsequent invocations. It is therefore strongly advised to set
* the minimum size at least equal to the average size of the encoded events to reduce
* unnecessary memory allocations and reduce pressure on the garbage collector.
*/
private int minBufferSize = 1024;
/**
* Per-thread {@link ReusableByteBuffer}
*/
private ThreadLocalReusableByteBuffer threadLocalBuffer;
private final AbstractCompositeJsonFormatter formatter;
public CompositeJsonLayout() {
super();
this.formatter = Objects.requireNonNull(createFormatter());
}
protected abstract AbstractCompositeJsonFormatter createFormatter();
@Override
public String doLayout(Event event) {
if (!isStarted()) {
throw new IllegalStateException("Layout is not started");
}
ReusableByteBuffer buffer = threadLocalBuffer.acquire();
try {
writeEvent(buffer, event);
return new String(buffer.toByteArray());
} catch (IOException e) {
addWarn("Error formatting logging event", e);
return null;
} finally {
threadLocalBuffer.release();
}
}
private void writeEvent(OutputStream outputStream, Event event) throws IOException {
try (Writer writer = new OutputStreamWriter(outputStream)) {
writeLayout(prefix, writer, event);
formatter.writeEvent(event, outputStream);
writeLayout(suffix, writer, event);
if (lineSeparator != null) {
writer.write(lineSeparator);
}
writer.flush();
}
}
private void writeLayout(Layout wrapped, Writer writer, Event event) throws IOException {
if (wrapped == null) {
return;
}
String str = wrapped.doLayout(event);
if (str != null) {
writer.write(str);
writer.flush();
}
}
@Override
public void start() {
if (isStarted()) {
return;
}
super.start();
formatter.setContext(getContext());
formatter.start();
startWrapped(prefix);
startWrapped(suffix);
this.threadLocalBuffer = new ThreadLocalReusableByteBuffer(minBufferSize);
}
private void startWrapped(Layout wrapped) {
if (wrapped instanceof PatternLayoutBase) {
/*
* Don't ensure exception output (for ILoggingEvents)
* or line separation (for IAccessEvents)
*/
PatternLayoutBase layout = (PatternLayoutBase) wrapped;
layout.setPostCompileProcessor(null);
/*
* The pattern will be re-parsed during start.
* Needed so that the pattern is re-parsed without
* the postCompileProcessor.
*/
layout.start();
}
if (wrapped != null && !wrapped.isStarted()) {
wrapped.start();
}
}
@Override
public void stop() {
if (!isStarted()) {
return;
}
super.stop();
formatter.stop();
stopWrapped(prefix);
stopWrapped(suffix);
this.threadLocalBuffer = null;
}
private void stopWrapped(Layout wrapped) {
if (wrapped != null && !wrapped.isStarted()) {
wrapped.stop();
}
}
public JsonProviders getProviders() {
return formatter.getProviders();
}
public void setProviders(JsonProviders jsonProviders) {
formatter.setProviders(jsonProviders);
}
public boolean isImmediateFlush() {
return immediateFlush;
}
public void setImmediateFlush(boolean immediateFlush) {
this.immediateFlush = immediateFlush;
}
public JsonFactoryDecorator getJsonFactoryDecorator() {
return formatter.getJsonFactoryDecorator();
}
public void setJsonFactoryDecorator(JsonFactoryDecorator jsonFactoryDecorator) {
formatter.setJsonFactoryDecorator(jsonFactoryDecorator);
}
public JsonGeneratorDecorator getJsonGeneratorDecorator() {
return formatter.getJsonGeneratorDecorator();
}
public void setJsonGeneratorDecorator(JsonGeneratorDecorator jsonGeneratorDecorator) {
formatter.setJsonGeneratorDecorator(jsonGeneratorDecorator);
}
public void setFindAndRegisterJacksonModules(boolean findAndRegisterJacksonModules) {
formatter.setFindAndRegisterJacksonModules(findAndRegisterJacksonModules);
}
protected AbstractCompositeJsonFormatter getFormatter() {
return formatter;
}
public Layout getPrefix() {
return prefix;
}
public void setPrefix(Layout prefix) {
this.prefix = prefix;
}
public Layout getSuffix() {
return suffix;
}
public void setSuffix(Layout suffix) {
this.suffix = suffix;
}
public String getLineSeparator() {
return lineSeparator;
}
/**
* Sets which lineSeparator to use between events.
*
* The following values have special meaning:
*
* - {@code null} or empty string = no new line. (default)
* - "{@code SYSTEM}" = operating system new line.
* - "{@code UNIX}" = unix line ending ({@code \n}).
* - "{@code WINDOWS}" = windows line ending ({@code \r\n}).
*
*
* Any other value will be used as given as the lineSeparator.
*
* @param lineSeparator the separator format
*/
public void setLineSeparator(String lineSeparator) {
this.lineSeparator = SeparatorParser.parseSeparator(lineSeparator);
}
public int getMinBufferSize() {
return minBufferSize;
}
/**
* The minimum size of the byte buffer used when encoding events.
*
* The buffer automatically grows above the {@code #minBufferSize} when needed to
* accommodate with larger events. However, only the first {@code minBufferSize} bytes
* will be reused by subsequent invocations. It is therefore strongly advised to set
* the minimum size at least equal to the average size of the encoded events to reduce
* unnecessary memory allocations and reduce pressure on the garbage collector.
*
*
Note: changes to the buffer size will not be taken into account after the encoder
* is started.
*
* @param minBufferSize the minimum buffer size (in bytes)
*/
public void setMinBufferSize(int minBufferSize) {
this.minBufferSize = minBufferSize;
}
}