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

co.elastic.logging.TimestampSerializer Maven / Gradle / Ivy

There is a newer version: 1.36.0
Show newest version
/*-
 * #%L
 * Java ECS logging
 * %%
 * Copyright (C) 2019 - 2020 Elastic and contributors
 * %%
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. 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.
 * #L%
 */
package co.elastic.logging;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * This class serializes an epoch timestamp in milliseconds to a ISO 8601 date time sting,
 * for example {@code 1970-01-01T00:00:00.000Z}
 * 

* The main advantage of this class is that is able to serialize the timestamp in a garbage free way, * i.e. without object allocations and that it is faster than {@link java.text.DateFormat#format(Date)}. *

*

* The most complex part when formatting a ISO date is to determine the actual year, * month and date as you have to account for leap years. * Leveraging the fact that for a whole day this stays the same * and that logging only requires to serialize the current timestamp and not arbitrary ones, * we offload this task to {@link java.text.DateFormat#format(Date)} and cache the result. * So we only have to serialize the time part of the ISO timestamp which is easy * as a day has exactly {@code 1000 * 60 * 60 * 24} milliseconds. * Also, we don't have to worry about leap seconds when dealing with the epoch timestamp. *

*

* This class is thread safe. *

*/ class TimestampSerializer { private static final long MILLIS_PER_SECOND = 1000; private static final long MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60; private static final long MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60; private static final long MILLIS_PER_DAY = MILLIS_PER_HOUR * 24; private static final char TIME_SEPARATOR = 'T'; private static final char TIME_ZONE_SEPARATOR = 'Z'; private static final char COLON = ':'; private static final char DOT = '.'; private static final char ZERO = '0'; private volatile CachedDate cachedDate = new CachedDate(System.currentTimeMillis()); void serializeEpochTimestampAsIsoDateTime(StringBuilder builder, long epochTimestamp) { CachedDate cachedDateLocal = cachedDate; if (cachedDateLocal == null || !cachedDateLocal.isDateCached(epochTimestamp)) { cachedDate = cachedDateLocal = new CachedDate(epochTimestamp); } builder.append(cachedDateLocal.getCachedDateIso()); builder.append(TIME_SEPARATOR); // hours long remainder = epochTimestamp % MILLIS_PER_DAY; serializeWithLeadingZero(builder, remainder / MILLIS_PER_HOUR, 2); builder.append(COLON); // minutes remainder %= MILLIS_PER_HOUR; serializeWithLeadingZero(builder, remainder / MILLIS_PER_MINUTE, 2); builder.append(COLON); // seconds remainder %= MILLIS_PER_MINUTE; serializeWithLeadingZero(builder, remainder / MILLIS_PER_SECOND, 2); builder.append(DOT); // milliseconds remainder %= MILLIS_PER_SECOND; serializeWithLeadingZero(builder, remainder, 3); builder.append(TIME_ZONE_SEPARATOR); } private void serializeWithLeadingZero(StringBuilder builder, long value, int minLength) { for (int i = minLength - 1; i > 0; i--) { if (value < Math.pow(10, i)) { builder.append(ZERO); } } builder.append(value); } private static class CachedDate { private final String cachedDateIso; private final long startOfCachedDate; private final long endOfCachedDate; private CachedDate(long epochTimestamp) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); cachedDateIso = dateFormat.format(new Date(epochTimestamp)); startOfCachedDate = atStartOfDay(epochTimestamp); endOfCachedDate = atEndOfDay(epochTimestamp); } private static long atStartOfDay(long epochTimestamp) { return epochTimestamp - epochTimestamp % MILLIS_PER_DAY; } private static long atEndOfDay(long epochTimestamp) { return atStartOfDay(epochTimestamp) + MILLIS_PER_DAY - 1; } private boolean isDateCached(long epochTimestamp) { return epochTimestamp >= startOfCachedDate && epochTimestamp <= endOfCachedDate; } public String getCachedDateIso() { return cachedDateIso; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy