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.

Note:

This feature is experimental. The configuration schema and behavior may change in future releases.

CAUTION: No‑code instrumentation adds runtime overhead because it injects additional logic into instrumented methods. This may impact performance, especially for frequently called methods. When possible, prefer manual instrumentation for better efficiency.

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 void and simple types to Task, 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.name

    The name of the assembly that contains the method.

  • type

    The fully qualified class name that defines the method.

  • method

    The method name.

  • signature.return_type

    The return type of the method.

  • signature.parameter_types

    A 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.
Note: Attributes with unsupported types or invalid values are omitted.

Example configuration

The following example illustrates what a no-code instrumentation rule may look like in your YAML configuration:
CODE
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:

CODE
public static void TestMethodStatic();

Configuration:

CODE
instrumentation/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 kind is omitted, it defaults to internal:

CODE
public void TestMethodA();

Configuration:

CODE
instrumentation/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:

CODE
public void TestMethod(string param1, string param2);

Configuration:

CODE
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
                - 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>:

CODE
public async Task<int> IntTaskTestMethodAsync();

Configuration:

CODE
instrumentation/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):

CODE
public static void TestMethodStatic();

Configuration with multiple attribute types:

CODE
instrumentation/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:

CODE
instrumentation/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 ValueTask or ValueTask<T>:

CODE
public async ValueTask<int> IntValueTaskTestMethodAsync();

Configuration:

CODE
instrumentation/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):

CODE
public 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>()):

CODE
instrumentation/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:

JAVA
public 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 !!0 is the first class type parameter)
  • Method-level type parameters: Use !0, !1, etc. (where !0 is the first method type parameter)
  • In the type name, use backtick notation: ClassName\N` where N is the number of generic parameters

Configuration:

CODE
instrumentation/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: internal

In 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)
Methods with return values

Instrument methods that return values:

CODE
// Method returning string
public string ReturningStringTestMethod();

// Method returning custom class
public TestClass ReturningCustomClassTestMethod();

Configuration:

CODE
instrumentation/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
Note:

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, or consumer. 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.