Skip to content

Commit 0ff03b2

Browse files
committed
Add logback implementation. Decouple KeyBuffer and BufferingAppender to avoid circular dependency when flushing buffer on error.
1 parent e158b76 commit 0ff03b2

File tree

15 files changed

+645
-61
lines changed

15 files changed

+645
-61
lines changed

examples/powertools-examples-core-utilities/sam/pom.xml

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</dependency>
2323
<dependency>
2424
<groupId>software.amazon.lambda</groupId>
25-
<artifactId>powertools-logging-log4j</artifactId>
25+
<artifactId>powertools-logging-logback</artifactId>
2626
<version>${project.version}</version>
2727
</dependency>
2828
<dependency>
@@ -99,19 +99,10 @@
9999
</goals>
100100
<configuration>
101101
<createDependencyReducedPom>false</createDependencyReducedPom>
102-
<transformers>
103-
<transformer implementation="org.apache.logging.log4j.maven.plugins.shade.transformer.Log4j2PluginCacheFileTransformer"/>
104-
</transformers>
105102
</configuration>
106103
</execution>
107104
</executions>
108-
<dependencies>
109-
<dependency>
110-
<groupId>org.apache.logging.log4j</groupId>
111-
<artifactId>log4j-transform-maven-shade-plugin-extensions</artifactId>
112-
<version>0.2.0</version>
113-
</dependency>
114-
</dependencies>
105+
115106
</plugin>
116107
<!-- Don't deploy the example -->
117108
<plugin>

examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/App.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEv
5858
log.info("INFO 1");
5959

6060
// Manually flush buffer to show buffered debug logs
61-
PowertoolsLogging.flushBuffer();
61+
// PowertoolsLogging.flushBuffer();
62+
log.error("Some error happened");
6263

6364
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
6465
.withHeaders(headers);

examples/powertools-examples-core-utilities/sam/src/main/java/helloworld/AppStream.java

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,49 @@
1414

1515
package helloworld;
1616

17-
import com.amazonaws.services.lambda.runtime.Context;
18-
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
19-
import com.fasterxml.jackson.databind.ObjectMapper;
20-
17+
import java.io.BufferedReader;
18+
import java.io.BufferedWriter;
2119
import java.io.IOException;
2220
import java.io.InputStream;
21+
import java.io.InputStreamReader;
2322
import java.io.OutputStream;
23+
import java.io.OutputStreamWriter;
24+
import java.io.PrintWriter;
2425
import java.nio.charset.StandardCharsets;
2526

26-
import org.apache.logging.log4j.LogManager;
27-
import org.apache.logging.log4j.Logger;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
import com.amazonaws.services.lambda.runtime.Context;
31+
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
32+
import com.fasterxml.jackson.databind.ObjectMapper;
33+
2834
import software.amazon.lambda.powertools.logging.Logging;
2935
import software.amazon.lambda.powertools.metrics.FlushMetrics;
3036

31-
import java.io.InputStreamReader;
32-
import java.io.BufferedReader;
33-
import java.io.BufferedWriter;
34-
import java.io.OutputStreamWriter;
35-
import java.io.PrintWriter;
36-
3737
public class AppStream implements RequestStreamHandler {
3838
private static final ObjectMapper mapper = new ObjectMapper();
39-
private final static Logger log = LogManager.getLogger(AppStream.class);
39+
private static final Logger log = LoggerFactory.getLogger(AppStream.class);
4040

4141
@Override
4242
@Logging(logEvent = true)
4343
@FlushMetrics(namespace = "ServerlessAirline", service = "payment", captureColdStart = true)
44-
// RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body or serialize response body yourself, instead of allowing that to happen automatically
45-
// Note that you still need to return a proper JSON for API Gateway to handle
46-
// See Lambda Response format for examples: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
44+
// RequestStreamHandler can be used instead of RequestHandler for cases when you'd like to deserialize request body
45+
// or serialize response body yourself, instead of allowing that to happen automatically
46+
// Note that you still need to return a proper JSON for API Gateway to handle
47+
// See Lambda Response format for examples:
48+
// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
4749
public void handleRequest(InputStream input, OutputStream output, Context context) {
4850
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
49-
PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) {
51+
PrintWriter writer = new PrintWriter(
52+
new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8)))) {
5053

51-
log.info("Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader)));
54+
log.info(
55+
"Received: " + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(mapper.readTree(reader)));
5256

5357
writer.write("{\"body\": \"" + System.currentTimeMillis() + "\"} ");
5458
} catch (IOException e) {
5559
log.error("Something has gone wrong: ", e);
5660
}
5761
}
5862
}
59-
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<appender name="JsonAppender" class="ch.qos.logback.core.ConsoleAppender">
4+
<encoder class="software.amazon.lambda.powertools.logging.logback.LambdaJsonEncoder" />
5+
</appender>
6+
7+
<appender name="BufferedAppender" class="software.amazon.lambda.powertools.logging.logback.BufferingAppender">
8+
<bufferAtVerbosity>DEBUG</bufferAtVerbosity>
9+
<maxBytes>8</maxBytes>
10+
<appender-ref ref="JsonAppender" />
11+
</appender>
12+
13+
<root level="DEBUG">
14+
<appender-ref ref="BufferedAppender" />
15+
</root>
16+
</configuration>

