/**
* 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;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.lang.ref.SoftReference;
import java.util.Map;
import net.logstash.logback.decorate.JsonFactoryDecorator;
import net.logstash.logback.decorate.JsonGeneratorDecorator;
import net.logstash.logback.decorate.NullJsonFactoryDecorator;
import net.logstash.logback.decorate.NullJsonGeneratorDecorator;
import net.logstash.logback.fieldnames.LogstashCommonFieldNames;
import org.apache.commons.lang.time.FastDateFormat;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.spi.LifeCycle;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.io.SegmentedStringWriter;
import com.fasterxml.jackson.core.util.BufferRecycler;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MappingJsonFactory;
abstract class LogstashAbstractFormatter<EventType extends DeferredProcessingAware, FieldNamesType extends LogstashCommonFieldNames> implements LifeCycle {
/**
* Field name to use in logback configuration files
* if you want the field to be ignored (not output).
*
* Unfortunately, logback does not provide a way to set a
* field value to null via xml config,
* so we have to fall back to using this magic string.
*
* Note that if you're programmatically configuring the field names,
* then you can just set the field name to null in the
* FieldNamesType.
*/
public static final String IGNORE_FIELD_INDICATOR = "[ignore]";
protected static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT_WITH_MILLIS = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
/**
* This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftReference} to a {@link BufferRecycler} used to provide a low-cost
* buffer recycling between writer instances.
*/
private final ThreadLocal<SoftReference<BufferRecycler>> recycler = new ThreadLocal<SoftReference<BufferRecycler>>() {
protected SoftReference<BufferRecycler> initialValue() {
final BufferRecycler bufferRecycler = new BufferRecycler();
return new SoftReference<BufferRecycler>(bufferRecycler);
}
};
/**
* The jsonFactory provided by {@link #jsonFactoryProvider}.
* This will only non-null when the formatter is started.
*/
private MappingJsonFactory jsonFactory = (MappingJsonFactory) new MappingJsonFactory()
.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
/**
* Decorates the {@link #jsonFactory}.
* Allows customization of the {@link #jsonFactory}.
*/
private JsonFactoryDecorator jsonFactoryDecorator = new NullJsonFactoryDecorator();
/**
* Decorates the generators generated by the {@link #jsonFactory}.
* Allows customization of the generators.
*/
private JsonGeneratorDecorator jsonGeneratorDecorator = new NullJsonGeneratorDecorator();
/**
* The field names to use when writing the standard event fields
*/
protected FieldNamesType fieldNames;
private volatile boolean started;
protected final ContextAware contextAware;
protected LogstashAbstractFormatter(ContextAware contextAware, FieldNamesType fieldNames) {
this.contextAware = contextAware;
this.fieldNames = fieldNames;
}
@Override
public void start() {
jsonFactory = this.jsonFactoryDecorator.decorate(this.jsonFactory);
started = true;
}
@Override
public void stop() {
started = false;
}
@Override
public boolean isStarted() {
return started;
}
public JsonFactory getJsonFactory() {
return jsonFactory;
}
public byte[] writeValueAsBytes(EventType event, Context context) throws IOException {
ByteArrayBuilder outputStream = new ByteArrayBuilder(getBufferRecycler());
try {
writeValueToOutputStream(event, context, outputStream);
return outputStream.toByteArray();
} finally {
outputStream.release();
}
}
public void writeValueToOutputStream(EventType event, Context context, OutputStream outputStream) throws IOException {
JsonGenerator generator = createGenerator(outputStream);
writeValueToGenerator(generator, event, context);
}
public String writeValueAsString(EventType event, Context context) throws IOException {
SegmentedStringWriter writer = new SegmentedStringWriter(getBufferRecycler());
JsonGenerator generator = createGenerator(writer);
writeValueToGenerator(generator, event, context);
return writer.getAndClear();
}
private JsonGenerator createGenerator(OutputStream outputStream) throws IOException {
return this.jsonGeneratorDecorator.decorate(jsonFactory.createGenerator(outputStream));
}
private JsonGenerator createGenerator(Writer writer) throws IOException {
return this.jsonGeneratorDecorator.decorate(jsonFactory.createGenerator(writer));
}
protected abstract void writeValueToGenerator(JsonGenerator generator, EventType event, Context context) throws IOException;
protected void writeMapEntries(JsonGenerator generator, Map<?, ?> map) throws IOException, JsonMappingException {
if (map != null) {
for (Map.Entry<?, ?> entry : map.entrySet()) {
generator.writeFieldName(entry.getKey().toString());
generator.writeObject(entry.getValue());
}
}
}
/**
* Writes the field to the generator if and only if the fieldName and fieldValue are not null.
*/
protected void writeStringField(JsonGenerator generator, String fieldName, String fieldValue) throws IOException {
if (shouldWriteField(fieldName) && shouldWriteField(fieldValue)) {
generator.writeStringField(fieldName, fieldValue);
}
}
/**
* Writes the field to the generator if and only if the fieldName is not null.
*/
protected void writeNumberField(JsonGenerator generator, String fieldName, int fieldValue) throws IOException {
if (shouldWriteField(fieldName)) {
generator.writeNumberField(fieldName, fieldValue);
}
}
/**
* Writes the field to the generator if and only if the fieldName is not null.
*/
protected void writeNumberField(JsonGenerator generator, String fieldName, long fieldValue) throws IOException {
if (shouldWriteField(fieldName)) {
generator.writeNumberField(fieldName, fieldValue);
}
}
private boolean shouldWriteField(String fieldName) {
return fieldName != null && !fieldName.equals(IGNORE_FIELD_INDICATOR);
}
private BufferRecycler getBufferRecycler() {
SoftReference<BufferRecycler> bufferRecyclerReference = recycler.get();
BufferRecycler bufferRecycler = bufferRecyclerReference.get();
if (bufferRecycler == null) {
recycler.remove();
return getBufferRecycler();
}
return bufferRecycler;
}
public FieldNamesType getFieldNames() {
return fieldNames;
}
public void setFieldNames(FieldNamesType fieldNames) {
this.fieldNames = fieldNames;
}
public JsonFactoryDecorator getJsonFactoryDecorator() {
return jsonFactoryDecorator;
}
public void setJsonFactoryDecorator(JsonFactoryDecorator jsonFactoryDecorator) {
this.jsonFactoryDecorator = jsonFactoryDecorator;
}
public JsonGeneratorDecorator getJsonGeneratorDecorator() {
return jsonGeneratorDecorator;
}
public void setJsonGeneratorDecorator(JsonGeneratorDecorator jsonGeneratorDecorator) {
this.jsonGeneratorDecorator = jsonGeneratorDecorator;
}
}