Skip to content

Instantly share code, notes, and snippets.

@schultzisaiah
Last active January 17, 2025 21:30
Show Gist options
  • Select an option

  • Save schultzisaiah/43b727b5e7257ff734513f82488475f6 to your computer and use it in GitHub Desktop.

Select an option

Save schultzisaiah/43b727b5e7257ff734513f82488475f6 to your computer and use it in GitHub Desktop.
Add to DataDog traces from Java code
import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import datadog.trace.api.interceptor.MutableSpan;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
@Slf4j
public class DataDogUtil {
public static final String DD_NESTED_TEMPLATE = "%s.%s";
private static final String DATA_LAYER_ERROR_NAME = "javaError";
private static final String DD_NESTED_ERROR_TEMPLATE = DD_NESTED_TEMPLATE + ".%s";
private static final String DD_LIST_NAME = "list";
private static final ObjectMapper objectMapper = new ObjectMapper();
private DataDogUtil() {}
public static Optional<MutableSpan> getRootSpan() {
return ofNullable(GlobalTracer.get())
.map(Tracer::activeSpan)
.filter(MutableSpan.class::isInstance)
.map(MutableSpan.class::cast)
.map(MutableSpan::getLocalRootSpan);
}
/**
* Set the given tag on the current DataDog Trace span as a String type.
* @param tagName The name of the tag to be set
* @param tagValue The value of the tag to be set
*/
public static void setTag(String tagName, String tagValue) {
getRootSpan()
.filter(span -> tagValue != null)
.ifPresent(span -> span.setTag(tagName, tagValue));
}
/**
* Set the given tag on the current DataDog Trace span as a Number type.
* @param tagName The name of the tag to be set
* @param tagValue The value of the tag to be set
*/
public static void setTag(String tagName, Number tagValue) {
getRootSpan()
.filter(span -> tagValue != null)
.ifPresent(span -> span.setTag(tagName, tagValue));
}
/**
* Set the given tag on the current DataDog Trace span as a Boolean type.
* @param tagName The name of the tag to be set
* @param tagValue The value of the tag to be set
*/
public static void setTag(String tagName, Boolean tagValue) {
getRootSpan()
.filter(span -> tagValue != null)
.ifPresent(span -> span.setTag(tagName, tagValue));
}
/**
* Set the given object as parsed JSON tags on the current DataDog Trace span.
* @param object The object to be tagged to the Trace
* @param rootTagName The root tag name to be used for the JSON entity
*/
public static void setTags(Object object, String rootTagName) {
Optional.ofNullable(object)
.map(DataDogUtil::toJson)
.ifPresent(jsonNode -> setTags(jsonNode, rootTagName));
}
/**
* Set the given JSON entity as tags on the current DataDog Trace span.
* @param currentNode The object to be tagged to the Trace
* @param rootTagName The root tag name to be used for the JSON entity
*/
public static void setTags(JsonNode currentNode, String rootTagName) {
getRootSpan().ifPresent(span -> setTags(currentNode, span, rootTagName));
}
/**
* Set the given error message and class name as tags on the current DataDog Trace span.
* Tags that will be added to the trace are:
* - {tagName}.javaError.message: The exception message
* - {tagName}.javaError.class: The exception class name
*/
public static void setErrorTags(String tagName, Exception e) {
getRootSpan().ifPresent(span -> setErrorTags(span, tagName, e));
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/// Private helpers:
private static void setTags(JsonNode currentNode, MutableSpan span, String rootTagName) {
try {
if (currentNode.isObject()) {
Iterator<Entry<String, JsonNode>> fields = currentNode.fields();
while (fields.hasNext()) {
Entry<String, JsonNode> field = fields.next();
String newPath = StringUtils.isBlank(rootTagName)
? field.getKey()
: format(DD_NESTED_TEMPLATE, rootTagName, field.getKey());
setTags(field.getValue(), span, newPath);
}
} else if (currentNode.isArray()) {
setListTags(currentNode, span, format(DD_NESTED_TEMPLATE, rootTagName, DD_LIST_NAME));
} else if (!currentNode.isNull()) {
String valAsText = currentNode.asText();
if (isNumberWithoutLeadingZero(currentNode)) {
// Leading zeros are often meaningful, so if they happen to be present, keep them.
setNumberTag(span, rootTagName, currentNode);
} else if (currentNode.isBoolean()) {
span.setTag(rootTagName, currentNode.asBoolean());
} else {
span.setTag(rootTagName, valAsText);
}
}
} catch (Exception e) {
log.warn("Error setting DataDog trace tags for path: {}", rootTagName, e);
setErrorTags(span, rootTagName, e);
}
}
private static void setListTags(JsonNode currentNode, MutableSpan span, String currentPath) {
try {
int num = currentNode.size();
String sizePath = format(DD_NESTED_TEMPLATE, currentPath, "size");
for (int i = 0; i < num; i++) {
String elementPath = format(DD_NESTED_TEMPLATE, currentPath, "element");
setTags(currentNode.get(i), span, format(DD_NESTED_TEMPLATE, sizePath, num));
setTags(currentNode.get(i), span, format(DD_NESTED_TEMPLATE, elementPath, i));
}
} catch (Exception e) {
log.warn("Error setting DataDog trace tags for path: {}", currentPath, e);
setErrorTags(span, currentPath, e);
}
}
private static boolean isNumberWithoutLeadingZero(JsonNode currentNode) {
return (currentNode.asText().startsWith("0.") || !currentNode.asText().startsWith("0"))
&& currentNode.isNumber();
}
private static void setNumberTag(MutableSpan span, String currentPath, JsonNode currentNode) {
if (currentNode.isInt()) {
span.setTag(currentPath, currentNode.asInt());
} else if (currentNode.isLong()) {
span.setTag(currentPath, currentNode.asLong());
} else if (currentNode.isDouble()) {
span.setTag(currentPath, currentNode.asDouble());
} else if (currentNode.isFloat()) {
span.setTag(currentPath, currentNode.floatValue());
} else {
span.setTag(currentPath, currentNode.asText());
}
}
private static void setErrorTags(MutableSpan span, String currentPath, Exception e) {
span.setTag(format(DD_NESTED_ERROR_TEMPLATE, currentPath, DATA_LAYER_ERROR_NAME,
"message"), e.getMessage());
span.setTag(format(DD_NESTED_ERROR_TEMPLATE, currentPath, DATA_LAYER_ERROR_NAME,
"class"), e.getClass().getName());
}
private static JsonNode toJson(Object object) {
if (object == null) {
return null;
}
try {
return objectMapper.valueToTree(object);
} catch (IllegalArgumentException e) {
log.error("Failed to map object to JsonNode", e);
return null;
}
}
}
@schultzisaiah
Copy link
Author

schultzisaiah commented Jan 17, 2025

Requires these dependencies:

io.opentracing:opentracing-util
com.datadoghq:dd-trace-api

This implementation also depends on JacksonDatabind, but feel free to refactor to whatever un/marshalling framework you want to use. It also depends on Lombok, but it's only for the logger. Feel free to refactor this as well to whatever suits your needs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment