Skip to content

Commit ea438e5

Browse files
authored
Dynatrace registry: Truncate log output (#3148)
* Dynatrace registry: Truncate log output * Only log stack trace in debug * Add indicator for truncation with StringUtils * Use truncation with indicator * Use WarnThenDebugLogger * Log 'send' exception message always and log once * Use entire length before abbreviating
1 parent d8bf36c commit ea438e5

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

implementations/micrometer-registry-dynatrace/src/main/java/io/micrometer/dynatrace/v2/DynatraceExporterV2.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616
package io.micrometer.dynatrace.v2;
1717

1818
import com.dynatrace.metric.util.*;
19+
1920
import io.micrometer.core.instrument.*;
2021
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
2122
import io.micrometer.core.instrument.distribution.ValueAtPercentile;
2223
import io.micrometer.core.instrument.util.AbstractPartition;
24+
import io.micrometer.core.instrument.util.StringUtils;
2325
import io.micrometer.core.ipc.http.HttpSender;
2426
import io.micrometer.core.util.internal.logging.InternalLogger;
2527
import io.micrometer.core.util.internal.logging.InternalLoggerFactory;
28+
import io.micrometer.core.util.internal.logging.WarnThenDebugLogger;
2629
import io.micrometer.dynatrace.AbstractDynatraceExporter;
2730
import io.micrometer.dynatrace.DynatraceConfig;
2831
import io.micrometer.dynatrace.types.DynatraceSummarySnapshot;
@@ -55,8 +58,11 @@ public final class DynatraceExporterV2 extends AbstractDynatraceExporter {
5558
private static final Pattern EXTRACT_LINES_OK = Pattern.compile("\"linesOk\":\\s?(\\d+)");
5659
private static final Pattern EXTRACT_LINES_INVALID = Pattern.compile("\"linesInvalid\":\\s?(\\d+)");
5760
private static final Pattern IS_NULL_ERROR_RESPONSE = Pattern.compile("\"error\":\\s?null");
61+
private static final int LOG_RESPONSE_BODY_TRUNCATION_LIMIT = 1_000;
62+
private static final String LOG_RESPONSE_BODY_TRUNCATION_INDICATOR = " (truncated)";
5863

5964
private final InternalLogger logger = InternalLoggerFactory.getInstance(DynatraceExporterV2.class);
65+
private static final WarnThenDebugLogger warnThenDebugLoggerSendStack = new WarnThenDebugLogger(DynatraceExporterV2.class);
6066
private static final Map<String, String> staticDimensions = Collections.singletonMap("dt.metrics.source", "micrometer");
6167

6268
private final MetricBuilderFactory metricBuilderFactory;
@@ -310,9 +316,12 @@ private void send(List<String> metricLines) {
310316
.withPlainText(body)
311317
.send()
312318
.onSuccess(response -> handleSuccess(metricLines.size(), response))
313-
.onError(response -> logger.error("Failed metric ingestion: Error Code={}, Response Body={}", response.code(), response.body()));
319+
.onError(response -> logger.error("Failed metric ingestion: Error Code={}, Response Body={}",
320+
response.code(),
321+
StringUtils.truncate(response.body(), LOG_RESPONSE_BODY_TRUNCATION_LIMIT, LOG_RESPONSE_BODY_TRUNCATION_INDICATOR)));
314322
} catch (Throwable throwable) {
315-
logger.error("Failed metric ingestion: " + throwable.getMessage(), throwable);
323+
logger.warn("Failed metric ingestion: " + throwable);
324+
warnThenDebugLoggerSendStack.log("Stack trace for previous 'Failed metric ingestion' warning log: ", throwable);
316325
}
317326
}
318327

@@ -325,16 +334,18 @@ private void handleSuccess(int totalSent, HttpSender.Response response) {
325334
logger.debug("Sent {} metric lines, linesOk: {}, linesInvalid: {}.",
326335
totalSent, linesOkMatchResult.group(1), linesInvalidMatchResult.group(1));
327336
} else {
328-
logger.warn("Unable to parse response: {}", response.body());
337+
logger.warn("Unable to parse response: {}",
338+
StringUtils.truncate(response.body(), LOG_RESPONSE_BODY_TRUNCATION_LIMIT, LOG_RESPONSE_BODY_TRUNCATION_INDICATOR));
329339
}
330340
} else {
331-
logger.warn("Unable to parse response: {}", response.body());
341+
logger.warn("Unable to parse response: {}",
342+
StringUtils.truncate(response.body(), LOG_RESPONSE_BODY_TRUNCATION_LIMIT, LOG_RESPONSE_BODY_TRUNCATION_INDICATOR));
332343
}
333344
} else {
334345
// common pitfall if URI is supplied in V1 format (without endpoint path)
335346
logger.error("Expected status code 202, got {}.\nResponse Body={}\nDid you specify the ingest path (e.g.: /api/v2/metrics/ingest)?",
336347
response.code(),
337-
response.body()
348+
StringUtils.truncate(response.body(), LOG_RESPONSE_BODY_TRUNCATION_LIMIT, LOG_RESPONSE_BODY_TRUNCATION_INDICATOR)
338349
);
339350
}
340351
}

