From 0e2443d856a370f688663a14c1f1b70c80c0b1b2 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 22 Nov 2022 18:05:03 +0100 Subject: [PATCH 01/74] logging module independent from implementation, based on slf4j --- powertools-logging/pom.xml | 23 +- .../lambda/powertools/logging/Logging.java | 4 +- .../powertools/logging/LoggingUtils.java | 16 +- .../internal/AbstractJacksonLayoutCopy.java | 519 ------------------ .../logging/internal/JacksonFactoryCopy.java | 133 ----- .../logging/internal/LambdaJsonLayout.java | 246 --------- .../logging/internal/LambdaLoggingAspect.java | 59 +- .../logging/internal/LoggingManager.java | 30 + ...ields.java => PowertoolsLoggedFields.java} | 30 +- .../logging/internal/PowertoolsResolver.java | 66 --- .../src/main/resources/LambdaEcsLayout.json | 52 -- .../src/main/resources/LambdaJsonLayout.json | 89 --- .../resources/log4j2.component.properties | 2 - .../core/layout/LambdaJsonLayoutTest.java | 144 ----- .../java/org/slf4j/test/OutputChoice.java | 79 +++ .../test/java/org/slf4j/test/TestLogger.java | 467 ++++++++++++++++ .../slf4j/test/TestLoggerConfiguration.java | 215 ++++++++ .../org/slf4j/test/TestLoggerFactory.java | 79 +++ .../org/slf4j/test/TestServiceProvider.java | 76 +++ .../powertools/logging/LoggingUtilsTest.java | 36 +- ...LogToolApiGatewayHttpApiCorrelationId.java | 6 +- ...LogToolApiGatewayRestApiCorrelationId.java | 6 +- .../logging/handlers/PowerLogToolEnabled.java | 6 +- .../handlers/PowerLogToolSamplingEnabled.java | 6 +- .../handlers/PowerToolLogEventEnabled.java | 28 - .../PowerToolLogEventEnabledForStream.java | 34 -- ...erToolLogEventEnabledWithCustomMapper.java | 63 --- .../PowertoolsLogAlbCorrelationId.java | 6 +- .../PowertoolsLogEnabledWithClearState.java | 38 -- .../internal/LambdaLoggingAspectTest.java | 89 +-- .../logging/internal/TestLoggingManager.java | 31 ++ .../org.slf4j.spi.SLF4JServiceProvider | 1 + .../src/test/resources/testlogger.properties | 3 + 33 files changed, 1149 insertions(+), 1533 deletions(-) delete mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java delete mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java delete mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java create mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java rename powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/{DefaultLambdaFields.java => PowertoolsLoggedFields.java} (64%) delete mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java delete mode 100644 powertools-logging/src/main/resources/LambdaEcsLayout.json delete mode 100644 powertools-logging/src/main/resources/LambdaJsonLayout.json delete mode 100644 powertools-logging/src/main/resources/log4j2.component.properties delete mode 100644 powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java create mode 100644 powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java create mode 100644 powertools-logging/src/test/java/org/slf4j/test/TestLogger.java create mode 100644 powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java create mode 100644 powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java create mode 100644 powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java delete mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java delete mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java delete mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java delete mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java create mode 100644 powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider create mode 100644 powertools-logging/src/test/resources/testlogger.properties diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 12ebdc335..3e14c14da 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -17,16 +17,15 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - powertools-logging - jar - powertools-parent software.amazon.lambda 2.0.0-SNAPSHOT + powertools-logging + jar + Powertools for AWS Lambda (Java) library Logging A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. @@ -68,20 +67,8 @@ jackson-databind - org.apache.logging.log4j - log4j-layout-template-json - - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-api + org.slf4j + slf4j-api diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index 9932eb700..3488264a2 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -43,9 +43,9 @@ *

By default {@code Logging} will also create keys for:

* * * diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index 6e11573cc..ed57df0fd 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -14,11 +14,11 @@ package software.amazon.lambda.powertools.logging; -import static java.util.Arrays.asList; - import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.MDC; + +import java.util.Arrays; import java.util.Map; -import org.apache.logging.log4j.ThreadContext; /** * A class of helper functions to add additional functionality to Logging. @@ -39,7 +39,7 @@ private LoggingUtils() { * @param value The value to be logged */ public static void appendKey(String key, String value) { - ThreadContext.put(key, value); + MDC.put(key, value); } @@ -50,7 +50,7 @@ public static void appendKey(String key, String value) { * @param customKeys Map of custom keys values to be appended to logs */ public static void appendKeys(Map customKeys) { - ThreadContext.putAll(customKeys); + customKeys.forEach(MDC::put); } /** @@ -59,7 +59,7 @@ public static void appendKeys(Map customKeys) { * @param customKey The name of the key to be logged */ public static void removeKey(String customKey) { - ThreadContext.remove(customKey); + MDC.remove(customKey); } @@ -69,7 +69,7 @@ public static void removeKey(String customKey) { * @param keys Map of custom keys values to be appended to logs */ public static void removeKeys(String... keys) { - ThreadContext.removeAll(asList(keys)); + Arrays.stream(keys).forEach(MDC::remove); } /** @@ -78,7 +78,7 @@ public static void removeKeys(String... keys) { * @param value The value of the correlation id */ public static void setCorrelationId(String value) { - ThreadContext.put("correlation_id", value); + MDC.put("correlation_id", value); } /** diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java deleted file mode 100644 index 17d09729f..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/AbstractJacksonLayoutCopy.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import com.fasterxml.jackson.core.JsonGenerationException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectWriter; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginElement; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.impl.ThrowableProxy; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.layout.AbstractStringLayout; -import org.apache.logging.log4j.core.lookup.StrSubstitutor; -import org.apache.logging.log4j.core.time.Instant; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.core.util.StringBuilderWriter; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.util.ReadOnlyStringMap; -import org.apache.logging.log4j.util.Strings; - -@Deprecated -abstract class AbstractJacksonLayoutCopy extends AbstractStringLayout { - - protected static final String DEFAULT_EOL = "\r\n"; - protected static final String COMPACT_EOL = Strings.EMPTY; - protected final String eol; - protected final ObjectWriter objectWriter; - protected final boolean compact; - protected final boolean complete; - protected final boolean includeNullDelimiter; - protected final ResolvableKeyValuePair[] additionalFields; - - @Deprecated - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, - final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, - final Serializer headerSerializer, - final Serializer footerSerializer) { - this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false); - } - - @Deprecated - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, - final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, - final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter) { - this(config, objectWriter, charset, compact, complete, eventEol, null, headerSerializer, footerSerializer, - includeNullDelimiter, null); - } - - protected AbstractJacksonLayoutCopy(final Configuration config, final ObjectWriter objectWriter, - final Charset charset, - final boolean compact, final boolean complete, final boolean eventEol, - final String endOfLine, final Serializer headerSerializer, - final Serializer footerSerializer, final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields) { - super(config, charset, headerSerializer, footerSerializer); - this.objectWriter = objectWriter; - this.compact = compact; - this.complete = complete; - this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL; - this.includeNullDelimiter = includeNullDelimiter; - this.additionalFields = prepareAdditionalFields(config, additionalFields); - } - - protected static boolean valueNeedsLookup(final String value) { - return value != null && value.contains("${"); - } - - private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config, - final KeyValuePair[] additionalFields) { - if (additionalFields == null || additionalFields.length == 0) { - // No fields set - return ResolvableKeyValuePair.EMPTY_ARRAY; - } - - // Convert to specific class which already determines whether values needs lookup during serialization - final ResolvableKeyValuePair[] resolvableFields = new ResolvableKeyValuePair[additionalFields.length]; - - for (int i = 0; i < additionalFields.length; i++) { - final ResolvableKeyValuePair resolvable = - resolvableFields[i] = new ResolvableKeyValuePair(additionalFields[i]); - - // Validate - if (config == null && resolvable.valueNeedsLookup) { - throw new IllegalArgumentException( - "configuration needs to be set when there are additional fields with variables"); - } - } - - return resolvableFields; - } - - private static LogEvent convertMutableToLog4jEvent(final LogEvent event) { - return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event); - } - - /** - * Formats a {@link org.apache.logging.log4j.core.LogEvent}. - * - * @param event The LogEvent. - * @return The XML representation of the LogEvent. - */ - @Override - public String toSerializable(final LogEvent event) { - final StringBuilderWriter writer = new StringBuilderWriter(); - try { - toSerializable(event, writer); - return writer.toString(); - } catch (final IOException e) { - // Should this be an ISE or IAE? - LOGGER.error(e); - return Strings.EMPTY; - } - } - - protected Object wrapLogEvent(final LogEvent event) { - if (additionalFields.length > 0) { - // Construct map for serialization - note that we are intentionally using original LogEvent - final Map additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(event, additionalFieldsMap); - } else if (event instanceof Message) { - // If the LogEvent implements the Messagee interface Jackson will not treat is as a LogEvent. - return new ReadOnlyLogEventWrapper(event); - } else { - // No additional fields, return original object - return event; - } - } - - private Map resolveAdditionalFields(final LogEvent logEvent) { - // Note: LinkedHashMap retains order - final Map additionalFieldsMap = new LinkedHashMap<>(additionalFields.length); - final StrSubstitutor strSubstitutor = configuration.getStrSubstitutor(); - - // Go over each field - for (final ResolvableKeyValuePair pair : additionalFields) { - if (pair.valueNeedsLookup) { - // Resolve value - additionalFieldsMap.put(pair.key, strSubstitutor.replace(logEvent, pair.value)); - } else { - // Plain text value - additionalFieldsMap.put(pair.key, pair.value); - } - } - - return additionalFieldsMap; - } - - public void toSerializable(final LogEvent event, final Writer writer) - throws JsonGenerationException, JsonMappingException, IOException { - objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event))); - writer.write(eol); - if (includeNullDelimiter) { - writer.write('\0'); - } - markEvent(); - } - - public static abstract class Builder> extends AbstractStringLayout.Builder { - - @PluginBuilderAttribute - private boolean eventEol; - - @PluginBuilderAttribute - private String endOfLine; - - @PluginBuilderAttribute - private boolean compact; - - @PluginBuilderAttribute - private boolean complete; - - @PluginBuilderAttribute - private boolean locationInfo; - - @PluginBuilderAttribute - private boolean properties; - - @PluginBuilderAttribute - private boolean includeStacktrace = true; - - @PluginBuilderAttribute - private boolean stacktraceAsString = false; - - @PluginBuilderAttribute - private boolean includeNullDelimiter = false; - - @PluginBuilderAttribute - private boolean includeTimeMillis = false; - - @PluginElement("AdditionalField") - private KeyValuePair[] additionalFields; - - protected String toStringOrNull(final byte[] header) { - return header == null ? null : new String(header, Charset.defaultCharset()); - } - - public boolean getEventEol() { - return eventEol; - } - - public B setEventEol(final boolean eventEol) { - this.eventEol = eventEol; - return asBuilder(); - } - - public String getEndOfLine() { - return endOfLine; - } - - public B setEndOfLine(final String endOfLine) { - this.endOfLine = endOfLine; - return asBuilder(); - } - - public boolean isCompact() { - return compact; - } - - public B setCompact(final boolean compact) { - this.compact = compact; - return asBuilder(); - } - - public boolean isComplete() { - return complete; - } - - public B setComplete(final boolean complete) { - this.complete = complete; - return asBuilder(); - } - - public boolean isLocationInfo() { - return locationInfo; - } - - public B setLocationInfo(final boolean locationInfo) { - this.locationInfo = locationInfo; - return asBuilder(); - } - - public boolean isProperties() { - return properties; - } - - public B setProperties(final boolean properties) { - this.properties = properties; - return asBuilder(); - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - * - * @return If "true", includes the stacktrace of any Throwable in the generated data, defaults to "true". - */ - public boolean isIncludeStacktrace() { - return includeStacktrace; - } - - /** - * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * - * @param includeStacktrace If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". - * @return this builder - */ - public B setIncludeStacktrace(final boolean includeStacktrace) { - this.includeStacktrace = includeStacktrace; - return asBuilder(); - } - - public boolean isStacktraceAsString() { - return stacktraceAsString; - } - - /** - * Whether to format the stacktrace as a string, and not a nested object (optional, defaults to false). - * - * @return this builder - */ - public B setStacktraceAsString(final boolean stacktraceAsString) { - this.stacktraceAsString = stacktraceAsString; - return asBuilder(); - } - - public boolean isIncludeNullDelimiter() { - return includeNullDelimiter; - } - - /** - * Whether to include NULL byte as delimiter after each event (optional, default to false). - * - * @return this builder - */ - public B setIncludeNullDelimiter(final boolean includeNullDelimiter) { - this.includeNullDelimiter = includeNullDelimiter; - return asBuilder(); - } - - public boolean isIncludeTimeMillis() { - return includeTimeMillis; - } - - /** - * Whether to include the timestamp (in addition to the Instant) (optional, default to false). - * - * @return this builder - */ - public B setIncludeTimeMillis(final boolean includeTimeMillis) { - this.includeTimeMillis = includeTimeMillis; - return asBuilder(); - } - - public KeyValuePair[] getAdditionalFields() { - return additionalFields; - } - - /** - * Additional fields to set on each log event. - * - * @return this builder - */ - public B setAdditionalFields(final KeyValuePair[] additionalFields) { - this.additionalFields = additionalFields; - return asBuilder(); - } - } - - @JsonRootName(XmlConstants.ELT_EVENT) - public static class LogEventWithAdditionalFields { - - private final Object logEvent; - private final Map additionalFields; - - public LogEventWithAdditionalFields(final Object logEvent, final Map additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - @SuppressWarnings("unused") - public Map getAdditionalFields() { - return additionalFields; - } - } - - protected static class ResolvableKeyValuePair { - - /** - * The empty array. - */ - static final ResolvableKeyValuePair[] EMPTY_ARRAY = {}; - - final String key; - final String value; - final boolean valueNeedsLookup; - - ResolvableKeyValuePair(final KeyValuePair pair) { - this.key = pair.getKey(); - this.value = pair.getValue(); - this.valueNeedsLookup = AbstractJacksonLayoutCopy.valueNeedsLookup(this.value); - } - } - - private static class ReadOnlyLogEventWrapper implements LogEvent { - - @JsonIgnore - private final LogEvent event; - - public ReadOnlyLogEventWrapper(LogEvent event) { - this.event = event; - } - - @Override - public LogEvent toImmutable() { - return event.toImmutable(); - } - - @Override - public Map getContextMap() { - return event.getContextMap(); - } - - @Override - public ReadOnlyStringMap getContextData() { - return event.getContextData(); - } - - @Override - public ThreadContext.ContextStack getContextStack() { - return event.getContextStack(); - } - - @Override - public String getLoggerFqcn() { - return event.getLoggerFqcn(); - } - - @Override - public Level getLevel() { - return event.getLevel(); - } - - @Override - public String getLoggerName() { - return event.getLoggerName(); - } - - @Override - public Marker getMarker() { - return event.getMarker(); - } - - @Override - public Message getMessage() { - return event.getMessage(); - } - - @Override - public long getTimeMillis() { - return event.getTimeMillis(); - } - - @Override - public Instant getInstant() { - return event.getInstant(); - } - - @Override - public StackTraceElement getSource() { - return event.getSource(); - } - - @Override - public String getThreadName() { - return event.getThreadName(); - } - - @Override - public long getThreadId() { - return event.getThreadId(); - } - - @Override - public int getThreadPriority() { - return event.getThreadPriority(); - } - - @Override - public Throwable getThrown() { - return event.getThrown(); - } - - @Override - public ThrowableProxy getThrownProxy() { - return event.getThrownProxy(); - } - - @Override - public boolean isEndOfBatch() { - return event.isEndOfBatch(); - } - - @Override - public void setEndOfBatch(boolean endOfBatch) { - - } - - @Override - public boolean isIncludeLocation() { - return event.isIncludeLocation(); - } - - @Override - public void setIncludeLocation(boolean locationRequired) { - - } - - @Override - public long getNanoTime() { - return event.getNanoTime(); - } - } -} \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java deleted file mode 100644 index 6b568be30..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/JacksonFactoryCopy.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import com.fasterxml.jackson.core.PrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.core.util.MinimalPrettyPrinter; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; -import java.util.HashSet; -import java.util.Set; -import org.apache.logging.log4j.core.impl.Log4jLogEvent; -import org.apache.logging.log4j.core.jackson.JsonConstants; -import org.apache.logging.log4j.core.jackson.Log4jJsonObjectMapper; - -@Deprecated -abstract class JacksonFactoryCopy { - - abstract protected String getPropertyNameForTimeMillis(); - - abstract protected String getPropertyNameForInstant(); - - abstract protected String getPropertNameForContextMap(); - - abstract protected String getPropertNameForSource(); - - abstract protected String getPropertNameForNanoTime(); - - abstract protected PrettyPrinter newCompactPrinter(); - - abstract protected ObjectMapper newObjectMapper(); - - abstract protected PrettyPrinter newPrettyPrinter(); - - ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact) { - return newWriter(locationInfo, properties, compact, false); - } - - ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact, - final boolean includeMillis) { - final SimpleFilterProvider filters = new SimpleFilterProvider(); - final Set except = new HashSet<>(3); - if (!locationInfo) { - except.add(this.getPropertNameForSource()); - } - if (!properties) { - except.add(this.getPropertNameForContextMap()); - } - if (includeMillis) { - except.add(getPropertyNameForInstant()); - } else { - except.add(getPropertyNameForTimeMillis()); - } - except.add(this.getPropertNameForNanoTime()); - filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except)); - final ObjectWriter writer = - this.newObjectMapper().writer(compact ? this.newCompactPrinter() : this.newPrettyPrinter()); - return writer.with(filters); - } - - static class JSON extends JacksonFactoryCopy { - - private final boolean encodeThreadContextAsList; - private final boolean includeStacktrace; - private final boolean stacktraceAsString; - private final boolean objectMessageAsJsonObject; - - public JSON(final boolean encodeThreadContextAsList, final boolean includeStacktrace, - final boolean stacktraceAsString, final boolean objectMessageAsJsonObject) { - this.encodeThreadContextAsList = encodeThreadContextAsList; - this.includeStacktrace = includeStacktrace; - this.stacktraceAsString = stacktraceAsString; - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - } - - @Override - protected String getPropertNameForContextMap() { - return JsonConstants.ELT_CONTEXT_MAP; - } - - @Override - protected String getPropertyNameForTimeMillis() { - return JsonConstants.ELT_TIME_MILLIS; - } - - @Override - protected String getPropertyNameForInstant() { - return JsonConstants.ELT_INSTANT; - } - - @Override - protected String getPropertNameForSource() { - return JsonConstants.ELT_SOURCE; - } - - @Override - protected String getPropertNameForNanoTime() { - return JsonConstants.ELT_NANO_TIME; - } - - @Override - protected PrettyPrinter newCompactPrinter() { - return new MinimalPrettyPrinter(); - } - - @Override - protected ObjectMapper newObjectMapper() { - return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, - objectMessageAsJsonObject); - } - - @Override - protected PrettyPrinter newPrettyPrinter() { - return new DefaultPrettyPrinter(); - } - - } - -} \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java deleted file mode 100644 index fd646ab50..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonLayout.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import static java.time.Instant.ofEpochMilli; -import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.annotation.JsonUnwrapped; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import org.apache.logging.log4j.core.Layout; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.DefaultConfiguration; -import org.apache.logging.log4j.core.config.Node; -import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; -import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; -import org.apache.logging.log4j.core.jackson.XmlConstants; -import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.core.util.KeyValuePair; -import org.apache.logging.log4j.util.Strings; - -/*** - * Note: The LambdaJsonLayout should be considered to be deprecated. Please use JsonTemplateLayout instead. - */ -@Deprecated -@Plugin(name = "LambdaJsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) -public final class LambdaJsonLayout extends AbstractJacksonLayoutCopy { - static final String CONTENT_TYPE = "application/json"; - private static final String DEFAULT_FOOTER = "]"; - private static final String DEFAULT_HEADER = "["; - - private LambdaJsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, - final boolean encodeThreadContextAsList, - final boolean complete, final boolean compact, final boolean eventEol, - final String headerPattern, final String footerPattern, final Charset charset, - final boolean includeStacktrace, final boolean stacktraceAsString, - final boolean includeNullDelimiter, - final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject) { - super(config, new JacksonFactoryCopy.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, - objectMessageAsJsonObject).newWriter( - locationInfo, properties, compact), - charset, compact, complete, eventEol, - null, - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern) - .setDefaultPattern(DEFAULT_HEADER).build(), - PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern) - .setDefaultPattern(DEFAULT_FOOTER).build(), - includeNullDelimiter, - additionalFields); - } - - @PluginBuilderFactory - public static > B newBuilder() { - return new Builder().asBuilder(); - } - - /** - * Creates a JSON Layout using the default settings. Useful for testing. - * - * @return A JSON Layout. - */ - public static LambdaJsonLayout createDefaultLayout() { - return new LambdaJsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, - DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null, false); - } - - /** - * Returns appropriate JSON header. - * - * @return a byte array containing the header, opening the JSON array. - */ - @Override - public byte[] getHeader() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - final String str = serializeToString(getHeaderSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - /** - * Returns appropriate JSON footer. - * - * @return a byte array containing the footer, closing the JSON array. - */ - @Override - public byte[] getFooter() { - if (!this.complete) { - return null; - } - final StringBuilder buf = new StringBuilder(); - buf.append(this.eol); - final String str = serializeToString(getFooterSerializer()); - if (str != null) { - buf.append(str); - } - buf.append(this.eol); - return getBytes(buf.toString()); - } - - @Override - public Map getContentFormat() { - final Map result = new HashMap<>(); - result.put("version", "2.0"); - return result; - } - - /** - * @return The content type. - */ - @Override - public String getContentType() { - return CONTENT_TYPE + "; charset=" + this.getCharset(); - } - - @Override - public Object wrapLogEvent(final LogEvent event) { - Map additionalFieldsMap = resolveAdditionalFields(event); - // This class combines LogEvent with AdditionalFields during serialization - return new LogEventWithAdditionalFields(event, additionalFieldsMap); - } - - @Override - public void toSerializable(final LogEvent event, final Writer writer) throws IOException { - if (complete && eventCount > 0) { - writer.append(", "); - } - super.toSerializable(event, writer); - } - - private Map resolveAdditionalFields(LogEvent logEvent) { - // Note: LinkedHashMap retains order - final Map additionalFieldsMap = new LinkedHashMap<>(additionalFields.length); - - // Go over MDC - logEvent.getContextData().forEach((key, value) -> - { - if (Strings.isNotBlank(key) && value != null) { - additionalFieldsMap.put(key, value); - } - }); - - return additionalFieldsMap; - } - - public static class Builder> extends AbstractJacksonLayoutCopy.Builder - implements org.apache.logging.log4j.core.util.Builder { - - @PluginBuilderAttribute - private boolean propertiesAsList; - - @PluginBuilderAttribute - private boolean objectMessageAsJsonObject; - - public Builder() { - super(); - setCharset(StandardCharsets.UTF_8); - } - - @Override - public LambdaJsonLayout build() { - final boolean encodeThreadContextAsList = isProperties() && propertiesAsList; - final String headerPattern = toStringOrNull(getHeader()); - final String footerPattern = toStringOrNull(getFooter()); - return new LambdaJsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, - isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), - isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), - getAdditionalFields(), getObjectMessageAsJsonObject()); - } - - public boolean isPropertiesAsList() { - return propertiesAsList; - } - - public B setPropertiesAsList(final boolean propertiesAsList) { - this.propertiesAsList = propertiesAsList; - return asBuilder(); - } - - public boolean getObjectMessageAsJsonObject() { - return objectMessageAsJsonObject; - } - - public B setObjectMessageAsJsonObject(final boolean objectMessageAsJsonObject) { - this.objectMessageAsJsonObject = objectMessageAsJsonObject; - return asBuilder(); - } - } - - @JsonRootName(XmlConstants.ELT_EVENT) - public static class LogEventWithAdditionalFields { - - private final LogEvent logEvent; - private final Map additionalFields; - - public LogEventWithAdditionalFields(LogEvent logEvent, Map additionalFields) { - this.logEvent = logEvent; - this.additionalFields = additionalFields; - } - - @JsonUnwrapped - public Object getLogEvent() { - return logEvent; - } - - @JsonAnyGetter - public Map getAdditionalFields() { - return additionalFields; - } - - @JsonGetter("timestamp") - public String getTimestamp() { - return ISO_ZONED_DATE_TIME.format( - ZonedDateTime.from(ofEpochMilli(logEvent.getTimeMillis()).atZone(ZoneId.systemDefault()))); - } - } -} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 0a36723f6..c60a1c85c 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -54,26 +54,50 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclarePrecedence; import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.event.Level; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; + @Aspect @DeclarePrecedence("*, software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect") public final class LambdaLoggingAspect { - private static final Logger LOG = LogManager.getLogger(LambdaLoggingAspect.class); + private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspect.class); private static final Random SAMPLER = new Random(); private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); private static final String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); - private static Level LEVEL_AT_INITIALISATION; + private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ + + private static final LoggingManager loggingManager; static { + loggingManager = loadLoggingManager(); + + LEVEL_AT_INITIALISATION = loggingManager.getLogLevel(LOG); + if (null != LOG_LEVEL) { - resetLogLevels(Level.getLevel(LOG_LEVEL)); + resetLogLevels(Level.valueOf(LOG_LEVEL)); } + } - LEVEL_AT_INITIALISATION = LOG.getLevel(); + private static LoggingManager loadLoggingManager() { + ServiceLoader loggingManagers = ServiceLoader.load(LoggingManager.class); + List loggingManagerList = new ArrayList<>(); + for (LoggingManager loggingManager : loggingManagers) { + loggingManagerList.add(loggingManager); + } + if (loggingManagerList.isEmpty()) { + throw new IllegalStateException("No LoggingManager was found on the classpath"); + } else if (loggingManagerList.size() > 1) { + throw new IllegalStateException("Multiple LoggingManagers were found on the classpath: " + loggingManagerList); + } else { + return loggingManagerList.get(0); + } } private static void resetLogLevels(Level logLevel) { @@ -97,12 +121,12 @@ public Object around(ProceedingJoinPoint pjp, Context extractedContext = extractContext(pjp); if (null != extractedContext) { - appendKeys(DefaultLambdaFields.values(extractedContext)); - appendKey("coldStart", isColdStart() ? "true" : "false"); - appendKey("service", serviceName()); + appendKeys(PowertoolsLoggedFields.setValuesFromLambdaContext(extractedContext)); + appendKey(FUNCTION_COLD_START.getName(), isColdStart() ? "true" : "false"); + appendKey(SERVICE.getName(), serviceName()); } - getXrayTraceId().ifPresent(xRayTraceId -> appendKey("xray_trace_id", xRayTraceId)); + getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); if (logging.logEvent()) { proceedArgs = logEvent(pjp); @@ -115,13 +139,17 @@ public Object around(ProceedingJoinPoint pjp, Object proceed = pjp.proceed(proceedArgs); if (logging.clearState()) { - ThreadContext.clearMap(); + MDC.clear(); } coldStartDone(); return proceed; } + private static void resetLogLevels(Level logLevel) { + loggingManager.resetLogLevel(logLevel); + } + private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, final Logging logging) { double samplingRate = samplingRate(logging); @@ -134,7 +162,7 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, return; } - appendKey("samplingRate", String.valueOf(samplingRate)); + appendKey(PowertoolsLoggedFields.SAMPLING_RATE.getName(), String.valueOf(samplingRate)); if (samplingRate == 0) { return; @@ -147,7 +175,7 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); - } else if (LEVEL_AT_INITIALISATION != LOG.getLevel()) { + } else if (LEVEL_AT_INITIALISATION != loggingManager.getLogLevel(LOG)) { resetLogLevels(LEVEL_AT_INITIALISATION); } } @@ -249,8 +277,11 @@ private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws try (ByteArrayOutputStream out = new ByteArrayOutputStream(); InputStreamReader reader = new InputStreamReader(inputStream, UTF_8)) { OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8); - - IOUtils.copy(reader, writer); + int n; + char[] buffer = new char[4096]; + while (-1 != (n = reader.read(buffer))) { + writer.write(buffer, 0, n); + } writer.flush(); return out.toByteArray(); } @@ -267,6 +298,6 @@ private Optional asJson(final ProceedingJoinPoint pjp, } private Logger logger(final ProceedingJoinPoint pjp) { - return LogManager.getLogger(pjp.getSignature().getDeclaringType()); + return LoggerFactory.getLogger(pjp.getSignature().getDeclaringType()); } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java new file mode 100644 index 000000000..4850c9413 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java @@ -0,0 +1,30 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +/** + * Due to limitations of SLF4J, we need to rely on implementations for some operations: + *
    + *
  • Accessing to all loggers and change their Level
  • + *
  • Retrieving the log Level of a Logger
  • + *
+ * + * Implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} + * (define a file named software.amazon.lambda.powertools.logging.internal.LoggingManager in src/main/resources/META-INF/services with the qualified name of the implementation). + * + */ +public interface LoggingManager { + /** + * Change the log Level of all loggers (named and root) + * @param logLevel the log Level (slf4j) to apply + */ + void resetLogLevel(Level logLevel); + + /** + * Retrieve the log Level of a specific logger + * @param logger the logger (slf4j) for which to retrieve the log Level + * @return the Level (slf4j) of this logger. Note that SLF4J only support ERROR, WARN, INFO, DEBUG, TRACE while some frameworks may support others (OFF, FATAL, ...) + */ + Level getLogLevel(Logger logger); +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java similarity index 64% rename from powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java rename to powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java index 2461ae771..1c64248f9 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefaultLambdaFields.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java @@ -16,22 +16,34 @@ import com.amazonaws.services.lambda.runtime.Context; import java.util.HashMap; +import java.util.List; import java.util.Map; - -enum DefaultLambdaFields { - FUNCTION_NAME("functionName"), - FUNCTION_VERSION("functionVersion"), - FUNCTION_ARN("functionArn"), - FUNCTION_MEMORY_SIZE("functionMemorySize"), - FUNCTION_REQUEST_ID("function_request_id"); +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum PowertoolsLoggedFields { + FUNCTION_NAME("function_name"), + FUNCTION_VERSION("function_version"), + FUNCTION_ARN("function_arn"), + FUNCTION_MEMORY_SIZE("function_memory_size"), + FUNCTION_REQUEST_ID("function_request_id"), + FUNCTION_COLD_START("cold_start"), + FUNCTION_TRACE_ID("xray_trace_id"), + SAMPLING_RATE("sampling_rate"), + SERVICE("service"); private final String name; - DefaultLambdaFields(String name) { + PowertoolsLoggedFields(String name) { this.name = name; } - static Map values(Context context) { + public static List stringValues() { + return Stream.of(values()).map(PowertoolsLoggedFields::getName).collect(Collectors.toList()); + } + + + static Map setValuesFromLambdaContext(Context context) { Map hashMap = new HashMap<>(); hashMap.put(FUNCTION_NAME.name, context.getFunctionName()); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java deleted file mode 100644 index dc9816932..000000000 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; -import org.apache.logging.log4j.layout.template.json.util.JsonWriter; -import org.apache.logging.log4j.util.ReadOnlyStringMap; - -final class PowertoolsResolver implements EventResolver { - - private final EventResolver internalResolver; - - PowertoolsResolver() { - internalResolver = new EventResolver() { - @Override - public boolean isResolvable(LogEvent value) { - ReadOnlyStringMap contextData = value.getContextData(); - return null != contextData && !contextData.isEmpty(); - } - - @Override - public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { - StringBuilder stringBuilder = jsonWriter.getStringBuilder(); - // remove dummy field to kick inn powertools resolver - stringBuilder.setLength(stringBuilder.length() - 4); - - // Inject all the context information. - ReadOnlyStringMap contextData = logEvent.getContextData(); - contextData.forEach((key, value) -> - { - jsonWriter.writeSeparator(); - jsonWriter.writeString(key); - stringBuilder.append(':'); - jsonWriter.writeValue(value); - }); - } - }; - } - - static String getName() { - return "powertools"; - } - - @Override - public void resolve(LogEvent value, JsonWriter jsonWriter) { - internalResolver.resolve(value, jsonWriter); - } - - @Override - public boolean isResolvable(LogEvent value) { - return internalResolver.isResolvable(value); - } -} diff --git a/powertools-logging/src/main/resources/LambdaEcsLayout.json b/powertools-logging/src/main/resources/LambdaEcsLayout.json deleted file mode 100644 index 4ab9c7ce2..000000000 --- a/powertools-logging/src/main/resources/LambdaEcsLayout.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "@timestamp": { - "$resolver": "timestamp", - "pattern": { - "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", - "timeZone": "UTC" - } - }, - "ecs.version": "1.2.0", - "log.level": { - "$resolver": "level", - "field": "name" - }, - "message": { - "$resolver": "message", - "stringified": true - }, - "process.thread.name": { - "$resolver": "thread", - "field": "name" - }, - "log.logger": { - "$resolver": "logger", - "field": "name" - }, - "labels": { - "$resolver": "mdc", - "flatten": true, - "stringified": true - }, - "tags": { - "$resolver": "ndc" - }, - "error.type": { - "$resolver": "exception", - "field": "className" - }, - "error.message": { - "$resolver": "exception", - "field": "message" - }, - "error.stack_trace": { - "$resolver": "exception", - "field": "stackTrace", - "stackTrace": { - "stringified": true - } - }, - "": { - "$resolver": "powertools" - } -} \ No newline at end of file diff --git a/powertools-logging/src/main/resources/LambdaJsonLayout.json b/powertools-logging/src/main/resources/LambdaJsonLayout.json deleted file mode 100644 index dfc1fc78f..000000000 --- a/powertools-logging/src/main/resources/LambdaJsonLayout.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "timestamp": { - "$resolver": "timestamp" - }, - "instant": { - "epochSecond": { - "$resolver": "timestamp", - "epoch": { - "unit": "secs", - "rounded": true - } - }, - "nanoOfSecond": { - "$resolver": "timestamp", - "epoch": { - "unit": "secs.nanos" - } - } - }, - "thread": { - "$resolver": "thread", - "field": "name" - }, - "level": { - "$resolver": "level", - "field": "name" - }, - "loggerName": { - "$resolver": "logger", - "field": "name" - }, - "message": { - "$resolver": "message", - "stringified": true - }, - "thrown": { - "message": { - "$resolver": "exception", - "field": "message" - }, - "name": { - "$resolver": "exception", - "field": "className" - }, - "extendedStackTrace": { - "$resolver": "exception", - "field": "stackTrace" - } - }, - "contextStack": { - "$resolver": "ndc" - }, - "endOfBatch": { - "$resolver": "endOfBatch" - }, - "loggerFqcn": { - "$resolver": "logger", - "field": "fqcn" - }, - "threadId": { - "$resolver": "thread", - "field": "id" - }, - "threadPriority": { - "$resolver": "thread", - "field": "priority" - }, - "source": { - "class": { - "$resolver": "source", - "field": "className" - }, - "method": { - "$resolver": "source", - "field": "methodName" - }, - "file": { - "$resolver": "source", - "field": "fileName" - }, - "line": { - "$resolver": "source", - "field": "lineNumber" - } - }, - "": { - "$resolver": "powertools" - } -} diff --git a/powertools-logging/src/main/resources/log4j2.component.properties b/powertools-logging/src/main/resources/log4j2.component.properties deleted file mode 100644 index 3c392dd13..000000000 --- a/powertools-logging/src/main/resources/log4j2.component.properties +++ /dev/null @@ -1,2 +0,0 @@ -log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz -#log4j.layout.jsonTemplate.timeZone= \ No newline at end of file diff --git a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java b/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java deleted file mode 100644 index 9b0c6165a..000000000 --- a/powertools-logging/src/test/java/org/apache/logging/log4j/core/layout/LambdaJsonLayoutTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 org.apache.logging.log4j.core.layout; - -import static java.util.Collections.emptyMap; -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.util.Map; -import org.apache.logging.log4j.Level; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolSamplingEnabled; -import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; - -class LambdaJsonLayoutTest { - - private RequestHandler handler = new PowerLogToolEnabled(); - - @Mock - private Context context; - - @BeforeEach - void setUp() throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { - openMocks(this); - setupContext(); - //Make sure file is cleaned up before running full stack logging regression - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - resetLogLevel(Level.INFO); - } - - @Test - void shouldLogInStructuredFormat() throws IOException { - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(1) - .allSatisfy(line -> assertThat(parseToMap(line)) - .containsEntry("functionName", "testFunction") - .containsEntry("functionVersion", "1") - .containsEntry("functionMemorySize", "10") - .containsEntry("functionArn", "testArn") - .containsKey("timestamp") - .containsKey("message") - .containsKey("service")); - } - - @Test - void shouldModifyLogLevelBasedOnEnvVariable() - throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { - resetLogLevel(Level.DEBUG); - - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(2) - .satisfies(line -> - { - assertThat(parseToMap(line.get(0))) - .containsEntry("level", "INFO") - .containsEntry("message", "Test event"); - - assertThat(parseToMap(line.get(1))) - .containsEntry("level", "DEBUG") - .containsEntry("message", "Test debug event"); - }); - } - - @Test - void shouldModifyLogLevelBasedOnSamplingRule() throws IOException { - handler = new PowerLogToolSamplingEnabled(); - - handler.handleRequest("test", context); - - assertThat(Files.lines(Paths.get("target/logfile.json"))) - .hasSize(3) - .satisfies(line -> - { - assertThat(parseToMap(line.get(0))) - .containsEntry("level", "DEBUG") - .containsEntry("loggerName", LambdaLoggingAspect.class.getCanonicalName()); - - assertThat(parseToMap(line.get(1))) - .containsEntry("level", "INFO") - .containsEntry("message", "Test event"); - - assertThat(parseToMap(line.get(2))) - .containsEntry("level", "DEBUG") - .containsEntry("message", "Test debug event"); - }); - } - - private void resetLogLevel(Level level) - throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); - resetLogLevels.setAccessible(true); - resetLogLevels.invoke(null, level); - writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); - } - - private Map parseToMap(String stringAsJson) { - try { - return new ObjectMapper().readValue(stringAsJson, Map.class); - } catch (JsonProcessingException e) { - fail("Failed parsing logger line " + stringAsJson); - return emptyMap(); - } - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn("testArn"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(10); - } -} \ No newline at end of file diff --git a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java new file mode 100644 index 000000000..096fa7d83 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.test; + +import java.io.PrintStream; + +/** + * This class encapsulates the user's choice of output target. + * + * @author Ceki Gülcü + * + */ +class OutputChoice { + + enum OutputChoiceType { + SYS_OUT, CACHED_SYS_OUT, SYS_ERR, CACHED_SYS_ERR, FILE; + } + + final OutputChoiceType outputChoiceType; + final PrintStream targetPrintStream; + + OutputChoice(OutputChoiceType outputChoiceType) { + if (outputChoiceType == OutputChoiceType.FILE) { + throw new IllegalArgumentException(); + } + this.outputChoiceType = outputChoiceType; + if (outputChoiceType == OutputChoiceType.CACHED_SYS_OUT) { + this.targetPrintStream = System.out; + } else if (outputChoiceType == OutputChoiceType.CACHED_SYS_ERR) { + this.targetPrintStream = System.err; + } else { + this.targetPrintStream = null; + } + } + + OutputChoice(PrintStream printStream) { + this.outputChoiceType = OutputChoiceType.FILE; + this.targetPrintStream = printStream; + } + + PrintStream getTargetPrintStream() { + switch (outputChoiceType) { + case SYS_OUT: + return System.out; + case SYS_ERR: + return System.err; + case CACHED_SYS_ERR: + case CACHED_SYS_OUT: + case FILE: + return targetPrintStream; + default: + throw new IllegalArgumentException(); + } + + } + +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java new file mode 100644 index 000000000..d54b51da4 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java @@ -0,0 +1,467 @@ +/** + * Copyright (c) 2004-2022 QOS.ch Sarl (Switzerland) + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.test; + +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; +import org.slf4j.helpers.LegacyAbstractLogger; +import org.slf4j.helpers.MessageFormatter; +import org.slf4j.helpers.NormalizedParameters; +import org.slf4j.spi.LocationAwareLogger; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + *

+ * Simple implementation of {@link Logger} that sends all enabled log messages, + * for all defined loggers, to the console ({@code System.err}). The following + * system properties are supported to configure the behavior of this logger: + * + * + *

    + *
  • org.slf4j.simpleLogger.logFile - The output target which can + * be the path to a file, or the special values "System.out" and + * "System.err". Default is "System.err".
  • + * + *
  • org.slf4j.simpleLogger.cacheOutputStream - If the output + * target is set to "System.out" or "System.err" (see preceding entry), by + * default, logs will be output to the latest value referenced by + * System.out/err variables. By setting this parameter to true, the + * output stream will be cached, i.e. assigned once at initialization time and + * re-used independently of the current value referenced by + * System.out/err.
  • + * + *
  • org.slf4j.simpleLogger.defaultLogLevel - Default log level + * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info", + * "warn", "error" or "off"). If not specified, defaults to "info".
  • + * + *
  • org.slf4j.simpleLogger.log.a.b.c - Logging detail + * level for a SimpleLogger instance named "a.b.c". Right-side value must be one + * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger + * named "a.b.c" is initialized, its level is assigned from this property. If + * unspecified, the level of nearest parent logger will be used, and if none is + * set, then the value specified by + * org.slf4j.simpleLogger.defaultLogLevel will be used.
  • + * + *
  • org.slf4j.simpleLogger.showDateTime - Set to + * true if you want the current date and time to be included in + * output messages. Default is false
  • + * + *
  • org.slf4j.simpleLogger.dateTimeFormat - The date and time + * format to be used in the output messages. The pattern describing the date and + * time format is defined by + * SimpleDateFormat. If the format is not specified or is + * invalid, the number of milliseconds since start up will be output.
  • + * + *
  • org.slf4j.simpleLogger.showThreadName -Set to + * true if you want to output the current thread name. Defaults to + * true.
  • + * + *
  • (since version 1.7.33 and 2.0.0-alpha6) org.slf4j.simpleLogger.showThreadId - + * If you would like to output the current thread id, then set to + * true. Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.showLogName - Set to + * true if you want the Logger instance name to be included in + * output messages. Defaults to true.
  • + * + *
  • org.slf4j.simpleLogger.showShortLogName - Set to + * true if you want the last component of the name to be included + * in output messages. Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.levelInBrackets - Should the level + * string be output in brackets? Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.warnLevelString - The string value + * output for the warn level. Defaults to WARN.
  • + * + *
+ * + *

+ * In addition to looking for system properties with the names specified above, + * this implementation also checks for a class loader resource named + * "simplelogger.properties", and includes any matching definitions + * from this resource (if it exists). + * + * + *

+ * With no configuration, the default output includes the relative time in + * milliseconds, thread name, the level, logger name, and the message followed + * by the line separator for the host. In log4j terms it amounts to the "%r [%t] + * %level %logger - %m%n" pattern. + * + *

+ * Sample output follows. + * + * + *

+ * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
+ * 225 [main] INFO examples.SortAlgo - Entered the sort method.
+ * 304 [main] INFO examples.SortAlgo - Dump of integer array:
+ * 317 [main] INFO examples.SortAlgo - Element [0] = 0
+ * 331 [main] INFO examples.SortAlgo - Element [1] = 1
+ * 343 [main] INFO examples.Sort - The next log statement should be an error message.
+ * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
+ *   at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
+ *   at org.log4j.examples.Sort.main(Sort.java:64)
+ * 467 [main] INFO  examples.Sort - Exiting main method.
+ * 
+ * + *

+ * This implementation is heavily inspired by + * Apache Commons Logging's + * SimpleLog. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + */ +public class TestLogger extends LegacyAbstractLogger { + + private static final long serialVersionUID = -632788891211436180L; + + private static final long START_TIME = System.currentTimeMillis(); + + protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT; + protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT; + protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT; + protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT; + protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT; + + static char SP = ' '; + static final String TID_PREFIX = "tid="; + + + // The OFF level can only be used in configuration files to disable logging. + // It has + // no printing method associated with it in o.s.Logger interface. + protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10; + + private static boolean INITIALIZED = false; + static final TestLoggerConfiguration CONFIG_PARAMS = new TestLoggerConfiguration(); + + static void lazyInit() { + if (INITIALIZED) { + return; + } + INITIALIZED = true; + init(); + } + + // external software might be invoking this method directly. Do not rename + // or change its semantics. + static void init() { + CONFIG_PARAMS.init(); + } + + /** The current log level */ + protected int currentLogLevel = LOG_LEVEL_INFO; + /** The short name of this simple log instance */ + private transient String shortLogName = null; + + /** + * All system properties used by SimpleLogger start with this + * prefix + */ + public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger."; + + public static final String LOG_KEY_PREFIX = TestLogger.SYSTEM_PREFIX + "log."; + + public static final String CACHE_OUTPUT_STREAM_STRING_KEY = TestLogger.SYSTEM_PREFIX + "cacheOutputStream"; + + public static final String WARN_LEVEL_STRING_KEY = TestLogger.SYSTEM_PREFIX + "warnLevelString"; + + public static final String LEVEL_IN_BRACKETS_KEY = TestLogger.SYSTEM_PREFIX + "levelInBrackets"; + + public static final String LOG_FILE_KEY = TestLogger.SYSTEM_PREFIX + "logFile"; + + public static final String SHOW_SHORT_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showShortLogName"; + + public static final String SHOW_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showLogName"; + + public static final String SHOW_THREAD_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showThreadName"; + + public static final String SHOW_THREAD_ID_KEY = TestLogger.SYSTEM_PREFIX + "showThreadId"; + + public static final String DATE_TIME_FORMAT_KEY = TestLogger.SYSTEM_PREFIX + "dateTimeFormat"; + + public static final String SHOW_DATE_TIME_KEY = TestLogger.SYSTEM_PREFIX + "showDateTime"; + + public static final String DEFAULT_LOG_LEVEL_KEY = TestLogger.SYSTEM_PREFIX + "defaultLogLevel"; + + /** + * Package access allows only {@link TestLoggerFactory} to instantiate + * SimpleLogger instances. + */ + TestLogger(String name) { + this.name = name; + + String levelString = recursivelyComputeLevelString(); + if (levelString != null) { + this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString); + } else { + this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel; + } + } + + public void setLogLevel(String levelString) { + this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString); + } + + public int getLogLevel() { + return currentLogLevel; + } + + String recursivelyComputeLevelString() { + String tempName = name; + String levelString = null; + int indexOfLastDot = tempName.length(); + while ((levelString == null) && (indexOfLastDot > -1)) { + tempName = tempName.substring(0, indexOfLastDot); + levelString = CONFIG_PARAMS.getStringProperty(TestLogger.LOG_KEY_PREFIX + tempName, null); + indexOfLastDot = String.valueOf(tempName).lastIndexOf("."); + } + return levelString; + } + + /** + * To avoid intermingling of log messages and associated stack traces, the two + * operations are done in a synchronized block. + * + * @param buf + * @param t + */ + void write(StringBuilder buf, Throwable t) { + PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream(); + + synchronized (CONFIG_PARAMS) { + targetStream.println(buf.toString()); + writeThrowable(t, targetStream); + targetStream.flush(); + } + + } + + protected void writeThrowable(Throwable t, PrintStream targetStream) { + if (t != null) { + t.printStackTrace(targetStream); + } + } + + private String getFormattedDate() { + Date now = new Date(); + String dateText; + synchronized (CONFIG_PARAMS.dateFormatter) { + dateText = CONFIG_PARAMS.dateFormatter.format(now); + } + return dateText; + } + + private String computeShortName() { + return name.substring(name.lastIndexOf(".") + 1); + } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arg1 + // * @param arg2 + // */ + // private void formatAndLog(int level, String format, Object arg1, Object arg2) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.format(format, arg1, arg2); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arguments + // * a list of 3 ore more arguments + // */ + // private void formatAndLog(int level, String format, Object... arguments) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + * @return whether the logger is enabled for the given level + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return (logLevel >= currentLogLevel); + } + + /** Are {@code trace} messages currently enabled? */ + public boolean isTraceEnabled() { + return isLevelEnabled(LOG_LEVEL_TRACE); + } + + /** Are {@code debug} messages currently enabled? */ + public boolean isDebugEnabled() { + return isLevelEnabled(LOG_LEVEL_DEBUG); + } + + /** Are {@code info} messages currently enabled? */ + public boolean isInfoEnabled() { + return isLevelEnabled(LOG_LEVEL_INFO); + } + + /** Are {@code warn} messages currently enabled? */ + public boolean isWarnEnabled() { + return isLevelEnabled(LOG_LEVEL_WARN); + } + + /** Are {@code error} messages currently enabled? */ + public boolean isErrorEnabled() { + return isLevelEnabled(LOG_LEVEL_ERROR); + } + + /** + * SimpleLogger's implementation of + * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall} + * } + * + * @param level the SLF4J level for this event + * @param marker The marker to be used for this event, may be null. + * @param messagePattern The message pattern which will be parsed and formatted + * @param arguments the array of arguments to be formatted, may be null + * @param throwable The exception whose stack trace should be logged, may be null + */ + @Override + protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) { + + List markers = null; + + if (marker != null) { + markers = new ArrayList<>(); + markers.add(marker); + } + + innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable); + } + + private void innerHandleNormalizedLoggingCall(Level level, List markers, String messagePattern, Object[] arguments, Throwable t) { + + StringBuilder buf = new StringBuilder(32); + + // Append date-time if so configured + if (CONFIG_PARAMS.showDateTime) { + if (CONFIG_PARAMS.dateFormatter != null) { + buf.append(getFormattedDate()); + buf.append(SP); + } else { + buf.append(System.currentTimeMillis() - START_TIME); + buf.append(SP); + } + } + + // Append current thread name if so configured + if (CONFIG_PARAMS.showThreadName) { + buf.append('['); + buf.append(Thread.currentThread().getName()); + buf.append("] "); + } + + if (CONFIG_PARAMS.showThreadId) { + buf.append(TID_PREFIX); + buf.append(Thread.currentThread().getId()); + buf.append(SP); + } + + if (CONFIG_PARAMS.levelInBrackets) + buf.append('['); + + // Append a readable representation of the log level + String levelStr = level.name(); + buf.append(levelStr); + if (CONFIG_PARAMS.levelInBrackets) + buf.append(']'); + buf.append(SP); + + // Append the name of the log instance if so configured + if (CONFIG_PARAMS.showShortLogName) { + if (shortLogName == null) + shortLogName = computeShortName(); + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if (CONFIG_PARAMS.showLogName) { + buf.append(String.valueOf(name)).append(" - "); + } + + if (markers != null) { + buf.append(SP); + for (Marker marker : markers) { + buf.append(marker.getName()).append(SP); + } + } + + String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments); + + // Append the message + buf.append(formattedMessage); + + write(buf, t); + } + + public void log(LoggingEvent event) { + int levelInt = event.getLevel().toInt(); + + if (!isLevelEnabled(levelInt)) { + return; + } + + NormalizedParameters np = NormalizedParameters.normalize(event); + + innerHandleNormalizedLoggingCall(event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable()); + } + + @Override + protected String getFullyQualifiedCallerName() { + return null; + } + +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java new file mode 100644 index 000000000..b90b6d04a --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.test; + +import org.slf4j.helpers.Util; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; + +/** + * This class holds configuration values for {@link TestLogger}. The + * values are computed at runtime. See {@link TestLogger} documentation for + * more information. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + * + * @since 1.7.25 + */ +public class TestLoggerConfiguration { + + private static final String CONFIGURATION_FILE = "testlogger.properties"; + + static int DEFAULT_LOG_LEVEL_DEFAULT = TestLogger.LOG_LEVEL_INFO; + int defaultLogLevel = DEFAULT_LOG_LEVEL_DEFAULT; + + private static final boolean SHOW_DATE_TIME_DEFAULT = false; + boolean showDateTime = SHOW_DATE_TIME_DEFAULT; + + private static final String DATE_TIME_FORMAT_STR_DEFAULT = null; + private static String dateTimeFormatStr = DATE_TIME_FORMAT_STR_DEFAULT; + + DateFormat dateFormatter = null; + + private static final boolean SHOW_THREAD_NAME_DEFAULT = true; + boolean showThreadName = SHOW_THREAD_NAME_DEFAULT; + + /** + * See https://jira.qos.ch/browse/SLF4J-499 + * @since 1.7.33 and 2.0.0-alpha6 + */ + private static final boolean SHOW_THREAD_ID_DEFAULT = false; + boolean showThreadId = SHOW_THREAD_ID_DEFAULT; + + final static boolean SHOW_LOG_NAME_DEFAULT = true; + boolean showLogName = SHOW_LOG_NAME_DEFAULT; + + private static final boolean SHOW_SHORT_LOG_NAME_DEFAULT = false; + boolean showShortLogName = SHOW_SHORT_LOG_NAME_DEFAULT; + + private static final boolean LEVEL_IN_BRACKETS_DEFAULT = false; + boolean levelInBrackets = LEVEL_IN_BRACKETS_DEFAULT; + + private static final String LOG_FILE_DEFAULT = "System.err"; + private String logFile = LOG_FILE_DEFAULT; + OutputChoice outputChoice = null; + + private static final boolean CACHE_OUTPUT_STREAM_DEFAULT = false; + private boolean cacheOutputStream = CACHE_OUTPUT_STREAM_DEFAULT; + + private static final String WARN_LEVELS_STRING_DEFAULT = "WARN"; + String warnLevelString = WARN_LEVELS_STRING_DEFAULT; + + private final Properties properties = new Properties(); + + void init() { + loadProperties(); + + String defaultLogLevelString = getStringProperty(TestLogger.DEFAULT_LOG_LEVEL_KEY, null); + if (defaultLogLevelString != null) + defaultLogLevel = stringToLevel(defaultLogLevelString); + + showLogName = getBooleanProperty(TestLogger.SHOW_LOG_NAME_KEY, TestLoggerConfiguration.SHOW_LOG_NAME_DEFAULT); + showShortLogName = getBooleanProperty(TestLogger.SHOW_SHORT_LOG_NAME_KEY, SHOW_SHORT_LOG_NAME_DEFAULT); + showDateTime = getBooleanProperty(TestLogger.SHOW_DATE_TIME_KEY, SHOW_DATE_TIME_DEFAULT); + showThreadName = getBooleanProperty(TestLogger.SHOW_THREAD_NAME_KEY, SHOW_THREAD_NAME_DEFAULT); + showThreadId = getBooleanProperty(TestLogger.SHOW_THREAD_ID_KEY, SHOW_THREAD_ID_DEFAULT); + dateTimeFormatStr = getStringProperty(TestLogger.DATE_TIME_FORMAT_KEY, DATE_TIME_FORMAT_STR_DEFAULT); + levelInBrackets = getBooleanProperty(TestLogger.LEVEL_IN_BRACKETS_KEY, LEVEL_IN_BRACKETS_DEFAULT); + warnLevelString = getStringProperty(TestLogger.WARN_LEVEL_STRING_KEY, WARN_LEVELS_STRING_DEFAULT); + + logFile = getStringProperty(TestLogger.LOG_FILE_KEY, logFile); + + cacheOutputStream = getBooleanProperty(TestLogger.CACHE_OUTPUT_STREAM_STRING_KEY, CACHE_OUTPUT_STREAM_DEFAULT); + outputChoice = computeOutputChoice(logFile, cacheOutputStream); + + if (dateTimeFormatStr != null) { + try { + dateFormatter = new SimpleDateFormat(dateTimeFormatStr); + } catch (IllegalArgumentException e) { + Util.report("Bad date format in " + CONFIGURATION_FILE + "; will output relative time", e); + } + } + } + + private void loadProperties() { + // Add props from the resource testlogger.properties + InputStream in = AccessController.doPrivileged((PrivilegedAction) () -> { + ClassLoader threadCL = Thread.currentThread().getContextClassLoader(); + if (threadCL != null) { + return threadCL.getResourceAsStream(CONFIGURATION_FILE); + } else { + return ClassLoader.getSystemResourceAsStream(CONFIGURATION_FILE); + } + }); + if (null != in) { + try { + properties.load(in); + } catch (java.io.IOException e) { + // ignored + } finally { + try { + in.close(); + } catch (java.io.IOException e) { + // ignored + } + } + } + } + + String getStringProperty(String name, String defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : prop; + } + + boolean getBooleanProperty(String name, boolean defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop); + } + + String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + ; // Ignore + } + return (prop == null) ? properties.getProperty(name) : prop; + } + + static int stringToLevel(String levelStr) { + if ("trace".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_TRACE; + } else if ("debug".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_DEBUG; + } else if ("info".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_INFO; + } else if ("warn".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_WARN; + } else if ("error".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_ERROR; + } else if ("off".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_OFF; + } + // assume INFO by default + return TestLogger.LOG_LEVEL_INFO; + } + + private static OutputChoice computeOutputChoice(String logFile, boolean cacheOutputStream) { + if ("System.err".equalsIgnoreCase(logFile)) + if (cacheOutputStream) + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_ERR); + else + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + else if ("System.out".equalsIgnoreCase(logFile)) { + if (cacheOutputStream) + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_OUT); + else + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_OUT); + } else { + try { + FileOutputStream fos = new FileOutputStream(logFile); + PrintStream printStream = new PrintStream(fos); + return new OutputChoice(printStream); + } catch (FileNotFoundException e) { + Util.report("Could not open [" + logFile + "]. Defaulting to System.err", e); + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + } + } + } + +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java new file mode 100644 index 000000000..db057d714 --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.test; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * An implementation of {@link ILoggerFactory} which always returns + * {@link TestLogger} instances. + * + * @author Ceki Gülcü + */ +public class TestLoggerFactory implements ILoggerFactory { + + ConcurrentMap loggerMap; + + public TestLoggerFactory() { + loggerMap = new ConcurrentHashMap<>(); + TestLogger.lazyInit(); + } + + public Map getLoggers() { + return loggerMap; + } + + /** + * Return an appropriate {@link TestLogger} instance by name. + */ + public Logger getLogger(String name) { + Logger simpleLogger = loggerMap.get(name); + if (simpleLogger != null) { + return simpleLogger; + } else { + Logger newInstance = new TestLogger(name); + Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); + return oldInstance == null ? newInstance : oldInstance; + } + } + + /** + * Clear the internal logger cache. + * + * This method is intended to be called by classes (in the same package) for + * testing purposes. This method is internal. It can be modified, renamed or + * removed at any time without notice. + * + * You are strongly discouraged from calling this method in production code. + */ + void reset() { + loggerMap.clear(); + } +} diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java new file mode 100644 index 000000000..62a0136ef --- /dev/null +++ b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2004-2011 QOS.ch + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package org.slf4j.test; + +import org.slf4j.ILoggerFactory; +import org.slf4j.IMarkerFactory; +import org.slf4j.helpers.BasicMDCAdapter; +import org.slf4j.helpers.BasicMarkerFactory; +import org.slf4j.spi.MDCAdapter; +import org.slf4j.spi.SLF4JServiceProvider; + +/** + * Copy of the org.slf4j.simple.SimpleServiceProvider, replacing the NoOpMDCAdapter with a BasicMDCAdapter to test the MDC + */ +public class TestServiceProvider implements SLF4JServiceProvider { + /** + * Declare the version of the SLF4J API this implementation is compiled against. + * The value of this field is modified with each major release. + */ + // to avoid constant folding by the compiler, this field must *not* be final + public static String REQUESTED_API_VERSION = "2.0.99"; // !final + + private ILoggerFactory loggerFactory; + private IMarkerFactory markerFactory; + private MDCAdapter mdcAdapter; + + public ILoggerFactory getLoggerFactory() { + return loggerFactory; + } + + @Override + public IMarkerFactory getMarkerFactory() { + return markerFactory; + } + + @Override + public MDCAdapter getMDCAdapter() { + return mdcAdapter; + } + + @Override + public String getRequestedApiVersion() { + return REQUESTED_API_VERSION; + } + + @Override + public void initialize() { + + loggerFactory = new TestLoggerFactory(); + markerFactory = new BasicMarkerFactory(); + mdcAdapter = new BasicMDCAdapter(); + } + +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java index 8889fb93c..2ad8d10a6 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java @@ -18,71 +18,71 @@ import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.ThreadContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.MDC; class LoggingUtilsTest { @BeforeEach void setUp() { - ThreadContext.clearAll(); + MDC.clear(); } @Test void shouldSetCustomKeyOnThreadContext() { - LoggingUtils.appendKey("test", "value"); + LoggingUtils.appendKey("org/slf4j/test", "value"); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(1) - .containsEntry("test", "value"); + .containsEntry("org/slf4j/test", "value"); } @Test void shouldSetCustomKeyAsMapOnThreadContext() { Map customKeys = new HashMap<>(); - customKeys.put("test", "value"); + customKeys.put("org/slf4j/test", "value"); customKeys.put("test1", "value1"); LoggingUtils.appendKeys(customKeys); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(2) - .containsEntry("test", "value") + .containsEntry("org/slf4j/test", "value") .containsEntry("test1", "value1"); } @Test void shouldRemoveCustomKeyOnThreadContext() { - LoggingUtils.appendKey("test", "value"); + LoggingUtils.appendKey("org/slf4j/test", "value"); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(1) - .containsEntry("test", "value"); + .containsEntry("org/slf4j/test", "value"); - LoggingUtils.removeKey("test"); + LoggingUtils.removeKey("org/slf4j/test"); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .isEmpty(); } @Test void shouldRemoveCustomKeysOnThreadContext() { Map customKeys = new HashMap<>(); - customKeys.put("test", "value"); + customKeys.put("org/slf4j/test", "value"); customKeys.put("test1", "value1"); LoggingUtils.appendKeys(customKeys); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(2) - .containsEntry("test", "value") + .containsEntry("org/slf4j/test", "value") .containsEntry("test1", "value1"); - LoggingUtils.removeKeys("test", "test1"); + LoggingUtils.removeKeys("org/slf4j/test", "test1"); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .isEmpty(); } } \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java index 54d87d5cb..07ead4a64 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java @@ -19,12 +19,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowerLogToolApiGatewayHttpApiCorrelationId implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolApiGatewayHttpApiCorrelationId.class); + private final Logger LOG = LoggerFactory.getLogger(PowerLogToolApiGatewayHttpApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_HTTP) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java index 2b6e5a8d4..9e0eeee6b 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java @@ -19,12 +19,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowerLogToolApiGatewayRestApiCorrelationId implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolApiGatewayRestApiCorrelationId.class); + private final Logger LOG = LoggerFactory.getLogger(PowerLogToolApiGatewayRestApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_REST) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java index df68ea14f..49dca7a8e 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java @@ -16,12 +16,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowerLogToolEnabled implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolEnabled.class); + private final Logger LOG = LoggerFactory.getLogger(PowerLogToolEnabled.class); @Override @Logging diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java index 357520395..c0b40310d 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java @@ -16,12 +16,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowerLogToolSamplingEnabled implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowerLogToolSamplingEnabled.class); + private final Logger LOG = LoggerFactory.getLogger(PowerLogToolSamplingEnabled.class); @Override @Logging(samplingRate = 1.0) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java deleted file mode 100644 index 8a960fa87..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabled.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import software.amazon.lambda.powertools.logging.Logging; - -public class PowerToolLogEventEnabled implements RequestHandler { - - @Logging(logEvent = true) - @Override - public Object handleRequest(Object input, Context context) { - return null; - } -} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java deleted file mode 100644 index 9de76586f..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledForStream.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Map; -import software.amazon.lambda.powertools.logging.Logging; - -public class PowerToolLogEventEnabledForStream implements RequestStreamHandler { - - @Logging(logEvent = true) - @Override - public void handleRequest(InputStream input, OutputStream output, Context context) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.writeValue(output, mapper.readValue(input, Map.class)); - } -} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java deleted file mode 100644 index 838de1216..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventEnabledWithCustomMapper.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import java.io.IOException; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.logging.LoggingUtils; - -public class PowerToolLogEventEnabledWithCustomMapper implements RequestHandler { - - static { - ObjectMapper objectMapper = new ObjectMapper(); - SimpleModule module = new SimpleModule(); - module.addSerializer(S3EventNotification.class, new S3EventNotificationSerializer()); - objectMapper.registerModule(module); - LoggingUtils.defaultObjectMapper(objectMapper); - } - - @Logging(logEvent = true) - @Override - public Object handleRequest(S3EventNotification input, Context context) { - return null; - } - - static class S3EventNotificationSerializer extends StdSerializer { - - public S3EventNotificationSerializer() { - this(null); - } - - public S3EventNotificationSerializer(Class t) { - super(t); - } - - @Override - public void serialize(S3EventNotification o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) - throws IOException { - jsonGenerator.writeStartObject(); - jsonGenerator.writeStringField("eventSource", o.getRecords().get(0).getEventSource()); - jsonGenerator.writeEndObject(); - } - } -} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java index a32e3e06e..021caf185 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java @@ -19,12 +19,12 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowertoolsLogAlbCorrelationId implements RequestHandler { - private final Logger LOG = LogManager.getLogger(PowertoolsLogAlbCorrelationId.class); + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogAlbCorrelationId.class); @Override @Logging(correlationIdPath = APPLICATION_LOAD_BALANCER) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java deleted file mode 100644 index f21d9f118..000000000 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledWithClearState.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.handlers; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.logging.LoggingUtils; - -public class PowertoolsLogEnabledWithClearState implements RequestHandler { - private static final Logger LOG = LogManager.getLogger(PowertoolsLogEnabledWithClearState.class); - public static int COUNT = 1; - - @Override - @Logging(clearState = true) - public Object handleRequest(Object input, Context context) { - if (COUNT == 1) { - LoggingUtils.appendKey("TestKey", "TestValue"); - } - LOG.info("Test event"); - COUNT++; - return null; - } -} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 942f79d32..6e3e8ec92 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -32,8 +32,8 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; import com.amazonaws.services.lambda.runtime.tests.annotations.Event; +import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.BufferedReader; @@ -59,6 +59,28 @@ import org.junit.jupiter.params.ParameterizedTest; import org.mockito.Mock; import org.mockito.MockedStatic; +import org.slf4j.MDC; +import org.slf4j.event.Level; +import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.core.internal.SystemWrapper; +import software.amazon.lambda.powertools.logging.handlers.*; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; @@ -86,7 +108,7 @@ class LambdaLoggingAspectTest { @BeforeEach void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { openMocks(this); - ThreadContext.clearAll(); + MDC.clear(); writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); setupContext(); requestHandler = new PowerLogToolEnabled(); @@ -100,15 +122,15 @@ void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, void shouldSetLambdaContextWhenEnabled() { requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry(DefaultLambdaFields.FUNCTION_ARN.getName(), "testArn") - .containsEntry(DefaultLambdaFields.FUNCTION_MEMORY_SIZE.getName(), "10") - .containsEntry(DefaultLambdaFields.FUNCTION_VERSION.getName(), "1") - .containsEntry(DefaultLambdaFields.FUNCTION_NAME.getName(), "testFunction") - .containsEntry(DefaultLambdaFields.FUNCTION_REQUEST_ID.getName(), "RequestId") - .containsKey("coldStart") - .containsKey("service"); + .containsEntry(FUNCTION_ARN.getName(), "testArn") + .containsEntry(FUNCTION_MEMORY_SIZE.getName(), "10") + .containsEntry(FUNCTION_VERSION.getName(), "1") + .containsEntry(FUNCTION_NAME.getName(), "testFunction") + .containsEntry(FUNCTION_REQUEST_ID.getName(), "RequestId") + .containsKey(FUNCTION_COLD_START.getName()) + .containsKey(SERVICE.getName()); } @Test @@ -118,15 +140,15 @@ void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry(DefaultLambdaFields.FUNCTION_ARN.getName(), "testArn") - .containsEntry(DefaultLambdaFields.FUNCTION_MEMORY_SIZE.getName(), "10") - .containsEntry(DefaultLambdaFields.FUNCTION_VERSION.getName(), "1") - .containsEntry(DefaultLambdaFields.FUNCTION_NAME.getName(), "testFunction") - .containsEntry(DefaultLambdaFields.FUNCTION_REQUEST_ID.getName(), "RequestId") - .containsKey("coldStart") - .containsKey("service"); + .containsEntry(FUNCTION_ARN.getName(), "testArn") + .containsEntry(FUNCTION_MEMORY_SIZE.getName(), "10") + .containsEntry(FUNCTION_VERSION.getName(), "1") + .containsEntry(FUNCTION_NAME.getName(), "testFunction") + .containsEntry(FUNCTION_REQUEST_ID.getName(), "RequestId") + .containsKey(FUNCTION_COLD_START.getName()) + .containsKey(SERVICE.getName()); } @Test @@ -134,16 +156,16 @@ void shouldSetColdStartFlag() throws IOException { requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("coldStart", "true"); + .containsEntry(FUNCTION_COLD_START.getName(), "true"); requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("coldStart", "false"); + .containsEntry(FUNCTION_COLD_START.getName(), "false"); } @Test @@ -152,8 +174,7 @@ void shouldNotSetLambdaContextWhenDisabled() { requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + assertThat(MDC.getCopyOfContextMap()).isNull(); } @Test @@ -162,8 +183,7 @@ void shouldNotSetLambdaContextForStreamHandlerWhenDisabled() throws IOException requestStreamHandler.handleRequest(null, null, context); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + assertThat(MDC.getCopyOfContextMap()).isNull(); } @Test @@ -172,8 +192,7 @@ void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { handler.anotherMethod(); - assertThat(ThreadContext.getImmutableContext()) - .isEmpty(); + assertThat(MDC.getCopyOfContextMap()).isNull(); } @Test @@ -240,9 +259,9 @@ void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { writeStaticField(LambdaHandlerProcessor.class, "SERVICE_NAME", "testService", true); requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) - .containsEntry("service", "testService"); + .containsEntry(SERVICE.getName(), "testService"); } @Test @@ -255,9 +274,9 @@ void shouldLogxRayTraceIdEnvVarSet() { requestHandler.handleRequest(new Object(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) - .containsEntry("xray_trace_id", xRayTraceId); + .containsEntry(FUNCTION_TRACE_ID.getName(), xRayTraceId); } } @@ -267,7 +286,7 @@ void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestE RequestHandler handler = new PowerLogToolApiGatewayRestApiCorrelationId(); handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); } @@ -278,7 +297,7 @@ void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) RequestHandler handler = new PowerLogToolApiGatewayHttpApiCorrelationId(); handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); } @@ -289,7 +308,7 @@ void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) RequestHandler handler = new PowertoolsLogAlbCorrelationId(); handler.handleRequest(event, context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getHeaders().get("x-amzn-trace-id")); } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java new file mode 100644 index 000000000..17ace1689 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java @@ -0,0 +1,31 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; +import org.slf4j.test.TestLogger; +import org.slf4j.test.TestLoggerFactory; + +public class TestLoggingManager implements LoggingManager { + + private final TestLoggerFactory loggerFactory; + + public TestLoggingManager() { + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (!(loggerFactory instanceof TestLoggerFactory)) { + throw new RuntimeException("LoggerFactory does not match required type: " + TestLoggerFactory.class.getName()); + } + this.loggerFactory = (TestLoggerFactory) loggerFactory; + } + + @Override + public void resetLogLevel(Level logLevel) { + loggerFactory.getLoggers().forEach( (key, logger) -> ((TestLogger) logger).setLogLevel(logLevel.toString())); + } + + @Override + public Level getLogLevel(Logger logger) { + return org.slf4j.event.Level.intToLevel(((TestLogger) logger).getLogLevel()); + } +} diff --git a/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider new file mode 100644 index 000000000..ade4bb1e2 --- /dev/null +++ b/powertools-logging/src/test/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider @@ -0,0 +1 @@ +org.slf4j.test.TestServiceProvider \ No newline at end of file diff --git a/powertools-logging/src/test/resources/testlogger.properties b/powertools-logging/src/test/resources/testlogger.properties new file mode 100644 index 000000000..84b7beaae --- /dev/null +++ b/powertools-logging/src/test/resources/testlogger.properties @@ -0,0 +1,3 @@ +org.slf4j.simpleLogger.defaultLogLevel=warn +org.slf4j.simpleLogger.log.software.amazon.lambda.powertools=info +org.slf4j.simpleLogger.logFile=target/logfile.json \ No newline at end of file From 6bda23cb395459c40c55a79a66d0f266e4d0f1f4 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 22 Nov 2022 18:05:52 +0100 Subject: [PATCH 02/74] log4j2 implementation of the logging module --- powertools-logging-log4j/pom.xml | 118 +++++++++++ .../logging/internal/Log4jLoggingManager.java | 25 +++ .../logging/internal/PowertoolsResolver.java | 187 ++++++++++++++++++ .../internal/PowertoolsResolverFactory.java | 8 +- .../src/main/resources/LambdaEcsLayout.json | 94 +++++++++ .../src/main/resources/LambdaJsonLayout.json | 72 +++++++ .../internal/Log4jLoggingManagerTest.java | 37 ++++ .../src/test/resources/log4j2.xml | 8 +- 8 files changed, 539 insertions(+), 10 deletions(-) create mode 100644 powertools-logging-log4j/pom.xml create mode 100644 powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java create mode 100644 powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java rename {powertools-logging => powertools-logging-log4j}/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java (77%) create mode 100644 powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json create mode 100644 powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json create mode 100644 powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java rename {powertools-logging => powertools-logging-log4j}/src/test/resources/log4j2.xml (63%) diff --git a/powertools-logging-log4j/pom.xml b/powertools-logging-log4j/pom.xml new file mode 100644 index 000000000..be401d6b8 --- /dev/null +++ b/powertools-logging-log4j/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + + powertools-parent + software.amazon.lambda + 1.12.3 + + + powertools-logging-log4j + jar + + AWS Lambda Powertools for Java library Logging with Log4j2 + + A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. + + https://aws.amazon.com/lambda/ + + GitHub Issues + https://github.com/awslabs/aws-lambda-powertools-java/issues + + + https://github.com/awslabs/aws-lambda-powertools-java.git + + + + AWS Lambda Powertools team + Amazon Web Services + https://aws.amazon.com/ + + + + + + ossrh + https://aws.oss.sonatype.org/content/repositories/snapshots + + + + + + software.amazon.lambda + powertools-logging + ${version} + + + org.apache.logging.log4j + log4j-slf4j2-impl + provided + + + org.apache.logging.log4j + log4j-core + provided + + + org.apache.logging.log4j + log4j-layout-template-json + provided + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-inline + test + + + org.aspectj + aspectjweaver + test + + + org.assertj + assertj-core + test + + + com.amazonaws + aws-lambda-java-events + test + + + com.amazonaws + aws-lambda-java-tests + test + + + org.skyscreamer + jsonassert + test + + + + \ No newline at end of file diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java new file mode 100644 index 000000000..bff21b1aa --- /dev/null +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -0,0 +1,25 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configurator; +import org.slf4j.Logger; + +public class Log4jLoggingManager implements LoggingManager { + + @Override + public void resetLogLevel(org.slf4j.event.Level logLevel) { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.getLevel(logLevel.toString())); + ctx.updateLoggers(); + } + + @Override + public org.slf4j.event.Level getLogLevel(Logger logger) { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + return org.slf4j.event.Level.valueOf(ctx.getLogger(logger.getName()).getLevel().toString()); + } + + +} diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java new file mode 100644 index 000000000..43e17dd25 --- /dev/null +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java @@ -0,0 +1,187 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.ReadOnlyStringMap; + +final class PowertoolsResolver implements EventResolver { + + private static final EventResolver COLD_START_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String coldStart = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + return null != coldStart; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String coldStart = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + jsonWriter.writeBoolean(Boolean.parseBoolean(coldStart)); + } + }; + + private static final EventResolver FUNCTION_NAME_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionName = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_NAME.getName()); + jsonWriter.writeString(functionName); + }; + + private static final EventResolver FUNCTION_VERSION_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionVersion = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_VERSION.getName()); + jsonWriter.writeString(functionVersion); + }; + + private static final EventResolver FUNCTION_ARN_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionArn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + jsonWriter.writeString(functionArn); + }; + + private static final EventResolver FUNCTION_REQ_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String functionRequestId = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName()); + jsonWriter.writeString(functionRequestId); + }; + + private static final EventResolver FUNCTION_MEMORY_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String functionMemory = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + return null != functionMemory; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String functionMemory = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + jsonWriter.writeNumber(Integer.parseInt(functionMemory)); + } + }; + + private static final EventResolver SAMPLING_RATE_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String samplingRate = logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + return null != samplingRate; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String samplingRate = logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + jsonWriter.writeNumber(Float.parseFloat(samplingRate)); + } + }; + + private static final EventResolver XRAY_TRACE_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String traceId = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + jsonWriter.writeString(traceId); + }; + + private static final EventResolver SERVICE_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> { + final String service = logEvent.getContextData().getValue(PowertoolsLoggedFields.SERVICE.getName()); + jsonWriter.writeString(service); + }; + + private static final EventResolver REGION_RESOLVER = + (final LogEvent logEvent, final JsonWriter jsonWriter) -> + jsonWriter.writeString(System.getenv("AWS_REGION")); + + private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String arn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + return null != arn && !arn.isEmpty(); + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String arn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + jsonWriter.writeString(arn.split(":")[4]); + } + }; + + private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER = + (LogEvent logEvent, JsonWriter jsonWriter) -> { + StringBuilder stringBuilder = jsonWriter.getStringBuilder(); + // remove dummy field to kick inn powertools resolver + stringBuilder.setLength(stringBuilder.length() - 4); + + // Inject all the context information. + ReadOnlyStringMap contextData = logEvent.getContextData(); + contextData.forEach((key, value) -> { + if (!PowertoolsLoggedFields.stringValues().contains(key)) { + jsonWriter.writeSeparator(); + jsonWriter.writeString(key); + stringBuilder.append(':'); + jsonWriter.writeValue(value); + } + }); + }; + + private final EventResolver internalResolver; + + PowertoolsResolver(final TemplateResolverConfig config) { + final String fieldName = config.getString("field"); + if (fieldName == null) { + internalResolver = NON_POWERTOOLS_FIELD_RESOLVER; + } else { + switch (fieldName) { + case "service": + internalResolver = SERVICE_RESOLVER; + break; + case "function_name": + internalResolver = FUNCTION_NAME_RESOLVER; + break; + case "function_version": + case "service_version": + internalResolver = FUNCTION_VERSION_RESOLVER; + break; + case "function_arn": + internalResolver = FUNCTION_ARN_RESOLVER; + break; + case "function_memory_size": + internalResolver = FUNCTION_MEMORY_RESOLVER; + break; + case "function_request_id": + internalResolver = FUNCTION_REQ_RESOLVER; + break; + case "cold_start": + internalResolver = COLD_START_RESOLVER; + break; + case "xray_trace_id": + internalResolver = XRAY_TRACE_RESOLVER; + break; + case "region": + internalResolver = REGION_RESOLVER; + break; + case "account_id": + internalResolver = ACCOUNT_ID_RESOLVER; + break; + case "sampling_rate": + internalResolver = SAMPLING_RATE_RESOLVER; + break; + default: + throw new IllegalArgumentException("unknown field: " + fieldName); + } + } + } + + static String getName() { + return "powertools"; + } + + @Override + public void resolve(LogEvent value, JsonWriter jsonWriter) { + internalResolver.resolve(value, jsonWriter); + } + + @Override + public boolean isResolvable(LogEvent value) { + ReadOnlyStringMap contextData = value.getContextData(); + return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable(); + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java similarity index 77% rename from powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java rename to powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java index 7d688f469..7070b1fe2 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java @@ -17,11 +17,7 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; +import org.apache.logging.log4j.layout.template.json.resolver.*; @Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) public final class PowertoolsResolverFactory implements EventResolverFactory { @@ -44,6 +40,6 @@ public String getName() { @Override public TemplateResolver create(EventResolverContext context, TemplateResolverConfig config) { - return new PowertoolsResolver(); + return new PowertoolsResolver(config); } } diff --git a/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json new file mode 100644 index 000000000..102ba5ec8 --- /dev/null +++ b/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json @@ -0,0 +1,94 @@ +{ + "@timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + "timeZone": "UTC" + } + }, + "ecs.version": "1.2.0", + "log.level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message", + "stringified": true + }, + "service.name": { + "$resolver": "powertools", + "field": "service" + }, + "service.version": { + "$resolver": "powertools", + "field": "service_version" + }, + "event.dataset": { + "$resolver": "powertools", + "field": "service_name" + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "error.type": { + "$resolver": "exception", + "field": "className" + }, + "error.message": { + "$resolver": "exception", + "field": "message" + }, + "error.stack_trace": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + }, + "cloud.provider" : "aws", + "cloud.service.name" : "lambda", + "cloud.region" : { + "$resolver": "powertools", + "field": "region" + }, + "cloud.account.id" : { + "$resolver": "powertools", + "field": "account_id" + }, + "faas.coldstart": { + "$resolver": "powertools", + "field": "cold_start" + }, + "faas.id": { + "$resolver": "powertools", + "field": "function_arn" + }, + "faas.memory": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "faas.name": { + "$resolver": "powertools", + "field": "function_name" + }, + "faas.execution": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "faas.version": { + "$resolver": "powertools", + "field": "function_version" + }, + "": { + "$resolver": "powertools" + }, + "trace.id": { + "$resolver": "powertools", + "field": "xray_trace_id" + } +} \ No newline at end of file diff --git a/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json new file mode 100644 index 000000000..f5caf3924 --- /dev/null +++ b/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json @@ -0,0 +1,72 @@ +{ + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, + "error": { + "message": { + "$resolver": "exception", + "field": "message" + }, + "name": { + "$resolver": "exception", + "field": "className" + }, + "stack": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "function_arn": { + "$resolver": "powertools", + "field": "function_arn" + }, + "function_memory_size": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "function_name": { + "$resolver": "powertools", + "field": "function_name" + }, + "function_request_id": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "function_version": { + "$resolver": "powertools", + "field": "function_version" + }, + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message", + "stringified": true + }, + "sampling_rate": { + "$resolver": "powertools", + "field": "sampling_rate" + }, + "service": { + "$resolver": "powertools", + "field": "service" + }, + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZz" + } + }, + "xray_trace_id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "": { + "$resolver": "powertools" + } +} diff --git a/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java new file mode 100644 index 000000000..9bd1c7550 --- /dev/null +++ b/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -0,0 +1,37 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.*; + +public class Log4jLoggingManagerTest { + + private static Logger LOG = LoggerFactory.getLogger(Log4jLoggingManagerTest.class); + private static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Test + @Order(1) + public void getLogLevel_shouldReturnConfiguredLogLevel() { + Log4jLoggingManager manager = new Log4jLoggingManager(); + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(INFO); + + logLevel = manager.getLogLevel(ROOT); + assertThat(logLevel).isEqualTo(WARN); + } + + @Test + @Order(2) + public void resetLogLevel() { + Log4jLoggingManager manager = new Log4jLoggingManager(); + manager.resetLogLevel(ERROR); + + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(ERROR); + } +} diff --git a/powertools-logging/src/test/resources/log4j2.xml b/powertools-logging-log4j/src/test/resources/log4j2.xml similarity index 63% rename from powertools-logging/src/test/resources/log4j2.xml rename to powertools-logging-log4j/src/test/resources/log4j2.xml index 22a44ee8b..489b70809 100644 --- a/powertools-logging/src/test/resources/log4j2.xml +++ b/powertools-logging-log4j/src/test/resources/log4j2.xml @@ -1,15 +1,15 @@ - + - + - + - + From 3d6ef5d834a26f77fb4df28fb6d97cd75971a94f Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 22 Nov 2022 18:06:30 +0100 Subject: [PATCH 03/74] logback implementation of the logging module --- .../LambdaJsonLayout.java | 137 ++++++++++++++ powertools-logging-logback/pom.xml | 113 ++++++++++++ .../powertools/logging/LambdaEcsEncoder.java | 94 ++++++++++ .../powertools/logging/LambdaJsonEncoder.java | 86 +++++++++ .../logging/internal/JsonUtils.java | 90 +++++++++ .../logging/internal/LambdaEcsSerializer.java | 171 ++++++++++++++++++ .../internal/LambdaJsonSerializer.java | 96 ++++++++++ .../internal/LogbackLoggingManager.java | 35 ++++ .../internal/LogbackLoggingManagerTest.java | 37 ++++ .../src/test/resources/logback-test.xml | 13 ++ 10 files changed, 872 insertions(+) create mode 100644 powertools-logging-logback/LambdaJsonLayout.java create mode 100644 powertools-logging-logback/pom.xml create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java create mode 100644 powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java create mode 100644 powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java create mode 100644 powertools-logging-logback/src/test/resources/logback-test.xml diff --git a/powertools-logging-logback/LambdaJsonLayout.java b/powertools-logging-logback/LambdaJsonLayout.java new file mode 100644 index 000000000..7c9b59ad7 --- /dev/null +++ b/powertools-logging-logback/LambdaJsonLayout.java @@ -0,0 +1,137 @@ +package software.amazon.lambda.powertools.logging; + +import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.LayoutBase; + +/** + * Custom layout for logback that encodes logs in JSON format. + * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. + */ +public class LambdaJsonLayout extends LayoutBase { + private final static String CONTENT_TYPE = "application/json"; + private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); + private ThrowableHandlingConverter throwableConverter; + private String timestampFormat; + private String timestampFormatTimezoneId; + private boolean includeThreadInfo; + + @Override + public String doLayout(ILoggingEvent event) { + StringBuilder builder = new StringBuilder(256); + LambdaJsonSerializer.serializeObjectStart(builder); + LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel()); + LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); + IThrowableProxy throwableProxy = event.getThrowableProxy(); + if (throwableProxy != null) { + if (throwableConverter != null) { + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); + } else if (throwableProxy instanceof ThrowableProxy) { + LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); + } else { + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); + } + } + LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); + if (includeThreadInfo) { + LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName()); + LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); + LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority())); + } + LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId); + LambdaJsonSerializer.serializeObjectEnd(builder); + return builder.toString(); + } + + @Override + public String getContentType() { + return CONTENT_TYPE; + } + + public void setTimestampFormat(String timestampFormat) { + this.timestampFormat = timestampFormat; + } + + public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { + this.timestampFormatTimezoneId = timestampFormatTimezoneId; + } + + public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { + this.throwableConverter = throwableConverter; + } + +// public static final String INSTANT_ATTR_NAME = "instant"; +// public static final String EPOCH_SEC_ATTR_NAME = "epochSecond"; +// public static final String NANO_SEC_ATTR_NAME = "nanoOfSecond"; +// public static final String LOGGER_FQCN_ATTR_NAME = "loggerFqcn"; +// public static final String LOGGER_ATTR_NAME = "loggerName"; +// public static final String THREAD_ID_ATTR_NAME = "threadId"; +// public static final String THREAD_PRIORITY_ATTR_NAME = "threadPriority"; +// +// private boolean includePowertools; +// private boolean includeInstant; +// private boolean includeThreadInfo; +// +// public LambdaJsonLayout() { +// super(); +// this.includeInstant = true; +// this.includePowertools = true; +// this.includeThreadInfo = true; +// } +// +// @Override +// protected Map toJsonMap(ILoggingEvent event) { +// Map map = new LinkedHashMap<>(); +// addTimestamp(TIMESTAMP_ATTR_NAME, this.includeTimestamp, event.getTimeStamp(), map); +// addInstant(this.includeInstant, event.getTimeStamp(), event.getNanoseconds(), map); +// add(THREAD_ATTR_NAME, this.includeThreadName || this.includeThreadInfo, event.getThreadName(), map); +// add(LEVEL_ATTR_NAME, this.includeLevel, String.valueOf(event.getLevel()), map); +// add(LOGGER_ATTR_NAME, this.includeLoggerName, event.getLoggerName(), map); +// add(FORMATTED_MESSAGE_ATTR_NAME, this.includeFormattedMessage, event.getFormattedMessage(), map); +// addThrowableInfo(EXCEPTION_ATTR_NAME, this.includeException, event, map); +// // contextStack ? +// // endOfBatch ? +// map.put(LOGGER_FQCN_ATTR_NAME, "ch.qos.logback.classic.Logger"); +// add(THREAD_ID_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getId()), map); +// add(THREAD_PRIORITY_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getPriority()), map); +// addPowertools(this.includePowertools, event.getMDCPropertyMap(), map); +// return map; +// } +// +// private void addPowertools(boolean includePowertools, Map mdcPropertyMap, Map map) { +// TreeMap sortedMap = new TreeMap<>(mdcPropertyMap); +// List powertoolsFields = DefaultLambdaFields.stringValues(); +// +// sortedMap.forEach((k, v) -> { +// if (includePowertools || !powertoolsFields.contains(k)) { +// map.put(k, v); +// } +// }); +// +// } +// +// private void addInstant(boolean includeInstant, long timeStamp, int nanoseconds, Map map) { +// if (includeInstant) { +// Map instantMap = new LinkedHashMap<>(); +// instantMap.put(EPOCH_SEC_ATTR_NAME, timeStamp / 1000); +// instantMap.put(NANO_SEC_ATTR_NAME, nanoseconds); +// map.put(LambdaJsonLayout.INSTANT_ATTR_NAME, instantMap); +// } +// } +// +// public void setIncludeInstant(boolean includeInstant) { +// this.includeInstant = includeInstant; +// } +// +// public void setIncludePowertools(boolean includePowertools) { +// this.includePowertools = includePowertools; +// } +// + public void setIncludeThreadInfo(boolean includeThreadInfo) { + this.includeThreadInfo = includeThreadInfo; + } + +} diff --git a/powertools-logging-logback/pom.xml b/powertools-logging-logback/pom.xml new file mode 100644 index 000000000..94358f549 --- /dev/null +++ b/powertools-logging-logback/pom.xml @@ -0,0 +1,113 @@ + + + + powertools-parent + software.amazon.lambda + 1.12.3 + + 4.0.0 + + powertools-logging-logback + AWS Lambda Powertools for Java library Logging with LogBack + + A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. + + https://aws.amazon.com/lambda/ + + GitHub Issues + https://github.com/awslabs/aws-lambda-powertools-java/issues + + + https://github.com/awslabs/aws-lambda-powertools-java.git + + + + AWS Lambda Powertools team + Amazon Web Services + https://aws.amazon.com/ + + + + + + ossrh + https://aws.oss.sonatype.org/content/repositories/snapshots + + + + + + + software.amazon.lambda + powertools-logging + ${version} + + + ch.qos.logback + logback-classic + 1.3.4 + provided + + + com.sun.mail + javax.mail + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.apache.commons + commons-lang3 + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-inline + test + + + org.aspectj + aspectjweaver + test + + + org.assertj + assertj-core + test + + + com.amazonaws + aws-lambda-java-events + test + + + com.amazonaws + aws-lambda-java-tests + test + + + org.skyscreamer + jsonassert + test + + + + \ No newline at end of file diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java new file mode 100644 index 000000000..c501c439b --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -0,0 +1,94 @@ +package software.amazon.lambda.powertools.logging; + +import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.encoder.EncoderBase; +import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer; + +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*; + +/** + * This class will encode the logback event into the format expected by the ECS service (ElasticSearch). + *
+ * Inspired from co.elastic.logging.logback.EcsEncoder, this class doesn't use + * any JSON (de)serialization library (Jackson, Gson, etc.) or Elastic library to avoid the dependency. + *
+ * This encoder also adds cloud information (see doc) + * and Lambda function information (see doc, currently in beta). + */ +public class LambdaEcsEncoder extends EncoderBase { + + protected static final String ECS_VERSION = "1.2.0"; + protected static final String CLOUD_PROVIDER = "aws"; + protected static final String CLOUD_SERVICE = "lambda"; + + private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); + protected ThrowableHandlingConverter throwableConverter = null; + + @Override + public byte[] headerBytes() { + return null; + } + + @Override + public byte[] encode(ILoggingEvent event) { + Map mdcPropertyMap = event.getMDCPropertyMap(); + + StringBuilder builder = new StringBuilder(256); + LambdaEcsSerializer.serializeObjectStart(builder); + LambdaEcsSerializer.serializeTimestamp(builder, event.getTimeStamp(), "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "UTC"); + LambdaEcsSerializer.serializeEcsVersion(builder, ECS_VERSION); + LambdaEcsSerializer.serializeLogLevel(builder, event.getLevel()); + LambdaEcsSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); + LambdaEcsSerializer.serializeServiceName(builder, LambdaHandlerProcessor.serviceName()); + LambdaEcsSerializer.serializeServiceVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + // TODO : Environment ? + LambdaEcsSerializer.serializeEventDataset(builder, LambdaHandlerProcessor.serviceName()); + LambdaEcsSerializer.serializeThreadName(builder, event.getThreadName()); + LambdaEcsSerializer.serializeLoggerName(builder, event.getLoggerName()); + IThrowableProxy throwableProxy = event.getThrowableProxy(); + if (throwableProxy != null) { + if (throwableConverter != null) { + LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event)); + } else if (throwableProxy instanceof ThrowableProxy) { + LambdaEcsSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); + } else { + LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event)); + } + } + LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER); + LambdaEcsSerializer.serializeCloudService(builder, CLOUD_SERVICE); + String arn = mdcPropertyMap.get(FUNCTION_ARN.getName()); + if (arn != null) { + String[] arnParts = arn.split(":"); + LambdaEcsSerializer.serializeCloudRegion(builder, arnParts[3]); + LambdaEcsSerializer.serializeCloudAccountId(builder, arnParts[4]); + } + LambdaEcsSerializer.serializeFunctionId(builder, arn); + LambdaEcsSerializer.serializeFunctionName(builder, mdcPropertyMap.get(FUNCTION_NAME.getName())); + LambdaEcsSerializer.serializeFunctionVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + LambdaEcsSerializer.serializeFunctionMemory(builder, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName())); + LambdaEcsSerializer.serializeFunctionExecutionId(builder, mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName())); + LambdaEcsSerializer.serializeColdStart(builder, mdcPropertyMap.get(FUNCTION_COLD_START.getName())); + LambdaEcsSerializer.serializeAdditionalFields(builder, event.getMDCPropertyMap()); + LambdaEcsSerializer.serializeTraceId(builder, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName())); + LambdaEcsSerializer.serializeObjectEnd(builder); + return builder.toString().getBytes(UTF_8); + } + + @Override + public byte[] footerBytes() { + return null; + } + + public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { + this.throwableConverter = throwableConverter; + } +} diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java new file mode 100644 index 000000000..7df90a4ad --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -0,0 +1,86 @@ +package software.amazon.lambda.powertools.logging; + +import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; +import ch.qos.logback.classic.pattern.ThrowableProxyConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.ThrowableProxy; +import ch.qos.logback.core.encoder.EncoderBase; +import software.amazon.lambda.powertools.logging.internal.LambdaJsonSerializer; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Custom encoder for logback that encodes logs in JSON format. + * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. + */ +public class LambdaJsonEncoder extends EncoderBase { + + private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); + protected ThrowableHandlingConverter throwableConverter = null; + protected String timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZz"; + protected String timestampFormatTimezoneId = null; + private boolean includeThreadInfo = false; + + @Override + public byte[] headerBytes() { + return null; + } + + @Override + public void start() { + super.start(); + throwableProxyConverter.start(); + if (throwableConverter != null) { + throwableConverter.start(); + } + } + + @Override + public byte[] encode(ILoggingEvent event) { + StringBuilder builder = new StringBuilder(256); + LambdaJsonSerializer.serializeObjectStart(builder); + LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel()); + LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); + IThrowableProxy throwableProxy = event.getThrowableProxy(); + if (throwableProxy != null) { + if (throwableConverter != null) { + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event)); + } else if (throwableProxy instanceof ThrowableProxy) { + LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); + } else { + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event)); + } + } + LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); + if (includeThreadInfo) { + LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName()); + LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); + LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority())); + } + LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId); + LambdaJsonSerializer.serializeObjectEnd(builder); + return builder.toString().getBytes(UTF_8); + } + + @Override + public byte[] footerBytes() { + return null; + } + + public void setTimestampFormat(String timestampFormat) { + this.timestampFormat = timestampFormat; + } + + public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { + this.timestampFormatTimezoneId = timestampFormatTimezoneId; + } + + public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { + this.throwableConverter = throwableConverter; + } + + public void setIncludeThreadInfo(boolean includeThreadInfo) { + this.includeThreadInfo = includeThreadInfo; + } +} diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java new file mode 100644 index 000000000..f073050f9 --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java @@ -0,0 +1,90 @@ +package software.amazon.lambda.powertools.logging.internal; + +public class JsonUtils { + + protected static void serializeAttribute(StringBuilder builder, String attr, String value, boolean notBegin) { + if (value != null) { + if (notBegin) { + builder.append(", "); + } + builder.append("\"").append(attr).append("\": "); + boolean isString = isString(value); + if (isString) builder.append("\""); + builder.append(value); + if (isString) builder.append("\""); + } + } + + protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value, boolean notBegin) { + if (value != null) { + if (notBegin) { + builder.append(", "); + } + builder.append("\"") + .append(attr) + .append("\": \"") + .append(value) + .append("\""); + } + } + + protected static void serializeAttribute(StringBuilder builder, String attr, String value) { + serializeAttribute(builder, attr, value, true); + } + + protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value) { + serializeAttributeAsString(builder, attr, value, true); + } + + /** + * As MDC is a Map, we need to check the type to output numbers and booleans correctly (without quotes) + */ + private static boolean isString(String str) { + if (str == null) { + return true; + } + if (str.equals("true") || str.equals("false")) { + return false; // boolean + } + return !isNumeric(str); // number + } + + /** + * Taken from commons-lang3 NumberUtils to avoid include the library + */ + private static boolean isNumeric(final String str) { + if (str == null || str.length() == 0) { + return false; + } + if (str.charAt(str.length() - 1) == '.') { + return false; + } + if (str.charAt(0) == '-') { + if (str.length() == 1) { + return false; + } + return withDecimalsParsing(str, 1); + } + return withDecimalsParsing(str, 0); + } + + /** + * Taken from commons-lang3 NumberUtils + */ + private static boolean withDecimalsParsing(final String str, final int beginIdx) { + int decimalPoints = 0; + for (int i = beginIdx; i < str.length(); i++) { + final boolean isDecimalPoint = str.charAt(i) == '.'; + if (isDecimalPoint) { + decimalPoints++; + } + if (decimalPoints > 1) { + return false; + } + if (!isDecimalPoint && !Character.isDigit(str.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java new file mode 100644 index 000000000..06c2de511 --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java @@ -0,0 +1,171 @@ +package software.amazon.lambda.powertools.logging.internal; + +import ch.qos.logback.classic.Level; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; + +import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttributeAsString; + +/** + * This class will serialize the log events in ecs format (ElasticSearch).
+ * + * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer, this class doesn't use + * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency + */ +public class LambdaEcsSerializer { + protected static final String TIMESTAMP_ATTR_NAME = "@timestamp"; + protected static final String ECS_VERSION_ATTR_NAME = "ecs.version"; + protected static final String LOGGER_ATTR_NAME = "log.logger"; + protected static final String LEVEL_ATTR_NAME = "log.level"; + protected static final String SERVICE_NAME_ATTR_NAME = "service.name"; + protected static final String SERVICE_VERSION_ATTR_NAME = "service.version"; + protected static final String SERVICE_ENV_ATTR_NAME = "service.environment"; + protected static final String EVENT_DATASET_ATTR_NAME = "event.dataset"; + protected static final String FORMATTED_MESSAGE_ATTR_NAME = "message"; + protected static final String THREAD_ATTR_NAME = "process.thread.name"; + protected static final String THREAD_ID_ATTR_NAME = "process.thread.id"; + protected static final String EXCEPTION_MSG_ATTR_NAME = "error.message"; + protected static final String EXCEPTION_CLASS_ATTR_NAME = "error.type"; + protected static final String EXCEPTION_STACK_ATTR_NAME = "error.stack_trace"; + protected static final String CLOUD_PROVIDER_ATTR_NAME = "cloud.provider"; + protected static final String CLOUD_REGION_ATTR_NAME = "cloud.region"; + protected static final String CLOUD_ACCOUNT_ATTR_NAME = "cloud.account.id"; + protected static final String CLOUD_SERVICE_ATTR_NAME = "cloud.service.name"; + protected static final String FUNCTION_COLD_START_ATTR_NAME = "faas.coldstart"; + protected static final String FUNCTION_REQUEST_ID_ATTR_NAME = "faas.execution"; + protected static final String FUNCTION_ARN_ATTR_NAME = "faas.id"; + protected static final String FUNCTION_NAME_ATTR_NAME = "faas.name"; + protected static final String FUNCTION_VERSION_ATTR_NAME = "faas.version"; + protected static final String FUNCTION_MEMORY_ATTR_NAME = "faas.memory"; + protected static final String FUNCTION_TRACE_ID_ATTR_NAME = "trace.id"; + + public static void serializeObjectStart(StringBuilder builder) { + builder.append('{'); + } + + public static void serializeObjectEnd(StringBuilder builder) { + builder.append("}\n"); + } + + public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, String timestampFormatTimezoneId) { + String formattedTimestamp; + if (timestampFormat == null || timestamp < 0) { + formattedTimestamp = String.valueOf(timestamp); + } else { + Date date = new Date(timestamp); + DateFormat format = new SimpleDateFormat(timestampFormat); + + if (timestampFormatTimezoneId != null) { + TimeZone tz = TimeZone.getTimeZone(timestampFormatTimezoneId); + format.setTimeZone(tz); + } + formattedTimestamp = format.format(date); + } + serializeAttributeAsString(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp, false); + } + + public static void serializeThreadName(StringBuilder builder, String threadName) { + if (threadName != null) { + serializeAttributeAsString(builder, THREAD_ATTR_NAME, threadName); + } + } + + public static void serializeLogLevel(StringBuilder builder, Level level) { + serializeAttributeAsString(builder, LEVEL_ATTR_NAME, level.toString()); + } + + public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { + serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + } + + public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { + serializeAttributeAsString(builder, EXCEPTION_MSG_ATTR_NAME, message); + serializeAttributeAsString(builder, EXCEPTION_CLASS_ATTR_NAME, className); + serializeAttributeAsString(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); + } + + public static void serializeException(StringBuilder builder, Throwable throwable) { + serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), Arrays.toString(throwable.getStackTrace())); + } + + public static void serializeThreadId(StringBuilder builder, String threadId) { + serializeAttributeAsString(builder, THREAD_ID_ATTR_NAME, threadId); + } + + public static void serializeAdditionalFields(StringBuilder builder, Map mdc) { + TreeMap sortedMap = new TreeMap<>(mdc); + + sortedMap.forEach((k, v) -> { + if (!PowertoolsLoggedFields.stringValues().contains(k)) { + serializeAttributeAsString(builder, k, v); + } + }); + } + + public static void serializeEcsVersion(StringBuilder builder, String ecsVersion) { + serializeAttributeAsString(builder, ECS_VERSION_ATTR_NAME, ecsVersion); + } + + public static void serializeServiceName(StringBuilder builder, String serviceName) { + serializeAttributeAsString(builder, SERVICE_NAME_ATTR_NAME, serviceName); + } + + public static void serializeServiceVersion(StringBuilder builder, String serviceVersion) { + serializeAttributeAsString(builder, SERVICE_VERSION_ATTR_NAME, serviceVersion); + } + + public static void serializeEventDataset(StringBuilder builder, String serviceName) { + serializeAttributeAsString(builder, EVENT_DATASET_ATTR_NAME, serviceName); + } + + public static void serializeLoggerName(StringBuilder builder, String loggerName) { + serializeAttributeAsString(builder, LOGGER_ATTR_NAME, loggerName); + } + + public static void serializeCloudProvider(StringBuilder builder, String cloudProvider) { + serializeAttributeAsString(builder, CLOUD_PROVIDER_ATTR_NAME, cloudProvider); + } + + public static void serializeCloudService(StringBuilder builder, String cloudService) { + serializeAttributeAsString(builder, CLOUD_SERVICE_ATTR_NAME, cloudService); + } + + public static void serializeCloudRegion(StringBuilder builder, String cloudRegion) { + serializeAttributeAsString(builder, CLOUD_REGION_ATTR_NAME, cloudRegion); + } + + public static void serializeCloudAccountId(StringBuilder builder, String cloudAccountId) { + serializeAttributeAsString(builder, CLOUD_ACCOUNT_ATTR_NAME, cloudAccountId); + } + + public static void serializeColdStart(StringBuilder builder, String coldStart) { + serializeAttributeAsString(builder, FUNCTION_COLD_START_ATTR_NAME, coldStart); + } + + public static void serializeFunctionExecutionId(StringBuilder builder, String requestId) { + serializeAttributeAsString(builder, FUNCTION_REQUEST_ID_ATTR_NAME, requestId); + } + + public static void serializeFunctionId(StringBuilder builder, String functionArn) { + serializeAttributeAsString(builder, FUNCTION_ARN_ATTR_NAME, functionArn); + } + + public static void serializeFunctionName(StringBuilder builder, String functionName) { + serializeAttributeAsString(builder, FUNCTION_NAME_ATTR_NAME, functionName); + } + + public static void serializeFunctionVersion(StringBuilder builder, String functionVersion) { + serializeAttributeAsString(builder, FUNCTION_VERSION_ATTR_NAME, functionVersion); + } + + public static void serializeFunctionMemory(StringBuilder builder, String functionMemory) { + serializeAttributeAsString(builder, FUNCTION_MEMORY_ATTR_NAME, functionMemory); + } + + public static void serializeTraceId(StringBuilder builder, String traceId) { + serializeAttributeAsString(builder, FUNCTION_TRACE_ID_ATTR_NAME, traceId); + } +} diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java new file mode 100644 index 000000000..1c368679b --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -0,0 +1,96 @@ +package software.amazon.lambda.powertools.logging.internal; + +import ch.qos.logback.classic.Level; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; + +import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttribute; + +/** + * This class will serialize the log events in json.
+ * + * Inspired from the ElasticSearch Serializer {@link co.elastic.logging.EcsJsonSerializer}, this class doesn't use + * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency + */ +public class LambdaJsonSerializer { + protected static final String TIMESTAMP_ATTR_NAME = "timestamp"; + protected static final String LEVEL_ATTR_NAME = "level"; + protected static final String FORMATTED_MESSAGE_ATTR_NAME = "message"; + protected static final String THREAD_ATTR_NAME = "thread"; + protected static final String THREAD_ID_ATTR_NAME = "thread_id"; + protected static final String THREAD_PRIORITY_ATTR_NAME = "thread_priority"; + protected static final String EXCEPTION_MSG_ATTR_NAME = "message"; + protected static final String EXCEPTION_CLASS_ATTR_NAME = "name"; + protected static final String EXCEPTION_STACK_ATTR_NAME = "stack"; + protected static final String EXCEPTION_ATTR_NAME = "error"; + + + public static void serializeObjectStart(StringBuilder builder) { + builder.append('{'); + } + + public static void serializeObjectEnd(StringBuilder builder) { + builder.append("}\n"); + } + + public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, String timestampFormatTimezoneId) { + String formattedTimestamp; + if (timestampFormat == null || timestamp < 0) { + formattedTimestamp = String.valueOf(timestamp); + } else { + Date date = new Date(timestamp); + DateFormat format = new SimpleDateFormat(timestampFormat); + + if (timestampFormatTimezoneId != null) { + TimeZone tz = TimeZone.getTimeZone(timestampFormatTimezoneId); + format.setTimeZone(tz); + } + formattedTimestamp = format.format(date); + } + serializeAttribute(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp); + } + + public static void serializeThreadName(StringBuilder builder, String threadName) { + if (threadName != null) { + serializeAttribute(builder, THREAD_ATTR_NAME, threadName); + } + } + + public static void serializeLogLevel(StringBuilder builder, Level level) { + serializeAttribute(builder, LEVEL_ATTR_NAME, level.toString(), false); + } + + public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { + serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + } + + public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { + builder.append("\"").append(EXCEPTION_ATTR_NAME).append("\": {"); + serializeAttribute(builder, EXCEPTION_MSG_ATTR_NAME, message, false); + serializeAttribute(builder, EXCEPTION_CLASS_ATTR_NAME, className); + serializeAttribute(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); + builder.append("},"); + } + + public static void serializeException(StringBuilder builder, Throwable throwable) { + serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), Arrays.toString(throwable.getStackTrace())); + } + + public static void serializeThreadId(StringBuilder builder, String threadId) { + serializeAttribute(builder, THREAD_ID_ATTR_NAME, threadId); + } + + public static void serializeThreadPriority(StringBuilder builder, String threadPriority) { + serializeAttribute(builder, THREAD_PRIORITY_ATTR_NAME, threadPriority); + } + + public static void serializePowertools(StringBuilder builder, Map mdc) { + TreeMap sortedMap = new TreeMap<>(mdc); + sortedMap.forEach((k, v) -> + serializeAttribute(builder, k, v)); + } + +} diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java new file mode 100644 index 000000000..fdf82dd1a --- /dev/null +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java @@ -0,0 +1,35 @@ +package software.amazon.lambda.powertools.logging.internal; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class LogbackLoggingManager implements LoggingManager { + + private final LoggerContext loggerContext; + + public LogbackLoggingManager() { + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (!(loggerFactory instanceof LoggerContext)) { + throw new RuntimeException("LoggerFactory does not match required type: " + LoggerContext.class.getName()); + } + loggerContext = (LoggerContext) loggerFactory; + } + + @Override + public void resetLogLevel(org.slf4j.event.Level logLevel) { + List loggers = loggerContext.getLoggerList(); + for (Logger logger : loggers) { + logger.setLevel(Level.convertAnSLF4JLevel(logLevel)); + } + } + + @Override + public org.slf4j.event.Level getLogLevel(org.slf4j.Logger logger) { + return org.slf4j.event.Level.valueOf(loggerContext.getLogger(logger.getName()).getEffectiveLevel().toString()); + } +} diff --git a/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java b/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java new file mode 100644 index 000000000..62a2aa374 --- /dev/null +++ b/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java @@ -0,0 +1,37 @@ +package software.amazon.lambda.powertools.logging.internal; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.*; + +public class LogbackLoggingManagerTest { + + private static Logger LOG = LoggerFactory.getLogger(LogbackLoggingManagerTest.class); + private static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Test + @Order(1) + public void getLogLevel_shouldReturnConfiguredLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(INFO); + + logLevel = manager.getLogLevel(ROOT); + assertThat(logLevel).isEqualTo(WARN); + } + + @Test + @Order(2) + public void resetLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + manager.resetLogLevel(ERROR); + + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(ERROR); + } +} diff --git a/powertools-logging-logback/src/test/resources/logback-test.xml b/powertools-logging-logback/src/test/resources/logback-test.xml new file mode 100644 index 000000000..116833c3f --- /dev/null +++ b/powertools-logging-logback/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file From 6e7227d78b50fcb8f759ed8e9ae6c5c9f8e7a705 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 22 Nov 2022 18:07:15 +0100 Subject: [PATCH 04/74] update parent pom --- pom.xml | 19 +++++++++++++++++++ spotbugs-exclude.xml | 32 ++++---------------------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 15de3af0b..7ebd86883 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,8 @@ powertools-common powertools-serialization powertools-logging + powertools-logging-log4j + powertools-logging-logback powertools-tracing powertools-metrics powertools-parameters @@ -72,6 +74,7 @@ 1.8 1.8 2.20.0 + 2.0.4 2.15.3 1.9.7 2.21.0 @@ -92,6 +95,7 @@ 5.10.0 1.0.6 0.5.1 + 1.5.0 @@ -195,6 +199,11 @@ log4j-core ${log4j.version} + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j.version} + org.apache.logging.log4j log4j-slf4j2-impl @@ -215,6 +224,16 @@ log4j-jcl ${log4j.version} + + co.elastic.logging + logback-ecs-encoder + ${elastic.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + com.amazonaws aws-xray-recorder-sdk-core diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index eca7e266f..2b75444c9 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -12,22 +12,6 @@ - - - - - - - - - - - - - - - - @@ -46,20 +30,12 @@ - - - - - - - - - - + + - - + + From c094ee4afc8e34b6538694152528777d1ecbfaea Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 22 Nov 2022 22:39:32 +0100 Subject: [PATCH 05/74] services --- ...powertools.logging.internal.LoggingManager | 1 + .../LambdaJsonLayout.java | 137 ------------------ ...powertools.logging.internal.LoggingManager | 1 + 3 files changed, 2 insertions(+), 137 deletions(-) create mode 100644 powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager delete mode 100644 powertools-logging-logback/LambdaJsonLayout.java create mode 100644 powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager diff --git a/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..ae2ec0cba --- /dev/null +++ b/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager \ No newline at end of file diff --git a/powertools-logging-logback/LambdaJsonLayout.java b/powertools-logging-logback/LambdaJsonLayout.java deleted file mode 100644 index 7c9b59ad7..000000000 --- a/powertools-logging-logback/LambdaJsonLayout.java +++ /dev/null @@ -1,137 +0,0 @@ -package software.amazon.lambda.powertools.logging; - -import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; -import ch.qos.logback.classic.pattern.ThrowableProxyConverter; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.classic.spi.IThrowableProxy; -import ch.qos.logback.classic.spi.ThrowableProxy; -import ch.qos.logback.core.LayoutBase; - -/** - * Custom layout for logback that encodes logs in JSON format. - * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. - */ -public class LambdaJsonLayout extends LayoutBase { - private final static String CONTENT_TYPE = "application/json"; - private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); - private ThrowableHandlingConverter throwableConverter; - private String timestampFormat; - private String timestampFormatTimezoneId; - private boolean includeThreadInfo; - - @Override - public String doLayout(ILoggingEvent event) { - StringBuilder builder = new StringBuilder(256); - LambdaJsonSerializer.serializeObjectStart(builder); - LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel()); - LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); - IThrowableProxy throwableProxy = event.getThrowableProxy(); - if (throwableProxy != null) { - if (throwableConverter != null) { - LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); - } else if (throwableProxy instanceof ThrowableProxy) { - LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); - } else { - LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event), throwableProxy.getStackTraceElementProxyArray()[0].toString()); - } - } - LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); - if (includeThreadInfo) { - LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName()); - LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); - LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority())); - } - LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId); - LambdaJsonSerializer.serializeObjectEnd(builder); - return builder.toString(); - } - - @Override - public String getContentType() { - return CONTENT_TYPE; - } - - public void setTimestampFormat(String timestampFormat) { - this.timestampFormat = timestampFormat; - } - - public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { - this.timestampFormatTimezoneId = timestampFormatTimezoneId; - } - - public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { - this.throwableConverter = throwableConverter; - } - -// public static final String INSTANT_ATTR_NAME = "instant"; -// public static final String EPOCH_SEC_ATTR_NAME = "epochSecond"; -// public static final String NANO_SEC_ATTR_NAME = "nanoOfSecond"; -// public static final String LOGGER_FQCN_ATTR_NAME = "loggerFqcn"; -// public static final String LOGGER_ATTR_NAME = "loggerName"; -// public static final String THREAD_ID_ATTR_NAME = "threadId"; -// public static final String THREAD_PRIORITY_ATTR_NAME = "threadPriority"; -// -// private boolean includePowertools; -// private boolean includeInstant; -// private boolean includeThreadInfo; -// -// public LambdaJsonLayout() { -// super(); -// this.includeInstant = true; -// this.includePowertools = true; -// this.includeThreadInfo = true; -// } -// -// @Override -// protected Map toJsonMap(ILoggingEvent event) { -// Map map = new LinkedHashMap<>(); -// addTimestamp(TIMESTAMP_ATTR_NAME, this.includeTimestamp, event.getTimeStamp(), map); -// addInstant(this.includeInstant, event.getTimeStamp(), event.getNanoseconds(), map); -// add(THREAD_ATTR_NAME, this.includeThreadName || this.includeThreadInfo, event.getThreadName(), map); -// add(LEVEL_ATTR_NAME, this.includeLevel, String.valueOf(event.getLevel()), map); -// add(LOGGER_ATTR_NAME, this.includeLoggerName, event.getLoggerName(), map); -// add(FORMATTED_MESSAGE_ATTR_NAME, this.includeFormattedMessage, event.getFormattedMessage(), map); -// addThrowableInfo(EXCEPTION_ATTR_NAME, this.includeException, event, map); -// // contextStack ? -// // endOfBatch ? -// map.put(LOGGER_FQCN_ATTR_NAME, "ch.qos.logback.classic.Logger"); -// add(THREAD_ID_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getId()), map); -// add(THREAD_PRIORITY_ATTR_NAME, this.includeThreadInfo, String.valueOf(Thread.currentThread().getPriority()), map); -// addPowertools(this.includePowertools, event.getMDCPropertyMap(), map); -// return map; -// } -// -// private void addPowertools(boolean includePowertools, Map mdcPropertyMap, Map map) { -// TreeMap sortedMap = new TreeMap<>(mdcPropertyMap); -// List powertoolsFields = DefaultLambdaFields.stringValues(); -// -// sortedMap.forEach((k, v) -> { -// if (includePowertools || !powertoolsFields.contains(k)) { -// map.put(k, v); -// } -// }); -// -// } -// -// private void addInstant(boolean includeInstant, long timeStamp, int nanoseconds, Map map) { -// if (includeInstant) { -// Map instantMap = new LinkedHashMap<>(); -// instantMap.put(EPOCH_SEC_ATTR_NAME, timeStamp / 1000); -// instantMap.put(NANO_SEC_ATTR_NAME, nanoseconds); -// map.put(LambdaJsonLayout.INSTANT_ATTR_NAME, instantMap); -// } -// } -// -// public void setIncludeInstant(boolean includeInstant) { -// this.includeInstant = includeInstant; -// } -// -// public void setIncludePowertools(boolean includePowertools) { -// this.includePowertools = includePowertools; -// } -// - public void setIncludeThreadInfo(boolean includeThreadInfo) { - this.includeThreadInfo = includeThreadInfo; - } - -} diff --git a/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..6d432bd94 --- /dev/null +++ b/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager \ No newline at end of file From 652099606056d03f3681c8d7c1f85128f34ad44a Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 21:37:00 +0200 Subject: [PATCH 06/74] rebase + fix build --- pom.xml | 1 - powertools-common/pom.xml | 5 + powertools-logging-log4j/pom.xml | 2 +- powertools-logging-logback/pom.xml | 2 +- .../powertools/logging/LambdaEcsEncoder.java | 16 ++- powertools-logging/pom.xml | 5 + .../logging/internal/LambdaLoggingAspect.java | 22 ++-- .../internal/LambdaLoggingAspectTest.java | 123 ++---------------- 8 files changed, 40 insertions(+), 136 deletions(-) diff --git a/pom.xml b/pom.xml index 7ebd86883..4b6cdbca4 100644 --- a/pom.xml +++ b/pom.xml @@ -625,7 +625,6 @@ 3.3.0 checkstyle.xml - UTF-8 true true false diff --git a/powertools-common/pom.xml b/powertools-common/pom.xml index 82e2c1d17..7f82fcd44 100644 --- a/powertools-common/pom.xml +++ b/powertools-common/pom.xml @@ -54,6 +54,11 @@ + + org.aspectj + aspectjrt + provided + com.amazonaws aws-lambda-java-core diff --git a/powertools-logging-log4j/pom.xml b/powertools-logging-log4j/pom.xml index be401d6b8..2ee08fa72 100644 --- a/powertools-logging-log4j/pom.xml +++ b/powertools-logging-log4j/pom.xml @@ -7,7 +7,7 @@ powertools-parent software.amazon.lambda - 1.12.3 + 2.0.0-SNAPSHOT powertools-logging-log4j diff --git a/powertools-logging-logback/pom.xml b/powertools-logging-logback/pom.xml index 94358f549..877bc8fbf 100644 --- a/powertools-logging-logback/pom.xml +++ b/powertools-logging-logback/pom.xml @@ -5,7 +5,7 @@ powertools-parent software.amazon.lambda - 1.12.3 + 2.0.0-SNAPSHOT 4.0.0 diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java index c501c439b..e123cf3bb 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -1,18 +1,24 @@ package software.amazon.lambda.powertools.logging; +import static java.nio.charset.StandardCharsets.UTF_8; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; + import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; import ch.qos.logback.classic.pattern.ThrowableProxyConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.encoder.EncoderBase; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer; - import java.util.Map; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer; -import static java.nio.charset.StandardCharsets.UTF_8; -import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*; /** * This class will encode the logback event into the format expected by the ECS service (ElasticSearch). diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 3e14c14da..3f1302890 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -70,6 +70,11 @@ org.slf4j slf4j-api + + org.aspectj + aspectjrt + provided + diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index c60a1c85c..e6e9921e5 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -28,6 +28,9 @@ import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys; import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonPointer; @@ -39,16 +42,12 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.util.IOUtils; +import java.util.ServiceLoader; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -94,18 +93,13 @@ private static LoggingManager loadLoggingManager() { if (loggingManagerList.isEmpty()) { throw new IllegalStateException("No LoggingManager was found on the classpath"); } else if (loggingManagerList.size() > 1) { - throw new IllegalStateException("Multiple LoggingManagers were found on the classpath: " + loggingManagerList); + throw new IllegalStateException( + "Multiple LoggingManagers were found on the classpath: " + loggingManagerList); } else { return loggingManagerList.get(0); } } - private static void resetLogLevels(Level logLevel) { - LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - Configurator.setAllLevels(LogManager.getRootLogger().getName(), logLevel); - ctx.updateLoggers(); - } - @SuppressWarnings({"EmptyMethod"}) @Pointcut("@annotation(logging)") public void callAt(Logging logging) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 6e3e8ec92..aa932fadb 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -16,15 +16,21 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; -import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; @@ -32,55 +38,27 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import com.amazonaws.services.lambda.runtime.tests.annotations.Event; import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; +import com.amazonaws.services.lambda.runtime.tests.annotations.Event; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.ThreadContext; -import org.json.JSONException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.mockito.Mock; import org.mockito.MockedStatic; import org.slf4j.MDC; -import org.slf4j.event.Level; -import software.amazon.lambda.powertools.core.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.core.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.*; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.channels.FileChannel; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; - -import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.core.internal.SystemWrapper.getenv; -import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.*; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; @@ -89,11 +67,7 @@ import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream; import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled; import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventEnabledWithCustomMapper; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledWithClearState; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; class LambdaLoggingAspectTest { @@ -195,65 +169,6 @@ void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { assertThat(MDC.getCopyOfContextMap()).isNull(); } - @Test - void shouldLogEventForHandler() throws IOException, JSONException { - requestHandler = new PowerToolLogEventEnabled(); - S3EventNotification s3EventNotification = s3EventNotification(); - - requestHandler.handleRequest(s3EventNotification, context); - - Map log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); - - String event = (String) log.get("message"); - - String expectEvent = new BufferedReader( - new InputStreamReader(this.getClass().getResourceAsStream("/s3EventNotification.json"))) - .lines().collect(joining("\n")); - - assertEquals(expectEvent, event, false); - } - - @Test - void shouldLogEventForHandlerWithOverriddenObjectMapper() throws IOException, JSONException { - RequestHandler handler = new PowerToolLogEventEnabledWithCustomMapper(); - S3EventNotification s3EventNotification = s3EventNotification(); - - handler.handleRequest(s3EventNotification, context); - - Map log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); - - String event = (String) log.get("message"); - - String expectEvent = new BufferedReader( - new InputStreamReader(this.getClass().getResourceAsStream("/customizedLogEvent.json"))) - .lines().collect(joining("\n")); - - assertEquals(expectEvent, event, false); - } - - @Test - void shouldLogEventForStreamAndLambdaStreamIsValid() throws IOException, JSONException { - requestStreamHandler = new PowerToolLogEventEnabledForStream(); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - S3EventNotification s3EventNotification = s3EventNotification(); - - requestStreamHandler.handleRequest( - new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(s3EventNotification)), output, context); - - assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) - .isNotEmpty(); - - Map log = parseToMap(Files.lines(Paths.get("target/logfile.json")).collect(joining())); - - String event = (String) log.get("message"); - - String expectEvent = new BufferedReader( - new InputStreamReader(this.getClass().getResourceAsStream("/s3EventNotification.json"))) - .lines().collect(joining("\n")); - - assertEquals(expectEvent, event, false); - } - @Test void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { writeStaticField(LambdaHandlerProcessor.class, "SERVICE_NAME", "testService", true); @@ -327,26 +242,6 @@ void shouldLogCorrelationIdOnStreamHandler() throws IOException { .containsEntry("correlation_id", eventId); } - @Test - void shouldLogAndClearLogContextOnEachRequest() throws IOException { - requestHandler = new PowertoolsLogEnabledWithClearState(); - S3EventNotification s3EventNotification = s3EventNotification(); - - requestHandler.handleRequest(s3EventNotification, context); - requestHandler.handleRequest(s3EventNotification, context); - - List logLines = Files.lines(Paths.get("target/logfile.json")).collect(Collectors.toList()); - Map invokeLog = parseToMap(logLines.get(0)); - - assertThat(invokeLog) - .containsEntry("TestKey", "TestValue"); - - invokeLog = parseToMap(logLines.get(1)); - - assertThat(invokeLog) - .doesNotContainKey("TestKey"); - } - private void setupContext() { when(context.getFunctionName()).thenReturn("testFunction"); when(context.getInvokedFunctionArn()).thenReturn("testArn"); From 9fa5aac478e69c27cbf7bbc981085330c88a21af Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 22:12:13 +0200 Subject: [PATCH 07/74] fix tests --- .../handlers/PowertoolsLogEventBridgeCorrelationId.java | 6 +++--- .../logging/internal/LambdaLoggingAspectTest.java | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java index 53e06cb2e..9f12247cd 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java @@ -21,13 +21,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; public class PowertoolsLogEventBridgeCorrelationId implements RequestStreamHandler { - private final Logger LOG = LogManager.getLogger(PowertoolsLogEventBridgeCorrelationId.class); + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEventBridgeCorrelationId.class); @Override @Logging(correlationIdPath = EVENT_BRIDGE) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index aa932fadb..2c667f3ec 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -51,14 +51,13 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Map; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.ThreadContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.mockito.Mock; import org.mockito.MockedStatic; import org.slf4j.MDC; +import org.slf4j.event.Level; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; @@ -237,7 +236,7 @@ void shouldLogCorrelationIdOnStreamHandler() throws IOException { handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); - assertThat(ThreadContext.getImmutableContext()) + assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", eventId); } From 1b98b65909cb469a117f5882437d48b3928bdd94 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 22:52:49 +0200 Subject: [PATCH 08/74] fix tests --- powertools-common/pom.xml | 9 +- .../common/internal/LambdaConstants.java | 6 - .../internal/UserAgentConfigurator.java | 6 +- .../logging/internal/Log4jLoggingManager.java | 14 ++ .../logging/internal/PowertoolsResolver.java | 77 ++++++--- .../internal/PowertoolsResolverFactory.java | 6 +- .../internal/Log4jLoggingManagerTest.java | 8 +- .../powertools/logging/LambdaEcsEncoder.java | 20 ++- .../powertools/logging/LambdaJsonEncoder.java | 27 +++- .../logging/internal/JsonUtils.java | 27 +++- .../logging/internal/LambdaEcsSerializer.java | 36 ++++- .../internal/LambdaJsonSerializer.java | 38 +++-- .../internal/LogbackLoggingManager.java | 17 +- .../internal/LogbackLoggingManagerTest.java | 14 +- .../powertools/logging/LoggingUtils.java | 3 +- .../logging/internal/LambdaLoggingAspect.java | 12 +- .../logging/internal/LoggingManager.java | 19 ++- .../java/org/slf4j/test/OutputChoice.java | 39 +++-- .../test/java/org/slf4j/test/TestLogger.java | 153 ++++++++---------- .../slf4j/test/TestLoggerConfiguration.java | 143 ++++++++-------- .../org/slf4j/test/TestLoggerFactory.java | 15 +- .../org/slf4j/test/TestServiceProvider.java | 8 +- .../logging/internal/TestLoggingManager.java | 5 +- 23 files changed, 416 insertions(+), 286 deletions(-) diff --git a/powertools-common/pom.xml b/powertools-common/pom.xml index 7f82fcd44..a8e02dafd 100644 --- a/powertools-common/pom.xml +++ b/powertools-common/pom.xml @@ -54,6 +54,10 @@ + + org.slf4j + slf4j-api + org.aspectj aspectjrt @@ -67,11 +71,6 @@ org.aspectj aspectjrt - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java index f43cd52a3..eb963c884 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java @@ -17,12 +17,6 @@ public class LambdaConstants { public static final String LAMBDA_FUNCTION_NAME_ENV = "AWS_LAMBDA_FUNCTION_NAME"; public static final String AWS_REGION_ENV = "AWS_REGION"; - // Also you can use AWS_LAMBDA_INITIALIZATION_TYPE to distinguish between on-demand and SnapStart initialization - // it's not recommended to use this env variable to initialize SDK clients or other resources. - @Deprecated - public static final String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"; - @Deprecated - public static final String ON_DEMAND = "on-demand"; public static final String X_AMZN_TRACE_ID = "_X_AMZN_TRACE_ID"; public static final String AWS_SAM_LOCAL = "AWS_SAM_LOCAL"; public static final String ROOT_EQUALS = "Root="; diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java index 585c38c59..8ae10ad62 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/UserAgentConfigurator.java @@ -37,8 +37,8 @@ public class UserAgentConfigurator { public static final String AWS_EXECUTION_ENV = "AWS_EXECUTION_ENV"; private static final Logger LOG = LoggerFactory.getLogger(UserAgentConfigurator.class); private static final String NO_OP = "no-op"; - private static String ptVersion = getProjectVersion(); - private static String userAgentPattern = "PT/" + PT_FEATURE_VARIABLE + "/" + ptVersion + " PTEnv/" + private static final String POWERTOOLS_VERSION = getProjectVersion(); + private static final String USER_AGENT_PATTERN = "PT/" + PT_FEATURE_VARIABLE + "/" + POWERTOOLS_VERSION + " PTEnv/" + PT_EXEC_ENV_VARIABLE; private UserAgentConfigurator() { @@ -99,7 +99,7 @@ public static String getUserAgent(String ptFeature) { String awsExecutionEnv = getenv(AWS_EXECUTION_ENV); String ptExecEnv = awsExecutionEnv != null ? awsExecutionEnv : NA; - String userAgent = userAgentPattern.replace(PT_EXEC_ENV_VARIABLE, ptExecEnv); + String userAgent = USER_AGENT_PATTERN.replace(PT_EXEC_ENV_VARIABLE, ptExecEnv); if (ptFeature == null || ptFeature.isEmpty()) { ptFeature = NO_OP; diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java index bff21b1aa..5e6f68c5a 100644 --- a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; import org.apache.logging.log4j.Level; diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java index 43e17dd25..20ef5f7e3 100644 --- a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; import org.apache.logging.log4j.core.LogEvent; @@ -11,51 +25,59 @@ final class PowertoolsResolver implements EventResolver { private static final EventResolver COLD_START_RESOLVER = new EventResolver() { @Override public boolean isResolvable(LogEvent logEvent) { - final String coldStart = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + final String coldStart = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); return null != coldStart; } @Override public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { - final String coldStart = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); + final String coldStart = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_COLD_START.getName()); jsonWriter.writeBoolean(Boolean.parseBoolean(coldStart)); } }; private static final EventResolver FUNCTION_NAME_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String functionName = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_NAME.getName()); + final String functionName = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_NAME.getName()); jsonWriter.writeString(functionName); }; private static final EventResolver FUNCTION_VERSION_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String functionVersion = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_VERSION.getName()); + final String functionVersion = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_VERSION.getName()); jsonWriter.writeString(functionVersion); }; private static final EventResolver FUNCTION_ARN_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String functionArn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); + final String functionArn = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName()); jsonWriter.writeString(functionArn); }; private static final EventResolver FUNCTION_REQ_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String functionRequestId = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName()); + final String functionRequestId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName()); jsonWriter.writeString(functionRequestId); }; private static final EventResolver FUNCTION_MEMORY_RESOLVER = new EventResolver() { @Override public boolean isResolvable(LogEvent logEvent) { - final String functionMemory = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + final String functionMemory = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); return null != functionMemory; } @Override public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { - final String functionMemory = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); + final String functionMemory = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName()); jsonWriter.writeNumber(Integer.parseInt(functionMemory)); } }; @@ -63,20 +85,23 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver SAMPLING_RATE_RESOLVER = new EventResolver() { @Override public boolean isResolvable(LogEvent logEvent) { - final String samplingRate = logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + final String samplingRate = + logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); return null != samplingRate; } @Override public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { - final String samplingRate = logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); + final String samplingRate = + logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); jsonWriter.writeNumber(Float.parseFloat(samplingRate)); } }; private static final EventResolver XRAY_TRACE_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String traceId = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + final String traceId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); jsonWriter.writeString(traceId); }; @@ -106,21 +131,21 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER = (LogEvent logEvent, JsonWriter jsonWriter) -> { - StringBuilder stringBuilder = jsonWriter.getStringBuilder(); - // remove dummy field to kick inn powertools resolver - stringBuilder.setLength(stringBuilder.length() - 4); - - // Inject all the context information. - ReadOnlyStringMap contextData = logEvent.getContextData(); - contextData.forEach((key, value) -> { - if (!PowertoolsLoggedFields.stringValues().contains(key)) { - jsonWriter.writeSeparator(); - jsonWriter.writeString(key); - stringBuilder.append(':'); - jsonWriter.writeValue(value); - } - }); - }; + StringBuilder stringBuilder = jsonWriter.getStringBuilder(); + // remove dummy field to kick inn powertools resolver + stringBuilder.setLength(stringBuilder.length() - 4); + + // Inject all the context information. + ReadOnlyStringMap contextData = logEvent.getContextData(); + contextData.forEach((key, value) -> { + if (!PowertoolsLoggedFields.stringValues().contains(key)) { + jsonWriter.writeSeparator(); + jsonWriter.writeString(key); + stringBuilder.append(':'); + jsonWriter.writeValue(value); + } + }); + }; private final EventResolver internalResolver; diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java index 7070b1fe2..297315120 100644 --- a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java @@ -17,7 +17,11 @@ import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.layout.template.json.resolver.*; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; +import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; +import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; @Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) public final class PowertoolsResolverFactory implements EventResolverFactory { diff --git a/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java index 9bd1c7550..b1371c963 100644 --- a/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java +++ b/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -1,14 +1,16 @@ package software.amazon.lambda.powertools.logging.internal; +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.WARN; + import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; -import static org.assertj.core.api.Assertions.assertThat; -import static org.slf4j.event.Level.*; - public class Log4jLoggingManagerTest { private static Logger LOG = LoggerFactory.getLogger(Log4jLoggingManagerTest.class); diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java index e123cf3bb..7df62a91e 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging; import static java.nio.charset.StandardCharsets.UTF_8; @@ -62,11 +76,13 @@ public byte[] encode(ILoggingEvent event) { IThrowableProxy throwableProxy = event.getThrowableProxy(); if (throwableProxy != null) { if (throwableConverter != null) { - LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event)); + LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableConverter.convert(event)); } else if (throwableProxy instanceof ThrowableProxy) { LambdaEcsSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); } else { - LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event)); + LambdaEcsSerializer.serializeException(builder, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableProxyConverter.convert(event)); } } LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER); diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java index 7df90a4ad..a1a378605 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -1,5 +1,21 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging; +import static java.nio.charset.StandardCharsets.UTF_8; + import ch.qos.logback.classic.pattern.ThrowableHandlingConverter; import ch.qos.logback.classic.pattern.ThrowableProxyConverter; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -8,8 +24,6 @@ import ch.qos.logback.core.encoder.EncoderBase; import software.amazon.lambda.powertools.logging.internal.LambdaJsonSerializer; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * Custom encoder for logback that encodes logs in JSON format. * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. @@ -45,11 +59,13 @@ public byte[] encode(ILoggingEvent event) { IThrowableProxy throwableProxy = event.getThrowableProxy(); if (throwableProxy != null) { if (throwableConverter != null) { - LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableConverter.convert(event)); + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableConverter.convert(event)); } else if (throwableProxy instanceof ThrowableProxy) { LambdaJsonSerializer.serializeException(builder, ((ThrowableProxy) throwableProxy).getThrowable()); } else { - LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), throwableProxy.getMessage(), throwableProxyConverter.convert(event)); + LambdaJsonSerializer.serializeException(builder, throwableProxy.getClassName(), + throwableProxy.getMessage(), throwableProxyConverter.convert(event)); } } LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); @@ -58,7 +74,8 @@ public byte[] encode(ILoggingEvent event) { LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); LambdaJsonSerializer.serializeThreadPriority(builder, String.valueOf(Thread.currentThread().getPriority())); } - LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, timestampFormatTimezoneId); + LambdaJsonSerializer.serializeTimestamp(builder, event.getTimeStamp(), timestampFormat, + timestampFormatTimezoneId); LambdaJsonSerializer.serializeObjectEnd(builder); return builder.toString().getBytes(UTF_8); } diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java index f073050f9..97b49595b 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; public class JsonUtils { @@ -9,13 +23,18 @@ protected static void serializeAttribute(StringBuilder builder, String attr, Str } builder.append("\"").append(attr).append("\": "); boolean isString = isString(value); - if (isString) builder.append("\""); + if (isString) { + builder.append("\""); + } builder.append(value); - if (isString) builder.append("\""); + if (isString) { + builder.append("\""); + } } } - protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value, boolean notBegin) { + protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value, + boolean notBegin) { if (value != null) { if (notBegin) { builder.append(", "); @@ -43,7 +62,7 @@ private static boolean isString(String str) { if (str == null) { return true; } - if (str.equals("true") || str.equals("false")) { + if ("true".equals(str) || "false".equals(str)) { return false; // boolean } return !isNumeric(str); // number diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java index 06c2de511..3a052e6a4 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java @@ -1,17 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; -import ch.qos.logback.classic.Level; +import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttributeAsString; +import ch.qos.logback.classic.Level; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; import java.util.regex.Matcher; -import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttributeAsString; - /** * This class will serialize the log events in ecs format (ElasticSearch).
- * + *

* Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer, this class doesn't use * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency */ @@ -50,7 +67,8 @@ public static void serializeObjectEnd(StringBuilder builder) { builder.append("}\n"); } - public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, String timestampFormatTimezoneId) { + public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, + String timestampFormatTimezoneId) { String formattedTimestamp; if (timestampFormat == null || timestamp < 0) { formattedTimestamp = String.valueOf(timestamp); @@ -78,7 +96,8 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { } public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { - serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, + formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { @@ -88,7 +107,8 @@ public static void serializeException(StringBuilder builder, String className, S } public static void serializeException(StringBuilder builder, Throwable throwable) { - serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), Arrays.toString(throwable.getStackTrace())); + serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), + Arrays.toString(throwable.getStackTrace())); } public static void serializeThreadId(StringBuilder builder, String threadId) { diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index 1c368679b..c3a29ac6b 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -1,18 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; -import ch.qos.logback.classic.Level; +import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttribute; +import ch.qos.logback.classic.Level; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; import java.util.regex.Matcher; -import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttribute; - /** * This class will serialize the log events in json.
- * - * Inspired from the ElasticSearch Serializer {@link co.elastic.logging.EcsJsonSerializer}, this class doesn't use + *

+ * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer, this class doesn't use * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency */ public class LambdaJsonSerializer { @@ -36,7 +53,8 @@ public static void serializeObjectEnd(StringBuilder builder) { builder.append("}\n"); } - public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, String timestampFormatTimezoneId) { + public static void serializeTimestamp(StringBuilder builder, long timestamp, String timestampFormat, + String timestampFormatTimezoneId) { String formattedTimestamp; if (timestampFormat == null || timestamp < 0) { formattedTimestamp = String.valueOf(timestamp); @@ -64,7 +82,8 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { } public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { - serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, + formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { @@ -76,7 +95,8 @@ public static void serializeException(StringBuilder builder, String className, S } public static void serializeException(StringBuilder builder, Throwable throwable) { - serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), Arrays.toString(throwable.getStackTrace())); + serializeException(builder, throwable.getClass().getName(), throwable.getMessage(), + Arrays.toString(throwable.getStackTrace())); } public static void serializeThreadId(StringBuilder builder, String threadId) { diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java index fdf82dd1a..1d27e103a 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java @@ -1,13 +1,26 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; +import java.util.List; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; -import java.util.List; - public class LogbackLoggingManager implements LoggingManager { private final LoggerContext loggerContext; diff --git a/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java b/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java index 62a2aa374..2d70ab3ff 100644 --- a/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java +++ b/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java @@ -1,22 +1,24 @@ package software.amazon.lambda.powertools.logging.internal; +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.INFO; +import static org.slf4j.event.Level.WARN; + import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; -import static org.assertj.core.api.Assertions.assertThat; -import static org.slf4j.event.Level.*; - -public class LogbackLoggingManagerTest { +class LogbackLoggingManagerTest { private static Logger LOG = LoggerFactory.getLogger(LogbackLoggingManagerTest.class); private static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); @Test @Order(1) - public void getLogLevel_shouldReturnConfiguredLogLevel() { + void getLogLevel_shouldReturnConfiguredLogLevel() { LogbackLoggingManager manager = new LogbackLoggingManager(); Level logLevel = manager.getLogLevel(LOG); assertThat(logLevel).isEqualTo(INFO); @@ -27,7 +29,7 @@ public void getLogLevel_shouldReturnConfiguredLogLevel() { @Test @Order(2) - public void resetLogLevel() { + void resetLogLevel() { LogbackLoggingManager manager = new LogbackLoggingManager(); manager.resetLogLevel(ERROR); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index ed57df0fd..fe3619fdc 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -15,10 +15,9 @@ package software.amazon.lambda.powertools.logging; import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.MDC; - import java.util.Arrays; import java.util.Map; +import org.slf4j.MDC; /** * A class of helper functions to add additional functionality to Logging. diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index e6e9921e5..020858494 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -69,10 +69,8 @@ public final class LambdaLoggingAspect { private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); private static final String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ - private static final LoggingManager loggingManager; + private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ static { loggingManager = loadLoggingManager(); @@ -100,6 +98,10 @@ private static LoggingManager loadLoggingManager() { } } + private static void resetLogLevels(Level logLevel) { + loggingManager.resetLogLevel(logLevel); + } + @SuppressWarnings({"EmptyMethod"}) @Pointcut("@annotation(logging)") public void callAt(Logging logging) { @@ -140,10 +142,6 @@ public Object around(ProceedingJoinPoint pjp, return proceed; } - private static void resetLogLevels(Level logLevel) { - loggingManager.resetLogLevel(logLevel); - } - private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, final Logging logging) { double samplingRate = samplingRate(logging); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java index 4850c9413..1f4a4e636 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java @@ -1,3 +1,17 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; import org.slf4j.Logger; @@ -9,20 +23,21 @@ *

  • Accessing to all loggers and change their Level
  • *
  • Retrieving the log Level of a Logger
  • * - * + *

    * Implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} * (define a file named software.amazon.lambda.powertools.logging.internal.LoggingManager in src/main/resources/META-INF/services with the qualified name of the implementation). - * */ public interface LoggingManager { /** * Change the log Level of all loggers (named and root) + * * @param logLevel the log Level (slf4j) to apply */ void resetLogLevel(Level logLevel); /** * Retrieve the log Level of a specific logger + * * @param logger the logger (slf4j) for which to retrieve the log Level * @return the Level (slf4j) of this logger. Note that SLF4J only support ERROR, WARN, INFO, DEBUG, TRACE while some frameworks may support others (OFF, FATAL, ...) */ diff --git a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java index 096fa7d83..164862e00 100644 --- a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java +++ b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2011 QOS.ch * All rights reserved. - * + *

    * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + *

    * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + *

    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -20,27 +20,22 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * */ + package org.slf4j.test; import java.io.PrintStream; /** * This class encapsulates the user's choice of output target. - * + * * @author Ceki Gülcü * */ class OutputChoice { - enum OutputChoiceType { - SYS_OUT, CACHED_SYS_OUT, SYS_ERR, CACHED_SYS_ERR, FILE; - } - final OutputChoiceType outputChoiceType; final PrintStream targetPrintStream; - OutputChoice(OutputChoiceType outputChoiceType) { if (outputChoiceType == OutputChoiceType.FILE) { throw new IllegalArgumentException(); @@ -62,18 +57,22 @@ enum OutputChoiceType { PrintStream getTargetPrintStream() { switch (outputChoiceType) { - case SYS_OUT: - return System.out; - case SYS_ERR: - return System.err; - case CACHED_SYS_ERR: - case CACHED_SYS_OUT: - case FILE: - return targetPrintStream; - default: - throw new IllegalArgumentException(); + case SYS_OUT: + return System.out; + case SYS_ERR: + return System.err; + case CACHED_SYS_ERR: + case CACHED_SYS_OUT: + case FILE: + return targetPrintStream; + default: + throw new IllegalArgumentException(); } } + enum OutputChoiceType { + SYS_OUT, CACHED_SYS_OUT, SYS_ERR, CACHED_SYS_ERR, FILE; + } + } diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java index d54b51da4..2a9322592 100644 --- a/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLogger.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2022 QOS.ch Sarl (Switzerland) * All rights reserved. - * + *

    * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + *

    * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + *

    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -20,10 +20,14 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * */ + package org.slf4j.test; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.event.Level; @@ -33,23 +37,18 @@ import org.slf4j.helpers.NormalizedParameters; import org.slf4j.spi.LocationAwareLogger; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - /** *

    * Simple implementation of {@link Logger} that sends all enabled log messages, * for all defined loggers, to the console ({@code System.err}). The following * system properties are supported to configure the behavior of this logger: - * + * * *

      *
    • org.slf4j.simpleLogger.logFile - The output target which can * be the path to a file, or the special values "System.out" and * "System.err". Default is "System.err".
    • - * + * *
    • org.slf4j.simpleLogger.cacheOutputStream - If the output * target is set to "System.out" or "System.err" (see preceding entry), by * default, logs will be output to the latest value referenced by @@ -57,7 +56,7 @@ * output stream will be cached, i.e. assigned once at initialization time and * re-used independently of the current value referenced by * System.out/err.
    • - * + * *
    • org.slf4j.simpleLogger.defaultLogLevel - Default log level * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info", * "warn", "error" or "off"). If not specified, defaults to "info".
    • @@ -84,11 +83,11 @@ *
    • org.slf4j.simpleLogger.showThreadName -Set to * true if you want to output the current thread name. Defaults to * true.
    • - * + * *
    • (since version 1.7.33 and 2.0.0-alpha6) org.slf4j.simpleLogger.showThreadId - * If you would like to output the current thread id, then set to * true. Defaults to false.
    • - * + * *
    • org.slf4j.simpleLogger.showLogName - Set to * true if you want the Logger instance name to be included in * output messages. Defaults to true.
    • @@ -102,7 +101,7 @@ * *
    • org.slf4j.simpleLogger.warnLevelString - The string value * output for the warn level. Defaults to WARN.
    • - * + * *
    * *

    @@ -110,18 +109,18 @@ * this implementation also checks for a class loader resource named * "simplelogger.properties", and includes any matching definitions * from this resource (if it exists). - * + * * *

    * With no configuration, the default output includes the relative time in * milliseconds, thread name, the level, logger name, and the message followed * by the line separator for the host. In log4j terms it amounts to the "%r [%t] * %level %logger - %m%n" pattern. - * + * *

    * Sample output follows. - * - * + * + * *

      * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
      * 225 [main] INFO examples.SortAlgo - Entered the sort method.
    @@ -139,7 +138,7 @@
      * This implementation is heavily inspired by
      * Apache Commons Logging's
      * SimpleLog.
    - * 
    + *
      *
      * @author Ceki Gülcü
      * @author Scott Sanders
    @@ -149,76 +148,42 @@
      */
     public class TestLogger extends LegacyAbstractLogger {
     
    -    private static final long serialVersionUID = -632788891211436180L;
    -
    -    private static final long START_TIME = System.currentTimeMillis();
    -
    -    protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
    -    protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
    -    protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
    -    protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
    -    protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
    -
    -    static char SP = ' ';
    -    static final String TID_PREFIX = "tid=";
    -
    -
    -    // The OFF level can only be used in configuration files to disable logging.
    -    // It has
    -    // no printing method associated with it in o.s.Logger interface.
    -    protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10;
    -
    -    private static boolean INITIALIZED = false;
    -    static final TestLoggerConfiguration CONFIG_PARAMS = new TestLoggerConfiguration();
    -    
    -    static void lazyInit() {
    -        if (INITIALIZED) {
    -            return;
    -        }
    -        INITIALIZED = true;
    -        init();
    -    }
    -
    -    // external software might be invoking this method directly. Do not rename
    -    // or change its semantics.
    -    static void init() {
    -        CONFIG_PARAMS.init();
    -    }
    -
    -    /** The current log level */
    -    protected int currentLogLevel = LOG_LEVEL_INFO;
    -    /** The short name of this simple log instance */
    -    private transient String shortLogName = null;
    -
         /**
          * All system properties used by SimpleLogger start with this
          * prefix
          */
         public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger.";
    -
         public static final String LOG_KEY_PREFIX = TestLogger.SYSTEM_PREFIX + "log.";
    -
         public static final String CACHE_OUTPUT_STREAM_STRING_KEY = TestLogger.SYSTEM_PREFIX + "cacheOutputStream";
    -
         public static final String WARN_LEVEL_STRING_KEY = TestLogger.SYSTEM_PREFIX + "warnLevelString";
    -
         public static final String LEVEL_IN_BRACKETS_KEY = TestLogger.SYSTEM_PREFIX + "levelInBrackets";
    -
         public static final String LOG_FILE_KEY = TestLogger.SYSTEM_PREFIX + "logFile";
    -
         public static final String SHOW_SHORT_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showShortLogName";
    -
         public static final String SHOW_LOG_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showLogName";
    -
         public static final String SHOW_THREAD_NAME_KEY = TestLogger.SYSTEM_PREFIX + "showThreadName";
    -
         public static final String SHOW_THREAD_ID_KEY = TestLogger.SYSTEM_PREFIX + "showThreadId";
    -    
         public static final String DATE_TIME_FORMAT_KEY = TestLogger.SYSTEM_PREFIX + "dateTimeFormat";
    -
         public static final String SHOW_DATE_TIME_KEY = TestLogger.SYSTEM_PREFIX + "showDateTime";
    -
         public static final String DEFAULT_LOG_LEVEL_KEY = TestLogger.SYSTEM_PREFIX + "defaultLogLevel";
    +    protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
    +    protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
    +    protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
    +    protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
    +    protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
    +    // The OFF level can only be used in configuration files to disable logging.
    +    // It has
    +    // no printing method associated with it in o.s.Logger interface.
    +    protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10;
    +    static final String TID_PREFIX = "tid=";
    +    static final TestLoggerConfiguration CONFIG_PARAMS = new TestLoggerConfiguration();
    +    private static final long serialVersionUID = -632788891211436180L;
    +    private static final long START_TIME = System.currentTimeMillis();
    +    static char SP = ' ';
    +    private static boolean INITIALIZED = false;
    +    /** The current log level */
    +    protected int currentLogLevel = LOG_LEVEL_INFO;
    +    /** The short name of this simple log instance */
    +    private transient String shortLogName = null;
     
         /**
          * Package access allows only {@link TestLoggerFactory} to instantiate
    @@ -235,14 +200,28 @@ static void init() {
             }
         }
     
    -    public void setLogLevel(String levelString) {
    -        this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString);
    +    static void lazyInit() {
    +        if (INITIALIZED) {
    +            return;
    +        }
    +        INITIALIZED = true;
    +        init();
    +    }
    +
    +    // external software might be invoking this method directly. Do not rename
    +    // or change its semantics.
    +    static void init() {
    +        CONFIG_PARAMS.init();
         }
     
         public int getLogLevel() {
             return currentLogLevel;
         }
     
    +    public void setLogLevel(String levelString) {
    +        this.currentLogLevel = TestLoggerConfiguration.stringToLevel(levelString);
    +    }
    +
         String recursivelyComputeLevelString() {
             String tempName = name;
             String levelString = null;
    @@ -258,7 +237,7 @@ String recursivelyComputeLevelString() {
         /**
          * To avoid intermingling of log messages and associated stack traces, the two
          * operations are done in a synchronized block.
    -     * 
    +     *
          * @param buf
          * @param t
          */
    @@ -269,7 +248,7 @@ void write(StringBuilder buf, Throwable t) {
                 targetStream.println(buf.toString());
                 writeThrowable(t, targetStream);
                 targetStream.flush();
    -        } 
    +        }
     
         }
     
    @@ -373,7 +352,8 @@ public boolean isErrorEnabled() {
          * @param throwable  The exception whose stack trace should be logged, may be null
          */
         @Override
    -    protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) {
    +    protected void handleNormalizedLoggingCall(Level level, Marker marker, String messagePattern, Object[] arguments,
    +                                               Throwable throwable) {
     
             List markers = null;
     
    @@ -385,7 +365,8 @@ protected void handleNormalizedLoggingCall(Level level, Marker marker, String me
             innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable);
         }
     
    -    private void innerHandleNormalizedLoggingCall(Level level, List markers, String messagePattern, Object[] arguments, Throwable t) {
    +    private void innerHandleNormalizedLoggingCall(Level level, List markers, String messagePattern,
    +                                                  Object[] arguments, Throwable t) {
     
             StringBuilder buf = new StringBuilder(32);
     
    @@ -406,27 +387,30 @@ private void innerHandleNormalizedLoggingCall(Level level, List markers,
                 buf.append(Thread.currentThread().getName());
                 buf.append("] ");
             }
    -        
    +
             if (CONFIG_PARAMS.showThreadId) {
                 buf.append(TID_PREFIX);
                 buf.append(Thread.currentThread().getId());
                 buf.append(SP);
             }
     
    -        if (CONFIG_PARAMS.levelInBrackets)
    +        if (CONFIG_PARAMS.levelInBrackets) {
                 buf.append('[');
    +        }
     
             // Append a readable representation of the log level
             String levelStr = level.name();
             buf.append(levelStr);
    -        if (CONFIG_PARAMS.levelInBrackets)
    +        if (CONFIG_PARAMS.levelInBrackets) {
                 buf.append(']');
    +        }
             buf.append(SP);
     
             // Append the name of the log instance if so configured
             if (CONFIG_PARAMS.showShortLogName) {
    -            if (shortLogName == null)
    +            if (shortLogName == null) {
                     shortLogName = computeShortName();
    +            }
                 buf.append(String.valueOf(shortLogName)).append(" - ");
             } else if (CONFIG_PARAMS.showLogName) {
                 buf.append(String.valueOf(name)).append(" - ");
    @@ -456,7 +440,8 @@ public void log(LoggingEvent event) {
     
             NormalizedParameters np = NormalizedParameters.normalize(event);
     
    -        innerHandleNormalizedLoggingCall(event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable());
    +        innerHandleNormalizedLoggingCall(event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(),
    +                event.getThrowable());
         }
     
         @Override
    diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java
    index b90b6d04a..7601dbfde 100644
    --- a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java
    +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerConfiguration.java
    @@ -1,7 +1,7 @@
     /**
      * Copyright (c) 2004-2011 QOS.ch
      * All rights reserved.
    - *
    + * 

    * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + *

    * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + *

    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -20,11 +20,9 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * */ -package org.slf4j.test; -import org.slf4j.helpers.Util; +package org.slf4j.test; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -35,73 +33,105 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Properties; +import org.slf4j.helpers.Util; /** * This class holds configuration values for {@link TestLogger}. The * values are computed at runtime. See {@link TestLogger} documentation for * more information. - * - * + * + * * @author Ceki Gülcü * @author Scott Sanders * @author Rod Waldhoff * @author Robert Burrell Donkin * @author Cédrik LIME - * + * * @since 1.7.25 */ public class TestLoggerConfiguration { + final static boolean SHOW_LOG_NAME_DEFAULT = true; private static final String CONFIGURATION_FILE = "testlogger.properties"; - - static int DEFAULT_LOG_LEVEL_DEFAULT = TestLogger.LOG_LEVEL_INFO; - int defaultLogLevel = DEFAULT_LOG_LEVEL_DEFAULT; - private static final boolean SHOW_DATE_TIME_DEFAULT = false; - boolean showDateTime = SHOW_DATE_TIME_DEFAULT; - private static final String DATE_TIME_FORMAT_STR_DEFAULT = null; - private static String dateTimeFormatStr = DATE_TIME_FORMAT_STR_DEFAULT; - - DateFormat dateFormatter = null; - private static final boolean SHOW_THREAD_NAME_DEFAULT = true; - boolean showThreadName = SHOW_THREAD_NAME_DEFAULT; - /** * See https://jira.qos.ch/browse/SLF4J-499 * @since 1.7.33 and 2.0.0-alpha6 */ private static final boolean SHOW_THREAD_ID_DEFAULT = false; + private static final boolean SHOW_SHORT_LOG_NAME_DEFAULT = false; + private static final boolean LEVEL_IN_BRACKETS_DEFAULT = false; + private static final String LOG_FILE_DEFAULT = "System.err"; + private static final boolean CACHE_OUTPUT_STREAM_DEFAULT = false; + private static final String WARN_LEVELS_STRING_DEFAULT = "WARN"; + static int DEFAULT_LOG_LEVEL_DEFAULT = TestLogger.LOG_LEVEL_INFO; + private static String dateTimeFormatStr = DATE_TIME_FORMAT_STR_DEFAULT; + private final Properties properties = new Properties(); + int defaultLogLevel = DEFAULT_LOG_LEVEL_DEFAULT; + boolean showDateTime = SHOW_DATE_TIME_DEFAULT; + DateFormat dateFormatter = null; + boolean showThreadName = SHOW_THREAD_NAME_DEFAULT; boolean showThreadId = SHOW_THREAD_ID_DEFAULT; - - final static boolean SHOW_LOG_NAME_DEFAULT = true; boolean showLogName = SHOW_LOG_NAME_DEFAULT; - - private static final boolean SHOW_SHORT_LOG_NAME_DEFAULT = false; boolean showShortLogName = SHOW_SHORT_LOG_NAME_DEFAULT; - - private static final boolean LEVEL_IN_BRACKETS_DEFAULT = false; boolean levelInBrackets = LEVEL_IN_BRACKETS_DEFAULT; - - private static final String LOG_FILE_DEFAULT = "System.err"; - private String logFile = LOG_FILE_DEFAULT; OutputChoice outputChoice = null; - - private static final boolean CACHE_OUTPUT_STREAM_DEFAULT = false; + String warnLevelString = WARN_LEVELS_STRING_DEFAULT; + private String logFile = LOG_FILE_DEFAULT; private boolean cacheOutputStream = CACHE_OUTPUT_STREAM_DEFAULT; - private static final String WARN_LEVELS_STRING_DEFAULT = "WARN"; - String warnLevelString = WARN_LEVELS_STRING_DEFAULT; + static int stringToLevel(String levelStr) { + if ("trace".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_TRACE; + } else if ("debug".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_DEBUG; + } else if ("info".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_INFO; + } else if ("warn".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_WARN; + } else if ("error".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_ERROR; + } else if ("off".equalsIgnoreCase(levelStr)) { + return TestLogger.LOG_LEVEL_OFF; + } + // assume INFO by default + return TestLogger.LOG_LEVEL_INFO; + } - private final Properties properties = new Properties(); + private static OutputChoice computeOutputChoice(String logFile, boolean cacheOutputStream) { + if ("System.err".equalsIgnoreCase(logFile)) { + if (cacheOutputStream) { + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_ERR); + } else { + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + } + } else if ("System.out".equalsIgnoreCase(logFile)) { + if (cacheOutputStream) { + return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_OUT); + } else { + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_OUT); + } + } else { + try { + FileOutputStream fos = new FileOutputStream(logFile); + PrintStream printStream = new PrintStream(fos); + return new OutputChoice(printStream); + } catch (FileNotFoundException e) { + Util.report("Could not open [" + logFile + "]. Defaulting to System.err", e); + return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); + } + } + } void init() { loadProperties(); String defaultLogLevelString = getStringProperty(TestLogger.DEFAULT_LOG_LEVEL_KEY, null); - if (defaultLogLevelString != null) + if (defaultLogLevelString != null) { defaultLogLevel = stringToLevel(defaultLogLevelString); + } showLogName = getBooleanProperty(TestLogger.SHOW_LOG_NAME_KEY, TestLoggerConfiguration.SHOW_LOG_NAME_DEFAULT); showShortLogName = getBooleanProperty(TestLogger.SHOW_SHORT_LOG_NAME_KEY, SHOW_SHORT_LOG_NAME_DEFAULT); @@ -171,45 +201,4 @@ String getStringProperty(String name) { return (prop == null) ? properties.getProperty(name) : prop; } - static int stringToLevel(String levelStr) { - if ("trace".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_TRACE; - } else if ("debug".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_DEBUG; - } else if ("info".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_INFO; - } else if ("warn".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_WARN; - } else if ("error".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_ERROR; - } else if ("off".equalsIgnoreCase(levelStr)) { - return TestLogger.LOG_LEVEL_OFF; - } - // assume INFO by default - return TestLogger.LOG_LEVEL_INFO; - } - - private static OutputChoice computeOutputChoice(String logFile, boolean cacheOutputStream) { - if ("System.err".equalsIgnoreCase(logFile)) - if (cacheOutputStream) - return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_ERR); - else - return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); - else if ("System.out".equalsIgnoreCase(logFile)) { - if (cacheOutputStream) - return new OutputChoice(OutputChoice.OutputChoiceType.CACHED_SYS_OUT); - else - return new OutputChoice(OutputChoice.OutputChoiceType.SYS_OUT); - } else { - try { - FileOutputStream fos = new FileOutputStream(logFile); - PrintStream printStream = new PrintStream(fos); - return new OutputChoice(printStream); - } catch (FileNotFoundException e) { - Util.report("Could not open [" + logFile + "]. Defaulting to System.err", e); - return new OutputChoice(OutputChoice.OutputChoiceType.SYS_ERR); - } - } - } - } diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java index db057d714..d597b5706 100644 --- a/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java +++ b/powertools-logging/src/test/java/org/slf4j/test/TestLoggerFactory.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2011 QOS.ch * All rights reserved. - * + *

    * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + *

    * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + *

    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -20,21 +20,20 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * */ -package org.slf4j.test; -import org.slf4j.ILoggerFactory; -import org.slf4j.Logger; +package org.slf4j.test; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; /** * An implementation of {@link ILoggerFactory} which always returns * {@link TestLogger} instances. - * + * * @author Ceki Gülcü */ public class TestLoggerFactory implements ILoggerFactory { diff --git a/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java index 62a0136ef..357360d1e 100644 --- a/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java +++ b/powertools-logging/src/test/java/org/slf4j/test/TestServiceProvider.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2004-2011 QOS.ch * All rights reserved. - * + *

    * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: - * + *

    * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. - * + *

    * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -20,8 +20,8 @@ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * */ + package org.slf4j.test; import org.slf4j.ILoggerFactory; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java index 17ace1689..03f992966 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java @@ -14,14 +14,15 @@ public class TestLoggingManager implements LoggingManager { public TestLoggingManager() { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); if (!(loggerFactory instanceof TestLoggerFactory)) { - throw new RuntimeException("LoggerFactory does not match required type: " + TestLoggerFactory.class.getName()); + throw new RuntimeException( + "LoggerFactory does not match required type: " + TestLoggerFactory.class.getName()); } this.loggerFactory = (TestLoggerFactory) loggerFactory; } @Override public void resetLogLevel(Level logLevel) { - loggerFactory.getLoggers().forEach( (key, logger) -> ((TestLogger) logger).setLogLevel(logLevel.toString())); + loggerFactory.getLoggers().forEach((key, logger) -> ((TestLogger) logger).setLogLevel(logLevel.toString())); } @Override From 381082d012b615946870c62fa7609733439ef6fd Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 23:01:45 +0200 Subject: [PATCH 09/74] add ignored file --- ...bda.powertools.logging.internal.LoggingManager | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager diff --git a/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..adbf7ae69 --- /dev/null +++ b/powertools-logging/src/test/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1,15 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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. +# +# + +software.amazon.lambda.powertools.logging.internal.TestLoggingManager \ No newline at end of file From c4f4b62244fa51aeb766b42f59080d50d5dd5ffd Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 23:11:20 +0200 Subject: [PATCH 10/74] fix example --- examples/powertools-examples-core-utilities/sam/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/powertools-examples-core-utilities/sam/pom.xml b/examples/powertools-examples-core-utilities/sam/pom.xml index a88150f4b..1992b3410 100644 --- a/examples/powertools-examples-core-utilities/sam/pom.xml +++ b/examples/powertools-examples-core-utilities/sam/pom.xml @@ -23,7 +23,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} From cd692e1969e4e04b193e6eaf7c581be97f1daa75 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Sun, 17 Sep 2023 23:31:08 +0200 Subject: [PATCH 11/74] replace log4j with slf4j everywhere --- .../dynamo/DynamoDBStreamBatchHandler.java | 6 ++--- .../org/demo/batch/dynamo/DynamoDBWriter.java | 6 ++--- .../batch/kinesis/KinesisBatchHandler.java | 6 ++--- .../batch/kinesis/KinesisBatchSender.java | 6 ++--- .../org/demo/batch/sqs/SqsBatchHandler.java | 6 ++--- .../org/demo/batch/sqs/SqsBatchSender.java | 6 ++--- .../src/main/java/helloworld/App.java | 20 ++++++++-------- .../cdk/app/src/main/java/helloworld/App.java | 7 +++--- .../gradle/src/main/java/helloworld/App.java | 6 ++--- .../sam/src/main/java/helloworld/App.java | 6 ++--- .../src/main/java/helloworld/App.java | 6 ++--- .../demo/parameters/ParametersFunction.java | 6 ++--- ...GatewayRequestDeserializationFunction.java | 13 ++++++---- .../SQSEventDeserializationFunction.java | 6 ++--- .../handler/DynamoDbBatchMessageHandler.java | 2 +- .../KinesisStreamsBatchMessageHandler.java | 2 +- .../batch/handler/SqsBatchMessageHandler.java | 4 ++-- .../lambda/powertools/e2e/Function.java | 24 ++++--------------- .../lambda/powertools/e2e/Function.java | 6 ++--- .../powertools/idempotency/Idempotency.java | 2 +- .../handlers/IdempotencyFunction.java | 6 ++--- .../powertools/utilities/JsonConfig.java | 2 +- .../validation/ValidationConfig.java | 2 +- 23 files changed, 71 insertions(+), 85 deletions(-) diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java index 988c49e86..e3c27c093 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBStreamBatchHandler.java @@ -4,14 +4,14 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; public class DynamoDBStreamBatchHandler implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(DynamoDBStreamBatchHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBStreamBatchHandler.class); private final BatchMessageHandler handler; public DynamoDBStreamBatchHandler() { diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java index 953ba8f23..fc1b0747b 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/dynamo/DynamoDBWriter.java @@ -8,8 +8,8 @@ import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.demo.batch.model.DdbProduct; import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient; import software.amazon.awssdk.enhanced.dynamodb.TableSchema; @@ -21,7 +21,7 @@ public class DynamoDBWriter implements RequestHandler { - private static final Logger LOGGER = LogManager.getLogger(DynamoDBWriter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDBWriter.class); private final DynamoDbEnhancedClient enhancedClient; diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java index b188df501..f5d7102b5 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchHandler.java @@ -4,15 +4,15 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.KinesisEvent; import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.demo.batch.model.Product; import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; public class KinesisBatchHandler implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(KinesisBatchHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisBatchHandler.class); private final BatchMessageHandler handler; public KinesisBatchHandler() { diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java index 0bc7dc42c..dadead1a2 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/kinesis/KinesisBatchSender.java @@ -10,8 +10,8 @@ import java.security.SecureRandom; import java.util.List; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.demo.batch.model.Product; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; @@ -28,7 +28,7 @@ */ public class KinesisBatchSender implements RequestHandler { - private static final Logger LOGGER = LogManager.getLogger(KinesisBatchSender.class); + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisBatchSender.class); private final KinesisClient kinesisClient; private final SecureRandom random; diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java index bb9d704d3..27689485c 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchHandler.java @@ -4,14 +4,14 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.SQSBatchResponse; import com.amazonaws.services.lambda.runtime.events.SQSEvent; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.demo.batch.model.Product; import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; public class SqsBatchHandler implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(SqsBatchHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchHandler.class); private final BatchMessageHandler handler; public SqsBatchHandler() { diff --git a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java index af78bed5a..4050ab98b 100644 --- a/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java +++ b/examples/powertools-examples-batch/src/main/java/org/demo/batch/sqs/SqsBatchSender.java @@ -10,8 +10,8 @@ import java.security.SecureRandom; import java.util.List; import java.util.stream.IntStream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.demo.batch.model.Product; import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; import software.amazon.awssdk.services.sqs.SqsClient; @@ -27,7 +27,7 @@ */ public class SqsBatchSender implements RequestHandler { - private static final Logger LOGGER = LogManager.getLogger(SqsBatchSender.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchSender.class); private final SqsClient sqsClient; private final SecureRandom random; diff --git a/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java b/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java index 54f13244c..c35ad8fb9 100644 --- a/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-cloudformation/src/main/java/helloworld/App.java @@ -3,8 +3,8 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.CloudFormationCustomResourceEvent; import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.waiters.WaiterResponse; @@ -24,7 +24,7 @@ */ public class App extends AbstractCustomResourceHandler { - private final static Logger log = LogManager.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); private final S3Client s3Client; public App() { @@ -47,7 +47,7 @@ protected Response create(CloudFormationCustomResourceEvent cloudFormationCustom Objects.requireNonNull(cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"), "BucketName cannot be null."); - log.info(cloudFormationCustomResourceEvent); + log.info(cloudFormationCustomResourceEvent.toString()); String bucketName = (String) cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"); log.info("Bucket Name {}", bucketName); try { @@ -57,7 +57,7 @@ protected Response create(CloudFormationCustomResourceEvent cloudFormationCustom return Response.success(bucketName); } catch (AwsServiceException | SdkClientException e) { // In case of error, return a failed response, with the bucketName as the physicalResourceId - log.error(e); + log.error("Unable to create bucket", e); return Response.failed(bucketName); } } @@ -77,7 +77,7 @@ protected Response update(CloudFormationCustomResourceEvent cloudFormationCustom Objects.requireNonNull(cloudFormationCustomResourceEvent.getResourceProperties().get("BucketName"), "BucketName cannot be null."); - log.info(cloudFormationCustomResourceEvent); + log.info(cloudFormationCustomResourceEvent.toString()); // Get the physicalResourceId. physicalResourceId is the value returned to CloudFormation in the Create request, and passed in on subsequent requests (e.g. UPDATE or DELETE) String physicalResourceId = cloudFormationCustomResourceEvent.getPhysicalResourceId(); log.info("Physical Resource ID {}", physicalResourceId); @@ -94,7 +94,7 @@ protected Response update(CloudFormationCustomResourceEvent cloudFormationCustom // Return a successful response with the newBucketName return Response.success(newBucketName); } catch (AwsServiceException | SdkClientException e) { - log.error(e); + log.error("Unable to create bucket", e); return Response.failed(newBucketName); } } else { @@ -120,7 +120,7 @@ protected Response delete(CloudFormationCustomResourceEvent cloudFormationCustom Objects.requireNonNull(cloudFormationCustomResourceEvent.getPhysicalResourceId(), "PhysicalResourceId cannot be null."); - log.info(cloudFormationCustomResourceEvent); + log.info(cloudFormationCustomResourceEvent.toString()); // Get the physicalResourceId. physicalResourceId is the value provided to CloudFormation in the Create request. String bucketName = cloudFormationCustomResourceEvent.getPhysicalResourceId(); log.info("Bucket Name {}", bucketName); @@ -135,7 +135,7 @@ protected Response delete(CloudFormationCustomResourceEvent cloudFormationCustom return Response.success(bucketName); } catch (AwsServiceException | SdkClientException e) { // Return a failed response in case of errors during the bucket deletion - log.error(e); + log.error("Unable to delete bucket", e); return Response.failed(bucketName); } } else { @@ -166,7 +166,7 @@ private void createBucket(String bucketName) { s3Client.createBucket(createBucketRequest); WaiterResponse waiterResponse = waiter.waitUntilBucketExists(HeadBucketRequest.builder().bucket(bucketName).build()); - waiterResponse.matched().response().ifPresent(log::info); + waiterResponse.matched().response().ifPresent(res -> log.info(res.toString())); log.info("Bucket Created {}", bucketName); } } \ No newline at end of file diff --git a/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java index 988da2a73..18eea0560 100644 --- a/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-core-utilities/cdk/app/src/main/java/helloworld/App.java @@ -29,8 +29,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; import software.amazon.lambda.powertools.logging.Logging; @@ -44,8 +44,7 @@ * Handler for requests to Lambda function. */ public class App implements RequestHandler { - private final static Logger log = LogManager.getLogger(App.class); - + private static final Logger log = LoggerFactory.getLogger(App.class); @Logging(logEvent = true, samplingRate = 0.7) @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) @Metrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true) diff --git a/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java index fccc63b9a..36ef72ae7 100644 --- a/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-core-utilities/gradle/src/main/java/helloworld/App.java @@ -29,8 +29,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; import software.amazon.lambda.powertools.logging.Logging; @@ -44,7 +44,7 @@ * Handler for requests to Lambda function. */ public class App implements RequestHandler { - private final static Logger log = LogManager.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); @Logging(logEvent = true, samplingRate = 0.7) @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) diff --git a/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java index fccc63b9a..36ef72ae7 100644 --- a/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java @@ -29,8 +29,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.cloudwatchlogs.emf.model.DimensionSet; import software.amazon.cloudwatchlogs.emf.model.Unit; import software.amazon.lambda.powertools.logging.Logging; @@ -44,7 +44,7 @@ * Handler for requests to Lambda function. */ public class App implements RequestHandler { - private final static Logger log = LogManager.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); @Logging(logEvent = true, samplingRate = 0.7) @Tracing(captureMode = CaptureMode.RESPONSE_AND_ERROR) diff --git a/examples/powertools-examples-idempotency/src/main/java/helloworld/App.java b/examples/powertools-examples-idempotency/src/main/java/helloworld/App.java index 72fa621ad..cf0c0ee31 100644 --- a/examples/powertools-examples-idempotency/src/main/java/helloworld/App.java +++ b/examples/powertools-examples-idempotency/src/main/java/helloworld/App.java @@ -26,8 +26,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.lambda.powertools.idempotency.Idempotency; import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; @@ -37,7 +37,7 @@ import software.amazon.lambda.powertools.utilities.JsonConfig; public class App implements RequestHandler { - private final static Logger log = LogManager.getLogger(App.class); + private static final Logger log = LoggerFactory.getLogger(App.class); public App() { this(null); diff --git a/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java b/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java index 5b691cfd9..56f3ef5af 100644 --- a/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java +++ b/examples/powertools-examples-parameters/src/main/java/org/demo/parameters/ParametersFunction.java @@ -29,14 +29,14 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.parameters.ParamManager; import software.amazon.lambda.powertools.parameters.SSMProvider; import software.amazon.lambda.powertools.parameters.SecretsProvider; public class ParametersFunction implements RequestHandler { - private final static Logger log = LogManager.getLogger(ParametersFunction.class); + private static final Logger log = LoggerFactory.getLogger(ParametersFunction.class); SSMProvider ssmProvider = ParamManager.getSsmProvider(); SecretsProvider secretsProvider = ParamManager.getSecretsProvider(); diff --git a/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java b/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java index e70b37959..3ca75cf4a 100644 --- a/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java +++ b/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/APIGatewayRequestDeserializationFunction.java @@ -22,18 +22,21 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import java.util.HashMap; import java.util.Map; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class APIGatewayRequestDeserializationFunction implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(APIGatewayRequestDeserializationFunction.class); - private static final Map HEADERS = new HashMap() {{ + private static final Logger LOGGER = LoggerFactory.getLogger(APIGatewayRequestDeserializationFunction.class); + private static final Map HEADERS = new HashMap() { + private static final long serialVersionUID = 7074189990115081999L; + { put("Content-Type", "application/json"); put("X-Custom-Header", "application/json"); - }}; + } + }; public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) { diff --git a/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java b/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java index 36dbed074..79097e19c 100644 --- a/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java +++ b/examples/powertools-examples-serialization/src/main/java/org/demo/serialization/SQSEventDeserializationFunction.java @@ -20,13 +20,13 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.SQSEvent; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SQSEventDeserializationFunction implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(SQSEventDeserializationFunction.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SQSEventDeserializationFunction.class); public String handleRequest(SQSEvent event, Context context) { List products = extractDataFrom(event).asListOf(Product.class); diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java index aa6eba839..83a8bf7dd 100644 --- a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/DynamoDbBatchMessageHandler.java @@ -30,7 +30,7 @@ * @see DynamoDB Streams batch failure reporting */ public class DynamoDbBatchMessageHandler implements BatchMessageHandler { - private final static Logger LOGGER = LoggerFactory.getLogger(DynamoDbBatchMessageHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbBatchMessageHandler.class); private final Consumer successHandler; private final BiConsumer failureHandler; diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java index fe1aaf354..ad1dd302d 100644 --- a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/KinesisStreamsBatchMessageHandler.java @@ -34,7 +34,7 @@ * @param The user-defined type of the Kinesis record payload */ public class KinesisStreamsBatchMessageHandler implements BatchMessageHandler { - private final static Logger LOGGER = LoggerFactory.getLogger(KinesisStreamsBatchMessageHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(KinesisStreamsBatchMessageHandler.class); private final BiConsumer rawMessageHandler; private final BiConsumer messageHandler; diff --git a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java index b3c416a69..b634f9b62 100644 --- a/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java +++ b/powertools-batch/src/main/java/software/amazon/lambda/powertools/batch/handler/SqsBatchMessageHandler.java @@ -31,11 +31,11 @@ * @see SQS Batch failure reporting */ public class SqsBatchMessageHandler implements BatchMessageHandler { - private final static Logger LOGGER = LoggerFactory.getLogger(SqsBatchMessageHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SqsBatchMessageHandler.class); // The attribute on an SQS-FIFO message used to record the message group ID // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#sample-fifo-queues-message-event - private final static String MESSAGE_GROUP_ID_KEY = "MessageGroupId"; + private static final String MESSAGE_GROUP_ID_KEY = "MessageGroupId"; private final Class messageClass; private final BiConsumer messageHandler; diff --git a/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java index 64f5a02c2..bfde65bc8 100644 --- a/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java +++ b/powertools-e2e-tests/handlers/batch/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -14,7 +14,6 @@ package software.amazon.lambda.powertools.e2e; -import com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.ObjectMapper; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.events.DynamodbEvent; @@ -24,41 +23,26 @@ import com.amazonaws.services.lambda.runtime.events.StreamsEventResponse; import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers; -import com.amazonaws.services.lambda.runtime.serialization.factories.JacksonFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.JsonNode; - import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.lambda.powertools.batch.BatchMessageHandlerBuilder; import software.amazon.lambda.powertools.batch.handler.BatchMessageHandler; import software.amazon.lambda.powertools.e2e.model.Product; -import software.amazon.lambda.powertools.logging.Logging; -import software.amazon.lambda.powertools.utilities.JsonConfig; - -import javax.management.Attribute; public class Function implements RequestHandler { - private final static Logger LOGGER = LogManager.getLogger(Function.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Function.class); private final BatchMessageHandler sqsHandler; private final BatchMessageHandler kinesisHandler; @@ -139,7 +123,7 @@ public Object createResult(String input, Context context) { SQSEvent event = serializer.fromJson(input); if (event.getRecords().get(0).getEventSource().equals("aws:sqs")) { LOGGER.info("Running for SQS"); - LOGGER.info(event); + LOGGER.info(event.toString()); return sqsHandler.processBatch(event, context); } diff --git a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java index 62ebabc6e..76ceff053 100644 --- a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java +++ b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -16,14 +16,14 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; public class Function implements RequestHandler { - private static final Logger LOG = LogManager.getLogger(Function.class); + private static final Logger LOG = LoggerFactory.getLogger(Function.class); @Logging public String handleRequest(Input input, Context context) { diff --git a/powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java b/powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java index 6da826c45..bd564caf8 100644 --- a/powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java +++ b/powertools-idempotency/src/main/java/software/amazon/lambda/powertools/idempotency/Idempotency.java @@ -82,7 +82,7 @@ private void setPersistenceStore(BasePersistenceStore persistenceStore) { } private static class Holder { - private final static Idempotency instance = new Idempotency(); + private static final Idempotency instance = new Idempotency(); } public static class Config { diff --git a/powertools-idempotency/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunction.java b/powertools-idempotency/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunction.java index 76c36ae9f..43e191fc2 100644 --- a/powertools-idempotency/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunction.java +++ b/powertools-idempotency/src/test/java/software/amazon/lambda/powertools/idempotency/handlers/IdempotencyFunction.java @@ -25,8 +25,8 @@ import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.lambda.powertools.idempotency.Idempotency; import software.amazon.lambda.powertools.idempotency.IdempotencyConfig; @@ -35,7 +35,7 @@ import software.amazon.lambda.powertools.utilities.JsonConfig; public class IdempotencyFunction implements RequestHandler { - private final static Logger LOG = LogManager.getLogger(IdempotencyFunction.class); + private static final Logger LOG = LoggerFactory.getLogger(IdempotencyFunction.class); public boolean handlerExecuted = false; diff --git a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java index baa6a0367..e961f21fa 100644 --- a/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java +++ b/powertools-serialization/src/main/java/software/amazon/lambda/powertools/utilities/JsonConfig.java @@ -82,6 +82,6 @@ public void addFunction(T function) { } private static class ConfigHolder { - private final static JsonConfig instance = new JsonConfig(); + private static final JsonConfig instance = new JsonConfig(); } } diff --git a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java index 143f0584d..ccc5a4c2c 100644 --- a/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java +++ b/powertools-validation/src/main/java/software/amazon/lambda/powertools/validation/ValidationConfig.java @@ -97,6 +97,6 @@ public ObjectMapper getObjectMapper() { } private static class ConfigHolder { - private final static ValidationConfig instance = new ValidationConfig(); + private static final ValidationConfig instance = new ValidationConfig(); } } From 5c0469d93b31875175887f49b7826552f5e08e02 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 18 Sep 2023 08:57:01 +0200 Subject: [PATCH 12/74] use the implementation --- examples/powertools-examples-batch/pom.xml | 2 +- examples/powertools-examples-cloudformation/pom.xml | 2 +- .../powertools-examples-core-utilities/cdk/app/pom.xml | 2 +- examples/powertools-examples-idempotency/pom.xml | 2 +- examples/powertools-examples-parameters/pom.xml | 2 +- examples/powertools-examples-serialization/pom.xml | 2 +- examples/powertools-examples-validation/pom.xml | 2 +- pom.xml | 10 ++++++++++ powertools-e2e-tests/handlers/batch/pom.xml | 2 +- powertools-e2e-tests/handlers/idempotency/pom.xml | 2 +- powertools-e2e-tests/handlers/largemessage/pom.xml | 2 +- .../handlers/largemessage_idempotent/pom.xml | 2 +- powertools-e2e-tests/handlers/logging/pom.xml | 2 +- powertools-e2e-tests/handlers/parameters/pom.xml | 2 +- powertools-e2e-tests/handlers/pom.xml | 2 +- powertools-idempotency/pom.xml | 5 +++++ 16 files changed, 29 insertions(+), 14 deletions(-) diff --git a/examples/powertools-examples-batch/pom.xml b/examples/powertools-examples-batch/pom.xml index a5115aeeb..fe405e8d9 100644 --- a/examples/powertools-examples-batch/pom.xml +++ b/examples/powertools-examples-batch/pom.xml @@ -26,7 +26,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml index d179fbe2e..a61564def 100644 --- a/examples/powertools-examples-cloudformation/pom.xml +++ b/examples/powertools-examples-cloudformation/pom.xml @@ -49,7 +49,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-core-utilities/cdk/app/pom.xml b/examples/powertools-examples-core-utilities/cdk/app/pom.xml index be8c9ca67..fd0996eef 100644 --- a/examples/powertools-examples-core-utilities/cdk/app/pom.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/pom.xml @@ -25,7 +25,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-idempotency/pom.xml b/examples/powertools-examples-idempotency/pom.xml index 99eb5dc9f..6c44a96dc 100644 --- a/examples/powertools-examples-idempotency/pom.xml +++ b/examples/powertools-examples-idempotency/pom.xml @@ -37,7 +37,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-parameters/pom.xml b/examples/powertools-examples-parameters/pom.xml index cffab7a65..9f7970335 100644 --- a/examples/powertools-examples-parameters/pom.xml +++ b/examples/powertools-examples-parameters/pom.xml @@ -16,7 +16,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-serialization/pom.xml b/examples/powertools-examples-serialization/pom.xml index 5d7036497..a78059b2c 100644 --- a/examples/powertools-examples-serialization/pom.xml +++ b/examples/powertools-examples-serialization/pom.xml @@ -15,7 +15,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/examples/powertools-examples-validation/pom.xml b/examples/powertools-examples-validation/pom.xml index 2d4ef07a0..b8a6c8b7e 100644 --- a/examples/powertools-examples-validation/pom.xml +++ b/examples/powertools-examples-validation/pom.xml @@ -30,7 +30,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} diff --git a/pom.xml b/pom.xml index 4b6cdbca4..dca4d3765 100644 --- a/pom.xml +++ b/pom.xml @@ -122,6 +122,16 @@ powertools-logging ${project.version} + + software.amazon.lambda + powertools-logging-log4j + ${project.version} + + + software.amazon.lambda + powertools-logging-logback + ${project.version} + software.amazon.lambda powertools-sqs diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml index aee9bf3dd..62f43aa1b 100644 --- a/powertools-e2e-tests/handlers/batch/pom.xml +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -19,7 +19,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j com.amazonaws diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index 22b6a1c53..28c8599b5 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -19,7 +19,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j org.apache.logging.log4j diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml index 8302624ef..e906538aa 100644 --- a/powertools-e2e-tests/handlers/largemessage/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -23,7 +23,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j com.amazonaws diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml index 1fe9092ef..347ccfff4 100644 --- a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -23,7 +23,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j com.amazonaws diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 0ee014116..e68acd094 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -15,7 +15,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j com.amazonaws diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml index 4d5330da0..d69856a10 100644 --- a/powertools-e2e-tests/handlers/parameters/pom.xml +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -15,7 +15,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j software.amazon.lambda diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index fbe2e6d8b..292049fb5 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -46,7 +46,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${lambda.powertools.version} diff --git a/powertools-idempotency/pom.xml b/powertools-idempotency/pom.xml index 61294b996..62dfad624 100644 --- a/powertools-idempotency/pom.xml +++ b/powertools-idempotency/pom.xml @@ -55,6 +55,11 @@ + + org.aspectj + aspectjrt + provided + software.amazon.lambda powertools-common From eff1dc1013c4f2cdc146f416e41d245a1c0f519d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 18 Sep 2023 16:32:53 +0200 Subject: [PATCH 13/74] not for cdk (previous version) --- examples/powertools-examples-core-utilities/cdk/app/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/powertools-examples-core-utilities/cdk/app/pom.xml b/examples/powertools-examples-core-utilities/cdk/app/pom.xml index fd0996eef..be8c9ca67 100644 --- a/examples/powertools-examples-core-utilities/cdk/app/pom.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/pom.xml @@ -25,7 +25,7 @@ software.amazon.lambda - powertools-logging-log4j + powertools-logging ${project.version} From afc6f649f485518cb987b7c853c7eb7a6b603183 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 18 Sep 2023 17:34:25 +0200 Subject: [PATCH 14/74] fix build --- examples/powertools-examples-idempotency/pom.xml | 8 +++++++- .../powertools/logging/internal/Log4jLoggingManager.java | 1 + .../logging/internal/LogbackLoggingManager.java | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/powertools-examples-idempotency/pom.xml b/examples/powertools-examples-idempotency/pom.xml index 6c44a96dc..b9e0bf249 100644 --- a/examples/powertools-examples-idempotency/pom.xml +++ b/examples/powertools-examples-idempotency/pom.xml @@ -86,7 +86,13 @@ com.amazonaws DynamoDBLocal - 1.22.0 + 1.23.0 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.15.2 test diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java index 5e6f68c5a..87c9f4183 100644 --- a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -23,6 +23,7 @@ public class Log4jLoggingManager implements LoggingManager { @Override + @SuppressWarnings("java:S4792") public void resetLogLevel(org.slf4j.event.Level logLevel) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.getLevel(logLevel.toString())); diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java index 1d27e103a..222adf7e8 100644 --- a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java +++ b/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java @@ -34,6 +34,7 @@ public LogbackLoggingManager() { } @Override + @SuppressWarnings("java:S4792") public void resetLogLevel(org.slf4j.event.Level logLevel) { List loggers = loggerContext.getLoggerList(); for (Logger logger : loggers) { From e6121df8c166c0c1ecb574c3e252f563b619e95d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 18 Sep 2023 22:25:11 +0200 Subject: [PATCH 15/74] fix e2e --- powertools-e2e-tests/handlers/idempotency/pom.xml | 5 +++++ powertools-e2e-tests/handlers/largemessage/pom.xml | 5 +++++ .../handlers/largemessage_idempotent/pom.xml | 5 +++++ powertools-e2e-tests/handlers/logging/pom.xml | 5 +++++ powertools-e2e-tests/handlers/parameters/pom.xml | 5 +++++ 5 files changed, 25 insertions(+) diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index 28c8599b5..ecc6190fe 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -21,6 +21,11 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-core + 2.20.0 + org.apache.logging.log4j log4j-slf4j2-impl diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml index e906538aa..e50695e8f 100644 --- a/powertools-e2e-tests/handlers/largemessage/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -25,6 +25,11 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-core + 2.20.0 + com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml index 347ccfff4..3306f055f 100644 --- a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -25,6 +25,11 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-core + 2.20.0 + com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index e68acd094..6b08689a1 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -17,6 +17,11 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-core + 2.20.0 + com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml index d69856a10..ef08aa3a7 100644 --- a/powertools-e2e-tests/handlers/parameters/pom.xml +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -17,6 +17,11 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-core + 2.20.0 + software.amazon.lambda powertools-parameters From fb4f7a0442fa9c2f655f0532b11000c4817c5b1d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 14:27:00 +0200 Subject: [PATCH 16/74] fix e2e logging --- powertools-e2e-tests/handlers/logging/pom.xml | 10 ++++++++++ .../amazon/lambda/powertools/e2e/Function.java | 2 -- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 6b08689a1..f7d3dfe6b 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -17,11 +17,21 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.20.0 + org.apache.logging.log4j log4j-core 2.20.0 + + org.apache.logging.log4j + log4j-layout-template-json + 2.20.0 + com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java index 76ceff053..c2634533d 100644 --- a/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java +++ b/powertools-e2e-tests/handlers/logging/src/main/java/software/amazon/lambda/powertools/e2e/Function.java @@ -22,12 +22,10 @@ import software.amazon.lambda.powertools.logging.LoggingUtils; public class Function implements RequestHandler { - private static final Logger LOG = LoggerFactory.getLogger(Function.class); @Logging public String handleRequest(Input input, Context context) { - LoggingUtils.appendKeys(input.getKeys()); LOG.info(input.getMessage()); From 9ba07fabe3818b2ac49dc08157e79bb87cd2b767 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 15:41:44 +0200 Subject: [PATCH 17/74] fix e2e logging + field names --- docs/core/logging.md | 44 +++++++++---------- .../amazon/lambda/powertools/LoggingE2ET.java | 4 +- .../lambda/powertools/logging/Logging.java | 6 +-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index 2df9a4529..58e907906 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -204,14 +204,14 @@ Key | Type | Example | Description ------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- **timestamp** | String | "2020-05-24 18:17:33,774" | Timestamp of actual log statement **level** | String | "INFO" | Logging level -**coldStart** | Boolean | true| ColdStart value. +**cold_start** | Boolean | true| ColdStart value. **service** | String | "payment" | Service name defined. "service_undefined" will be used if unknown -**samplingRate** | int | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case +**sampling_rate** | int | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case **message** | String | "Collecting payment" | Log statement value. Unserializable JSON values will be casted to string -**functionName**| String | "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" -**functionVersion**| String | "12" -**functionMemorySize**| String | "128" -**functionArn**| String | "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" +**function_name**| String | "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" +**function_version**| String | "12" +**function_memory_size**| String | "128" +**function_arn**| String | "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" **xray_trace_id**| String | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when Lambda function has enabled Tracing **function_request_id**| String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context @@ -343,10 +343,10 @@ You can set a Correlation ID using `correlationIdPath` attribute by passing a [J "message": "Collecting payment", "timestamp": "2021-05-03 11:47:12,494+0200", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", + "cold_start": true, + "function_name": "test", + "function_memory_size": 128, + "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } @@ -393,10 +393,10 @@ for known event sources, where either a request ID or X-Ray Trace ID are present "message": "Collecting payment", "timestamp": "2021-05-03 11:47:12,494+0200", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", + "cold_start": true, + "function_name": "test", + "function_memory_size": 128, + "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } @@ -506,10 +506,10 @@ this means that custom keys can be persisted across invocations. If you want all "message": "Collecting payment", "timestamp": "2021-05-03 11:47:12,494+0200", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", + "cold_start": true, + "function_name": "test", + "function_memory_size": 128, + "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "specialKey": "value" } @@ -523,10 +523,10 @@ this means that custom keys can be persisted across invocations. If you want all "message": "Collecting payment", "timestamp": "2021-05-03 11:47:12,494+0200", "service": "payment", - "coldStart": true, - "functionName": "test", - "functionMemorySize": 128, - "functionArn": "arn:aws:lambda:eu-west-1:12345678910:function:test", + "cold_start": true, + "function_name": "test", + "function_memory_size": 128, + "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72" } ``` diff --git a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java index f958970d8..eed7f6e0c 100644 --- a/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java +++ b/powertools-e2e-tests/src/test/java/software/amazon/lambda/powertools/LoggingE2ET.java @@ -82,13 +82,13 @@ public void test_logInfoWithAdditionalKeys() throws JsonProcessingException { JsonNode jsonNode = objectMapper.readTree(functionLogs[0]); assertThat(jsonNode.get("message").asText()).isEqualTo("New Order"); assertThat(jsonNode.get("orderId").asText()).isEqualTo(orderId); - assertThat(jsonNode.get("coldStart").asBoolean()).isTrue(); + assertThat(jsonNode.get("cold_start").asBoolean()).isTrue(); assertThat(jsonNode.get("function_request_id").asText()).isEqualTo(invocationResult1.getRequestId()); // second call should not be cold start functionLogs = invocationResult2.getLogs().getFunctionLogs(INFO); assertThat(functionLogs).hasSize(1); jsonNode = objectMapper.readTree(functionLogs[0]); - assertThat(jsonNode.get("coldStart").asBoolean()).isFalse(); + assertThat(jsonNode.get("cold_start").asBoolean()).isFalse(); } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index 3488264a2..0790452cc 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -34,9 +34,9 @@ * {@code com.amazonaws.services.lambda.runtime.Context}

    * *
      - *
    • FunctionName
    • - *
    • FunctionVersion
    • - *
    • InvokedFunctionArn
    • + *
    • function_name
    • + *
    • function_version
    • + *
    • function_arn
    • *
    • MemoryLimitInMB
    • *
    * From 8db9b7c49e46e5930fee2539e2f04af63fe5bf15 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 15:48:10 +0200 Subject: [PATCH 18/74] new tests --- .../internal/LambdaHandlerProcessor.java | 1 - powertools-logging/pom.xml | 9 +- .../logging/internal/LambdaLoggingAspect.java | 18 ++- ...olsLogApiGatewayHttpApiCorrelationId.java} | 4 +- ...olsLogApiGatewayRestApiCorrelationId.java} | 4 +- .../handlers/PowertoolsLogClearState.java | 35 +++++ ...sabled.java => PowertoolsLogDisabled.java} | 2 +- ...va => PowertoolsLogDisabledForStream.java} | 2 +- ...Enabled.java => PowertoolsLogEnabled.java} | 4 +- ...ava => PowertoolsLogEnabledForStream.java} | 2 +- .../logging/handlers/PowertoolsLogEvent.java | 28 ++++ .../handlers/PowertoolsLogEventForStream.java | 34 +++++ .../PowertoolsLogSamplingDisabled.java | 33 ++++ ...java => PowertoolsLogSamplingEnabled.java} | 8 +- .../internal/LambdaLoggingAspectTest.java | 143 +++++++++++------- .../test/resources/s3EventNotification.json | 38 ----- 16 files changed, 249 insertions(+), 116 deletions(-) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerLogToolApiGatewayHttpApiCorrelationId.java => PowertoolsLogApiGatewayHttpApiCorrelationId.java} (85%) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerLogToolApiGatewayRestApiCorrelationId.java => PowertoolsLogApiGatewayRestApiCorrelationId.java} (85%) create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerToolDisabled.java => PowertoolsLogDisabled.java} (91%) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerToolDisabledForStream.java => PowertoolsLogDisabledForStream.java} (92%) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerLogToolEnabled.java => PowertoolsLogEnabled.java} (88%) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerLogToolEnabledForStream.java => PowertoolsLogEnabledForStream.java} (93%) create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerLogToolSamplingEnabled.java => PowertoolsLogSamplingEnabled.java} (78%) delete mode 100644 powertools-logging/src/test/resources/s3EventNotification.json diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java index 1a2b29c2c..7ef156f0e 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java @@ -61,7 +61,6 @@ public static boolean placedOnStreamHandler(final ProceedingJoinPoint pjp) { } public static Context extractContext(final ProceedingJoinPoint pjp) { - if (placedOnRequestHandler(pjp)) { return (Context) pjp.getArgs()[1]; } else if (placedOnStreamHandler(pjp)) { diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 3f1302890..730afda36 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -27,9 +27,7 @@ jar Powertools for AWS Lambda (Java) library Logging - - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - + Set of utility for better logging - common https://aws.amazon.com/lambda/ GitHub Issues @@ -87,6 +85,11 @@ junit-jupiter-engine test + + org.junit-pioneer + junit-pioneer + test + org.apache.commons commons-lang3 diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 020858494..2881d8f1a 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -66,9 +66,7 @@ public final class LambdaLoggingAspect { private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspect.class); private static final Random SAMPLER = new Random(); - private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); - private static final String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); private static final LoggingManager loggingManager; private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ @@ -83,6 +81,11 @@ public final class LambdaLoggingAspect { } private static LoggingManager loadLoggingManager() { + // JAVA_TOOL_OPTIONS="-Dslf4j.binding=ch.qos.logback.classic.spi.LogbackServiceProvider" + // JAVA_TOOL_OPTIONS="-Dslf4j.binding=org.apache.logging.slf4j.SLF4JServiceProvider" + String slf4jBinding = System.getProperty("slf4j.binding"); + System.out.println(slf4jBinding); // TODO + ServiceLoader loggingManagers = ServiceLoader.load(LoggingManager.class); List loggingManagerList = new ArrayList<>(); for (LoggingManager loggingManager : loggingManagers) { @@ -149,7 +152,7 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, if (isHandlerMethod(pjp)) { if (samplingRate < 0 || samplingRate > 1) { - LOG.debug("Skipping sampling rate configuration because of invalid value. Sampling rate: {}", + LOG.warn("Skipping sampling rate configuration because of invalid value. Sampling rate: {}", samplingRate); return; } @@ -174,12 +177,13 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, } private double samplingRate(final Logging logging) { - if (null != SAMPLING_RATE) { + String sampleRate = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); + if (null != sampleRate) { try { - return Double.parseDouble(SAMPLING_RATE); + return Double.parseDouble(sampleRate); } catch (NumberFormatException e) { - LOG.debug("Skipping sampling rate on environment variable configuration because of invalid " + - "value. Sampling rate: {}", SAMPLING_RATE); + LOG.warn("Skipping sampling rate on environment variable configuration because of invalid " + + "value. Sampling rate: {}", sampleRate); } } return logging.samplingRate(); diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java similarity index 85% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java index 07ead4a64..010e68f0f 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayHttpApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java @@ -23,8 +23,8 @@ import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolApiGatewayHttpApiCorrelationId implements RequestHandler { - private final Logger LOG = LoggerFactory.getLogger(PowerLogToolApiGatewayHttpApiCorrelationId.class); +public class PowertoolsLogApiGatewayHttpApiCorrelationId implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogApiGatewayHttpApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_HTTP) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java similarity index 85% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java index 9e0eeee6b..f0be69ae3 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolApiGatewayRestApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java @@ -23,8 +23,8 @@ import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolApiGatewayRestApiCorrelationId implements RequestHandler { - private final Logger LOG = LoggerFactory.getLogger(PowerLogToolApiGatewayRestApiCorrelationId.class); +public class PowertoolsLogApiGatewayRestApiCorrelationId implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogApiGatewayRestApiCorrelationId.class); @Override @Logging(correlationIdPath = API_GATEWAY_REST) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java new file mode 100644 index 000000000..e1829a777 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogClearState.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; + +public class PowertoolsLogClearState implements RequestHandler, Object> { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogClearState.class); + + @Override + @Logging(clearState = true) + public Object handleRequest(Map input, Context context) { + LoggingUtils.appendKey("mySuperSecret", input.get("mySuperSecret")); + LOG.info("Test event"); + return null; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java similarity index 91% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java index 48a2e3b81..54e887e40 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabled.java @@ -17,7 +17,7 @@ import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; -public class PowerToolDisabled implements RequestHandler { +public class PowertoolsLogDisabled implements RequestHandler { @Override public Object handleRequest(Object input, Context context) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java similarity index 92% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java index 7f93145c7..7f7418ed6 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolDisabledForStream.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogDisabledForStream.java @@ -19,7 +19,7 @@ import java.io.InputStream; import java.io.OutputStream; -public class PowerToolDisabledForStream implements RequestStreamHandler { +public class PowertoolsLogDisabledForStream implements RequestStreamHandler { @Override public void handleRequest(InputStream input, OutputStream output, Context context) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java similarity index 88% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java index 49dca7a8e..79c463405 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java @@ -20,8 +20,8 @@ import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolEnabled implements RequestHandler { - private final Logger LOG = LoggerFactory.getLogger(PowerLogToolEnabled.class); +public class PowertoolsLogEnabled implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); @Override @Logging diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java similarity index 93% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java index 83a370437..c95627302 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolEnabledForStream.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabledForStream.java @@ -20,7 +20,7 @@ import java.io.OutputStream; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolEnabledForStream implements RequestStreamHandler { +public class PowertoolsLogEnabledForStream implements RequestStreamHandler { @Logging @Override diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java new file mode 100644 index 000000000..87677d601 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEvent.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEvent implements RequestHandler { + + @Override + @Logging(logEvent = true) + public Object handleRequest(Object input, Context context) { + return null; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java new file mode 100644 index 000000000..350b29cde --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventForStream.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogEventForStream implements RequestStreamHandler { + + @Override + @Logging(logEvent = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(outputStream, mapper.readValue(inputStream, Map.class)); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java new file mode 100644 index 000000000..5e2a7f148 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingDisabled.java @@ -0,0 +1,33 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogSamplingDisabled implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogSamplingDisabled.class); + + @Override + @Logging(samplingRate = 0.0) + public Boolean handleRequest(Object input, Context context) { + LOG.info("Test event"); + LOG.debug("Test debug event"); + return LOG.isDebugEnabled(); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java similarity index 78% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java index c0b40310d..6a8c37896 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerLogToolSamplingEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogSamplingEnabled.java @@ -20,14 +20,14 @@ import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; -public class PowerLogToolSamplingEnabled implements RequestHandler { - private final Logger LOG = LoggerFactory.getLogger(PowerLogToolSamplingEnabled.class); +public class PowertoolsLogSamplingEnabled implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogSamplingEnabled.class); @Override @Logging(samplingRate = 1.0) - public Object handleRequest(Object input, Context context) { + public Boolean handleRequest(Object input, Context context) { LOG.info("Test event"); LOG.debug("Test debug event"); - return null; + return LOG.isDebugEnabled(); } } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 2c667f3ec..502dc6b5d 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -14,11 +14,9 @@ package software.amazon.lambda.powertools.logging.internal; -import static java.util.Collections.emptyMap; -import static java.util.Collections.singletonList; import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.contentOf; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; @@ -38,36 +36,43 @@ import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent; import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent; -import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification; import com.amazonaws.services.lambda.runtime.tests.annotations.Event; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.Map; +import java.util.Collections; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junitpioneer.jupiter.ClearEnvironmentVariable; +import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; import org.mockito.MockedStatic; import org.slf4j.MDC; import org.slf4j.event.Level; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayHttpApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolApiGatewayRestApiCorrelationId; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabled; -import software.amazon.lambda.powertools.logging.handlers.PowerLogToolEnabledForStream; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabled; -import software.amazon.lambda.powertools.logging.handlers.PowerToolDisabledForStream; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayHttpApiCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayRestApiCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogClearState; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabledForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEvent; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingDisabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingEnabled; class LambdaLoggingAspectTest { @@ -79,13 +84,14 @@ class LambdaLoggingAspectTest { private Context context; @BeforeEach + @ClearEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE") void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { openMocks(this); MDC.clear(); writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); setupContext(); - requestHandler = new PowerLogToolEnabled(); - requestStreamHandler = new PowerLogToolEnabledForStream(); + requestHandler = new PowertoolsLogEnabled(); + requestStreamHandler = new PowertoolsLogEnabledForStream(); //Make sure file is cleaned up before running full stack logging regression FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); resetLogLevel(Level.INFO); @@ -108,7 +114,7 @@ void shouldSetLambdaContextWhenEnabled() { @Test void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { - requestStreamHandler = new PowerLogToolEnabledForStream(); + requestStreamHandler = new PowertoolsLogEnabledForStream(); requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[] {}), new ByteArrayOutputStream(), context); @@ -143,7 +149,7 @@ void shouldSetColdStartFlag() throws IOException { @Test void shouldNotSetLambdaContextWhenDisabled() { - requestHandler = new PowerToolDisabled(); + requestHandler = new PowertoolsLogDisabled(); requestHandler.handleRequest(new Object(), context); @@ -152,16 +158,56 @@ void shouldNotSetLambdaContextWhenDisabled() { @Test void shouldNotSetLambdaContextForStreamHandlerWhenDisabled() throws IOException { - requestStreamHandler = new PowerToolDisabledForStream(); + requestStreamHandler = new PowertoolsLogDisabledForStream(); requestStreamHandler.handleRequest(null, null, context); assertThat(MDC.getCopyOfContextMap()).isNull(); } + @Test + void shouldClearStateWhenClearStateIsTrue() { + PowertoolsLogClearState handler = new PowertoolsLogClearState(); + + handler.handleRequest(Collections.singletonMap("mySuperSecret", "P@ssw0Rd"), context); + + assertThat(MDC.getCopyOfContextMap()).isNull(); + } + + @Test + void shouldLogDebugWhenSamplingEqualsOne() { + PowertoolsLogSamplingEnabled handler = new PowertoolsLogSamplingEnabled(); + Boolean debugEnabled = handler.handleRequest(new Object(), context); + assertThat(debugEnabled).isTrue(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "1") + void shouldLogDebugWhenSamplingEnvVarEqualsOne() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Test debug event"); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "42") + void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { + requestHandler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + } + + @Test + void shouldNotLogDebugWhenSamplingEqualsZero() { + PowertoolsLogSamplingDisabled handler = new PowertoolsLogSamplingDisabled(); + Boolean debugEnabled = handler.handleRequest(new Object(), context); + assertThat(debugEnabled).isFalse(); + } + @Test void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { - PowerLogToolEnabled handler = new PowerLogToolEnabled(); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); handler.anotherMethod(); @@ -194,10 +240,33 @@ void shouldLogxRayTraceIdEnvVarSet() { } } + @Test + void shouldLogEventForHandler() throws IOException { + requestHandler = new PowertoolsLogEvent(); + + requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); + } + + @Test + void shouldLogEventForStreamHandler() throws IOException { + requestStreamHandler = new PowertoolsLogEventForStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + requestStreamHandler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(Collections.singletonMap("key", "value"))), output, context); + + assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) + .isNotEmpty(); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("{\"key\":\"value\"}"); + } + @ParameterizedTest @Event(value = "apiGatewayProxyEventV1.json", type = APIGatewayProxyRequestEvent.class) void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestEvent event) { - RequestHandler handler = new PowerLogToolApiGatewayRestApiCorrelationId(); + RequestHandler handler = new PowertoolsLogApiGatewayRestApiCorrelationId(); handler.handleRequest(event, context); assertThat(MDC.getCopyOfContextMap()) @@ -208,7 +277,7 @@ void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestE @ParameterizedTest @Event(value = "apiGatewayProxyEventV2.json", type = APIGatewayV2HTTPEvent.class) void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) { - RequestHandler handler = new PowerLogToolApiGatewayHttpApiCorrelationId(); + RequestHandler handler = new PowertoolsLogApiGatewayHttpApiCorrelationId(); handler.handleRequest(event, context); assertThat(MDC.getCopyOfContextMap()) @@ -257,38 +326,4 @@ private void resetLogLevel(Level level) writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); } - private S3EventNotification s3EventNotification() { - S3EventNotification.S3EventNotificationRecord record = - new S3EventNotification.S3EventNotificationRecord("us-west-2", - "ObjectCreated:Put", - "aws:s3", - null, - "2.1", - new S3EventNotification.RequestParametersEntity("127.0.0.1"), - new S3EventNotification.ResponseElementsEntity("C3D13FE58DE4C810", - "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"), - new S3EventNotification.S3Entity("testConfigRule", - new S3EventNotification.S3BucketEntity("mybucket", - new S3EventNotification.UserIdentityEntity("A3NL1KOZZKExample"), - "arn:aws:s3:::mybucket"), - new S3EventNotification.S3ObjectEntity("HappyFace.jpg", - 1024L, - "d41d8cd98f00b204e9800998ecf8427e", - "096fKKXTRTtl3on89fVO.nfljtsv6qko", - "0055AED6DCD90281E5"), - "1.0"), - new S3EventNotification.UserIdentityEntity("AIDAJDPLRKLG7UEXAMPLE") - ); - - return new S3EventNotification(singletonList(record)); - } - - private Map parseToMap(String stringAsJson) { - try { - return new ObjectMapper().readValue(stringAsJson, Map.class); - } catch (JsonProcessingException e) { - fail("Failed parsing logger line " + stringAsJson); - return emptyMap(); - } - } } \ No newline at end of file diff --git a/powertools-logging/src/test/resources/s3EventNotification.json b/powertools-logging/src/test/resources/s3EventNotification.json deleted file mode 100644 index feb88ec02..000000000 --- a/powertools-logging/src/test/resources/s3EventNotification.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "records":[ - { - "eventVersion":"2.1", - "eventSource":"aws:s3", - "awsRegion":"us-west-2", - "eventName":"ObjectCreated:Put", - "userIdentity":{ - "principalId":"AIDAJDPLRKLG7UEXAMPLE" - }, - "requestParameters":{ - "sourceIPAddress":"127.0.0.1" - }, - "responseElements":{ - "xAmzId2":"C3D13FE58DE4C810", - "xAmzRequestId":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" - }, - "s3":{ - "s3SchemaVersion":"1.0", - "configurationId":"testConfigRule", - "bucket":{ - "name":"mybucket", - "ownerIdentity":{ - "principalId":"A3NL1KOZZKExample" - }, - "arn":"arn:aws:s3:::mybucket" - }, - "object":{ - "key":"HappyFace.jpg", - "size":1024, - "eTag":"d41d8cd98f00b204e9800998ecf8427e", - "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko", - "sequencer":"0055AED6DCD90281E5" - } - } - } - ] -} \ No newline at end of file From 135e2d5928972dbda142c196ed89fa2a8c433089 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 15:48:57 +0200 Subject: [PATCH 19/74] move implementations to logging folder --- pom.xml | 4 ++-- ...da.powertools.logging.internal.LoggingManager | 1 - ...da.powertools.logging.internal.LoggingManager | 1 - .../powertools-logging-log4j}/pom.xml | 16 ++++++++++++---- .../logging/internal/Log4jLoggingManager.java | 0 .../logging/internal/PowertoolsResolver.java | 0 .../internal/PowertoolsResolverFactory.java | 0 .../src/main/resources/LambdaEcsLayout.json | 0 .../src/main/resources/LambdaJsonLayout.json | 0 .../internal/Log4jLoggingManagerTest.java | 0 .../src/test/resources/log4j2.xml | 0 .../powertools-logging-logback}/pom.xml | 16 +++++++++++++--- .../powertools/logging/LambdaEcsEncoder.java | 0 .../powertools/logging/LambdaJsonEncoder.java | 0 .../powertools/logging/internal/JsonUtils.java | 0 .../logging/internal/LambdaEcsSerializer.java | 0 .../logging/internal/LambdaJsonSerializer.java | 0 .../logging/internal/LogbackLoggingManager.java | 0 .../internal/LogbackLoggingManagerTest.java | 0 .../src/test/resources/logback-test.xml | 0 20 files changed, 27 insertions(+), 11 deletions(-) delete mode 100644 powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager delete mode 100644 powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/pom.xml (91%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/main/resources/LambdaEcsLayout.json (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/main/resources/LambdaJsonLayout.json (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java (100%) rename {powertools-logging-log4j => powertools-logging/powertools-logging-log4j}/src/test/resources/log4j2.xml (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/pom.xml (91%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java (100%) rename {powertools-logging-logback => powertools-logging/powertools-logging-logback}/src/test/resources/logback-test.xml (100%) diff --git a/pom.xml b/pom.xml index dca4d3765..01b0a6de7 100644 --- a/pom.xml +++ b/pom.xml @@ -44,8 +44,8 @@ powertools-common powertools-serialization powertools-logging - powertools-logging-log4j - powertools-logging-logback + powertools-logging/powertools-logging-log4j + powertools-logging/powertools-logging-logback powertools-tracing powertools-metrics powertools-parameters diff --git a/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager deleted file mode 100644 index ae2ec0cba..000000000 --- a/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager +++ /dev/null @@ -1 +0,0 @@ -software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager \ No newline at end of file diff --git a/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager deleted file mode 100644 index 6d432bd94..000000000 --- a/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager +++ /dev/null @@ -1 +0,0 @@ -software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager \ No newline at end of file diff --git a/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml similarity index 91% rename from powertools-logging-log4j/pom.xml rename to powertools-logging/powertools-logging-log4j/pom.xml index 2ee08fa72..20ec39280 100644 --- a/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -8,15 +8,14 @@ powertools-parent software.amazon.lambda 2.0.0-SNAPSHOT + ../../pom.xml powertools-logging-log4j jar - AWS Lambda Powertools for Java library Logging with Log4j2 - - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - + Set of utility for better logging with log4j + https://aws.amazon.com/lambda/ GitHub Issues @@ -115,4 +114,13 @@ + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + \ No newline at end of file diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java similarity index 100% rename from powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java rename to powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java similarity index 100% rename from powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java rename to powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java diff --git a/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java similarity index 100% rename from powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java rename to powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java diff --git a/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json similarity index 100% rename from powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json rename to powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json diff --git a/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json similarity index 100% rename from powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json rename to powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json diff --git a/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java similarity index 100% rename from powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java rename to powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java diff --git a/powertools-logging-log4j/src/test/resources/log4j2.xml b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml similarity index 100% rename from powertools-logging-log4j/src/test/resources/log4j2.xml rename to powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml diff --git a/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml similarity index 91% rename from powertools-logging-logback/pom.xml rename to powertools-logging/powertools-logging-logback/pom.xml index 877bc8fbf..f7643e6c3 100644 --- a/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -6,14 +6,14 @@ powertools-parent software.amazon.lambda 2.0.0-SNAPSHOT + ../../pom.xml 4.0.0 powertools-logging-logback AWS Lambda Powertools for Java library Logging with LogBack - - A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - + Set of utility for better logging with logback + https://aws.amazon.com/lambda/ GitHub Issues @@ -110,4 +110,14 @@ + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + + \ No newline at end of file diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java diff --git a/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java similarity index 100% rename from powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java diff --git a/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java similarity index 100% rename from powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java rename to powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java diff --git a/powertools-logging-logback/src/test/resources/logback-test.xml b/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml similarity index 100% rename from powertools-logging-logback/src/test/resources/logback-test.xml rename to powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml From eb88735510933d5101d153a4b0f8e292691563e0 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 16:19:00 +0200 Subject: [PATCH 20/74] proper project naming --- powertools-batch/pom.xml | 2 +- powertools-e2e-tests/pom.xml | 2 +- powertools-logging/powertools-logging-log4j/pom.xml | 2 +- powertools-logging/powertools-logging-logback/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/powertools-batch/pom.xml b/powertools-batch/pom.xml index d7246b816..309fac10c 100644 --- a/powertools-batch/pom.xml +++ b/powertools-batch/pom.xml @@ -10,7 +10,7 @@ A suite of utilities that makes batch message processing using AWS Lambda easier. - Powertools for AWS Lambda (Java) batch messages + Powertools for AWS Lambda (Java) library batch messages diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index c512beb3a..6cc47a64a 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -24,7 +24,7 @@ powertools-e2e-tests - Powertools for AWS Lambda (Java)library End-to-end tests + Powertools for AWS Lambda (Java) library End-to-end tests Powertools for AWS Lambda (Java)End-To-End Tests diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 20ec39280..0e86a3bb9 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -13,7 +13,7 @@ powertools-logging-log4j jar - AWS Lambda Powertools for Java library Logging with Log4j2 + Powertools for AWS Lambda (Java) library Logging with Log4j2 Set of utility for better logging with log4j https://aws.amazon.com/lambda/ diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index f7643e6c3..01f234fd1 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -11,7 +11,7 @@ 4.0.0 powertools-logging-logback - AWS Lambda Powertools for Java library Logging with LogBack + Powertools for AWS Lambda (Java) library Logging with LogBack Set of utility for better logging with logback https://aws.amazon.com/lambda/ From 7b04add65961c98813eddb78d6d21716d7154c9b Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 16:19:33 +0200 Subject: [PATCH 21/74] add slf4j-log4j --- .../sam/pom.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/powertools-examples-core-utilities/sam/pom.xml b/examples/powertools-examples-core-utilities/sam/pom.xml index 1992b3410..db4a6a7f8 100644 --- a/examples/powertools-examples-core-utilities/sam/pom.xml +++ b/examples/powertools-examples-core-utilities/sam/pom.xml @@ -26,6 +26,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + software.amazon.lambda powertools-metrics @@ -41,16 +51,6 @@ aws-lambda-java-events 3.11.3 - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - org.aspectj aspectjrt From 7e1f48e218f53d409e004eff88c14fbc634cdbe1 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 16:39:34 +0200 Subject: [PATCH 22/74] fix spotbug --- .../powertools-logging-log4j/pom.xml | 5 ++ .../powertools-logging-logback/pom.xml | 5 ++ powertools-logging/spotbugs-exclude.xml | 49 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 powertools-logging/spotbugs-exclude.xml diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 0e86a3bb9..8901c5096 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -45,6 +45,11 @@ powertools-logging ${version} + + org.aspectj + aspectjrt + provided + org.apache.logging.log4j log4j-slf4j2-impl diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 01f234fd1..7eea5f1d9 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -44,6 +44,11 @@ powertools-logging ${version} + + org.aspectj + aspectjrt + provided + ch.qos.logback logback-classic diff --git a/powertools-logging/spotbugs-exclude.xml b/powertools-logging/spotbugs-exclude.xml new file mode 100644 index 000000000..339d30a91 --- /dev/null +++ b/powertools-logging/spotbugs-exclude.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 4990e186d0161c7e53d8580960b9bf46eddc93c2 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 17:41:00 +0200 Subject: [PATCH 23/74] use JsonConfig from serialization module --- powertools-logging/pom.xml | 4 ++ .../powertools/logging/LoggingUtils.java | 18 ------ .../logging/internal/LambdaLoggingAspect.java | 59 ++++++++----------- powertools-serialization/pom.xml | 4 +- 4 files changed, 32 insertions(+), 53 deletions(-) diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 730afda36..c042a8240 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -56,6 +56,10 @@ software.amazon.lambda powertools-common + + software.amazon.lambda + powertools-serialization + com.amazonaws aws-lambda-java-core diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index fe3619fdc..ed338b0f9 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -79,22 +79,4 @@ public static void removeKeys(String... keys) { public static void setCorrelationId(String value) { MDC.put("correlation_id", value); } - - /** - * Sets the instance of ObjectMapper object which is used for serialising event when - * {@code @Logging(logEvent = true)}. - * - * @param objectMapper Custom implementation of object mapper to be used for logging serialised event - */ - public static void defaultObjectMapper(ObjectMapper objectMapper) { - LoggingUtils.objectMapper = objectMapper; - } - - public static ObjectMapper objectMapper() { - if (null == objectMapper) { - objectMapper = new ObjectMapper(); - } - - return objectMapper; - } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 2881d8f1a..517a80232 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -27,7 +27,6 @@ import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys; -import static software.amazon.lambda.powertools.logging.LoggingUtils.objectMapper; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; @@ -59,6 +58,7 @@ import org.slf4j.event.Level; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; +import software.amazon.lambda.powertools.utilities.JsonConfig; @Aspect @@ -192,16 +192,12 @@ private double samplingRate(final Logging logging) { private Object[] logEvent(final ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - Logger log = logger(pjp); - asJson(pjp, pjp.getArgs()[0]) - .ifPresent(log::info); - } - - if (placedOnStreamHandler(pjp)) { - args = logFromInputStream(pjp); - } + if (placedOnRequestHandler(pjp)) { + Logger log = logger(pjp); + asJson(pjp, pjp.getArgs()[0]) + .ifPresent(log::info); + } else if (placedOnStreamHandler(pjp)) { + args = logFromInputStream(pjp); } return args; @@ -210,29 +206,26 @@ private Object[] logEvent(final ProceedingJoinPoint pjp) { private Object[] captureCorrelationId(final String correlationIdPath, final ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); - if (isHandlerMethod(pjp)) { - if (placedOnRequestHandler(pjp)) { - Object arg = pjp.getArgs()[0]; - JsonNode jsonNode = objectMapper().valueToTree(arg); - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); + if (placedOnRequestHandler(pjp)) { + Object arg = pjp.getArgs()[0]; + JsonNode jsonNode = JsonConfig.get().getObjectMapper().valueToTree(arg); - return args; - } + setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); - if (placedOnStreamHandler(pjp)) { - try { - byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); - JsonNode jsonNode = objectMapper().readTree(bytes); - args[0] = new ByteArrayInputStream(bytes); + return args; + } else if (placedOnStreamHandler(pjp)) { + try { + byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); + JsonNode jsonNode = JsonConfig.get().getObjectMapper().readTree(bytes); + args[0] = new ByteArrayInputStream(bytes); - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); + setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); - return args; - } catch (IOException e) { - Logger log = logger(pjp); - log.warn("Failed to capture correlation id on event from supplied input stream.", e); - } + return args; + } catch (IOException e) { + Logger log = logger(pjp); + log.warn("Failed to capture correlation id on event from supplied input stream.", e); } } @@ -246,7 +239,7 @@ private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPo if (null != asText && !asText.isEmpty()) { LoggingUtils.setCorrelationId(asText); } else { - logger(pjp).debug("Unable to extract any correlation id. Is your function expecting supported event type?"); + logger(pjp).warn("Unable to extract any correlation id. Is your function expecting supported event type?"); } } @@ -258,12 +251,12 @@ private Object[] logFromInputStream(final ProceedingJoinPoint pjp) { args[0] = new ByteArrayInputStream(bytes); Logger log = logger(pjp); - asJson(pjp, objectMapper().readValue(bytes, Map.class)) + asJson(pjp, JsonConfig.get().getObjectMapper().readValue(bytes, Map.class)) .ifPresent(log::info); } catch (IOException e) { Logger log = logger(pjp); - log.debug("Failed to log event from supplied input stream.", e); + log.warn("Failed to log event from supplied input stream.", e); } return args; @@ -286,7 +279,7 @@ private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws private Optional asJson(final ProceedingJoinPoint pjp, final Object target) { try { - return ofNullable(objectMapper().writeValueAsString(target)); + return ofNullable(JsonConfig.get().getObjectMapper().writeValueAsString(target)); } catch (JsonProcessingException e) { logger(pjp).error("Failed logging event of type {}", target.getClass(), e); return empty(); diff --git a/powertools-serialization/pom.xml b/powertools-serialization/pom.xml index d92d68fb0..911663507 100644 --- a/powertools-serialization/pom.xml +++ b/powertools-serialization/pom.xml @@ -64,8 +64,8 @@ aws-lambda-java-events - org.apache.logging.log4j - log4j-slf4j2-impl + org.slf4j + slf4j-api com.fasterxml.jackson.core From 198b5dcecd775ae74be9b09644bee60654946e85 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 17:41:10 +0200 Subject: [PATCH 24/74] complete tests --- .../powertools/logging/LoggingUtilsTest.java | 17 +++++++++++++---- .../internal/LambdaLoggingAspectTest.java | 9 +++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java index 2ad8d10a6..c06b69767 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java @@ -31,7 +31,7 @@ void setUp() { } @Test - void shouldSetCustomKeyOnThreadContext() { + void shouldSetCustomKeyInLoggingContext() { LoggingUtils.appendKey("org/slf4j/test", "value"); assertThat(MDC.getCopyOfContextMap()) @@ -40,7 +40,7 @@ void shouldSetCustomKeyOnThreadContext() { } @Test - void shouldSetCustomKeyAsMapOnThreadContext() { + void shouldSetCustomKeyAsMapInLoggingContext() { Map customKeys = new HashMap<>(); customKeys.put("org/slf4j/test", "value"); customKeys.put("test1", "value1"); @@ -54,7 +54,7 @@ void shouldSetCustomKeyAsMapOnThreadContext() { } @Test - void shouldRemoveCustomKeyOnThreadContext() { + void shouldRemoveCustomKeyInLoggingContext() { LoggingUtils.appendKey("org/slf4j/test", "value"); assertThat(MDC.getCopyOfContextMap()) @@ -68,7 +68,7 @@ void shouldRemoveCustomKeyOnThreadContext() { } @Test - void shouldRemoveCustomKeysOnThreadContext() { + void shouldRemoveCustomKeysInLoggingContext() { Map customKeys = new HashMap<>(); customKeys.put("org/slf4j/test", "value"); customKeys.put("test1", "value1"); @@ -85,4 +85,13 @@ void shouldRemoveCustomKeysOnThreadContext() { assertThat(MDC.getCopyOfContextMap()) .isEmpty(); } + + @Test + void shouldAddCorrelationIdToLoggingContext() { + LoggingUtils.setCorrelationId("correlationID_12345"); + + assertThat(MDC.getCopyOfContextMap()) + .hasSize(1) + .containsEntry("correlation_id", "correlationID_12345"); + } } \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 502dc6b5d..0a6b86002 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -192,10 +192,19 @@ void shouldLogDebugWhenSamplingEnvVarEqualsOne() { @Test @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "42") + void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() { + requestHandler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "NotANumber") void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { requestHandler.handleRequest(new Object(), context); File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + assertThat(contentOf(logFile)).contains("Skipping sampling rate on environment variable configuration because of invalid value"); } @Test From 923ed7663a16b33d0ed6cebf179c8b15949d213d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Tue, 19 Sep 2023 17:49:31 +0200 Subject: [PATCH 25/74] add aspectj for compilation in the IDE (scope provided) --- powertools-large-messages/pom.xml | 5 +++++ powertools-metrics/pom.xml | 5 +++++ powertools-parameters/pom.xml | 5 +++++ powertools-tracing/pom.xml | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/powertools-large-messages/pom.xml b/powertools-large-messages/pom.xml index a56623518..72e6e6f1a 100644 --- a/powertools-large-messages/pom.xml +++ b/powertools-large-messages/pom.xml @@ -54,6 +54,11 @@ + + org.aspectj + aspectjrt + provided + software.amazon.lambda powertools-common diff --git a/powertools-metrics/pom.xml b/powertools-metrics/pom.xml index 8c9ce79ad..142852299 100644 --- a/powertools-metrics/pom.xml +++ b/powertools-metrics/pom.xml @@ -56,6 +56,11 @@ + + org.aspectj + aspectjrt + provided + software.amazon.lambda powertools-common diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index eb73962e1..3895ef475 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -55,6 +55,11 @@ + + org.aspectj + aspectjrt + provided + software.amazon.lambda powertools-common diff --git a/powertools-tracing/pom.xml b/powertools-tracing/pom.xml index 6498d3772..273b807c8 100644 --- a/powertools-tracing/pom.xml +++ b/powertools-tracing/pom.xml @@ -56,6 +56,11 @@ + + org.aspectj + aspectjrt + provided + software.amazon.lambda powertools-common From 36cc83d082d490c60e67354569aa4eae480adae5 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 20 Sep 2023 17:53:35 +0200 Subject: [PATCH 26/74] complete tests for log4j implementation --- .../powertools-logging-log4j/pom.xml | 35 ++++++ .../json/resolver}/PowertoolsResolver.java | 48 +++++--- .../resolver}/PowertoolsResolverFactory.java | 10 +- .../src/main/resources/LambdaEcsLayout.json | 4 - .../PowerToolsResolverFactoryTest.java | 94 ++++++++++++++++ .../json/resolver/PowertoolsResolverTest.java | 103 ++++++++++++++++++ .../internal/Log4jLoggingManagerTest.java | 27 +++-- .../handler/PowertoolsLogEnabled.java | 35 ++++++ .../src/test/resources/log4j2.xml | 16 ++- .../powertools/logging/LoggingUtils.java | 3 - 10 files changed, 332 insertions(+), 43 deletions(-) rename powertools-logging/powertools-logging-log4j/src/main/java/{software/amazon/lambda/powertools/logging/internal => org/apache/logging/log4j/layout/template/json/resolver}/PowertoolsResolver.java (84%) rename powertools-logging/powertools-logging-log4j/src/main/java/{software/amazon/lambda/powertools/logging/internal => org/apache/logging/log4j/layout/template/json/resolver}/PowertoolsResolverFactory.java (73%) create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 8901c5096..dab90deaf 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -117,10 +117,45 @@ jsonassert test + + org.junit-pioneer + junit-pioneer + test + + + dev.aspectj + aspectj-maven-plugin + 1.13.1 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-logging + + + + + + + compile + + + + + + org.aspectj + aspectjtools + ${aspectj.version} + + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java similarity index 84% rename from powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java rename to powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java index 20ef5f7e3..a02173c6b 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolver.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java @@ -12,14 +12,19 @@ * */ -package software.amazon.lambda.powertools.logging.internal; +package org.apache.logging.log4j.layout.template.json.resolver; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolver; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; import org.apache.logging.log4j.util.ReadOnlyStringMap; +import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; +/** + * Custom {@link org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver} + * used by {@link org.apache.logging.log4j.layout.template.json.JsonTemplateLayout} + * to be able to recognize powertools fields in the LambdaJsonLayout.json file. + */ final class PowertoolsResolver implements EventResolver { private static final EventResolver COLD_START_RESOLVER = new EventResolver() { @@ -87,7 +92,11 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { public boolean isResolvable(LogEvent logEvent) { final String samplingRate = logEvent.getContextData().getValue(PowertoolsLoggedFields.SAMPLING_RATE.getName()); - return null != samplingRate; + try { + return (null != samplingRate && Float.parseFloat(samplingRate) > 0.f); + } catch (NumberFormatException nfe) { + return false; + } } @Override @@ -98,12 +107,21 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { } }; - private static final EventResolver XRAY_TRACE_RESOLVER = - (final LogEvent logEvent, final JsonWriter jsonWriter) -> { - final String traceId = - logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); - jsonWriter.writeString(traceId); - }; + private static final EventResolver XRAY_TRACE_RESOLVER = new EventResolver() { + @Override + public boolean isResolvable(LogEvent logEvent) { + final String traceId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + return null != traceId; + } + + @Override + public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { + final String traceId = + logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_TRACE_ID.getName()); + jsonWriter.writeString(traceId); + } + }; private static final EventResolver SERVICE_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { @@ -113,7 +131,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver REGION_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> - jsonWriter.writeString(System.getenv("AWS_REGION")); + jsonWriter.writeString(System.getenv(LambdaConstants.AWS_REGION_ENV)); private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() { @Override @@ -132,7 +150,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER = (LogEvent logEvent, JsonWriter jsonWriter) -> { StringBuilder stringBuilder = jsonWriter.getStringBuilder(); - // remove dummy field to kick inn powertools resolver + // remove dummy field to kick in powertools resolver stringBuilder.setLength(stringBuilder.length() - 4); // Inject all the context information. @@ -195,10 +213,6 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { } } - static String getName() { - return "powertools"; - } - @Override public void resolve(LogEvent value, JsonWriter jsonWriter) { internalResolver.resolve(value, jsonWriter); @@ -207,6 +221,6 @@ public void resolve(LogEvent value, JsonWriter jsonWriter) { @Override public boolean isResolvable(LogEvent value) { ReadOnlyStringMap contextData = value.getContextData(); - return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable(); + return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable(value); } } diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java similarity index 73% rename from powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java rename to powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java index 297315120..14d4f67aa 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsResolverFactory.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java @@ -12,21 +12,17 @@ * */ -package software.amazon.lambda.powertools.logging.internal; +package org.apache.logging.log4j.layout.template.json.resolver; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginFactory; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverContext; -import org.apache.logging.log4j.layout.template.json.resolver.EventResolverFactory; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolver; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverConfig; -import org.apache.logging.log4j.layout.template.json.resolver.TemplateResolverFactory; @Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) public final class PowertoolsResolverFactory implements EventResolverFactory { private static final PowertoolsResolverFactory INSTANCE = new PowertoolsResolverFactory(); + private static final String RESOLVER_NAME = "powertools"; private PowertoolsResolverFactory() { } @@ -38,7 +34,7 @@ public static PowertoolsResolverFactory getInstance() { @Override public String getName() { - return PowertoolsResolver.getName(); + return RESOLVER_NAME; } @Override diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json index 102ba5ec8..d3d81a383 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json @@ -23,10 +23,6 @@ "$resolver": "powertools", "field": "service_version" }, - "event.dataset": { - "$resolver": "powertools", - "field": "service_name" - }, "process.thread.name": { "$resolver": "thread", "field": "name" diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java new file mode 100644 index 000000000..df3c90e62 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 org.apache.logging.log4j.layout.template.json.resolver; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import com.amazonaws.services.lambda.runtime.Context; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +class PowerToolsResolverFactoryTest { + + @Mock + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + openMocks(this); + MDC.clear(); + writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); + setupContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4j") + @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001") + @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") + void shouldLogInJsonFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).startsWith( + "{\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"level\":\"INFO\",\"message\":\"Test event\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") + .endsWith("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } + + @Test + @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1") + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4jEcs") + @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") + void shouldLogInEcsFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).endsWith( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"INFO\",\"message\":\"Test event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"process.thread.name\":\"main\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.coldstart\":true,\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.memory\":1024,\"faas.name\":\"testFunction\",\"faas.execution\":\"RequestId\",\"faas.version\":\"1\",\"myKey\":\"myValue\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\"}\n") + .startsWith("{\"@timestamp\":\""); + } + + private void setupContext() { + when(context.getFunctionName()).thenReturn("testFunction"); + when(context.getInvokedFunctionArn()).thenReturn( + "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); + when(context.getFunctionVersion()).thenReturn("1"); + when(context.getMemoryLimitInMB()).thenReturn(1024); + when(context.getAwsRequestId()).thenReturn("RequestId"); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java new file mode 100644 index 000000000..89380d703 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 org.apache.logging.log4j.layout.template.json.resolver; + +import static org.assertj.core.api.Assertions.assertThat; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE; + +import java.util.HashMap; +import java.util.Map; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.layout.template.json.util.JsonWriter; +import org.apache.logging.log4j.util.SortedArrayStringMap; +import org.apache.logging.log4j.util.StringMap; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; + +class PowertoolsResolverTest { + + @ParameterizedTest + @EnumSource(value = PowertoolsLoggedFields.class, + mode = EnumSource.Mode.EXCLUDE, + names = {"FUNCTION_MEMORY_SIZE", "SAMPLING_RATE", "FUNCTION_COLD_START"}) + void shouldResolveFunctionStringInfo(PowertoolsLoggedFields field) { + String result = resolveField(field.getName(), "value"); + assertThat(result).isEqualTo("\"value\""); + } + + @Test + void shouldResolveMemorySize() { + String result = resolveField(FUNCTION_MEMORY_SIZE.getName(), "42"); + assertThat(result).isEqualTo("42"); + } + + @Test + void shouldResolveSamplingRate() { + String result = resolveField(SAMPLING_RATE.getName(), "0.4"); + assertThat(result).isEqualTo("0.4"); + } + + @Test + void shouldResolveColdStart() { + String result = resolveField(FUNCTION_COLD_START.getName(), "true"); + assertThat(result).isEqualTo("true"); + } + + @Test + void shouldResolveAccountId() { + String result = resolveField(FUNCTION_ARN.getName(), "account_id", "arn:aws:lambda:us-east-2:123456789012:function:my-function"); + assertThat(result).isEqualTo("\"123456789012\""); + } + + @Test + @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-2") + void shouldResolveRegion() { + String result = resolveField("region", "dummy, will use the env var"); + assertThat(result).isEqualTo("\"eu-central-2\""); + } + + private static String resolveField(String field, String value) { + return resolveField(field, field, value); + } + + private static String resolveField(String data, String field, String value) { + Map configMap = new HashMap<>(); + configMap.put("field", field); + + TemplateResolverConfig config = new TemplateResolverConfig(configMap); + PowertoolsResolver resolver = new PowertoolsResolver(config); + JsonWriter writer = JsonWriter + .newBuilder() + .setMaxStringLength(1000) + .setTruncatedStringSuffix("") + .build(); + + StringMap contextMap = new SortedArrayStringMap(); + contextMap.putValue(data, value); + + Log4jLogEvent logEvent = Log4jLogEvent.newBuilder().setContextData(contextMap).build(); + if (resolver.isResolvable(logEvent)) { + resolver.resolve(logEvent, writer); + } + + return writer.getStringBuilder().toString(); + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java index b1371c963..7bd0d2414 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -11,29 +11,40 @@ import org.slf4j.LoggerFactory; import org.slf4j.event.Level; -public class Log4jLoggingManagerTest { +class Log4jLoggingManagerTest { - private static Logger LOG = LoggerFactory.getLogger(Log4jLoggingManagerTest.class); - private static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + private final static Logger LOG = LoggerFactory.getLogger(Log4jLoggingManagerTest.class); + private final static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); @Test @Order(1) - public void getLogLevel_shouldReturnConfiguredLogLevel() { + void getLogLevel_shouldReturnConfiguredLogLevel() { + // Given log4j2.xml in resources + + // When Log4jLoggingManager manager = new Log4jLoggingManager(); Level logLevel = manager.getLogLevel(LOG); - assertThat(logLevel).isEqualTo(INFO); + Level rootLevel = manager.getLogLevel(ROOT); - logLevel = manager.getLogLevel(ROOT); - assertThat(logLevel).isEqualTo(WARN); + // Then + assertThat(logLevel).isEqualTo(INFO); + assertThat(rootLevel).isEqualTo(WARN); } @Test @Order(2) - public void resetLogLevel() { + void resetLogLevel() { + // Given log4j2.xml in resources + + // When Log4jLoggingManager manager = new Log4jLoggingManager(); manager.resetLogLevel(ERROR); + Level rootLevel = manager.getLogLevel(ROOT); Level logLevel = manager.getLogLevel(LOG); + + // Then + assertThat(rootLevel).isEqualTo(ERROR); assertThat(logLevel).isEqualTo(ERROR); } } diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java new file mode 100644 index 000000000..be0535b24 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; + +public class PowertoolsLogEnabled implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + + @Override + @Logging + public Object handleRequest(Object input, Context context) { + LoggingUtils.appendKey("myKey", "myValue"); + LOG.info("Test event"); + LOG.debug("Test debug event"); + return null; + } +} diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml index 489b70809..3156c77e6 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml @@ -1,16 +1,24 @@ - + + + + + + + - + q - + + - + + \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index ed338b0f9..78f6b3828 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -14,7 +14,6 @@ package software.amazon.lambda.powertools.logging; -import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; import java.util.Map; import org.slf4j.MDC; @@ -25,8 +24,6 @@ * {@see Logging} */ public final class LoggingUtils { - private static ObjectMapper objectMapper; - private LoggingUtils() { } From 4749c5ca58f7ed4b818b6c6d0e38000d36a37cfd Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 21 Sep 2023 15:38:31 +0200 Subject: [PATCH 27/74] add services files --- ...ware.amazon.lambda.powertools.logging.internal.LoggingManager | 1 + ...ware.amazon.lambda.powertools.logging.internal.LoggingManager | 1 + 2 files changed, 2 insertions(+) create mode 100644 powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager create mode 100644 powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..ae2ec0cba --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager new file mode 100644 index 000000000..6d432bd94 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -0,0 +1 @@ +software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager \ No newline at end of file From ab9ce9c643d96ef2124ca3744aa92d79f1e21f1a Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 21 Sep 2023 15:38:52 +0200 Subject: [PATCH 28/74] complete tests for log4j --- .../powertools-logging-log4j/pom.xml | 8 ++ .../logging/internal/Log4jLoggingManager.java | 2 - .../src/main/resources/LambdaEcsLayout.json | 58 ++++++------- .../src/main/resources/LambdaJsonLayout.json | 22 ++--- .../PowerToolsResolverFactoryTest.java | 22 +++-- .../internal/LambdaLoggingAspectTest.java | 82 +++++++++++++++++++ .../internal/Log4jLoggingManagerTest.java | 4 +- .../handler/PowertoolsLogEnabled.java | 3 +- .../test/resources/junit-platform.properties | 17 ++++ .../src/test/resources/log4j2.xml | 2 +- .../logging/internal/LambdaLoggingAspect.java | 63 ++++++++++++-- .../internal/LambdaLoggingAspectTest.java | 7 +- 12 files changed, 226 insertions(+), 64 deletions(-) create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java create mode 100644 powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index dab90deaf..3a9157271 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -156,6 +156,14 @@ + + maven-surefire-plugin + + + org.apache.logging.slf4j.SLF4JServiceProvider + + + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java index 87c9f4183..1b49a1fb3 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -35,6 +35,4 @@ public org.slf4j.event.Level getLogLevel(Logger logger) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); return org.slf4j.event.Level.valueOf(ctx.getLogger(logger.getName()).getLevel().toString()); } - - } diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json index d3d81a383..c92098a4a 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json @@ -15,22 +15,6 @@ "$resolver": "message", "stringified": true }, - "service.name": { - "$resolver": "powertools", - "field": "service" - }, - "service.version": { - "$resolver": "powertools", - "field": "service_version" - }, - "process.thread.name": { - "$resolver": "thread", - "field": "name" - }, - "log.logger": { - "$resolver": "logger", - "field": "name" - }, "error.type": { "$resolver": "exception", "field": "className" @@ -46,6 +30,22 @@ "stringified": true } }, + "service.name": { + "$resolver": "powertools", + "field": "service" + }, + "service.version": { + "$resolver": "powertools", + "field": "service_version" + }, + "log.logger": { + "$resolver": "logger", + "field": "name" + }, + "process.thread.name": { + "$resolver": "thread", + "field": "name" + }, "cloud.provider" : "aws", "cloud.service.name" : "lambda", "cloud.region" : { @@ -56,35 +56,35 @@ "$resolver": "powertools", "field": "account_id" }, - "faas.coldstart": { - "$resolver": "powertools", - "field": "cold_start" - }, "faas.id": { "$resolver": "powertools", "field": "function_arn" }, - "faas.memory": { - "$resolver": "powertools", - "field": "function_memory_size" - }, "faas.name": { "$resolver": "powertools", "field": "function_name" }, + "faas.version": { + "$resolver": "powertools", + "field": "function_version" + }, + "faas.memory": { + "$resolver": "powertools", + "field": "function_memory_size" + }, "faas.execution": { "$resolver": "powertools", "field": "function_request_id" }, - "faas.version": { + "faas.coldstart": { "$resolver": "powertools", - "field": "function_version" - }, - "": { - "$resolver": "powertools" + "field": "cold_start" }, "trace.id": { "$resolver": "powertools", "field": "xray_trace_id" + }, + "": { + "$resolver": "powertools" } } \ No newline at end of file diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json index f5caf3924..fef17c68b 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json @@ -1,7 +1,11 @@ { - "cold_start": { - "$resolver": "powertools", - "field": "cold_start" + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "message", + "stringified": true }, "error": { "message": { @@ -20,6 +24,10 @@ } } }, + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, "function_arn": { "$resolver": "powertools", "field": "function_arn" @@ -40,14 +48,6 @@ "$resolver": "powertools", "field": "function_version" }, - "level": { - "$resolver": "level", - "field": "name" - }, - "message": { - "$resolver": "message", - "stringified": true - }, "sampling_rate": { "$resolver": "powertools", "field": "sampling_rate" diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java index df3c90e62..29e063382 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -27,7 +27,9 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; @@ -35,6 +37,7 @@ import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; +@Order(2) class PowerToolsResolverFactoryTest { @Mock @@ -55,8 +58,13 @@ void setUp() throws IllegalAccessException, IOException { } } + @AfterEach + void cleanUp() throws IOException{ + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } + @Test - @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4j") @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001") @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") void shouldLogInJsonFormat() { @@ -64,23 +72,21 @@ void shouldLogInJsonFormat() { handler.handleRequest("Input", context); File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).startsWith( - "{\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"level\":\"INFO\",\"message\":\"Test event\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") - .endsWith("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") + .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } @Test @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1") - @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4jEcs") @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") void shouldLogInEcsFormat() { PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); handler.handleRequest("Input", context); File logFile = new File("target/ecslogfile.json"); - assertThat(contentOf(logFile)).endsWith( - "\"ecs.version\":\"1.2.0\",\"log.level\":\"INFO\",\"message\":\"Test event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"process.thread.name\":\"main\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.coldstart\":true,\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.memory\":1024,\"faas.name\":\"testFunction\",\"faas.execution\":\"RequestId\",\"faas.version\":\"1\",\"myKey\":\"myValue\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\"}\n") - .startsWith("{\"@timestamp\":\""); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":1024,\"faas.execution\":\"RequestId\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } private void setupContext() { diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java new file mode 100644 index 000000000..f7563e2de --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import com.amazonaws.services.lambda.runtime.Context; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +@Order(1) +class LambdaLoggingAspectTest { + + @Mock + private Context context; + + @BeforeEach + void setUp() throws IOException { + openMocks(this); + MDC.clear(); + setupContext(); + //Make sure file is cleaned up before running full stack logging regression + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file might not be there for the first run + } + } + + @AfterEach + void cleanUp() throws IOException { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4j") + void testSlf4jBinding() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("slf4j.binding is set to org.apache.logging.slf4j.SLF4JServiceProvider") + .contains("Loading software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"); + } + + private void setupContext() { + when(context.getFunctionName()).thenReturn("testFunction"); + when(context.getInvokedFunctionArn()).thenReturn( + "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); + when(context.getFunctionVersion()).thenReturn("1"); + when(context.getMemoryLimitInMB()).thenReturn(1024); + when(context.getAwsRequestId()).thenReturn("RequestId"); + } +} \ No newline at end of file diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java index 7bd0d2414..0debf88e5 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -1,8 +1,8 @@ package software.amazon.lambda.powertools.logging.internal; import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.DEBUG; import static org.slf4j.event.Level.ERROR; -import static org.slf4j.event.Level.INFO; import static org.slf4j.event.Level.WARN; import org.junit.jupiter.api.Order; @@ -27,7 +27,7 @@ void getLogLevel_shouldReturnConfiguredLogLevel() { Level rootLevel = manager.getLogLevel(ROOT); // Then - assertThat(logLevel).isEqualTo(INFO); + assertThat(logLevel).isEqualTo(DEBUG); assertThat(rootLevel).isEqualTo(WARN); } diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java index be0535b24..faa722756 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java @@ -25,10 +25,9 @@ public class PowertoolsLogEnabled implements RequestHandler { private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); @Override - @Logging + @Logging(clearState = true) public Object handleRequest(Object input, Context context) { LoggingUtils.appendKey("myKey", "myValue"); - LOG.info("Test event"); LOG.debug("Test debug event"); return null; } diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties b/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..80a2481d7 --- /dev/null +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/junit-platform.properties @@ -0,0 +1,17 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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. +# +# + +# because of LambdaLoggingAspect static initialization of the LoggingManager, we need to +# set an order in the unit tests, especially LambdaLoggingAspectTest needs to be first +junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation \ No newline at end of file diff --git a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml index 3156c77e6..778077bc5 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml +++ b/powertools-logging/powertools-logging-log4j/src/test/resources/log4j2.xml @@ -12,7 +12,7 @@ q - + diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 517a80232..c5fec317d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -81,24 +81,71 @@ public final class LambdaLoggingAspect { } private static LoggingManager loadLoggingManager() { - // JAVA_TOOL_OPTIONS="-Dslf4j.binding=ch.qos.logback.classic.spi.LogbackServiceProvider" - // JAVA_TOOL_OPTIONS="-Dslf4j.binding=org.apache.logging.slf4j.SLF4JServiceProvider" + return getLoggingManagerFromSlf4jBinding().orElse(getLoggingManagerFromServiceLoader()); + } + + /** + * To avoid Service Loader loading of the log provider, SLF4j provides a system property 'slf4j.binding' + * that users can set. If this is set, we can also leverage it and avoid Service Loader for the {@link LoggingManager}. + *

    In Lambda, system properties can be set with JAVA_TOOL_OPTIONS: + *

      + *
    • JAVA_TOOL_OPTIONS="-Dslf4j.binding=ch.qos.logback.classic.spi.LogbackServiceProvider"
    • + *
    • JAVA_TOOL_OPTIONS="-Dslf4j.binding=org.apache.logging.slf4j.SLF4JServiceProvider"
    • + *
    + *

    + * @return an instance of {@link LoggingManager} or null + */ + private static Optional getLoggingManagerFromSlf4jBinding() { + LoggingManager loggingManager = null; + String slf4jBinding = System.getProperty("slf4j.binding"); - System.out.println(slf4jBinding); // TODO + if (slf4jBinding != null) { + LOG.debug("slf4j.binding is set to {}", slf4jBinding); + try { + if ("org.apache.logging.slf4j.SLF4JServiceProvider".equals(slf4jBinding)) { + String managerClass = "software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"; + LOG.debug("Loading {}", managerClass); + Class log4jManagerClass = LambdaLoggingAspect.class.getClassLoader() + .loadClass(managerClass); + loggingManager = (LoggingManager) log4jManagerClass.newInstance(); + } else if ("ch.qos.logback.classic.spi.LogbackServiceProvider".equals(slf4jBinding)) { + String managerClass = "software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"; + LOG.debug("Loading {}", managerClass); + Class log4backManagerClass = LambdaLoggingAspect.class.getClassLoader() + .loadClass(managerClass); + loggingManager = (LoggingManager) log4backManagerClass.newInstance(); + } else { + LOG.warn("slf4j.binding {} not supported, fallback to Service Loader. " + + "Only log4j and logback are supported.", slf4jBinding); + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + LOG.warn("Could not instantiate LoggingManager based on slf4j.binding {}, " + + "make sure to add either powertools-logging-log4j or powertools-logging-logback module", + slf4jBinding); + } + } + return Optional.ofNullable(loggingManager); + } + + private static LoggingManager getLoggingManagerFromServiceLoader() { + LoggingManager loggingManager; ServiceLoader loggingManagers = ServiceLoader.load(LoggingManager.class); List loggingManagerList = new ArrayList<>(); - for (LoggingManager loggingManager : loggingManagers) { - loggingManagerList.add(loggingManager); + for (LoggingManager lm : loggingManagers) { + loggingManagerList.add(lm); } if (loggingManagerList.isEmpty()) { - throw new IllegalStateException("No LoggingManager was found on the classpath"); + throw new IllegalStateException("No LoggingManager was found on the classpath, " + + "make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); } else if (loggingManagerList.size() > 1) { throw new IllegalStateException( - "Multiple LoggingManagers were found on the classpath: " + loggingManagerList); + "Multiple LoggingManagers were found on the classpath: " + loggingManagerList + + ", make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies"); } else { - return loggingManagerList.get(0); + loggingManager = loggingManagerList.get(0); } + return loggingManager; } private static void resetLogLevels(Level logLevel) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 0a6b86002..6d568f544 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -49,6 +49,7 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Collections; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -92,9 +93,13 @@ void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, setupContext(); requestHandler = new PowertoolsLogEnabled(); requestStreamHandler = new PowertoolsLogEnabledForStream(); + resetLogLevel(Level.INFO); + } + + @AfterEach + void cleanUp() throws IOException { //Make sure file is cleaned up before running full stack logging regression FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - resetLogLevel(Level.INFO); } @Test From bdee1f32d2ad795b0788b7e55e9c4bc9b078bec5 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 21 Sep 2023 23:16:43 +0200 Subject: [PATCH 29/74] complete tests for logback --- .../powertools-logging-logback/pom.xml | 43 ++++ .../powertools/logging/LambdaEcsEncoder.java | 111 ++++++++-- .../powertools/logging/LambdaJsonEncoder.java | 79 +++++++- .../logging/internal/JsonUtils.java | 10 +- .../logging/internal/LambdaEcsSerializer.java | 4 - .../internal/LambdaJsonSerializer.java | 13 +- .../logging/LogbackLoggingManagerTest.java | 54 +++++ .../internal/LambdaEcsEncoderTest.java | 179 +++++++++++++++++ .../internal/LambdaJsonEncoderTest.java | 190 ++++++++++++++++++ .../internal/LambdaLoggingAspectTest.java | 82 ++++++++ .../internal/LogbackLoggingManagerTest.java | 39 ---- .../handler/PowertoolsLogEnabled.java | 34 ++++ .../test/resources/junit-platform.properties | 17 ++ .../src/test/resources/logback-test.xml | 22 +- 14 files changed, 800 insertions(+), 77 deletions(-) create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java delete mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java create mode 100644 powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 7eea5f1d9..1fb473962 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -113,14 +113,57 @@ jsonassert test
    + + org.junit-pioneer + junit-pioneer + test +
    + + dev.aspectj + aspectj-maven-plugin + 1.13.1 + + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.target} + + + software.amazon.lambda + powertools-logging + + + + + + + compile + + + + + + org.aspectj + aspectjtools + ${aspectj.version} + + + org.apache.maven.plugins maven-checkstyle-plugin + + maven-surefire-plugin + + + ch.qos.logback.classic.spi.LogbackServiceProvider + + + diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java index 7df62a91e..f83a43ef4 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -51,6 +51,8 @@ public class LambdaEcsEncoder extends EncoderBase { private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); protected ThrowableHandlingConverter throwableConverter = null; + private boolean includeCloudInfo = true; + private boolean includeFaasInfo = true; @Override public byte[] headerBytes() { @@ -67,12 +69,6 @@ public byte[] encode(ILoggingEvent event) { LambdaEcsSerializer.serializeEcsVersion(builder, ECS_VERSION); LambdaEcsSerializer.serializeLogLevel(builder, event.getLevel()); LambdaEcsSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); - LambdaEcsSerializer.serializeServiceName(builder, LambdaHandlerProcessor.serviceName()); - LambdaEcsSerializer.serializeServiceVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); - // TODO : Environment ? - LambdaEcsSerializer.serializeEventDataset(builder, LambdaHandlerProcessor.serviceName()); - LambdaEcsSerializer.serializeThreadName(builder, event.getThreadName()); - LambdaEcsSerializer.serializeLoggerName(builder, event.getLoggerName()); IThrowableProxy throwableProxy = event.getThrowableProxy(); if (throwableProxy != null) { if (throwableConverter != null) { @@ -85,22 +81,33 @@ public byte[] encode(ILoggingEvent event) { throwableProxy.getMessage(), throwableProxyConverter.convert(event)); } } - LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER); - LambdaEcsSerializer.serializeCloudService(builder, CLOUD_SERVICE); + LambdaEcsSerializer.serializeServiceName(builder, LambdaHandlerProcessor.serviceName()); + LambdaEcsSerializer.serializeServiceVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + LambdaEcsSerializer.serializeLoggerName(builder, event.getLoggerName()); + LambdaEcsSerializer.serializeThreadName(builder, event.getThreadName()); String arn = mdcPropertyMap.get(FUNCTION_ARN.getName()); - if (arn != null) { - String[] arnParts = arn.split(":"); - LambdaEcsSerializer.serializeCloudRegion(builder, arnParts[3]); - LambdaEcsSerializer.serializeCloudAccountId(builder, arnParts[4]); + + if (includeCloudInfo) { + LambdaEcsSerializer.serializeCloudProvider(builder, CLOUD_PROVIDER); + LambdaEcsSerializer.serializeCloudService(builder, CLOUD_SERVICE); + if (arn != null) { + String[] arnParts = arn.split(":"); + LambdaEcsSerializer.serializeCloudRegion(builder, arnParts[3]); + LambdaEcsSerializer.serializeCloudAccountId(builder, arnParts[4]); + } + } + + if (includeFaasInfo) { + LambdaEcsSerializer.serializeFunctionId(builder, arn); + LambdaEcsSerializer.serializeFunctionName(builder, mdcPropertyMap.get(FUNCTION_NAME.getName())); + LambdaEcsSerializer.serializeFunctionVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); + LambdaEcsSerializer.serializeFunctionMemory(builder, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName())); + LambdaEcsSerializer.serializeFunctionExecutionId(builder, + mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName())); + LambdaEcsSerializer.serializeColdStart(builder, mdcPropertyMap.get(FUNCTION_COLD_START.getName())); + LambdaEcsSerializer.serializeTraceId(builder, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName())); } - LambdaEcsSerializer.serializeFunctionId(builder, arn); - LambdaEcsSerializer.serializeFunctionName(builder, mdcPropertyMap.get(FUNCTION_NAME.getName())); - LambdaEcsSerializer.serializeFunctionVersion(builder, mdcPropertyMap.get(FUNCTION_VERSION.getName())); - LambdaEcsSerializer.serializeFunctionMemory(builder, mdcPropertyMap.get(FUNCTION_MEMORY_SIZE.getName())); - LambdaEcsSerializer.serializeFunctionExecutionId(builder, mdcPropertyMap.get(FUNCTION_REQUEST_ID.getName())); - LambdaEcsSerializer.serializeColdStart(builder, mdcPropertyMap.get(FUNCTION_COLD_START.getName())); LambdaEcsSerializer.serializeAdditionalFields(builder, event.getMDCPropertyMap()); - LambdaEcsSerializer.serializeTraceId(builder, mdcPropertyMap.get(FUNCTION_TRACE_ID.getName())); LambdaEcsSerializer.serializeObjectEnd(builder); return builder.toString().getBytes(UTF_8); } @@ -110,7 +117,73 @@ public byte[] footerBytes() { return null; } + /** + * Specify a throwable converter to format the stacktrace according to your need (default is null, no throwableConverter): + *
    + *
    {@code
    +     *     
    +     *         
    +     *              30
    +     *              2048
    +     *              20
    +     *              sun\.reflect\..*\.invoke.*
    +     *              net\.sf\.cglib\.proxy\.MethodProxy\.invoke
    +     *              
    +     *              true
    +     *              true
    +     *         
    +     *     
    +     * }
    + * @param throwableConverter + */ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { this.throwableConverter = throwableConverter; } + + /** + * Specify if cloud information should be logged (default is true): + *
      + *
    • cloud.provider
    • + *
    • cloud.service.name
    • + *
    • cloud.region
    • + *
    • cloud.account.id
    • + *
    + *
    + * We strongly recommend to keep these information. + *
    + *
    {@code
    +     *     
    +     *         false
    +     *     
    +     * }
    + * @param includeCloudInfo if thread information should be logged + */ + public void setIncludeCloudInfo(boolean includeCloudInfo) { + this.includeCloudInfo = includeCloudInfo; + } + + /** + * Specify if Lambda function information should be logged (default is true): + *
      + *
    • faas.id
    • + *
    • faas.name
    • + *
    • faas.version
    • + *
    • faas.memory
    • + *
    • faas.execution
    • + *
    • faas.coldstart
    • + *
    • trace.id
    • + *
    + *
    + * We strongly recommend to keep these information. + *
    + *
    {@code
    +     *     
    +     *         false
    +     *     
    +     * }
    + * @param includeFaasInfo if function information should be logged + */ + public void setIncludeFaasInfo(boolean includeFaasInfo) { + this.includeFaasInfo = includeFaasInfo; + } } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java index a1a378605..2a901ebd4 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -35,6 +35,7 @@ public class LambdaJsonEncoder extends EncoderBase { protected String timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZz"; protected String timestampFormatTimezoneId = null; private boolean includeThreadInfo = false; + private boolean includePowertoolsInfo = true; @Override public byte[] headerBytes() { @@ -68,7 +69,7 @@ public byte[] encode(ILoggingEvent event) { throwableProxy.getMessage(), throwableProxyConverter.convert(event)); } } - LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap()); + LambdaJsonSerializer.serializePowertools(builder, event.getMDCPropertyMap(), includePowertoolsInfo); if (includeThreadInfo) { LambdaJsonSerializer.serializeThreadName(builder, event.getThreadName()); LambdaJsonSerializer.serializeThreadId(builder, String.valueOf(Thread.currentThread().getId())); @@ -85,19 +86,95 @@ public byte[] footerBytes() { return null; } + /** + * Specify the format of the timestamp (default is yyyy-MM-dd'T'HH:mm:ss.SSSZz): + *
    + *
    {@code
    +     *     
    +     *         yyyy-MM-dd'T'HH:mm:ss.SSSZz
    +     *     
    +     * }
    + * @param timestampFormat format of the timestamp (compatible with {@link java.text.SimpleDateFormat}) + */ public void setTimestampFormat(String timestampFormat) { this.timestampFormat = timestampFormat; } + /** + * Specify the format of the time zone id for timestamp (default is null, no timezone): + *
    + *
    {@code
    +     *     
    +     *         Europe/Paris
    +     *     
    +     * }
    + * @param timestampFormatTimezoneId Zone Id (see {@link java.util.TimeZone}) + */ public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { this.timestampFormatTimezoneId = timestampFormatTimezoneId; } + /** + * Specify a throwable converter to format the stacktrace according to your need (default is null, no throwableConverter): + *
    + *
    {@code
    +     *     
    +     *         
    +     *              30
    +     *              2048
    +     *              20
    +     *              sun\.reflect\..*\.invoke.*
    +     *              net\.sf\.cglib\.proxy\.MethodProxy\.invoke
    +     *              
    +     *              true
    +     *              true
    +     *         
    +     *     
    +     * }
    + * @param throwableConverter + */ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { this.throwableConverter = throwableConverter; } + /** + * Specify if thread information should be logged (default is false) + *
    + *
    {@code
    +     *     
    +     *         true
    +     *     
    +     * }
    + * @param includeThreadInfo if thread information should be logged + */ public void setIncludeThreadInfo(boolean includeThreadInfo) { this.includeThreadInfo = includeThreadInfo; } + + /** + * Specify if Lambda function information should be logged (default is true): + *
      + *
    • function_name
    • + *
    • function_version
    • + *
    • function_arn
    • + *
    • function_memory_size
    • + *
    • function_request_id
    • + *
    • cold_start
    • + *
    • xray_trace_id
    • + *
    • sampling_rate
    • + *
    • service
    • + *
    + *
    + * We strongly recommend to keep these information. + *
    + *
    {@code
    +     *     
    +     *         false
    +     *     
    +     * }
    + * @param includePowertoolsInfo if function information should be logged + */ + public void setIncludePowertoolsInfo(boolean includePowertoolsInfo) { + this.includePowertoolsInfo = includePowertoolsInfo; + } } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java index 97b49595b..fd895092b 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java @@ -19,9 +19,9 @@ public class JsonUtils { protected static void serializeAttribute(StringBuilder builder, String attr, String value, boolean notBegin) { if (value != null) { if (notBegin) { - builder.append(", "); + builder.append(","); } - builder.append("\"").append(attr).append("\": "); + builder.append("\"").append(attr).append("\":"); boolean isString = isString(value); if (isString) { builder.append("\""); @@ -37,11 +37,11 @@ protected static void serializeAttributeAsString(StringBuilder builder, String a boolean notBegin) { if (value != null) { if (notBegin) { - builder.append(", "); + builder.append(","); } builder.append("\"") .append(attr) - .append("\": \"") + .append("\":\"") .append(value) .append("\""); } @@ -72,7 +72,7 @@ private static boolean isString(String str) { * Taken from commons-lang3 NumberUtils to avoid include the library */ private static boolean isNumeric(final String str) { - if (str == null || str.length() == 0) { + if (str == null || str.isEmpty()) { return false; } if (str.charAt(str.length() - 1) == '.') { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java index 3a052e6a4..6bdc96221 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java @@ -137,10 +137,6 @@ public static void serializeServiceVersion(StringBuilder builder, String service serializeAttributeAsString(builder, SERVICE_VERSION_ATTR_NAME, serviceVersion); } - public static void serializeEventDataset(StringBuilder builder, String serviceName) { - serializeAttributeAsString(builder, EVENT_DATASET_ATTR_NAME, serviceName); - } - public static void serializeLoggerName(StringBuilder builder, String loggerName) { serializeAttributeAsString(builder, LOGGER_ATTR_NAME, loggerName); } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index c3a29ac6b..a2a6c2029 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -87,11 +87,11 @@ public static void serializeFormattedMessage(StringBuilder builder, String forma } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { - builder.append("\"").append(EXCEPTION_ATTR_NAME).append("\": {"); + builder.append(",\"").append(EXCEPTION_ATTR_NAME).append("\":{"); serializeAttribute(builder, EXCEPTION_MSG_ATTR_NAME, message, false); serializeAttribute(builder, EXCEPTION_CLASS_ATTR_NAME, className); serializeAttribute(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); - builder.append("},"); + builder.append("}"); } public static void serializeException(StringBuilder builder, Throwable throwable) { @@ -107,10 +107,13 @@ public static void serializeThreadPriority(StringBuilder builder, String threadP serializeAttribute(builder, THREAD_PRIORITY_ATTR_NAME, threadPriority); } - public static void serializePowertools(StringBuilder builder, Map mdc) { + public static void serializePowertools(StringBuilder builder, Map mdc, boolean includePowertoolsInfo) { TreeMap sortedMap = new TreeMap<>(mdc); - sortedMap.forEach((k, v) -> - serializeAttribute(builder, k, v)); + sortedMap.forEach((k, v) -> { + if ((PowertoolsLoggedFields.stringValues().contains(k) && includePowertoolsInfo) || !PowertoolsLoggedFields.stringValues().contains(k)) { + serializeAttribute(builder, k, v); + } + }); } } diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java new file mode 100644 index 000000000..e13e78eb4 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.slf4j.event.Level.DEBUG; +import static org.slf4j.event.Level.ERROR; +import static org.slf4j.event.Level.WARN; + +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; +import software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager; + +class LogbackLoggingManagerTest { + + private static final Logger LOG = LoggerFactory.getLogger(LogbackLoggingManagerTest.class); + private static final Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Test + @Order(1) + void getLogLevel_shouldReturnConfiguredLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(DEBUG); + + logLevel = manager.getLogLevel(ROOT); + assertThat(logLevel).isEqualTo(WARN); + } + + @Test + @Order(2) + void resetLogLevel() { + LogbackLoggingManager manager = new LogbackLoggingManager(); + manager.resetLogLevel(ERROR); + + Level logLevel = manager.getLogLevel(LOG); + assertThat(logLevel).isEqualTo(ERROR); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java new file mode 100644 index 000000000..908c5fa40 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; +import ch.qos.logback.classic.spi.LoggingEvent; +import com.amazonaws.services.lambda.runtime.Context; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.LambdaEcsEncoder; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +@Order(3) +class LambdaEcsEncoderTest { + + private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaEcsEncoderTest.class.getName()); + + + @Mock + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + openMocks(this); + MDC.clear(); + writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); + setupContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @AfterEach + void cleanUp() throws IOException{ +// FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } + + @Test + @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1") + @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") + void shouldLogInEcsFormat() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } + + private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); + + @Test + void shouldNotLogFunctionInfo() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + setMDC(); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"false\""); + + // WHEN (includeFaasInfo = false) + encoder.setIncludeFaasInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no faas info in logs) + assertThat(result).doesNotContain("faas"); + } + + @Test + void shouldNotLogCloudInfo() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + setMDC(); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\""); + + // WHEN (includeCloudInfo = false) + encoder.setIncludeCloudInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no faas info in logs) + assertThat(result).doesNotContain("cloud"); + } + + @Test + void shouldLogException() { + // GIVEN + LambdaEcsEncoder encoder = new LambdaEcsEncoder(); + encoder.start(); + LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error", new IllegalStateException("Unexpected value"), null); + + // WHEN + byte[] encoded = encoder.encode(errorloggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"[software.amazon.lambda.powertools.logging.internal.LambdaEcsEncoderTest.shouldLogException"); + + // WHEN (configure a custom throwableConverter) + encoder = new LambdaEcsEncoder(); + RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter(); + encoder.setThrowableConverter(throwableConverter); + encoder.start(); + encoded = encoder.encode(errorloggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (stack is logged with root cause first) + assertThat(result).contains("\"message\":\"Error\",\"error.message\":\"Unexpected value\",\"error.type\":\"java.lang.IllegalStateException\",\"error.stack_trace\":\"java.lang.IllegalStateException: Unexpected value\n"); + } + + private void setMDC() { + MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName()); + MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn()); + MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion()); + MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(), + String.valueOf(context.getMemoryLimitInMB())); + MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId()); + MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false"); + MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2"); + MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service"); + } + + private void setupContext() { + when(context.getFunctionName()).thenReturn("testFunction"); + when(context.getInvokedFunctionArn()).thenReturn( + "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); + when(context.getFunctionVersion()).thenReturn("1"); + when(context.getMemoryLimitInMB()).thenReturn(1024); + when(context.getAwsRequestId()).thenReturn("RequestId"); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java new file mode 100644 index 000000000..c579e3b7c --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -0,0 +1,190 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; + +import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; +import ch.qos.logback.classic.spi.LoggingEvent; +import com.amazonaws.services.lambda.runtime.Context; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.logging.LambdaJsonEncoder; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +@Order(2) +class LambdaJsonEncoderTest { + private static final Logger logger = (Logger) LoggerFactory.getLogger(LambdaJsonEncoderTest.class.getName()); + + @Mock + private Context context; + + @BeforeEach + void setUp() throws IllegalAccessException, IOException { + openMocks(this); + MDC.clear(); + writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); + setupContext(); + // Make sure file is cleaned up before running tests + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file may not exist on the first launch + } + } + + @AfterEach + void cleanUp() throws IOException{ + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001") + @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") + void shouldLogInJsonFormat() { + // GIVEN + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + + // WHEN + handler.handleRequest("Input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); + } + + private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); + + @Test + void shouldNotLogPowertoolsInfo() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + + MDC.put(PowertoolsLoggedFields.FUNCTION_NAME.getName(), context.getFunctionName()); + MDC.put(PowertoolsLoggedFields.FUNCTION_ARN.getName(), context.getInvokedFunctionArn()); + MDC.put(PowertoolsLoggedFields.FUNCTION_VERSION.getName(), context.getFunctionVersion()); + MDC.put(PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE.getName(), + String.valueOf(context.getMemoryLimitInMB())); + MDC.put(PowertoolsLoggedFields.FUNCTION_REQUEST_ID.getName(), context.getAwsRequestId()); + MDC.put(PowertoolsLoggedFields.FUNCTION_COLD_START.getName(), "false"); + MDC.put(PowertoolsLoggedFields.SAMPLING_RATE.getName(), "0.2"); + MDC.put(PowertoolsLoggedFields.SERVICE.getName(), "Service"); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("{\"level\":\"INFO\",\"message\":\"message\",\"cold_start\":false,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"sampling_rate\":0.2,\"service\":\"Service\",\"timestamp\":"); + + // WHEN (powertoolsInfo = false) + encoder.setIncludePowertoolsInfo(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (no powertools info in logs) + assertThat(result).doesNotContain("cold_start", "function_arn", "function_memory_size", "function_name", "function_request_id", "function_version", "sampling_rate", "service"); + } + + @Test + void shouldLogThreadInfo() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.setIncludeThreadInfo(true); + + // WHEN + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"thread\":\"main\",\"thread_id\":1,\"thread_priority\":5"); + } + + @Test + void shouldLogTimestampDifferently() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + String pattern = "yyyy-MM-dd_HH"; + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); + encoder.setTimestampFormat(pattern); + encoder.setTimestampFormatTimezoneId("Europe/Paris"); + + // WHEN + Date date = new Date(); + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"timestamp\":\""+simpleDateFormat.format(date)+"\""); + } + + @Test + void shouldLogException() { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.start(); + LoggingEvent errorloggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "Error", new IllegalStateException("Unexpected value"), null); + + // WHEN + byte[] encoded = encoder.encode(errorloggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN + assertThat(result).contains("\"message\":\"Error\",\"error\":{\"message\":\"Unexpected value\",\"name\":\"java.lang.IllegalStateException\",\"stack\":\"[software.amazon.lambda.powertools.logging.internal.LambdaJsonEncoderTest.shouldLogException"); + + // WHEN (configure a custom throwableConverter) + encoder = new LambdaJsonEncoder(); + RootCauseFirstThrowableProxyConverter throwableConverter = new RootCauseFirstThrowableProxyConverter(); + encoder.setThrowableConverter(throwableConverter); + encoder.start(); + encoded = encoder.encode(errorloggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (stack is logged with root cause first) + assertThat(result).contains("\"message\":\"Error\",\"error\":{\"message\":\"Unexpected value\",\"name\":\"java.lang.IllegalStateException\",\"stack\":\"java.lang.IllegalStateException: Unexpected value\n"); + } + + private void setupContext() { + when(context.getFunctionName()).thenReturn("testFunction"); + when(context.getInvokedFunctionArn()).thenReturn( + "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); + when(context.getFunctionVersion()).thenReturn("1"); + when(context.getMemoryLimitInMB()).thenReturn(1024); + when(context.getAwsRequestId()).thenReturn("RequestId"); + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java new file mode 100644 index 000000000..982ee86b7 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import com.amazonaws.services.lambda.runtime.Context; +import java.io.File; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.NoSuchFileException; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.Mock; +import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; + +@Order(1) +class LambdaLoggingAspectTest { + + @Mock + private Context context; + + @BeforeEach + void setUp() throws IOException { + openMocks(this); + MDC.clear(); + setupContext(); + //Make sure file is cleaned up before running full stack logging regression + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // file might not be there for the first run + } + } + + @AfterEach + void cleanUp() throws IOException { +// FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } + + @Test + @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLogback") + void testSlf4jBinding() { + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("slf4j.binding is set to ch.qos.logback.classic.spi.LogbackServiceProvider") + .contains("Loading software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"); + } + + private void setupContext() { + when(context.getFunctionName()).thenReturn("testFunction"); + when(context.getInvokedFunctionArn()).thenReturn( + "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); + when(context.getFunctionVersion()).thenReturn("1"); + when(context.getMemoryLimitInMB()).thenReturn(1024); + when(context.getAwsRequestId()).thenReturn("RequestId"); + } +} \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java deleted file mode 100644 index 2d70ab3ff..000000000 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManagerTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package software.amazon.lambda.powertools.logging.internal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.slf4j.event.Level.ERROR; -import static org.slf4j.event.Level.INFO; -import static org.slf4j.event.Level.WARN; - -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; - -class LogbackLoggingManagerTest { - - private static Logger LOG = LoggerFactory.getLogger(LogbackLoggingManagerTest.class); - private static Logger ROOT = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - - @Test - @Order(1) - void getLogLevel_shouldReturnConfiguredLogLevel() { - LogbackLoggingManager manager = new LogbackLoggingManager(); - Level logLevel = manager.getLogLevel(LOG); - assertThat(logLevel).isEqualTo(INFO); - - logLevel = manager.getLogLevel(ROOT); - assertThat(logLevel).isEqualTo(WARN); - } - - @Test - @Order(2) - void resetLogLevel() { - LogbackLoggingManager manager = new LogbackLoggingManager(); - manager.resetLogLevel(ERROR); - - Level logLevel = manager.getLogLevel(LOG); - assertThat(logLevel).isEqualTo(ERROR); - } -} diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java new file mode 100644 index 000000000..faa722756 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsLogEnabled.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; + +public class PowertoolsLogEnabled implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + + @Override + @Logging(clearState = true) + public Object handleRequest(Object input, Context context) { + LoggingUtils.appendKey("myKey", "myValue"); + LOG.debug("Test debug event"); + return null; + } +} diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties b/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties new file mode 100644 index 000000000..80a2481d7 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/resources/junit-platform.properties @@ -0,0 +1,17 @@ +# +# Copyright 2023 Amazon.com, Inc. or its affiliates. +# 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. +# +# + +# because of LambdaLoggingAspect static initialization of the LoggingManager, we need to +# set an order in the unit tests, especially LambdaLoggingAspectTest needs to be first +junit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer$OrderAnnotation \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml b/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml index 116833c3f..7afc74026 100644 --- a/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml +++ b/powertools-logging/powertools-logging-logback/src/test/resources/logback-test.xml @@ -1,13 +1,27 @@ - + - - + + target/logfile.json + + + + + + target/ecslogfile.json + + + + + + + - + + \ No newline at end of file From 788dc97a95d5f672f97048221b55b57ebd5e3aa9 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 21 Sep 2023 23:23:12 +0200 Subject: [PATCH 30/74] fixe timezone --- .../powertools/logging/internal/LambdaJsonEncoderTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index c579e3b7c..c2d11e162 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -34,6 +34,7 @@ import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.TimeZone; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; @@ -140,9 +141,9 @@ void shouldLogTimestampDifferently() { // GIVEN LambdaJsonEncoder encoder = new LambdaJsonEncoder(); String pattern = "yyyy-MM-dd_HH"; - SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); + String timeZone = "Europe/Paris"; encoder.setTimestampFormat(pattern); - encoder.setTimestampFormatTimezoneId("Europe/Paris"); + encoder.setTimestampFormatTimezoneId(timeZone); // WHEN Date date = new Date(); @@ -150,6 +151,8 @@ void shouldLogTimestampDifferently() { String result = new String(encoded, StandardCharsets.UTF_8); // THEN + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone(timeZone)); assertThat(result).contains("\"timestamp\":\""+simpleDateFormat.format(date)+"\""); } From 895843e737b572df3258ff86c23c6b6631f3b1d0 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 09:30:48 +0200 Subject: [PATCH 31/74] fix checkstyle --- .../resolver/PowertoolsResolverFactory.java | 3 ++ .../logging/internal/Log4jLoggingManager.java | 9 +++++ .../powertools/logging/LambdaEcsEncoder.java | 16 ++++++-- .../powertools/logging/LambdaJsonEncoder.java | 13 +++++-- .../logging/internal/JsonUtils.java | 18 ++++++--- .../internal/LambdaJsonSerializer.java | 6 ++- .../internal/LogbackLoggingManager.java | 9 +++++ .../lambda/powertools/logging/Logging.java | 12 ++++-- .../logging/internal/LambdaLoggingAspect.java | 38 ++++++++++++------- .../logging/internal/LoggingManager.java | 6 ++- .../internal/PowertoolsLoggedFields.java | 4 +- 11 files changed, 101 insertions(+), 33 deletions(-) diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java index 14d4f67aa..297c00691 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverFactory.java @@ -18,6 +18,9 @@ import org.apache.logging.log4j.core.config.plugins.Plugin; import org.apache.logging.log4j.core.config.plugins.PluginFactory; +/** + * Factory for {@link PowertoolsResolver}. Log4j plugin to process powertools fields in the layout.json + */ @Plugin(name = "PowertoolsResolverFactory", category = TemplateResolverFactory.CATEGORY) public final class PowertoolsResolverFactory implements EventResolverFactory { diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java index 1b49a1fb3..a086ff596 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -20,8 +20,14 @@ import org.apache.logging.log4j.core.config.Configurator; import org.slf4j.Logger; +/** + * LoggingManager for Log4j2 (see {@link LoggingManager}). + */ public class Log4jLoggingManager implements LoggingManager { + /** + * @inheritDoc + */ @Override @SuppressWarnings("java:S4792") public void resetLogLevel(org.slf4j.event.Level logLevel) { @@ -30,6 +36,9 @@ public void resetLogLevel(org.slf4j.event.Level logLevel) { ctx.updateLoggers(); } + /** + * @inheritDoc + */ @Override public org.slf4j.event.Level getLogLevel(Logger logger) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java index f83a43ef4..4124180f0 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -59,9 +59,15 @@ public byte[] headerBytes() { return null; } + /** + * Main method of the encoder. Encode a logging event into Json format (with Elastic Search fields) + * + * @param event the logging event + * @return the encoded bytes + */ @Override public byte[] encode(ILoggingEvent event) { - Map mdcPropertyMap = event.getMDCPropertyMap(); + final Map mdcPropertyMap = event.getMDCPropertyMap(); StringBuilder builder = new StringBuilder(256); LambdaEcsSerializer.serializeObjectStart(builder); @@ -118,7 +124,8 @@ public byte[] footerBytes() { } /** - * Specify a throwable converter to format the stacktrace according to your need (default is null, no throwableConverter): + * Specify a throwable converter to format the stacktrace according to your need + * (default is null, no throwableConverter): *
    *
    {@code
          *     
    @@ -134,7 +141,8 @@ public byte[] footerBytes() {
          *         
          *     
          * }
    - * @param throwableConverter + * + * @param throwableConverter converter for the throwable */ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { this.throwableConverter = throwableConverter; @@ -156,6 +164,7 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) * false * * }
    + * * @param includeCloudInfo if thread information should be logged */ public void setIncludeCloudInfo(boolean includeCloudInfo) { @@ -181,6 +190,7 @@ public void setIncludeCloudInfo(boolean includeCloudInfo) { * false * * } + * * @param includeFaasInfo if function information should be logged */ public void setIncludeFaasInfo(boolean includeFaasInfo) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java index 2a901ebd4..a007045de 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -26,7 +26,8 @@ /** * Custom encoder for logback that encodes logs in JSON format. - * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) to reduce the weight of the library. + * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) + * to reduce the weight of the library. */ public class LambdaJsonEncoder extends EncoderBase { @@ -94,6 +95,7 @@ public byte[] footerBytes() { * yyyy-MM-dd'T'HH:mm:ss.SSSZz * * } + * * @param timestampFormat format of the timestamp (compatible with {@link java.text.SimpleDateFormat}) */ public void setTimestampFormat(String timestampFormat) { @@ -108,6 +110,7 @@ public void setTimestampFormat(String timestampFormat) { * Europe/Paris * * } + * * @param timestampFormatTimezoneId Zone Id (see {@link java.util.TimeZone}) */ public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { @@ -115,7 +118,8 @@ public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { } /** - * Specify a throwable converter to format the stacktrace according to your need (default is null, no throwableConverter): + * Specify a throwable converter to format the stacktrace according to your need + * (default is null, no throwableConverter): *
    *
    {@code
          *     
    @@ -131,7 +135,8 @@ public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) {
          *         
          *     
          * }
    - * @param throwableConverter + * + * @param throwableConverter converter for the throwable */ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) { this.throwableConverter = throwableConverter; @@ -145,6 +150,7 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) * true * * } + * * @param includeThreadInfo if thread information should be logged */ public void setIncludeThreadInfo(boolean includeThreadInfo) { @@ -172,6 +178,7 @@ public void setIncludeThreadInfo(boolean includeThreadInfo) { * false * * } + * * @param includePowertoolsInfo if function information should be logged */ public void setIncludePowertoolsInfo(boolean includePowertoolsInfo) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java index fd895092b..a31782b8e 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java @@ -14,8 +14,15 @@ package software.amazon.lambda.powertools.logging.internal; +/** + * Json tools to serialize attributes manually, to avoid using further dependencies (jackson, gson...) + */ public class JsonUtils { + private JsonUtils() { + // static utils + } + protected static void serializeAttribute(StringBuilder builder, String attr, String value, boolean notBegin) { if (value != null) { if (notBegin) { @@ -33,6 +40,10 @@ protected static void serializeAttribute(StringBuilder builder, String attr, Str } } + protected static void serializeAttribute(StringBuilder builder, String attr, String value) { + serializeAttribute(builder, attr, value, true); + } + protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value, boolean notBegin) { if (value != null) { @@ -47,16 +58,13 @@ protected static void serializeAttributeAsString(StringBuilder builder, String a } } - protected static void serializeAttribute(StringBuilder builder, String attr, String value) { - serializeAttribute(builder, attr, value, true); - } - protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value) { serializeAttributeAsString(builder, attr, value, true); } /** - * As MDC is a Map, we need to check the type to output numbers and booleans correctly (without quotes) + * As MDC is a {@code Map}, we need to check the type + * to output numbers and booleans correctly (without quotes) */ private static boolean isString(String str) { if (str == null) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index a2a6c2029..c7738340c 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -107,10 +107,12 @@ public static void serializeThreadPriority(StringBuilder builder, String threadP serializeAttribute(builder, THREAD_PRIORITY_ATTR_NAME, threadPriority); } - public static void serializePowertools(StringBuilder builder, Map mdc, boolean includePowertoolsInfo) { + public static void serializePowertools(StringBuilder builder, Map mdc, + boolean includePowertoolsInfo) { TreeMap sortedMap = new TreeMap<>(mdc); sortedMap.forEach((k, v) -> { - if ((PowertoolsLoggedFields.stringValues().contains(k) && includePowertoolsInfo) || !PowertoolsLoggedFields.stringValues().contains(k)) { + if ((PowertoolsLoggedFields.stringValues().contains(k) && includePowertoolsInfo) + || !PowertoolsLoggedFields.stringValues().contains(k)) { serializeAttribute(builder, k, v); } }); diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java index 222adf7e8..d010e2977 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java @@ -21,6 +21,9 @@ import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; +/** + * LoggingManager for Logback (see {@link LoggingManager}). + */ public class LogbackLoggingManager implements LoggingManager { private final LoggerContext loggerContext; @@ -33,6 +36,9 @@ public LogbackLoggingManager() { loggerContext = (LoggerContext) loggerFactory; } + /** + * @inheritDoc + */ @Override @SuppressWarnings("java:S4792") public void resetLogLevel(org.slf4j.event.Level logLevel) { @@ -42,6 +48,9 @@ public void resetLogLevel(org.slf4j.event.Level logLevel) { } } + /** + * @inheritDoc + */ @Override public org.slf4j.event.Level getLogLevel(org.slf4j.Logger logger) { return org.slf4j.event.Level.valueOf(loggerContext.getLogger(logger.getName()).getEffectiveLevel().toString()); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index 0790452cc..818b85ec9 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -37,7 +37,8 @@ *
  • function_name
  • *
  • function_version
  • *
  • function_arn
  • - *
  • MemoryLimitInMB
  • + *
  • function_memory_size
  • + *
  • function_request_id
  • * * *

    By default {@code Logging} will also create keys for:

    @@ -56,17 +57,20 @@ *

    By default {@code Logging} will not log the event which has trigger the invoke of the Lambda function. * This can be enabled using {@code @Logging(logEvent = true)}.

    * - *

    By default {@code Logging} all debug logs will follow log4j2 configuration unless configured via - * POWERTOOLS_LOGGER_SAMPLE_RATE environment variable {@code @Logging(samplingRate = <0.0-1.0>)}.

    - * *

    To append additional keys to each log entry you can use {@link LoggingUtils#appendKey(String, String)}

    */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Logging { + /** + * Set to true if you want to log the event received by the Lambda function handler + */ boolean logEvent() default false; + /** + * Sampling rate to change log level to DEBUG. (values must be >=0.0, <=1.0) + */ double samplingRate() default 0; /** diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index c5fec317d..054b46201 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -93,6 +93,7 @@ private static LoggingManager loadLoggingManager() { *
  • JAVA_TOOL_OPTIONS="-Dslf4j.binding=org.apache.logging.slf4j.SLF4JServiceProvider"
  • * *

    + * * @return an instance of {@link LoggingManager} or null */ private static Optional getLoggingManagerFromSlf4jBinding() { @@ -115,18 +116,26 @@ private static Optional getLoggingManagerFromSlf4jBinding() { .loadClass(managerClass); loggingManager = (LoggingManager) log4backManagerClass.newInstance(); } else { - LOG.warn("slf4j.binding {} not supported, fallback to Service Loader. " + - "Only log4j and logback are supported.", slf4jBinding); + LOG.warn("slf4j.binding {} not supported, fallback to Service Loader. " + + "Only log4j and logback are supported.", slf4jBinding); } } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - LOG.warn("Could not instantiate LoggingManager based on slf4j.binding {}, " + - "make sure to add either powertools-logging-log4j or powertools-logging-logback module", + LOG.warn("Could not instantiate LoggingManager based on slf4j.binding {}, " + + "make sure to add either powertools-logging-log4j or powertools-logging-logback module", slf4jBinding); } } return Optional.ofNullable(loggingManager); } + /** + * Use {@link ServiceLoader} to lookup for a {@link LoggingManager}. + * A file software.amazon.lambda.powertools.logging.internal.LoggingManager must be created in + * META-INF/services/ folder with the appropriate implementation of the {@link LoggingManager} + * + * @return an instance of {@link LoggingManager} + * @throws IllegalStateException if no {@link LoggingManager} could be found + */ private static LoggingManager getLoggingManagerFromServiceLoader() { LoggingManager loggingManager; @@ -136,12 +145,12 @@ private static LoggingManager getLoggingManagerFromServiceLoader() { loggingManagerList.add(lm); } if (loggingManagerList.isEmpty()) { - throw new IllegalStateException("No LoggingManager was found on the classpath, " + - "make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); + throw new IllegalStateException("No LoggingManager was found on the classpath, " + + "make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); } else if (loggingManagerList.size() > 1) { throw new IllegalStateException( - "Multiple LoggingManagers were found on the classpath: " + loggingManagerList + - ", make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies"); + "Multiple LoggingManagers were found on the classpath: " + loggingManagerList + + ", make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies"); } else { loggingManager = loggingManagerList.get(0); } @@ -157,6 +166,9 @@ private static void resetLogLevels(Level logLevel) { public void callAt(Logging logging) { } + /** + * Main method of the aspect + */ @Around(value = "callAt(logging) && execution(@Logging * *.*(..))", argNames = "pjp,logging") public Object around(ProceedingJoinPoint pjp, Logging logging) throws Throwable { @@ -215,8 +227,8 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, if (samplingRate > sample) { resetLogLevels(Level.DEBUG); - LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + - "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); + LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + + "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); } else if (LEVEL_AT_INITIALISATION != loggingManager.getLogLevel(LOG)) { resetLogLevels(LEVEL_AT_INITIALISATION); } @@ -229,8 +241,8 @@ private double samplingRate(final Logging logging) { try { return Double.parseDouble(sampleRate); } catch (NumberFormatException e) { - LOG.warn("Skipping sampling rate on environment variable configuration because of invalid " + - "value. Sampling rate: {}", sampleRate); + LOG.warn("Skipping sampling rate on environment variable configuration because of invalid " + + "value. Sampling rate: {}", sampleRate); } } return logging.samplingRate(); @@ -311,7 +323,7 @@ private Object[] logFromInputStream(final ProceedingJoinPoint pjp) { private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws IOException { try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStreamReader reader = new InputStreamReader(inputStream, UTF_8)) { + InputStreamReader reader = new InputStreamReader(inputStream, UTF_8)) { OutputStreamWriter writer = new OutputStreamWriter(out, UTF_8); int n; char[] buffer = new char[4096]; diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java index 1f4a4e636..a141910cb 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java @@ -25,7 +25,8 @@ * *

    * Implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} - * (define a file named software.amazon.lambda.powertools.logging.internal.LoggingManager in src/main/resources/META-INF/services with the qualified name of the implementation). + * (define a file named software.amazon.lambda.powertools.logging.internal.LoggingManager + * in src/main/resources/META-INF/services with the qualified name of the implementation). */ public interface LoggingManager { /** @@ -39,7 +40,8 @@ public interface LoggingManager { * Retrieve the log Level of a specific logger * * @param logger the logger (slf4j) for which to retrieve the log Level - * @return the Level (slf4j) of this logger. Note that SLF4J only support ERROR, WARN, INFO, DEBUG, TRACE while some frameworks may support others (OFF, FATAL, ...) + * @return the Level (slf4j) of this logger. + * Note that SLF4J only support ERROR, WARN, INFO, DEBUG, TRACE while some frameworks may support others (OFF, FATAL, ...) */ Level getLogLevel(Logger logger); } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java index 1c64248f9..686c8c0c2 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java @@ -21,6 +21,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * Fields added in the logs by Powertools + */ public enum PowertoolsLoggedFields { FUNCTION_NAME("function_name"), FUNCTION_VERSION("function_version"), @@ -42,7 +45,6 @@ public static List stringValues() { return Stream.of(values()).map(PowertoolsLoggedFields::getName).collect(Collectors.toList()); } - static Map setValuesFromLambdaContext(Context context) { Map hashMap = new HashMap<>(); From e5e3f187d81ce27398b4d2547d16f48f2b65562e Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 10:10:31 +0200 Subject: [PATCH 32/74] improve correlationId: - use jmespath for correlationId - add appsync + test - rename constant --- docs/core/logging.md | 2 +- ...Constants.java => CorrelationIdPaths.java} | 14 ++++--- .../lambda/powertools/logging/Logging.java | 3 +- .../logging/internal/LambdaLoggingAspect.java | 5 ++- .../PowertoolsLogAlbCorrelationId.java | 2 +- ...oolsLogApiGatewayHttpApiCorrelationId.java | 2 +- ...oolsLogApiGatewayRestApiCorrelationId.java | 2 +- .../PowertoolsLogAppSyncCorrelationId.java | 37 +++++++++++++++++++ ...PowertoolsLogEventBridgeCorrelationId.java | 2 +- .../internal/LambdaLoggingAspectTest.java | 15 +++++++- 10 files changed, 70 insertions(+), 14 deletions(-) rename powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/{CorrelationIdPathConstants.java => CorrelationIdPaths.java} (69%) create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java diff --git a/docs/core/logging.md b/docs/core/logging.md index 58e907906..9d4b73588 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -357,7 +357,7 @@ for known event sources, where either a request ID or X-Ray Trace ID are present === "App.java" ```java hl_lines="10" - import software.amazon.lambda.powertools.logging.CorrelationIdPathConstants; + import software.amazon.lambda.powertools.logging.CorrelationIdPaths; /** * Handler for requests to Lambda function. diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java similarity index 69% rename from powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java rename to powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java index ce43c9aa0..6fb38502f 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPathConstants.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/CorrelationIdPaths.java @@ -17,21 +17,25 @@ /** * Supported Event types from which Correlation ID can be extracted */ -public class CorrelationIdPathConstants { +public class CorrelationIdPaths { /** * To use when function is expecting API Gateway Rest API Request event */ - public static final String API_GATEWAY_REST = "/requestContext/requestId"; + public static final String API_GATEWAY_REST = "requestContext.requestId"; /** * To use when function is expecting API Gateway HTTP API Request event */ - public static final String API_GATEWAY_HTTP = "/requestContext/requestId"; + public static final String API_GATEWAY_HTTP = "requestContext.requestId"; /** * To use when function is expecting Application Load balancer Request event */ - public static final String APPLICATION_LOAD_BALANCER = "/headers/x-amzn-trace-id"; + public static final String APPLICATION_LOAD_BALANCER = "headers.\"x-amzn-trace-id\""; /** * To use when function is expecting Event Bridge Request event */ - public static final String EVENT_BRIDGE = "/id"; + public static final String EVENT_BRIDGE = "id"; + /** + * To use when function is expecting an AppSync request + */ + public static final String APPSYNC_RESOLVER = "request.headers.\"x-amzn-trace-id\""; } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index 818b85ec9..b9eab7da0 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -64,7 +64,8 @@ public @interface Logging { /** - * Set to true if you want to log the event received by the Lambda function handler + * Set to true if you want to log the event received by the Lambda function handler. + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_EVENT' environment variable */ boolean logEvent() default false; diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 054b46201..aebb6ab1d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -32,9 +32,9 @@ import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import io.burt.jmespath.Expression; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -292,7 +292,8 @@ private Object[] captureCorrelationId(final String correlationIdPath, } private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPoint pjp, JsonNode jsonNode) { - JsonNode node = jsonNode.at(JsonPointer.compile(correlationIdPath)); + Expression jmesExpression = JsonConfig.get().getJmesPath().compile(correlationIdPath); + JsonNode node = jmesExpression.search(jsonNode); String asText = node.asText(); if (null != asText && !asText.isEmpty()) { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java index 021caf185..065d4c5b0 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAlbCorrelationId.java @@ -14,7 +14,7 @@ package software.amazon.lambda.powertools.logging.handlers; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.APPLICATION_LOAD_BALANCER; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.APPLICATION_LOAD_BALANCER; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java index 010e68f0f..922a09f13 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayHttpApiCorrelationId.java @@ -14,7 +14,7 @@ package software.amazon.lambda.powertools.logging.handlers; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_HTTP; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.API_GATEWAY_HTTP; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java index f0be69ae3..7271e1d24 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogApiGatewayRestApiCorrelationId.java @@ -14,7 +14,7 @@ package software.amazon.lambda.powertools.logging.handlers; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.API_GATEWAY_REST; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.API_GATEWAY_REST; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java new file mode 100644 index 000000000..fbe2bc89b --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogAppSyncCorrelationId.java @@ -0,0 +1,37 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.APPSYNC_RESOLVER; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogAppSyncCorrelationId implements RequestStreamHandler { + + private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogAppSyncCorrelationId.class); + + @Override + @Logging(correlationIdPath = APPSYNC_RESOLVER) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + LOG.info("Test event"); + } +} \ No newline at end of file diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java index 9f12247cd..04d56d38c 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventBridgeCorrelationId.java @@ -14,7 +14,7 @@ package software.amazon.lambda.powertools.logging.handlers; -import static software.amazon.lambda.powertools.logging.CorrelationIdPathConstants.EVENT_BRIDGE; +import static software.amazon.lambda.powertools.logging.CorrelationIdPaths.EVENT_BRIDGE; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 6d568f544..bd0d61ba6 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -64,6 +64,7 @@ import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayHttpApiCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayRestApiCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAppSyncCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogClearState; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabledForStream; @@ -314,10 +315,22 @@ void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) void shouldLogCorrelationIdOnStreamHandler() throws IOException { RequestStreamHandler handler = new PowertoolsLogEventBridgeCorrelationId(); String eventId = "3"; - String event = "{\"id\":" + eventId + "}"; // CorrelationIdPathConstants.EVENT_BRIDGE + String event = "{\"id\":" + eventId + "}"; // CorrelationIdPath.EVENT_BRIDGE ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + assertThat(MDC.getCopyOfContextMap()) + .hasSize(EXPECTED_CONTEXT_SIZE + 1) + .containsEntry("correlation_id", eventId); + } + + @Test + void shouldLogCorrelationIdOnAppSyncEvent() throws IOException { + RequestStreamHandler handler = new PowertoolsLogAppSyncCorrelationId(); + String eventId = "456"; + String event = "{\"request\":{\"headers\":{\"x-amzn-trace-id\":" + eventId + "}}}"; // CorrelationIdPath.APPSYNC_RESOLVER + ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) From e13876b3a058cca2abe4a6acefc07ea210c94255 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 10:52:37 +0200 Subject: [PATCH 33/74] add support for environment variable POWERTOOLS_LOGGER_LOG_EVENT --- powertools-logging/pom.xml | 5 -- .../logging/internal/LambdaLoggingAspect.java | 5 +- .../internal/LambdaLoggingAspectTest.java | 69 ++++++++++++++----- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index c042a8240..1a2db6a3e 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -89,11 +89,6 @@ junit-jupiter-engine test - - org.junit-pioneer - junit-pioneer - test - org.apache.commons commons-lang3 diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index aebb6ab1d..ff2ee72ff 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -56,6 +56,7 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.slf4j.event.Level; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; import software.amazon.lambda.powertools.utilities.JsonConfig; @@ -186,7 +187,7 @@ public Object around(ProceedingJoinPoint pjp, getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); - if (logging.logEvent()) { + if (logging.logEvent() || "true".equals(SystemWrapper.getenv("POWERTOOLS_LOGGER_LOG_EVENT"))) { proceedArgs = logEvent(pjp); } @@ -236,7 +237,7 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, } private double samplingRate(final Logging logging) { - String sampleRate = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); + String sampleRate = SystemWrapper.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); if (null != sampleRate) { try { return Double.parseDouble(sampleRate); diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index bd0d61ba6..b7df0118f 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -46,6 +46,7 @@ import java.lang.reflect.Method; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Collections; @@ -53,8 +54,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junitpioneer.jupiter.ClearEnvironmentVariable; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; import org.mockito.MockedStatic; import org.slf4j.MDC; @@ -86,8 +85,7 @@ class LambdaLoggingAspectTest { private Context context; @BeforeEach - @ClearEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE") - void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, InvocationTargetException { + void setUp() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, IOException { openMocks(this); MDC.clear(); writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); @@ -95,6 +93,11 @@ void setUp() throws IllegalAccessException, IOException, NoSuchMethodException, requestHandler = new PowertoolsLogEnabled(); requestStreamHandler = new PowertoolsLogEnabledForStream(); resetLogLevel(Level.INFO); + try { + FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + } catch (NoSuchFileException e) { + // may not be there in the first run + } } @AfterEach @@ -188,29 +191,42 @@ void shouldLogDebugWhenSamplingEqualsOne() { } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "1") void shouldLogDebugWhenSamplingEnvVarEqualsOne() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains("Test debug event"); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) + .thenReturn("1"); + + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Test debug event"); + } } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "42") void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() { - requestHandler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) + .thenReturn("42"); + + requestHandler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + } } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "NotANumber") void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { - requestHandler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).doesNotContain("Test debug event"); - assertThat(contentOf(logFile)).contains("Skipping sampling rate on environment variable configuration because of invalid value"); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) + .thenReturn("NotANumber"); + + requestHandler.handleRequest(new Object(), context); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + assertThat(contentOf(logFile)).contains( + "Skipping sampling rate on environment variable configuration because of invalid value"); + } } @Test @@ -256,7 +272,7 @@ void shouldLogxRayTraceIdEnvVarSet() { } @Test - void shouldLogEventForHandler() throws IOException { + void shouldLogEventForHandlerWithLogEventAnnotation() { requestHandler = new PowertoolsLogEvent(); requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); @@ -265,6 +281,21 @@ void shouldLogEventForHandler() throws IOException { assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); } + @Test + void shouldLogEventForHandlerWithLogEventEnvVar() { + requestHandler = new PowertoolsLogEnabled(); + + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_LOGGER_LOG_EVENT")) + .thenReturn("true"); + + requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); + } + } + @Test void shouldLogEventForStreamHandler() throws IOException { requestStreamHandler = new PowertoolsLogEventForStream(); From 988d46e7e8778af5dea312c512280b392d748a41 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 11:40:38 +0200 Subject: [PATCH 34/74] load env var at init --- .../logging/internal/LambdaLoggingAspect.java | 8 +- .../internal/LambdaLoggingAspectTest.java | 113 ++++++++++++------ 2 files changed, 83 insertions(+), 38 deletions(-) diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index ff2ee72ff..3f53ef67d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -56,7 +56,6 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.slf4j.event.Level; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; import software.amazon.lambda.powertools.utilities.JsonConfig; @@ -69,6 +68,9 @@ public final class LambdaLoggingAspect { private static final Random SAMPLER = new Random(); private static final String LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); private static final LoggingManager loggingManager; + + private static String LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ + private static String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ static { @@ -187,7 +189,7 @@ public Object around(ProceedingJoinPoint pjp, getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); - if (logging.logEvent() || "true".equals(SystemWrapper.getenv("POWERTOOLS_LOGGER_LOG_EVENT"))) { + if (logging.logEvent() || "true".equals(LOG_EVENT)) { proceedArgs = logEvent(pjp); } @@ -237,7 +239,7 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, } private double samplingRate(final Logging logging) { - String sampleRate = SystemWrapper.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); + String sampleRate = SAMPLING_RATE; if (null != sampleRate) { try { return Double.parseDouble(sampleRate); diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index b7df0118f..d7d6b1775 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -93,6 +93,8 @@ void setUp() throws IllegalAccessException, NoSuchMethodException, InvocationTar requestHandler = new PowertoolsLogEnabled(); requestStreamHandler = new PowertoolsLogEnabledForStream(); resetLogLevel(Level.INFO); + writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", null, true); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true); try { FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); } catch (NoSuchFileException e) { @@ -186,58 +188,66 @@ void shouldClearStateWhenClearStateIsTrue() { @Test void shouldLogDebugWhenSamplingEqualsOne() { PowertoolsLogSamplingEnabled handler = new PowertoolsLogSamplingEnabled(); + Boolean debugEnabled = handler.handleRequest(new Object(), context); + assertThat(debugEnabled).isTrue(); } @Test - void shouldLogDebugWhenSamplingEnvVarEqualsOne() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) - .thenReturn("1"); + void shouldLogDebugWhenSamplingEnvVarEqualsOne() throws IllegalAccessException { + // GIVEN + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "1", true); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains("Test debug event"); - } + // WHEN + handler.handleRequest(new Object(), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Test debug event"); } @Test - void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) - .thenReturn("42"); + void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() throws IllegalAccessException { + // GIVEN + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "42", true); - requestHandler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).doesNotContain("Test debug event"); - } + // WHEN + requestHandler.handleRequest(new Object(), context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); } @Test - void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_LOGGER_SAMPLE_RATE")) - .thenReturn("NotANumber"); + void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() throws IllegalAccessException { + // GIVEN + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "NotANumber", true); + // WHEN requestHandler.handleRequest(new Object(), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).doesNotContain("Test debug event"); - assertThat(contentOf(logFile)).contains( - "Skipping sampling rate on environment variable configuration because of invalid value"); - } + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain("Test debug event"); + assertThat(contentOf(logFile)).contains( + "Skipping sampling rate on environment variable configuration because of invalid value"); } @Test void shouldNotLogDebugWhenSamplingEqualsZero() { PowertoolsLogSamplingDisabled handler = new PowertoolsLogSamplingDisabled(); + Boolean debugEnabled = handler.handleRequest(new Object(), context); + assertThat(debugEnabled).isFalse(); } @Test void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { + // GIVEN PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); handler.anotherMethod(); @@ -247,9 +257,13 @@ void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { @Test void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { + // GIVEN writeStaticField(LambdaHandlerProcessor.class, "SERVICE_NAME", "testService", true); + + // WHEN requestHandler.handleRequest(new Object(), context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE) .containsEntry(SERVICE.getName(), "testService"); @@ -257,14 +271,17 @@ void shouldLogServiceNameWhenEnvVarSet() throws IllegalAccessException { @Test void shouldLogxRayTraceIdEnvVarSet() { + // GIVEN String xRayTraceId = "1-5759e988-bd862e3fe1be46a994272793"; try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) .thenReturn("Root=1-5759e988-bd862e3fe1be46a994272793;Parent=53995c3f42cd8ad8;Sampled=1\""); + // WHEN requestHandler.handleRequest(new Object(), context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry(FUNCTION_TRACE_ID.getName(), xRayTraceId); @@ -273,35 +290,41 @@ void shouldLogxRayTraceIdEnvVarSet() { @Test void shouldLogEventForHandlerWithLogEventAnnotation() { + // GIVEN requestHandler = new PowertoolsLogEvent(); + // WHEN requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); + // THEN File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); } @Test - void shouldLogEventForHandlerWithLogEventEnvVar() { + void shouldLogEventForHandlerWithLogEventEnvVar() throws IllegalAccessException { + // GIVEN + writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", "true", true); requestHandler = new PowertoolsLogEnabled(); - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_LOGGER_LOG_EVENT")) - .thenReturn("true"); - - requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); + // WHEN + requestHandler.handleRequest(Collections.singletonList("ListOfOneElement"), context); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); - } + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); } @Test void shouldLogEventForStreamHandler() throws IOException { + // GIVEN requestStreamHandler = new PowertoolsLogEventForStream(); ByteArrayOutputStream output = new ByteArrayOutputStream(); + + // WHEN requestStreamHandler.handleRequest(new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(Collections.singletonMap("key", "value"))), output, context); + // THEN assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) .isNotEmpty(); @@ -312,9 +335,13 @@ void shouldLogEventForStreamHandler() throws IOException { @ParameterizedTest @Event(value = "apiGatewayProxyEventV1.json", type = APIGatewayProxyRequestEvent.class) void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestEvent event) { + // GIVEN RequestHandler handler = new PowertoolsLogApiGatewayRestApiCorrelationId(); + + // WHEN handler.handleRequest(event, context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); @@ -323,9 +350,13 @@ void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestE @ParameterizedTest @Event(value = "apiGatewayProxyEventV2.json", type = APIGatewayV2HTTPEvent.class) void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) { + // GIVEN RequestHandler handler = new PowertoolsLogApiGatewayHttpApiCorrelationId(); + + // WHEN handler.handleRequest(event, context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getRequestContext().getRequestId()); @@ -334,9 +365,13 @@ void shouldLogCorrelationIdOnAPIGatewayV2HTTPEvent(APIGatewayV2HTTPEvent event) @ParameterizedTest @Event(value = "albEvent.json", type = ApplicationLoadBalancerRequestEvent.class) void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) { + // GIVEN RequestHandler handler = new PowertoolsLogAlbCorrelationId(); + + // WHEN handler.handleRequest(event, context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", event.getHeaders().get("x-amzn-trace-id")); @@ -344,12 +379,16 @@ void shouldLogCorrelationIdOnALBEvent(ApplicationLoadBalancerRequestEvent event) @Test void shouldLogCorrelationIdOnStreamHandler() throws IOException { + // GIVEN RequestStreamHandler handler = new PowertoolsLogEventBridgeCorrelationId(); String eventId = "3"; String event = "{\"id\":" + eventId + "}"; // CorrelationIdPath.EVENT_BRIDGE ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + + // WHEN handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", eventId); @@ -357,12 +396,16 @@ void shouldLogCorrelationIdOnStreamHandler() throws IOException { @Test void shouldLogCorrelationIdOnAppSyncEvent() throws IOException { + // GIVEN RequestStreamHandler handler = new PowertoolsLogAppSyncCorrelationId(); String eventId = "456"; String event = "{\"request\":{\"headers\":{\"x-amzn-trace-id\":" + eventId + "}}}"; // CorrelationIdPath.APPSYNC_RESOLVER ByteArrayInputStream inputStream = new ByteArrayInputStream(event.getBytes()); + + // WHEN handler.handleRequest(inputStream, new ByteArrayOutputStream(), context); + // THEN assertThat(MDC.getCopyOfContextMap()) .hasSize(EXPECTED_CONTEXT_SIZE + 1) .containsEntry("correlation_id", eventId); From 4a5d790079d48596e43dd3f36df2ca272bde74c2 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 12:14:08 +0200 Subject: [PATCH 35/74] remove junit-pioneer and mock env var --- .../powertools-logging-log4j/pom.xml | 5 -- .../json/resolver/PowertoolsResolver.java | 3 +- .../PowerToolsResolverFactoryTest.java | 49 ++++++++++++------- .../json/resolver/PowertoolsResolverTest.java | 15 ++++-- .../internal/LambdaLoggingAspectTest.java | 23 ++++++--- .../powertools-logging-logback/pom.xml | 5 -- .../internal/LambdaEcsEncoderTest.java | 26 ++++++---- .../internal/LambdaJsonEncoderTest.java | 30 +++++++----- .../internal/LambdaLoggingAspectTest.java | 23 ++++++--- .../logging/internal/LambdaLoggingAspect.java | 2 +- 10 files changed, 112 insertions(+), 69 deletions(-) diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 3a9157271..3b14a0c66 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -117,11 +117,6 @@ jsonassert test - - org.junit-pioneer - junit-pioneer - test - diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java index a02173c6b..327ed615b 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java @@ -18,6 +18,7 @@ import org.apache.logging.log4j.layout.template.json.util.JsonWriter; import org.apache.logging.log4j.util.ReadOnlyStringMap; import software.amazon.lambda.powertools.common.internal.LambdaConstants; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; /** @@ -131,7 +132,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver REGION_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> - jsonWriter.writeString(System.getenv(LambdaConstants.AWS_REGION_ENV)); + jsonWriter.writeString(SystemWrapper.getenv(LambdaConstants.AWS_REGION_ENV)); private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() { @Override diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java index 29e063382..1f5c858af 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -17,8 +17,10 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import com.amazonaws.services.lambda.runtime.Context; import java.io.File; @@ -31,10 +33,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; +import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @Order(2) @@ -56,6 +60,7 @@ void setUp() throws IllegalAccessException, IOException { } catch (NoSuchFileException e) { // file may not exist on the first launch } + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true); } @AfterEach @@ -65,28 +70,38 @@ void cleanUp() throws IOException{ } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001") - @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") - void shouldLogInJsonFormat() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); + void shouldLogInJsonFormat() throws IllegalAccessException { + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) + .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains( - "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") - .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); + + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") + .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } } @Test - @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1") - @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") void shouldLogInEcsFormat() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) + .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); + mocked.when(() -> getenv("AWS_REGION")) + .thenReturn("eu-central-1"); - File logFile = new File("target/ecslogfile.json"); - assertThat(contentOf(logFile)).contains( - "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":1024,\"faas.execution\":\"RequestId\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":1024,\"faas.execution\":\"RequestId\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } } private void setupContext() { diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java index 89380d703..bbe6f6b75 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java @@ -15,6 +15,8 @@ package org.apache.logging.log4j.layout.template.json.resolver; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; @@ -29,7 +31,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.mockito.MockedStatic; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; class PowertoolsResolverTest { @@ -68,10 +71,14 @@ void shouldResolveAccountId() { } @Test - @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-2") void shouldResolveRegion() { - String result = resolveField("region", "dummy, will use the env var"); - assertThat(result).isEqualTo("\"eu-central-2\""); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("AWS_REGION")) + .thenReturn("eu-central-2"); + + String result = resolveField("region", "dummy, will use the env var"); + assertThat(result).isEqualTo("\"eu-central-2\""); + } } private static String resolveField(String field, String value) { diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index f7563e2de..3f3272aa6 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -16,8 +16,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import com.amazonaws.services.lambda.runtime.Context; import java.io.File; @@ -30,9 +32,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @Order(1) @@ -60,15 +63,19 @@ void cleanUp() throws IOException { } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLog4j") void testSlf4jBinding() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) + .thenReturn("testLog4j"); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)) - .contains("slf4j.binding is set to org.apache.logging.slf4j.SLF4JServiceProvider") - .contains("Loading software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("slf4j.binding is set to org.apache.logging.slf4j.SLF4JServiceProvider") + .contains("Loading software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"); + } } private void setupContext() { diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 1fb473962..1628428b4 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -113,11 +113,6 @@ jsonassert test - - org.junit-pioneer - junit-pioneer - test - diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index 908c5fa40..b28d83a3e 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -17,8 +17,10 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -36,11 +38,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.LambdaEcsEncoder; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -73,15 +76,20 @@ void cleanUp() throws IOException{ } @Test - @SetEnvironmentVariable(key = "AWS_REGION", value = "eu-central-1") - @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") void shouldLogInEcsFormat() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/ecslogfile.json"); - assertThat(contentOf(logFile)).contains( - "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) + .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); + mocked.when(() -> getenv("AWS_REGION")) + .thenReturn("eu-central-1"); + + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); + } } private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index c2d11e162..661ec879b 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -17,8 +17,10 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -39,11 +41,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.LambdaJsonEncoder; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -59,6 +62,7 @@ void setUp() throws IllegalAccessException, IOException { openMocks(this); MDC.clear(); writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true); setupContext(); // Make sure file is cleaned up before running tests try { @@ -74,19 +78,23 @@ void cleanUp() throws IOException{ } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_LOGGER_SAMPLE_RATE", value = "0.000000001") - @SetEnvironmentVariable(key = "_X_AMZN_TRACE_ID", value = "Root=1-63441c4a-abcdef012345678912345678") - void shouldLogInJsonFormat() { + void shouldLogInJsonFormat() throws IllegalAccessException { // GIVEN - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) + .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); - // WHEN - handler.handleRequest("Input", context); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - // THEN - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains( - "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); + // WHEN + handler.handleRequest("Input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); + } } private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 982ee86b7..85615c4a6 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -16,8 +16,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import com.amazonaws.services.lambda.runtime.Context; import java.io.File; @@ -30,9 +32,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.slf4j.MDC; +import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @Order(1) @@ -60,15 +63,19 @@ void cleanUp() throws IOException { } @Test - @SetEnvironmentVariable(key = "POWERTOOLS_SERVICE_NAME", value = "testLogback") void testSlf4jBinding() { - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); + try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { + mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) + .thenReturn("testLogback"); - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)) - .contains("slf4j.binding is set to ch.qos.logback.classic.spi.LogbackServiceProvider") - .contains("Loading software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("slf4j.binding is set to ch.qos.logback.classic.spi.LogbackServiceProvider") + .contains("Loading software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"); + } } private void setupContext() { diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 3f53ef67d..adf4d8615 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -70,7 +70,7 @@ public final class LambdaLoggingAspect { private static final LoggingManager loggingManager; private static String LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ - private static String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ + public static String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ static { From 0853b88e442c44796012b083f1dcaa2e5e5e2786 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 13:11:45 +0200 Subject: [PATCH 36/74] sampling rate should be final, maybe --- spotbugs-exclude.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 2b75444c9..ce6dfb803 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -80,6 +80,15 @@ + + + + + + + + + From f6dcad82246afb12fed86b78770e3571e102fb0b Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 22 Sep 2023 13:15:27 +0200 Subject: [PATCH 37/74] sonar lint --- .../powertools/logging/internal/LambdaEcsSerializer.java | 4 +++- .../powertools/logging/internal/LambdaJsonSerializer.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java index 6bdc96221..fabccf18b 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java @@ -59,6 +59,8 @@ public class LambdaEcsSerializer { protected static final String FUNCTION_MEMORY_ATTR_NAME = "faas.memory"; protected static final String FUNCTION_TRACE_ID_ATTR_NAME = "trace.id"; + private LambdaEcsSerializer() {} + public static void serializeObjectStart(StringBuilder builder) { builder.append('{'); } @@ -97,7 +99,7 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, - formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + formattedMessage.replace("\"", Matcher.quoteReplacement("\\\""))); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index c7738340c..7ac64f546 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -44,6 +44,7 @@ public class LambdaJsonSerializer { protected static final String EXCEPTION_STACK_ATTR_NAME = "stack"; protected static final String EXCEPTION_ATTR_NAME = "error"; + private LambdaJsonSerializer() {} public static void serializeObjectStart(StringBuilder builder) { builder.append('{'); @@ -83,7 +84,7 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, - formattedMessage.replaceAll("\"", Matcher.quoteReplacement("\\\""))); + formattedMessage.replace("\"", Matcher.quoteReplacement("\\\""))); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { From 7ef955b53a0c5287aa286c221a1ca0ff05ba45d6 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 27 Sep 2023 11:56:30 +0200 Subject: [PATCH 38/74] remove slf4j binding (does not improve perfs) --- pom.xml | 4 +- .../powertools-logging-log4j/pom.xml | 8 -- .../PowerToolsResolverFactoryTest.java | 6 +- .../internal/LambdaLoggingAspectTest.java | 89 ------------------- .../powertools-logging-logback/pom.xml | 8 -- .../internal/LambdaLoggingAspectTest.java | 89 ------------------- .../logging/internal/LambdaLoggingAspect.java | 50 +---------- 7 files changed, 8 insertions(+), 246 deletions(-) delete mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java delete mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java diff --git a/pom.xml b/pom.xml index 01b0a6de7..72c67c4db 100644 --- a/pom.xml +++ b/pom.xml @@ -74,8 +74,8 @@ 1.8 1.8 2.20.0 - 2.0.4 - 2.15.3 + 2.0.7 + 2.15.2 1.9.7 2.21.0 2.14.0 diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 3b14a0c66..28d2ec9f6 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -151,14 +151,6 @@ - - maven-surefire-plugin - - - org.apache.logging.slf4j.SLF4JServiceProvider - - - org.apache.maven.plugins maven-checkstyle-plugin diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java index 1f5c858af..b48368d86 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -41,7 +41,7 @@ import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; -@Order(2) +@Order(1) class PowerToolsResolverFactoryTest { @Mock @@ -74,6 +74,8 @@ void shouldLogInJsonFormat() throws IllegalAccessException { try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); + mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) + .thenReturn("testLog4j"); writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); @@ -94,6 +96,8 @@ void shouldLogInEcsFormat() { .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); mocked.when(() -> getenv("AWS_REGION")) .thenReturn("eu-central-1"); + mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) + .thenReturn("testLog4j"); PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); handler.handleRequest("Input", context); diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java deleted file mode 100644 index 3f3272aa6..000000000 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.contentOf; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; - -import com.amazonaws.services.lambda.runtime.Context; -import java.io.File; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.NoSuchFileException; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.slf4j.MDC; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; - -@Order(1) -class LambdaLoggingAspectTest { - - @Mock - private Context context; - - @BeforeEach - void setUp() throws IOException { - openMocks(this); - MDC.clear(); - setupContext(); - //Make sure file is cleaned up before running full stack logging regression - try { - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - } catch (NoSuchFileException e) { - // file might not be there for the first run - } - } - - @AfterEach - void cleanUp() throws IOException { - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - } - - @Test - void testSlf4jBinding() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) - .thenReturn("testLog4j"); - - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)) - .contains("slf4j.binding is set to org.apache.logging.slf4j.SLF4JServiceProvider") - .contains("Loading software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"); - } - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn( - "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(1024); - when(context.getAwsRequestId()).thenReturn("RequestId"); - } -} \ No newline at end of file diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 1628428b4..55a4a7627 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -151,14 +151,6 @@ org.apache.maven.plugins maven-checkstyle-plugin - - maven-surefire-plugin - - - ch.qos.logback.classic.spi.LogbackServiceProvider - - - diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java deleted file mode 100644 index 85615c4a6..000000000 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2023 Amazon.com, Inc. or its affiliates. - * 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 software.amazon.lambda.powertools.logging.internal; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.contentOf; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; - -import com.amazonaws.services.lambda.runtime.Context; -import java.io.File; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.NoSuchFileException; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.slf4j.MDC; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; - -@Order(1) -class LambdaLoggingAspectTest { - - @Mock - private Context context; - - @BeforeEach - void setUp() throws IOException { - openMocks(this); - MDC.clear(); - setupContext(); - //Make sure file is cleaned up before running full stack logging regression - try { - FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - } catch (NoSuchFileException e) { - // file might not be there for the first run - } - } - - @AfterEach - void cleanUp() throws IOException { -// FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); - } - - @Test - void testSlf4jBinding() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) - .thenReturn("testLogback"); - - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)) - .contains("slf4j.binding is set to ch.qos.logback.classic.spi.LogbackServiceProvider") - .contains("Loading software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"); - } - } - - private void setupContext() { - when(context.getFunctionName()).thenReturn("testFunction"); - when(context.getInvokedFunctionArn()).thenReturn( - "arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1"); - when(context.getFunctionVersion()).thenReturn("1"); - when(context.getMemoryLimitInMB()).thenReturn(1024); - when(context.getAwsRequestId()).thenReturn("RequestId"); - } -} \ No newline at end of file diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index adf4d8615..3680d001e 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -74,7 +74,7 @@ public final class LambdaLoggingAspect { private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ static { - loggingManager = loadLoggingManager(); + loggingManager = getLoggingManagerFromServiceLoader(); LEVEL_AT_INITIALISATION = loggingManager.getLogLevel(LOG); @@ -83,54 +83,6 @@ public final class LambdaLoggingAspect { } } - private static LoggingManager loadLoggingManager() { - return getLoggingManagerFromSlf4jBinding().orElse(getLoggingManagerFromServiceLoader()); - } - - /** - * To avoid Service Loader loading of the log provider, SLF4j provides a system property 'slf4j.binding' - * that users can set. If this is set, we can also leverage it and avoid Service Loader for the {@link LoggingManager}. - *

    In Lambda, system properties can be set with JAVA_TOOL_OPTIONS: - *

      - *
    • JAVA_TOOL_OPTIONS="-Dslf4j.binding=ch.qos.logback.classic.spi.LogbackServiceProvider"
    • - *
    • JAVA_TOOL_OPTIONS="-Dslf4j.binding=org.apache.logging.slf4j.SLF4JServiceProvider"
    • - *
    - *

    - * - * @return an instance of {@link LoggingManager} or null - */ - private static Optional getLoggingManagerFromSlf4jBinding() { - LoggingManager loggingManager = null; - - String slf4jBinding = System.getProperty("slf4j.binding"); - if (slf4jBinding != null) { - LOG.debug("slf4j.binding is set to {}", slf4jBinding); - try { - if ("org.apache.logging.slf4j.SLF4JServiceProvider".equals(slf4jBinding)) { - String managerClass = "software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager"; - LOG.debug("Loading {}", managerClass); - Class log4jManagerClass = LambdaLoggingAspect.class.getClassLoader() - .loadClass(managerClass); - loggingManager = (LoggingManager) log4jManagerClass.newInstance(); - } else if ("ch.qos.logback.classic.spi.LogbackServiceProvider".equals(slf4jBinding)) { - String managerClass = "software.amazon.lambda.powertools.logging.internal.LogbackLoggingManager"; - LOG.debug("Loading {}", managerClass); - Class log4backManagerClass = LambdaLoggingAspect.class.getClassLoader() - .loadClass(managerClass); - loggingManager = (LoggingManager) log4backManagerClass.newInstance(); - } else { - LOG.warn("slf4j.binding {} not supported, fallback to Service Loader. " - + "Only log4j and logback are supported.", slf4jBinding); - } - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - LOG.warn("Could not instantiate LoggingManager based on slf4j.binding {}, " - + "make sure to add either powertools-logging-log4j or powertools-logging-logback module", - slf4jBinding); - } - } - return Optional.ofNullable(loggingManager); - } - /** * Use {@link ServiceLoader} to lookup for a {@link LoggingManager}. * A file software.amazon.lambda.powertools.logging.internal.LoggingManager must be created in From cdc07a563b5b60e668031fe54431816cbf5f84e0 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 12 Oct 2023 13:40:26 +0200 Subject: [PATCH 39/74] fix unit tests (env var) --- .../powertools-logging-log4j/pom.xml | 12 +++++ .../PowerToolsResolverFactoryTest.java | 44 +++++-------------- .../powertools-logging-logback/pom.xml | 12 +++++ .../internal/LambdaEcsEncoderTest.java | 25 +++-------- .../internal/LambdaJsonEncoderTest.java | 24 ++++------ 5 files changed, 51 insertions(+), 66 deletions(-) diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 28d2ec9f6..3398466aa 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -155,6 +155,18 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + testLog4j + eu-central-1 + <_X_AMZN_TRACE_ID>Root=1-63441c4a-abcdef012345678912345678 + + + diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java index b48368d86..d9cd79828 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -17,10 +17,8 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import com.amazonaws.services.lambda.runtime.Context; import java.io.File; @@ -34,10 +32,8 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.MockedStatic; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -71,41 +67,25 @@ void cleanUp() throws IOException{ @Test void shouldLogInJsonFormat() throws IllegalAccessException { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) - .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); - mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) - .thenReturn("testLog4j"); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains( - "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") - .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); - } + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") + .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } @Test void shouldLogInEcsFormat() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) - .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); - mocked.when(() -> getenv("AWS_REGION")) - .thenReturn("eu-central-1"); - mocked.when(() -> getenv("POWERTOOLS_SERVICE_NAME")) - .thenReturn("testLog4j"); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/ecslogfile.json"); - assertThat(contentOf(logFile)).contains( - "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":1024,\"faas.execution\":\"RequestId\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); - } + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLog4j\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-central-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":1024,\"faas.execution\":\"RequestId\",\"faas.coldstart\":true,\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } private void setupContext() { diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 55a4a7627..c11dd2aed 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -151,6 +151,18 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + testLogback + eu-central-1 + <_X_AMZN_TRACE_ID>Root=1-63441c4a-abcdef012345678912345678 + + + diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java index b28d83a3e..9aef1d88d 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsEncoderTest.java @@ -17,10 +17,8 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -39,11 +37,9 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.MockedStatic; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.LambdaEcsEncoder; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -72,24 +68,17 @@ void setUp() throws IllegalAccessException, IOException { @AfterEach void cleanUp() throws IOException{ -// FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); + FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close(); } @Test void shouldLogInEcsFormat() { - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) - .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); - mocked.when(() -> getenv("AWS_REGION")) - .thenReturn("eu-central-1"); - - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - handler.handleRequest("Input", context); - - File logFile = new File("target/ecslogfile.json"); - assertThat(contentOf(logFile)).contains( - "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); - } + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + handler.handleRequest("Input", context); + + File logFile = new File("target/ecslogfile.json"); + assertThat(contentOf(logFile)).contains( + "\"ecs.version\":\"1.2.0\",\"log.level\":\"DEBUG\",\"message\":\"Test debug event\",\"service.name\":\"testLogback\",\"service.version\":\"1\",\"log.logger\":\"software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled\",\"process.thread.name\":\"main\",\"cloud.provider\":\"aws\",\"cloud.service.name\":\"lambda\",\"cloud.region\":\"eu-west-1\",\"cloud.account.id\":\"012345678910\",\"faas.id\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"faas.name\":\"testFunction\",\"faas.version\":\"1\",\"faas.memory\":\"1024\",\"faas.execution\":\"RequestId\",\"faas.coldstart\":\"true\",\"trace.id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 661ec879b..bd36ae069 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -17,10 +17,8 @@ import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; -import static software.amazon.lambda.powertools.common.internal.SystemWrapper.getenv; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -42,11 +40,9 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import org.mockito.MockedStatic; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; import software.amazon.lambda.powertools.logging.LambdaJsonEncoder; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -80,21 +76,17 @@ void cleanUp() throws IOException{ @Test void shouldLogInJsonFormat() throws IllegalAccessException { // GIVEN - try (MockedStatic mocked = mockStatic(SystemWrapper.class)) { - mocked.when(() -> getenv("_X_AMZN_TRACE_ID")) - .thenReturn("Root=1-63441c4a-abcdef012345678912345678"); - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); + writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); - PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); - // WHEN - handler.handleRequest("Input", context); + // WHEN + handler.handleRequest("Input", context); - // THEN - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains( - "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); - } + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains( + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); } private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); From 87bd0c2793ff9f74658876ede6b5e237888adeb9 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 26 Oct 2023 21:11:28 +0200 Subject: [PATCH 40/74] remove mockito --- powertools-logging/powertools-logging-log4j/pom.xml | 10 ---------- powertools-logging/powertools-logging-logback/pom.xml | 10 ---------- 2 files changed, 20 deletions(-) diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 3398466aa..8f1ea9a56 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -82,16 +82,6 @@ commons-lang3 test - - org.mockito - mockito-core - test - - - org.mockito - mockito-inline - test - org.aspectj aspectjweaver diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index c11dd2aed..e4833389e 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -78,16 +78,6 @@ commons-lang3 test - - org.mockito - mockito-core - test - - - org.mockito - mockito-inline - test - org.aspectj aspectjweaver From 5ffa5bde65ea7d408d21f94c3dd7982626ebf52f Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 26 Oct 2023 22:58:14 +0200 Subject: [PATCH 41/74] fix logging examples --- .../powertools-examples-core-utilities/gradle/build.gradle | 6 +++--- .../kotlin/src/main/kotlin/helloworld/App.kt | 5 +++-- .../powertools-examples-core-utilities/serverless/pom.xml | 4 ++-- .../powertools-examples-core-utilities/terraform/pom.xml | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index 06aec59e8..307c7c8df 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -27,8 +27,8 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' - aspect 'software.amazon.lambda:powertools-tracing:1.17.0' - aspect 'software.amazon.lambda:powertools-logging:1.17.0' - aspect 'software.amazon.lambda:powertools-metrics:1.17.0' + aspect 'software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT' + aspect 'software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT' + aspect 'software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT' } diff --git a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt index ed4cf267a..017247f5b 100644 --- a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt +++ b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt @@ -18,7 +18,8 @@ import com.amazonaws.services.lambda.runtime.RequestHandler import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent import com.amazonaws.xray.entities.Subsegment -import org.apache.logging.log4j.LogManager +import org.slf4j.Logger +import org.slf4j.LoggerFactory import software.amazon.cloudwatchlogs.emf.logger.MetricsLogger import software.amazon.cloudwatchlogs.emf.model.DimensionSet import software.amazon.cloudwatchlogs.emf.model.Unit @@ -92,5 +93,5 @@ class App : RequestHandler software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} @@ -48,7 +48,7 @@ org.apache.logging.log4j - log4j-api + log4j-slf4j2-impl ${log4j.version} diff --git a/examples/powertools-examples-core-utilities/terraform/pom.xml b/examples/powertools-examples-core-utilities/terraform/pom.xml index d74ce7085..91cfcbd24 100644 --- a/examples/powertools-examples-core-utilities/terraform/pom.xml +++ b/examples/powertools-examples-core-utilities/terraform/pom.xml @@ -23,7 +23,7 @@ software.amazon.lambda - powertools-logging + powertools-logging-log4j ${project.version} @@ -48,7 +48,7 @@ org.apache.logging.log4j - log4j-api + log4j-slf4j2-impl ${log4j.version} From c1b8e66ed412314af9eb3e85d5aca67c4661bd6f Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 26 Oct 2023 23:17:06 +0200 Subject: [PATCH 42/74] fix logging examples #2 --- examples/powertools-examples-core-utilities/gradle/build.gradle | 1 + .../powertools-examples-core-utilities/kotlin/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index 307c7c8df..9f7fad9b3 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -18,6 +18,7 @@ compileJava { } repositories { + mavenLocal() mavenCentral() } diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index 29a29b0c4..d1e4397d5 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.amazonaws:aws-lambda-java-events:3.11.0") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2") aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") - aspect("software.amazon.lambda:powertools-logging:2.0.0-SNAPSHOT") + aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT") testImplementation("junit:junit:4.13.2") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") From 3462c8f5a9c72d307f775945238923fb58239ca0 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 26 Oct 2023 23:42:13 +0200 Subject: [PATCH 43/74] fix logging examples #3 --- .../kotlin/src/main/kotlin/helloworld/App.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt index 017247f5b..1c925d4f4 100644 --- a/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt +++ b/examples/powertools-examples-core-utilities/kotlin/src/main/kotlin/helloworld/App.kt @@ -93,5 +93,5 @@ class App : RequestHandler Date: Fri, 27 Oct 2023 09:37:03 +0200 Subject: [PATCH 44/74] fix logging examples #4 --- .../kotlin/build.gradle.kts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index d1e4397d5..ad74311d3 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -17,7 +17,6 @@ dependencies { aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT") - testImplementation("junit:junit:4.13.2") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") } @@ -27,12 +26,6 @@ tasks.compileKotlin { } } -tasks.compileTestKotlin { - kotlinOptions { - jvmTarget = "1.8" - } -} - // If using JDK 11 or higher, use the following instead: //kotlin { // jvmToolchain(11) From bb6f87e6f42989631d335ec7f79779f557cbe2dd Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 27 Oct 2023 09:51:44 +0200 Subject: [PATCH 45/74] fix logging examples #5 --- .../src/test/kotlin/helloworld/AppTest.kt | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 examples/powertools-examples-core-utilities/kotlin/src/test/kotlin/helloworld/AppTest.kt diff --git a/examples/powertools-examples-core-utilities/kotlin/src/test/kotlin/helloworld/AppTest.kt b/examples/powertools-examples-core-utilities/kotlin/src/test/kotlin/helloworld/AppTest.kt deleted file mode 100644 index 8aae081e8..000000000 --- a/examples/powertools-examples-core-utilities/kotlin/src/test/kotlin/helloworld/AppTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package helloworld - - -import org.junit.Assert -import org.junit.Test - -class AppTest { - @Test - fun successfulResponse() { - val app = App() - val result = app.handleRequest(null, null) - Assert.assertEquals(200, result.statusCode.toLong()) - Assert.assertEquals("application/json", result.headers["Content-Type"]) - val content = result.body - Assert.assertNotNull(content) - Assert.assertTrue(""""message"""" in content) - Assert.assertTrue(""""hello world"""" in content) - Assert.assertTrue(""""location"""" in content) - } -} From d94a0c5bbc1380743f12328071fad16e892c65ec Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 24 Nov 2023 11:10:17 +0100 Subject: [PATCH 46/74] add more tests --- .../logging/internal/LambdaLoggingAspect.java | 44 ++-- .../logging/internal/LoggingConstants.java | 10 +- .../handlers/PowertoolsLogEnabled.java | 2 + ...d.java => PowertoolsLogEventDisabled.java} | 2 +- .../internal/LambdaLoggingAspectTest.java | 236 +++++++++++++----- 5 files changed, 212 insertions(+), 82 deletions(-) rename powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/{PowerToolLogEventDisabled.java => PowertoolsLogEventDisabled.java} (92%) diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index eff420935..93365ea8d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -27,6 +27,9 @@ import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_EVENT; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_LEVEL; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_SAMPLING_RATE; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; @@ -67,26 +70,24 @@ @DeclarePrecedence("*, software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect") public final class LambdaLoggingAspect { private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspect.class); - private static final String POWERTOOLS_LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); - private static final Random SAMPLER = new Random(); - private static String SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ - - private static String LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ - - private static Level LEVEL_AT_INITIALISATION; + static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ - private static final LoggingManager loggingManager; + private static final LoggingManager LOGGING_MANAGER; static { - loggingManager = getLoggingManagerFromServiceLoader(); + LOGGING_MANAGER = getLoggingManagerFromServiceLoader(); - LEVEL_AT_INITIALISATION = loggingManager.getLogLevel(LOG); + setLogLevel(); + + LEVEL_AT_INITIALISATION = LOGGING_MANAGER.getLogLevel(LOG); + } + static void setLogLevel() { if (POWERTOOLS_LOG_LEVEL != null) { - Level powertoolsLevel = getLevel(POWERTOOLS_LOG_LEVEL); + Level powertoolsLevel = getLevelFromEnvironmentVariable(POWERTOOLS_LOG_LEVEL); if (LAMBDA_LOG_LEVEL != null) { - Level lambdaLevel = getLevel(LAMBDA_LOG_LEVEL); + Level lambdaLevel = getLevelFromEnvironmentVariable(LAMBDA_LOG_LEVEL); if (powertoolsLevel.toInt() < lambdaLevel.toInt()) { LOG.warn("Current log level ({}) does not match AWS Lambda Advanced Logging Controls minimum log level ({}). This can lead to data loss, consider adjusting them.", POWERTOOLS_LOG_LEVEL, LAMBDA_LOG_LEVEL); @@ -94,15 +95,16 @@ public final class LambdaLoggingAspect { } resetLogLevels(powertoolsLevel); } else if (LAMBDA_LOG_LEVEL != null) { - resetLogLevels(getLevel(LAMBDA_LOG_LEVEL)); + resetLogLevels(getLevelFromEnvironmentVariable(LAMBDA_LOG_LEVEL)); } } - private static Level getLevel(String level) { - if (Arrays.stream(Level.values()).anyMatch(slf4jLevel -> slf4jLevel.name().equals(level))) { - return Level.valueOf(level); + private static Level getLevelFromEnvironmentVariable(String level) { + if (Arrays.stream(Level.values()).anyMatch(slf4jLevel -> slf4jLevel.name().equalsIgnoreCase(level))) { + return Level.valueOf(level.toUpperCase()); } else { - if ("FATAL".equals(level)) { + // FATAL does not exist in slf4j + if ("FATAL".equalsIgnoreCase(level)) { return Level.ERROR; } } @@ -142,7 +144,7 @@ private static LoggingManager getLoggingManagerFromServiceLoader() { } private static void resetLogLevels(Level logLevel) { - loggingManager.resetLogLevel(logLevel); + LOGGING_MANAGER.resetLogLevel(logLevel); } @SuppressWarnings({"EmptyMethod"}) @@ -170,7 +172,7 @@ public Object around(ProceedingJoinPoint pjp, getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); - if (logging.logEvent() || "true".equals(LOG_EVENT)) { + if (logging.logEvent() || "true".equals(POWERTOOLS_LOG_EVENT)) { proceedArgs = logEvent(pjp); } @@ -213,14 +215,14 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); - } else if (LEVEL_AT_INITIALISATION != loggingManager.getLogLevel(LOG)) { + } else if (LEVEL_AT_INITIALISATION != LOGGING_MANAGER.getLogLevel(LOG)) { resetLogLevels(LEVEL_AT_INITIALISATION); } } } private double samplingRate(final Logging logging) { - String sampleRate = SAMPLING_RATE; + String sampleRate = POWERTOOLS_SAMPLING_RATE; if (null != sampleRate) { try { return Double.parseDouble(sampleRate); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java index e58ca4109..301f40ba2 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java @@ -14,12 +14,14 @@ package software.amazon.lambda.powertools.logging.internal; -public class LoggingConstants { - public static final String LAMBDA_LOG_LEVEL = System.getenv("AWS_LAMBDA_LOG_LEVEL"); +class LoggingConstants { + static String LAMBDA_LOG_LEVEL = System.getenv("AWS_LAMBDA_LOG_LEVEL"); /* not final for test purpose */ - public static final String LAMBDA_LOG_FORMAT = System.getenv("AWS_LAMBDA_LOG_FORMAT"); + static String POWERTOOLS_LOG_LEVEL = System.getenv("POWERTOOLS_LOG_LEVEL"); /* not final for test purpose */ - public static final String LOG_DATE_RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + static String POWERTOOLS_SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ + + static String POWERTOOLS_LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ private LoggingConstants() { // constants diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java index 79c463405..5186ae290 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java @@ -26,6 +26,8 @@ public class PowertoolsLogEnabled implements RequestHandler { @Override @Logging public Object handleRequest(Object input, Context context) { + LOG.error("Test error event"); + LOG.warn("Test warn event"); LOG.info("Test event"); LOG.debug("Test debug event"); return null; diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventDisabled.java similarity index 92% rename from powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java rename to powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventDisabled.java index 00757ea7a..fc1feb52d 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowerToolLogEventDisabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEventDisabled.java @@ -18,7 +18,7 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; import software.amazon.lambda.powertools.logging.Logging; -public class PowerToolLogEventDisabled implements RequestHandler { +public class PowertoolsLogEventDisabled implements RequestHandler { @Logging @Override diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 469e3ad82..1838d9ac7 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -55,7 +55,6 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Collections; -import org.json.JSONException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -63,11 +62,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.mockito.Mock; import org.mockito.MockedStatic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.slf4j.event.Level; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowerToolLogEventDisabled; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventDisabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayHttpApiCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayRestApiCorrelationId; @@ -85,6 +86,7 @@ class LambdaLoggingAspectTest { + private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspectTest.class); private static final int EXPECTED_CONTEXT_SIZE = 8; private RequestStreamHandler requestStreamHandler; private RequestHandler requestHandler; @@ -101,8 +103,10 @@ void setUp() throws IllegalAccessException, NoSuchMethodException, InvocationTar requestHandler = new PowertoolsLogEnabled(); requestStreamHandler = new PowertoolsLogEnabledForStream(); resetLogLevel(Level.INFO); - writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", null, true); - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_SAMPLING_RATE", null, true); try { FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); } catch (NoSuchFileException e) { @@ -116,6 +120,137 @@ void cleanUp() throws IOException { FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); } + @Test + void shouldLogDebugWhenPowertoolsLevelEnvVarIsDebug() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "DEBUG", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isTrue(); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInfo() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INFO", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInvalid() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INVALID", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "WARN", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + } + + @Test + void shouldLogErrorWhenPowertoolsLevelEnvVarIsError() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "ERROR", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isFalse(); + assertThat(LOG.isErrorEnabled()).isTrue(); + } + + @Test + void shouldLogErrorWhenPowertoolsLevelEnvVarIsFatal() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "FATAL", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isFalse(); + assertThat(LOG.isErrorEnabled()).isTrue(); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarIsWarnAndLambdaLevelVarIsInfo() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "WARN", true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "INFO", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).doesNotContain(" does not match AWS Lambda Advanced Logging Controls minimum log level"); + } + + @Test + void shouldLogInfoWhenPowertoolsLevelEnvVarIsInfoAndLambdaLevelVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", "INFO", true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "WARN", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isTrue(); + File logFile = new File("target/logfile.json"); + // should log a warning as powertools level is lower than lambda level + assertThat(contentOf(logFile)).contains("Current log level (INFO) does not match AWS Lambda Advanced Logging Controls minimum log level (WARN). This can lead to data loss, consider adjusting them."); + } + + @Test + void shouldLogWarnWhenPowertoolsLevelEnvVarINotSetAndLambdaLevelVarIsWarn() throws IllegalAccessException { + // GIVEN + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); + writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", "WARN", true); + + // WHEN + LambdaLoggingAspect.setLogLevel(); + + // THEN + assertThat(LOG.isDebugEnabled()).isFalse(); + assertThat(LOG.isInfoEnabled()).isFalse(); + assertThat(LOG.isWarnEnabled()).isTrue(); + } + @Test void shouldSetLambdaContextWhenEnabled() { requestHandler.handleRequest(new Object(), context); @@ -150,7 +285,7 @@ void shouldSetLambdaContextForStreamHandlerWhenEnabled() throws IOException { } @Test - void shouldSetColdStartFlag() throws IOException { + void shouldSetColdStartFlagOnFirstCallNotOnSecondCall() throws IOException { requestStreamHandler.handleRequest(new ByteArrayInputStream(new byte[]{}), new ByteArrayOutputStream(), context); @@ -202,51 +337,10 @@ void shouldLogDebugWhenSamplingEqualsOne() { assertThat(debugEnabled).isTrue(); } - /** - * If POWERTOOLS_LOGGER_LOG_EVENT was set to true, the handler should log, despite @Logging(logEvent=false) - * - * @throws IOException - */ - @Test - void shouldLogEventForHandlerWhenEnvVariableSetToTrue() throws IOException, IllegalAccessException, JSONException { - try { - writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", "true", true); - - requestHandler = new PowerToolLogEventDisabled(); - - SQSEvent.SQSMessage message = new SQSEvent.SQSMessage(); - message.setBody("body"); - message.setMessageId("1234abcd"); - message.setAwsRegion("eu-west-1"); - - requestHandler.handleRequest(message, context); - - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains("\"body\":\"body\"").contains("\"messageId\":\"1234abcd\"").contains("\"awsRegion\":\"eu-west-1\""); - } finally { - writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", "false", true); - } - } - - /** - * If POWERTOOLS_LOGGER_LOG_EVENT was set to false and @Logging(logEvent=false), the handler shouldn't log - * - * @throws IOException - */ - @Test - void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException { - requestHandler = new PowerToolLogEventDisabled(); - - requestHandler.handleRequest(singletonList("ListOfOneElement"), context); - - Assertions.assertEquals(0, - Files.lines(Paths.get("target/logfile.json")).collect(joining()).length()); - } - @Test void shouldLogDebugWhenSamplingEnvVarEqualsOne() throws IllegalAccessException { // GIVEN - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "1", true); + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "1"; PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); // WHEN @@ -260,7 +354,7 @@ void shouldLogDebugWhenSamplingEnvVarEqualsOne() throws IllegalAccessException { @Test void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() throws IllegalAccessException { // GIVEN - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "42", true); + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "42"; // WHEN requestHandler.handleRequest(new Object(), context); @@ -271,9 +365,9 @@ void shouldNotLogDebugWhenSamplingEnvVarIsTooBig() throws IllegalAccessException } @Test - void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() throws IllegalAccessException { + void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() { // GIVEN - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "NotANumber", true); + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "NotANumber"; // WHEN requestHandler.handleRequest(new Object(), context); @@ -287,10 +381,14 @@ void shouldNotLogDebugWhenSamplingEnvVarIsInvalid() throws IllegalAccessExceptio @Test void shouldNotLogDebugWhenSamplingEqualsZero() { + // GIVEN + LoggingConstants.POWERTOOLS_SAMPLING_RATE = "0"; PowertoolsLogSamplingDisabled handler = new PowertoolsLogSamplingDisabled(); + // WHEN Boolean debugEnabled = handler.handleRequest(new Object(), context); + // THEN assertThat(debugEnabled).isFalse(); } @@ -299,8 +397,10 @@ void shouldHaveNoEffectIfNotUsedOnLambdaHandler() { // GIVEN PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); + // WHEN handler.anotherMethod(); + // THEN assertThat(MDC.getCopyOfContextMap()).isNull(); } @@ -369,17 +469,41 @@ void shouldLogEventForHandlerWithLogEventAnnotation() { } @Test - void shouldLogEventForHandlerWithLogEventEnvVar() throws IllegalAccessException { + void shouldLogEventForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_EVENT = "true"; + + requestHandler = new PowertoolsLogEnabled(); + + SQSEvent.SQSMessage message = new SQSEvent.SQSMessage(); + message.setBody("body"); + message.setMessageId("1234abcd"); + message.setAwsRegion("eu-west-1"); + + // WHEN + requestHandler.handleRequest(message, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("\"body\":\"body\"").contains("\"messageId\":\"1234abcd\"").contains("\"awsRegion\":\"eu-west-1\""); + } finally { + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", "false", true); + } + } + + @Test + void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException { // GIVEN - writeStaticField(LambdaLoggingAspect.class, "LOG_EVENT", "true", true); - requestHandler = new PowertoolsLogEnabled(); + LoggingConstants.POWERTOOLS_LOG_EVENT = "false"; // WHEN + requestHandler = new PowertoolsLogEventDisabled(); requestHandler.handleRequest(singletonList("ListOfOneElement"), context); // THEN - File logFile = new File("target/logfile.json"); - assertThat(contentOf(logFile)).contains("[\"ListOfOneElement\"]"); + Assertions.assertEquals(0, + Files.lines(Paths.get("target/logfile.json")).collect(joining()).length()); } @Test @@ -491,6 +615,6 @@ private void resetLogLevel(Level level) Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); resetLogLevels.setAccessible(true); resetLogLevels.invoke(null, level); - writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); + LambdaLoggingAspect.LEVEL_AT_INITIALISATION = level; } } \ No newline at end of file From 8a4f05e9576f51498302010cb9aa6bb100d2fc44 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 24 Nov 2023 13:13:54 +0100 Subject: [PATCH 47/74] refactor and cleanup --- pom.xml | 2 +- .../json/resolver/PowertoolsResolver.java | 70 +++++++++---------- .../src/main/resources/LambdaEcsLayout.json | 2 +- .../src/main/resources/LambdaJsonLayout.json | 2 +- .../PowerToolsResolverFactoryTest.java | 8 +-- .../powertools/logging/LambdaJsonEncoder.java | 4 +- .../internal/LambdaJsonSerializer.java | 4 +- .../internal/LambdaJsonEncoderTest.java | 7 +- 8 files changed, 44 insertions(+), 55 deletions(-) diff --git a/pom.xml b/pom.xml index a56630554..4ed136d76 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ 3.1.0 5.10.0 1.0.6 - 0.5.1 + 0.6.0 1.5.0 diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java index 327ed615b..0214e7c9c 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java @@ -14,6 +14,19 @@ package org.apache.logging.log4j.layout.template.json.resolver; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_NAME; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_REQUEST_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_VERSION; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.layout.template.json.util.JsonWriter; import org.apache.logging.log4j.util.ReadOnlyStringMap; @@ -47,7 +60,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private static final EventResolver FUNCTION_NAME_RESOLVER = (final LogEvent logEvent, final JsonWriter jsonWriter) -> { final String functionName = - logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_NAME.getName()); + logEvent.getContextData().getValue(FUNCTION_NAME.getName()); jsonWriter.writeString(functionName); }; @@ -168,48 +181,29 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { private final EventResolver internalResolver; + private static final Map eventResolverMap = Stream.of(new Object[][] { + { SERVICE.getName(), SERVICE_RESOLVER }, + { FUNCTION_NAME.getName(), FUNCTION_NAME_RESOLVER }, + { FUNCTION_VERSION.getName(), FUNCTION_VERSION_RESOLVER }, + { FUNCTION_ARN.getName(), FUNCTION_ARN_RESOLVER }, + { FUNCTION_MEMORY_SIZE.getName(), FUNCTION_MEMORY_RESOLVER }, + { FUNCTION_REQUEST_ID.getName(), FUNCTION_REQ_RESOLVER }, + { FUNCTION_COLD_START.getName(), COLD_START_RESOLVER }, + { FUNCTION_TRACE_ID.getName(), XRAY_TRACE_RESOLVER }, + { SAMPLING_RATE.getName(), SAMPLING_RATE_RESOLVER }, + { "region", REGION_RESOLVER }, + { "account_id", ACCOUNT_ID_RESOLVER }, + }).collect(Collectors.toMap(data -> (String) data[0], data -> (EventResolver) data[1])); + + PowertoolsResolver(final TemplateResolverConfig config) { final String fieldName = config.getString("field"); if (fieldName == null) { internalResolver = NON_POWERTOOLS_FIELD_RESOLVER; } else { - switch (fieldName) { - case "service": - internalResolver = SERVICE_RESOLVER; - break; - case "function_name": - internalResolver = FUNCTION_NAME_RESOLVER; - break; - case "function_version": - case "service_version": - internalResolver = FUNCTION_VERSION_RESOLVER; - break; - case "function_arn": - internalResolver = FUNCTION_ARN_RESOLVER; - break; - case "function_memory_size": - internalResolver = FUNCTION_MEMORY_RESOLVER; - break; - case "function_request_id": - internalResolver = FUNCTION_REQ_RESOLVER; - break; - case "cold_start": - internalResolver = COLD_START_RESOLVER; - break; - case "xray_trace_id": - internalResolver = XRAY_TRACE_RESOLVER; - break; - case "region": - internalResolver = REGION_RESOLVER; - break; - case "account_id": - internalResolver = ACCOUNT_ID_RESOLVER; - break; - case "sampling_rate": - internalResolver = SAMPLING_RATE_RESOLVER; - break; - default: - throw new IllegalArgumentException("unknown field: " + fieldName); + internalResolver = eventResolverMap.get(fieldName); + if (internalResolver == null) { + throw new IllegalArgumentException("unknown field: " + fieldName); } } } diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json index c92098a4a..7d7809cd3 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json @@ -36,7 +36,7 @@ }, "service.version": { "$resolver": "powertools", - "field": "service_version" + "field": "function_version" }, "log.logger": { "$resolver": "logger", diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json index fef17c68b..02ae09dc6 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json @@ -59,7 +59,7 @@ "timestamp": { "$resolver": "timestamp", "pattern": { - "format": "yyyy-MM-dd'T'HH:mm:ss.SSSZz" + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" } }, "xray_trace_id": { diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java index d9cd79828..4cf798a47 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowerToolsResolverFactoryTest.java @@ -34,7 +34,6 @@ import org.mockito.Mock; import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.logging.internal.LambdaLoggingAspect; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @Order(1) @@ -56,7 +55,6 @@ void setUp() throws IllegalAccessException, IOException { } catch (NoSuchFileException e) { // file may not exist on the first launch } - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true); } @AfterEach @@ -66,15 +64,13 @@ void cleanUp() throws IOException{ } @Test - void shouldLogInJsonFormat() throws IllegalAccessException { - writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true); - + void shouldLogInJsonFormat() { PowertoolsLogEnabled handler = new PowertoolsLogEnabled(); handler.handleRequest("Input", context); File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).contains( - "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"sampling_rate\":1.0E-9,\"service\":\"testLog4j\",\"timestamp\":") + "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":\"1\",\"service\":\"testLog4j\",\"timestamp\":") .contains("\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"myKey\":\"myValue\"}\n"); } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java index a007045de..db54ecf63 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -33,7 +33,7 @@ public class LambdaJsonEncoder extends EncoderBase { private final ThrowableProxyConverter throwableProxyConverter = new ThrowableProxyConverter(); protected ThrowableHandlingConverter throwableConverter = null; - protected String timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZz"; + protected String timestampFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; protected String timestampFormatTimezoneId = null; private boolean includeThreadInfo = false; private boolean includePowertoolsInfo = true; @@ -88,7 +88,7 @@ public byte[] footerBytes() { } /** - * Specify the format of the timestamp (default is yyyy-MM-dd'T'HH:mm:ss.SSSZz): + * Specify the format of the timestamp (default is yyyy-MM-dd'T'HH:mm:ss.SSS'Z'): *
    *
    {@code
          *     
    diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    index 7ac64f546..a8785f929 100644
    --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    @@ -114,7 +114,9 @@ public static void serializePowertools(StringBuilder builder, Map {
                 if ((PowertoolsLoggedFields.stringValues().contains(k) && includePowertoolsInfo)
                         || !PowertoolsLoggedFields.stringValues().contains(k)) {
    -                serializeAttribute(builder, k, v);
    +                if (!k.equals(PowertoolsLoggedFields.SAMPLING_RATE.getName()) || !v.equals("0.0")) { // do not log sampling rate when 0
    +                    serializeAttribute(builder, k, v);
    +                }
                 }
             });
         }
    diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java
    index bd36ae069..0b4f8c1bd 100644
    --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java
    +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java
    @@ -58,7 +58,6 @@ void setUp() throws IllegalAccessException, IOException {
             openMocks(this);
             MDC.clear();
             writeStaticField(LambdaHandlerProcessor.class, "IS_COLD_START", null, true);
    -        writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", null, true);
             setupContext();
             // Make sure file is cleaned up before running tests
             try {
    @@ -74,10 +73,8 @@ void cleanUp() throws IOException{
         }
     
         @Test
    -    void shouldLogInJsonFormat() throws IllegalAccessException {
    +    void shouldLogInJsonFormat() {
             // GIVEN
    -          writeStaticField(LambdaLoggingAspect.class, "SAMPLING_RATE", "0.000000001", true);
    -
             PowertoolsLogEnabled handler = new PowertoolsLogEnabled();
     
             // WHEN
    @@ -86,7 +83,7 @@ void shouldLogInJsonFormat() throws IllegalAccessException {
             // THEN
             File logFile = new File("target/logfile.json");
             assertThat(contentOf(logFile)).contains(
    -                        "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"sampling_rate\":\"1.0E-9\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":");
    +                        "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":");
         }
     
         private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null);
    
    From 7d9336baa2d8407abb3a76ddc3d73b9d14800429 Mon Sep 17 00:00:00 2001
    From: Jerome Van Der Linden 
    Date: Mon, 27 Nov 2023 13:16:51 +0100
    Subject: [PATCH 48/74] custom message resolver for json strings
    
    ---
     .../powertools-logging-log4j/pom.xml          |   3 -
     .../json/resolver/PowertoolsResolver.java     |  46 +++++++-
     .../src/main/resources/LambdaEcsLayout.json   |   4 +-
     .../src/main/resources/LambdaJsonLayout.json  |   4 +-
     .../PowertoolsMessageResolverTest.java        | 108 ++++++++++++++++++
     .../handler/PowertoolsJsonMessage.java        |  39 +++++++
     6 files changed, 196 insertions(+), 8 deletions(-)
     create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
     create mode 100644 powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    
    diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml
    index 8f1ea9a56..5a32c2db5 100644
    --- a/powertools-logging/powertools-logging-log4j/pom.xml
    +++ b/powertools-logging/powertools-logging-log4j/pom.xml
    @@ -53,17 +53,14 @@
             
                 org.apache.logging.log4j
                 log4j-slf4j2-impl
    -            provided
             
             
                 org.apache.logging.log4j
                 log4j-core
    -            provided
             
             
                 org.apache.logging.log4j
                 log4j-layout-template-json
    -            provided
             
     
             
    diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    index 0214e7c9c..39d7c41eb 100644
    --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    @@ -24,11 +24,15 @@
     import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SAMPLING_RATE;
     import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE;
     
    +import com.fasterxml.jackson.core.JacksonException;
    +import com.fasterxml.jackson.databind.DeserializationFeature;
    +import com.fasterxml.jackson.databind.ObjectMapper;
     import java.util.Map;
     import java.util.stream.Collectors;
     import java.util.stream.Stream;
     import org.apache.logging.log4j.core.LogEvent;
     import org.apache.logging.log4j.layout.template.json.util.JsonWriter;
    +import org.apache.logging.log4j.message.Message;
     import org.apache.logging.log4j.util.ReadOnlyStringMap;
     import software.amazon.lambda.powertools.common.internal.LambdaConstants;
     import software.amazon.lambda.powertools.common.internal.SystemWrapper;
    @@ -147,11 +151,14 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
                 (final LogEvent logEvent, final JsonWriter jsonWriter) ->
                         jsonWriter.writeString(SystemWrapper.getenv(LambdaConstants.AWS_REGION_ENV));
     
    +    public static final String LAMBDA_ARN_REGEX =
    +            "^arn:(aws|aws-us-gov|aws-cn):lambda:[a-zA-Z0-9-]+:\\d{12}:function:[a-zA-Z0-9-_]+(:[a-zA-Z0-9-_]+)?$";
    +
         private static final EventResolver ACCOUNT_ID_RESOLVER = new EventResolver() {
             @Override
             public boolean isResolvable(LogEvent logEvent) {
                 final String arn = logEvent.getContextData().getValue(PowertoolsLoggedFields.FUNCTION_ARN.getName());
    -            return null != arn && !arn.isEmpty();
    +            return null != arn && !arn.isEmpty() && arn.matches(LAMBDA_ARN_REGEX);
             }
     
             @Override
    @@ -161,6 +168,42 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
             }
         };
     
    +    /**
    +     * Use a custom message resolver to permit to log json string in json format without escaped quotes.
    +     */
    +    private static final EventResolver MESSAGE_RESOLVER = new EventResolver() {
    +        private final ObjectMapper mapper = new ObjectMapper()
    +                .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
    +
    +        public boolean isValidJson(String json) {
    +            if (!(json.startsWith("{") || json.startsWith("["))) {
    +                return false;
    +            }
    +            try {
    +                mapper.readTree(json);
    +            } catch (JacksonException e) {
    +                return false;
    +            }
    +            return true;
    +        }
    +
    +        @Override
    +        public boolean isResolvable(LogEvent logEvent) {
    +            final Message msg = logEvent.getMessage();
    +            return null != msg && null != msg.getFormattedMessage();
    +        }
    +
    +        @Override
    +        public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
    +            String message = logEvent.getMessage().getFormattedMessage();
    +            if (isValidJson(message)) {
    +                jsonWriter.writeRawString(message);
    +            } else {
    +                jsonWriter.writeString(message);
    +            }
    +        }
    +    };
    +
         private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER =
                 (LogEvent logEvent, JsonWriter jsonWriter) -> {
                     StringBuilder stringBuilder = jsonWriter.getStringBuilder();
    @@ -193,6 +236,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) {
                 { SAMPLING_RATE.getName(), SAMPLING_RATE_RESOLVER },
                 { "region", REGION_RESOLVER },
                 { "account_id", ACCOUNT_ID_RESOLVER },
    +            { "message", MESSAGE_RESOLVER }
         }).collect(Collectors.toMap(data -> (String) data[0], data -> (EventResolver) data[1]));
     
     
    diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    index 7d7809cd3..1591f1b56 100644
    --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    @@ -12,8 +12,8 @@
         "field": "name"
       },
       "message": {
    -    "$resolver": "message",
    -    "stringified": true
    +    "$resolver": "powertools",
    +    "field": "message"
       },
       "error.type": {
         "$resolver": "exception",
    diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json
    index 02ae09dc6..d8d8810f6 100644
    --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json
    +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json
    @@ -4,8 +4,8 @@
         "field": "name"
       },
       "message": {
    -    "$resolver": "message",
    -    "stringified": true
    +    "$resolver": "powertools",
    +    "field": "message"
       },
       "error": {
         "message": {
    diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
    new file mode 100644
    index 000000000..4150ab96b
    --- /dev/null
    +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
    @@ -0,0 +1,108 @@
    +/*
    + * Copyright 2023 Amazon.com, Inc. or its affiliates.
    + * 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 org.apache.logging.log4j.layout.template.json.resolver;
    +
    +import static org.assertj.core.api.Assertions.assertThat;
    +import static org.assertj.core.api.Assertions.contentOf;
    +import static org.mockito.Mockito.when;
    +import static org.mockito.MockitoAnnotations.openMocks;
    +
    +import com.amazonaws.services.lambda.runtime.Context;
    +import com.amazonaws.services.lambda.runtime.events.SQSEvent;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.channels.FileChannel;
    +import java.nio.file.NoSuchFileException;
    +import java.nio.file.Paths;
    +import java.nio.file.StandardOpenOption;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import org.junit.jupiter.api.AfterEach;
    +import org.junit.jupiter.api.BeforeEach;
    +import org.junit.jupiter.api.Test;
    +import org.mockito.Mock;
    +import org.slf4j.MDC;
    +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsJsonMessage;
    +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled;
    +
    +class PowertoolsMessageResolverTest {
    +
    +    @Mock
    +    private Context context;
    +
    +    @BeforeEach
    +    void setUp() throws IOException {
    +        openMocks(this);
    +        MDC.clear();
    +        setupContext();
    +
    +        try {
    +            FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
    +            FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close();
    +        } catch (NoSuchFileException e) {
    +            // may not be there in the first run
    +        }
    +    }
    +
    +    @AfterEach
    +    void cleanUp() throws IOException {
    +        //Make sure file is cleaned up before running full stack logging regression
    +        FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close();
    +        FileChannel.open(Paths.get("target/ecslogfile.json"), StandardOpenOption.WRITE).truncate(0).close();
    +    }
    +
    +    @Test
    +    void shouldLogJsonMessageWithoutEscapedStrings() {
    +        // GIVEN
    +        PowertoolsJsonMessage requestHandler = new PowertoolsJsonMessage();
    +        SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage();
    +        msg.setMessageId("1212abcd");
    +        msg.setBody("plop");
    +        msg.setEventSource("eb");
    +        msg.setAwsRegion("eu-west-1");
    +        SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute();
    +        attribute.setStringListValues(Arrays.asList("val1", "val2", "val3"));
    +        msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute));
    +
    +        // WHEN
    +        requestHandler.handleRequest(msg, context);
    +
    +        // THEN
    +        File logFile = new File("target/logfile.json");
    +        assertThat(contentOf(logFile)).contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}");
    +    }
    +
    +
    +    @Test
    +    void shouldLogStringMessageWhenNotJson() {
    +        // GIVEN
    +        PowertoolsLogEnabled requestHandler = new PowertoolsLogEnabled();
    +
    +        // WHEN
    +        requestHandler.handleRequest(null, context);
    +
    +        // THEN
    +        File logFile = new File("target/logfile.json");
    +        assertThat(contentOf(logFile)).contains("\"message\":\"Test debug event\"");
    +    }
    +
    +    private void setupContext() {
    +        when(context.getFunctionName()).thenReturn("testFunction");
    +        when(context.getInvokedFunctionArn()).thenReturn("testArn");
    +        when(context.getFunctionVersion()).thenReturn("1");
    +        when(context.getMemoryLimitInMB()).thenReturn(10);
    +        when(context.getAwsRequestId()).thenReturn("RequestId");
    +    }
    +}
    \ No newline at end of file
    diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    new file mode 100644
    index 000000000..b30ae7da2
    --- /dev/null
    +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    @@ -0,0 +1,39 @@
    +/*
    + * Copyright 2023 Amazon.com, Inc. or its affiliates.
    + * 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 software.amazon.lambda.powertools.logging.internal.handler;
    +
    +import com.amazonaws.services.lambda.runtime.Context;
    +import com.amazonaws.services.lambda.runtime.RequestHandler;
    +import com.amazonaws.services.lambda.runtime.events.SQSEvent;
    +import com.fasterxml.jackson.core.JsonProcessingException;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +import software.amazon.lambda.powertools.logging.Logging;
    +import software.amazon.lambda.powertools.utilities.JsonConfig;
    +
    +public class PowertoolsJsonMessage implements RequestHandler {
    +    private final Logger LOG = LoggerFactory.getLogger(PowertoolsJsonMessage.class);
    +
    +    @Override
    +    @Logging(clearState = true)
    +    public String handleRequest(SQSEvent.SQSMessage input, Context context) {
    +        try {
    +            LOG.debug(JsonConfig.get().getObjectMapper().writeValueAsString(input));
    +        } catch (JsonProcessingException e) {
    +            throw new RuntimeException(e);
    +        }
    +        return input.getMessageId();
    +    }
    +}
    
    From 3206439cf0bc2320b8f4e2d9822443f94bfcb32b Mon Sep 17 00:00:00 2001
    From: Jerome Van Der Linden 
    Date: Mon, 27 Nov 2023 14:26:56 +0100
    Subject: [PATCH 49/74] fix
    
    ---
     .../layout/template/json/resolver/PowertoolsResolver.java   | 3 +--
     .../json/resolver/PowertoolsMessageResolverTest.java        | 6 ++++--
     .../logging/internal/handler/PowertoolsJsonMessage.java     | 2 ++
     3 files changed, 7 insertions(+), 4 deletions(-)
    
    diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    index 39d7c41eb..f2549eb85 100644
    --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java
    @@ -259,7 +259,6 @@ public void resolve(LogEvent value, JsonWriter jsonWriter) {
     
         @Override
         public boolean isResolvable(LogEvent value) {
    -        ReadOnlyStringMap contextData = value.getContextData();
    -        return null != contextData && !contextData.isEmpty() && internalResolver.isResolvable(value);
    +        return value != null && value.getContextData() != null && internalResolver.isResolvable(value);
         }
     }
    diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
    index 4150ab96b..8ea1ce470 100644
    --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
    +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java
    @@ -81,10 +81,12 @@ void shouldLogJsonMessageWithoutEscapedStrings() {
     
             // THEN
             File logFile = new File("target/logfile.json");
    -        assertThat(contentOf(logFile)).contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}");
    +        assertThat(contentOf(logFile))
    +                .contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}")
    +                .contains("\"message\":\"1212abcd\"")
    +                .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"");
         }
     
    -
         @Test
         void shouldLogStringMessageWhenNotJson() {
             // GIVEN
    diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    index b30ae7da2..d9a653c55 100644
    --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java
    @@ -31,6 +31,8 @@ public class PowertoolsJsonMessage implements RequestHandler
    Date: Mon, 27 Nov 2023 15:35:46 +0100
    Subject: [PATCH 50/74] logback message json
    
    ---
     .../src/main/resources/LambdaEcsLayout.json   |  3 +-
     .../logging/internal/JsonUtils.java           | 27 ++++++++++++++++--
     .../internal/LambdaJsonSerializer.java        |  4 +--
     .../internal/LambdaJsonEncoderTest.java       | 28 +++++++++++++++++++
     4 files changed, 55 insertions(+), 7 deletions(-)
    
    diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    index 1591f1b56..19f13f199 100644
    --- a/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaEcsLayout.json
    @@ -12,8 +12,7 @@
         "field": "name"
       },
       "message": {
    -    "$resolver": "powertools",
    -    "field": "message"
    +    "$resolver": "message"
       },
       "error.type": {
         "$resolver": "exception",
    diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java
    index a31782b8e..7d9224453 100644
    --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java
    +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java
    @@ -14,6 +14,10 @@
     
     package software.amazon.lambda.powertools.logging.internal;
     
    +import com.fasterxml.jackson.core.JacksonException;
    +import com.fasterxml.jackson.databind.DeserializationFeature;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
     /**
      * Json tools to serialize attributes manually, to avoid using further dependencies (jackson, gson...)
      */
    @@ -33,7 +37,11 @@ protected static void serializeAttribute(StringBuilder builder, String attr, Str
                 if (isString) {
                     builder.append("\"");
                 }
    -            builder.append(value);
    +            if (isString) {
    +                builder.append(value.replace("\"", "\\\"")); // escape quotes in string
    +            } else {
    +                builder.append(value);
    +            }
                 if (isString) {
                     builder.append("\"");
                 }
    @@ -73,7 +81,22 @@ private static boolean isString(String str) {
             if ("true".equals(str) || "false".equals(str)) {
                 return false; // boolean
             }
    -        return !isNumeric(str); // number
    +        return !isNumeric(str) && !isJson(str); // number
    +    }
    +
    +    private static final ObjectMapper mapper = new ObjectMapper()
    +            .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
    +
    +    private static boolean isJson(String str) {
    +        if (!(str.startsWith("{") || str.startsWith("["))) {
    +            return false;
    +        }
    +        try {
    +            mapper.readTree(str);
    +        } catch (JacksonException e) {
    +            return false;
    +        }
    +        return true;
         }
     
         /**
    diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    index a8785f929..1735e21a4 100644
    --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java
    @@ -24,7 +24,6 @@
     import java.util.Map;
     import java.util.TimeZone;
     import java.util.TreeMap;
    -import java.util.regex.Matcher;
     
     /**
      * This class will serialize the log events in json.
    @@ -83,8 +82,7 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { } public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { - serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, - formattedMessage.replace("\"", Matcher.quoteReplacement("\\\""))); + serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index 0b4f8c1bd..c8b7531da 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -25,6 +25,7 @@ import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter; import ch.qos.logback.classic.spi.LoggingEvent; import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; @@ -33,6 +34,8 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.TimeZone; import org.junit.jupiter.api.AfterEach; @@ -44,6 +47,7 @@ import org.slf4j.MDC; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.logging.LambdaJsonEncoder; +import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsJsonMessage; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @Order(2) @@ -86,6 +90,30 @@ void shouldLogInJsonFormat() { "{\"level\":\"DEBUG\",\"message\":\"Test debug event\",\"cold_start\":true,\"function_arn\":\"arn:aws:lambda:eu-west-1:012345678910:function:testFunction:1\",\"function_memory_size\":1024,\"function_name\":\"testFunction\",\"function_request_id\":\"RequestId\",\"function_version\":1,\"myKey\":\"myValue\",\"service\":\"testLogback\",\"xray_trace_id\":\"1-63441c4a-abcdef012345678912345678\",\"timestamp\":"); } + @Test + void shouldLogJsonMessageWithoutEscapedStrings() { + // GIVEN + PowertoolsJsonMessage requestHandler = new PowertoolsJsonMessage(); + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("eu-west-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + requestHandler.handleRequest(msg, context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)) + .contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}") + .contains("\"message\":\"1212abcd\"") + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\""); + } + private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); @Test From 48dfa9a24a7c5f13175a3cf2a9773ed8511677af Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 27 Nov 2023 15:37:27 +0100 Subject: [PATCH 51/74] logback message json --- .../handler/PowertoolsJsonMessage.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java new file mode 100644 index 000000000..d9a653c55 --- /dev/null +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal.handler; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.utilities.JsonConfig; + +public class PowertoolsJsonMessage implements RequestHandler { + private final Logger LOG = LoggerFactory.getLogger(PowertoolsJsonMessage.class); + + @Override + @Logging(clearState = true) + public String handleRequest(SQSEvent.SQSMessage input, Context context) { + try { + LOG.debug(JsonConfig.get().getObjectMapper().writeValueAsString(input)); + LOG.debug("{}", input.getMessageId()); + LOG.warn("Message body = {} and id = \"{}\"", input.getBody(), input.getMessageId()); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return input.getMessageId(); + } +} From 232d9ef0cb0808f952d1d2c7f4c239dfa6b84e89 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 27 Nov 2023 16:10:33 +0100 Subject: [PATCH 52/74] fix maven warnings --- powertools-common/pom.xml | 4 ---- powertools-e2e-tests/pom.xml | 1 + powertools-idempotency/pom.xml | 4 ---- powertools-logging/powertools-logging-log4j/pom.xml | 2 +- powertools-logging/powertools-logging-logback/pom.xml | 2 +- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/powertools-common/pom.xml b/powertools-common/pom.xml index a8e02dafd..8f6752a4d 100644 --- a/powertools-common/pom.xml +++ b/powertools-common/pom.xml @@ -67,10 +67,6 @@ com.amazonaws aws-lambda-java-core - - org.aspectj - aspectjrt - diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index 8ce85fcce..bea1648b3 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -184,6 +184,7 @@ org.apache.maven.plugins maven-deploy-plugin + 3.1.1 true diff --git a/powertools-idempotency/pom.xml b/powertools-idempotency/pom.xml index 3cac8d66a..b53036399 100644 --- a/powertools-idempotency/pom.xml +++ b/powertools-idempotency/pom.xml @@ -91,10 +91,6 @@ url-connection-client ${aws.sdk.version} - - org.aspectj - aspectjrt - diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 5a32c2db5..6630f677f 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -43,7 +43,7 @@ software.amazon.lambda powertools-logging - ${version} + ${project.version} org.aspectj diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index e4833389e..c47c94021 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -42,7 +42,7 @@ software.amazon.lambda powertools-logging - ${version} + ${project.version} org.aspectj From c35fbc7be01fae99b839ccc7acd47cfc1e426fc2 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 27 Nov 2023 17:54:31 +0100 Subject: [PATCH 53/74] fix tests --- .../template/json/resolver/PowertoolsMessageResolverTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java index 8ea1ce470..f97ee6b4a 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java @@ -31,12 +31,14 @@ import java.util.Collections; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.slf4j.MDC; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsJsonMessage; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; +@Order(2) class PowertoolsMessageResolverTest { @Mock From 59342152f2f156c96396ead52c604a1fddcb2020 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 27 Nov 2023 17:58:23 +0100 Subject: [PATCH 54/74] fix gradle build --- examples/powertools-examples-core-utilities/gradle/build.gradle | 1 + .../powertools-examples-core-utilities/kotlin/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index 9f7fad9b3..1380097ae 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' + implementation 'org.aspectj:aspectjrt:1.9.7' aspect 'software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT' aspect 'software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT' aspect 'software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT' diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index ad74311d3..c216687fb 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2.2") implementation("com.amazonaws:aws-lambda-java-events:3.11.0") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2") + implementation("org.aspectj:aspectjrt:1.9.7") aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT") From 6a93f9fd869dd2c7bd649c8ca43a79bc60e25903 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 30 Nov 2023 09:35:00 +0100 Subject: [PATCH 55/74] log messages as JSON (optional) --- .../json/resolver/PowertoolsResolver.java | 36 +++++++++++--- .../PowertoolsMessageResolverTest.java | 7 ++- .../handler/PowertoolsJsonMessage.java | 6 +++ .../powertools/logging/LambdaJsonEncoder.java | 26 +++++++++- .../logging/internal/JsonUtils.java | 40 ++++++--------- .../internal/LambdaJsonSerializer.java | 44 ++++++++++++++--- .../internal/LambdaJsonEncoderTest.java | 49 ++++++++++++++++++- .../handler/PowertoolsJsonMessage.java | 3 ++ .../powertools/logging/LoggingUtils.java | 12 +++++ .../logging/internal/LambdaLoggingAspect.java | 2 + 10 files changed, 180 insertions(+), 45 deletions(-) diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java index f2549eb85..95086a085 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java @@ -14,6 +14,8 @@ package org.apache.logging.log4j.layout.template.json.resolver; +import static java.lang.Boolean.TRUE; +import static software.amazon.lambda.powertools.logging.LoggingUtils.LOG_MESSAGES_AS_JSON; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_MEMORY_SIZE; @@ -171,9 +173,14 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { /** * Use a custom message resolver to permit to log json string in json format without escaped quotes. */ - private static final EventResolver MESSAGE_RESOLVER = new EventResolver() { + private static final class MessageResolver implements EventResolver { private final ObjectMapper mapper = new ObjectMapper() .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + private final boolean logMessagesAsJsonGlobal; + + public MessageResolver(boolean logMessagesAsJson) { + this.logMessagesAsJsonGlobal = logMessagesAsJson; + } public boolean isValidJson(String json) { if (!(json.startsWith("{") || json.startsWith("["))) { @@ -196,13 +203,21 @@ public boolean isResolvable(LogEvent logEvent) { @Override public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { String message = logEvent.getMessage().getFormattedMessage(); - if (isValidJson(message)) { + + String logMessagesAsJsonLocal = logEvent.getContextData().getValue(LOG_MESSAGES_AS_JSON); + Boolean logMessagesAsJson = null; + if (logMessagesAsJsonLocal != null) { + logMessagesAsJson = Boolean.parseBoolean(logMessagesAsJsonLocal); + } + + if (((logMessagesAsJsonGlobal && logMessagesAsJson == null) || TRUE.equals(logMessagesAsJson)) + && isValidJson(message)) { jsonWriter.writeRawString(message); } else { jsonWriter.writeString(message); } } - }; + } private static final EventResolver NON_POWERTOOLS_FIELD_RESOLVER = (LogEvent logEvent, JsonWriter jsonWriter) -> { @@ -213,7 +228,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { // Inject all the context information. ReadOnlyStringMap contextData = logEvent.getContextData(); contextData.forEach((key, value) -> { - if (!PowertoolsLoggedFields.stringValues().contains(key)) { + if (!PowertoolsLoggedFields.stringValues().contains(key) && !LOG_MESSAGES_AS_JSON.equals(key)) { jsonWriter.writeSeparator(); jsonWriter.writeString(key); stringBuilder.append(':'); @@ -235,8 +250,7 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { { FUNCTION_TRACE_ID.getName(), XRAY_TRACE_RESOLVER }, { SAMPLING_RATE.getName(), SAMPLING_RATE_RESOLVER }, { "region", REGION_RESOLVER }, - { "account_id", ACCOUNT_ID_RESOLVER }, - { "message", MESSAGE_RESOLVER } + { "account_id", ACCOUNT_ID_RESOLVER } }).collect(Collectors.toMap(data -> (String) data[0], data -> (EventResolver) data[1])); @@ -245,7 +259,15 @@ public void resolve(LogEvent logEvent, JsonWriter jsonWriter) { if (fieldName == null) { internalResolver = NON_POWERTOOLS_FIELD_RESOLVER; } else { - internalResolver = eventResolverMap.get(fieldName); + boolean logMessagesAsJson = false; + if (config.exists("asJson")) { + logMessagesAsJson = config.getBoolean("asJson"); + } + if ("message".equals(fieldName)) { + internalResolver = new MessageResolver(logMessagesAsJson); + } else { + internalResolver = eventResolverMap.get(fieldName); + } if (internalResolver == null) { throw new IllegalArgumentException("unknown field: " + fieldName); } diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java index f97ee6b4a..a00b78906 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsMessageResolverTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.slf4j.MDC; +import software.amazon.lambda.powertools.logging.LoggingUtils; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsJsonMessage; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; @@ -66,7 +67,7 @@ void cleanUp() throws IOException { } @Test - void shouldLogJsonMessageWithoutEscapedStrings() { + void shouldLogJsonMessageWithoutEscapedStringsWhenSettingLogAsJson() { // GIVEN PowertoolsJsonMessage requestHandler = new PowertoolsJsonMessage(); SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); @@ -86,7 +87,9 @@ void shouldLogJsonMessageWithoutEscapedStrings() { assertThat(contentOf(logFile)) .contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}") .contains("\"message\":\"1212abcd\"") - .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\""); + .contains("\"message\":\"{\\\"key\\\":\\\"value\\\"}\"") + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .doesNotContain(LoggingUtils.LOG_MESSAGES_AS_JSON); } @Test diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java index d9a653c55..3d196e5fb 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; import software.amazon.lambda.powertools.utilities.JsonConfig; public class PowertoolsJsonMessage implements RequestHandler { @@ -30,7 +31,12 @@ public class PowertoolsJsonMessage implements RequestHandler { protected String timestampFormatTimezoneId = null; private boolean includeThreadInfo = false; private boolean includePowertoolsInfo = true; + private boolean logMessagesAsJsonGlobal; @Override public byte[] headerBytes() { @@ -57,7 +59,11 @@ public byte[] encode(ILoggingEvent event) { StringBuilder builder = new StringBuilder(256); LambdaJsonSerializer.serializeObjectStart(builder); LambdaJsonSerializer.serializeLogLevel(builder, event.getLevel()); - LambdaJsonSerializer.serializeFormattedMessage(builder, event.getFormattedMessage()); + LambdaJsonSerializer.serializeFormattedMessage( + builder, + event.getFormattedMessage(), + logMessagesAsJsonGlobal, + event.getMDCPropertyMap().get(LOG_MESSAGES_AS_JSON)); IThrowableProxy throwableProxy = event.getThrowableProxy(); if (throwableProxy != null) { if (throwableConverter != null) { @@ -88,7 +94,8 @@ public byte[] footerBytes() { } /** - * Specify the format of the timestamp (default is yyyy-MM-dd'T'HH:mm:ss.SSS'Z'): + * Specify the format of the timestamp (default is yyyy-MM-dd'T'HH:mm:ss.SSS'Z'). + * Note that if you use the Lambda Advanced Logging Configuration, you should keep the default format. *
    *
    {@code
          *     
    @@ -184,4 +191,19 @@ public void setIncludeThreadInfo(boolean includeThreadInfo) {
         public void setIncludePowertoolsInfo(boolean includePowertoolsInfo) {
             this.includePowertoolsInfo = includePowertoolsInfo;
         }
    +
    +    /**
    +     * Specify if messages should be logged as JSON, without escaping string (default is false):
    +     * 
    + *
    {@code
    +     *     
    +     *         true
    +     *     
    +     * }
    + * + * @param logMessagesAsJson if messages should be looged as JSON (non escaped quotes) + */ + public void setLogMessagesAsJson(boolean logMessagesAsJson) { + this.logMessagesAsJsonGlobal = logMessagesAsJson; + } } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java index 7d9224453..7f5e380b2 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java @@ -14,10 +14,6 @@ package software.amazon.lambda.powertools.logging.internal; -import com.fasterxml.jackson.core.JacksonException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * Json tools to serialize attributes manually, to avoid using further dependencies (jackson, gson...) */ @@ -37,11 +33,7 @@ protected static void serializeAttribute(StringBuilder builder, String attr, Str if (isString) { builder.append("\""); } - if (isString) { - builder.append(value.replace("\"", "\\\"")); // escape quotes in string - } else { - builder.append(value); - } + builder.append(value); if (isString) { builder.append("\""); } @@ -52,6 +44,19 @@ protected static void serializeAttribute(StringBuilder builder, String attr, Str serializeAttribute(builder, attr, value, true); } + protected static void serializeMessage(StringBuilder builder, String attr, String value, boolean logAsJson) { + builder.append(","); + builder.append("\"").append(attr).append("\":"); + if (logAsJson) { + builder.append(value); // log JSON without quotes + } else { + builder.append("\""); + builder.append(value.replace("\"", "\\\"")); // escape quotes in string + builder.append("\""); + } + } + + protected static void serializeAttributeAsString(StringBuilder builder, String attr, String value, boolean notBegin) { if (value != null) { @@ -81,22 +86,7 @@ private static boolean isString(String str) { if ("true".equals(str) || "false".equals(str)) { return false; // boolean } - return !isNumeric(str) && !isJson(str); // number - } - - private static final ObjectMapper mapper = new ObjectMapper() - .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); - - private static boolean isJson(String str) { - if (!(str.startsWith("{") || str.startsWith("["))) { - return false; - } - try { - mapper.readTree(str); - } catch (JacksonException e) { - return false; - } - return true; + return !isNumeric(str); // number } /** diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index 1735e21a4..9d0e3cdd0 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -14,9 +14,15 @@ package software.amazon.lambda.powertools.logging.internal; +import static java.lang.Boolean.TRUE; +import static software.amazon.lambda.powertools.logging.LoggingUtils.LOG_MESSAGES_AS_JSON; import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttribute; +import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeMessage; import ch.qos.logback.classic.Level; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; @@ -81,8 +87,16 @@ public static void serializeLogLevel(StringBuilder builder, Level level) { serializeAttribute(builder, LEVEL_ATTR_NAME, level.toString(), false); } - public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { - serializeAttribute(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage); + public static void serializeFormattedMessage(StringBuilder builder, String message, + boolean logMessagesAsJsonGlobal, String logMessagesAsJsonLocal) { + Boolean logMessagesAsJson = null; + if (logMessagesAsJsonLocal != null) { + logMessagesAsJson = Boolean.parseBoolean(logMessagesAsJsonLocal); + } + + boolean logAsJson = ((logMessagesAsJsonGlobal && logMessagesAsJson == null) || TRUE.equals(logMessagesAsJson)) + && isValidJson(message); + serializeMessage(builder, FORMATTED_MESSAGE_ATTR_NAME, message, logAsJson); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { @@ -110,13 +124,29 @@ public static void serializePowertools(StringBuilder builder, Map sortedMap = new TreeMap<>(mdc); sortedMap.forEach((k, v) -> { - if ((PowertoolsLoggedFields.stringValues().contains(k) && includePowertoolsInfo) - || !PowertoolsLoggedFields.stringValues().contains(k)) { - if (!k.equals(PowertoolsLoggedFields.SAMPLING_RATE.getName()) || !v.equals("0.0")) { // do not log sampling rate when 0 - serializeAttribute(builder, k, v); - } + if ((includePowertoolsInfo || !PowertoolsLoggedFields.stringValues().contains(k)) // do not log already logged powertools info + && !(k.equals(PowertoolsLoggedFields.SAMPLING_RATE.getName()) && v.equals("0.0")) // do not log sampling rate when 0 + && !LOG_MESSAGES_AS_JSON.equals(k)) // do not log internal keys + { + serializeAttribute(builder, k, v); } }); + + } + + private static final ObjectMapper mapper = new ObjectMapper() + .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + + private static boolean isValidJson(String str) { + if (!(str.startsWith("{") || str.startsWith("["))) { + return false; + } + try { + mapper.readTree(str); + } catch (JacksonException e) { + return false; + } + return true; } } diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java index c8b7531da..4b7033232 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonEncoderTest.java @@ -14,11 +14,13 @@ package software.amazon.lambda.powertools.logging.internal; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; import static org.apache.commons.lang3.reflect.FieldUtils.writeStaticField; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.openMocks; +import static software.amazon.lambda.powertools.logging.LoggingUtils.LOG_MESSAGES_AS_JSON; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; @@ -26,6 +28,7 @@ import ch.qos.logback.classic.spi.LoggingEvent; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.SQSEvent; +import com.fasterxml.jackson.core.JsonProcessingException; import java.io.File; import java.io.IOException; import java.nio.channels.FileChannel; @@ -39,6 +42,7 @@ import java.util.Date; import java.util.TimeZone; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; @@ -49,6 +53,7 @@ import software.amazon.lambda.powertools.logging.LambdaJsonEncoder; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsJsonMessage; import software.amazon.lambda.powertools.logging.internal.handler.PowertoolsLogEnabled; +import software.amazon.lambda.powertools.utilities.JsonConfig; @Order(2) class LambdaJsonEncoderTest { @@ -57,6 +62,11 @@ class LambdaJsonEncoderTest { @Mock private Context context; + @BeforeAll + private static void init() { + JsonConfig.get().getObjectMapper().setSerializationInclusion(NON_NULL); + } + @BeforeEach void setUp() throws IllegalAccessException, IOException { openMocks(this); @@ -109,9 +119,10 @@ void shouldLogJsonMessageWithoutEscapedStrings() { // THEN File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)) - .contains("\"message\":{\"messageId\":\"1212abcd\",\"receiptHandle\":null,\"body\":\"plop\",\"md5OfBody\":null,\"md5OfMessageAttributes\":null,\"eventSourceArn\":null,\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"attributes\":null,\"messageAttributes\":{\"keyAttribute\":{\"stringValue\":null,\"binaryValue\":null,\"stringListValues\":[\"val1\",\"val2\",\"val3\"],\"binaryListValues\":null,\"dataType\":null}}}") + .contains("\"message\":{\"messageId\":\"1212abcd\",\"body\":\"plop\",\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}}}") .contains("\"message\":\"1212abcd\"") - .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\""); + .contains("\"message\":\"Message body = plop and id = \\\"1212abcd\\\"\"") + .doesNotContain(LOG_MESSAGES_AS_JSON); } private final LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, "message", null, null); @@ -147,6 +158,40 @@ void shouldNotLogPowertoolsInfo() { assertThat(result).doesNotContain("cold_start", "function_arn", "function_memory_size", "function_name", "function_request_id", "function_version", "sampling_rate", "service"); } + @Test + void shouldLogMessagesAsJsonWhenEnabledInLogbackConfig() throws JsonProcessingException { + // GIVEN + LambdaJsonEncoder encoder = new LambdaJsonEncoder(); + encoder.setLogMessagesAsJson(true); + + SQSEvent.SQSMessage msg = new SQSEvent.SQSMessage(); + msg.setMessageId("1212abcd"); + msg.setBody("plop"); + msg.setEventSource("eb"); + msg.setAwsRegion("eu-west-1"); + SQSEvent.MessageAttribute attribute = new SQSEvent.MessageAttribute(); + attribute.setStringListValues(Arrays.asList("val1", "val2", "val3")); + msg.setMessageAttributes(Collections.singletonMap("keyAttribute", attribute)); + + // WHEN + LoggingEvent loggingEvent = new LoggingEvent("fqcn", logger, Level.INFO, JsonConfig.get().getObjectMapper().writeValueAsString(msg), null, null); + byte[] encoded = encoder.encode(loggingEvent); + String result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (logged as JSON) + assertThat(result) + .contains("\"message\":{\"messageId\":\"1212abcd\",\"body\":\"plop\",\"eventSource\":\"eb\",\"awsRegion\":\"eu-west-1\",\"messageAttributes\":{\"keyAttribute\":{\"stringListValues\":[\"val1\",\"val2\",\"val3\"]}}}"); + + // WHEN (disabling logging as json) + encoder.setLogMessagesAsJson(false); + encoded = encoder.encode(loggingEvent); + result = new String(encoded, StandardCharsets.UTF_8); + + // THEN (logged as String) + assertThat(result) + .contains("\"message\":\"{\\\"messageId\\\":\\\"1212abcd\\\",\\\"body\\\":\\\"plop\\\",\\\"eventSource\\\":\\\"eb\\\",\\\"awsRegion\\\":\\\"eu-west-1\\\",\\\"messageAttributes\\\":{\\\"keyAttribute\\\":{\\\"stringListValues\\\":[\\\"val1\\\",\\\"val2\\\",\\\"val3\\\"]}}}\""); + } + @Test void shouldLogThreadInfo() { // GIVEN diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java index d9a653c55..fdc279319 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/internal/handler/PowertoolsJsonMessage.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; +import software.amazon.lambda.powertools.logging.LoggingUtils; import software.amazon.lambda.powertools.utilities.JsonConfig; public class PowertoolsJsonMessage implements RequestHandler { @@ -30,6 +31,8 @@ public class PowertoolsJsonMessage implements RequestHandler Date: Fri, 1 Dec 2023 17:23:51 +0100 Subject: [PATCH 56/74] log responses and log exceptions --- .../internal/LambdaHandlerProcessorTest.java | 3 - .../lambda/powertools/logging/Logging.java | 16 +- .../logging/internal/LambdaLoggingAspect.java | 195 +++++++++++------- .../logging/internal/LoggingConstants.java | 4 + .../handlers/PowertoolsLogEnabled.java | 14 +- .../logging/handlers/PowertoolsLogError.java | 28 +++ .../handlers/PowertoolsLogResponse.java | 28 +++ .../PowertoolsLogResponseForStream.java | 35 ++++ .../internal/LambdaLoggingAspectTest.java | 94 ++++++++- 9 files changed, 340 insertions(+), 77 deletions(-) create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java create mode 100644 powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java diff --git a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java index 589aab703..4ad170600 100644 --- a/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java +++ b/powertools-common/src/test/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessorTest.java @@ -31,9 +31,6 @@ import org.aspectj.lang.Signature; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import software.amazon.lambda.powertools.common.internal.LambdaConstants; -import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.common.internal.SystemWrapper; class LambdaHandlerProcessorTest { diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java index b9eab7da0..05a9cfe31 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/Logging.java @@ -64,11 +64,25 @@ public @interface Logging { /** - * Set to true if you want to log the event received by the Lambda function handler. + * Set to true if you want to log the event received by the Lambda function handler.
    * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_EVENT' environment variable */ boolean logEvent() default false; + /** + * Set to true if you want to log the response sent by the Lambda function handler.
    + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_RESPONE' environment variable + */ + boolean logResponse() default false; + + /** + * Set to true if you want to log the exception thrown by the Lambda function handler. + * It is already logged by AWS Lambda but with no context information. Setting this to true + * will log the exception and all the powertools additional fields, for more context.
    + * Can also be configured with the 'POWERTOOLS_LOGGER_LOG_ERROR' environment variable + */ + boolean logError() default false; + /** * Sampling rate to change log level to DEBUG. (values must be >=0.0, <=1.0) */ diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 2dfa3b6e9..1f78f6cd8 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -27,13 +27,15 @@ import static software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor.serviceName; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKey; import static software.amazon.lambda.powertools.logging.LoggingUtils.appendKeys; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.LAMBDA_LOG_LEVEL; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_ERROR; import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_EVENT; import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_LEVEL; +import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_LOG_RESPONSE; import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.POWERTOOLS_SAMPLING_RATE; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_COLD_START; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_TRACE_ID; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.SERVICE; -import static software.amazon.lambda.powertools.logging.internal.LoggingConstants.LAMBDA_LOG_LEVEL; import com.amazonaws.services.lambda.runtime.Context; import com.fasterxml.jackson.core.JsonProcessingException; @@ -44,11 +46,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.ServiceLoader; @@ -60,6 +62,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import org.slf4j.MarkerFactory; import org.slf4j.event.Level; import software.amazon.lambda.powertools.logging.Logging; import software.amazon.lambda.powertools.logging.LoggingUtils; @@ -158,36 +161,85 @@ public void callAt(Logging logging) { @Around(value = "callAt(logging) && execution(@Logging * *.*(..))", argNames = "pjp,logging") public Object around(ProceedingJoinPoint pjp, Logging logging) throws Throwable { - Object[] proceedArgs = pjp.getArgs(); + + boolean isOnRequestHandler = placedOnRequestHandler(pjp); + boolean isOnRequestStreamHandler = placedOnStreamHandler(pjp); setLogLevelBasedOnSamplingRate(pjp, logging); - Context extractedContext = extractContext(pjp); + addLambdaContextToLoggingContext(pjp); - if (null != extractedContext) { - appendKeys(PowertoolsLoggedFields.setValuesFromLambdaContext(extractedContext)); - appendKey(FUNCTION_COLD_START.getName(), isColdStart() ? "true" : "false"); - appendKey(SERVICE.getName(), serviceName()); + getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); + + Object[] proceedArgs = logEvent(pjp, logging, isOnRequestHandler, isOnRequestStreamHandler); + + if (!logging.correlationIdPath().isEmpty()) { + captureCorrelationId(logging.correlationIdPath(), proceedArgs, isOnRequestHandler, isOnRequestStreamHandler); } - getXrayTraceId().ifPresent(xRayTraceId -> appendKey(FUNCTION_TRACE_ID.getName(), xRayTraceId)); + // To log the result of a RequestStreamHandler (OutputStream), we need to do the following: + // 1. backup a reference to the OutputStream provided by Lambda + // 2. create a temporary OutputStream and pass it to the handler method + // 3. retrieve this temporary stream to log it (if enabled) + // 4. write it back to the OutputStream provided by Lambda + OutputStream backupOutputStream = null; + if ((logging.logResponse() || "true".equals(POWERTOOLS_LOG_RESPONSE)) && isOnRequestStreamHandler) { + backupOutputStream = (OutputStream) proceedArgs[1]; + proceedArgs[1] = new ByteArrayOutputStream(); + } - if (logging.logEvent() || "true".equals(POWERTOOLS_LOG_EVENT)) { - proceedArgs = logEvent(pjp); + Object lambdaFunctionResponse; + + try { + lambdaFunctionResponse = pjp.proceed(proceedArgs); + } catch (Throwable t) { + if (logging.logError() || "true".equals(POWERTOOLS_LOG_ERROR)) { + // logging the exception with additional context + logger(pjp).error(MarkerFactory.getMarker("FATAL"), "Exception", t); + } + throw t; + } finally { + if (logging.clearState()) { + MDC.clear(); + } + coldStartDone(); } - if (!logging.correlationIdPath().isEmpty()) { - proceedArgs = captureCorrelationId(logging.correlationIdPath(), pjp); + if ((logging.logResponse() || "true".equals(POWERTOOLS_LOG_RESPONSE))) { + if (isOnRequestHandler) { + logRequestHandlerResponse(pjp, lambdaFunctionResponse); + } else if (isOnRequestStreamHandler && backupOutputStream != null) { + byte[] bytes = ((ByteArrayOutputStream)proceedArgs[1]).toByteArray(); + logRequestStreamHandlerResponse(pjp, bytes); + backupOutputStream.write(bytes); + } } - Object proceed = pjp.proceed(proceedArgs); + return lambdaFunctionResponse; + } + + private Object[] logEvent(ProceedingJoinPoint pjp, Logging logging, + boolean isOnRequestHandler, boolean isOnRequestStreamHandler) { + Object[] proceedArgs = pjp.getArgs(); - if (logging.clearState()) { - MDC.clear(); + if (logging.logEvent() || "true".equals(POWERTOOLS_LOG_EVENT)) { + if (isOnRequestHandler) { + logRequestHandlerEvent(pjp, pjp.getArgs()[0]); + } else if (isOnRequestStreamHandler) { + proceedArgs = logRequestStreamHandlerEvent(pjp); + } } + return proceedArgs; + } - coldStartDone(); - return proceed; + private void addLambdaContextToLoggingContext(ProceedingJoinPoint pjp) { + Context extractedContext = extractContext(pjp); + + if (extractedContext != null) { + appendKeys(PowertoolsLoggedFields.setValuesFromLambdaContext(extractedContext)); + appendKey(FUNCTION_COLD_START.getName(), isColdStart() ? "true" : "false"); + appendKey(SERVICE.getName(), serviceName()); + } } private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, @@ -234,52 +286,72 @@ private double samplingRate(final Logging logging) { return logging.samplingRate(); } - private Object[] logEvent(final ProceedingJoinPoint pjp) { - Object[] args = pjp.getArgs(); - LoggingUtils.logMessagesAsJson(true); - - if (placedOnRequestHandler(pjp)) { - Logger log = logger(pjp); - asJson(pjp, pjp.getArgs()[0]) - .ifPresent(log::info); - } else if (placedOnStreamHandler(pjp)) { - args = logFromInputStream(pjp); + private void logRequestHandlerEvent(final ProceedingJoinPoint pjp, final Object event) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + LoggingUtils.logMessagesAsJson(true); + asJson(event).ifPresent(log::info); + LoggingUtils.logMessagesAsJson(false); } - - LoggingUtils.logMessagesAsJson(false); - return args; } - private Object[] captureCorrelationId(final String correlationIdPath, - final ProceedingJoinPoint pjp) { + private Object[] logRequestStreamHandlerEvent(final ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + LoggingUtils.logMessagesAsJson(true); + try { + byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); + args[0] = new ByteArrayInputStream(bytes); + log.info("{}", new String(bytes, UTF_8)); // do not log asJson as it can be something else (String, XML...) + } catch (IOException e) { + LOG.warn("Failed to log event from supplied input stream.", e); + } + LoggingUtils.logMessagesAsJson(false); + } + return args; + } - if (placedOnRequestHandler(pjp)) { - Object arg = pjp.getArgs()[0]; - JsonNode jsonNode = JsonConfig.get().getObjectMapper().valueToTree(arg); + private void logRequestHandlerResponse(final ProceedingJoinPoint pjp, final Object response) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + LoggingUtils.logMessagesAsJson(true); + asJson(response).ifPresent(log::info); + LoggingUtils.logMessagesAsJson(false); + } + } - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); + private void logRequestStreamHandlerResponse(final ProceedingJoinPoint pjp, final byte[] bytes) { + Logger log = logger(pjp); + if (log.isInfoEnabled()) { + LoggingUtils.logMessagesAsJson(true); + // we do not log with asJson as it can be something else (string, xml, ...) + log.info("{}", new String(bytes, UTF_8)); + LoggingUtils.logMessagesAsJson(false); + } + } - return args; - } else if (placedOnStreamHandler(pjp)) { + private void captureCorrelationId(final String correlationIdPath, + Object[] proceedArgs, + final boolean isOnRequestHandler, + final boolean isOnRequestStreamHandler) { + if (isOnRequestHandler) { + JsonNode jsonNode = JsonConfig.get().getObjectMapper().valueToTree(proceedArgs[0]); + setCorrelationIdFromNode(correlationIdPath, jsonNode); + } else if (isOnRequestStreamHandler) { try { - byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); + byte[] bytes = bytesFromInputStreamSafely((InputStream) proceedArgs[0]); JsonNode jsonNode = JsonConfig.get().getObjectMapper().readTree(bytes); - args[0] = new ByteArrayInputStream(bytes); - - setCorrelationIdFromNode(correlationIdPath, pjp, jsonNode); + proceedArgs[0] = new ByteArrayInputStream(bytes); - return args; + setCorrelationIdFromNode(correlationIdPath, jsonNode); } catch (IOException e) { - Logger log = logger(pjp); - log.warn("Failed to capture correlation id on event from supplied input stream.", e); + LOG.warn("Failed to capture correlation id on event from supplied input stream.", e); } } - - return args; } - private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPoint pjp, JsonNode jsonNode) { + private void setCorrelationIdFromNode(String correlationIdPath, JsonNode jsonNode) { Expression jmesExpression = JsonConfig.get().getJmesPath().compile(correlationIdPath); JsonNode node = jmesExpression.search(jsonNode); @@ -287,28 +359,10 @@ private void setCorrelationIdFromNode(String correlationIdPath, ProceedingJoinPo if (null != asText && !asText.isEmpty()) { LoggingUtils.setCorrelationId(asText); } else { - logger(pjp).warn("Unable to extract any correlation id. Is your function expecting supported event type?"); + LOG.warn("Unable to extract any correlation id. Is your function expecting supported event type?"); } } - private Object[] logFromInputStream(final ProceedingJoinPoint pjp) { - Object[] args = pjp.getArgs(); - - try { - byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); - args[0] = new ByteArrayInputStream(bytes); - Logger log = logger(pjp); - - asJson(pjp, JsonConfig.get().getObjectMapper().readValue(bytes, Map.class)) - .ifPresent(log::info); - - } catch (IOException e) { - Logger log = logger(pjp); - log.warn("Failed to log event from supplied input stream.", e); - } - - return args; - } private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws IOException { try (ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -324,12 +378,11 @@ private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws } } - private Optional asJson(final ProceedingJoinPoint pjp, - final Object target) { + private Optional asJson(final Object target) { try { return ofNullable(JsonConfig.get().getObjectMapper().writeValueAsString(target)); } catch (JsonProcessingException e) { - logger(pjp).error("Failed logging event of type {}", target.getClass(), e); + LOG.error("Failed logging object of type {}", target.getClass(), e); return empty(); } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java index 301f40ba2..09d834dc9 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java @@ -23,6 +23,10 @@ class LoggingConstants { static String POWERTOOLS_LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ + static String POWERTOOLS_LOG_RESPONSE = System.getenv("POWERTOOLS_LOGGER_LOG_RESPONSE"); /* not final for test purpose */ + + static String POWERTOOLS_LOG_ERROR = System.getenv("POWERTOOLS_LOGGER_LOG_ERROR"); /* not final for test purpose */ + private LoggingConstants() { // constants } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java index 5186ae290..d6c79a445 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogEnabled.java @@ -22,15 +22,27 @@ public class PowertoolsLogEnabled implements RequestHandler { private final Logger LOG = LoggerFactory.getLogger(PowertoolsLogEnabled.class); + private final boolean throwError; + + public PowertoolsLogEnabled(boolean throwError) { + this.throwError = throwError; + } + + public PowertoolsLogEnabled() { + this(false); + } @Override @Logging public Object handleRequest(Object input, Context context) { + if (throwError) { + throw new RuntimeException("Something went wrong"); + } LOG.error("Test error event"); LOG.warn("Test warn event"); LOG.info("Test event"); LOG.debug("Test debug event"); - return null; + return "Bonjour le monde"; } @Logging diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java new file mode 100644 index 000000000..a6b1ed915 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogError.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogError implements RequestHandler { + + @Override + @Logging(logError = true) + public Object handleRequest(Object input, Context context) { + throw new UnsupportedOperationException("This is an error"); + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java new file mode 100644 index 000000000..001bde3ed --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponse implements RequestHandler { + + @Override + @Logging(logResponse = true) + public Object handleRequest(Object input, Context context) { + return "Hola mundo"; + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java new file mode 100644 index 000000000..38be5c025 --- /dev/null +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/handlers/PowertoolsLogResponseForStream.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.handlers; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import software.amazon.lambda.powertools.logging.Logging; + +public class PowertoolsLogResponseForStream implements RequestStreamHandler { + + @Override + @Logging(logResponse = true) + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + byte[] buf = new byte[1024]; + int length; + while ((length = inputStream.read(buf)) != -1) { + outputStream.write(buf, 0, length); + } + } +} diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 1838d9ac7..47c98c88e 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -68,7 +68,6 @@ import org.slf4j.event.Level; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; import software.amazon.lambda.powertools.common.internal.SystemWrapper; -import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventDisabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogAlbCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayHttpApiCorrelationId; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogApiGatewayRestApiCorrelationId; @@ -78,9 +77,13 @@ import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogDisabledForStream; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEnabledForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogError; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEvent; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventBridgeCorrelationId; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventDisabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogEventForStream; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogResponse; +import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogResponseForStream; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingDisabled; import software.amazon.lambda.powertools.logging.handlers.PowertoolsLogSamplingEnabled; @@ -523,6 +526,95 @@ void shouldLogEventForStreamHandler() throws IOException { assertThat(contentOf(logFile)).contains("{\"key\":\"value\"}"); } + @Test + void shouldLogResponseForHandlerWithLogResponseAnnotation() { + // GIVEN + requestHandler = new PowertoolsLogResponse(); + + // WHEN + requestHandler.handleRequest("input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Hola mundo"); + } + + @Test + void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_RESPONSE = "true"; + + requestHandler = new PowertoolsLogEnabled(); + + // WHEN + requestHandler.handleRequest("input", context); + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Bonjour le monde"); + } finally { + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_RESPONSE", "false", true); + } + } + + @Test + void shouldLogResponseForStreamHandler() throws IOException { + // GIVEN + requestStreamHandler = new PowertoolsLogResponseForStream(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + String input = "BobThe Sponge"; + + // WHEN + requestStreamHandler.handleRequest(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), output, context); + + // THEN + assertThat(new String(output.toByteArray(), StandardCharsets.UTF_8)) + .isEqualTo(input); + + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains(input); + } + + @Test + void shouldLogErrorForHandlerWithLogErrorAnnotation() { + // GIVEN + requestHandler = new PowertoolsLogError(); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("This is an error"); + } + + @Test + void shouldLogErrorForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { + try { + // GIVEN + LoggingConstants.POWERTOOLS_LOG_ERROR = "true"; + + requestHandler = new PowertoolsLogEnabled(true); + + // WHEN + try { + requestHandler.handleRequest("input", context); + } catch (Exception e) { + // ignore + } + // THEN + File logFile = new File("target/logfile.json"); + assertThat(contentOf(logFile)).contains("Something went wrong"); + } finally { + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_ERROR", "false", true); + } + } + @ParameterizedTest @Event(value = "apiGatewayProxyEventV1.json", type = APIGatewayProxyRequestEvent.class) void shouldLogCorrelationIdOnAPIGatewayProxyRequestEvent(APIGatewayProxyRequestEvent event) { From 5ba6d1a998bf60dcf2f78931411a8ab0613b8f56 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 1 Dec 2023 21:32:26 +0100 Subject: [PATCH 57/74] fix modules in e2e-parent --- powertools-e2e-tests/handlers/pom.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index e7eff28c7..2bf76d22c 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -30,9 +30,13 @@ logging tracing metrics + batch idempotency + largemessage + largemessage_idempotent parameters - validation + validation-alb-event + validation-apigw-event From 75f866fb8388c535e33e345023262690d2314332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Van=20Der=20Linden?= <117538+jeromevdl@users.noreply.github.com> Date: Wed, 6 Dec 2023 09:43:38 +0100 Subject: [PATCH 58/74] Apply suggestions from code review Co-authored-by: Scott Gerring --- powertools-logging/pom.xml | 2 +- .../amazon/lambda/powertools/logging/LambdaJsonEncoder.java | 1 - .../powertools/logging/internal/LambdaJsonSerializer.java | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 87d658748..67e92deda 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -27,7 +27,7 @@ jar Powertools for AWS Lambda (Java) library Logging - Set of utility for better logging - common + Set of utilities to improve Lambda function handler logging - common https://aws.amazon.com/lambda/ GitHub Issues diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java index c70cc2533..9d375089d 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java @@ -28,7 +28,6 @@ /** * Custom encoder for logback that encodes logs in JSON format. * It does not use a JSON library but a custom serializer ({@link LambdaJsonSerializer}) - * to reduce the weight of the library. */ public class LambdaJsonEncoder extends EncoderBase { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java index 9d0e3cdd0..fbe11adec 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java @@ -34,8 +34,7 @@ /** * This class will serialize the log events in json.
    *

    - * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer, this class doesn't use - * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency + * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer */ public class LambdaJsonSerializer { protected static final String TIMESTAMP_ATTR_NAME = "timestamp"; From 313f6da43e14fa0a406eccc1cfdb75e22ae49164 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Wed, 6 Dec 2023 23:59:58 +0100 Subject: [PATCH 59/74] post review changes --- examples/pom.xml | 2 +- examples/powertools-examples-batch/pom.xml | 12 +++++++- .../pom.xml | 6 ++-- .../cdk/app/pom.xml | 2 +- .../gradle/build.gradle | 4 ++- .../kotlin/build.gradle.kts | 16 ++++++----- .../sam/pom.xml | 2 +- .../serverless/pom.xml | 10 ++++++- .../terraform/pom.xml | 10 ++++++- .../powertools-examples-idempotency/pom.xml | 12 +++++++- .../powertools-examples-parameters/pom.xml | 13 ++++++++- .../powertools-examples-serialization/pom.xml | 13 ++++++++- .../powertools-examples-validation/pom.xml | 13 ++++++++- pom.xml | 2 +- powertools-batch/pom.xml | 2 +- powertools-cloudformation/pom.xml | 2 +- powertools-common/pom.xml | 2 +- powertools-e2e-tests/handlers/batch/pom.xml | 8 ++++++ .../handlers/idempotency/pom.xml | 1 - .../handlers/largemessage/pom.xml | 9 +++--- .../handlers/largemessage_idempotent/pom.xml | 9 +++--- powertools-e2e-tests/handlers/logging/pom.xml | 2 -- .../handlers/parameters/pom.xml | 5 +++- powertools-e2e-tests/handlers/pom.xml | 8 ++++++ powertools-e2e-tests/pom.xml | 2 +- powertools-idempotency/pom.xml | 2 +- powertools-large-messages/pom.xml | 2 +- powertools-logging/pom.xml | 27 ++---------------- .../powertools-logging-log4j/pom.xml | 27 ++---------------- .../logging/internal/Log4jLoggingManager.java | 2 +- .../json/resolver/PowertoolsResolverTest.java | 2 +- .../internal/Log4jLoggingManagerTest.java | 2 +- .../powertools-logging-logback/pom.xml | 28 ++----------------- .../powertools/logging/LambdaEcsEncoder.java | 2 +- .../internal/LogbackLoggingManager.java | 2 +- .../logging/LogbackLoggingManagerTest.java | 2 +- .../powertools/logging/LoggingUtils.java | 12 +++++++- .../logging/internal/LambdaLoggingAspect.java | 4 +-- .../logging/internal/LoggingManager.java | 5 ++-- .../internal/PowertoolsLoggedFields.java | 4 ++- .../java/org/slf4j/test/OutputChoice.java | 3 +- .../logging/internal/TestLoggingManager.java | 2 +- powertools-metrics/pom.xml | 2 +- powertools-parameters/pom.xml | 2 +- powertools-serialization/pom.xml | 2 +- powertools-tracing/pom.xml | 2 +- powertools-validation/pom.xml | 2 +- 47 files changed, 168 insertions(+), 137 deletions(-) diff --git a/examples/pom.xml b/examples/pom.xml index 3526be2a4..943cad950 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -23,7 +23,7 @@ 2.0.0-SNAPSHOT pom - Powertools for AWS Lambda (Java) library Examples + Powertools for AWS Lambda (Java) - Examples A suite of examples accompanying for Powertools for AWS Lambda (Java). diff --git a/examples/powertools-examples-batch/pom.xml b/examples/powertools-examples-batch/pom.xml index 775737acf..4187f0af5 100644 --- a/examples/powertools-examples-batch/pom.xml +++ b/examples/powertools-examples-batch/pom.xml @@ -8,7 +8,7 @@ 2.0.0-SNAPSHOT powertools-examples-batch jar - Powertools for AWS Lambda (Java) library Examples - Batch + Powertools for AWS Lambda (Java) - Examples - Batch 2.20.0 @@ -29,6 +29,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + software.amazon.lambda powertools-batch diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml index e93a455fc..9dd2163a4 100644 --- a/examples/powertools-examples-cloudformation/pom.xml +++ b/examples/powertools-examples-cloudformation/pom.xml @@ -7,7 +7,7 @@ powertools-examples-cloudformation jar - Powertools for AWS Lambda (Java) library Examples - CloudFormation + Powertools for AWS Lambda (Java) - Examples - CloudFormation 2.20.0 @@ -54,12 +54,12 @@ org.apache.logging.log4j - log4j-core + log4j-slf4j2-impl ${log4j.version} org.apache.logging.log4j - log4j-api + log4j-core ${log4j.version} diff --git a/examples/powertools-examples-core-utilities/cdk/app/pom.xml b/examples/powertools-examples-core-utilities/cdk/app/pom.xml index 822e87633..e6d6e7982 100644 --- a/examples/powertools-examples-core-utilities/cdk/app/pom.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - Powertools for AWS Lambda (Java) library Examples - Core Utilities (logging, tracing, metrics) with CDK + Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with CDK software.amazon.lambda.examples diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index 32ac30d5d..ba38e83e8 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -28,7 +28,9 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' - implementation 'org.aspectj:aspectjrt:1.9.7' + implementation 'org.apache.logging.log4:log4j-slf4j2-impl:2.20.0' + implementation 'org.apache.logging.log4j:log4j-core:2.20.0' + implementation 'org.aspectj:aspectjrt:1.9.20.1' aspect 'software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT' aspect 'software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT' aspect 'software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT' diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index 4b4b6c18a..bef141a41 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -9,16 +9,18 @@ repositories { } dependencies { - implementation("com.amazonaws:aws-lambda-java-core:1.2.2") - implementation("com.fasterxml.jackson.core:jackson-annotations:2.13.2") - implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2.2") - implementation("com.amazonaws:aws-lambda-java-events:3.11.0") - implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2") - implementation("org.aspectj:aspectjrt:1.9.7") + implementation("com.amazonaws:aws-lambda-java-core:1.2.3") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.15.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") + implementation("com.amazonaws:aws-lambda-java-events:3.11.3") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") + implementation("org.aspectj:aspectjrt:1.9.20.1") + implementation("org.apache.logging.log4:log4j-slf4j2-impl:2.20.0") + implementation("org.apache.logging.log4j:log4j-core:2.20.0") aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10") } kotlin { diff --git a/examples/powertools-examples-core-utilities/sam/pom.xml b/examples/powertools-examples-core-utilities/sam/pom.xml index 289b7d18e..1489af9c3 100644 --- a/examples/powertools-examples-core-utilities/sam/pom.xml +++ b/examples/powertools-examples-core-utilities/sam/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - Powertools for AWS Lambda (Java) library Examples - Core Utilities (logging, tracing, metrics) with SAM + Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with SAM software.amazon.lambda.examples 2.0.0-SNAPSHOT powertools-examples-core-utilities-sam diff --git a/examples/powertools-examples-core-utilities/serverless/pom.xml b/examples/powertools-examples-core-utilities/serverless/pom.xml index e267e281a..d1fbc933c 100644 --- a/examples/powertools-examples-core-utilities/serverless/pom.xml +++ b/examples/powertools-examples-core-utilities/serverless/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - Powertools for AWS Lambda (Java) library Examples - Core Utilities (logging, tracing, metrics) with Serverless + Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with Serverless software.amazon.lambda.examples 2.0.0-SNAPSHOT powertools-examples-core-utilities-serverless @@ -26,6 +26,14 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.apache.logging.log4j + log4j-core + software.amazon.lambda powertools-metrics diff --git a/examples/powertools-examples-core-utilities/terraform/pom.xml b/examples/powertools-examples-core-utilities/terraform/pom.xml index 68ee3b5d9..d0105c93f 100644 --- a/examples/powertools-examples-core-utilities/terraform/pom.xml +++ b/examples/powertools-examples-core-utilities/terraform/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - Powertools for AWS Lambda (Java) library Examples - Core Utilities (logging, tracing, metrics) with Terraform + Powertools for AWS Lambda (Java) - Examples - Core Utilities (logging, tracing, metrics) with Terraform software.amazon.lambda.examples 2.0.0-SNAPSHOT powertools-examples-core-utilities-terraform @@ -26,6 +26,14 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.apache.logging.log4j + log4j-core + software.amazon.lambda powertools-metrics diff --git a/examples/powertools-examples-idempotency/pom.xml b/examples/powertools-examples-idempotency/pom.xml index 83905d195..122b7d0e8 100644 --- a/examples/powertools-examples-idempotency/pom.xml +++ b/examples/powertools-examples-idempotency/pom.xml @@ -20,7 +20,7 @@ 2.0.0-SNAPSHOT powertools-examples-idempotency jar - Powertools for AWS Lambda (Java) library Examples - Idempotency + Powertools for AWS Lambda (Java) - Examples - Idempotency 2.20.0 @@ -40,6 +40,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + software.amazon.lambda powertools-idempotency diff --git a/examples/powertools-examples-parameters/pom.xml b/examples/powertools-examples-parameters/pom.xml index 398815264..e041ab494 100644 --- a/examples/powertools-examples-parameters/pom.xml +++ b/examples/powertools-examples-parameters/pom.xml @@ -5,12 +5,13 @@ 2.0.0-SNAPSHOT powertools-examples-parameters jar - Powertools for AWS Lambda (Java) library Examples - Parameters + Powertools for AWS Lambda (Java) - Examples - Parameters 11 11 1.9.20 + 2.20.0 @@ -19,6 +20,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + software.amazon.lambda powertools-parameters diff --git a/examples/powertools-examples-serialization/pom.xml b/examples/powertools-examples-serialization/pom.xml index b6374bcd6..c5678c879 100644 --- a/examples/powertools-examples-serialization/pom.xml +++ b/examples/powertools-examples-serialization/pom.xml @@ -5,11 +5,12 @@ 2.0.0-SNAPSHOT powertools-examples-serialization jar - Powertools for AWS Lambda (Java) library Examples - Serialization + Powertools for AWS Lambda (Java) - Examples - Serialization 11 11 + 2.20.0 @@ -18,6 +19,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + software.amazon.lambda powertools-serialization diff --git a/examples/powertools-examples-validation/pom.xml b/examples/powertools-examples-validation/pom.xml index b8a6c8b7e..391936194 100644 --- a/examples/powertools-examples-validation/pom.xml +++ b/examples/powertools-examples-validation/pom.xml @@ -19,12 +19,13 @@ 2.0.0-SNAPSHOT powertools-examples-validation jar - Powertools for AWS Lambda (Java) library Examples - Validation + Powertools for AWS Lambda (Java) - Examples - Validation 1.8 1.8 1.9.20 + 2.20.0 @@ -33,6 +34,16 @@ powertools-logging-log4j ${project.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + software.amazon.lambda powertools-validation diff --git a/pom.xml b/pom.xml index 4ed136d76..ca32c2785 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.0.0-SNAPSHOT pom - Powertools for AWS Lambda (Java) library Parent + Powertools for AWS Lambda (Java) - Parent A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. diff --git a/powertools-batch/pom.xml b/powertools-batch/pom.xml index 309fac10c..eaafdb56e 100644 --- a/powertools-batch/pom.xml +++ b/powertools-batch/pom.xml @@ -10,7 +10,7 @@ A suite of utilities that makes batch message processing using AWS Lambda easier. - Powertools for AWS Lambda (Java) library batch messages + Powertools for AWS Lambda (Java) - Batch messages diff --git a/powertools-cloudformation/pom.xml b/powertools-cloudformation/pom.xml index 0e6763e25..79071d532 100644 --- a/powertools-cloudformation/pom.xml +++ b/powertools-cloudformation/pom.xml @@ -27,7 +27,7 @@ 2.0.0-SNAPSHOT - Powertools for AWS Lambda (Java) library Cloudformation + Powertools for AWS Lambda (Java) - Cloudformation A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. diff --git a/powertools-common/pom.xml b/powertools-common/pom.xml index 8f6752a4d..363d2e944 100644 --- a/powertools-common/pom.xml +++ b/powertools-common/pom.xml @@ -27,7 +27,7 @@ 2.0.0-SNAPSHOT - Powertools for AWS Lambda (Java) library Common Internal Utilities + Powertools for AWS Lambda (Java) - Common Internal Utilities Internal utilities shared by the Powertools for AWS Lambda (Java) modules. Do not use directly in your project. https://aws.amazon.com/lambda/ diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml index 62f43aa1b..024e2b609 100644 --- a/powertools-e2e-tests/handlers/batch/pom.xml +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -21,6 +21,14 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.apache.logging.log4j + log4j-core + com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index ecc6190fe..bb4a0b49b 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -24,7 +24,6 @@ org.apache.logging.log4j log4j-core - 2.20.0 org.apache.logging.log4j diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml index e50695e8f..cd7b35240 100644 --- a/powertools-e2e-tests/handlers/largemessage/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -28,16 +28,15 @@ org.apache.logging.log4j log4j-core - 2.20.0 - - - com.amazonaws - aws-lambda-java-events org.apache.logging.log4j log4j-slf4j2-impl + + com.amazonaws + aws-lambda-java-events + org.aspectj aspectjrt diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml index 3306f055f..3e8366292 100644 --- a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -28,16 +28,15 @@ org.apache.logging.log4j log4j-core - 2.20.0 - - - com.amazonaws - aws-lambda-java-events org.apache.logging.log4j log4j-slf4j2-impl + + com.amazonaws + aws-lambda-java-events + org.aspectj aspectjrt diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index b3cad89a6..140f1d719 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -20,12 +20,10 @@ org.apache.logging.log4j log4j-slf4j2-impl - 2.20.0 org.apache.logging.log4j log4j-core - 2.20.0 org.apache.logging.log4j diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml index ef08aa3a7..b48429aaa 100644 --- a/powertools-e2e-tests/handlers/parameters/pom.xml +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -17,10 +17,13 @@ software.amazon.lambda powertools-logging-log4j + + org.apache.logging.log4j + log4j-slf4j2-impl + org.apache.logging.log4j log4j-core - 2.20.0 software.amazon.lambda diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 2bf76d22c..9e7f4a702 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -58,6 +58,14 @@ powertools-logging-log4j ${lambda.powertools.version} + + org.apache.logging.log4j + log4j-slf4j2-impl + + + org.apache.logging.log4j + log4j-core + software.amazon.lambda powertools-tracing diff --git a/powertools-e2e-tests/pom.xml b/powertools-e2e-tests/pom.xml index bea1648b3..a64e7a354 100644 --- a/powertools-e2e-tests/pom.xml +++ b/powertools-e2e-tests/pom.xml @@ -24,7 +24,7 @@ powertools-e2e-tests - Powertools for AWS Lambda (Java) library End-to-end tests + Powertools for AWS Lambda (Java) - End-to-end tests Powertools for AWS Lambda (Java)End-To-End Tests diff --git a/powertools-idempotency/pom.xml b/powertools-idempotency/pom.xml index b53036399..eda7bd982 100644 --- a/powertools-idempotency/pom.xml +++ b/powertools-idempotency/pom.xml @@ -27,7 +27,7 @@ powertools-idempotency jar - Powertools for AWS Lambda (Java) library Idempotency + Powertools for AWS Lambda (Java) - Idempotency diff --git a/powertools-large-messages/pom.xml b/powertools-large-messages/pom.xml index 72e6e6f1a..af031ff21 100644 --- a/powertools-large-messages/pom.xml +++ b/powertools-large-messages/pom.xml @@ -29,7 +29,7 @@ powertools-large-messages jar - Powertools for AWS Lambda (Java) library Large messages + Powertools for AWS Lambda (Java) - Large messages GitHub Issues diff --git a/powertools-logging/pom.xml b/powertools-logging/pom.xml index 67e92deda..56ca0e62b 100644 --- a/powertools-logging/pom.xml +++ b/powertools-logging/pom.xml @@ -25,31 +25,8 @@ powertools-logging jar - - Powertools for AWS Lambda (Java) library Logging - Set of utilities to improve Lambda function handler logging - common - https://aws.amazon.com/lambda/ - - GitHub Issues - https://github.com/aws-powertools/powertools-lambda-java/issues - - - https://github.com/aws-powertools/powertools-lambda-java.git - - - - Powertools for AWS Lambda team - Amazon Web Services - https://aws.amazon.com/ - - - - - - ossrh - https://aws.oss.sonatype.org/content/repositories/snapshots - - + Powertools for AWS Lambda (Java) - Logging + Set of utility for better logging - common diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 6630f677f..626596cfe 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -13,32 +13,9 @@ powertools-logging-log4j jar - Powertools for AWS Lambda (Java) library Logging with Log4j2 + Powertools for AWS Lambda (Java) - Logging with Log4j2 Set of utility for better logging with log4j - https://aws.amazon.com/lambda/ - - GitHub Issues - https://github.com/awslabs/aws-lambda-powertools-java/issues - - - https://github.com/awslabs/aws-lambda-powertools-java.git - - - - AWS Lambda Powertools team - Amazon Web Services - https://aws.amazon.com/ - - - - - - ossrh - https://aws.oss.sonatype.org/content/repositories/snapshots - - - software.amazon.lambda @@ -53,10 +30,12 @@ org.apache.logging.log4j log4j-slf4j2-impl + provided org.apache.logging.log4j log4j-core + provided org.apache.logging.log4j diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java index a086ff596..a20671739 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java @@ -30,7 +30,7 @@ public class Log4jLoggingManager implements LoggingManager { */ @Override @SuppressWarnings("java:S4792") - public void resetLogLevel(org.slf4j.event.Level logLevel) { + public void setLogLevel(org.slf4j.event.Level logLevel) { LoggerContext ctx = (LoggerContext) LogManager.getContext(false); Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.getLevel(logLevel.toString())); ctx.updateLoggers(); diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java index bbe6f6b75..1aa98fdef 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolverTest.java @@ -40,7 +40,7 @@ class PowertoolsResolverTest { @ParameterizedTest @EnumSource(value = PowertoolsLoggedFields.class, mode = EnumSource.Mode.EXCLUDE, - names = {"FUNCTION_MEMORY_SIZE", "SAMPLING_RATE", "FUNCTION_COLD_START"}) + names = {"FUNCTION_MEMORY_SIZE", "SAMPLING_RATE", "FUNCTION_COLD_START", "CORRELATION_ID"}) void shouldResolveFunctionStringInfo(PowertoolsLoggedFields field) { String result = resolveField(field.getName(), "value"); assertThat(result).isEqualTo("\"value\""); diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java index 0debf88e5..0942eca52 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -38,7 +38,7 @@ void resetLogLevel() { // When Log4jLoggingManager manager = new Log4jLoggingManager(); - manager.resetLogLevel(ERROR); + manager.setLogLevel(ERROR); Level rootLevel = manager.getLogLevel(ROOT); Level logLevel = manager.getLogLevel(LOG); diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index c47c94021..89ca7b7ca 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -2,42 +2,18 @@ + 4.0.0 powertools-parent software.amazon.lambda 2.0.0-SNAPSHOT ../../pom.xml - 4.0.0 powertools-logging-logback - Powertools for AWS Lambda (Java) library Logging with LogBack + Powertools for AWS Lambda (Java) - Logging with LogBack Set of utility for better logging with logback - https://aws.amazon.com/lambda/ - - GitHub Issues - https://github.com/awslabs/aws-lambda-powertools-java/issues - - - https://github.com/awslabs/aws-lambda-powertools-java.git - - - - AWS Lambda Powertools team - Amazon Web Services - https://aws.amazon.com/ - - - - - - ossrh - https://aws.oss.sonatype.org/content/repositories/snapshots - - - - software.amazon.lambda diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java index 4124180f0..1416c8192 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java @@ -35,7 +35,7 @@ /** - * This class will encode the logback event into the format expected by the ECS service (ElasticSearch). + * This class will encode the logback event into the format expected by the Elastic Common Schema (ECS) service (for Elasticsearch). *
    * Inspired from co.elastic.logging.logback.EcsEncoder, this class doesn't use * any JSON (de)serialization library (Jackson, Gson, etc.) or Elastic library to avoid the dependency. diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java index d010e2977..43e534dd9 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LogbackLoggingManager.java @@ -41,7 +41,7 @@ public LogbackLoggingManager() { */ @Override @SuppressWarnings("java:S4792") - public void resetLogLevel(org.slf4j.event.Level logLevel) { + public void setLogLevel(org.slf4j.event.Level logLevel) { List loggers = loggerContext.getLoggerList(); for (Logger logger : loggers) { logger.setLevel(Level.convertAnSLF4JLevel(logLevel)); diff --git a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java index e13e78eb4..311a77a50 100644 --- a/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java +++ b/powertools-logging/powertools-logging-logback/src/test/java/software/amazon/lambda/powertools/logging/LogbackLoggingManagerTest.java @@ -46,7 +46,7 @@ void getLogLevel_shouldReturnConfiguredLogLevel() { @Order(2) void resetLogLevel() { LogbackLoggingManager manager = new LogbackLoggingManager(); - manager.resetLogLevel(ERROR); + manager.setLogLevel(ERROR); Level logLevel = manager.getLogLevel(LOG); assertThat(logLevel).isEqualTo(ERROR); diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index 67eeb6af6..47bcedc52 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -14,6 +14,8 @@ package software.amazon.lambda.powertools.logging; +import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; + import java.util.Arrays; import java.util.Map; import org.slf4j.MDC; @@ -77,7 +79,15 @@ public static void removeKeys(String... keys) { * @param value The value of the correlation id */ public static void setCorrelationId(String value) { - MDC.put("correlation_id", value); + MDC.put(CORRELATION_ID.getName(), value); + } + + /** + * Get correlation id attribute. Maybe null. + * @return correlation id set `@Logging(correlationIdPath="JMESPATH Expression")` or `LoggingUtils.setCorrelationId("value")` + */ + public static String getCorrelationId() { + return MDC.get(CORRELATION_ID.getName()); } /** diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 1f78f6cd8..fc2d30de3 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -147,7 +147,7 @@ private static LoggingManager getLoggingManagerFromServiceLoader() { } private static void resetLogLevels(Level logLevel) { - LOGGING_MANAGER.resetLogLevel(logLevel); + LOGGING_MANAGER.setLogLevel(logLevel); } @SuppressWarnings({"EmptyMethod"}) @@ -195,7 +195,7 @@ public Object around(ProceedingJoinPoint pjp, } catch (Throwable t) { if (logging.logError() || "true".equals(POWERTOOLS_LOG_ERROR)) { // logging the exception with additional context - logger(pjp).error(MarkerFactory.getMarker("FATAL"), "Exception", t); + logger(pjp).error(MarkerFactory.getMarker("FATAL"), "Exception in Lambda Handler", t); } throw t; } finally { diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java index a141910cb..51d05b25d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingManager.java @@ -23,8 +23,9 @@ *

  • Accessing to all loggers and change their Level
  • *
  • Retrieving the log Level of a Logger
  • * + * *

    - * Implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} + * This interface is used for these operations and implementations are provided in submodules and loaded thanks to a {@link java.util.ServiceLoader} * (define a file named software.amazon.lambda.powertools.logging.internal.LoggingManager * in src/main/resources/META-INF/services with the qualified name of the implementation). */ @@ -34,7 +35,7 @@ public interface LoggingManager { * * @param logLevel the log Level (slf4j) to apply */ - void resetLogLevel(Level logLevel); + void setLogLevel(Level logLevel); /** * Retrieve the log Level of a specific logger diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java index 686c8c0c2..6e0047f4f 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/PowertoolsLoggedFields.java @@ -22,7 +22,8 @@ import java.util.stream.Stream; /** - * Fields added in the logs by Powertools + * Fields added in the logs by Powertools. + * Same as python */ public enum PowertoolsLoggedFields { FUNCTION_NAME("function_name"), @@ -33,6 +34,7 @@ public enum PowertoolsLoggedFields { FUNCTION_COLD_START("cold_start"), FUNCTION_TRACE_ID("xray_trace_id"), SAMPLING_RATE("sampling_rate"), + CORRELATION_ID("correlation_id"), SERVICE("service"); private final String name; diff --git a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java index 164862e00..9a6d56b81 100644 --- a/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java +++ b/powertools-logging/src/test/java/org/slf4j/test/OutputChoice.java @@ -29,8 +29,7 @@ /** * This class encapsulates the user's choice of output target. * - * @author Ceki Gülcü - * + * @see ... */ class OutputChoice { diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java index 03f992966..0958e0d3b 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/TestLoggingManager.java @@ -21,7 +21,7 @@ public TestLoggingManager() { } @Override - public void resetLogLevel(Level logLevel) { + public void setLogLevel(Level logLevel) { loggerFactory.getLoggers().forEach((key, logger) -> ((TestLogger) logger).setLogLevel(logLevel.toString())); } diff --git a/powertools-metrics/pom.xml b/powertools-metrics/pom.xml index 142852299..7de1efa3f 100644 --- a/powertools-metrics/pom.xml +++ b/powertools-metrics/pom.xml @@ -27,7 +27,7 @@ 2.0.0-SNAPSHOT - Powertools for AWS Lambda (Java) library Metrics + Powertools for AWS Lambda (Java) - Metrics A suite of utilities for AWS Lambda Functions that make creating custom metrics via AWS Embedded Metric Format asynchronously easier. diff --git a/powertools-parameters/pom.xml b/powertools-parameters/pom.xml index 3895ef475..36f8e9aa7 100644 --- a/powertools-parameters/pom.xml +++ b/powertools-parameters/pom.xml @@ -26,7 +26,7 @@ powertools-parameters - Powertools for AWS Lambda (Java) library Parameters + Powertools for AWS Lambda (Java) - Parameters Set of utilities to retrieve parameters from Secrets Manager or SSM Parameter Store diff --git a/powertools-serialization/pom.xml b/powertools-serialization/pom.xml index 911663507..d1b2de826 100644 --- a/powertools-serialization/pom.xml +++ b/powertools-serialization/pom.xml @@ -27,7 +27,7 @@ powertools-serialization jar - Powertools for AWS Lambda (Java) library Serialization Utilities + Powertools for AWS Lambda (Java) - Serialization Utilities diff --git a/powertools-tracing/pom.xml b/powertools-tracing/pom.xml index 273b807c8..1625fd0cb 100644 --- a/powertools-tracing/pom.xml +++ b/powertools-tracing/pom.xml @@ -27,7 +27,7 @@ 2.0.0-SNAPSHOT - Powertools for AWS Lambda (Java) library Tracing + Powertools for AWS Lambda (Java) - Tracing A suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. diff --git a/powertools-validation/pom.xml b/powertools-validation/pom.xml index 269513a46..bd57fa6c5 100644 --- a/powertools-validation/pom.xml +++ b/powertools-validation/pom.xml @@ -27,7 +27,7 @@ 2.0.0-SNAPSHOT - Powertools for AWS Lambda (Java) validation library + Powertools for AWS Lambda (Java) - Validation Json schema validation for Lambda events and responses From cff2ee6bb820b3aec80dc2bf44a76ff70187b2f9 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 08:47:42 +0100 Subject: [PATCH 60/74] fix gradle build --- examples/powertools-examples-core-utilities/gradle/build.gradle | 2 +- .../powertools-examples-core-utilities/kotlin/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index ba38e83e8..d8184952e 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -28,7 +28,7 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' - implementation 'org.apache.logging.log4:log4j-slf4j2-impl:2.20.0' + implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' implementation 'org.apache.logging.log4j:log4j-core:2.20.0' implementation 'org.aspectj:aspectjrt:1.9.20.1' aspect 'software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT' diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index bef141a41..261161084 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.amazonaws:aws-lambda-java-events:3.11.3") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") implementation("org.aspectj:aspectjrt:1.9.20.1") - implementation("org.apache.logging.log4:log4j-slf4j2-impl:2.20.0") + implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0") implementation("org.apache.logging.log4j:log4j-core:2.20.0") aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") From 4572bf46390489c54dd411579af17f44e7779a1b Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 09:28:07 +0100 Subject: [PATCH 61/74] fix e2e tests --- powertools-e2e-tests/handlers/batch/pom.xml | 4 ---- powertools-e2e-tests/handlers/logging/pom.xml | 4 ---- powertools-e2e-tests/handlers/pom.xml | 2 ++ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml index 024e2b609..6f98062ac 100644 --- a/powertools-e2e-tests/handlers/batch/pom.xml +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -37,10 +37,6 @@ com.amazonaws aws-lambda-java-serialization - - org.apache.logging.log4j - log4j-slf4j2-impl - software.amazon.awssdk dynamodb diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 140f1d719..8257f7f30 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -38,10 +38,6 @@ com.amazonaws aws-lambda-java-events - - org.aspectj - aspectjrt - diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 9e7f4a702..74aecbf80 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -61,10 +61,12 @@ org.apache.logging.log4j log4j-slf4j2-impl + ${log4j.version} org.apache.logging.log4j log4j-core + ${log4j.version} software.amazon.lambda From 12b284475684c70a4ca3593c3100ee951e065b98 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 12:10:52 +0100 Subject: [PATCH 62/74] update poms (transitive dependency for log4j) --- examples/powertools-examples-batch/pom.xml | 13 +---------- .../pom.xml | 21 +---------------- .../cdk/app/pom.xml | 2 +- .../gradle/build.gradle | 2 -- .../kotlin/build.gradle.kts | 2 -- .../sam/pom.xml | 13 +---------- .../serverless/pom.xml | 21 +---------------- .../terraform/pom.xml | 21 +---------------- .../powertools-examples-idempotency/pom.xml | 23 +------------------ .../powertools-examples-parameters/pom.xml | 13 +---------- .../powertools-examples-serialization/pom.xml | 11 --------- .../powertools-examples-validation/pom.xml | 13 +---------- powertools-e2e-tests/handlers/batch/pom.xml | 8 ------- .../handlers/idempotency/pom.xml | 8 ------- .../handlers/largemessage/pom.xml | 8 ------- .../handlers/largemessage_idempotent/pom.xml | 8 ------- powertools-e2e-tests/handlers/logging/pom.xml | 8 ------- .../handlers/parameters/pom.xml | 8 ------- powertools-e2e-tests/handlers/pom.xml | 18 +-------------- .../powertools-logging-log4j/pom.xml | 2 -- .../powertools-logging-logback/pom.xml | 1 + .../powertools/logging/LoggingUtils.java | 21 +++++++++++++++++ .../logging/internal/LambdaLoggingAspect.java | 6 ++--- 23 files changed, 35 insertions(+), 216 deletions(-) diff --git a/examples/powertools-examples-batch/pom.xml b/examples/powertools-examples-batch/pom.xml index 4187f0af5..a1b4c0bbc 100644 --- a/examples/powertools-examples-batch/pom.xml +++ b/examples/powertools-examples-batch/pom.xml @@ -11,10 +11,9 @@ Powertools for AWS Lambda (Java) - Examples - Batch - 2.20.0 11 11 - 1.9.20 + 1.9.20.1 2.21.1 @@ -29,16 +28,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-batch diff --git a/examples/powertools-examples-cloudformation/pom.xml b/examples/powertools-examples-cloudformation/pom.xml index 9dd2163a4..bee97e52a 100644 --- a/examples/powertools-examples-cloudformation/pom.xml +++ b/examples/powertools-examples-cloudformation/pom.xml @@ -10,13 +10,12 @@ Powertools for AWS Lambda (Java) - Examples - CloudFormation - 2.20.0 11 11 1.2.3 3.11.3 2.21.0 - 1.9.20 + 1.9.20.1 @@ -52,16 +51,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - org.aspectj aspectjrt @@ -91,14 +80,6 @@ - - org.apache.logging.log4j - log4j-jcl - ${log4j.version} - - - - diff --git a/examples/powertools-examples-core-utilities/cdk/app/pom.xml b/examples/powertools-examples-core-utilities/cdk/app/pom.xml index e6d6e7982..f8d340f3b 100644 --- a/examples/powertools-examples-core-utilities/cdk/app/pom.xml +++ b/examples/powertools-examples-core-utilities/cdk/app/pom.xml @@ -14,7 +14,7 @@ 2.20.0 11 11 - 1.9.20 + 1.9.20.1 diff --git a/examples/powertools-examples-core-utilities/gradle/build.gradle b/examples/powertools-examples-core-utilities/gradle/build.gradle index d8184952e..38cf96c1c 100644 --- a/examples/powertools-examples-core-utilities/gradle/build.gradle +++ b/examples/powertools-examples-core-utilities/gradle/build.gradle @@ -28,8 +28,6 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.2' implementation 'com.amazonaws:aws-lambda-java-events:3.11.0' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.2' - implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' - implementation 'org.apache.logging.log4j:log4j-core:2.20.0' implementation 'org.aspectj:aspectjrt:1.9.20.1' aspect 'software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT' aspect 'software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT' diff --git a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts index 261161084..b37ef6d31 100644 --- a/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts +++ b/examples/powertools-examples-core-utilities/kotlin/build.gradle.kts @@ -15,8 +15,6 @@ dependencies { implementation("com.amazonaws:aws-lambda-java-events:3.11.3") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") implementation("org.aspectj:aspectjrt:1.9.20.1") - implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0") - implementation("org.apache.logging.log4j:log4j-core:2.20.0") aspect("software.amazon.lambda:powertools-tracing:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-logging-log4j:2.0.0-SNAPSHOT") aspect("software.amazon.lambda:powertools-metrics:2.0.0-SNAPSHOT") diff --git a/examples/powertools-examples-core-utilities/sam/pom.xml b/examples/powertools-examples-core-utilities/sam/pom.xml index 1489af9c3..c9d90d281 100644 --- a/examples/powertools-examples-core-utilities/sam/pom.xml +++ b/examples/powertools-examples-core-utilities/sam/pom.xml @@ -9,10 +9,9 @@ jar - 2.20.0 11 11 - 1.9.20 + 1.9.20.1 @@ -26,16 +25,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - software.amazon.lambda powertools-metrics diff --git a/examples/powertools-examples-core-utilities/serverless/pom.xml b/examples/powertools-examples-core-utilities/serverless/pom.xml index d1fbc933c..32cca9bb4 100644 --- a/examples/powertools-examples-core-utilities/serverless/pom.xml +++ b/examples/powertools-examples-core-utilities/serverless/pom.xml @@ -9,10 +9,9 @@ jar - 2.20.0 11 11 - 1.9.20 + 1.9.20.1 @@ -26,14 +25,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-core - software.amazon.lambda powertools-metrics @@ -49,16 +40,6 @@ aws-lambda-java-events 3.11.3 - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - org.aspectj aspectjrt diff --git a/examples/powertools-examples-core-utilities/terraform/pom.xml b/examples/powertools-examples-core-utilities/terraform/pom.xml index d0105c93f..c6f838619 100644 --- a/examples/powertools-examples-core-utilities/terraform/pom.xml +++ b/examples/powertools-examples-core-utilities/terraform/pom.xml @@ -9,10 +9,9 @@ jar - 2.20.0 11 11 - 1.9.20 + 1.9.20.1 @@ -26,14 +25,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-core - software.amazon.lambda powertools-metrics @@ -49,16 +40,6 @@ aws-lambda-java-events 3.11.3 - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - org.aspectj aspectjrt diff --git a/examples/powertools-examples-idempotency/pom.xml b/examples/powertools-examples-idempotency/pom.xml index 122b7d0e8..13ea8dec1 100644 --- a/examples/powertools-examples-idempotency/pom.xml +++ b/examples/powertools-examples-idempotency/pom.xml @@ -23,10 +23,9 @@ Powertools for AWS Lambda (Java) - Examples - Idempotency - 2.20.0 11 11 - 1.9.20 + 1.9.20.1 @@ -40,16 +39,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-idempotency @@ -65,16 +54,6 @@ aws-lambda-java-events 3.11.3 - - org.apache.logging.log4j - log4j-core - ${log4j.version} - - - org.apache.logging.log4j - log4j-api - ${log4j.version} - org.aspectj aspectjrt diff --git a/examples/powertools-examples-parameters/pom.xml b/examples/powertools-examples-parameters/pom.xml index e041ab494..c16bbd41e 100644 --- a/examples/powertools-examples-parameters/pom.xml +++ b/examples/powertools-examples-parameters/pom.xml @@ -10,8 +10,7 @@ 11 11 - 1.9.20 - 2.20.0 + 1.9.20.1 @@ -20,16 +19,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-parameters diff --git a/examples/powertools-examples-serialization/pom.xml b/examples/powertools-examples-serialization/pom.xml index c5678c879..2c8fc951a 100644 --- a/examples/powertools-examples-serialization/pom.xml +++ b/examples/powertools-examples-serialization/pom.xml @@ -10,7 +10,6 @@ 11 11 - 2.20.0 @@ -19,16 +18,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-serialization diff --git a/examples/powertools-examples-validation/pom.xml b/examples/powertools-examples-validation/pom.xml index 391936194..ed33568cb 100644 --- a/examples/powertools-examples-validation/pom.xml +++ b/examples/powertools-examples-validation/pom.xml @@ -24,8 +24,7 @@ 1.8 1.8 - 1.9.20 - 2.20.0 + 1.9.20.1 @@ -34,16 +33,6 @@ powertools-logging-log4j ${project.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-validation diff --git a/powertools-e2e-tests/handlers/batch/pom.xml b/powertools-e2e-tests/handlers/batch/pom.xml index 6f98062ac..cab543f6c 100644 --- a/powertools-e2e-tests/handlers/batch/pom.xml +++ b/powertools-e2e-tests/handlers/batch/pom.xml @@ -21,14 +21,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-core - com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/idempotency/pom.xml b/powertools-e2e-tests/handlers/idempotency/pom.xml index bb4a0b49b..36cf74b3b 100644 --- a/powertools-e2e-tests/handlers/idempotency/pom.xml +++ b/powertools-e2e-tests/handlers/idempotency/pom.xml @@ -21,14 +21,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-slf4j2-impl - com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/largemessage/pom.xml b/powertools-e2e-tests/handlers/largemessage/pom.xml index cd7b35240..e2b4f71d0 100644 --- a/powertools-e2e-tests/handlers/largemessage/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage/pom.xml @@ -25,14 +25,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-slf4j2-impl - com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml index 3e8366292..25238e034 100644 --- a/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml +++ b/powertools-e2e-tests/handlers/largemessage_idempotent/pom.xml @@ -25,14 +25,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-slf4j2-impl - com.amazonaws aws-lambda-java-events diff --git a/powertools-e2e-tests/handlers/logging/pom.xml b/powertools-e2e-tests/handlers/logging/pom.xml index 8257f7f30..1375fcad8 100644 --- a/powertools-e2e-tests/handlers/logging/pom.xml +++ b/powertools-e2e-tests/handlers/logging/pom.xml @@ -17,14 +17,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-core - org.apache.logging.log4j log4j-layout-template-json diff --git a/powertools-e2e-tests/handlers/parameters/pom.xml b/powertools-e2e-tests/handlers/parameters/pom.xml index b48429aaa..d69856a10 100644 --- a/powertools-e2e-tests/handlers/parameters/pom.xml +++ b/powertools-e2e-tests/handlers/parameters/pom.xml @@ -17,14 +17,6 @@ software.amazon.lambda powertools-logging-log4j - - org.apache.logging.log4j - log4j-slf4j2-impl - - - org.apache.logging.log4j - log4j-core - software.amazon.lambda powertools-parameters diff --git a/powertools-e2e-tests/handlers/pom.xml b/powertools-e2e-tests/handlers/pom.xml index 74aecbf80..beded56e0 100644 --- a/powertools-e2e-tests/handlers/pom.xml +++ b/powertools-e2e-tests/handlers/pom.xml @@ -22,8 +22,7 @@ 1.13.1 3.11.0 2.20.108 - 2.20.0 - 1.9.20 + 1.9.20.1 @@ -58,16 +57,6 @@ powertools-logging-log4j ${lambda.powertools.version} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - - - org.apache.logging.log4j - log4j-core - ${log4j.version} - software.amazon.lambda powertools-tracing @@ -118,11 +107,6 @@ aws-lambda-java-serialization ${lambda.java.serialization} - - org.apache.logging.log4j - log4j-slf4j2-impl - ${log4j.version} - diff --git a/powertools-logging/powertools-logging-log4j/pom.xml b/powertools-logging/powertools-logging-log4j/pom.xml index 626596cfe..df6154560 100644 --- a/powertools-logging/powertools-logging-log4j/pom.xml +++ b/powertools-logging/powertools-logging-log4j/pom.xml @@ -30,12 +30,10 @@ org.apache.logging.log4j log4j-slf4j2-impl - provided org.apache.logging.log4j log4j-core - provided org.apache.logging.log4j diff --git a/powertools-logging/powertools-logging-logback/pom.xml b/powertools-logging/powertools-logging-logback/pom.xml index 89ca7b7ca..99fff3ab9 100644 --- a/powertools-logging/powertools-logging-logback/pom.xml +++ b/powertools-logging/powertools-logging-logback/pom.xml @@ -28,6 +28,7 @@ ch.qos.logback logback-classic + 1.3.4 provided diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index 47bcedc52..6b7298524 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -16,9 +16,11 @@ import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.CORRELATION_ID; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Arrays; import java.util.Map; import org.slf4j.MDC; +import software.amazon.lambda.powertools.utilities.JsonConfig; /** * A class of helper functions to add additional functionality to Logging. @@ -29,6 +31,8 @@ public final class LoggingUtils { public static final String LOG_MESSAGES_AS_JSON = "PowertoolsLogMessagesAsJson"; + private static ObjectMapper objectMapper; + private LoggingUtils() { } @@ -98,4 +102,21 @@ public static String getCorrelationId() { public static void logMessagesAsJson(boolean value) { MDC.put(LOG_MESSAGES_AS_JSON, String.valueOf(value)); } + + /** + * Sets the instance of ObjectMapper object which is used for serialising event when + * {@code @Logging(logEvent = true)}. + * + * @param objectMapper Custom implementation of object mapper to be used for logging serialised event + */ + public static void setObjectMapper(ObjectMapper objectMapper) { + LoggingUtils.objectMapper = objectMapper; + } + + public static ObjectMapper getObjectMapper() { + if (null == objectMapper) { + objectMapper = JsonConfig.get().getObjectMapper(); + } + return objectMapper; + } } diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index fc2d30de3..9ab53718d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -336,12 +336,12 @@ private void captureCorrelationId(final String correlationIdPath, final boolean isOnRequestHandler, final boolean isOnRequestStreamHandler) { if (isOnRequestHandler) { - JsonNode jsonNode = JsonConfig.get().getObjectMapper().valueToTree(proceedArgs[0]); + JsonNode jsonNode = LoggingUtils.getObjectMapper().valueToTree(proceedArgs[0]); setCorrelationIdFromNode(correlationIdPath, jsonNode); } else if (isOnRequestStreamHandler) { try { byte[] bytes = bytesFromInputStreamSafely((InputStream) proceedArgs[0]); - JsonNode jsonNode = JsonConfig.get().getObjectMapper().readTree(bytes); + JsonNode jsonNode = LoggingUtils.getObjectMapper().readTree(bytes); proceedArgs[0] = new ByteArrayInputStream(bytes); setCorrelationIdFromNode(correlationIdPath, jsonNode); @@ -380,7 +380,7 @@ private byte[] bytesFromInputStreamSafely(final InputStream inputStream) throws private Optional asJson(final Object target) { try { - return ofNullable(JsonConfig.get().getObjectMapper().writeValueAsString(target)); + return ofNullable(LoggingUtils.getObjectMapper().writeValueAsString(target)); } catch (JsonProcessingException e) { LOG.error("Failed logging object of type {}", target.getClass(), e); return empty(); From 74b377ac3573562a8c7c4ce9a92abffe541693fd Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 12:11:05 +0100 Subject: [PATCH 63/74] documentation --- docs/core/logging.md | 1127 +++++++++++++++++++++++++----------- docs/stylesheets/extra.css | 2 +- mkdocs.yml | 2 +- pom.xml | 2 +- 4 files changed, 788 insertions(+), 345 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index 02dff401d..40880327c 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -5,24 +5,34 @@ description: Core utility Logging provides an opinionated logger with output structured as JSON. -**Key features** +## Key features +* Leverages standard logging libraries: _SLF4J_ as the API, and _log4j2_ or _logback_ for the implementation * Capture key fields from Lambda context, cold start and structures logging output as JSON -* Log Lambda event when instructed, disabled by default, can be enabled explicitly via annotation param +* Log Lambda event when instructed, disabled by default +* Log Lambda response when instructed, disabled by default +* Log sampling enables DEBUG log level for a percentage of requests (disabled by default) * Append additional keys to structured log at any point in time -## Install -Depending on your version of Java (either Java 1.8 or 11+), the configuration slightly changes. +## Getting started -=== "Maven Java 11+" +???+ tip + You can find complete examples in the [project repository](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/examples/powertools-examples-core-utilities){target="_blank"}. - ```xml hl_lines="3-7 16 18 24-27" +### Installation +Depending on your preferences, you might choose to use _log4j2_ or _logback_. In both case you need to configure _aspectj_ +to weave the code and make sure the annotation is processed. + +#### Maven +=== "log4j2" + + ```xml hl_lines="3-7 24-27" ... software.amazon.lambda - powertools-logging + powertools-logging-log4j {{ powertools.version }} ... @@ -60,14 +70,14 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl ``` -=== "Maven Java 1.8" +=== "logback" - ```xml hl_lines="3-7 16 18 24-27" + ```xml hl_lines="3-7 24-27" ... software.amazon.lambda - powertools-logging + powertools-logging-logback {{ powertools.version }} ... @@ -78,13 +88,13 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl ... - org.codehaus.mojo + dev.aspectj aspectj-maven-plugin - 1.14.0 + 1.13.1 - 1.8 - 1.8 - 1.8 + 11 + 11 + 11 software.amazon.lambda @@ -105,7 +115,9 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl ``` -=== "Gradle Java 11+" +#### Gradle + +=== "log4j2" ```groovy hl_lines="3 11" plugins { @@ -118,19 +130,19 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl } dependencies { - aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-logging-log4j:{{ powertools.version }}' } sourceCompatibility = 11 targetCompatibility = 11 ``` -=== "Gradle Java 1.8" +=== "logback" ```groovy hl_lines="3 11" plugins { id 'java' - id 'io.freefair.aspectj.post-compile-weaving' version '6.6.3' + id 'io.freefair.aspectj.post-compile-weaving' version '8.1.0' } repositories { @@ -138,27 +150,61 @@ Depending on your version of Java (either Java 1.8 or 11+), the configuration sl } dependencies { - aspect 'software.amazon.lambda:powertools-logging:{{ powertools.version }}' + aspect 'software.amazon.lambda:powertools-logging-logback:{{ powertools.version }}' } - sourceCompatibility = 1.8 - targetCompatibility = 1.8 + sourceCompatibility = 11 + targetCompatibility = 11 ``` -## Initialization +### Configuration -Powertools for AWS Lambda (Java) extends the functionality of Log4J. Below is an example `#!xml log4j2.xml` file, with the `JsonTemplateLayout` using `#!json LambdaJsonLayout.json` configured. +#### Main environment variables -!!! info "LambdaJsonLayout is now deprecated" +The logging module requires two settings: - Configuring utiltiy using `` plugin is deprecated now. While utility still supports the old configuration, we strongly recommend upgrading the - `log4j2.xml` configuration to `JsonTemplateLayout` instead. [JsonTemplateLayout](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html) is recommended way of doing structured logging. - - Please follow [this guide](#upgrade-to-jsontemplatelayout-from-deprecated-lambdajsonlayout-configuration-in-log4j2xml) for upgrade steps. +| Environment variable | Setting | Description | +|---------------------------|-------------------|-------------------------------------------------------------------------------------------------------------| +| `POWERTOOLS_LOG_LEVEL` | **Logging level** | Sets how verbose Logger should be. If not set, will use the [Logging configuration](#logging-configuration) | +| `POWERTOOLS_SERVICE_NAME` | **Service** | Sets service key that will be present across all log statements (Default is `service_undefined`) | + +Here is an example using AWS Serverless Application Model (SAM): + +=== "template.yaml" +``` yaml hl_lines="10 11" +Resources: + PaymentFunction: + Type: AWS::Serverless::Function + Properties: + MemorySize: 512 + Timeout: 20 + Runtime: java17 + Environment: + Variables: + POWERTOOLS_LOG_LEVEL: WARN + POWERTOOLS_SERVICE_NAME: payment +``` + +There are some other environment variables which can be set to modify Logging's settings at a global scope: + +| Environment variable | Type | Description | +|---------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------| +| `POWERTOOLS_LOGGER_SAMPLE_RATE` | float | Configure the sampling rate to set the log level at `DEBUG`. See [sampling rage](#sampling-debug-logs) | +| `POWERTOOLS_LOG_EVENT` | boolean | Specify if the incoming Lambda event should be logged. See [Logging event](#logging-incoming-event) | +| `POWERTOOLS_LOG_RESPONSE` | boolean | Specify if the Lambda response should be logged. See [logging response](#logging-handler-response) | +| `POWERTOOLS_LOG_ERROR` | boolean | Specify if a Lambda uncaught exception should be logged. See [logging exception](#logging-handler-uncaught-exception ) | + +#### Logging configuration + +Powertools for AWS Lambda (Java) simply extends the functionality of the underlying library you choose (_log4j2_ or _logback_). +You can leverage the standard configuration files (_log4j2.xml_ or _logback.xml_): === "log4j2.xml" + With log4j2, we leverage the [`JsonTemplateLayout`](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html){target="_blank"} + to provide structured logging. A default template is provided in powertools ([_LambdaJsonLayout.json_](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/powertools-logging/powertools-logging-log4j/src/main/resources/LambdaJsonLayout.json){target="_blank"}): + ```xml hl_lines="5" @@ -168,7 +214,7 @@ Powertools for AWS Lambda (Java) extends the functionality of Log4J. Below is an - + @@ -178,148 +224,286 @@ Powertools for AWS Lambda (Java) extends the functionality of Log4J. Below is an ``` -You can also override log level by setting **`POWERTOOLS_LOG_LEVEL`** env var. Here is an example using AWS Serverless Application Model (SAM) +=== "logback.xml" -=== "template.yaml" - ``` yaml hl_lines="9 10" - Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - ... - Runtime: java8 - Environment: - Variables: - POWERTOOLS_LOG_LEVEL: DEBUG - POWERTOOLS_SERVICE_NAME: example + With logback, we leverage a custom [Encoder](https://logback.qos.ch/manual/encoders.html){target="_blank"} + to provide structured logging: + + ```xml hl_lines="4 5" + + + + + + + + + + + + + ``` -You can also explicitly set a service name via **`POWERTOOLS_SERVICE_NAME`** env var. This sets **service** key that will be present across all log statements. +## Log level +Log level is generally configured in the `log4j2.xml` or `logback.xml`. But this level is static and needs a redeployment of the function to be changed. +Powertools for AWS Lambda permits to change this level dynamically thanks to an environment variable `POWERTOOLS_LOG_LEVEL`. -## Standard structured keys +We support the following log levels (SLF4J levels): `TRACE` (0), `DEBUG` (10), `INFO` (20), `WARN` (30), `ERROR` (40). +If the level is set to `CRITICAL` (supported in log4j but not logback), we revert it back to `ERROR`. +If the level is set to any other value, we set it to the default value (`INFO`). -Your logs will always include the following keys to your structured logging: +### AWS Lambda Advanced Logging Controls (ALC) -Key | Type | Example | Description -------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------- -**timestamp** | String | "2020-05-24 18:17:33,774" | Timestamp of actual log statement -**level** | String | "INFO" | Logging level -**cold_start** | Boolean | true| ColdStart value. -**service** | String | "payment" | Service name defined. "service_undefined" will be used if unknown -**sampling_rate** | int | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case -**message** | String | "Collecting payment" | Log statement value. Unserializable JSON values will be casted to string -**function_name**| String | "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" -**function_version**| String | "12" -**function_memory_size**| String | "128" -**function_arn**| String | "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" -**xray_trace_id**| String | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when Lambda function has enabled Tracing -**function_request_id**| String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context +!!!question "When is it useful?" + When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions, regardless of runtime and logger used. -## Capturing context Lambda info + +With [AWS Lambda Advanced Logging Controls (ALC)](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-advanced){target="_blank"}, you can enforce a minimum log level that Lambda will accept from your application code. -When debugging in non-production environments, you can instruct Logger to log the incoming event with `@Logger(logEvent = true)` or via `POWERTOOLS_LOGGER_LOG_EVENT=true` environment variable. +When enabled, you should keep Powertools and ALC log level in sync to avoid data loss. -!!! warning - Log event is disabled by default to prevent sensitive info being logged. +Here's a sequence diagram to demonstrate how ALC will drop both `INFO` and `DEBUG` logs emitted from `Logger`, when ALC log level is stricter than `Logger`. + +```mermaid +sequenceDiagram + participant Lambda service + participant Lambda function + participant Application Logger -=== "App.java" + Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN" + Note over Application Logger: POWERTOOLS_LOG_LEVEL="DEBUG" - ```java hl_lines="14" - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - import software.amazon.lambda.powertools.logging.LoggingUtils; + Lambda service->>Lambda function: Invoke (event) + Lambda function->>Lambda function: Calls handler + Lambda function->>Application Logger: logger.error("Something happened") + Lambda function-->>Application Logger: logger.debug("Something happened") + Lambda function-->>Application Logger: logger.info("Something happened") + Lambda service--xLambda service: DROP INFO and DEBUG logs + Lambda service->>CloudWatch Logs: Ingest error logs +``` + +### Priority of log level settings in Powertools for AWS Lambda + +We prioritise log level settings in this order: + +1. `AWS_LAMBDA_LOG_LEVEL` environment variable +2. `POWERTOOLS_LOG_LEVEL` environment variable +3. level defined in the `log4j2.xml` or `logback.xml` files + +If you set Powertools level lower than ALC, we will emit a warning informing you that your messages will be discarded by Lambda. + +> **NOTE** +> +> With ALC enabled, we are unable to increase the minimum log level below the `AWS_LAMBDA_LOG_LEVEL` environment variable value, see [AWS Lambda service documentation](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-log-level){target="_blank"} for more details. + +## Basic Usage + +To use Lambda Powertools for AWS Lambda Logging, use the `@Logging` annotation in your code and the standard _SLF4J_ logger: + +=== "PaymentFunction.java" + + ```java hl_lines="8 10 12 14" + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; import software.amazon.lambda.powertools.logging.Logging; - ... - - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { + // ... other imports + + public class PaymentFunction implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + + @Logging + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + LOGGER.info("Collecting payment"); + // ... + LOGGER.debug("order={}, amount={}", order.getId(), order.getAmount()); + // ... + } + } + ``` + +## Standard structured keys + +Your logs will always include the following keys in your structured logging: + +| Key | Type | Example | Description | +|-------------------|--------|-----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| **timestamp** | String | "2023-12-01T14:49:19.293Z" | Timestamp of actual log statement, by default uses default AWS Lambda timezone (UTC) | +| **level** | String | "INFO" | Logging level (any level supported by _SLF4J_ (i.e. `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`) | +| **service** | String | "payment" | Service name defined, by default `service_undefined` | +| **sampling_rate** | float | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case (logged if not 0) | +| **message** | String | "Collecting payment" | Log statement value. Unserializable JSON values will be casted to string | +| **xray_trace_id** | String | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when [Tracing is enabled](https://docs.aws.amazon.com/lambda/latest/dg/services-xray.html){target="_blank"} | +| **error** | Map | `{ "name": "InvalidAmountException", "message": "Amount must be superior to 0", "stack": "at..." }` | Eventual exception (e.g. when doing `logger.error("Error", new InvalidAmountException("Amount must be superior to 0"));`) | + +### Log messages as JSON +By default, `message` is logged as a `String` (e.g `"message": "The message"`). When logging JSON content, +you may want to avoid the escaped String (`"message:"{\"key\":\"value\"}"`) for better readability. +You can use `LoggingUtils.logMessagesAsJson(true)` to enable this programmatically. + +=== "PaymentFunction.java" + + ```java hl_lines="14 15 17-20" + import static software.amazon.lambda.powertools.utilities.EventDeserializer.extractDataFrom; + import software.amazon.lambda.powertools.logging.LoggingUtils; + import software.amazon.lambda.powertools.utilities.JsonConfig; + // ... other imports + + public class PaymentFunction implements RequestHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentFunction.class); + @Logging public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + Order order = extractDataFrom(input).as(Order.class); + + // logged as a String + LOGGER.debug("{}", JsonConfig.get().getObjectMapper().writeValueAsString(order)); + + // Logged as JSON + LoggingUtils.logMessagesAsJson(true); + LOGGER.debug("{}", JsonConfig.get().getObjectMapper().writeValueAsString(order)); + LoggingUtils.logMessagesAsJson(false); + + // ... } } ``` +=== "Order.java" + + ```java + public class Order { + private String id; + private Date date; + private Double amount; + } + ``` + +=== "Example CloudWatch Logs" + + ```json hl_lines="3 9-13" + { + "level": "DEBUG", + "message": "{\"id\":\"435iuh2j3hb4\", \"date\":\"2023-12-01T14:48:59\", \"amount\":435.5}", + "timestamp": "2023-12-01T14:49:19.293Z", + "service": "payment", + } + { + "level": "DEBUG", + "message": { + "id": "435iuh2j3hb4", + "date": "2023-12-01T14:48:59", + "amount":435.5 + }, + "timestamp": "2023-12-01T14:49:19.312Z", + "service": "payment", + } + ``` + +You can also achieve this more broadly for all JSON messages (see advanced configuration for [log4j](#log-messages-as-json-1) & [logback](#log-messages-as-json-2)). + +## Additional structured keys + +### Logging Lambda context information +The following keys will also be added to your structured logs (unless [configured otherwise](#more-customization-1)): + +| Key | Type | Example | Description | +|--------------------------|---------|----------------------------------------------------------------------------------------|------------------------------------| +| **cold_start** | Boolean | false | ColdStart value | +| **function_name** | String | "example-PaymentFunction-1P1Z6B39FLU73" | Name of the function | +| **function_version** | String | "12" | Version of the function | +| **function_memory_size** | String | "512" | Memory configure for the function | +| **function_arn** | String | "arn:aws:lambda:eu-west-1:012345678910:function:example-PaymentFunction-1P1Z6B39FLU73" | ARN of the function | +| **function_request_id** | String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context | + + +### Logging incoming event + +When debugging in non-production environments, you can instruct the `@Logging` annotation to log the incoming event with `logEvent` param or via `POWERTOOLS_LOGGER_LOG_EVENT` env var. + +???+ warning + This is disabled by default to prevent sensitive info being logged + === "AppLogEvent.java" - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ - public class AppLogEvent implements RequestHandler { + ```java hl_lines="5" + public class AppLogEvent implements RequestHandler { - Logger log = LogManager.getLogger(AppLogEvent.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogEvent.class); @Logging(logEvent = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + // ... } } ``` -### Customising fields in logs +### Logging handler response -- Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSSZz` and in system default timezone. -If you need to customize format and timezone, you can do so by configuring `log4j2.component.properties` and configuring properties as shown in example below: +When debugging in non-production environments, you can instruct the `@Logging` annotation to log the response with `logResponse` param or via `POWERTOOLS_LOGGER_LOG_RESPONSE` env var. -=== "log4j2.component.properties" +???+ warning + This is disabled by default to prevent sensitive info being logged - ```properties hl_lines="1 2" - log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz - log4j.layout.jsonTemplate.timeZone=Europe/Oslo +=== "AppLogResponse.java" + + ```java hl_lines="5" + public class AppLogResponse implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); + + @Logging(logResponse = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } ``` -- Utility also provides sample template for [Elastic Common Schema(ECS)](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html) layout. -The field emitted in logs will follow specs from [ECS](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html) together with field captured by utility as mentioned [above](#standard-structured-keys). +### Logging handler uncaught exception +By default, AWS Lambda logs any uncaught exception that might happen in the handler. However, this log is not structured +and does not contain any additional context. You can instruct the `@Logging` annotation to log this kind of exception +with `logError` param or via `POWERTOOLS_LOGGER_LOG_ERROR` env var. - Use `LambdaEcsLayout.json` as `eventTemplateUri` when configuring `JsonTemplateLayout`. +???+ warning + This is disabled by default to prevent double logging -=== "log4j2.xml" +=== "AppLogResponse.java" - ```xml hl_lines="5" - - - - - - - - - - - - - - - - + ```java hl_lines="5" + public class AppLogError implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogError.class); + + @Logging(logError = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } ``` -## Setting a Correlation ID +### Logging additional keys -You can set a Correlation ID using `correlationIdPath` attribute by passing a [JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"}. +#### Logging a correlation ID -=== "App.java" +You can set a correlation ID using the `correlationIdPath` attribute of the `@Logging`annotation, +by passing a [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank"}, +including our custom [JMESPath Functions](../utilities/serialization.md#built-in-functions). - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { +=== "AppCorrelationId.java" + + ```java hl_lines="5" + public class AppCorrelationId implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); - @Logging(correlationIdPath = "/headers/my_request_id_header") + @Logging(correlationIdPath = "headers.my_request_id_header") public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - log.info("Collecting payment") - ... + // ... + LOGGER.info("Collecting payment") + // ... } } ``` @@ -333,42 +517,89 @@ You can set a Correlation ID using `correlationIdPath` attribute by passing a [J } ``` -=== "Example CloudWatch Logs excerpt" +=== "Example CloudWatch Logs" - ```json hl_lines="11" + ```json hl_lines="6" { "level": "INFO", "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", + "timestamp": "2023-12-01T14:49:19.293Z", "service": "payment", - "cold_start": true, - "function_name": "test", - "function_memory_size": 128, - "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } ``` -We provide [built-in JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"} -for known event sources, where either a request ID or X-Ray Trace ID are present. -=== "App.java" +** setCorrelationId method** + +You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere else in your code. + +=== "AppCorrelationId.java" + + ```java hl_lines="8" + public class AppCorrelationId implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); + + @Logging + public String handleRequest(final ScheduledEvent event, final Context context) { + // ... + LoggingUtils.setCorrelationId(event.getId()); + LOGGER.info("Scheduled Event") + // ... + } + } + ``` + +=== "Example Event" - ```java hl_lines="10" + ```json hl_lines="2" + { + "id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "2023-12-01T14:49:19Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/ExampleRule" + ], + "detail": {} + } + ``` + +=== "Example CloudWatch Logs" + + ```json hl_lines="6" + { + "level": "INFO", + "message": "Scheduled Event", + "timestamp": "2023-12-01T14:49:19.293Z", + "service": "payment", + "correlation_id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c" + } + ``` +???+ tip + You can retrieve correlation IDs via `LoggingUtils.getCorrelationId()` method if needed. + +** Known correlation IDs ** + +To ease routine tasks like extracting correlation ID from popular event sources, +we provide [built-in JMESPath expressions](#built-in-correlation-id-expressions). + +=== "AppCorrelationId.java" + + ```java hl_lines="1 7" import software.amazon.lambda.powertools.logging.CorrelationIdPaths; - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { + public class AppCorrelationId implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); - @Logging(correlationIdPath = CorrelationIdPathConstants.API_GATEWAY_REST) + @Logging(correlationIdPath = CorrelationIdPaths.API_GATEWAY_REST) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - log.info("Collecting payment") - ... + // ... + LOGGER.info("Collecting payment") + // ... } } ``` @@ -377,316 +608,511 @@ for known event sources, where either a request ID or X-Ray Trace ID are present ```json hl_lines="3" { - "requestContext": { - "requestId": "correlation_id_value" - } + "requestContext": { + "requestId": "correlation_id_value" + } } ``` -=== "Example CloudWatch Logs excerpt" +=== "Example CloudWatch Logs" - ```json hl_lines="11" + ```json hl_lines="6" { "level": "INFO", "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", + "timestamp": "2023-12-01T14:49:19.293Z", "service": "payment", - "cold_start": true, - "function_name": "test", - "function_memory_size": 128, - "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", "correlation_id": "correlation_id_value" } ``` - -## Appending additional keys -!!! info "Custom keys are persisted across warm invocations" - Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [`clearState=true`](#clearing-all-state). +#### Custom keys -You can append your own keys to your existing logs via `appendKey`. +???+ warning "Custom keys are persisted across warm invocations" + Always set additional keys as part of your handler method to ensure they have the latest value, or explicitly clear them with [`clearState=true`](#clearing-state). -=== "App.java" +To append an additional key in your logs, you can use the `LoggingUtils.appendKey()` or `LoggingUtils.appendKeys()` for multiple keys: - ```java hl_lines="11 19" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { +=== "PaymentFunction.java" + + ```java hl_lines="8 9 15 16" + public class PaymentFunction implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); - @Logging(logEvent = true) + @Logging public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - LoggingUtils.appendKey("test", "willBeLogged"); - ... - - ... - Map customKeys = new HashMap<>(); - customKeys.put("test", "value"); - customKeys.put("test1", "value1"); + // ... + LoggingUtils.appendKey("orderId", order.getId()); + LOGGER.info("Collecting payment"); - LoggingUtils.appendKeys(customKeys); - ... + // ... + Map customKeys = new HashMap<>(); + customKeys.put("paymentId", payment.getId()); + customKeys.put("amount", payment.getAmount); + LoggingUtils.appendKeys(customKeys); + LOGGER.info("Payment successful"); } } ``` +=== "Example CloudWatch Logs" + + ```json hl_lines="7 16-18" + { + "level": "INFO", + "message": "Collecting payment", + "service": "payment", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "orderId": "41376" + } + ... + { + "level": "INFO", + "message": "Payment successful", + "service": "payment", + "timestamp": "2023-12-01T14:49:20.118Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "orderId": "41376", + "paymentId": "3245", + "amount": 345.99 + } + ``` + +???+ tip "Additional keys are based on the MDC" + Mapped Diagnostic Context (MDC) is essentially a Key-Value store. It is supported by the [SLF4J API](https://www.slf4j.org/manual.html#mdc){target="_blank"}, + [logback](https://logback.qos.ch/manual/mdc.html){target="_blank"} and log4j (known as [ThreadContext](https://logging.apache.org/log4j/2.x/manual/thread-context.html){target="_blank"}). + + `LoggingUtils.appendKey("key", "value")` is equivalent to `MDC.put("key", "value")`. + ### Removing additional keys -You can remove any additional key from entry using `LoggingUtils.removeKeys()`. +You can remove any additional key from entry using `LoggingUtils.removeKey()` or `LoggingUtils.removeKeys()` for multiple keys: -=== "App.java" +=== "PaymentFunction.java" ```java hl_lines="19 20" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { + public class PaymentFunction implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); - @Logging(logEvent = true) + @Logging(logResponse = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - LoggingUtils.appendKey("test", "willBeLogged"); - ... - Map customKeys = new HashMap<>(); - customKeys.put("test1", "value"); - customKeys.put("test2", "value1"); + // ... + LoggingUtils.appendKey("orderId", order.getId()); + LOGGER.info("Collecting payment"); + // ... + Map customKeys = new HashMap<>(); + customKeys.put("paymentId", payment.getId()); + customKeys.put("amount", payment.getAmount); LoggingUtils.appendKeys(customKeys); - ... - LoggingUtils.removeKey("test"); - LoggingUtils.removeKeys("test1", "test2"); - ... + LOGGER.info("Payment successful"); + + // ... + LoggingUtils.removeKey("orderId"); + LoggingUtils.removeKeys("paymentId", "amount"); + + return response; } } ``` -### Clearing all state +=== "Example CloudWatch Logs" + Response is logged (`logResponse=true`) without the additional keys: -Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html), -this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use -`clearState=true` attribute on `@Logging` annotation. + ```json + ... + { + "level": "INFO", + "message": { + "statusCode": 200, + "isBase64Encoded": false, + "body": ..., + "headers": ..., + "multiValueHeaders": ... + }, + "service": "payment", + "timestamp": "2023-12-01T14:49:20.118Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5" + } + ``` +???+ tip "Additional keys are based on the MDC" + `LoggingUtils.removeKey("key")` is equivalent to `MDC.remove("key")`. -=== "App.java" +#### Clearing state - ```java hl_lines="8 12" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { +Logger is commonly initialized in the global scope. Due to [Lambda Execution Context reuse](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-context.html){target="_blank"}, +this means that custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use +`clearState=true` attribute on the `@Logging` annotation. + +=== "CreditCardFunction.java" + + ```java hl_lines="5 8" + public class CreditCardFunction implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(CreditCardFunction.class); @Logging(clearState = true) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - if(input.getHeaders().get("someSpecialHeader")) { - LoggingUtils.appendKey("specialKey", "value"); - } - - log.info("Collecting payment"); - ... + // ... + LoggingUtils.appendKey("cardNumber", card.getId()); + LOGGER.info("Updating card information"); + // ... } } ``` + === "#1 Request" - ```json hl_lines="11" - { - "level": "INFO", - "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", - "service": "payment", - "cold_start": true, - "function_name": "test", - "function_memory_size": 128, - "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72", - "specialKey": "value" - } + ```json hl_lines="7" + { + "level": "INFO", + "message": "Updating card information", + "service": "card", + "timestamp": "2023-12-01T14:49:19.293Z", + "xray_trace_id": "1-6569f266-4b0c7f97280dcd8428d3c9b5", + "cardNumber": "6818 8419 9395 5322" + } ``` === "#2 Request" - ```json - { - "level": "INFO", - "message": "Collecting payment", - "timestamp": "2021-05-03 11:47:12,494+0200", - "service": "payment", - "cold_start": true, - "function_name": "test", - "function_memory_size": 128, - "function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test", - "function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72" - } + ```json hl_lines="7" + { + "level": "INFO", + "message": "Updating card information", + "service": "card", + "timestamp": "2023-12-01T14:49:20.213Z", + "xray_trace_id": "2-7a518f43-5e9d2b1f6cfd5e8b3a4e1f9c", + "cardNumber": "7201 6897 6685 3285" + } ``` -## Override default object mapper - -You can optionally choose to override default object mapper which is used to serialize lambda function events. You might -want to supply custom object mapper in order to control how serialisation is done, for example, when you want to log only -specific fields from received event due to security. +???+ tip "Additional keys are based on the MDC" + `clearState` is based on `MDC.clear()`. State clearing is automatically done at the end of the execution of the handler if set to `true`. -=== "App.java" - ```java hl_lines="9 10" - /** - * Handler for requests to Lambda function. - */ - public class App implements RequestHandler { - - Logger log = LogManager.getLogger(App.class); - - static { - ObjectMapper objectMapper = new ObjectMapper(); - LoggingUtils.defaultObjectMapper(objectMapper); - } - - @Logging(logEvent = true) - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... - } - } - ``` +# Advanced ## Sampling debug logs You can dynamically set a percentage of your logs to **DEBUG** level via env var `POWERTOOLS_LOGGER_SAMPLE_RATE` or -via `samplingRate` attribute on annotation. +via `samplingRate` attribute on the `@Logging` annotation. !!! info Configuration on environment variable is given precedence over sampling rate configuration on annotation, provided it's in valid value range. === "Sampling via annotation attribute" - ```java hl_lines="8" - /** - * Handler for requests to Lambda function. - */ + ```java hl_lines="5" public class App implements RequestHandler { - Logger log = LogManager.getLogger(App.class); + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); @Logging(samplingRate = 0.5) public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - ... + // will eventually be logged based on the sampling rate + LOGGER.debug("Handle payment"); } } ``` === "Sampling via environment variable" - ```yaml hl_lines="9" + ```yaml hl_lines="8" Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - ... - Runtime: java8 - Environment: - Variables: - POWERTOOLS_LOGGER_SAMPLE_RATE: 0.5 + PaymentFunction: + Type: AWS::Serverless::Function + Properties: + ... + Environment: + Variables: + POWERTOOLS_LOGGER_SAMPLE_RATE: 0.5 + ``` -## AWS Lambda Advanced Logging Controls (ALC) +## Built-in Correlation ID expressions -!!!question "When is it useful?" - When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions, regardless of runtime and logger used. +You can use any of the following built-in JMESPath expressions as part of `@Logging(correlationIdPath = ...)`: - -With [AWS Lambda Advanced Logging Controls (ALC)](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-advanced){target="_blank"}, you can enforce a minimum log level that Lambda will accept from your application code. +???+ note "Note: Any object key named with `-` must be escaped" + For example, **`request.headers."x-amzn-trace-id"`**. -When enabled, you should keep `Logger` and ALC log level in sync to avoid data loss. +| Name | Expression | Description | +|-------------------------------|-------------------------------------|---------------------------------| +| **API_GATEWAY_REST** | `"requestContext.requestId"` | API Gateway REST API request ID | +| **API_GATEWAY_HTTP** | `"requestContext.requestId"` | API Gateway HTTP API request ID | +| **APPSYNC_RESOLVER** | `request.headers."x-amzn-trace-id"` | AppSync X-Ray Trace ID | +| **APPLICATION_LOAD_BALANCER** | `headers."x-amzn-trace-id"` | ALB X-Ray Trace ID | +| **EVENT_BRIDGE** | `"id"` | EventBridge Event ID | -Here's a sequence diagram to demonstrate how ALC will drop both `INFO` and `DEBUG` logs emitted from `Logger`, when ALC log level is stricter than `Logger`. - -```mermaid -sequenceDiagram - participant Lambda service - participant Lambda function - participant Application Logger +## Customising fields in logs - Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN" - Note over Application Logger: POWERTOOLS_LOG_LEVEL="DEBUG" +Powertools for AWS Lambda comes with default json structure ([standard fields](#standard-structured-keys) & [lambda context fields](#logging-lambda-context-information)). - Lambda service->>Lambda function: Invoke (event) - Lambda function->>Lambda function: Calls handler - Lambda function->>Application Logger: logger.error("Something happened") - Lambda function-->>Application Logger: logger.debug("Something happened") - Lambda function-->>Application Logger: logger.info("Something happened") - Lambda service--xLambda service: DROP INFO and DEBUG logs - Lambda service->>CloudWatch Logs: Ingest error logs +You can go further and customize which fields you want to keep in your logs or not. The configuration varies according to the underlying logging library. + +### Log4j2 configuration +Log4j2 configuration is done in _log4j2.xml_ and leverages `JsonTemplateLayout`: + +```xml + + + ``` -### Priority of log level settings in Powertools for AWS Lambda +The `JsonTemplateLayout` is configured with the provided template: -We prioritise log level settings in this order: +??? example "LambdaJsonLayout.json" + ```json + { + "level": { + "$resolver": "level", + "field": "name" + }, + "message": { + "$resolver": "powertools", + "field": "message" + }, + "error": { + "message": { + "$resolver": "exception", + "field": "message" + }, + "name": { + "$resolver": "exception", + "field": "className" + }, + "stack": { + "$resolver": "exception", + "field": "stackTrace", + "stackTrace": { + "stringified": true + } + } + }, + "cold_start": { + "$resolver": "powertools", + "field": "cold_start" + }, + "function_arn": { + "$resolver": "powertools", + "field": "function_arn" + }, + "function_memory_size": { + "$resolver": "powertools", + "field": "function_memory_size" + }, + "function_name": { + "$resolver": "powertools", + "field": "function_name" + }, + "function_request_id": { + "$resolver": "powertools", + "field": "function_request_id" + }, + "function_version": { + "$resolver": "powertools", + "field": "function_version" + }, + "sampling_rate": { + "$resolver": "powertools", + "field": "sampling_rate" + }, + "service": { + "$resolver": "powertools", + "field": "service" + }, + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + } + }, + "xray_trace_id": { + "$resolver": "powertools", + "field": "xray_trace_id" + }, + "": { + "$resolver": "powertools" + } + } + ``` -1. `AWS_LAMBDA_LOG_LEVEL` environment variable -2. `POWERTOOLS_LOG_LEVEL` environment variable +You can create your own template and leverage the [PowertoolsResolver](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/powertools-logging/powertools-logging-log4j/src/main/java/org/apache/logging/log4j/layout/template/json/resolver/PowertoolsResolver.java){target="_blank"} +and any other resolver to log the desired fields with the desired format. Some examples of customization are given below: -If you set `Logger` level lower than ALC, we will emit a warning informing you that your messages will be discarded by Lambda. +#### Log messages as JSON +`message` field is not handled with the standard [`MessageResolver`](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-message){target="_blank"} but by the `PowertoolsResolver`. +With this resolver, you can choose to log all the JSON messages as JSON and not as String. -> **NOTE** -> -> With ALC enabled, we are unable to increase the minimum log level below the `AWS_LAMBDA_LOG_LEVEL` environment variable value, see [AWS Lambda service documentation](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-log-level){target="_blank"} for more details. +=== "my-custom-template.json" -### Timestamp format + ```json + { + "message": { + "$resolver": "powertools", + "field": "message", + "asJson": true + } + } + ``` -When the Advanced Logging Controls feature is enabled, Powertools for AWS Lambda must comply with the timestamp format required by AWS Lambda, which is [RFC3339](https://www.rfc-editor.org/rfc/rfc3339). -In this case the format will be `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'`. +#### Customising date format -## Upgrade to JsonTemplateLayout from deprecated LambdaJsonLayout configuration in log4j2.xml +Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` and in system default timezone. +If you need to customize format and timezone, you can update your template.json or by configuring `log4j2.component.properties` as shown in examples below: -Prior to version [1.10.0](https://github.com/aws-powertools/powertools-lambda-java/releases/tag/v1.10.0), only supported way of configuring `log4j2.xml` was via ``. This plugin is -deprecated now and will be removed in future version. Switching to `JsonTemplateLayout` is straight forward. +=== "my-custom-template.json" -Below examples shows deprecated and new configuration of `log4j2.xml`. + ```json + { + "timestamp": { + "$resolver": "timestamp", + "pattern": { + "format": "yyyy-MM-dd HH:mm:ss", + "timeZone": "Europe/Paris", + } + }, + } + ``` -=== "Deprecated configuration of log4j2.xml" +=== "log4j2.component.properties" - ```xml hl_lines="5" - - - - - - - - - - - - - - - - + ```properties hl_lines="1 2" + log4j.layout.jsonTemplate.timestampFormatPattern=yyyy-MM-dd'T'HH:mm:ss.SSSZz + log4j.layout.jsonTemplate.timeZone=Europe/Oslo + ``` + +See [`TimestampResolver` documentation](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-timestamp){target="_blank"} for more details. + +???+ warning "Lambda Advanced Logging Controls date format" + When using the Lambda ALC, you must have a date format compatible with the [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) + +#### More customization +You can also customize how [exceptions are logged](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html#event-template-resolver-exception){target="_blank"}, and much more. +See the [JSON Layout template documentation](https://logging.apache.org/log4j/2.x/manual/json-template-layout.html){target="_blank"} for more details. + +### Logback configuration +Logback configuration is done in _logback.xml_ and the Powertools [`LambdaJsonEncoder`](): + +```xml + + + + +``` + +The `LambdaJsonEncoder` can be customized in different ways: + +#### Log messages as JSON + +With the following configuration, you choose to log all the JSON messages as JSON and not as String (default is `false`): + +```xml + + true + +``` + +#### Customising date format +Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` and in system default timezone. +If you need to customize format and timezone, you can change use the following: + +```xml + + yyyy-MM-dd HH:mm:ss + Europe/Paris + +``` + +#### More customization + +- You can use a standard `ThrowableHandlingConverter` to customize the exception format (default is no converter). Example: + +```xml + + + 30 + 2048 + 20 + sun\.reflect\..*\.invoke.* + net\.sf\.cglib\.proxy\.MethodProxy\.invoke + + true + true + + +``` + +- You can choose to add information about threads (default is `false`): + +```xml + + true + +``` + +- You can even choose to remove Powertools information from the logs like function name, arn: + +```xml + + false + +``` + +## Override default object mapper + +You can optionally choose to override default object mapper which is used to serialize lambda function events. You might +want to supply custom object mapper in order to control how serialisation is done, for example, when you want to log only +specific fields from received event due to security. + +=== "App.java" + + ```java hl_lines="6-10" + public class App implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + static { + ObjectMapper objectMapper = new ObjectMapper() + .enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + LoggingUtils.setObjectMapper(objectMapper); + } + + @Logging(logEvent = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } ``` -=== "New configuration of log4j2.xml" +## Elastic Common Schema (ECS) Support + +Utility also supports [Elastic Common Schema(ECS)](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html){target="_blank"} format. +The field emitted in logs will follow specs from [ECS](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html){target="_blank"} together with field captured by utility as mentioned [above](#standard-structured-keys). + +### Log4j2 configuration + +Use `LambdaEcsLayout.json` as `eventTemplateUri` when configuring `JsonTemplateLayout`. + +=== "log4j2.xml" ```xml hl_lines="5" - + - - - @@ -694,3 +1120,20 @@ Below examples shows deprecated and new configuration of `log4j2.xml`. ``` +### Logback configuration + +=== "logback.xml" + +Use the `LambdaEcsEncoder` rather than the `LambdaJsonEncoder` when configuring the appender: + + ```xml + + + + + + + + + + ``` \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index d135d7210..dc08ef51e 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,5 +1,5 @@ .md-grid { - max-width: 81vw + max-width: 90vw } .highlight .hll { diff --git a/mkdocs.yml b/mkdocs.yml index a8569f664..b7f793e18 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,7 +85,7 @@ extra_javascript: extra: powertools: - version: 1.18.0 # to update after each release (we do not want snapshot version here) + version: 2.0.0 # to update after each release (we do not want snapshot version here) repo_url: https://github.com/aws-powertools/powertools-lambda-java edit_uri: edit/main/docs diff --git a/pom.xml b/pom.xml index ca32c2785..6b8776822 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 1.8 1.8 - 2.20.0 + 2.22.0 2.0.7 2.15.3 2.21.0 From 8cc5c665b26bc515480c2dfe98098dcbf4a39752 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 13:10:53 +0100 Subject: [PATCH 64/74] spotbugs --- powertools-logging/spotbugs-exclude.xml | 19 ------------------- .../powertools/logging/LoggingUtils.java | 6 +++--- spotbugs-exclude.xml | 4 ++-- 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/powertools-logging/spotbugs-exclude.xml b/powertools-logging/spotbugs-exclude.xml index 339d30a91..30a9c8557 100644 --- a/powertools-logging/spotbugs-exclude.xml +++ b/powertools-logging/spotbugs-exclude.xml @@ -21,25 +21,6 @@ - - - - - - - - - - - - - - - - - - - diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index 6b7298524..4b280bbb7 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -114,9 +114,9 @@ public static void setObjectMapper(ObjectMapper objectMapper) { } public static ObjectMapper getObjectMapper() { - if (null == objectMapper) { - objectMapper = JsonConfig.get().getObjectMapper(); + if (null == LoggingUtils.objectMapper) { + LoggingUtils.objectMapper = JsonConfig.get().getObjectMapper(); } - return objectMapper; + return LoggingUtils.objectMapper; } } diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index ce6dfb803..a2559017c 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -94,7 +94,7 @@ - + @@ -115,7 +115,7 @@ - + From 13e18dccf88580cc18b0607f7bc46147126c4e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Van=20Der=20Linden?= <117538+jeromevdl@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:59:14 +0100 Subject: [PATCH 65/74] Apply suggestions from code review on documentation Co-authored-by: Scott Gerring --- docs/core/logging.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index 40880327c..3f9df128f 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -7,12 +7,12 @@ Logging provides an opinionated logger with output structured as JSON. ## Key features -* Leverages standard logging libraries: _SLF4J_ as the API, and _log4j2_ or _logback_ for the implementation -* Capture key fields from Lambda context, cold start and structures logging output as JSON -* Log Lambda event when instructed, disabled by default -* Log Lambda response when instructed, disabled by default -* Log sampling enables DEBUG log level for a percentage of requests (disabled by default) -* Append additional keys to structured log at any point in time +* Leverages standard logging libraries: [_SLF4J_](https://www.slf4j.org/){target="_blank"} as the API, and [_log4j2_](https://logging.apache.org/log4j/2.x/){target="_blank"} or [_logback_](https://logback.qos.ch/){target="_blank"} for the implementation +* Captures key fields from Lambda context, cold start and structures logging output as JSON +* Optionally logs Lambda request +* Optionally logs Lambda response +* Optionally supports log sampling by including a configurable percentage of DEBUG logs in logging output +* Allows additional keys to be appended to the structured log at any point in time ## Getting started @@ -21,7 +21,7 @@ Logging provides an opinionated logger with output structured as JSON. You can find complete examples in the [project repository](https://github.com/aws-powertools/powertools-lambda-java/tree/v2/examples/powertools-examples-core-utilities){target="_blank"}. ### Installation -Depending on your preferences, you might choose to use _log4j2_ or _logback_. In both case you need to configure _aspectj_ +Depending on preference, you must choose to use either _log4j2_ or _logback_ as your log provider. In both cases you need to configure _aspectj_ to weave the code and make sure the annotation is processed. #### Maven @@ -167,7 +167,7 @@ The logging module requires two settings: | Environment variable | Setting | Description | |---------------------------|-------------------|-------------------------------------------------------------------------------------------------------------| | `POWERTOOLS_LOG_LEVEL` | **Logging level** | Sets how verbose Logger should be. If not set, will use the [Logging configuration](#logging-configuration) | -| `POWERTOOLS_SERVICE_NAME` | **Service** | Sets service key that will be present across all log statements (Default is `service_undefined`) | +| `POWERTOOLS_SERVICE_NAME` | **Service** | Sets service key that will be included in all log statements (Default is `service_undefined`) | Here is an example using AWS Serverless Application Model (SAM): @@ -190,7 +190,7 @@ There are some other environment variables which can be set to modify Logging's | Environment variable | Type | Description | |---------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------| -| `POWERTOOLS_LOGGER_SAMPLE_RATE` | float | Configure the sampling rate to set the log level at `DEBUG`. See [sampling rage](#sampling-debug-logs) | +| `POWERTOOLS_LOGGER_SAMPLE_RATE` | float | Configure the sampling rate at which `DEBUG` logs should be included. See [sampling rate](#sampling-debug-logs) | | `POWERTOOLS_LOG_EVENT` | boolean | Specify if the incoming Lambda event should be logged. See [Logging event](#logging-incoming-event) | | `POWERTOOLS_LOG_RESPONSE` | boolean | Specify if the Lambda response should be logged. See [logging response](#logging-handler-response) | | `POWERTOOLS_LOG_ERROR` | boolean | Specify if a Lambda uncaught exception should be logged. See [logging exception](#logging-handler-uncaught-exception ) | @@ -249,7 +249,7 @@ You can leverage the standard configuration files (_log4j2.xml_ or _logback.xml_ Log level is generally configured in the `log4j2.xml` or `logback.xml`. But this level is static and needs a redeployment of the function to be changed. Powertools for AWS Lambda permits to change this level dynamically thanks to an environment variable `POWERTOOLS_LOG_LEVEL`. -We support the following log levels (SLF4J levels): `TRACE` (0), `DEBUG` (10), `INFO` (20), `WARN` (30), `ERROR` (40). +We support the following log levels (SLF4J levels): `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. If the level is set to `CRITICAL` (supported in log4j but not logback), we revert it back to `ERROR`. If the level is set to any other value, we set it to the default value (`INFO`). @@ -408,7 +408,7 @@ You can also achieve this more broadly for all JSON messages (see advanced confi ## Additional structured keys ### Logging Lambda context information -The following keys will also be added to your structured logs (unless [configured otherwise](#more-customization-1)): +The following keys will also be added to all your structured logs (unless [configured otherwise](#more-customization-1)): | Key | Type | Example | Description | |--------------------------|---------|----------------------------------------------------------------------------------------|------------------------------------| @@ -529,7 +529,7 @@ including our custom [JMESPath Functions](../utilities/serialization.md#built-in } ``` -** setCorrelationId method** +**setCorrelationId method** You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere else in your code. @@ -581,7 +581,7 @@ You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere ???+ tip You can retrieve correlation IDs via `LoggingUtils.getCorrelationId()` method if needed. -** Known correlation IDs ** +**Known correlation IDs** To ease routine tasks like extracting correlation ID from popular event sources, we provide [built-in JMESPath expressions](#built-in-correlation-id-expressions). @@ -800,7 +800,7 @@ this means that custom keys can be persisted across invocations. If you want all ## Sampling debug logs -You can dynamically set a percentage of your logs to **DEBUG** level via env var `POWERTOOLS_LOGGER_SAMPLE_RATE` or +You can dynamically set a percentage of your logs to`DEBUG` level to be included in the logger output, regardless of configured log leve, using the`POWERTOOLS_LOGGER_SAMPLE_RATE` environment variable or via `samplingRate` attribute on the `@Logging` annotation. !!! info @@ -866,7 +866,7 @@ Log4j2 configuration is done in _log4j2.xml_ and leverages `JsonTemplateLayout` ``` -The `JsonTemplateLayout` is configured with the provided template: +The `JsonTemplateLayout` is automatically configured with the provided template: ??? example "LambdaJsonLayout.json" ```json From b1d94a22b53b00586b98745c849ae0181f6f4f7d Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 15:38:53 +0100 Subject: [PATCH 66/74] cleanup --- .../powertools-examples-idempotency/pom.xml | 17 ----------------- .../logging/internal/LambdaEcsSerializer.java | 3 +-- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/examples/powertools-examples-idempotency/pom.xml b/examples/powertools-examples-idempotency/pom.xml index 13ea8dec1..5a040fec0 100644 --- a/examples/powertools-examples-idempotency/pom.xml +++ b/examples/powertools-examples-idempotency/pom.xml @@ -72,23 +72,6 @@ 5.9.3 test - - com.amazonaws - DynamoDBLocal - 1.23.0 - test - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.15.2 - test - - - com.amazonaws - aws-lambda-java-tests - 1.1.1 - diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java index fabccf18b..124e0b5c6 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java @@ -29,8 +29,7 @@ /** * This class will serialize the log events in ecs format (ElasticSearch).
    *

    - * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer, this class doesn't use - * any JSON (de)serialization library (Jackson, Gson, etc.) to avoid the dependency + * Inspired from the ElasticSearch Serializer co.elastic.logging.EcsJsonSerializer */ public class LambdaEcsSerializer { protected static final String TIMESTAMP_ATTR_NAME = "@timestamp"; From 4d783080bd194b8745737fd35c5d25d2b32f3170 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Thu, 7 Dec 2023 15:56:08 +0100 Subject: [PATCH 67/74] doc and test completion --- .../powertools/logging/LoggingUtils.java | 11 +++++----- .../logging/internal/LambdaLoggingAspect.java | 5 +++-- .../powertools/logging/LoggingUtilsTest.java | 21 +++++++++++++++++-- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java index 4b280bbb7..d7ceb8ccd 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/LoggingUtils.java @@ -23,9 +23,8 @@ import software.amazon.lambda.powertools.utilities.JsonConfig; /** - * A class of helper functions to add additional functionality to Logging. - *

    - * {@see Logging} + * A class of helper functions to add functionality to Logging. + * Adding/removing keys is based on MDC, which is ThreadSafe. */ public final class LoggingUtils { @@ -105,7 +104,9 @@ public static void logMessagesAsJson(boolean value) { /** * Sets the instance of ObjectMapper object which is used for serialising event when - * {@code @Logging(logEvent = true)}. + * {@code @Logging(logEvent = true, logResponse = true)}. + * + * Not Thread Safe, the object mapper is static, changing it in different threads can lead to unexpected behaviour * * @param objectMapper Custom implementation of object mapper to be used for logging serialised event */ @@ -114,7 +115,7 @@ public static void setObjectMapper(ObjectMapper objectMapper) { } public static ObjectMapper getObjectMapper() { - if (null == LoggingUtils.objectMapper) { + if (LoggingUtils.objectMapper == null) { LoggingUtils.objectMapper = JsonConfig.get().getObjectMapper(); } return LoggingUtils.objectMapper; diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index 9ab53718d..c371ead06 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -303,7 +303,8 @@ private Object[] logRequestStreamHandlerEvent(final ProceedingJoinPoint pjp) { try { byte[] bytes = bytesFromInputStreamSafely((InputStream) pjp.getArgs()[0]); args[0] = new ByteArrayInputStream(bytes); - log.info("{}", new String(bytes, UTF_8)); // do not log asJson as it can be something else (String, XML...) + // do not log asJson as it can be something else (String, XML...) + log.info("{}", new String(bytes, UTF_8)); } catch (IOException e) { LOG.warn("Failed to log event from supplied input stream.", e); } @@ -325,7 +326,7 @@ private void logRequestStreamHandlerResponse(final ProceedingJoinPoint pjp, fina Logger log = logger(pjp); if (log.isInfoEnabled()) { LoggingUtils.logMessagesAsJson(true); - // we do not log with asJson as it can be something else (string, xml, ...) + // we do not log with asJson as it can be something else (String, XML, ...) log.info("{}", new String(bytes, UTF_8)); LoggingUtils.logMessagesAsJson(false); } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java index c06b69767..04e977c58 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/LoggingUtilsTest.java @@ -16,11 +16,14 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.MDC; +import software.amazon.lambda.powertools.utilities.JsonConfig; class LoggingUtilsTest { @@ -88,10 +91,24 @@ void shouldRemoveCustomKeysInLoggingContext() { @Test void shouldAddCorrelationIdToLoggingContext() { - LoggingUtils.setCorrelationId("correlationID_12345"); + String id = "correlationID_12345"; + LoggingUtils.setCorrelationId(id); assertThat(MDC.getCopyOfContextMap()) .hasSize(1) - .containsEntry("correlation_id", "correlationID_12345"); + .containsEntry("correlation_id", id); + + assertThat(LoggingUtils.getCorrelationId()).isEqualTo(id); + } + + @Test + void shouldGetObjectMapper() { + assertThat(LoggingUtils.getObjectMapper()).isNotNull(); + assertThat(LoggingUtils.getObjectMapper()).isEqualTo(JsonConfig.get().getObjectMapper()); + + ObjectMapper mapper = new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + LoggingUtils.setObjectMapper(mapper); + assertThat(LoggingUtils.getObjectMapper()).isEqualTo(mapper); + } } \ No newline at end of file From 11efab33fef1230ed3e1e98a0634164a043298d1 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 8 Dec 2023 14:59:59 +0100 Subject: [PATCH 68/74] update elastic doc --- docs/core/logging.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index 3f9df128f..e33e9a332 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -1122,11 +1122,11 @@ Use `LambdaEcsLayout.json` as `eventTemplateUri` when configuring `JsonTemplateL ### Logback configuration -=== "logback.xml" - Use the `LambdaEcsEncoder` rather than the `LambdaJsonEncoder` when configuring the appender: - ```xml +=== "logback.xml" + + ```xml hl_lines="3" From 83233c701dfc5cc21c15ce40e81b87ae06d92f57 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 8 Dec 2023 15:14:29 +0100 Subject: [PATCH 69/74] improve doc --- docs/core/logging.md | 153 +++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 77 deletions(-) diff --git a/docs/core/logging.md b/docs/core/logging.md index e33e9a332..cff12d34c 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -403,12 +403,12 @@ You can use `LoggingUtils.logMessagesAsJson(true)` to enable this programmatical } ``` -You can also achieve this more broadly for all JSON messages (see advanced configuration for [log4j](#log-messages-as-json-1) & [logback](#log-messages-as-json-2)). +You can also achieve this more broadly for all JSON messages (see advanced configuration for [log4j](#log-messages-as-json_1) & [logback](#log-messages-as-json_2)). ## Additional structured keys ### Logging Lambda context information -The following keys will also be added to all your structured logs (unless [configured otherwise](#more-customization-1)): +The following keys will also be added to all your structured logs (unless [configured otherwise](#more-customization_1)): | Key | Type | Example | Description | |--------------------------|---------|----------------------------------------------------------------------------------------|------------------------------------| @@ -419,71 +419,6 @@ The following keys will also be added to all your structured logs (unless [confi | **function_arn** | String | "arn:aws:lambda:eu-west-1:012345678910:function:example-PaymentFunction-1P1Z6B39FLU73" | ARN of the function | | **function_request_id** | String | "899856cb-83d1-40d7-8611-9e78f15f32f4"" | AWS Request ID from lambda context | - -### Logging incoming event - -When debugging in non-production environments, you can instruct the `@Logging` annotation to log the incoming event with `logEvent` param or via `POWERTOOLS_LOGGER_LOG_EVENT` env var. - -???+ warning - This is disabled by default to prevent sensitive info being logged - -=== "AppLogEvent.java" - - ```java hl_lines="5" - public class AppLogEvent implements RequestHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(AppLogEvent.class); - - @Logging(logEvent = true) - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - // ... - } - } - ``` - -### Logging handler response - -When debugging in non-production environments, you can instruct the `@Logging` annotation to log the response with `logResponse` param or via `POWERTOOLS_LOGGER_LOG_RESPONSE` env var. - -???+ warning - This is disabled by default to prevent sensitive info being logged - -=== "AppLogResponse.java" - - ```java hl_lines="5" - public class AppLogResponse implements RequestHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); - - @Logging(logResponse = true) - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - // ... - } - } - ``` - -### Logging handler uncaught exception -By default, AWS Lambda logs any uncaught exception that might happen in the handler. However, this log is not structured -and does not contain any additional context. You can instruct the `@Logging` annotation to log this kind of exception -with `logError` param or via `POWERTOOLS_LOGGER_LOG_ERROR` env var. - -???+ warning - This is disabled by default to prevent double logging - -=== "AppLogResponse.java" - - ```java hl_lines="5" - public class AppLogError implements RequestHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(AppLogError.class); - - @Logging(logError = true) - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - // ... - } - } - ``` - ### Logging additional keys #### Logging a correlation ID @@ -492,12 +427,12 @@ You can set a correlation ID using the `correlationIdPath` attribute of the `@Lo by passing a [JMESPath expression](https://jmespath.org/tutorial.html){target="_blank"}, including our custom [JMESPath Functions](../utilities/serialization.md#built-in-functions). -=== "AppCorrelationId.java" +=== "AppCorrelationIdPath.java" ```java hl_lines="5" - public class AppCorrelationId implements RequestHandler { + public class AppCorrelationIdPath implements RequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationIdPath.class); @Logging(correlationIdPath = "headers.my_request_id_header") public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { @@ -507,7 +442,7 @@ including our custom [JMESPath Functions](../utilities/serialization.md#built-in } } ``` -=== "Example Event" +=== "Example HTTP Event" ```json hl_lines="3" { @@ -517,7 +452,7 @@ including our custom [JMESPath Functions](../utilities/serialization.md#built-in } ``` -=== "Example CloudWatch Logs" +=== "CloudWatch Logs" ```json hl_lines="6" { @@ -533,12 +468,12 @@ including our custom [JMESPath Functions](../utilities/serialization.md#built-in You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere else in your code. -=== "AppCorrelationId.java" +=== "AppSetCorrelationId.java" ```java hl_lines="8" - public class AppCorrelationId implements RequestHandler { + public class AppSetCorrelationId implements RequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(AppCorrelationId.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AppSetCorrelationId.class); @Logging public String handleRequest(final ScheduledEvent event, final Context context) { @@ -550,7 +485,7 @@ You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere } ``` -=== "Example Event" +=== "Example Schedule Event" ```json hl_lines="2" { @@ -567,7 +502,7 @@ You can also use `LoggingUtils.setCorrelationId()` method to inject it anywhere } ``` -=== "Example CloudWatch Logs" +=== "CloudWatch Logs with correlation id" ```json hl_lines="6" { @@ -796,6 +731,70 @@ this means that custom keys can be persisted across invocations. If you want all `clearState` is based on `MDC.clear()`. State clearing is automatically done at the end of the execution of the handler if set to `true`. +## Logging incoming event + +When debugging in non-production environments, you can instruct the `@Logging` annotation to log the incoming event with `logEvent` param or via `POWERTOOLS_LOGGER_LOG_EVENT` env var. + +???+ warning + This is disabled by default to prevent sensitive info being logged + +=== "AppLogEvent.java" + + ```java hl_lines="5" + public class AppLogEvent implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogEvent.class); + + @Logging(logEvent = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } + ``` + +## Logging handler response + +When debugging in non-production environments, you can instruct the `@Logging` annotation to log the response with `logResponse` param or via `POWERTOOLS_LOGGER_LOG_RESPONSE` env var. + +???+ warning + This is disabled by default to prevent sensitive info being logged + +=== "AppLogResponse.java" + + ```java hl_lines="5" + public class AppLogResponse implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogResponse.class); + + @Logging(logResponse = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } + ``` + +## Logging handler uncaught exception +By default, AWS Lambda logs any uncaught exception that might happen in the handler. However, this log is not structured +and does not contain any additional context. You can instruct the `@Logging` annotation to log this kind of exception +with `logError` param or via `POWERTOOLS_LOGGER_LOG_ERROR` env var. + +???+ warning + This is disabled by default to prevent double logging + +=== "AppLogResponse.java" + + ```java hl_lines="5" + public class AppLogError implements RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AppLogError.class); + + @Logging(logError = true) + public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { + // ... + } + } + ``` + # Advanced ## Sampling debug logs From 3f6412c3853086705efdb9cd71882a5ee9715b1a Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Fri, 8 Dec 2023 15:20:39 +0100 Subject: [PATCH 70/74] improve doc --- docs/core/logging.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/core/logging.md b/docs/core/logging.md index cff12d34c..3c2475b34 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -752,6 +752,9 @@ When debugging in non-production environments, you can instruct the `@Logging` a } ``` +???+ note + If you use this on a RequestStreamHandler, Powertools must duplicate input streams in order to log them. + ## Logging handler response When debugging in non-production environments, you can instruct the `@Logging` annotation to log the response with `logResponse` param or via `POWERTOOLS_LOGGER_LOG_RESPONSE` env var. @@ -773,6 +776,9 @@ When debugging in non-production environments, you can instruct the `@Logging` a } ``` +???+ note + If you use this on a RequestStreamHandler, Powertools must duplicate output streams in order to log them. + ## Logging handler uncaught exception By default, AWS Lambda logs any uncaught exception that might happen in the handler. However, this log is not structured and does not contain any additional context. You can instruct the `@Logging` annotation to log this kind of exception From 64e79dd00a51343276d4837da9d67e57c79be8c8 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 11 Dec 2023 09:00:34 +0100 Subject: [PATCH 71/74] code review --- .../logging/internal/LambdaLoggingAspect.java | 18 +++++++++--------- .../internal/LambdaLoggingAspectTest.java | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index c371ead06..efda042ef 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -74,7 +74,7 @@ public final class LambdaLoggingAspect { private static final Logger LOG = LoggerFactory.getLogger(LambdaLoggingAspect.class); private static final Random SAMPLER = new Random(); - static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ + private static Level LEVEL_AT_INITIALISATION; /* not final for test purpose */ private static final LoggingManager LOGGING_MANAGER; @@ -88,21 +88,21 @@ public final class LambdaLoggingAspect { static void setLogLevel() { if (POWERTOOLS_LOG_LEVEL != null) { - Level powertoolsLevel = getLevelFromEnvironmentVariable(POWERTOOLS_LOG_LEVEL); + Level powertoolsLevel = getLevelFromString(POWERTOOLS_LOG_LEVEL); if (LAMBDA_LOG_LEVEL != null) { - Level lambdaLevel = getLevelFromEnvironmentVariable(LAMBDA_LOG_LEVEL); + Level lambdaLevel = getLevelFromString(LAMBDA_LOG_LEVEL); if (powertoolsLevel.toInt() < lambdaLevel.toInt()) { LOG.warn("Current log level ({}) does not match AWS Lambda Advanced Logging Controls minimum log level ({}). This can lead to data loss, consider adjusting them.", POWERTOOLS_LOG_LEVEL, LAMBDA_LOG_LEVEL); } } - resetLogLevels(powertoolsLevel); + setLogLevels(powertoolsLevel); } else if (LAMBDA_LOG_LEVEL != null) { - resetLogLevels(getLevelFromEnvironmentVariable(LAMBDA_LOG_LEVEL)); + setLogLevels(getLevelFromString(LAMBDA_LOG_LEVEL)); } } - private static Level getLevelFromEnvironmentVariable(String level) { + private static Level getLevelFromString(String level) { if (Arrays.stream(Level.values()).anyMatch(slf4jLevel -> slf4jLevel.name().equalsIgnoreCase(level))) { return Level.valueOf(level.toUpperCase()); } else { @@ -146,7 +146,7 @@ private static LoggingManager getLoggingManagerFromServiceLoader() { return loggingManager; } - private static void resetLogLevels(Level logLevel) { + private static void setLogLevels(Level logLevel) { LOGGING_MANAGER.setLogLevel(logLevel); } @@ -263,12 +263,12 @@ private void setLogLevelBasedOnSamplingRate(final ProceedingJoinPoint pjp, float sample = SAMPLER.nextFloat(); if (samplingRate > sample) { - resetLogLevels(Level.DEBUG); + setLogLevels(Level.DEBUG); LOG.debug("Changed log level to DEBUG based on Sampling configuration. " + "Sampling Rate: {}, Sampler Value: {}.", samplingRate, sample); } else if (LEVEL_AT_INITIALISATION != LOGGING_MANAGER.getLogLevel(LOG)) { - resetLogLevels(LEVEL_AT_INITIALISATION); + setLogLevels(LEVEL_AT_INITIALISATION); } } } diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 47c98c88e..88df818f2 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -707,6 +707,6 @@ private void resetLogLevel(Level level) Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); resetLogLevels.setAccessible(true); resetLogLevels.invoke(null, level); - LambdaLoggingAspect.LEVEL_AT_INITIALISATION = level; + writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); } } \ No newline at end of file From e7edb576b7ec7059a21528c145c51dc4a44193a8 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 11 Dec 2023 10:16:52 +0100 Subject: [PATCH 72/74] fix tests --- .../logging/internal/LambdaLoggingAspectTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index 88df818f2..fcf3dea36 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -704,9 +704,9 @@ private void setupContext() { private void resetLogLevel(Level level) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method resetLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("resetLogLevels", Level.class); - resetLogLevels.setAccessible(true); - resetLogLevels.invoke(null, level); + Method setLogLevels = LambdaLoggingAspect.class.getDeclaredMethod("setLogLevels", Level.class); + setLogLevels.setAccessible(true); + setLogLevels.invoke(null, level); writeStaticField(LambdaLoggingAspect.class, "LEVEL_AT_INITIALISATION", level, true); } } \ No newline at end of file From fd334db561cb417d91e885cb134dd8fd8396bd41 Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 11 Dec 2023 14:36:01 +0100 Subject: [PATCH 73/74] code review --- .../internal/DefautlLoggingManager.java | 35 +++++++++++ .../logging/internal/LambdaLoggingAspect.java | 47 +++++++++----- .../logging/internal/LoggingConstants.java | 6 +- .../internal/LambdaLoggingAspectTest.java | 62 ++++++++++++++++--- 4 files changed, 125 insertions(+), 25 deletions(-) create mode 100644 powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefautlLoggingManager.java diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefautlLoggingManager.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefautlLoggingManager.java new file mode 100644 index 000000000..5326f53e6 --- /dev/null +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/DefautlLoggingManager.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * 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 software.amazon.lambda.powertools.logging.internal; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +/** + * When no LoggingManager is found, setting a default one with no action on logging implementation + * Powertools cannot change the log level based on the environment variable, will use the logger configuration + */ +public class DefautlLoggingManager implements LoggingManager { + + @Override + public void setLogLevel(Level logLevel) { + // do nothing + } + + @Override + public Level getLogLevel(Logger logger) { + return Level.ERROR; + } +} diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java index efda042ef..48f67700d 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspect.java @@ -48,6 +48,9 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -123,24 +126,40 @@ private static Level getLevelFromString(String level) { * @return an instance of {@link LoggingManager} * @throws IllegalStateException if no {@link LoggingManager} could be found */ + @SuppressWarnings("java:S106") // S106: System.err is used rather than logger to make sure message is printed private static LoggingManager getLoggingManagerFromServiceLoader() { - LoggingManager loggingManager; + ServiceLoader loggingManagers; + SecurityManager securityManager = System.getSecurityManager(); + if (securityManager == null) { + loggingManagers = ServiceLoader.load(LoggingManager.class); + } else { + final PrivilegedAction> action = () -> ServiceLoader.load(LoggingManager.class); + loggingManagers = AccessController.doPrivileged(action); + } - ServiceLoader loggingManagers = ServiceLoader.load(LoggingManager.class); List loggingManagerList = new ArrayList<>(); for (LoggingManager lm : loggingManagers) { loggingManagerList.add(lm); } + return getLoggingManager(loggingManagerList, System.err); + } + + static LoggingManager getLoggingManager(List loggingManagerList, PrintStream printStream) { + LoggingManager loggingManager; if (loggingManagerList.isEmpty()) { - throw new IllegalStateException("No LoggingManager was found on the classpath, " - + - "make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); - } else if (loggingManagerList.size() > 1) { - throw new IllegalStateException( - "Multiple LoggingManagers were found on the classpath: " + loggingManagerList - + - ", make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies"); + printStream.println("ERROR. No LoggingManager was found on the classpath"); + printStream.println("ERROR. Applying default LoggingManager: POWERTOOLS_LOG_LEVEL variable is ignored"); + printStream.println("ERROR. Make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); + loggingManager = new DefautlLoggingManager(); } else { + if (loggingManagerList.size() > 1) { + printStream.println("WARN. Multiple LoggingManagers were found on the classpath"); + for (LoggingManager manager : loggingManagerList) { + printStream.println("WARN. Found LoggingManager: [" + manager + "]"); + } + printStream.println("WARN. Make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies"); + printStream.println("WARN. Using the first LoggingManager found on the classpath: [" + loggingManagerList.get(0) + "]"); + } loggingManager = loggingManagerList.get(0); } return loggingManager; @@ -183,7 +202,7 @@ public Object around(ProceedingJoinPoint pjp, // 3. retrieve this temporary stream to log it (if enabled) // 4. write it back to the OutputStream provided by Lambda OutputStream backupOutputStream = null; - if ((logging.logResponse() || "true".equals(POWERTOOLS_LOG_RESPONSE)) && isOnRequestStreamHandler) { + if ((logging.logResponse() || POWERTOOLS_LOG_RESPONSE) && isOnRequestStreamHandler) { backupOutputStream = (OutputStream) proceedArgs[1]; proceedArgs[1] = new ByteArrayOutputStream(); } @@ -193,7 +212,7 @@ public Object around(ProceedingJoinPoint pjp, try { lambdaFunctionResponse = pjp.proceed(proceedArgs); } catch (Throwable t) { - if (logging.logError() || "true".equals(POWERTOOLS_LOG_ERROR)) { + if (logging.logError() || POWERTOOLS_LOG_ERROR) { // logging the exception with additional context logger(pjp).error(MarkerFactory.getMarker("FATAL"), "Exception in Lambda Handler", t); } @@ -205,7 +224,7 @@ public Object around(ProceedingJoinPoint pjp, coldStartDone(); } - if ((logging.logResponse() || "true".equals(POWERTOOLS_LOG_RESPONSE))) { + if ((logging.logResponse() || POWERTOOLS_LOG_RESPONSE)) { if (isOnRequestHandler) { logRequestHandlerResponse(pjp, lambdaFunctionResponse); } else if (isOnRequestStreamHandler && backupOutputStream != null) { @@ -222,7 +241,7 @@ private Object[] logEvent(ProceedingJoinPoint pjp, Logging logging, boolean isOnRequestHandler, boolean isOnRequestStreamHandler) { Object[] proceedArgs = pjp.getArgs(); - if (logging.logEvent() || "true".equals(POWERTOOLS_LOG_EVENT)) { + if (logging.logEvent() || POWERTOOLS_LOG_EVENT) { if (isOnRequestHandler) { logRequestHandlerEvent(pjp, pjp.getArgs()[0]); } else if (isOnRequestStreamHandler) { diff --git a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java index 09d834dc9..989608a77 100644 --- a/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java +++ b/powertools-logging/src/main/java/software/amazon/lambda/powertools/logging/internal/LoggingConstants.java @@ -21,11 +21,11 @@ class LoggingConstants { static String POWERTOOLS_SAMPLING_RATE = System.getenv("POWERTOOLS_LOGGER_SAMPLE_RATE"); /* not final for test purpose */ - static String POWERTOOLS_LOG_EVENT = System.getenv("POWERTOOLS_LOGGER_LOG_EVENT"); /* not final for test purpose */ + static boolean POWERTOOLS_LOG_EVENT = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_EVENT")); /* not final for test purpose */ - static String POWERTOOLS_LOG_RESPONSE = System.getenv("POWERTOOLS_LOGGER_LOG_RESPONSE"); /* not final for test purpose */ + static boolean POWERTOOLS_LOG_RESPONSE = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_RESPONSE")); /* not final for test purpose */ - static String POWERTOOLS_LOG_ERROR = System.getenv("POWERTOOLS_LOGGER_LOG_ERROR"); /* not final for test purpose */ + static boolean POWERTOOLS_LOG_ERROR = "true".equals(System.getenv("POWERTOOLS_LOGGER_LOG_ERROR")); /* not final for test purpose */ private LoggingConstants() { // constants diff --git a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java index fcf3dea36..bc5e53675 100644 --- a/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java +++ b/powertools-logging/src/test/java/software/amazon/lambda/powertools/logging/internal/LambdaLoggingAspectTest.java @@ -46,6 +46,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.channels.FileChannel; @@ -54,7 +56,9 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -108,7 +112,7 @@ void setUp() throws IllegalAccessException, NoSuchMethodException, InvocationTar resetLogLevel(Level.INFO); writeStaticField(LoggingConstants.class, "LAMBDA_LOG_LEVEL", null, true); writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_LEVEL", null, true); - writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", null, true); + writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", false, true); writeStaticField(LoggingConstants.class, "POWERTOOLS_SAMPLING_RATE", null, true); try { FileChannel.open(Paths.get("target/logfile.json"), StandardOpenOption.WRITE).truncate(0).close(); @@ -475,7 +479,7 @@ void shouldLogEventForHandlerWithLogEventAnnotation() { void shouldLogEventForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { try { // GIVEN - LoggingConstants.POWERTOOLS_LOG_EVENT = "true"; + LoggingConstants.POWERTOOLS_LOG_EVENT = true; requestHandler = new PowertoolsLogEnabled(); @@ -491,14 +495,14 @@ void shouldLogEventForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessExce File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).contains("\"body\":\"body\"").contains("\"messageId\":\"1234abcd\"").contains("\"awsRegion\":\"eu-west-1\""); } finally { - writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_EVENT", "false", true); + LoggingConstants.POWERTOOLS_LOG_EVENT = false; } } @Test void shouldNotLogEventForHandlerWhenEnvVariableSetToFalse() throws IOException { // GIVEN - LoggingConstants.POWERTOOLS_LOG_EVENT = "false"; + LoggingConstants.POWERTOOLS_LOG_EVENT = false; // WHEN requestHandler = new PowertoolsLogEventDisabled(); @@ -543,7 +547,7 @@ void shouldLogResponseForHandlerWithLogResponseAnnotation() { void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { try { // GIVEN - LoggingConstants.POWERTOOLS_LOG_RESPONSE = "true"; + LoggingConstants.POWERTOOLS_LOG_RESPONSE = true; requestHandler = new PowertoolsLogEnabled(); @@ -554,7 +558,7 @@ void shouldLogResponseForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessE File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).contains("Bonjour le monde"); } finally { - writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_RESPONSE", "false", true); + LoggingConstants.POWERTOOLS_LOG_RESPONSE = false; } } @@ -597,7 +601,7 @@ void shouldLogErrorForHandlerWithLogErrorAnnotation() { void shouldLogErrorForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessException { try { // GIVEN - LoggingConstants.POWERTOOLS_LOG_ERROR = "true"; + LoggingConstants.POWERTOOLS_LOG_ERROR = true; requestHandler = new PowertoolsLogEnabled(true); @@ -611,7 +615,7 @@ void shouldLogErrorForHandlerWhenEnvVariableSetToTrue() throws IllegalAccessExce File logFile = new File("target/logfile.json"); assertThat(contentOf(logFile)).contains("Something went wrong"); } finally { - writeStaticField(LoggingConstants.class, "POWERTOOLS_LOG_ERROR", "false", true); + LoggingConstants.POWERTOOLS_LOG_ERROR = false; } } @@ -694,6 +698,48 @@ void shouldLogCorrelationIdOnAppSyncEvent() throws IOException { .containsEntry("correlation_id", eventId); } + @Test + void testMultipleLoggingManagers_shouldWarnAndSelectFirstOne() throws UnsupportedEncodingException { + // GIVEN + List list = new ArrayList<>(); + list.add(new TestLoggingManager()); + list.add(new DefautlLoggingManager()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(outputStream); + + // WHEN + LambdaLoggingAspect.getLoggingManager(list, stream); + + // THEN + String output = outputStream.toString("UTF-8"); + assertThat(output) + .contains("WARN. Multiple LoggingManagers were found on the classpath") + .contains("WARN. Make sure to have only one of powertools-logging-log4j OR powertools-logging-logback to your dependencies") + .contains("WARN. Using the first LoggingManager found on the classpath: [" + list.get(0) + "]"); + } + + @Test + void testNoLoggingManagers_shouldWarnAndCreateDefault() throws UnsupportedEncodingException { + // GIVEN + List list = new ArrayList<>(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(outputStream); + + // WHEN + LoggingManager loggingManager = LambdaLoggingAspect.getLoggingManager(list, stream); + + // THEN + String output = outputStream.toString("UTF-8"); + assertThat(output) + .contains("ERROR. No LoggingManager was found on the classpath") + .contains("ERROR. Applying default LoggingManager: POWERTOOLS_LOG_LEVEL variable is ignored") + .contains("ERROR. Make sure to add either powertools-logging-log4j or powertools-logging-logback to your dependencies"); + + assertThat(loggingManager).isExactlyInstanceOf(DefautlLoggingManager.class); + } + private void setupContext() { when(context.getFunctionName()).thenReturn("testFunction"); when(context.getInvokedFunctionArn()).thenReturn("testArn"); From 634d13e60ce5594f9f5f50e767b9a6655edbc45f Mon Sep 17 00:00:00 2001 From: Jerome Van Der Linden Date: Mon, 11 Dec 2023 15:03:48 +0100 Subject: [PATCH 74/74] code review last episode --- docs/core/logging.md | 16 +++--- .../internal/Log4jLoggingManager.java | 3 +- ...powertools.logging.internal.LoggingManager | 2 +- .../internal/Log4jLoggingManagerTest.java | 1 + .../{ => logback}/LambdaEcsEncoder.java | 10 ++-- .../{ => logback}/LambdaJsonEncoder.java | 16 +++--- .../{ => logback}/internal/JsonUtils.java | 2 +- .../internal/LambdaEcsSerializer.java | 53 +++++++++---------- .../internal/LambdaJsonSerializer.java | 25 +++++---- .../internal/LogbackLoggingManager.java | 3 +- ...powertools.logging.internal.LoggingManager | 2 +- .../logging/LogbackLoggingManagerTest.java | 2 +- .../internal/LambdaEcsEncoderTest.java | 2 +- .../internal/LambdaJsonEncoderTest.java | 2 +- .../src/test/resources/logback-test.xml | 6 +-- powertools-logging/spotbugs-exclude.xml | 4 +- spotbugs-exclude.xml | 4 +- 17 files changed, 77 insertions(+), 76 deletions(-) rename powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/{ => log4}/internal/Log4jLoggingManager.java (92%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/LambdaEcsEncoder.java (97%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/LambdaJsonEncoder.java (96%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/internal/JsonUtils.java (98%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/internal/LambdaEcsSerializer.java (73%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/internal/LambdaJsonSerializer.java (84%) rename powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/{ => logback}/internal/LogbackLoggingManager.java (93%) diff --git a/docs/core/logging.md b/docs/core/logging.md index 3c2475b34..f0fba760a 100644 --- a/docs/core/logging.md +++ b/docs/core/logging.md @@ -233,7 +233,7 @@ You can leverage the standard configuration files (_log4j2.xml_ or _logback.xml_ - + @@ -1008,7 +1008,7 @@ Logback configuration is done in _logback.xml_ and the Powertools [`LambdaJsonEn ```xml - + ``` @@ -1020,7 +1020,7 @@ The `LambdaJsonEncoder` can be customized in different ways: With the following configuration, you choose to log all the JSON messages as JSON and not as String (default is `false`): ```xml - + true ``` @@ -1030,7 +1030,7 @@ Utility by default emits `timestamp` field in the logs in format `yyyy-MM-dd'T'H If you need to customize format and timezone, you can change use the following: ```xml - + yyyy-MM-dd HH:mm:ss Europe/Paris @@ -1041,7 +1041,7 @@ If you need to customize format and timezone, you can change use the following: - You can use a standard `ThrowableHandlingConverter` to customize the exception format (default is no converter). Example: ```xml - + 30 2048 @@ -1058,7 +1058,7 @@ If you need to customize format and timezone, you can change use the following: - You can choose to add information about threads (default is `false`): ```xml - + true ``` @@ -1066,7 +1066,7 @@ If you need to customize format and timezone, you can change use the following: - You can even choose to remove Powertools information from the logs like function name, arn: ```xml - + false ``` @@ -1134,7 +1134,7 @@ Use the `LambdaEcsEncoder` rather than the `LambdaJsonEncoder` when configuring ```xml hl_lines="3" - + diff --git a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4/internal/Log4jLoggingManager.java similarity index 92% rename from powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java rename to powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4/internal/Log4jLoggingManager.java index a20671739..4e57a8e45 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManager.java +++ b/powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4/internal/Log4jLoggingManager.java @@ -12,13 +12,14 @@ * */ -package software.amazon.lambda.powertools.logging.internal; +package software.amazon.lambda.powertools.logging.log4.internal; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configurator; import org.slf4j.Logger; +import software.amazon.lambda.powertools.logging.internal.LoggingManager; /** * LoggingManager for Log4j2 (see {@link LoggingManager}). diff --git a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager index ae2ec0cba..d444c5525 100644 --- a/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager +++ b/powertools-logging/powertools-logging-log4j/src/main/resources/META-INF/services/software.amazon.lambda.powertools.logging.internal.LoggingManager @@ -1 +1 @@ -software.amazon.lambda.powertools.logging.internal.Log4jLoggingManager \ No newline at end of file +software.amazon.lambda.powertools.logging.log4.internal.Log4jLoggingManager \ No newline at end of file diff --git a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java index 0942eca52..69e1ee710 100644 --- a/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java +++ b/powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/internal/Log4jLoggingManagerTest.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import software.amazon.lambda.powertools.logging.log4.internal.Log4jLoggingManager; class Log4jLoggingManagerTest { diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java similarity index 97% rename from powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java index 1416c8192..1fc98ec67 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaEcsEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaEcsEncoder.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.logging; +package software.amazon.lambda.powertools.logging.logback; import static java.nio.charset.StandardCharsets.UTF_8; import static software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields.FUNCTION_ARN; @@ -31,7 +31,7 @@ import ch.qos.logback.core.encoder.EncoderBase; import java.util.Map; import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor; -import software.amazon.lambda.powertools.logging.internal.LambdaEcsSerializer; +import software.amazon.lambda.powertools.logging.logback.internal.LambdaEcsSerializer; /** @@ -128,7 +128,7 @@ public byte[] footerBytes() { * (default is null, no throwableConverter): *
    *

    {@code
    -     *     
    +     *     
          *         
          *              30
          *              2048
    @@ -160,7 +160,7 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
          * We strongly recommend to keep these information.
          * 
    *
    {@code
    -     *     
    +     *     
          *         false
          *     
          * }
    @@ -186,7 +186,7 @@ public void setIncludeCloudInfo(boolean includeCloudInfo) { * We strongly recommend to keep these information. *
    *
    {@code
    -     *     
    +     *     
          *         false
          *     
          * }
    diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java similarity index 96% rename from powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java index 9d375089d..b951e266e 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/LambdaJsonEncoder.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/LambdaJsonEncoder.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.logging; +package software.amazon.lambda.powertools.logging.logback; import static java.nio.charset.StandardCharsets.UTF_8; import static software.amazon.lambda.powertools.logging.LoggingUtils.LOG_MESSAGES_AS_JSON; @@ -23,7 +23,7 @@ import ch.qos.logback.classic.spi.IThrowableProxy; import ch.qos.logback.classic.spi.ThrowableProxy; import ch.qos.logback.core.encoder.EncoderBase; -import software.amazon.lambda.powertools.logging.internal.LambdaJsonSerializer; +import software.amazon.lambda.powertools.logging.logback.internal.LambdaJsonSerializer; /** * Custom encoder for logback that encodes logs in JSON format. @@ -97,7 +97,7 @@ public byte[] footerBytes() { * Note that if you use the Lambda Advanced Logging Configuration, you should keep the default format. *
    *
    {@code
    -     *     
    +     *     
          *         yyyy-MM-dd'T'HH:mm:ss.SSSZz
          *     
          * }
    @@ -112,7 +112,7 @@ public void setTimestampFormat(String timestampFormat) { * Specify the format of the time zone id for timestamp (default is null, no timezone): *
    *
    {@code
    -     *     
    +     *     
          *         Europe/Paris
          *     
          * }
    @@ -128,7 +128,7 @@ public void setTimestampFormatTimezoneId(String timestampFormatTimezoneId) { * (default is null, no throwableConverter): *
    *
    {@code
    -     *     
    +     *     
          *         
          *              30
          *              2048
    @@ -152,7 +152,7 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
          * Specify if thread information should be logged (default is false)
          * 
    *
    {@code
    -     *     
    +     *     
          *         true
          *     
          * }
    @@ -180,7 +180,7 @@ public void setIncludeThreadInfo(boolean includeThreadInfo) { * We strongly recommend to keep these information. *
    *
    {@code
    -     *     
    +     *     
          *         false
          *     
          * }
    @@ -195,7 +195,7 @@ public void setIncludePowertoolsInfo(boolean includePowertoolsInfo) { * Specify if messages should be logged as JSON, without escaping string (default is false): *
    *
    {@code
    -     *     
    +     *     
          *         true
          *     
          * }
    diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/JsonUtils.java similarity index 98% rename from powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/JsonUtils.java index 7f5e380b2..e604d10c7 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/JsonUtils.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/JsonUtils.java @@ -12,7 +12,7 @@ * */ -package software.amazon.lambda.powertools.logging.internal; +package software.amazon.lambda.powertools.logging.logback.internal; /** * Json tools to serialize attributes manually, to avoid using further dependencies (jackson, gson...) diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaEcsSerializer.java similarity index 73% rename from powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaEcsSerializer.java index 124e0b5c6..bab1a32fc 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaEcsSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaEcsSerializer.java @@ -12,9 +12,7 @@ * */ -package software.amazon.lambda.powertools.logging.internal; - -import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttributeAsString; +package software.amazon.lambda.powertools.logging.logback.internal; import ch.qos.logback.classic.Level; import java.text.DateFormat; @@ -25,6 +23,7 @@ import java.util.TimeZone; import java.util.TreeMap; import java.util.regex.Matcher; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; /** * This class will serialize the log events in ecs format (ElasticSearch).
    @@ -83,28 +82,28 @@ public static void serializeTimestamp(StringBuilder builder, long timestamp, Str } formattedTimestamp = format.format(date); } - serializeAttributeAsString(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp, false); + JsonUtils.serializeAttributeAsString(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp, false); } public static void serializeThreadName(StringBuilder builder, String threadName) { if (threadName != null) { - serializeAttributeAsString(builder, THREAD_ATTR_NAME, threadName); + JsonUtils.serializeAttributeAsString(builder, THREAD_ATTR_NAME, threadName); } } public static void serializeLogLevel(StringBuilder builder, Level level) { - serializeAttributeAsString(builder, LEVEL_ATTR_NAME, level.toString()); + JsonUtils.serializeAttributeAsString(builder, LEVEL_ATTR_NAME, level.toString()); } public static void serializeFormattedMessage(StringBuilder builder, String formattedMessage) { - serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, + JsonUtils.serializeAttributeAsString(builder, FORMATTED_MESSAGE_ATTR_NAME, formattedMessage.replace("\"", Matcher.quoteReplacement("\\\""))); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { - serializeAttributeAsString(builder, EXCEPTION_MSG_ATTR_NAME, message); - serializeAttributeAsString(builder, EXCEPTION_CLASS_ATTR_NAME, className); - serializeAttributeAsString(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); + JsonUtils.serializeAttributeAsString(builder, EXCEPTION_MSG_ATTR_NAME, message); + JsonUtils.serializeAttributeAsString(builder, EXCEPTION_CLASS_ATTR_NAME, className); + JsonUtils.serializeAttributeAsString(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); } public static void serializeException(StringBuilder builder, Throwable throwable) { @@ -113,7 +112,7 @@ public static void serializeException(StringBuilder builder, Throwable throwable } public static void serializeThreadId(StringBuilder builder, String threadId) { - serializeAttributeAsString(builder, THREAD_ID_ATTR_NAME, threadId); + JsonUtils.serializeAttributeAsString(builder, THREAD_ID_ATTR_NAME, threadId); } public static void serializeAdditionalFields(StringBuilder builder, Map mdc) { @@ -121,68 +120,68 @@ public static void serializeAdditionalFields(StringBuilder builder, Map { if (!PowertoolsLoggedFields.stringValues().contains(k)) { - serializeAttributeAsString(builder, k, v); + JsonUtils.serializeAttributeAsString(builder, k, v); } }); } public static void serializeEcsVersion(StringBuilder builder, String ecsVersion) { - serializeAttributeAsString(builder, ECS_VERSION_ATTR_NAME, ecsVersion); + JsonUtils.serializeAttributeAsString(builder, ECS_VERSION_ATTR_NAME, ecsVersion); } public static void serializeServiceName(StringBuilder builder, String serviceName) { - serializeAttributeAsString(builder, SERVICE_NAME_ATTR_NAME, serviceName); + JsonUtils.serializeAttributeAsString(builder, SERVICE_NAME_ATTR_NAME, serviceName); } public static void serializeServiceVersion(StringBuilder builder, String serviceVersion) { - serializeAttributeAsString(builder, SERVICE_VERSION_ATTR_NAME, serviceVersion); + JsonUtils.serializeAttributeAsString(builder, SERVICE_VERSION_ATTR_NAME, serviceVersion); } public static void serializeLoggerName(StringBuilder builder, String loggerName) { - serializeAttributeAsString(builder, LOGGER_ATTR_NAME, loggerName); + JsonUtils.serializeAttributeAsString(builder, LOGGER_ATTR_NAME, loggerName); } public static void serializeCloudProvider(StringBuilder builder, String cloudProvider) { - serializeAttributeAsString(builder, CLOUD_PROVIDER_ATTR_NAME, cloudProvider); + JsonUtils.serializeAttributeAsString(builder, CLOUD_PROVIDER_ATTR_NAME, cloudProvider); } public static void serializeCloudService(StringBuilder builder, String cloudService) { - serializeAttributeAsString(builder, CLOUD_SERVICE_ATTR_NAME, cloudService); + JsonUtils.serializeAttributeAsString(builder, CLOUD_SERVICE_ATTR_NAME, cloudService); } public static void serializeCloudRegion(StringBuilder builder, String cloudRegion) { - serializeAttributeAsString(builder, CLOUD_REGION_ATTR_NAME, cloudRegion); + JsonUtils.serializeAttributeAsString(builder, CLOUD_REGION_ATTR_NAME, cloudRegion); } public static void serializeCloudAccountId(StringBuilder builder, String cloudAccountId) { - serializeAttributeAsString(builder, CLOUD_ACCOUNT_ATTR_NAME, cloudAccountId); + JsonUtils.serializeAttributeAsString(builder, CLOUD_ACCOUNT_ATTR_NAME, cloudAccountId); } public static void serializeColdStart(StringBuilder builder, String coldStart) { - serializeAttributeAsString(builder, FUNCTION_COLD_START_ATTR_NAME, coldStart); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_COLD_START_ATTR_NAME, coldStart); } public static void serializeFunctionExecutionId(StringBuilder builder, String requestId) { - serializeAttributeAsString(builder, FUNCTION_REQUEST_ID_ATTR_NAME, requestId); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_REQUEST_ID_ATTR_NAME, requestId); } public static void serializeFunctionId(StringBuilder builder, String functionArn) { - serializeAttributeAsString(builder, FUNCTION_ARN_ATTR_NAME, functionArn); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_ARN_ATTR_NAME, functionArn); } public static void serializeFunctionName(StringBuilder builder, String functionName) { - serializeAttributeAsString(builder, FUNCTION_NAME_ATTR_NAME, functionName); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_NAME_ATTR_NAME, functionName); } public static void serializeFunctionVersion(StringBuilder builder, String functionVersion) { - serializeAttributeAsString(builder, FUNCTION_VERSION_ATTR_NAME, functionVersion); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_VERSION_ATTR_NAME, functionVersion); } public static void serializeFunctionMemory(StringBuilder builder, String functionMemory) { - serializeAttributeAsString(builder, FUNCTION_MEMORY_ATTR_NAME, functionMemory); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_MEMORY_ATTR_NAME, functionMemory); } public static void serializeTraceId(StringBuilder builder, String traceId) { - serializeAttributeAsString(builder, FUNCTION_TRACE_ID_ATTR_NAME, traceId); + JsonUtils.serializeAttributeAsString(builder, FUNCTION_TRACE_ID_ATTR_NAME, traceId); } } diff --git a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaJsonSerializer.java similarity index 84% rename from powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java rename to powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaJsonSerializer.java index fbe11adec..7d7b8d0d7 100644 --- a/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/internal/LambdaJsonSerializer.java +++ b/powertools-logging/powertools-logging-logback/src/main/java/software/amazon/lambda/powertools/logging/logback/internal/LambdaJsonSerializer.java @@ -12,12 +12,10 @@ * */ -package software.amazon.lambda.powertools.logging.internal; +package software.amazon.lambda.powertools.logging.logback.internal; import static java.lang.Boolean.TRUE; import static software.amazon.lambda.powertools.logging.LoggingUtils.LOG_MESSAGES_AS_JSON; -import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeAttribute; -import static software.amazon.lambda.powertools.logging.internal.JsonUtils.serializeMessage; import ch.qos.logback.classic.Level; import com.fasterxml.jackson.core.JacksonException; @@ -30,6 +28,7 @@ import java.util.Map; import java.util.TimeZone; import java.util.TreeMap; +import software.amazon.lambda.powertools.logging.internal.PowertoolsLoggedFields; /** * This class will serialize the log events in json.
    @@ -73,17 +72,17 @@ public static void serializeTimestamp(StringBuilder builder, long timestamp, Str } formattedTimestamp = format.format(date); } - serializeAttribute(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp); + JsonUtils.serializeAttribute(builder, TIMESTAMP_ATTR_NAME, formattedTimestamp); } public static void serializeThreadName(StringBuilder builder, String threadName) { if (threadName != null) { - serializeAttribute(builder, THREAD_ATTR_NAME, threadName); + JsonUtils.serializeAttribute(builder, THREAD_ATTR_NAME, threadName); } } public static void serializeLogLevel(StringBuilder builder, Level level) { - serializeAttribute(builder, LEVEL_ATTR_NAME, level.toString(), false); + JsonUtils.serializeAttribute(builder, LEVEL_ATTR_NAME, level.toString(), false); } public static void serializeFormattedMessage(StringBuilder builder, String message, @@ -95,14 +94,14 @@ public static void serializeFormattedMessage(StringBuilder builder, String messa boolean logAsJson = ((logMessagesAsJsonGlobal && logMessagesAsJson == null) || TRUE.equals(logMessagesAsJson)) && isValidJson(message); - serializeMessage(builder, FORMATTED_MESSAGE_ATTR_NAME, message, logAsJson); + JsonUtils.serializeMessage(builder, FORMATTED_MESSAGE_ATTR_NAME, message, logAsJson); } public static void serializeException(StringBuilder builder, String className, String message, String stackTrace) { builder.append(",\"").append(EXCEPTION_ATTR_NAME).append("\":{"); - serializeAttribute(builder, EXCEPTION_MSG_ATTR_NAME, message, false); - serializeAttribute(builder, EXCEPTION_CLASS_ATTR_NAME, className); - serializeAttribute(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); + JsonUtils.serializeAttribute(builder, EXCEPTION_MSG_ATTR_NAME, message, false); + JsonUtils.serializeAttribute(builder, EXCEPTION_CLASS_ATTR_NAME, className); + JsonUtils.serializeAttribute(builder, EXCEPTION_STACK_ATTR_NAME, stackTrace); builder.append("}"); } @@ -112,11 +111,11 @@ public static void serializeException(StringBuilder builder, Throwable throwable } public static void serializeThreadId(StringBuilder builder, String threadId) { - serializeAttribute(builder, THREAD_ID_ATTR_NAME, threadId); + JsonUtils.serializeAttribute(builder, THREAD_ID_ATTR_NAME, threadId); } public static void serializeThreadPriority(StringBuilder builder, String threadPriority) { - serializeAttribute(builder, THREAD_PRIORITY_ATTR_NAME, threadPriority); + JsonUtils.serializeAttribute(builder, THREAD_PRIORITY_ATTR_NAME, threadPriority); } public static void serializePowertools(StringBuilder builder, Map mdc, @@ -127,7 +126,7 @@ public static void serializePowertools(StringBuilder builder, Map - + target/logfile.json - + target/ecslogfile.json - + diff --git a/powertools-logging/spotbugs-exclude.xml b/powertools-logging/spotbugs-exclude.xml index 30a9c8557..0437849ae 100644 --- a/powertools-logging/spotbugs-exclude.xml +++ b/powertools-logging/spotbugs-exclude.xml @@ -12,11 +12,11 @@ - + - + diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index a2559017c..7c42513f0 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -30,11 +30,11 @@ - + - +