No-code instrumentation for .NET in Splunk Observability Cloud
This document explains how to instrument a .NET application using no‑code, configuration‑based instrumentation.
Overview
No‑code instrumentation for .NET lets you collect spans and method‑level telemetry without modifying your application's source code. Instead of adding manual instrumentation, you define the methods you want to instrument in a YAML configuration file. At runtime, the .NET instrumentation agent automatically applies these rules and adds the necessary logic to create spans, capture attributes, and enrich your observability data.
This approach is especially useful when you need to instrument third‑party libraries, legacy applications, or large codebases where changing the source code is difficult or not possible.
This feature is experimental. The configuration schema and behavior may change in future releases.
Key capabilities
No-code instrumentation for .NET provides a flexible, configuration-driven way to instrument methods across your application and external libraries without modifying source code. The following capabilities are supported:
- Universal method instrumentation
You can instrument almost any method in any .NET assembly — your own code, shared libraries, or third-party packages — without touching the source code.
- Flexible method targeting
You're free to instrument many kinds of methods: static, instance, async, generic, and even overloaded ones. If your application can call it, you can most likely instrument it.
- Parameter support
Methods with up to nine parameters are supported, so you can target a wide range of real-world method signatures.
- Return type support
You can instrument methods that return anything from
voidand simple types toTask,Task<T>,ValueTask,ValueTask<T>, or custom classes. - Span customization
You decide what the span looks like — name, kind, and any custom attributes — all defined directly in your YAML configuration.
- Method overload support
If a method has multiple overloads, you can target the exact one you want by specifying its full signature.
Configuration structure
The no-code instrumentation feature is configured under the no_code/development section of your YAML configuration file. This section defines a list of instrumentation rules that specify which methods should be instrumented and how spans should be created when those methods are invoked.
Each rule describes a single target method, including its assembly, type, method name, and signature, along with the span settings applied during execution.
Target fields
The target section identifies the exact method to instrument. These fields ensure that the correct method is matched, even when multiple overloads exist.
assembly.nameThe name of the assembly that contains the method.
typeThe fully qualified class name that defines the method.
methodThe method name.
signature.return_typeThe return type of the method.
signature.parameter_typesA list of parameter types used to match the correct overload.
Span fields
The span section defines how the span should be created when the target method is invoked.
- name
A custom span name. If omitted, the method name is used.
- kind
The span kind (for example:
internal,server,client,producer,consumer). - attributes
A list of custom attributes added to the span.
Attribute types
The following attribute types are supported. Use them to ensure that span attributes are recorded correctly and not dropped during processing.
string- Text values such as identifiers, names, or descriptive labels.bool- Boolean values (true/false) used for flags or binary states.int- Whole numbers, typically used for counters, sizes, or numeric identifiers.double- Floating-point numbers for measurements or values requiring decimal precision.string_array- An array of text values, useful for lists such as tags or categories.bool_array- An array of boolean values.int_array- An array of integers, often used for numeric collections or ranges.double_array- An array of floating-point numbers for sets of measurements.
Example configuration
file_format: "1.0-rc.1"
instrumentation/development:
dotnet:
no_code:
targets:
- target:
assembly:
name: TestApplication.NoCode
type: TestApplication.NoCode.NoCodeTestingClass
method: TestMethod
signature:
return_type: System.Void
parameter_types:
- System.String
span:
name: Span-TestMethod1String
kind: internal
attributes:
- name: custom.attribute
value: "attribute_value"
type: string
- Basic method instrumentation
-
Instrument a simple static method:
CODEpublic static void TestMethodStatic();Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethodStatic signature: return_type: System.Void parameter_types: span: name: Span-TestMethodStatic kind: internal - Instrumentation with default span kind
-
When
kindis omitted, it defaults tointernal:CODEpublic void TestMethodA();Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethodA signature: return_type: System.Void parameter_types: span: name: Span-TestMethodA # kind defaults to 'internal' when omitted - Method with parameters
-
Instrument a method with specific parameters:
CODEpublic void TestMethod(string param1, string param2);Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethod signature: return_type: System.Void parameter_types: - System.String - System.String span: name: Span-TestMethod2 kind: server attributes: - name: operation.type value: "test_method" type: string - Async method instrumentation
-
Instrument an async method returning
Task<T>:CODEpublic async Task<int> IntTaskTestMethodAsync();Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: IntTaskTestMethodAsync signature: return_type: System.Threading.Tasks.Task`1[System.Int32] parameter_types: span: name: Span-IntTaskTestMethodAsync kind: client attributes: - name: async.operation value: "task_with_return" type: string - Multiple attributes with different types
-
Configure spans with various attribute types (from the actual test configuration):
CODEpublic static void TestMethodStatic();Configuration with multiple attribute types:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethodStatic signature: return_type: System.Void parameter_types: span: name: Span-TestMethodStatic kind: internal attributes: - name: attribute_key_string value: "string_value" type: string - name: attribute_key_bool value: true type: bool - name: attribute_key_int value: 12345 type: int - name: attribute_key_double value: 123.45 type: double - name: attribute_key_string_array value: ["value1", "value2", "value3"] type: string_array - name: attribute_key_bool_array value: [true, false, true] type: bool_array - name: attribute_key_int_array value: [123, 456, 789] type: int_array - name: attribute_key_double_array value: [123.45, 678.90] type: double_array - Overload targeting
-
Target specific method overloads by parameter types:
CODE// Parameterless overload public void TestMethod(); // String parameter overload public void TestMethod(string param1); // Int parameter overload public void TestMethod(int param1);Configuration for targeting specific overloads:
CODEinstrumentation/development: dotnet: no_code: targets: # Parameterless overload - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethod signature: return_type: System.Void parameter_types: span: name: Span-TestMethod0 kind: client # String parameter overload - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethod signature: return_type: System.Void parameter_types: - System.String span: name: Span-TestMethod1String kind: producer # Int parameter overload - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: TestMethod signature: return_type: System.Void parameter_types: - System.Int32 span: name: Span-TestMethod1Int kind: server - ValueTask Support (.NET 8+ only)
-
Instrument methods returning
ValueTaskorValueTask<T>:CODEpublic async ValueTask<int> IntValueTaskTestMethodAsync();Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: IntValueTaskTestMethodAsync signature: return_type: System.Threading.Tasks.ValueTask`1[System.Int32] parameter_types: span: name: Span-IntValueTaskTestMethodAsync kind: client - Generic method instrumentation
-
Instrument generic methods (note the return type specification):
CODEpublic T? GenericTestMethod<T>();Note: When instrumenting generic methods, make sure to specify the concrete types that result from generic type substitution. Both the return type and all parameter types must reflect the compiled method signature. To determine the exact types, use tools such as ILSpy or other IL viewers. In ILSpy, switch the view to IL with C# to inspect the fully resolved method signature.Configuration (when called as
GenericTestMethod<int>()):CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: GenericTestMethod signature: return_type: System.Int32 parameter_types: span: name: Span-GenericTestMethod kind: internal - Generic class instrumentation
-
Instrument methods in generic classes with class-level type parameters:
JAVApublic class GenericNoCodeTestingClass<TFooClass, TBarClass> { public TFooMethod GenericTestMethod<TFooMethod, TBarMethod>( TFooMethod fooMethod, TBarMethod barMethod, TFooClass fooClass, TBarClass barClass) { return fooMethod; } }For generic classes, use the backtick notation with the number of class-level type parameters:
Generic Type Parameter Notation:
- Class-level type parameters: Use
!!0,!!1, etc. (where!!0is the first class type parameter) - Method-level type parameters: Use
!0,!1, etc. (where!0is the first method type parameter) - In the type name, use backtick notation:
ClassName\N` where N is the number of generic parameters
Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.GenericNoCodeTestingClass`2 method: GenericTestMethod signature: return_type: '!0' parameter_types: - '!0' - '!1' - '!!0' - '!!1' span: name: Span-GenericTestMethodWithParameters kind: internalIn this example:
GenericNoCodeTestingClass\2` indicates a class with 2 generic type parameters'!0'represents the first method type parameter (TFooMethod)'!1'represents the second method type parameter (TBarMethod)'!!0'represents the first class type parameter (TFooClass)'!!1'represents the second class type parameter (TBarClass)
- Class-level type parameters: Use
- Methods with return values
-
Instrument methods that return values:
CODE// Method returning string public string ReturningStringTestMethod(); // Method returning custom class public TestClass ReturningCustomClassTestMethod();Configuration:
CODEinstrumentation/development: dotnet: no_code: targets: # Method returning string - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: ReturningStringTestMethod signature: return_type: System.String parameter_types: span: name: Span-ReturningStringTestMethod kind: internal # Method returning custom class - target: assembly: name: TestApplication.NoCode type: TestApplication.NoCode.NoCodeTestingClass method: ReturningCustomClassTestMethod signature: return_type: TestApplication.NoCode.TestClass parameter_types: span: name: Span-ReturningCustomClassTestMethod kind: internal
The repository includes a full test application that contains complete, working examples of all no‑code instrumentation scenarios. You can review the:
to see how each rule behaves in a real .NET environment.
Best practices
Apply the following best practices to ensure reliable, performant, and maintainable no-code instrumentation:
- Prefer manual instrumentation when feasible:
If the source code is accessible, manual instrumentation provides lower overhead, more granular control over span boundaries, and better alignment with application semantics.
- Define explicit and stable span names:
Use deterministic naming conventions that accurately represent the instrumented operation. Avoid ambiguous or context-dependent names.
- Select the correct span kind for the operation model:
Ensure that the configured span kind reflects the method’s functional role, such as
internal,server,client,producer, orconsumer. Incorrect span kinds can degrade trace interpretation. - Emit attributes that improve diagnostic value:
Add attributes that carry operational context, identifiers, or input/output metadata. Avoid attributes that are redundant, unstable, or high-cardinality unless strictly necessary.
- Target method overloads precisely:
Always specify the full method signature, including return type and parameter types, to avoid unintentionally instrumenting the wrong overload.
- Validate configuration in an isolated test environment:
Use a dedicated test application to confirm that spans are emitted as expected before rolling out changes to production.
- Evaluate performance impact on high-frequency code paths:
Instrumentation introduces overhead. For hot paths or tight loops, assess whether the diagnostic value justifies the cost, and consider sampling or selective instrumentation.