Skip to content

Commit 7795b1f

Browse files
committed
Forward wellknown, runtime and process tags in crash reports
1 parent 295f7bf commit 7795b1f

File tree

13 files changed

+355
-132
lines changed

13 files changed

+355
-132
lines changed

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

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package datadog.crashtracking;
2+
3+
import static datadog.crashtracking.Initializer.PID_PREFIX;
4+
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
5+
import static datadog.trace.util.AgentThreadFactory.AGENT_THREAD_GROUP;
6+
7+
import datadog.environment.SystemProperties;
8+
import datadog.trace.api.Config;
9+
import datadog.trace.api.ProcessTags;
10+
import datadog.trace.api.WellKnownTags;
11+
import datadog.trace.util.PidHelper;
12+
import java.io.BufferedReader;
13+
import java.io.BufferedWriter;
14+
import java.io.IOException;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
17+
import java.util.stream.Collectors;
18+
import javax.annotation.Nullable;
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
public class ConfigManager {
23+
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigManager.class);
24+
25+
public static class StoredConfig {
26+
final String service;
27+
final String env;
28+
final String version;
29+
final String tags;
30+
final String processTags;
31+
final String runtimeId;
32+
33+
StoredConfig(
34+
String service,
35+
String env,
36+
String version,
37+
String tags,
38+
String processTags,
39+
String runtimeId) {
40+
this.service = service;
41+
this.env = env;
42+
this.version = version;
43+
this.tags = tags;
44+
this.processTags = processTags;
45+
this.runtimeId = runtimeId;
46+
}
47+
48+
public static class Builder {
49+
String service;
50+
String env;
51+
String version;
52+
String tags;
53+
String processTags;
54+
String runtimeId;
55+
56+
public Builder(Config config) {
57+
// get sane defaults
58+
this.service = config.getServiceName();
59+
this.env = config.getEnv();
60+
this.version = config.getVersion();
61+
this.runtimeId = config.getRuntimeId();
62+
}
63+
64+
Builder service(String service) {
65+
this.service = service;
66+
return this;
67+
}
68+
69+
Builder env(String env) {
70+
this.env = env;
71+
return this;
72+
}
73+
74+
Builder version(String version) {
75+
this.version = version;
76+
return this;
77+
}
78+
79+
Builder tags(String tags) {
80+
this.tags = tags;
81+
return this;
82+
}
83+
84+
Builder processTags(String processTags) {
85+
this.processTags = processTags;
86+
return this;
87+
}
88+
89+
Builder runtimeId(String runtimeId) {
90+
this.runtimeId = runtimeId;
91+
return this;
92+
}
93+
94+
StoredConfig build() {
95+
return new StoredConfig(service, env, version, tags, processTags, runtimeId);
96+
}
97+
}
98+
}
99+
100+
private ConfigManager() {}
101+
102+
private static String getBaseName(Path path) {
103+
String filename = path.getFileName().toString();
104+
int dotIndex = filename.lastIndexOf('.');
105+
if (dotIndex == -1) {
106+
return filename;
107+
}
108+
return filename.substring(0, dotIndex);
109+
}
110+
111+
private static String getMergedTagsForSerialization(Config config) {
112+
return config.getMergedCrashTrackingTags().entrySet().stream()
113+
.map(e -> e.getKey() + ":" + e.getValue())
114+
.collect(Collectors.joining(","));
115+
}
116+
117+
private static void writeEntry(BufferedWriter writer, CharSequence key, CharSequence value)
118+
throws IOException {
119+
if (key == null || value == null) {
120+
return;
121+
}
122+
writer.write(key.toString());
123+
writer.write('=');
124+
writer.write(value.toString());
125+
writer.newLine();
126+
}
127+
128+
public static void writeConfigToPath(Path scriptPath, String... additionalEntries) {
129+
String cfgFileName = getBaseName(scriptPath) + PID_PREFIX + PidHelper.getPid() + ".cfg";
130+
Path cfgPath = scriptPath.resolveSibling(cfgFileName);
131+
writeConfigToFile(Config.get(), cfgPath, additionalEntries);
132+
}
133+
134+
// @VisibleForTesting
135+
static void writeConfigToFile(Config config, Path cfgPath, String... additionalEntries) {
136+
final WellKnownTags wellKnownTags = config.getWellKnownTags();
137+
138+
LOGGER.debug("Writing config file: {}", cfgPath);
139+
try (BufferedWriter bw = Files.newBufferedWriter(cfgPath)) {
140+
for (int i = 0; i < additionalEntries.length; i += 2) {
141+
writeEntry(bw, additionalEntries[i], additionalEntries[i + 1]);
142+
}
143+
writeEntry(bw, "tags", getMergedTagsForSerialization(config));
144+
writeEntry(bw, "service", wellKnownTags.getService());
145+
writeEntry(bw, "version", wellKnownTags.getVersion());
146+
writeEntry(bw, "env", wellKnownTags.getEnv());
147+
writeEntry(bw, "process_tags", ProcessTags.getTagsForSerialization());
148+
writeEntry(bw, "runtime_id", wellKnownTags.getRuntimeId());
149+
writeEntry(bw, "java_home", SystemProperties.get("java.home"));
150+
151+
Runtime.getRuntime()
152+
.addShutdownHook(
153+
new Thread(
154+
AGENT_THREAD_GROUP,
155+
() -> {
156+
try {
157+
LOGGER.debug("Deleting config file: {}", cfgPath);
158+
Files.deleteIfExists(cfgPath);
159+
} catch (IOException e) {
160+
LOGGER.warn(SEND_TELEMETRY, "Failed deleting config file: {}", cfgPath, e);
161+
}
162+
}));
163+
LOGGER.debug("Config file written: {}", cfgPath);
164+
} catch (IOException e) {
165+
LOGGER.warn(SEND_TELEMETRY, "Failed writing config file: {}", cfgPath);
166+
try {
167+
Files.deleteIfExists(cfgPath);
168+
} catch (IOException ignored) {
169+
// ignore
170+
}
171+
}
172+
}
173+
174+
@Nullable
175+
public static StoredConfig readConfig(Config config, Path scriptPath) {
176+
try (final BufferedReader reader = Files.newBufferedReader(scriptPath)) {
177+
final StoredConfig.Builder cfgBuilder = new StoredConfig.Builder(config);
178+
String line;
179+
while ((line = reader.readLine()) != null) {
180+
if (line.isEmpty()) {
181+
continue;
182+
}
183+
String[] parts = line.split("=", 2);
184+
if (parts.length != 2) {
185+
continue;
186+
}
187+
final String value = parts[1];
188+
switch (parts[0]) {
189+
case "tags":
190+
cfgBuilder.tags(value);
191+
break;
192+
case "service":
193+
cfgBuilder.service(value);
194+
break;
195+
case "env":
196+
cfgBuilder.env(value);
197+
break;
198+
case "version":
199+
cfgBuilder.version(value);
200+
break;
201+
case "process_tags":
202+
cfgBuilder.processTags(value);
203+
break;
204+
case "runtime_id":
205+
cfgBuilder.runtimeId(value);
206+
break;
207+
default:
208+
// ignore
209+
break;
210+
}
211+
}
212+
return cfgBuilder.build();
213+
} catch (Throwable t) {
214+
LOGGER.error("Failed to read config file: {}", scriptPath, t);
215+
}
216+
return null;
217+
}
218+
}

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

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import datadog.trace.api.DDTags;
1818
import datadog.trace.bootstrap.config.provider.ConfigProvider;
1919
import datadog.trace.util.PidHelper;
20-
import datadog.trace.util.RandomUtils;
2120
import de.thetaphi.forbiddenapis.SuppressForbidden;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
2222
import java.io.*;
2323
import java.nio.charset.Charset;
2424
import java.nio.charset.StandardCharsets;
@@ -65,30 +65,36 @@ public final class CrashUploader {
6565
MediaType.parse("application/octet-stream");
6666

6767
private final Config config;
68+
private final ConfigManager.StoredConfig storedConfig;
6869

6970
private final OkHttpClient telemetryClient;
7071
private final HttpUrl telemetryUrl;
7172
private final boolean agentless;
7273
private final String tags;
7374

74-
public CrashUploader() {
75-
this(Config.get());
75+
public CrashUploader(@Nonnull final ConfigManager.StoredConfig storedConfig) {
76+
this(Config.get(), storedConfig);
7677
}
7778

78-
CrashUploader(final Config config) {
79+
CrashUploader(
80+
@NonNull final Config config, @Nonnull final ConfigManager.StoredConfig storedConfig) {
7981
this.config = config;
82+
this.storedConfig = storedConfig;
8083

8184
telemetryUrl = HttpUrl.get(config.getFinalCrashTrackingTelemetryUrl());
8285
agentless = config.isCrashTrackingAgentless();
8386

84-
final Map<String, String> tagsMap = new HashMap<>(config.getMergedCrashTrackingTags());
85-
tagsMap.put(VersionInfo.LIBRARY_VERSION_TAG, VersionInfo.VERSION);
87+
final StringBuilder tagsBuilder =
88+
new StringBuilder(storedConfig.tags != null ? storedConfig.tags : "");
89+
if (!tagsBuilder.toString().isEmpty()) {
90+
tagsBuilder.append(",");
91+
}
92+
tagsBuilder.append(VersionInfo.LIBRARY_VERSION_TAG).append('=').append(VersionInfo.VERSION);
8693
// PID can be empty if we cannot find it out from the system
8794
if (!PidHelper.getPid().isEmpty()) {
88-
tagsMap.put(DDTags.PID_TAG, PidHelper.getPid());
95+
tagsBuilder.append(",").append(DDTags.PID_TAG).append('=').append(PidHelper.getPid());
8996
}
90-
// Comma separated tags string for V2.4 format
91-
tags = tagsToString(tagsMap);
97+
tags = tagsBuilder.toString();
9298

9399
ConfigProvider configProvider = config.configProvider();
94100

@@ -108,13 +114,6 @@ public CrashUploader() {
108114
CRASH_TRACKING_UPLOAD_TIMEOUT, CRASH_TRACKING_UPLOAD_TIMEOUT_DEFAULT)));
109115
}
110116

111-
private String tagsToString(final Map<String, String> tags) {
112-
return tags.entrySet().stream()
113-
.filter(e -> e.getValue() != null && !e.getValue().isEmpty())
114-
.map(e -> e.getKey() + ":" + e.getValue())
115-
.collect(Collectors.joining(","));
116-
}
117-
118117
public void upload(@Nonnull List<Path> files) throws IOException {
119118
for (Path file : files) {
120119
uploadToLogs(file);
@@ -141,7 +140,9 @@ void uploadToLogs(@Nonnull String message, @Nonnull PrintStream out) throws IOEx
141140
writer.name("ddsource").value("crashtracker");
142141
writer.name("ddtags").value(tags);
143142
writer.name("hostname").value(config.getHostName());
144-
writer.name("service").value(config.getServiceName());
143+
writer.name("service").value(storedConfig.service);
144+
writer.name("version").value(storedConfig.version);
145+
writer.name("env").value(storedConfig.env);
145146
writer.name("message").value(message);
146147
writer.name("level").value("ERROR");
147148
writer.name("error");
@@ -282,11 +283,7 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOE
282283
writer.beginObject();
283284
writer.name("api_version").value(TELEMETRY_API_VERSION);
284285
writer.name("request_type").value("logs");
285-
writer
286-
.name("runtime_id")
287-
// this is unknowable at this point because the process has crashed
288-
// though we may be able to save it in the tmpdir
289-
.value(RandomUtils.randomUUID().toString());
286+
writer.name("runtime_id").value(storedConfig.runtimeId);
290287
writer.name("tracer_time").value(Instant.now().getEpochSecond());
291288
writer.name("seq_id").value(1);
292289
writer.name("debug").value(true);
@@ -303,13 +300,13 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOE
303300
writer.endArray();
304301
writer.name("application");
305302
writer.beginObject();
306-
writer.name("env").value(config.getEnv());
303+
writer.name("env").value(storedConfig.env);
307304
writer.name("language_name").value("jvm");
308305
writer
309306
.name("language_version")
310307
.value(SystemProperties.getOrDefault("java.version", "unknown"));
311-
writer.name("service_name").value(config.getServiceName());
312-
writer.name("service_version").value(config.getVersion());
308+
writer.name("service_name").value(storedConfig.service);
309+
writer.name("service_version").value(storedConfig.version);
313310
writer.name("tracer_version").value(VersionInfo.VERSION);
314311
writer.endObject();
315312
writer.name("host");
@@ -318,7 +315,7 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOE
318315
writer.name("container_id").value(ContainerInfo.get().getContainerId());
319316
}
320317
writer.name("hostname").value(config.getHostName());
321-
writer.name("env").value(config.getEnv());
318+
writer.name("env").value(storedConfig.env);
322319
writer.endObject();
323320
writer.endObject();
324321
}
@@ -327,23 +324,11 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOE
327324
}
328325
}
329326

