Skip to content

Commit 8ee4a5d

Browse files
authored
Add support for new telemetry crash data format (#7675)
1 parent 845dfba commit 8ee4a5d

21 files changed

+12354
-142
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.datadog.crashtracking;
2+
3+
import com.datadog.crashtracking.dto.CrashLog;
4+
import com.datadog.crashtracking.parsers.HotspotCrashLogParser;
5+
6+
public final class CrashLogParser {
7+
public static CrashLog fromHotspotCrashLog(String logText) {
8+
return new HotspotCrashLogParser().parse(logText);
9+
}
10+
}

dd-java-agent/agent-crashtracking/src/main/java/com/datadog/crashtracking/CrashUploader.java

+62-50
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static datadog.trace.api.config.CrashTrackingConfig.CRASH_TRACKING_UPLOAD_TIMEOUT;
88
import static datadog.trace.api.config.CrashTrackingConfig.CRASH_TRACKING_UPLOAD_TIMEOUT_DEFAULT;
99

10+
import com.datadog.crashtracking.dto.CrashLog;
1011
import com.squareup.moshi.JsonWriter;
1112
import datadog.common.container.ContainerInfo;
1213
import datadog.common.version.VersionInfo;
@@ -16,13 +17,12 @@
1617
import datadog.trace.bootstrap.config.provider.ConfigProvider;
1718
import datadog.trace.util.PidHelper;
1819
import de.thetaphi.forbiddenapis.SuppressForbidden;
19-
import java.io.IOException;
20-
import java.io.InputStream;
21-
import java.io.InputStreamReader;
22-
import java.io.PrintStream;
20+
import java.io.*;
21+
import java.nio.charset.Charset;
2322
import java.nio.charset.StandardCharsets;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
2425
import java.time.Instant;
25-
import java.util.ArrayList;
2626
import java.util.Arrays;
2727
import java.util.HashMap;
2828
import java.util.List;
@@ -54,7 +54,7 @@ public final class CrashUploader {
5454
static final String JAVA_TRACING_LIBRARY = "dd-trace-java";
5555
static final String HEADER_DD_EVP_ORIGIN_VERSION = "DD-EVP-ORIGIN-VERSION";
5656
static final String HEADER_DD_TELEMETRY_API_VERSION = "DD-Telemetry-API-Version";
57-
static final String TELEMETRY_API_VERSION = "v1";
57+
static final String TELEMETRY_API_VERSION = "v2";
5858
static final String HEADER_DD_TELEMETRY_REQUEST_TYPE = "DD-Telemetry-Request-Type";
5959
static final String TELEMETRY_REQUEST_TYPE = "logs";
6060

@@ -114,43 +114,44 @@ private String tagsToString(final Map<String, String> tags) {
114114
.collect(Collectors.joining(","));
115115
}
116116

117-
public void upload(@Nonnull List<InputStream> files) throws IOException {
118-
List<String> filesContent = new ArrayList<>(files.size());
119-
for (InputStream file : files) {
120-
filesContent.add(readContent(file));
117+
public void upload(@Nonnull List<Path> files) throws IOException {
118+
for (Path file : files) {
119+
uploadToLogs(file);
120+
uploadToTelemetry(file);
121121
}
122-
uploadToLogs(filesContent);
123-
uploadToTelemetry(filesContent);
124122
}
125123

126-
void uploadToLogs(@Nonnull List<String> filesContent) throws IOException {
127-
uploadToLogs(filesContent, System.out);
124+
boolean uploadToLogs(@Nonnull Path file) {
125+
try {
126+
uploadToLogs(new String(Files.readAllBytes(file), StandardCharsets.UTF_8), System.out);
127+
} catch (IOException e) {
128+
log.error("Failed to upload crash file: {}", file, e);
129+
return false;
130+
}
131+
return true;
128132
}
129133

130-
void uploadToLogs(@Nonnull List<String> filesContent, @Nonnull PrintStream out)
131-
throws IOException {
134+
void uploadToLogs(@Nonnull String message, @Nonnull PrintStream out) throws IOException {
132135
// print on the output, and the application/container/host log will pick it up
133-
for (String message : filesContent) {
134-
try (Buffer buf = new Buffer()) {
135-
try (JsonWriter writer = JsonWriter.of(buf)) {
136-
writer.beginObject();
137-
writer.name("ddsource").value("crashtracker");
138-
writer.name("ddtags").value(tags);
139-
writer.name("hostname").value(config.getHostName());
140-
writer.name("service").value(config.getServiceName());
141-
writer.name("message").value(message);
142-
writer.name("level").value("ERROR");
143-
writer.name("error");
144-
writer.beginObject();
145-
writer.name("kind").value(extractErrorKind(message));
146-
writer.name("message").value(extractErrorMessage(message));
147-
writer.name("stack").value(extractErrorStackTrace(message, false));
148-
writer.endObject();
149-
writer.endObject();
150-
}
151-
152-
out.println(buf.readByteString().utf8());
136+
try (Buffer buf = new Buffer()) {
137+
try (JsonWriter writer = JsonWriter.of(buf)) {
138+
writer.beginObject();
139+
writer.name("ddsource").value("crashtracker");
140+
writer.name("ddtags").value(tags);
141+
writer.name("hostname").value(config.getHostName());
142+
writer.name("service").value(config.getServiceName());
143+
writer.name("message").value(message);
144+
writer.name("level").value("ERROR");
145+
writer.name("error");
146+
writer.beginObject();
147+
writer.name("kind").value(extractErrorKind(message));
148+
writer.name("message").value(extractErrorMessage(message));
149+
writer.name("stack").value(extractErrorStackTrace(message, false));
150+
writer.endObject();
151+
writer.endObject();
153152
}
153+
154+
out.println(buf.readByteString().utf8());
154155
}
155156
}
156157

@@ -235,16 +236,26 @@ private String extractErrorStackTrace(String fileContent) {
235236
return extractErrorStackTrace(fileContent, true);
236237
}
237238

238-
void uploadToTelemetry(@Nonnull List<String> filesContent) throws IOException {
239-
handleCall(makeTelemetryRequest(filesContent));
239+
boolean uploadToTelemetry(@Nonnull Path file) {
240+
try {
241+
String content = new String(Files.readAllBytes(file), Charset.defaultCharset());
242+
handleCall(makeTelemetryRequest(content));
243+
} catch (IOException e) {
244+
log.error("Failed to upload crash file: {}", file, e);
245+
return false;
246+
}
247+
return true;
240248
}
241249

242-
private Call makeTelemetryRequest(@Nonnull List<String> filesContent) throws IOException {
243-
final RequestBody requestBody = makeTelemetryRequestBody(filesContent);
250+
private Call makeTelemetryRequest(@Nonnull String content) throws IOException {
251+
final RequestBody requestBody = makeTelemetryRequestBody(content);
244252

245253
final Map<String, String> headers = new HashMap<>();
246254
// Set chunked transfer
247-
headers.put("Content-Type", requestBody.contentType().toString());
255+
MediaType contentType = requestBody.contentType();
256+
if (contentType != null) {
257+
headers.put("Content-Type", contentType.toString());
258+
}
248259
headers.put("Content-Length", Long.toString(requestBody.contentLength()));
249260
headers.put("Transfer-Encoding", "chunked");
250261
headers.put(HEADER_DD_EVP_ORIGIN, JAVA_TRACING_LIBRARY);
@@ -258,8 +269,11 @@ private Call makeTelemetryRequest(@Nonnull List<String> filesContent) throws IOE
258269
.build());
259270
}
260271

261-
private RequestBody makeTelemetryRequestBody(@Nonnull List<String> filesContent)
262-
throws IOException {
272+
private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOException {
273+
CrashLog crashLog = CrashLogParser.fromHotspotCrashLog(content);
274+
if (crashLog == null) {
275+
throw new IOException("Failed to parse crash log");
276+
}
263277
try (Buffer buf = new Buffer()) {
264278
try (JsonWriter writer = JsonWriter.of(buf)) {
265279
writer.beginObject();
@@ -275,13 +289,11 @@ private RequestBody makeTelemetryRequestBody(@Nonnull List<String> filesContent)
275289
writer.name("debug").value(true);
276290
writer.name("payload");
277291
writer.beginArray();
278-
for (String message : filesContent) {
279-
writer.beginObject();
280-
writer.name("message").value(extractErrorStackTrace(message));
281-
writer.name("level").value("ERROR");
282-
writer.name("tags").value("severity:crash");
283-
writer.endObject();
284-
}
292+
writer.beginObject();
293+
writer.name("message").value(crashLog.toJson());
294+
writer.name("level").value("ERROR");
295+
writer.name("tags").value("severity:crash");
296+
writer.endObject();
285297
writer.endArray();
286298
writer.name("application");
287299
writer.beginObject();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.datadog.crashtracking.dto;
2+
3+
import com.squareup.moshi.Json;
4+
import com.squareup.moshi.JsonAdapter;
5+
import com.squareup.moshi.Moshi;
6+
import java.io.IOException;
7+
import java.util.Objects;
8+
import java.util.UUID;
9+
10+
public final class CrashLog {
11+
private static final int VERSION = 0;
12+
13+
private static final JsonAdapter<CrashLog> ADAPTER;
14+
15+
static {
16+
Moshi moshi = new Moshi.Builder().add(new SemanticVersion.SemanticVersionAdapter()).build();
17+
ADAPTER = moshi.adapter(CrashLog.class);
18+
}
19+
20+
public final String uuid = UUID.randomUUID().toString();
21+
public final String timestamp;
22+
public final boolean incomplete;
23+
public final ErrorData error;
24+
public final Metadata metadata;
25+
26+
@Json(name = "os_info")
27+
public final OSInfo osInfo;
28+
29+
@Json(name = "proc_info")
30+
public final ProcInfo procInfo;
31+
32+
@Json(name = "version_id")
33+
public final int version = VERSION;
34+
35+
public CrashLog(
36+
boolean incomplete,
37+
String timestamp,
38+
ErrorData error,
39+
Metadata metadata,
40+
OSInfo osInfo,
41+
ProcInfo procInfo) {
42+
this.incomplete = incomplete;
43+
this.timestamp = timestamp;
44+
this.error = error;
45+
this.metadata = metadata;
46+
this.osInfo = osInfo;
47+
this.procInfo = procInfo;
48+
}
49+
50+
public String toJson() {
51+
return ADAPTER.toJson(this);
52+
}
53+
54+
public static CrashLog fromJson(String json) throws IOException {
55+
return ADAPTER.fromJson(json);
56+
}
57+
58+
@Override
59+
public boolean equals(Object o) {
60+
if (this == o) {
61+
return true;
62+
}
63+
if (o == null || getClass() != o.getClass()) {
64+
return false;
65+
}
66+
CrashLog crashLog = (CrashLog) o;
67+
return incomplete == crashLog.incomplete
68+
&& version == crashLog.version
69+
&& Objects.equals(uuid, crashLog.uuid)
70+
&& Objects.equals(timestamp, crashLog.timestamp)
71+
&& Objects.equals(error, crashLog.error)
72+
&& Objects.equals(metadata, crashLog.metadata)
73+
&& Objects.equals(osInfo, crashLog.osInfo)
74+
&& Objects.equals(procInfo, crashLog.procInfo);
75+
}
76+
77+
@Override
78+
public int hashCode() {
79+
return Objects.hash(uuid, timestamp, incomplete, error, metadata, osInfo, procInfo, version);
80+
}
81+
82+
public boolean equalsForTest(Object o) {
83+
// for tests, we need to ignore UUID, OSInfo and Metadata part
84+
if (this == o) {
85+
return true;
86+
}
87+
if (o == null || getClass() != o.getClass()) {
88+
return false;
89+
}
90+
CrashLog crashLog = (CrashLog) o;
91+
return incomplete == crashLog.incomplete
92+
&& version == crashLog.version
93+
&& Objects.equals(timestamp, crashLog.timestamp)
94+
&& Objects.equals(error, crashLog.error)
95+
&& Objects.equals(procInfo, crashLog.procInfo);
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.datadog.crashtracking.dto;
2+
3+
import com.squareup.moshi.Json;
4+
import java.util.Objects;
5+
6+
public final class ErrorData {
7+
@Json(name = "is_crash")
8+
public final boolean isCrash = true;
9+
10+
public final String kind;
11+
public final String message;
12+
13+
@Json(name = "source_type")
14+
public final String sourceType = "crashtracking";
15+
16+
public final StackTrace stack;
17+
18+
public ErrorData(String kind, String message, StackTrace stack) {
19+
this.kind = kind;
20+
this.message = message;
21+
this.stack = stack;
22+
}
23+
24+
@Override
25+
public boolean equals(Object o) {
26+
if (this == o) {
27+
return true;
28+
}
29+
if (o == null || getClass() != o.getClass()) {
30+
return false;
31+
}
32+
ErrorData errorData = (ErrorData) o;
33+
return isCrash == errorData.isCrash
34+
&& Objects.equals(kind, errorData.kind)
35+
&& Objects.equals(message, errorData.message)
36+
&& Objects.equals(sourceType, errorData.sourceType)
37+
&& Objects.equals(stack, errorData.stack);
38+
}
39+
40+
@Override
41+
public int hashCode() {
42+
return Objects.hash(isCrash, kind, message, sourceType, stack);
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.datadog.crashtracking.dto;
2+
3+
import com.squareup.moshi.Json;
4+
import java.util.Collections;
5+
import java.util.Map;
6+
import java.util.Objects;
7+
8+
public final class Metadata {
9+
@Json(name = "library_name")
10+
public final String libraryName;
11+
12+
@Json(name = "library_version")
13+
public final String libraryVersion;
14+
15+
public final String family;
16+
public final Map<String, String> tags;
17+
18+
public Metadata(
19+
String libraryName, String libraryVersion, String family, Map<String, String> tags) {
20+
this.libraryName = libraryName;
21+
this.libraryVersion = libraryVersion;
22+
this.family = family;
23+
this.tags = tags != null ? Collections.unmodifiableMap(tags) : Collections.emptyMap();
24+
}
25+
26+
@Override
27+
public boolean equals(Object o) {
28+
if (this == o) {
29+
return true;
30+
}
31+
if (o == null || getClass() != o.getClass()) {
32+
return false;
33+
}
34+
Metadata metadata = (Metadata) o;
35+
return Objects.equals(libraryName, metadata.libraryName)
36+
&& Objects.equals(libraryVersion, metadata.libraryVersion)
37+
&& Objects.equals(family, metadata.family)
38+
&& Objects.equals(tags, metadata.tags);
39+
}
40+
41+
@Override
42+
public int hashCode() {
43+
return Objects.hash(libraryName, libraryVersion, family, tags);
44+
}
45+
}

0 commit comments

Comments
 (0)