powertools-logging/powertools-logging-log4j/src/main/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppender.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.apache.logging.log4j.core.config.plugins.PluginElement;
3535
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
3636
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
37+
import org.apache.logging.log4j.message.SimpleMessage;
3738

3839
import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor;
3940
import software.amazon.lambda.powertools.logging.internal.BufferManager;
@@ -91,6 +92,7 @@ public class BufferingAppender extends AbstractAppender implements BufferManager
9192
private final boolean flushOnErrorLog;
9293
private final KeyBuffer<String, LogEvent> buffer;
9394

95+
@SuppressWarnings("java:S107") // Constructor has too many parameters, which is OK for a Log4j2 plugin
9496
protected BufferingAppender(String name, Filter filter, Layout<? extends Serializable> layout,
9597
AppenderRef[] appenderRefs, Configuration configuration, Level bufferAtVerbosity, int maxBytes,
9698
boolean flushOnErrorLog) {
@@ -99,7 +101,8 @@ protected BufferingAppender(String name, Filter filter, Layout<? extends Seriali
99101
this.configuration = configuration;
100102
this.bufferAtVerbosity = bufferAtVerbosity;
101103
this.flushOnErrorLog = flushOnErrorLog;
102-
this.buffer = new KeyBuffer<>(maxBytes, event -> event.getMessage().getFormattedMessage().length());
104+
this.buffer = new KeyBuffer<>(maxBytes, event -> event.getMessage().getFormattedMessage().length(),
105+
this::logOverflowWarning);
103106
}
104107

105108
@Override
@@ -159,6 +162,7 @@ private void flushBuffer(String traceId) {
159162
}
160163

161164
@PluginFactory
165+
@SuppressWarnings("java:S107") // Method has too many parameters, which is OK for a Log4j2 plugin factory
162166
public static BufferingAppender createAppender(
163167
@PluginAttribute("name") String name,
164168
@PluginElement("Filter") Filter filter,
@@ -182,4 +186,18 @@ public static BufferingAppender createAppender(
182186
return new BufferingAppender(name, filter, layout, appenderRefs, configuration, level, maxBytes,
183187
flushOnErrorLog);
184188
}
189+
190+
private void logOverflowWarning() {
191+
// Create a properly formatted warning event and send directly to child appenders. Used to avoid circular
192+
// dependency between KeyBuffer and BufferingAppender.
193+
SimpleMessage message = new SimpleMessage(
194+
"Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer.");
195+
LogEvent warningEvent = Log4jLogEvent.newBuilder()
196+
.setLoggerName(BufferingAppender.class.getName())
197+
.setLevel(Level.WARN)
198+
.setMessage(message)
199+
.setTimeMillis(System.currentTimeMillis())
200+
.build();
201+
callAppenders(warningEvent);
202+
}
185203
}

powertools-logging/powertools-logging-log4j/src/test/java/software/amazon/lambda/powertools/logging/log4j/BufferingAppenderTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ void shouldClearBufferManually() {
110110
.doesNotContain("Buffered message");
111111
}
112112

113+
@Test
114+
void shouldLogOverflowWarningWhenBufferOverflows() {
115+
// When - fill buffer beyond capacity to trigger overflow
116+
for (int i = 0; i < 100; i++) {
117+
logger.debug("Debug message " + i);
118+
}
119+
120+
// When - flush buffer to trigger overflow warning
121+
BufferingAppender appender = getBufferingAppender();
122+
appender.flushBuffer();
123+
124+
// Then - overflow warning should be logged
125+
File logFile = new File("target/logfile.json");
126+
assertThat(contentOf(logFile))
127+
.contains("Some logs are not displayed because they were evicted from the buffer");
128+
}
129+
113130
private BufferingAppender getBufferingAppender() {
114131
return (BufferingAppender) ((org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false))
115132
.getConfiguration().getAppender(Log4jConstants.BUFFERING_APPENDER_PLUGIN_NAME);

powertools-logging/powertools-logging-logback/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@
8585
<artifactId>jsonassert</artifactId>
8686
<scope>test</scope>
8787
</dependency>
88+
<dependency>
89+
<groupId>org.junit-pioneer</groupId>
90+
<artifactId>junit-pioneer</artifactId>
91+
<scope>test</scope>
92+
</dependency>
8893
</dependencies>
8994

9095
<profiles>

0 commit comments

Comments
 (0)