Skip to content

Commit f3999b7

Browse files
authored
Forward wellknown, runtime and process tags in crash reports (#9719)
* Forward wellknown, runtime and process tags in crash reports * Add process tags to payload * forbiddenApi
1 parent ffe013a commit f3999b7

File tree

13 files changed

+370
-132
lines changed

13 files changed

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

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

Lines changed: 29 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,22 +300,25 @@ 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);
311+
if (storedConfig.processTags != null) {
312+
writer.name("process_tags").value(storedConfig.processTags);
313+
}
314314
writer.endObject();
315315
writer.name("host");
316316
writer.beginObject();
317317
if (ContainerInfo.get().getContainerId() != null) {
318318
writer.name("container_id").value(ContainerInfo.get().getContainerId());
319319
}
320320
writer.name("hostname").value(config.getHostName());
321-
writer.name("env").value(config.getEnv());
321+
writer.name("env").value(storedConfig.env);
322322
writer.endObject();
323323
writer.endObject();
324324
}
@@ -327,23 +327,11 @@ private RequestBody makeTelemetryRequestBody(@Nonnull String content) throws IOE
327327
}
328328
}
329329

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-
342330
private void handleCall(final Call call) {
343331
try (Response response = call.execute()) {
344332
handleSuccess(call, response);
345333
} catch (IOException e) {
346-
handleFailure(call, e);
334+
handleFailure(e);
347335
}
348336
}
349337

@@ -364,7 +352,7 @@ private void handleSuccess(final Call call, final Response response) throws IOEx
364352
}
365353
}
366354

367-
private void handleFailure(final Call call, final IOException exception) {
368-
log.error("Failed to upload crash files, got exception: {}", exception.getMessage(), exception);
355+
private void handleFailure(final IOException exception) {
356+
log.error("Failed to upload crash files, got exception", exception);
369357
}
370358
}

0 commit comments

Comments
 (0)