Skip to content

Commit e36d590

Browse files
committed
Fix log sampling overrides by running preview processors before filtering
1 parent b344e5d commit e36d590

File tree

5 files changed

+233
-8
lines changed

5 files changed

+233
-8
lines changed

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/AzureMonitorLogFilteringProcessor.java

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,52 @@
55

66
import com.azure.monitor.opentelemetry.autoconfigure.implementation.AiSemanticAttributes;
77
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration;
8+
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.ProcessorConfig;
9+
import com.microsoft.applicationinsights.agent.internal.configuration.Configuration.ProcessorType;
810
import com.microsoft.applicationinsights.agent.internal.sampling.AiFixedPercentageSampler;
911
import com.microsoft.applicationinsights.agent.internal.sampling.SamplingOverrides;
12+
import com.microsoft.applicationinsights.agent.internal.processors.AgentProcessor;
13+
import com.microsoft.applicationinsights.agent.internal.processors.AttributeProcessor;
14+
import com.microsoft.applicationinsights.agent.internal.processors.LogProcessor;
1015
import io.opentelemetry.api.trace.Span;
1116
import io.opentelemetry.api.trace.SpanContext;
1217
import io.opentelemetry.context.Context;
1318
import io.opentelemetry.instrumentation.api.instrumenter.LocalRootSpan;
1419
import io.opentelemetry.sdk.common.CompletableResultCode;
1520
import io.opentelemetry.sdk.logs.LogRecordProcessor;
1621
import io.opentelemetry.sdk.logs.ReadWriteLogRecord;
22+
import io.opentelemetry.sdk.logs.data.LogRecordData;
1723
import io.opentelemetry.sdk.trace.ReadableSpan;
1824
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
1925
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
2026
import io.opentelemetry.semconv.ExceptionAttributes;
27+
import java.util.ArrayList;
28+
import java.util.Collections;
2129
import java.util.List;
2230

