1- /*
2- * Copyright 2025. New Relic Corporation. All rights reserved.
3- * SPDX-License-Identifier: Apache-2.0
4- */
5-
6- import ch .qos .logback .classic .AsyncAppender ;
7- import ch .qos .logback .classic .Level ;
81import ch .qos .logback .classic .LoggerContext ;
92import ch .qos .logback .classic .spi .ILoggingEvent ;
103import ch .qos .logback .classic .spi .LoggingEvent ;
11- import ch .qos .logback .classic .spi .ThrowableProxy ;
124import ch .qos .logback .core .ConsoleAppender ;
13- import com . google . common . collect . ImmutableMap ;
5+ import ch . qos . logback . core . read . ListAppender ;
146import com .newrelic .api .agent .Agent ;
15- import com .newrelic .logging .core .LogAsserts ;
16- import com .newrelic .logging .logback13 .CustomArgument ;
177import com .newrelic .logging .logback13 .NewRelicAsyncAppender ;
188import com .newrelic .logging .logback13 .NewRelicEncoder ;
19- import org .junit .jupiter .api .AfterAll ;
209import org .junit .jupiter .api .AfterEach ;
21- import org .junit .jupiter .api .BeforeAll ;
2210import org .junit .jupiter .api .BeforeEach ;
2311import org .junit .jupiter .api .Test ;
24- import org .junit .jupiter .api .Timeout ;
2512import org .mockito .Mockito ;
26- import org .slf4j .LoggerFactory ;
2713import org .slf4j .MDC ;
2814
29- import java .io .BufferedReader ;
30- import java .io .IOException ;
31- import java .io .InputStreamReader ;
32- import java .io .PipedInputStream ;
33- import java .io .PipedOutputStream ;
34- import java .nio .charset .StandardCharsets ;
35- import java .util .function .Supplier ;
36- import java .util .regex .Pattern ;
15+ import java .util .Map ;
3716
38- import static org . junit . jupiter . api . Assertions .*;
17+ public class NewRelicLogback13Tests {
3918
40- class NewRelicLogback13Tests {
41- private AsyncAppender appender ;
42- private LoggingEvent event ;
43- private PipedOutputStream outputStream ;
44- private BufferedReader bufferedReader ;
45- private String output ;
19+ private static final LoggerContext loggerContext = new LoggerContext ();
20+ private static final String TEST_MESSAGE = "This is an amazing test message." ;
4621
47- private static Supplier < Agent > savedSupplier ;
48- private boolean isNoOpMDC ;
22+ private NewRelicAsyncAppender appender ;
23+ private ListAppender < ILoggingEvent > listAppender ;
4924
5025 @ BeforeEach
51- void setUp () throws Exception {
52- // Clear MDC data before each test
53- isNoOpMDC = NewRelicAsyncAppender .isNoOpMDC ;
54- MDC .clear ();
55- outputStream = new PipedOutputStream ();
56- PipedInputStream inputStream = new PipedInputStream (outputStream );
57- bufferedReader = new BufferedReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ));
58- }
59-
60- @ AfterEach
61- void tearDown () throws Exception {
62- // Clear MDC data before each test
63- MDC .clear ();
64- NewRelicAsyncAppender .isNoOpMDC = isNoOpMDC ;
65- appender .stop ();
66- appender .detachAndStopAllAppenders ();
67- outputStream .close ();
68- bufferedReader .close ();
69- }
70-
71- @ BeforeAll
72- static void setUpClass () {
73- savedSupplier = NewRelicAsyncAppender .agentSupplier ;
74- }
75-
76- @ AfterAll
77- static void tearDownClass () {
78- NewRelicAsyncAppender .agentSupplier = savedSupplier ;
79- }
80-
81- @ Test
82- @ Timeout (3 )
83- void shouldWrapJsonConsoleAppenderCorrectly () throws Throwable {
84- givenMockAgentData ();
85- givenARedirectedAppender ();
86- givenALoggingEvent ();
87- whenTheEventIsAppended ();
88- thenMockAgentDataIsInTheMessage ();
89- thenJsonLayoutWasUsed ();
90- }
91-
92- @ Test
93- @ Timeout (3 )
94- void shouldAllWorkCorrectlyEvenWithoutMDC () throws Throwable {
95- givenMockAgentData ();
96- givenARedirectedAppender ();
97- givenMDCIsANoOp ();
98- givenALoggingEvent ();
99- whenTheEventIsAppended ();
100- thenMockAgentDataIsInTheMessage ();
101- thenJsonLayoutWasUsed ();
102- }
103-
104- @ Test
105- @ Timeout (3 )
106- void shouldAppendCallerDataToJsonCorrectly () throws Throwable {
107- givenMockAgentData ();
108- givenARedirectedAppender ();
109- givenALoggingEventWithCallerData ();
110- whenTheEventIsAppended ();
111- thenJsonLayoutWasUsed ();
112- thenTheCallerDataIsInTheMessage ();
113- }
114-
115- @ Test
116- @ Timeout (3 )
117- void shouldAppendErrorDataCorrectly () throws Throwable {
118- givenMockAgentData ();
119- givenARedirectedAppender ();
120- givenALoggingEventWithExceptionData ();
121- whenTheEventIsAppended ();
122- thenJsonLayoutWasUsed ();
123- thenTheExceptionDataIsInTheMessage ();
124- }
125-
126- @ Test
127- @ Timeout (3 )
128- void shouldAppendCustomArgsToJsonCorrectly () throws Throwable {
129- givenMockAgentData ();
130- givenARedirectedAppender ();
131- givenALoggingEventWithCustomArgs ();
132- whenTheEventIsAppended ();
133- thenJsonLayoutWasUsed ();
134- thenTheCustomArgsAreInTheMessage ();
135- }
136-
137- @ Test
138- @ Timeout (3 )
139- void shouldAppendMDCArgsToJsonWhenEnabled () throws Throwable {
140- givenMockAgentData ();
141- givenARedirectedAppender ();
142- givenALoggingEventWithMDCEnabled ();
143- whenTheEventIsAppended ();
144- thenJsonLayoutWasUsed ();
145- thenTheMDCFieldsAreInTheMessage (true );
146- }
147-
148- @ Test
149- @ Timeout (3 )
150- void shouldNotAppendMDCArgsToJsonWhenDisabled () throws Throwable {
151- givenMockAgentData ();
152- givenARedirectedAppender ();
153- givenALoggingEventWithMDCDisabled ();
154- whenTheEventIsAppended ();
155- thenJsonLayoutWasUsed ();
156- thenTheMDCFieldsAreInTheMessage (false );
157- }
158-
159- private void givenMockAgentData () {
26+ void setup () {
16027 Agent mockAgent = Mockito .mock (Agent .class );
161- Mockito .when (mockAgent .getLinkingMetadata ()).thenReturn (ImmutableMap .of ("some.key " , "some.value " ));
28+ Mockito .when (mockAgent .getLinkingMetadata ()).thenReturn (Map .of ("traceId " , "abd123" , "spanId" , "xyz789 " ));
16229 NewRelicAsyncAppender .agentSupplier = () -> mockAgent ;
163- }
164-
165- private void givenMDCIsANoOp () {
166- // Wipe the MDC to mimic a NOPMDCAdapter.
167- NewRelicAsyncAppender .isNoOpMDC = true ;
168- }
169-
170- private void givenALoggingEvent () {
171- event = new LoggingEvent ();
172- event .setMessage ("test_error_message" );
173- event .setLevel (Level .ERROR );
174- event .setLoggerContext ((LoggerContext ) LoggerFactory .getILoggerFactory ());
175- }
176-
177- private void givenALoggingEventWithExceptionData () {
178- givenALoggingEvent ();
179- event .setThrowableProxy (new ThrowableProxy (new Exception ("some interesting info" )));
180- }
181-
182- private void givenALoggingEventWithCallerData () {
183- givenALoggingEvent ();
184- event .setCallerData (new StackTraceElement [] { new Exception ().getStackTrace ()[0 ] });
185- }
18630
187- private void givenALoggingEventWithCustomArgs () {
188- givenALoggingEvent ();
189- CustomArgument customArgument1 = new CustomArgument ("customKey1" , "customValue1" );
190- CustomArgument customArgument2 = new CustomArgument ("customKey2" , "customValue2" );
191- Object [] customArgs = new Object [2 ];
192- customArgs [0 ] = customArgument1 ;
193- customArgs [1 ] = customArgument2 ;
194- event .setArgumentArray (customArgs );
195- }
196-
197- private void givenALoggingEventWithMDCEnabled () {
198- // Enable MDC collection
199- System .setProperty ("newrelic.log_extension.add_mdc" , "true" );
200-
201- // Add MDC data
202- MDC .put ("contextKey1" , "contextData1" );
203- MDC .put ("contextKey2" , "contextData2" );
204- MDC .put ("contextKey3" , "contextData3" );
205-
206- givenALoggingEvent ();
207- }
208-
209- private void givenALoggingEventWithMDCDisabled () {
210- // Disable MDC collection
211- System .setProperty ("newrelic.log_extension.add_mdc" , "false" );
212-
213- // Add MDC data
214- MDC .put ("contextKey1" , "contextData1" );
215- MDC .put ("contextKey2" , "contextData2" );
216- MDC .put ("contextKey3" , "contextData3" );
217-
218- givenALoggingEvent ();
219- }
220-
221- private void givenARedirectedAppender () {
22231 NewRelicEncoder encoder = new NewRelicEncoder ();
22332 encoder .start ();
22433
225- LoggerContext context = new LoggerContext ();
22634 ConsoleAppender <ILoggingEvent > consoleAppender = new ConsoleAppender <>();
227- consoleAppender .setContext (context );
35+ consoleAppender .setContext (loggerContext );
22836 consoleAppender .setEncoder (encoder );
22937 consoleAppender .start ();
230- // must be set _after_ start()
231- consoleAppender .setOutputStream (outputStream );
23238
23339 appender = new NewRelicAsyncAppender ();
234- appender .setContext (context );
40+ appender .setContext (loggerContext );
23541 appender .addAppender (consoleAppender );
23642 appender .start ();
23743 }
23844
239- private void whenTheEventIsAppended () throws IOException {
240- appender .doAppend (event );
241- outputStream .flush ();
242- }
243-
244- private void thenJsonLayoutWasUsed () throws IOException {
245- LogAsserts .assertFieldValues (
246- getOutput (),
247- ImmutableMap .of (
248- "message" , "test_error_message" ,
249- "log.level" , "ERROR" ,
250- "some.key" , "some.value"
251- )
252- );
253- }
254-
255- private void thenMockAgentDataIsInTheMessage () throws Throwable {
256- assertTrue (
257- getOutput ().contains ("some.key=some.value" )
258- || getOutput ().contains ("\" some.key\" :\" some.value\" " ),
259- "Expected >>" + getOutput () + "<< to contain some.key to some.value"
260- );
45+ @ AfterEach
46+ void tearDown () {
47+ MDC .clear ();
48+ appender .stop ();
49+ appender .detachAndStopAllAppenders ();
26150 }
26251
263- private void thenTheCallerDataIsInTheMessage () throws Throwable {
264- LogAsserts .assertFieldValues (
265- getOutput (),
266- ImmutableMap .of ("class.name" , this .getClass ().getName (), "method.name" , "givenALoggingEventWithCallerData" )
267- );
268- }
52+ @ Test
53+ void testBasicLogMessageIncludesLinkingMetadata () {
54+ // LoggingEvent event = createBasicEvent(TEST_MESSAGE);
55+ // appender.doAppend(event);
26956
270- private void thenTheExceptionDataIsInTheMessage () throws Throwable {
271- LogAsserts .assertFieldValues (
272- getOutput (),
273- ImmutableMap .of (
274- "error.class" , "java.lang.Exception" ,
275- "error.stack" , Pattern .compile (".*NewRelicLogback13Tests\\ .shouldAppendErrorDataCorrectly.*" , Pattern .DOTALL ),
276- "error.message" , "some error message" )
277- );
278- }
57+ // AssertTrue(event.getMDCPropertyMap().containsKey("NewRelic:"));
27958
280- private void thenTheCustomArgsAreInTheMessage () throws Throwable {
281- LogAsserts .assertFieldValues (
282- getOutput (),
283- ImmutableMap .of ("customKey1" , "customValue1" , "customKey2" , "customValue2" )
284- );
285- }
28659
287- private void thenTheMDCFieldsAreInTheMessage (boolean shouldExist ) throws Throwable {
288- String result = getOutput ();
289- boolean contextKey1Exists = LogAsserts .assertFieldExistence (
290- "context.contextKey1" ,
291- result ,
292- shouldExist
293- );
294- assertEquals (shouldExist , contextKey1Exists , "MDC context.contextKey1 should exist: " + shouldExist );
29560
296- boolean contextKey2Exists = LogAsserts .assertFieldExistence (
297- "context.contextKey2" ,
298- result ,
299- shouldExist
300- );
301- assertEquals (shouldExist , contextKey2Exists , "MDC context.contextKey2 should exist: " + shouldExist );
61+ ILoggingEvent event = Mockito .mock (ILoggingEvent .class );
62+ Mockito .when (event .getMessage ()).thenReturn (TEST_MESSAGE );
63+ Mockito .when (event .getMDCPropertyMap ()).thenReturn (Map .of ("customKey" , "customValue" ));
30264
303- boolean contextKey3Exists = LogAsserts .assertFieldExistence (
304- "context.contextKey3" ,
305- result ,
306- shouldExist
307- );
308- assertEquals (shouldExist , contextKey3Exists , "MDC context.contextKey3 should exist: " + shouldExist );
309- }
65+ appender .doAppend (event );
31066
311- private String getOutput () throws IOException {
312- if (output == null ) {
313- output = bufferedReader .readLine () + "\n " ;
314- appender .stop ();
315- }
316- assertNotNull (output );
317- return output ;
67+ // Verify that the message was logged with the expected metadata
68+ // assert appender.getAppender("console").getEncoder().encode(event).contains("traceId\":\"abd123");
69+ // assert appender.getAppender("console").getEncoder().encode(event).contains("spanId\":\"xyz789");
31870 }
319-
320- }
71+ }
0 commit comments