Skip to content

Commit fa47ee7

Browse files
Eyal KorenEyal Koren
authored andcommitted
Reimplementing log correlation
1 parent 0035152 commit fa47ee7

File tree

21 files changed

+533
-46
lines changed

21 files changed

+533
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.log.shader;
20+
21+
import co.elastic.apm.agent.impl.transaction.Transaction;
22+
import co.elastic.apm.agent.sdk.state.CallDepth;
23+
24+
import javax.annotation.Nullable;
25+
26+
public abstract class AbstractLogCorrelationHelper {
27+
28+
private static final CallDepth callDepth = CallDepth.get(AbstractLogCorrelationHelper.class);
29+
30+
public static final String TRACE_ID_MDC_KEY = "trace.id";
31+
public static final String TRANSACTION_ID_MDC_KEY = "transaction.id";
32+
33+
/**
34+
* Adds the active transaction's ID and trace ID to the MDC in the outmost logging API call
35+
* @param activeTransaction the currently active transaction, or {@code null} if there is no such
36+
* @return returns {@code true} if the transaction IDs were added to the MDC
37+
*/
38+
public boolean beforeLoggingApiCall(@Nullable Transaction activeTransaction) {
39+
if (callDepth.isNestedCallAndIncrement() || activeTransaction == null) {
40+
return false;
41+
}
42+
addToMdc(TRACE_ID_MDC_KEY, activeTransaction.getTraceContext().getTraceId().toString());
43+
addToMdc(TRANSACTION_ID_MDC_KEY, activeTransaction.getTraceContext().getTransactionId().toString());
44+
return true;
45+
}
46+
47+
/**
48+
* Clears transaction and trace ID from the MDC if required
49+
* @param added should reflect the value returned from {@link #beforeLoggingApiCall(Transaction)} for the corresponding API call
50+
*/
51+
public void afterLoggingApi(boolean added) {
52+
if (callDepth.isNestedCallAndDecrement() && added) {
53+
removeFromMdc(TRACE_ID_MDC_KEY);
54+
removeFromMdc(TRANSACTION_ID_MDC_KEY);
55+
}
56+
}
57+
58+
protected abstract void addToMdc(String key, String value);
59+
60+
protected abstract void removeFromMdc(String key);
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.log.shader;
20+
21+
22+
import co.elastic.apm.agent.bci.TracerAwareInstrumentation;
23+
import co.elastic.apm.agent.bci.bytebuddy.CustomElementMatchers;
24+
import net.bytebuddy.description.method.MethodDescription;
25+
import net.bytebuddy.matcher.ElementMatcher;
26+
27+
import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
28+
import static net.bytebuddy.matcher.ElementMatchers.named;
29+
import static net.bytebuddy.matcher.ElementMatchers.not;
30+
31+
public abstract class AbstractLogCorrelationInstrumentation extends TracerAwareInstrumentation {
32+
33+
@Override
34+
public ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
35+
return not(isBootstrapClassLoader()).and(not(CustomElementMatchers.isAgentClassLoader()));
36+
}
37+
38+
@Override
39+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
40+
return named("trace").or(
41+
named("debug").or(
42+
named("info").or(
43+
named("warn").or(
44+
named("error").or(
45+
named("log")
46+
)
47+
)
48+
)
49+
)
50+
);
51+
}
52+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import java.util.ArrayList;
2525
import java.util.Collection;
2626