2331
public class AzureMonitorLogFilteringProcessor implements LogRecordProcessor {
2432

2533
private final SamplingOverrides logSamplingOverrides;
2634
private final SamplingOverrides exceptionSamplingOverrides;
2735
private final LogRecordProcessor batchLogRecordProcessor;
36+
private final List<ProcessorApplier> previewProcessorAppliers;
37+
private final boolean hasPreviewProcessors;
2838

2939
private volatile int severityThreshold;
3040

3141
public AzureMonitorLogFilteringProcessor(
3242
List<Configuration.SamplingOverride> logSamplingOverrides,
3343
List<Configuration.SamplingOverride> exceptionSamplingOverrides,
3444
LogRecordProcessor batchLogRecordProcessor,
35-
int severityThreshold) {
45+
int severityThreshold,
46+
List<ProcessorConfig> processorConfigs) {
3647

3748
this.severityThreshold = severityThreshold;
3849
this.logSamplingOverrides = new SamplingOverrides(logSamplingOverrides);
3950
this.exceptionSamplingOverrides = new SamplingOverrides(exceptionSamplingOverrides);
4051
this.batchLogRecordProcessor = batchLogRecordProcessor;
41-
this.severityThreshold = severityThreshold;
52+
this.previewProcessorAppliers = buildProcessorAppliers(processorConfigs);
53+
this.hasPreviewProcessors = !previewProcessorAppliers.isEmpty();
4254
}
4355

4456
public void setSeverityThreshold(int severityThreshold) {
@@ -54,6 +66,9 @@ public void onEmit(Context context, ReadWriteLogRecord logRecord) {
5466
return;
5567
}
5668

69+
LogRecordData processedLogRecord =
70+
hasPreviewProcessors ? applyPreviewProcessors(logRecord) : logRecord.toLogRecordData();
71+
5772
Double parentSpanSampleRate = null;
5873
Span currentSpan = Span.fromContext(context);
5974
if (currentSpan instanceof ReadableSpan) {
@@ -64,14 +79,17 @@ public void onEmit(Context context, ReadWriteLogRecord logRecord) {
6479
// deal with sampling synchronously so that we only call setAttributeExceptionLogged()
6580
// when we know we are emitting the exception (span sampling happens synchronously as well)
6681

67-
String stack = logRecord.getAttribute(ExceptionAttributes.EXCEPTION_STACKTRACE);
82+
String stack =
83+
processedLogRecord
84+
.getAttributes()
85+
.get(ExceptionAttributes.EXCEPTION_STACKTRACE);
6886

6987
SamplingOverrides samplingOverrides =
7088
stack != null ? exceptionSamplingOverrides : logSamplingOverrides;
7189

7290
SpanContext spanContext = logRecord.getSpanContext();
7391

74-
AiFixedPercentageSampler sampler = samplingOverrides.getOverride(logRecord.getAttributes());
92+
AiFixedPercentageSampler sampler = samplingOverrides.getOverride(processedLogRecord.getAttributes());
7593

7694
boolean hasSamplingOverride = sampler != null;
7795

@@ -98,7 +116,7 @@ public void onEmit(Context context, ReadWriteLogRecord logRecord) {
98116
logRecord.setAttribute(AiSemanticAttributes.SAMPLE_RATE, sampleRate);
99117
}
100118

101-
setAttributeExceptionLogged(LocalRootSpan.fromContext(context), logRecord);
119+
setAttributeExceptionLogged(LocalRootSpan.fromContext(context), stack);
102120

103121
batchLogRecordProcessor.onEmit(context, logRecord);
104122
}
@@ -118,10 +136,94 @@ public void close() {
118136
batchLogRecordProcessor.close();
119137
}
120138

121-
private static void setAttributeExceptionLogged(Span span, ReadWriteLogRecord logRecord) {
122-
String stacktrace = logRecord.getAttribute(ExceptionAttributes.EXCEPTION_STACKTRACE);
139+
private LogRecordData applyPreviewProcessors(ReadWriteLogRecord logRecord) {
140+
LogRecordData processed = logRecord.toLogRecordData();
141+
for (ProcessorApplier processorApplier : previewProcessorAppliers) {
142+
processed = processorApplier.apply(processed);
143+
}
144+
return processed;
145+
}
146+
147+
private static List<ProcessorApplier> buildProcessorAppliers(
148+
List<ProcessorConfig> processorConfigs) {
149+
if (processorConfigs.isEmpty()) {
150+
return Collections.emptyList();
151+
}
152+
List<ProcessorApplier> appliers = new ArrayList<>(processorConfigs.size());
153+
for (ProcessorConfig processorConfig : processorConfigs) {
154+
appliers.add(ProcessorApplier.fromConfig(processorConfig));
155+
}
156+
return Collections.unmodifiableList(appliers);
157+
}
158+
159+
private static void setAttributeExceptionLogged(Span span, String stacktrace) {
123160
if (stacktrace != null) {
124161
span.setAttribute(AiSemanticAttributes.LOGGED_EXCEPTION, stacktrace);
125162
}
126163
}
164+
165+
private interface ProcessorApplier {
166+
LogRecordData apply(LogRecordData logRecordData);
167+
168+
static ProcessorApplier fromConfig(ProcessorConfig config) {
169+
config.validate();
170+
if (config.type == ProcessorType.ATTRIBUTE) {
171+
AttributeProcessor attributeProcessor = AttributeProcessor.create(config, true);
172+
return new AttributeProcessorApplier(attributeProcessor);
173+
}
174+
if (config.type == ProcessorType.LOG) {
175+
LogProcessor logProcessor = LogProcessor.create(config);
176+
return new LogBodyProcessorApplier(logProcessor);
177+
}
178+
throw new IllegalStateException("Unsupported processor type: " + config.type);
179+
}
180+
}
181+
182+
private static final class AttributeProcessorApplier implements ProcessorApplier {
183+
private final AttributeProcessor attributeProcessor;
184+
185+
private AttributeProcessorApplier(AttributeProcessor attributeProcessor) {
186+
this.attributeProcessor = attributeProcessor;
187+
}
188+
189+
@Override
190+
public LogRecordData apply(LogRecordData logRecordData) {
191+
AgentProcessor.IncludeExclude include = attributeProcessor.getInclude();
192+
if (include != null
193+
&& !include.isMatch(logRecordData.getAttributes(), logRecordData.getBody().asString())) {
194+
return logRecordData;
195+
}
196+
AgentProcessor.IncludeExclude exclude = attributeProcessor.getExclude();
197+
if (exclude != null
198+
&& exclude.isMatch(logRecordData.getAttributes(), logRecordData.getBody().asString())) {
199+
return logRecordData;
200+
}
201+
return attributeProcessor.processActions(logRecordData);
202+
}
203+
}
204+
205+
private static final class LogBodyProcessorApplier implements ProcessorApplier {
206+
private final LogProcessor logProcessor;
207+
208+
private LogBodyProcessorApplier(LogProcessor logProcessor) {
209+
this.logProcessor = logProcessor;
210+
}
211+
212+
@Override
213+
public LogRecordData apply(LogRecordData logRecordData) {
214+
AgentProcessor.IncludeExclude include = logProcessor.getInclude();
215+
if (include != null
216+
&& !include.isMatch(logRecordData.getAttributes(), logRecordData.getBody().asString())) {
217+
return logRecordData;
218+
}
219+
AgentProcessor.IncludeExclude exclude = logProcessor.getExclude();
220+
if (exclude != null
221+
&& exclude.isMatch(logRecordData.getAttributes(), logRecordData.getBody().asString())) {
222+
return logRecordData;
223+
}
224+
225+
LogRecordData updatedLog = logProcessor.processFromAttributes(logRecordData);
226+
return logProcessor.processToAttributes(updatedLog);
227+
}
228+
}
127229
}

agent/agent-tooling/src/main/java/com/microsoft/applicationinsights/agent/internal/init/SecondEntryPoint.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,8 @@ private static AzureMonitorLogFilteringProcessor createLogFilteringProcessor(
345345
logSamplingOverrides,
346346
exceptionSamplingOverrides,
347347
logRecordProcessor,
348-
configuration.instrumentation.logging.getSeverityThreshold());
348+
configuration.instrumentation.logging.getSeverityThreshold(),
349+
getLogProcessorConfigs(configuration));
349350
}
350351

351352
private static SpanExporter buildTraceExporter(

smoke-tests/apps/TelemetryProcessors/src/main/java/com/microsoft/applicationinsights/smoketestapp/TestController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public String deleteExistingLogAttribute() {
4646
return "delete existing log attribute";
4747
}
4848

49+
@GetMapping("/log-filtered-by-attribute")
50+
public String logFilteredByAttribute() {
51+
logger.info("Noisy log that should not be sent to AppInsights");
52+
return "log filtered by attribute";
53+
}
54+
4955
@GetMapping("/test-non-string-strict-span-attributes")
5056
public String testNonStringStrictSpanAttributes() {
5157
Span.current()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.microsoft.applicationinsights.smoketest;
5+
6+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_11;
7+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_11_OPENJ9;
8+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_17;
9+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_17_OPENJ9;
10+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_21;
11+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_21_OPENJ9;
12+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_25;
13+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_25_OPENJ9;
14+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8;
15+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.TOMCAT_8_JAVA_8_OPENJ9;
16+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.WILDFLY_13_JAVA_8;
17+
import static com.microsoft.applicationinsights.smoketest.EnvironmentValue.WILDFLY_13_JAVA_8_OPENJ9;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.RegisterExtension;
22+
23+
@UseAgent("applicationinsights-log-filtered-by-attribute.json")
24+
abstract class TelemetryProcessorLogFilterSamplingOverridesTest {
25+
26+
@RegisterExtension static final SmokeTestExtension testing = SmokeTestExtension.create();
27+
28+
@Test
29+
@TargetUri("/log-filtered-by-attribute")
30+
void logFilteredBySamplingOverrides() throws Exception {
31+
Telemetry telemetry = testing.getTelemetry(0);
32+
assertThat(telemetry.rd.getName())
33+
.isEqualTo("GET /TelemetryProcessors/log-filtered-by-attribute");
34+
35+
assertThat(testing.mockedIngestion.getCountForType("MessageData")).isZero();
36+
}
37+
38+
@Environment(TOMCAT_8_JAVA_8)
39+
static class Tomcat8Java8Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
40+
41+
@Environment(TOMCAT_8_JAVA_8_OPENJ9)
42+
static class Tomcat8Java8OpenJ9Test
43+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
44+
45+
@Environment(TOMCAT_8_JAVA_11)
46+
static class Tomcat8Java11Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
47+
48+
@Environment(TOMCAT_8_JAVA_11_OPENJ9)
49+
static class Tomcat8Java11OpenJ9Test
50+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
51+
52+
@Environment(TOMCAT_8_JAVA_17)
53+
static class Tomcat8Java17Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
54+
55+
@Environment(TOMCAT_8_JAVA_17_OPENJ9)
56+
static class Tomcat8Java17OpenJ9Test
57+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
58+
59+
@Environment(TOMCAT_8_JAVA_21)
60+
static class Tomcat8Java21Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
61+
62+
@Environment(TOMCAT_8_JAVA_21_OPENJ9)
63+
static class Tomcat8Java21OpenJ9Test
64+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
65+
66+
@Environment(TOMCAT_8_JAVA_25)
67+
static class Tomcat8Java25Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
68+
69+
@Environment(TOMCAT_8_JAVA_25_OPENJ9)
70+
static class Tomcat8Java25OpenJ9Test
71+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
72+
73+
@Environment(WILDFLY_13_JAVA_8)
74+
static class Wildfly13Java8Test extends TelemetryProcessorLogFilterSamplingOverridesTest {}
75+
76+
@Environment(WILDFLY_13_JAVA_8_OPENJ9)
77+
static class Wildfly13Java8OpenJ9Test
78+
extends TelemetryProcessorLogFilterSamplingOverridesTest {}
79+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"role": {
3+
"name": "testrolename",
4+
"instance": "testroleinstance"
5+
},
6+
"sampling": {
7+
"percentage": 100,
8+
"overrides": [
9+
{
10+
"telemetryType": "trace",
11+
"attributes": [
12+
{
13+
"key": "TODELETE",
14+
"value": ".*",
15+
"matchType": "regexp"
16+
}
17+
],
18+
"percentage": 0
19+
}
20+
]
21+
},
22+
"preview": {
23+
"processors": [
24+
{
25+
"type": "log",
26+
"body": {
27+
"toAttributes": {
28+
"rules": [
29+
"^Noisy log (?<TODELETE>.*)"
30+
]
31+
}
32+
},
33+
"id": "/log/extractToDelete"
34+
}
35+
]
36+
}
37+
}

0 commit comments

Comments
 (0)