330-
private String readContent(InputStream file) throws IOException {
331-
try (InputStreamReader reader = new InputStreamReader(file, StandardCharsets.UTF_8)) {
332-
int read;
333-
char[] buffer = new char[1 << 14];
334-
StringBuilder sb = new StringBuilder();
335-
while ((read = reader.read(buffer, 0, buffer.length)) > 0) {
336-
sb.append(buffer, 0, read);
337-
}
338-
return sb.toString();
339-
}
340-
}
341-
342327
private void handleCall(final Call call) {
343328
try (Response response = call.execute()) {
344329
handleSuccess(call, response);
345330
} catch (IOException e) {
346-
handleFailure(call, e);
331+
handleFailure(e);
347332
}
348333
}
349334

@@ -364,7 +349,7 @@ private void handleSuccess(final Call call, final Response response) throws IOEx
364349
}
365350
}
366351

367-
private void handleFailure(final Call call, final IOException exception) {
368-
log.error("Failed to upload crash files, got exception: {}", exception.getMessage(), exception);
352+
private void handleFailure(final IOException exception) {
353+
log.error("Failed to upload crash files, got exception", exception);
369354
}
370355
}

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/CrashUploaderScriptInitializer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package datadog.crashtracking;
22

3+
import static datadog.crashtracking.ConfigManager.writeConfigToPath;
34
import static datadog.crashtracking.Initializer.LOG;
45
import static datadog.crashtracking.Initializer.RWXRWXRWX;
56
import static datadog.crashtracking.Initializer.R_XR_XR_X;
67
import static datadog.crashtracking.Initializer.findAgentJar;
78
import static datadog.crashtracking.Initializer.getCrashUploaderTemplate;
8-
import static datadog.crashtracking.Initializer.writeConfig;
99
import static datadog.trace.api.telemetry.LogCollector.SEND_TELEMETRY;
1010
import static java.nio.file.attribute.PosixFilePermissions.asFileAttribute;
1111
import static java.nio.file.attribute.PosixFilePermissions.fromString;
@@ -56,7 +56,7 @@ static void initialize(String onErrorVal, String onErrorFile) {
5656
return;
5757
}
5858

59-
writeConfig(scriptPath, "agent", agentJar, "hs_err", onErrorFile);
59+
writeConfigToPath(scriptPath, "agent", agentJar, "hs_err", onErrorFile);
6060
}
6161

6262
private static boolean copyCrashUploaderScript(

0 commit comments

Comments
 (0)