27-
public abstract class AbstractLogShadingInstrumentation extends TracerAwareInstrumentation {
27+
public abstract class AbstractLogIntegrationInstrumentation extends TracerAwareInstrumentation {
2828

2929
@Override
3030
public Collection<String> getInstrumentationGroupNames() {

apm-agent-plugins/apm-log-shader-plugin/apm-log-shader-plugin-common/src/test/java/co/elastic/apm/agent/log/shader/LogShadingInstrumentationTest.java

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import co.elastic.apm.agent.AbstractInstrumentationTest;
2222
import co.elastic.apm.agent.configuration.CoreConfiguration;
23+
import co.elastic.apm.agent.impl.transaction.Transaction;
2324
import co.elastic.apm.agent.logging.LogEcsReformatting;
2425
import co.elastic.apm.agent.logging.LoggingConfiguration;
2526
import co.elastic.apm.agent.logging.TestUtils;
@@ -29,8 +30,6 @@
2930
import org.junit.jupiter.api.BeforeEach;
3031
import org.junit.jupiter.api.Test;
3132

32-
import javax.annotation.Nonnull;
33-
import javax.annotation.Nullable;
3433
import java.io.IOException;
3534
import java.nio.file.Files;
3635
import java.nio.file.Path;
@@ -42,11 +41,12 @@
4241
import java.util.Map;
4342
import java.util.Objects;
4443
import java.util.TimeZone;
45-
import java.util.UUID;
4644
import java.util.concurrent.TimeUnit;
4745
import java.util.stream.Collectors;
4846
import java.util.stream.Stream;
4947

48+
import static co.elastic.apm.agent.log.shader.AbstractLogCorrelationHelper.TRACE_ID_MDC_KEY;
49+
import static co.elastic.apm.agent.log.shader.AbstractLogCorrelationHelper.TRANSACTION_ID_MDC_KEY;
5050
import static org.assertj.core.api.Assertions.assertThat;
5151
import static org.mockito.Mockito.doReturn;
5252
import static org.mockito.Mockito.when;
@@ -65,6 +65,7 @@ public abstract class LogShadingInstrumentationTest extends AbstractInstrumentat
6565
private final ObjectMapper objectMapper;
6666
private LoggingConfiguration loggingConfig;
6767
private String serviceName;
68+
private Transaction transaction;
6869

6970
public LogShadingInstrumentationTest() {
7071
logger = createLoggerFacade();
@@ -82,6 +83,8 @@ public void setup() throws Exception {
8283

8384
// IMPORTANT: keep this last, so that it doesn't interfere with Mockito settings above
8485
serviceName = Objects.requireNonNull(tracer.getMetaDataFuture().get(2000, TimeUnit.MILLISECONDS).getService().getName());
86+
87+
transaction = Objects.requireNonNull(tracer.startRootTransaction(null)).activate();
8588
}
8689

8790
private void setEcsReformattingConfig(LogEcsReformatting ecsReformattingConfig) {
@@ -96,6 +99,7 @@ private void initializeShadeDir(String dirName) throws IOException {
9699

97100
@AfterEach
98101
public void closeLogger() {
102+
transaction.deactivate().end();
99103
logger.close();
100104
}
101105

@@ -109,16 +113,10 @@ public void testSimpleLogShading() throws Exception {
109113
}
110114

111115
private void runSimpleScenario() throws Exception {
112-
String traceId = UUID.randomUUID().toString();
113-
logger.putTraceIdToMdc(traceId);
114-
try {
115-
logger.trace(TRACE_MESSAGE);
116-
logger.debug(DEBUG_MESSAGE);
117-
logger.warn(WARN_MESSAGE);
118-
logger.error(ERROR_MESSAGE);
119-
} finally {
120-
logger.removeTraceIdFromMdc();
121-
}
116+
logger.trace(TRACE_MESSAGE);
117+
logger.debug(DEBUG_MESSAGE);
118+
logger.warn(WARN_MESSAGE);
119+
logger.error(ERROR_MESSAGE);
122120

123121
ArrayList<String[]> rawLogLines = readRawLogLines();
124122
assertThat(rawLogLines).hasSize(4);
@@ -127,7 +125,7 @@ private void runSimpleScenario() throws Exception {
127125
assertThat(ecsLogLines).hasSize(4);
128126

129127
for (int i = 0; i < 4; i++) {
130-
verifyEcsFormat(rawLogLines.get(i), ecsLogLines.get(i), traceId);
128+
verifyEcsFormat(rawLogLines.get(i), ecsLogLines.get(i));
131129
}
132130
}
133131

@@ -146,7 +144,7 @@ public void testMarkers() throws Exception {
146144
assertThat(ecsLogLines).hasSize(1);
147145
JsonNode ecsLogLine = ecsLogLines.get(0);
148146

149-
verifyEcsFormat(rawLogLine, ecsLogLine, null);
147+
verifyEcsFormat(rawLogLine, ecsLogLine);
150148

151149
JsonNode tagsJson = ecsLogLine.get("tags");
152150
assertThat(tagsJson.isArray()).isTrue();
@@ -181,8 +179,8 @@ public void testLazyShadeFileCreation() throws Exception {
181179

182180
ArrayList<JsonNode> ecsLogLines = readShadeLogFile();
183181
assertThat(ecsLogLines).hasSize(2);
184-
verifyEcsFormat(rawLogLines.get(2), ecsLogLines.get(0), null);
185-
verifyEcsFormat(rawLogLines.get(3), ecsLogLines.get(1), null);
182+
verifyEcsFormat(rawLogLines.get(2), ecsLogLines.get(0));
183+
verifyEcsFormat(rawLogLines.get(3), ecsLogLines.get(1));
186184
}
187185

188186
@Test
@@ -265,9 +263,9 @@ public void testDynamicConfiguration() throws Exception {
265263
assertThat(debugLogLine.get("log.level").textValue()).isEqualTo("DEBUG");
266264

267265
// WARN messages should match content but not format
268-
verifyEcsFormat(originalLogLines.get(2).split("\\s+"), ecsLogLines.get(0), null);
266+
verifyEcsFormat(originalLogLines.get(2).split("\\s+"), ecsLogLines.get(0));
269267
assertThat(ecsLogLines.get(0).get("log.level").textValue()).isEqualTo("WARN");
270-
verifyEcsFormat(originalLogLines.get(5).split("\\s+"), ecsLogLines.get(2), null);
268+
verifyEcsFormat(originalLogLines.get(5).split("\\s+"), ecsLogLines.get(2));
271269
assertThat(ecsLogLines.get(2).get("log.level").textValue()).isEqualTo("WARN");
272270

273271
// ERROR messages should be only in shade file in ECS format
@@ -288,14 +286,14 @@ private void verifyEcsLogLine(JsonNode ecsLogLineTree) {
288286
assertThat(ecsLogLineTree.get("event.dataset").textValue()).isEqualTo(serviceName + ".FILE");
289287
assertThat(ecsLogLineTree.get("service.version").textValue()).isEqualTo("v42");
290288
assertThat(ecsLogLineTree.get("some.field").textValue()).isEqualTo("some-value");
289+
assertThat(ecsLogLineTree.get(TRACE_ID_MDC_KEY).textValue()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
290+
assertThat(ecsLogLineTree.get(TRANSACTION_ID_MDC_KEY).textValue()).isEqualTo(transaction.getTraceContext().getTransactionId().toString());
291291
}
292292

293-
@Nonnull
294293
private ArrayList<JsonNode> readShadeLogFile() throws IOException {
295294
return TestUtils.readJsonFile(getShadeLogFilePath());
296295
}
297296

298-
@Nonnull
299297
private ArrayList<String[]> readRawLogLines() throws IOException {
300298
ArrayList<String[]> rawLogLines;
301299
try (Stream<String> stream = Files.lines(getOriginalLogFilePath())) {
@@ -304,17 +302,15 @@ private ArrayList<String[]> readRawLogLines() throws IOException {
304302
return rawLogLines;
305303
}
306304

307-
@Nonnull
308305
private Path getOriginalLogFilePath() {
309306
return Paths.get(logger.getLogFilePath());
310307
}
311308

312-
@Nonnull
313309
protected String getShadeLogFilePath() {
314310
return Utils.computeShadeLogFilePath(logger.getLogFilePath(), loggingConfig.getLogEcsFormattingDestinationDir());
315311
}
316312

317-
private void verifyEcsFormat(String[] splitRawLogLine, JsonNode ecsLogLineTree, @Nullable String traceId) throws Exception {
313+
private void verifyEcsFormat(String[] splitRawLogLine, JsonNode ecsLogLineTree) throws Exception {
318314
SimpleDateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
319315
Date rawTimestamp = timestampFormat.parse(splitRawLogLine[0]);
320316
timestampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -329,13 +325,8 @@ private void verifyEcsFormat(String[] splitRawLogLine, JsonNode ecsLogLineTree,
329325
assertThat(ecsLogLineTree.get("event.dataset").textValue()).isEqualTo(serviceName + ".FILE");
330326
assertThat(ecsLogLineTree.get("service.version").textValue()).isEqualTo("v42");
331327
assertThat(ecsLogLineTree.get("some.field").textValue()).isEqualTo("some-value");
332-
JsonNode jsonTraceId = ecsLogLineTree.get("trace.id");
333-
if (traceId != null) {
334-
assertThat(jsonTraceId).isNotNull();
335-
assertThat(jsonTraceId.asText()).isEqualTo(traceId);
336-
} else {
337-
assertThat(jsonTraceId).isNull();
338-
}
328+
assertThat(ecsLogLineTree.get(TRACE_ID_MDC_KEY).textValue()).isEqualTo(transaction.getTraceContext().getTraceId().toString());
329+
assertThat(ecsLogLineTree.get(TRANSACTION_ID_MDC_KEY).textValue()).isEqualTo(transaction.getTraceContext().getTransactionId().toString());
339330
}
340331

341332
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.log4j1;
20+
21+
import co.elastic.apm.agent.log.shader.AbstractLogCorrelationHelper;
22+
import org.apache.log4j.MDC;
23+
24+
public class Log4j1LogCorrelationHelper extends AbstractLogCorrelationHelper {
25+
@Override
26+
protected void addToMdc(String key, String value) {
27+
MDC.put(key, value);
28+
}
29+
30+
@Override
31+
protected void removeFromMdc(String key) {
32+
MDC.remove(key);
33+
}
34+
}

apm-agent-plugins/apm-log-shader-plugin/apm-log4j1-plugin/src/main/java/co/elastic/apm/agent/log4j1/Log4j1LogShadingInstrumentation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
package co.elastic.apm.agent.log4j1;
2020

21-
import co.elastic.apm.agent.log.shader.AbstractLogShadingInstrumentation;
21+
import co.elastic.apm.agent.log.shader.AbstractLogIntegrationInstrumentation;
2222
import net.bytebuddy.description.method.MethodDescription;
2323
import net.bytebuddy.description.type.TypeDescription;
2424
import net.bytebuddy.matcher.ElementMatcher;
@@ -32,7 +32,7 @@
3232
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
3333
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
3434

35-
public abstract class Log4j1LogShadingInstrumentation extends AbstractLogShadingInstrumentation {
35+
public abstract class Log4j1LogShadingInstrumentation extends AbstractLogIntegrationInstrumentation {
3636

3737
@Override
3838
public Collection<String> getInstrumentationGroupNames() {

0 commit comments

Comments
 (0)