Skip to content

Instantly share code, notes, and snippets.

@szalapski
Last active January 29, 2025 15:51
Show Gist options
  • Save szalapski/406b0c9472985e2867179ff2fd0226de to your computer and use it in GitHub Desktop.
Save szalapski/406b0c9472985e2867179ff2fd0226de to your computer and use it in GitHub Desktop.
/// <summary>
/// Extension methods for working with DataDog spans.
/// </summary>
/// <remarks>These could someday be made into a simple NuGet package for use anywhere.</remarks>
public static class DatadogExtensions
{
/// <summary>
/// Sets a Datadog tag with a parsed complex value onto a span, from an XML string
/// </summary>
/// <remarks>If the XML string cannot be parsed as XML, the tag will still be added, but with "string" appended
/// to the key name.</remarks>
public static void SetTagFromXml(this ISpan span, string key, string valueXmlString)
{
try
{
XElement element = XElement.Parse(valueXmlString);
span.SetTagFromXElement(key, element);
}
catch (Exception e)
{
span.SetTag($"{key}string", valueXmlString);
bool isReallyAnError = span.Error;
span.SetException(e);
span.Error = isReallyAnError; // make XML parsing errors not count as errors
}
}
/// <summary>
///
/// </summary>
/// <param name="span"></param>
/// <param name="keyPrefix"></param>
/// <param name="element"></param>
/// <remarks>This parses only elements and texts from the XML; it could be further enhanced to capture comments,
/// attributes, etc.</remarks>
public static void SetTagFromXElement(this ISpan span, string keyPrefix, XElement? element)
{
if (element is null)
{
span.SetTag(keyPrefix.NormalizeTagKey(), ""); // note that DataDog doesn't allow an explicit null
return;
}
string name = $"{keyPrefix}.{element.Name.LocalName}";
if (element.HasElements)
{
// if there is both text and child elements, add a special "text_" child tag that is unlikely to be an
// actual child element. (In the very rare case that it is, the text node will be overwritten by the
// actual child element.)
string textValue = string.Concat(element.Nodes().OfType<XText>().Select(t => t.Value));
if (!string.IsNullOrWhiteSpace(textValue)) span.SetTag($"{name.NormalizeTagKey()}.text_", textValue);
foreach (XElement child in element.Elements()) span.SetTagFromXElement(name, child);
}
else span.SetTag(name.NormalizeTagKey(), element.Value);
}
/// <summary>
/// Returns a key string that is changed to conform to DataDog's restrictions for tag key names,
/// per https://docs.datadoghq.com/getting_started/tagging/.
/// </summary>
public static string NormalizeTagKey(this string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentOutOfRangeException(nameof(key), "String value must not be null, empty, or whitespace.");
// Allow only alphanumerics, underscores, minuses, colons, periods, slashes; substitute a _ for all others
const string disallowedCharacters = @"[^\w\-:\./]";
string result = Regex.Replace(key, disallowedCharacters, "_");
// The first character is more restricted: it must be a letter, so prepend a "z" if it isn't
if (char.ToLowerInvariant(result[0]) is < 'a' or > 'z') result = $"z{result}";
return result;
}
public static ISpan SetTagsRange(this ISpan span, string tag, NameValueCollection collection)
{
foreach (string key in collection.AllKeys) span.SetTag($"{tag}.{key}", collection[key]);
return span;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment