
net.logstash.logback.composite.AbstractFormattedTimestampJsonProvider Maven / Gradle / Ivy
/*
* Copyright 2013-2023 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.composite;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.TimeZone;
import java.util.function.Function;
import net.logstash.logback.fieldnames.LogstashCommonFieldNames;
import net.logstash.logback.util.TimeZoneUtils;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import com.fasterxml.jackson.core.JsonGenerator;
/**
* Writes the timestamp field as either:
*
* - A string value formatted by a {@link DateTimeFormatter} pattern
* - A string value representing the number of milliseconds since unix epoch (designated by specifying the pattern value as {@value #UNIX_TIMESTAMP_AS_STRING})
* - A number value of the milliseconds since unix epoch (designated by specifying the pattern value as {@value #UNIX_TIMESTAMP_AS_NUMBER})
*
*/
public abstract class AbstractFormattedTimestampJsonProvider extends AbstractFieldJsonProvider implements FieldNamesAware {
public static final String FIELD_TIMESTAMP = "@timestamp";
/**
* Setting the {@link #pattern} as this value will make it so that the timestamp
* is written as a number value of the milliseconds since unix epoch.
*/
public static final String UNIX_TIMESTAMP_AS_NUMBER = "[UNIX_TIMESTAMP_AS_NUMBER]";
/**
* Setting the {@link #pattern} as this value will make it so that the timestamp
* is written as a string value representing the number of milliseconds since unix epoch
*/
public static final String UNIX_TIMESTAMP_AS_STRING = "[UNIX_TIMESTAMP_AS_STRING]";
/**
* The default {@link #pattern} value.
*/
private static final String DEFAULT_PATTERN = "[ISO_OFFSET_DATE_TIME]";
/**
* Keyword used by {@link #setTimeZone(String)} to denote the system default time zone.
*/
public static final String DEFAULT_TIMEZONE_KEYWORD = "[DEFAULT]";
/**
* The pattern in which to format the timestamp.
*
* Possible values:
*
*
* - {@value #UNIX_TIMESTAMP_AS_NUMBER} - timestamp written as a JSON number value of the milliseconds since unix epoch
* - {@value #UNIX_TIMESTAMP_AS_STRING} - timestamp written as a JSON string value of the milliseconds since unix epoch
* [constant]
- timestamp written using the {@link DateTimeFormatter} constant specified by constant
(e.g. {@code [ISO_OFFSET_DATE_TIME]})
* - any other value - timestamp written by a {@link DateTimeFormatter} created from the pattern string specified
*
*/
private String pattern = DEFAULT_PATTERN;
/**
* The timezone for which to write the timestamp.
* Only applicable if the pattern is not {@value #UNIX_TIMESTAMP_AS_NUMBER} or {@value #UNIX_TIMESTAMP_AS_STRING}
*/
private TimeZone timeZone = TimeZone.getDefault();
/**
* Writes the timestamp to the JsonGenerator.
*/
private TimestampWriter timestampWriter;
/**
* Writes the timestamp to the JsonGenerator
*/
protected interface TimestampWriter {
void writeTo(JsonGenerator generator, String fieldName, Instant timestamp) throws IOException;
String getTimestampAsString(Instant timestamp);
}
/**
* Writes the timestamp to the JsonGenerator as a number of milliseconds since unix epoch.
*/
protected static class NumberTimestampWriter implements TimestampWriter {
@Override
public void writeTo(JsonGenerator generator, String fieldName, Instant timestamp) throws IOException {
JsonWritingUtils.writeNumberField(generator, fieldName, timestamp.toEpochMilli());
}
@Override
public String getTimestampAsString(Instant timestamp) {
return Long.toString(timestamp.toEpochMilli());
}
}
/**
* Writes the timestamp to the JsonGenerator as a string, converting the timestamp millis into a
* String using the supplied Function.
*/
protected static class StringFormatterWriter implements TimestampWriter {
private final Function provider;
StringFormatterWriter(Function provider) {
this.provider = Objects.requireNonNull(provider);
}
@Override
public void writeTo(JsonGenerator generator, String fieldName, Instant timestamp) throws IOException {
JsonWritingUtils.writeStringField(generator, fieldName, getTimestampAsString(timestamp));
}
@Override
public String getTimestampAsString(Instant timestamp) {
return provider.apply(timestamp);
}
static StringFormatterWriter with(DateTimeFormatter formatter) {
return new StringFormatterWriter(tstamp -> formatter.format(tstamp));
}
static StringFormatterWriter with(FastISOTimestampFormatter formatter) {
return new StringFormatterWriter(formatter::format);
}
static StringFormatterWriter with(Function formatter) {
return new StringFormatterWriter(formatter);
}
}
public AbstractFormattedTimestampJsonProvider() {
setFieldName(FIELD_TIMESTAMP);
updateTimestampWriter();
}
@Override
public void setFieldNames(FieldNames fieldNames) {
setFieldName(fieldNames.getTimestamp());
}
@Override
public void writeTo(JsonGenerator generator, Event event) throws IOException {
timestampWriter.writeTo(generator, getFieldName(), getTimestampAsInstant(event));
}
protected String getFormattedTimestamp(Event event) {
return timestampWriter.getTimestampAsString(getTimestampAsInstant(event));
}
protected abstract Instant getTimestampAsInstant(Event event);
/**
* Updates the {@link #timestampWriter} value based on the current pattern and timeZone.
*/
private void updateTimestampWriter() {
timestampWriter = createTimestampWriter();
}
private TimestampWriter createTimestampWriter() {
if (UNIX_TIMESTAMP_AS_NUMBER.equals(pattern)) {
return new NumberTimestampWriter();
}
if (UNIX_TIMESTAMP_AS_STRING.equals(pattern)) {
return StringFormatterWriter.with(tstamp -> Long.toString(tstamp.toEpochMilli()));
}
if (pattern.startsWith("[") && pattern.endsWith("]")) {
// Get the standard formatter by name...
//
String constant = pattern.substring(1, pattern.length() - 1);
// Use our fast FastISOTimestampFormatter if suitable...
//
ZoneId zone = timeZone.toZoneId();
if ("ISO_OFFSET_DATE_TIME".equals(constant)) {
return StringFormatterWriter.with(FastISOTimestampFormatter.isoOffsetDateTime(zone));
}
if ("ISO_ZONED_DATE_TIME".equals(constant)) {
return StringFormatterWriter.with(FastISOTimestampFormatter.isoZonedDateTime(zone));
}
if ("ISO_LOCAL_DATE_TIME".equals(constant)) {
return StringFormatterWriter.with(FastISOTimestampFormatter.isoLocalDateTime(zone));
}
if ("ISO_DATE_TIME".equals(constant)) {
return StringFormatterWriter.with(FastISOTimestampFormatter.isoDateTime(zone));
}
if ("ISO_INSTANT".equals(constant)) {
return StringFormatterWriter.with(FastISOTimestampFormatter.isoInstant(zone));
}
// Otherwise try one of the default formatters...
//
DateTimeFormatter formatter = getStandardDateTimeFormatter(constant).withZone(zone);
return StringFormatterWriter.with(formatter);
}
// Construct using a pattern
//
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern).withZone(timeZone.toZoneId());
return StringFormatterWriter.with(formatter);
}
private DateTimeFormatter getStandardDateTimeFormatter(String name) {
try {
Field field = DateTimeFormatter.class.getField(name);
if (Modifier.isStatic(field.getModifiers())
&& Modifier.isFinal(field.getModifiers())
&& field.getType().equals(DateTimeFormatter.class)) {
return (DateTimeFormatter) field.get(null);
}
else {
throw new IllegalArgumentException(String.format("Field named %s in %s is not a constant %s", name, DateTimeFormatter.class, DateTimeFormatter.class));
}
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException(String.format("Unable to get value of constant named %s in %s", name, DateTimeFormatter.class), e);
}
catch (NoSuchFieldException e) {
throw new IllegalArgumentException(String.format("No constant named %s found in %s", name, DateTimeFormatter.class), e);
}
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
updateTimestampWriter();
}
/**
* Get the time zone used to write the timestamp.
*
* @return the time zone used to write the timestamp
*/
public String getTimeZone() {
return timeZone.getID();
}
/**
* Set the timezone for which to write the timestamp.
* Only applicable if the pattern is not {@value #UNIX_TIMESTAMP_AS_NUMBER} or {@value #UNIX_TIMESTAMP_AS_STRING}.
*
* The value of the {@code timeZone} can be any string accepted by java's {@link TimeZone#getTimeZone(String)} method.
* For example "America/Los_Angeles" or "GMT+10".
*
*
Use a blank string, {@code null} or the value {@value #DEFAULT_TIMEZONE_KEYWORD} to use the default TimeZone of the system.
*
* @param timeZone the textual representation of the desired time zone
* @throws IllegalArgumentException if the input string is not a valid TimeZone representation
*/
public void setTimeZone(String timeZone) {
if (timeZone == null || timeZone.trim().isEmpty() || DEFAULT_TIMEZONE_KEYWORD.equalsIgnoreCase(timeZone)) {
this.timeZone = TimeZone.getDefault();
} else {
this.timeZone = TimeZoneUtils.parseTimeZone(timeZone);
}
updateTimestampWriter();
}
}