You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by vy...@apache.org on 2021/07/07 13:04:24 UTC
[logging-log4j2] branch release-2.x updated: LOG4J2-3067 Add
CounterResolver.
This is an automated email from the ASF dual-hosted git repository.
vy pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/release-2.x by this push:
new d9e7ed4 LOG4J2-3067 Add CounterResolver.
d9e7ed4 is described below
commit d9e7ed452130895a615cf50e0904b05495d941a1
Author: Volkan Yazici <vo...@gmail.com>
AuthorDate: Wed Jul 7 15:04:10 2021 +0200
LOG4J2-3067 Add CounterResolver.
---
.../json/resolver/CaseConverterResolver.java | 1 -
.../template/json/resolver/CounterResolver.java | 169 +++++++++++++++++++++
.../json/resolver/CounterResolverFactory.java | 50 ++++++
.../json/resolver/CounterResolverTest.java | 109 +++++++++++++
src/changes/changes.xml | 3 +
.../asciidoc/manual/json-template-layout.adoc.vm | 59 +++++++
6 files changed, 390 insertions(+), 1 deletion(-)
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
index 51372e8..0ac69f7 100644
--- a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CaseConverterResolver.java
@@ -44,7 +44,6 @@ import java.util.function.Function;
* "replace"
* )
* replacement = "replacement" -> JSON
- *
* </pre>
*
* {@code input} can be any available template value; e.g., a JSON literal,
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
new file mode 100644
index 0000000..9071277
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolver.java
@@ -0,0 +1,169 @@
+/*
+ * 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.layout.template.json.util.JsonWriter;
+
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.function.Consumer;
+
+/**
+ * Resolves a number from an internal counter.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = [ start ] , [ overflow ]
+ * start = "start" -> number
+ * overflow = "overflow" -> boolean
+ * </pre>
+ *
+ * Unless provided, <tt>start</tt> and <tt>overflow</tt> are respectively set to
+ * zero and <tt>true</tt> by default.
+ * <p>
+ * When <tt>overflow</tt> is enabled, the internal counter is created using a
+ * <tt>long</tt>, which is subject to overflow while incrementing, though
+ * garbage-free. Otherwise, a {@link BigInteger} is used, which does not
+ * overflow, but incurs allocation costs.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolves a sequence of numbers starting from 0. Once {@link Long#MAX_VALUE}
+ * is reached, counter overflows to {@link Long#MIN_VALUE}.
+ *
+ * <pre>
+ * {
+ * "$resolver": "counter"
+ * }
+ * </pre>
+ *
+ * Resolves a sequence of numbers starting from 1000. Once {@link Long#MAX_VALUE}
+ * is reached, counter overflows to {@link Long#MIN_VALUE}.
+ *
+ * <pre>
+ * {
+ * "$resolver": "counter",
+ * "start": 1000
+ * }
+ * </pre>
+ *
+ * Resolves a sequence of numbers starting from 0 and keeps on doing as long as
+ * JVM heap allows.
+ *
+ * <pre>
+ * {
+ * "$resolver": "counter",
+ * "overflow": false
+ * }
+ * </pre>
+ */
+public class CounterResolver implements EventResolver {
+
+ private final Consumer<JsonWriter> delegate;
+
+ public CounterResolver(final TemplateResolverConfig config) {
+ final BigInteger start = readStart(config);
+ final boolean overflow = config.getBoolean("overflow", true);
+ this.delegate = overflow
+ ? createLongResolver(start)
+ : createBigIntegerResolver(start);
+ }
+
+ private static BigInteger readStart(final TemplateResolverConfig config) {
+ final Object start = config.getObject("start", Object.class);
+ if (start == null) {
+ return BigInteger.ZERO;
+ } else if (start instanceof Short || start instanceof Integer || start instanceof Long) {
+ return BigInteger.valueOf(((Number) start).longValue());
+ } else if (start instanceof BigInteger) {
+ return (BigInteger) start;
+ } else {
+ final Class<?> clazz = start.getClass();
+ final String message = String.format(
+ "could not read start of type %s: %s", clazz, config);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ private static Consumer<JsonWriter> createLongResolver(final BigInteger start) {
+ final long effectiveStart = start.longValue();
+ final AtomicLong counter = new AtomicLong(effectiveStart);
+ return (jsonWriter) -> {
+ final long number = counter.getAndIncrement();
+ jsonWriter.writeNumber(number);
+ };
+ }
+
+ private static Consumer<JsonWriter> createBigIntegerResolver(final BigInteger start) {
+ final AtomicBigInteger counter = new AtomicBigInteger(start);
+ return jsonWriter -> {
+ final BigInteger number = counter.getAndIncrement();
+ jsonWriter.writeNumber(number);
+ };
+ }
+
+ private static final class AtomicBigInteger {
+
+ private final AtomicReference<BigInteger> lastNumber;
+
+ private AtomicBigInteger(final BigInteger start) {
+ this.lastNumber = new AtomicReference<>(start);
+ }
+
+ private BigInteger getAndIncrement() {
+ BigInteger prevNumber;
+ BigInteger nextNumber;
+ do {
+ prevNumber = lastNumber.get();
+ nextNumber = prevNumber.add(BigInteger.ONE);
+ } while (!compareAndSetWithBackOff(prevNumber, nextNumber));
+ return prevNumber;
+ }
+
+ /**
+ * {@link AtomicReference#compareAndSet(Object, Object)} shortcut with a
+ * constant back off. This technique was originally described in
+ * <a href="https://arxiv.org/abs/1305.5800">Lightweight Contention
+ * Management for Efficient Compare-and-Swap Operations</a> and showed
+ * great results in benchmarks.
+ */
+ private boolean compareAndSetWithBackOff(
+ final BigInteger prevNumber,
+ final BigInteger nextNumber) {
+ if (lastNumber.compareAndSet(prevNumber, nextNumber)) {
+ return true;
+ }
+ LockSupport.parkNanos(1); // back-off
+ return false;
+ }
+
+ }
+
+ static String getName() {
+ return "counter";
+ }
+
+ @Override
+ public void resolve(final LogEvent ignored, final JsonWriter jsonWriter) {
+ delegate.accept(jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java
new file mode 100644
index 0000000..eb1daec
--- /dev/null
+++ b/log4j-layout-template-json/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+
+/**
+ * {@link CounterResolver} factory.
+ */
+@Plugin(name = "CounterResolverFactory", category = TemplateResolverFactory.CATEGORY)
+public final class CounterResolverFactory implements EventResolverFactory {
+
+ private static final CounterResolverFactory INSTANCE =
+ new CounterResolverFactory();
+
+ private CounterResolverFactory() {}
+
+ @PluginFactory
+ public static CounterResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return CounterResolver.getName();
+ }
+
+ @Override
+ public CounterResolver create(
+ final EventResolverContext ignored,
+ final TemplateResolverConfig config) {
+ return new CounterResolver(config);
+ }
+
+}
diff --git a/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java
new file mode 100644
index 0000000..25f02f1
--- /dev/null
+++ b/log4j-layout-template-json/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/CounterResolverTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.impl.Log4jLogEvent;
+import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout;
+import org.apache.logging.log4j.layout.template.json.util.JsonReader;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+
+import static org.apache.logging.log4j.layout.template.json.TestHelpers.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CounterResolverTest {
+
+ @Test
+ void no_arg_setup_should_start_from_zero() {
+ final String eventTemplate = writeJson(asMap("$resolver", "counter"));
+ verify(eventTemplate, 0, 1);
+ }
+
+ @Test
+ void positive_start_should_work() {
+ final String eventTemplate = writeJson(asMap(
+ "$resolver", "counter",
+ "start", 3));
+ verify(eventTemplate, 3, 4);
+ }
+
+ @Test
+ void negative_start_should_work() {
+ final String eventTemplate = writeJson(asMap(
+ "$resolver", "counter",
+ "start", -3));
+ verify(eventTemplate, -3, -2);
+ }
+
+ @Test
+ void min_long_should_work_when_overflow_enabled() {
+ final String eventTemplate = writeJson(asMap(
+ "$resolver", "counter",
+ "start", Long.MIN_VALUE));
+ verify(eventTemplate, Long.MIN_VALUE, Long.MIN_VALUE + 1L);
+ }
+
+ @Test
+ void max_long_should_work_when_overflow_enabled() {
+ final String eventTemplate = writeJson(asMap(
+ "$resolver", "counter",
+ "start", Long.MAX_VALUE));
+ verify(eventTemplate, Long.MAX_VALUE, Long.MIN_VALUE);
+ }
+
+ @Test
+ void max_long_should_work_when_overflow_disabled() {
+ final String eventTemplate = writeJson(asMap(
+ "$resolver", "counter",
+ "start", Long.MAX_VALUE,
+ "overflow", false));
+ verify(
+ eventTemplate,
+ Long.MAX_VALUE,
+ BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE));
+ }
+
+ private static void verify(
+ final String eventTemplate,
+ final Number expectedNumber1,
+ final Number expectedNumber2) {
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event.
+ final LogEvent logEvent = Log4jLogEvent.newBuilder().build();
+
+ // Check the 1st serialized event.
+ final String serializedLogEvent1 = layout.toSerializable(logEvent);
+ final Object deserializedLogEvent1 = JsonReader.read(serializedLogEvent1);
+ assertThat(deserializedLogEvent1).isEqualTo(expectedNumber1);
+
+ // Check the 2nd serialized event.
+ final String serializedLogEvent2 = layout.toSerializable(logEvent);
+ final Object deserializedLogEvent2 = JsonReader.read(serializedLogEvent2);
+ assertThat(deserializedLogEvent2).isEqualTo(expectedNumber2);
+
+ }
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index bbddae2..dd1ecf0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -31,6 +31,9 @@
-->
<release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
<!-- ADDS -->
+ <action issue="LOG4J2-3067" dev="vy" type="add">
+ Add CounterResolver to JsonTemplateLayout.
+ </action>
<action issue="LOG4J2-3074" dev="vy" type="add">
Add replacement parameter to ReadOnlyStringMapResolver.
</action>
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc.vm b/src/site/asciidoc/manual/json-template-layout.adoc.vm
index 768e222..77c2747 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc.vm
+++ b/src/site/asciidoc/manual/json-template-layout.adoc.vm
@@ -454,6 +454,62 @@ similar to the following:
The complete list of available event template resolvers are provided below in
detail.
+[#event-template-resolver-counter]
+===== `counter`
+
+[source]
+----
+config = [ start ] , [ overflow ]
+start = "start" -> number
+overflow = "overflow" -> boolean
+----
+
+Resolves a number from an internal counter.
+
+Unless provided, `start` and `overflow` are respectively set to zero and `true`
+by default.
+
+[WARNING]
+====
+When `overflow` is enabled, the internal counter is created using a `long`,
+which is subject to overflow while incrementing, though garbage-free. Otherwise,
+a `BigInteger` is used, which does not overflow, but incurs allocation costs.
+====
+
+====== Examples
+
+Resolves a sequence of numbers starting from 0. Once `Long.MAX_VALUE` is
+reached, counter overflows to `Long.MIN_VALUE`.
+
+[source,json]
+----
+{
+ "$resolver": "counter"
+}
+----
+
+Resolves a sequence of numbers starting from 1000. Once `Long.MAX_VALUE` is
+reached, counter overflows to `Long.MIN_VALUE`.
+
+[source,json]
+----
+{
+ "$resolver": "counter",
+ "start": 1000
+}
+----
+
+Resolves a sequence of numbers starting from 0 and keeps on doing as long as
+JVM heap allows.
+
+[source,json]
+----
+{
+ "$resolver": "counter",
+ "overflow": false
+}
+----
+
[#event-template-resolver-caseConverter]
===== `caseConverter`
@@ -501,8 +557,11 @@ is always expected to be of type string, using non-string ``replacement``s or
`pass` in `errorHandlingStrategy` might result in type incompatibility issues at
the storage level.
+[WARNING]
+====
Unless the input value is ``pass``ed intact or ``replace``d, case conversion is
not garbage-free.
+====
====== Examples