Skip to main content

DotNet Custom Instrumentation

This guide explains how to implement Custom Instrumentation in .NET applications using the Motadata Custom Instrumentation package.

Custom instrumentation helps you add business-specific attributes to the currently active span without affecting the existing zero-code or auto-instrumentation flow. It is designed to align with the OTel instrumentation model while keeping attribute handling safe, validated, and consistent.

Prerequisites

  • The application must already be instrumented using Motadata Auto Instrumentation so that active span context is available.
  • The application must use one of the following supported runtimes:
    • .NET 8 or higher on Windows or Linux
    • .NET Framework 4.6.2 or higher on Windows
  • The Motadata Agent 8.1.2 or higher must be installed and running.
  • The application must already be generating traces in APM.
  • The project must support NuGet package installation.

Configuration Steps

Step 1: Install the .NET Custom Instrumentation Package

Add the Motadata Custom Instrumentation package to your application.

Using .NET CLI

dotnet add package Motadata.Apm.CustomInstrumentation

Using Package Manager Console

Install-Package Motadata.Apm.CustomInstrumentation

Using Project File

Add the following to your .csproj file:

<ItemGroup>
<PackageReference Include="Motadata.Apm.CustomInstrumentation" Version="1.0.0" />
</ItemGroup>

Step 2: Import the Package

After adding the dependency, import the package in your code:

using Motadata.Apm.CustomInstrumentation;

Step 3: Add Custom Attributes to the Active Span

Use the package APIs to inject custom attributes into the currently active span.

Basic Example

using Motadata.Apm.CustomInstrumentation;

CustomInstrumentation.Set("apm.user.id", 12345L);
CustomInstrumentation.Set("apm.user.name", "john.doe");
CustomInstrumentation.Set("apm.request.success", true);
CustomInstrumentation.SetStringList("apm.tags", new[] { "api", "production", "critical" });

Keys are automatically prefixed with apm. if the prefix is not provided. However, it is recommended to explicitly use the apm. prefix for consistency across services.

Since the package throws runtime exceptions for invalid input or missing span context, wrap calls in try/catch in production code.

using Motadata.Apm.CustomInstrumentation;

try
{
CustomInstrumentation.Set("apm.order.id", orderId);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Failed to set apm.order.id");
}

Supported Methods

Scalar Attribute Setters

MethodParameter
Set(string key, bool value)bool
Set(string key, double value)double
Set(string key, float value)float
Set(string key, int value)int
Set(string key, long value)long
Set(string key, string value)string

Collection Attribute Setters

MethodParameter
SetBooleanList(string key, bool[] values)bool[]
SetDoubleList(string key, double[] values)double[]
SetFloatList(string key, float[] values)float[]
SetIntegerList(string key, int[] values)int[]
SetLongList(string key, long[] values)long[]
SetStringList(string key, string[] values)string[]

Attribute Behavior and Validation Rules

The .NET Custom Instrumentation package validates both keys and values before attaching attributes to the span.

Key Handling

  • Keys are automatically prefixed with apm. when absent.
  • Keys are normalized to lowercase.
  • Keys allow only alphanumeric characters and dots.
  • Keys must not be null, empty, or whitespace-only.

Value Handling

  • double and float scalar values must be finite. NaN and Infinity are not allowed.
  • float values are converted to double.
  • int values are converted to long.
  • string scalar values must not be null, empty, or whitespace-only.
  • List methods require non-null and non-empty arrays.
  • SetStringList removes null elements and requires at least one remaining value.
  • SetDoubleList and SetFloatList remove invalid numeric values and require at least one remaining value.

Runtime Behavior

  • The package throws Exception for invalid input.
  • The package throws Exception when no active span is present, such as when Activity.Current == null.
  • The package is designed to work as a safe extension to the current Motadata auto-instrumentation flow.

Example Scenarios

Add Business Context to a Request

try
{
CustomInstrumentation.Set("apm.customer.id", 1001L);
CustomInstrumentation.Set("apm.customer.name", "john.doe");
CustomInstrumentation.Set("apm.customer.tier", "gold");
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Failed to set customer attributes");
}

Add Order and Transaction Details

try
{
CustomInstrumentation.Set("apm.order.id", 987654L);
CustomInstrumentation.Set("apm.order.amount", 2599.75);
CustomInstrumentation.Set("apm.order.currency", "USD");
CustomInstrumentation.Set("apm.order.success", true);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Failed to set order attributes");
}