micrometer-core/src/main/java/io/micrometer/core/instrument/util/StringUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,25 @@ public static String truncate(String string, int maxLength) {
8989
return string;
9090
}
9191

92+
/**
93+
* Truncate the String to the max length and append string to indicate if truncation was applied
94+
*
95+
* @param string String to truncate
96+
* @param maxLength max length, which includes the length required for {@code truncationIndicator}
97+
* @param truncationIndicator A string that is appended if {@code string} is truncated
98+
* @return truncated String
99+
*/
100+
public static String truncate(String string, int maxLength, String truncationIndicator) {
101+
if (truncationIndicator.length() >= maxLength) {
102+
throw new IllegalArgumentException("maxLength must be greater than length of truncationIndicator");
103+
}
104+
if (string.length() > maxLength) {
105+
final int remainingLength = maxLength - truncationIndicator.length();
106+
return string.substring(0, remainingLength) + truncationIndicator;
107+
}
108+
return string;
109+
}
110+
92111
private StringUtils() {
93112
}
94113

micrometer-core/src/test/java/io/micrometer/core/instrument/util/StringUtilsTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.junit.jupiter.api.Test;
1919

2020
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.junit.jupiter.api.Assertions.assertThrows;
2122

2223
/**
2324
* Tests for {@link StringUtils}.
@@ -36,6 +37,36 @@ void truncateWhenLessThanMaxLengthShouldReturnItself() {
3637
assertThat(StringUtils.truncate("123", 5)).isEqualTo("123");
3738
}
3839

40+
@Test
41+
void truncateWithIndicatorWhenGreaterThanMaxLengthShouldTruncate() {
42+
assertThat(StringUtils.truncate("1234567890", 7, "...")).isEqualTo("1234...");
43+
}
44+
45+
@Test
46+
void truncateWithEmptyIndicatorWhenGreaterThanMaxLengthShouldTruncate() {
47+
assertThat(StringUtils.truncate("1234567890", 7, "")).isEqualTo("1234567");
48+
}
49+
50+
@Test
51+
void truncateWithIndicatorWhenSameAsMaxLengthShouldReturnItself() {
52+
assertThat(StringUtils.truncate("1234567", 7, "...")).isEqualTo("1234567");
53+
}
54+
55+
@Test
56+
void truncateWithIndicatorWhenLessThanMaxLengthShouldReturnItself() {
57+
assertThat(StringUtils.truncate("123", 7, "...")).isEqualTo("123");
58+
}
59+
60+
@Test
61+
void truncateWithIndicatorThrowsOnInvalidLength1() {
62+
assertThrows(IllegalArgumentException.class, () -> StringUtils.truncate("12345", 7, "[abbreviated]"));
63+
}
64+
65+
@Test
66+
void truncateWithIndicatorThrowsOnInvalidLength2() {
67+
assertThrows(IllegalArgumentException.class, () -> StringUtils.truncate("1234567890", 7, "[abbreviated]"));
68+
}
69+
3970
@Test
4071
void isNotEmptyWhenNullShouldBeFalse() {
4172
assertThat(StringUtils.isNotEmpty(null)).isFalse();

0 commit comments

Comments
 (0)