Skip to content

Commit d9eb350

Browse files
committed
Move InstantFormatter to log4j-core
1 parent f7c26cd commit d9eb350

File tree

23 files changed

+1824
-665
lines changed

23 files changed

+1824
-665
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.core.util.internal;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
import java.util.Arrays;
22+
import java.util.stream.Stream;
23+
import org.apache.logging.log4j.core.time.Instant;
24+
import org.apache.logging.log4j.core.time.MutableInstant;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.MethodSource;
27+
28+
class InstantNumberFormatterTest {
29+
30+
@ParameterizedTest
31+
@MethodSource("testCases")
32+
void should_produce_expected_output(
33+
final InstantFormatter formatter, final Instant instant, final String expectedOutput) {
34+
final String actualOutput = formatter.format(instant);
35+
assertThat(actualOutput).isEqualTo(expectedOutput);
36+
}
37+
38+
static Stream<Object[]> testCases() {
39+
return Stream.concat(
40+
testCases(1581082727, 982123456, new Object[][] {
41+
{InstantNumberFormatter.EPOCH_SECONDS, "1581082727.982123456"},
42+
{InstantNumberFormatter.EPOCH_SECONDS_ROUNDED, "1581082727"},
43+
{InstantNumberFormatter.EPOCH_SECONDS_NANOS, "982123456"},
44+
{InstantNumberFormatter.EPOCH_MILLIS, "1581082727982.123456"},
45+
{InstantNumberFormatter.EPOCH_MILLIS_ROUNDED, "1581082727982"},
46+
{InstantNumberFormatter.EPOCH_MILLIS_NANOS, "123456"},
47+
{InstantNumberFormatter.EPOCH_NANOS, "1581082727982123456"}
48+
}),
49+
testCases(1591177590, 5000001, new Object[][] {
50+
{InstantNumberFormatter.EPOCH_SECONDS, "1591177590.005000001"},
51+
{InstantNumberFormatter.EPOCH_SECONDS_ROUNDED, "1591177590"},
52+
{InstantNumberFormatter.EPOCH_SECONDS_NANOS, "5000001"},
53+
{InstantNumberFormatter.EPOCH_MILLIS, "1591177590005.000001"},
54+
{InstantNumberFormatter.EPOCH_MILLIS_ROUNDED, "1591177590005"},
55+
{InstantNumberFormatter.EPOCH_MILLIS_NANOS, "1"},
56+
{InstantNumberFormatter.EPOCH_NANOS, "1591177590005000001"}
57+
}));
58+
}
59+
60+
private static Stream<Object[]> testCases(
61+
long epochSeconds, int epochSecondsNanos, Object[][] formatterAndOutputPairs) {
62+
return Arrays.stream(formatterAndOutputPairs).map(formatterAndOutputPair -> {
63+
final InstantFormatter formatter = (InstantFormatter) formatterAndOutputPair[0];
64+
final String expectedOutput = (String) formatterAndOutputPair[1];
65+
final MutableInstant instant = new MutableInstant();
66+
instant.initFromEpochSecond(epochSeconds, epochSecondsNanos);
67+
return new Object[] {formatter, instant, expectedOutput};
68+
});
69+
}
70+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.log4j.core.util.internal;
18+
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
21+
22+
import java.time.temporal.ChronoUnit;
23+
import java.util.Locale;
24+
import java.util.TimeZone;
25+
import org.apache.logging.log4j.Level;
26+
import org.apache.logging.log4j.core.time.MutableInstant;
27+
import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
28+
import org.apache.logging.log4j.core.util.datetime.FixedDateFormat;
29+
import org.apache.logging.log4j.test.ListStatusListener;
30+
import org.apache.logging.log4j.test.junit.UsingStatusListener;
31+
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.params.ParameterizedTest;
33+
import org.junit.jupiter.params.provider.CsvSource;
34+
import org.junit.jupiter.params.provider.ValueSource;
35+
36+
class InstantPatternDynamicFormatterTest {
37+
38+
@ParameterizedTest
39+
@CsvSource({
40+
"yyyy-MM-dd'T'HH:mm:ss.SSS" + ",FixedDateFormat",
41+
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + ",FastDateFormat",
42+
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'" + ",DateTimeFormatter"
43+
})
44+
void all_internal_implementations_should_be_used(final String pattern, final String className) {
45+
final InstantPatternDynamicFormatter formatter =
46+
new InstantPatternDynamicFormatter(pattern, Locale.getDefault(), TimeZone.getDefault());
47+
assertThat(formatter.getInternalImplementationClass())
48+
.asString()
49+
.describedAs("pattern=`%s`", pattern)
50+
.endsWith("." + className);
51+
}
52+
53+
/**
54+
* Reproduces <a href="https://issues.apache.org/jira/browse/LOG4J2-3075">LOG4J2-3075</a>.
55+
*/
56+
@Test
57+
void nanoseconds_should_be_formatted() {
58+
final InstantFormatter formatter = new InstantPatternDynamicFormatter(
59+
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'", Locale.getDefault(), TimeZone.getTimeZone("UTC"));
60+
final MutableInstant instant = new MutableInstant();
61+
instant.initFromEpochSecond(0, 123_456_789);
62+
assertThat(formatter.format(instant)).isEqualTo("1970-01-01T00:00:00.123456789Z");
63+
}
64+
65+
/**
66+
* Reproduces <a href="https://issues.apache.org/jira/browse/LOG4J2-3614">LOG4J2-3614</a>.
67+
*/
68+
@Test
69+
void FastDateFormat_failures_should_be_handled() {
70+
71+
// Define a pattern causing `FastDateFormat` to fail.
72+
final String pattern = "ss.nnnnnnnnn";
73+
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
74+
final Locale locale = Locale.US;
75+
76+
// Assert that the pattern is not supported by `FixedDateFormat`.
77+
final FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(pattern, timeZone.getID());
78+
assertThat(fixedDateFormat).isNull();
79+
80+
// Assert that the pattern indeed causes a `FastDateFormat` failure.
81+
assertThatThrownBy(() -> FastDateFormat.getInstance(pattern, timeZone, locale))
82+
.isInstanceOf(IllegalArgumentException.class)
83+
.hasMessage("Illegal pattern component: nnnnnnnnn");
84+
85+
// Assert that `InstantFormatter` falls back to `DateTimeFormatter`.
86+
final InstantPatternDynamicFormatter formatter =
87+
new InstantPatternDynamicFormatter(pattern, Locale.getDefault(), timeZone);
88+
assertThat(formatter.getInternalImplementationClass()).asString().endsWith(".DateTimeFormatter");
89+
90+
// Assert that formatting works.
91+
final MutableInstant instant = new MutableInstant();
92+
instant.initFromEpochSecond(0, 123_456_789);
93+
assertThat(formatter.format(instant)).isEqualTo("00.123456789");
94+
}
95+
96+
/**
97+
* Reproduces <a href="https://github.com/apache/logging-log4j2/issues/1418">#1418</a>.
98+
*/
99+
@Test
100+
@UsingStatusListener
101+
void FixedFormatter_should_allocate_large_enough_buffer(final ListStatusListener listener) {
102+
final String pattern = "yyyy-MM-dd'T'HH:mm:ss,SSSXXX";
103+
final TimeZone timeZone = TimeZone.getTimeZone("America/Chicago");
104+
final Locale locale = Locale.ENGLISH;
105+
final InstantPatternDynamicFormatter formatter = new InstantPatternDynamicFormatter(pattern, locale, timeZone);
106+
107+
// On this pattern the `FixedFormatter` used a buffer shorter than necessary,
108+
// which caused exceptions and warnings.
109+
assertThat(listener.findStatusData(Level.WARN)).hasSize(0);
110+
assertThat(formatter.getInternalImplementationClass()).asString().endsWith(".FixedDateFormat");
111+
}
112+
113+
@ParameterizedTest
114+
@ValueSource(
115+
strings = {
116+
// Basics
117+
"S",
118+
"SSSS",
119+
"SSSSS",
120+
"SSSSSS",
121+
"SSSSSSS",
122+
"SSSSSSSSS",
123+
"n",
124+
"nn",
125+
"N",
126+
"NN",
127+
// Mixed with other stuff
128+
"yyyy-MM-dd HH:mm:ss,SSSSSS",
129+
"yyyy-MM-dd HH:mm:ss,SSSSSS",
130+
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS",
131+
"yyyy-MM-dd'T'HH:mm:ss.SXXX"
132+
})
133+
void should_recognize_patterns_of_nano_precision(final String pattern) {
134+
assertPatternPrecision(pattern, ChronoUnit.NANOS);
135+
}
136+
137+
@ParameterizedTest
138+
@ValueSource(
139+
strings = {
140+
// Basics
141+
"SS",
142+
"SSS",
143+
"A",
144+
"AA",
145+
// Mixed with other stuff
146+
"yyyy-MM-dd HH:mm:ss,SS",
147+
"yyyy-MM-dd HH:mm:ss,SSS",
148+
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
149+
// Single-quoted text containing nanosecond directives
150+
"yyyy-MM-dd'S'HH:mm:ss.SSSXXX",
151+
"yyyy-MM-dd'n'HH:mm:ss.SSSXXX",
152+
"yyyy-MM-dd'N'HH:mm:ss.SSSXXX",
153+
})
154+
void should_recognize_patterns_of_milli_precision(final String pattern) {
155+
assertPatternPrecision(pattern, ChronoUnit.MILLIS);
156+
}
157+
158+
@ParameterizedTest
159+
@ValueSource(
160+
strings = {
161+
// Basics
162+
"s",
163+
"ss",
164+
// Mixed with other stuff
165+
"yyyy-MM-dd HH:mm:ss",
166+
"yyyy-MM-dd HH:mm:ss",
167+
"yyyy-MM-dd'T'HH:mm:ss",
168+
"HH:mm",
169+
"yyyy-MM-dd'T'",
170+
// Single-quoted text containing nanosecond and millisecond directives
171+
"yyyy-MM-dd'S'HH:mm:ss",
172+
"yyyy-MM-dd'n'HH:mm:ss",
173+
"yyyy-MM-dd'N'HH:mm:ss",
174+
"yyyy-MM-dd'A'HH:mm:ss"
175+
})
176+
void should_recognize_patterns_of_second_precision(final String pattern) {
177+
assertPatternPrecision(pattern, ChronoUnit.SECONDS);
178+
}
179+
180+
private static void assertPatternPrecision(final String pattern, final ChronoUnit expectedPrecision) {
181+
final ChronoUnit actualPrecision = InstantPatternDynamicFormatter.patternPrecision(pattern);
182+
assertThat(actualPrecision).as("pattern=`%s`", pattern).isEqualTo(expectedPrecision);
183+
}
184+
}

0 commit comments

Comments
 (0)