Add Collection Attributes

try
{
CustomInstrumentation.SetStringList(
"apm.order.items",
new[] { "router", "switch", "firewall" }
);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Failed to set order items");
}

Add Failure Context

try
{
paymentService.ProcessPayment(paymentRequest);
}
catch (Exception exception)
{
try
{
CustomInstrumentation.Set("apm.payment.status", "failed");
CustomInstrumentation.Set("apm.payment.error_message", exception.Message);
}
catch (Exception instrumentationException)
{
_logger.LogWarning(instrumentationException, "Failed to set payment error attributes");
}

throw;
}

Advanced Example

The following example demonstrates an enterprise-style flow where business attributes are added across the lifecycle of an order request.

using System;
using Motadata.Apm.CustomInstrumentation;

public class OrderService
{
private readonly ILogger<OrderService> _logger;

public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}

public void ProcessOrder(Order order)
{
try
{
CustomInstrumentation.Set("apm.order.id", order.Id);
CustomInstrumentation.Set("apm.order.customer_id", order.CustomerId);
CustomInstrumentation.Set("apm.order.amount", order.Amount);
CustomInstrumentation.Set("apm.order.currency", order.Currency);
CustomInstrumentation.SetStringList(
"apm.order.tags",
new[] { "sales", "priority", "online" }
);

if (order.Amount > 10000)
{
CustomInstrumentation.Set("apm.order.high_value", true);
}

CustomInstrumentation.Set("apm.order.stage", "validation");
ValidateOrder(order);

CustomInstrumentation.Set("apm.order.stage", "inventory_check");
CheckInventory(order);

CustomInstrumentation.Set("apm.order.stage", "payment_processing");
ProcessPayment(order);

CustomInstrumentation.Set("apm.order.stage", "dispatch");
DispatchOrder(order);

CustomInstrumentation.Set("apm.order.status", "completed");
}
catch (Exception exception)
{
try
{
CustomInstrumentation.Set("apm.order.status", "failed");
CustomInstrumentation.Set("apm.order.error_message", exception.Message);
}
catch (Exception instrumentationException)
{
_logger.LogWarning(instrumentationException, "Failed to set failure attributes");
}

_logger.LogError(exception, "Order processing failed");
throw;
}
}

private void ValidateOrder(Order order)
{
// validation logic
}

private void CheckInventory(Order order)
{
// inventory logic
}

private void ProcessPayment(Order order)
{
// payment logic
}

private void DispatchOrder(Order order)
{
// dispatch logic
}
}

public class Order
{
public long Id { get; set; }
public long CustomerId { get; set; }
public double Amount { get; set; }
public string Currency { get; set; }
}

This approach helps enrich the trace with operational and business metadata such as order ID, customer, amount, status, stage, and error details without breaking the existing tracing lifecycle.

Best Practices

  • Use descriptive, hierarchical keys already prefixed with apm. such as apm.order.id.
  • Use numeric types for IDs and metrics, and booleans for flags instead of converting all values to strings.
  • Use list setters for collections and allow the package to handle filtering.
  • Keep key naming consistent across services for easier querying and dashboard usage.
  • Treat instrumentation as non-blocking telemetry. Catch exceptions, log them, and continue business execution.

What to Avoid

  • Do not attach sensitive data such as passwords, secrets, or authentication tokens.
  • Do not use inconsistent naming for the same business attribute across applications.
  • Do not send whitespace-only strings as attribute values.
  • Do not use invalid keys with unsupported symbols or spaces.
  • Do not assume custom attributes will be attached when no active span exists.

Verify the Attributes

After implementing custom instrumentation and redeploying the application:

  1. Go to APM Explorer.
  2. Open the required trace.
  3. Select the relevant span.
  4. Confirm that the custom attributes are visible in span attributes.

Support

  • Email: engg@motadata.com
  • Issues: GitHub Issues on the package repository

License

Copyright (c) 2026 Motadata. All rights reserved.

Proprietary software; see LICENSE for full terms.

Summary

The Motadata .NET Custom Instrumentation package provides a lightweight and enterprise-ready way to inject validated, namespaced, business-specific attributes into the currently active span. It works alongside existing auto-instrumentation and helps enrich traces with meaningful application context while preserving compatibility with current tracing, span lifecycle handling, and service configuration.