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.
Recommended Production Pattern
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
| Method | Parameter |
|---|---|
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
| Method | Parameter |
|---|---|
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
doubleandfloatscalar values must be finite.NaNandInfinityare not allowed.floatvalues are converted todouble.intvalues are converted tolong.stringscalar values must not be null, empty, or whitespace-only.- List methods require non-null and non-empty arrays.
SetStringListremoves null elements and requires at least one remaining value.SetDoubleListandSetFloatListremove invalid numeric values and require at least one remaining value.
Runtime Behavior
- The package throws
Exceptionfor invalid input. - The package throws
Exceptionwhen no active span is present, such as whenActivity.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 asapm.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:
- Go to APM Explorer.
- Open the required trace.
- Select the relevant span.
- 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.