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

org.apache.logging.log4j.layout.template.json.resolver.TimestampResolver Maven / Gradle / Ivy

There is a newer version: 3.0.0-beta2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.logging.log4j.layout.template.json.resolver;

import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.layout.template.json.JsonTemplateLayoutDefaults;
import org.apache.logging.log4j.layout.template.json.util.InstantFormatter;
import org.apache.logging.log4j.layout.template.json.util.JsonWriter;

import java.util.Locale;
import java.util.TimeZone;

/**
 * Timestamp resolver.
 *
 * 

Configuration

* *
 * config        = [ patternConfig | epochConfig ]
 *
 * patternConfig = "pattern" -> ( [ format ] , [ timeZone ] , [ locale ] )
 * format        = "format" -> string
 * timeZone      = "timeZone" -> string
 * locale        = "locale" -> (
 *                     language                                   |
 *                   ( language , "_" , country )                 |
 *                   ( language , "_" , country , "_" , variant )
 *                 )
 *
 * epochConfig   = "epoch" -> ( unit , [ rounded ] )
 * unit          = "unit" -> (
 *                     "nanos"         |
 *                     "millis"        |
 *                     "secs"          |
 *                     "millis.nanos"  |
 *                     "secs.nanos"    |
 *                  )
 * rounded       = "rounded" -> boolean
 * 
* * If no configuration options are provided, pattern-config is * employed. There {@link * JsonTemplateLayoutDefaults#getTimestampFormatPattern()}, {@link * JsonTemplateLayoutDefaults#getTimeZone()}, {@link * JsonTemplateLayoutDefaults#getLocale()} are used as defaults for * pattern, timeZone, and locale, respectively. * * In epoch-config, millis.nanos, secs.nanos stand * for the fractional component in nanoseconds. * *

Examples

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
ConfigurationOutput
 * {
 *   "$resolver": "timestamp"
 * }
 *     
 * 2020-02-07T13:38:47.098+02:00
 *     
 * {
 *   "$resolver": "timestamp",
 *   "pattern": {
 *     "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
 *     "timeZone": "UTC",
 *     "locale": "en_US"
 *   }
 * }
 *     
 * 2020-02-07T13:38:47.098Z
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "secs"
 *   }
 * }
 *     
 * 1581082727.982123456
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "secs",
 *     "rounded": true
 *   }
 * }
 *     
 * 1581082727
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "secs.nanos"
 *   }
 * }
 *     
 *            982123456
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "millis"
 *   }
 * }
 *     
 * 1581082727982.123456
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "millis",
 *     "rounded": true
 *   }
 * }
 *     
 * 1581082727982
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "millis.nanos"
 *   }
 * }
 *     
 *              123456
 *     
 * {
 *   "$resolver": "timestamp",
 *   "epoch": {
 *     "unit": "nanos"
 *   }
 * }
 *     
 * 1581082727982123456
 *     
*/ public final class TimestampResolver implements EventResolver { private final EventResolver internalResolver; TimestampResolver(final TemplateResolverConfig config) { this.internalResolver = createResolver(config); } private static EventResolver createResolver( final TemplateResolverConfig config) { final boolean patternProvided = config.exists("pattern"); final boolean epochProvided = config.exists("epoch"); if (patternProvided && epochProvided) { throw new IllegalArgumentException( "conflicting configuration options are provided: " + config); } return epochProvided ? createEpochResolver(config) : createPatternResolver(config); } private static final class PatternResolverContext { private final InstantFormatter formatter; private final StringBuilder lastFormattedInstantBuffer = new StringBuilder(); private final MutableInstant lastFormattedInstant = new MutableInstant(); private PatternResolverContext( final String pattern, final TimeZone timeZone, final Locale locale) { this.formatter = InstantFormatter .newBuilder() .setPattern(pattern) .setTimeZone(timeZone) .setLocale(locale) .build(); lastFormattedInstant.initFromEpochSecond(-1, 0); } private static PatternResolverContext fromConfig( final TemplateResolverConfig config) { final String pattern = readPattern(config); final TimeZone timeZone = readTimeZone(config); final Locale locale = config.getLocale(new String[]{"pattern", "locale"}); return new PatternResolverContext(pattern, timeZone, locale); } private static String readPattern(final TemplateResolverConfig config) { final String format = config.getString(new String[]{"pattern", "format"}); return format != null ? format : JsonTemplateLayoutDefaults.getTimestampFormatPattern(); } private static TimeZone readTimeZone(final TemplateResolverConfig config) { final String timeZoneId = config.getString(new String[]{"pattern", "timeZone"}); if (timeZoneId == null) { return JsonTemplateLayoutDefaults.getTimeZone(); } boolean found = false; for (final String availableTimeZone : TimeZone.getAvailableIDs()) { if (availableTimeZone.equalsIgnoreCase(timeZoneId)) { found = true; break; } } if (!found) { throw new IllegalArgumentException( "invalid timestamp time zone: " + config); } return TimeZone.getTimeZone(timeZoneId); } } private static final class PatternResolver implements EventResolver { private final PatternResolverContext patternResolverContext; private PatternResolver(final PatternResolverContext patternResolverContext) { this.patternResolverContext = patternResolverContext; } @Override public synchronized void resolve( final LogEvent logEvent, final JsonWriter jsonWriter) { // Format timestamp if it doesn't match the last cached one. final boolean instantMatching = patternResolverContext.formatter.isInstantMatching( patternResolverContext.lastFormattedInstant, logEvent.getInstant()); if (!instantMatching) { // Format the timestamp. patternResolverContext.lastFormattedInstantBuffer.setLength(0); patternResolverContext.lastFormattedInstant.initFrom(logEvent.getInstant()); patternResolverContext.formatter.format( patternResolverContext.lastFormattedInstant, patternResolverContext.lastFormattedInstantBuffer); // Write the formatted timestamp. final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder(); final int startIndex = jsonWriterStringBuilder.length(); jsonWriter.writeString(patternResolverContext.lastFormattedInstantBuffer); // Cache the written value. patternResolverContext.lastFormattedInstantBuffer.setLength(0); patternResolverContext.lastFormattedInstantBuffer.append( jsonWriterStringBuilder, startIndex, jsonWriterStringBuilder.length()); } // Write the cached formatted timestamp. else { jsonWriter.writeRawString( patternResolverContext.lastFormattedInstantBuffer); } } } private static EventResolver createPatternResolver( final TemplateResolverConfig config) { final PatternResolverContext patternResolverContext = PatternResolverContext.fromConfig(config); return new PatternResolver(patternResolverContext); } private static EventResolver createEpochResolver( final TemplateResolverConfig config) { final String unit = config.getString(new String[]{"epoch", "unit"}); final Boolean rounded = config.getBoolean(new String[]{"epoch", "rounded"}); if ("nanos".equals(unit) && !Boolean.FALSE.equals(rounded)) { return EPOCH_NANOS_RESOLVER; } else if ("millis".equals(unit)) { return !Boolean.TRUE.equals(rounded) ? EPOCH_MILLIS_RESOLVER : EPOCH_MILLIS_ROUNDED_RESOLVER; } else if ("millis.nanos".equals(unit) && rounded == null) { return EPOCH_MILLIS_NANOS_RESOLVER; } else if ("secs".equals(unit)) { return !Boolean.TRUE.equals(rounded) ? EPOCH_SECS_RESOLVER : EPOCH_SECS_ROUNDED_RESOLVER; } else if ("secs.nanos".equals(unit) && rounded == null) { return EPOCH_SECS_NANOS_RESOLVER; } throw new IllegalArgumentException( "invalid epoch configuration: " + config); } private static final class EpochResolutionRecord { private static final int MAX_LONG_LENGTH = String.valueOf(Long.MAX_VALUE).length(); private final MutableInstant instant = new MutableInstant(); private final char[] resolution = new char[ /* integral: */ MAX_LONG_LENGTH + /* dot: */ 1 + /* fractional: */ MAX_LONG_LENGTH]; private int resolutionLength; private EpochResolutionRecord() { instant.initFromEpochSecond(-1, 0); } } private static abstract class EpochResolver implements EventResolver { private final EpochResolutionRecord resolutionRecord = new EpochResolutionRecord(); @Override public synchronized void resolve( final LogEvent logEvent, final JsonWriter jsonWriter) { final Instant logEventInstant = logEvent.getInstant(); if (logEventInstant.equals(resolutionRecord.instant)) { jsonWriter.writeRawString( resolutionRecord.resolution, 0, resolutionRecord.resolutionLength); } else { resolutionRecord.instant.initFrom(logEventInstant); final StringBuilder stringBuilder = jsonWriter.getStringBuilder(); final int startIndex = stringBuilder.length(); resolve(logEventInstant, jsonWriter); resolutionRecord.resolutionLength = stringBuilder.length() - startIndex; stringBuilder.getChars( startIndex, stringBuilder.length(), resolutionRecord.resolution, 0); } } abstract void resolve(Instant logEventInstant, JsonWriter jsonWriter); } private static final EventResolver EPOCH_NANOS_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { final long nanos = epochNanos(logEventInstant); jsonWriter.writeNumber(nanos); } }; private static final EventResolver EPOCH_MILLIS_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder(); final long nanos = epochNanos(logEventInstant); jsonWriterStringBuilder.append(nanos); jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 6, '.'); } }; private static final EventResolver EPOCH_MILLIS_ROUNDED_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { jsonWriter.writeNumber(logEventInstant.getEpochMillisecond()); } }; private static final EventResolver EPOCH_MILLIS_NANOS_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { final long nanos = epochNanos(logEventInstant); final long fraction = nanos % 1_000_000L; jsonWriter.writeNumber(fraction); } }; private static final EventResolver EPOCH_SECS_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder(); final long nanos = epochNanos(logEventInstant); jsonWriterStringBuilder.append(nanos); jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 9, '.'); } }; private static final EventResolver EPOCH_SECS_ROUNDED_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { jsonWriter.writeNumber(logEventInstant.getEpochSecond()); } }; private static final EventResolver EPOCH_SECS_NANOS_RESOLVER = new EpochResolver() { @Override void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) { jsonWriter.writeNumber(logEventInstant.getNanoOfSecond()); } }; private static long epochNanos(final Instant instant) { final long nanos = Math.multiplyExact(1_000_000_000L, instant.getEpochSecond()); return Math.addExact(nanos, instant.getNanoOfSecond()); } static String getName() { return "timestamp"; } @Override public void resolve( final LogEvent logEvent, final JsonWriter jsonWriter) { internalResolver.resolve(logEvent, jsonWriter); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy