Public APIedit

The public API of the Elastic APM .NET agent lets you customize and manually create spans and transactions, as well as track errors.

Initializationedit

The API does not require explicit Agent initialization—agent initialization is optional. The Elastic.Apm.Agent.IsConfigured property lets you check whether the agent is already initialized.

Implicit agent initializationedit

If you don’t explicitly initialize the agent, it will be started with a default component setup. This means the Agent will read configuration settings from environment variables. If you don’t set an environment variable, the Agent will use the default value. For example, the ServerUrls default is http://localhost:8200.

This implicit initialization of the agent happens on the first call on the Elastic.Apm.Agent class.

One exception is the Elastic.Apm.Agent.IsConfigured method. This method never initializes the agent, it only checks if the agent is already initialized.

Another example of initialization is when you enable the Agent with one of the technology-specific methods from the Set up the Agent instructions. Specifically when the UseElasticApm or UseAllElasticApm method is called in ASP.NET Core or when the IIS module is initialized in an IIS application.

The default agent setup should cover most of the use cases and the primary way to configure the agent is through environment variables.

Explicit agent initializationedit

If you would like to replace one of the agent components, you can do so by calling the Elastic.Apm.Agent.Setup(AgentComponents) method. In the AgentComponents you can pass following optional components to the agent:

  • IApmLogger: A logger implementation that will be used to print log messages. Default: A console logger.
  • IPayloadSender: A component that receives all the captured events like spans, transactions, and metrics. The default implementation serializes all events and sends them to the Elastic APM Server
  • IConfigurationReader: A component that reads agent configuration settings. The default implementation reads configuration through environment variables.

In the case of ASP.NET Core, when you register the agent, the UseElasticApm and the UseAllElasticApm methods both implicitly initialize the agent by calling the Elastic.Apm.Agent.Setup method internally. In that setup, the IConfigurationReader implementation will read configuration from the ASP.NET Core configuration system in case you pass an IConfiguration instance to the method. The IApmLogger instance will also log through the configured logging provider by integrating into the ASP.NET Core logging system.

Auto instrumentation in combination with the Public Agent APIedit

With the Elastic.ApmAgent.Subscribe(params IDiagnosticsSubscriber[] subscribers) method you can turn on auto instrumentation for supported libraries.

In the case of ASP.NET Core, when you turn on the agent with the UseAllElasticApm method, the agent will do this automatically.

With a typical console application, you need to do this manually.

IDiagnosticsSubscriber implementations are offered by the agent and they subscribe to diagnostic source events or other data sources in order to capture events automatically.

Some examples:

  • HttpDiagnosticsSubscriber: captures HTTP calls through HttpClient
  • EfCoreDiagnosticsSubscriber: captures database calls through Entity Framework Core
  • SqlClientDiagnosticSubscriber: captures database calls through SqlClient

Tracer APIedit

The tracer gives you access to the currently active transaction and it enables you to manually start a transaction.

You can access the API by using the static property on the Agent: Elastic.Apm.Agent.Tracer.

ITransaction StartTransaction(string name, string type, DistributedTracingData = null)edit

Use this method to create a custom transaction.

Note that in the case of auto-instrumentation, the agent will automatically do this for you. For example, if you have incoming HTTP calls in an ASP.NET Core application, the agent automatically starts a transaction. In these instances, this method is not needed.

It’s important to call void End() when the transaction has ended. A best practice is to use the transaction in a try-catch-finally block or to use the CaptureTransaction method.

Example:

var transaction = Elastic.Apm.Agent
        .Tracer.StartTransaction("MyTransaction", ApiConstants.TypeRequest);
try
{
    //application code that is captured as a transaction
}
catch (Exception e)
{
    transaction.CaptureException(e);
    throw;
}
finally
{
    transaction.End();
}

ITransaction CurrentTransactionedit

Returns the currently active transaction. See the Transaction API to customize the current transaction.

If there is no current transaction, this method will return null.

var transaction = Elastic.Apm.Agent.Tracer.CurrentTransaction;

ISpan CurrentSpanedit

Returns the currently active span. See the Span API to customize the current span.

If there is no current span, this method will return null.

var span = Elastic.Apm.Agent.Tracer.CurrentSpan;

CaptureTransactionedit

This is a convenient method which starts and ends a transaction and captures unhandled exceptions. It has 3 required parameters:

  • name: The name of the transaction
  • type The type of the transaction
  • One of the following types which references the code that you want to capture as a transaction:

    • Action
    • Action<ITransaction>
    • Func<T>
    • Func<ITransaction,T>
    • Func<Task>
    • Func<ITransaction,Task>
    • Func<Task<T>>
    • Func<ITransaction,Task<T>>

And an optional parameter:

  • distributedTracingData: A DistributedTracingData instance that contains the distributed tracing information in case you want the new transaction to be a part of a trace.

The following code is the equivalent of the previous example with the convenient API. It automatically starts and ends the transaction and reports unhandled exceptions. The t parameter gives you access to the ITransaction instance which represents the transaction that you just created.

Elastic.Apm.Agent.Tracer
        .CaptureTransaction("TestTransaction", ApiConstants.TypeRequest, (t) =>
{
   //application code that is captured as a transaction
});

This API also supports async methods with the Func<Task> overloads.

The duration of the transaction will be the timespan between the first and the last line of the async lambda expression.

Example:

await Elastic.Apm.Agent.Tracer
        .CaptureTransaction("TestTransaction", "TestType", async () =>
{
    //application code that is captured as a transaction
    await Task.Delay(500); //sample async code
});

Return value of CaptureTransaction method overloads that accept Task (or Task<T>) is the same Task (or Task<T>) instance as the one passed as the argument so if your application should continue only after the task completes you have to call CaptureTransaction with await keyword.

Manually propagating distributed tracing contextedit

Agent automatically propagates distributed tracing context for the supported technologies (see Networking client-side technologies). If your application communicates over a protocol that is not supported by the agent you can manually propagate distributed tracing context from the caller to the callee side using Public Agent API.

First you serialize distributed tracing context on the caller side:

string outgoingDistributedTracingData =
    (Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData
        ?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData)?.SerializeToString();

Then you transfer the resulted string to the callee side and you continue the trace by passing deserialized distributed tracing context to any of ITransaction StartTransaction(string name, string type, DistributedTracingData = null) or CaptureTransaction APIs - all of these APIs have an optional DistributedTracingData parameter. For example:

var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction",
     DistributedTracingData.TryDeserializeFromString(serializedDistributedTracingData));

Transaction APIedit

A transaction describes an event captured by an Elastic APM agent monitoring a service. Transactions help combine multiple Spans into logical groups, and they are the first Span of a service. More information on Transactions and Spans is available in the APM data model documentation.

See ITransaction CurrentTransaction on how to get a reference of the current transaction.

Calling any of the transaction’s methods after void End() has been called is illegal. You may only interact with a transaction when you have control over its lifecycle.

ISpan StartSpan(string name, string type, string subType = null, string action = null)edit

Start and return a new custom span as a child of the given transaction.

It is important to call void End() when the span has ended or to use the CaptureSpan method. A best practice is to use the span in a try-catch-finally block.

Example:

ISpan span = transaction.StartSpan("Select FROM customer",
     ApiConstants.TypeDb, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);
try
{
    //execute db query
}
catch(Exception e)
{
    span.CaptureException(e);
    throw;
}
finally
{
    span.End();
}

Dictionary<string,string> Labelsedit

A flat mapping of user-defined labels with string values.

If the key contains any special characters (.,*, "), they will be replaced with underscores. For example a.b will be stored as a_b.

Before using custom labels, ensure you understand the different types of metadata that are available.

Avoid defining too many user-specified labels. Defining too many unique fields in an index is a condition that can lead to a mapping explosion.

Agent.Tracer
 .CaptureTransaction(TransactionName, TransactionType,
    transaction =>
    {
        transaction.Labels["foo"] = "bar";
        //application code that is captured as a transaction
    });
  • key: The label key
  • value: The label value

void End()edit

Ends the transaction and schedules it to be reported to the APM Server.

It is illegal to call any methods on a span instance which has already ended. This also includes this method and ISpan StartSpan(string name, string type, string subType = null, string action = null).

Example:

transaction.End();

If you use the CaptureTransaction method you must not call void End().

void CaptureException(Exception e)edit

Captures an exception and reports it to the APM server.

void CaptureError(string message, string culprit, StackFrame[] frames)edit

Captures a custom error and reports it to the APM server.

This method is typically used when you want to report an error, but you don’t have an Exception instance.

CaptureSpanedit

This is a convenient method which starts and ends a span on the given transaction and captures unhandled exceptions. It has the same overloads as the CaptureTransaction method.

It has 3 required parameters:

  • name: The name of the span
  • type The type of the span
  • One of the following types which references the code that you want to capture as a transaction:

    • Action
    • Action<ITransaction>
    • Func<T>
    • Func<ITransaction,T>
    • Func<Task>
    • Func<ITransaction,Task>
    • Func<Task<T>>
    • Func<ITransaction,Task<T>>

and 2 optional parameters:

  • supType: The subtype of the span
  • action: The action of the span

The following code is the equivalent of the previous example from the ISpan StartSpan(string name, string type, string subType = null, string action = null) section with the convenient API. It automatically starts and ends the span and reports unhandled exceptions. The s parameter gives you access to the ISpan instance which represents the span that you just created.

ITransaction transaction = Elastic.Apm.Agent.Tracer.CurrentTransaction;

transaction.CaptureSpan("SampleSpan", ApiConstants.TypeDb, (s) =>
{
    //execute db query
}, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);

Similar to the CaptureTransaction API, this method also supports async methods with the Func<Task> overloads.

The duration of the span will be the timespan between the first and the last line of the async lambda expression.

This example shows you how to track an async code block that returns a result (Task<T>) as a span:

ITransaction transaction = Elastic.Apm.Agent.Tracer.CurrentTransaction;
var asyncResult = await transaction.CaptureSpan("Select FROM customer", ApiConstants.TypeDb, async(s) =>
{
    //application code that is captured as a span
    await Task.Delay(500); //sample async code
    return 42;
});

Return value of CaptureSpan method overloads that accept Task (or Task<T>) is the same Task (or Task<T>) instance as the one passed as the argument so if your application should continue only after the task completes you have to call CaptureSpan with await keyword.

Code samples above use Elastic.Apm.Agent.Tracer.CurrentTransaction. In production code you should make sure the CurrentTransaction is not null.

EnsureParentIdedit

If the transaction does not have a ParentId yet, calling this method generates a new ID, sets it as the ParentId of this transaction, and returns it as a string.

This enables the correlation of the spans the JavaScript Real User Monitoring (RUM) agent creates for the initial page load with the transaction of the backend service.

If your service generates the HTML page dynamically, initializing the JavaScript RUM agent with the value of this method allows analyzing the time spent in the browser vs in the backend services.

To enable the JavaScript RUM agent in ASP.NET Core, initialize the RUM agent with the .NET agent’s current transaction:

<script>
	elasticApm.init({
		serviceName: 'MyService',
		serverUrl: 'http://localhost:8200',
		pageLoadTraceId: '@Elastic.Apm.Agent.Tracer.CurrentTransaction?.TraceId',
		pageLoadSpanId: '@Elastic.Apm.Agent.Tracer.CurrentTransaction?.EnsureParentId()',
		pageLoadSampled: @Json.Serialize(Elastic.Apm.Agent.Tracer?.CurrentTransaction.IsSampled)
		})
</script>

See the JavaScript RUM agent documentation for more information.

Dictionary<string,string> Customedit

Custom context is used to add non-indexed, custom contextual information to transactions. Non-indexed means the data is not searchable or aggregatable in Elasticsearch, and you cannot build dashboards on top of the data. However, non-indexed information is useful for other reasons, like providing contextual information to help you quickly debug performance issues or errors.

If the key contains any special characters (.,*, "), they will be replaced with underscores. For example a.b will be stored as a_b.

Unlike Dictionary<string,string> Labels, the data in this property is not trimmed.

Agent.Tracer.CaptureTransaction(transactionName, transactionType, (transaction) =>
{
	transaction.Custom["foo"] = "bar";
	transaction.End();
});

Contextedit

You can attach additional context to manually captured transactions.

If you use a web framework for which agent doesn’t capture transactions automatically (see Web frameworks), you can add context related to the captured transaction by setting various properties of transaction’s Context property. For example:

Agent.Tracer.CaptureTransaction("MyCustomTransaction",ApiConstants.TypeRequest, (transaction) =>
{
  transaction.Context.Request = new Request(myRequestMethod, myRequestUri);

  // ... code executing the request

  transaction.Context.Response =
     new Response { StatusCode = myStatusCode, Finished = wasFinished };
});

Span APIedit

A span contains information about a specific code path, executed as part of a transaction.

If for example a database query happens within a recorded transaction, a span representing this database query may be created. In such a case, the name of the span will contain information about the query itself, and the type will hold information about the database type.

ISpan StartSpan(string name, string type, string subType = null, string action = null)edit

Start and return a new custom span as a child of the given span. Very similar to the ISpan StartSpan(string name, string type, string subType = null, string action = null) method on ITransaction, but in this case the parent of the newly created span is a span itself.

It is important to call void End() when the span has ended or to use the CaptureSpan method. A best practice is to use the span in a try-catch-finally block.

Example:

ISpan childSpan = parentSpan.StartSpan("Select FROM customer",
     ApiConstants.TypeDb, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);
try
{
    //execute db query
}
catch(Exception e)
{
    childSpan?.CaptureException(e);
    throw;
}
finally
{
    childSpan?.End();
}

Dictionary<string,string> Labelsedit

Similar to Dictionary<string,string> Labels on the Transaction API: A flat mapping of user-defined labels with string values.

If the key contains any special characters (.,*, "), they will be replaced with underscores. For example a.b will be stored as a_b.

Before using custom labels, ensure you understand the different types of metadata that are available.

Avoid defining too many user-specified labels. Defining too many unique fields in an index is a condition that can lead to a mapping explosion.

transaction.CaptureSpan(SpanName, SpanType,
span =>
    {
        span.Labels["foo"] = "bar";
        //application code that is captured as a span
    });

void CaptureException(Exception e)edit

Captures an exception and reports it to the APM server.

void CaptureError(string message, string culprit, StackFrame[] frames)edit

Captures a custom error and reports it to the APM server.

This method is typically used when you want to report an error, but you don’t have an Exception instance.

void End()edit

Ends the span and schedules it to be reported to the APM Server.

It is illegal to call any methods on a span instance which has already ended.

Contextedit

You can attach additional context to manually captured spans.

If you use a database library for which agent doesn’t capture spans automatically (see Data access technologies), you can add context related to the captured database operation by setting span’s Context.Db property. For example:

Agent.Tracer.CurrentTransaction.CaptureSpan("MyDbWrite", ApiConstants.TypeDb, (span) =>
{
    span.Context.Db = new Database
        { Statement = myDbStatement, Type = myDbType, Instance = myDbInstance };

    // ... code executing the database operation
});

If you use an HTTP library for which agent doesn’t capture spans automatically (see Networking client-side technologies), you can add context related to the captured HTTP operation by setting span’s Context.Http property. For example:

Agent.Tracer.CurrentTransaction.CaptureSpan("MyHttpOperation", ApiConstants.TypeExternal, (span) =>
{
    span.Context.Http = new Http
        { Url = myUrl, Method = myMethod };

    // ... code executing the HTTP operation

    span.Context.Http.StatusCode = myStatusCode;
});

CaptureSpanedit

This is a convenient method which starts and ends a child span on the given span and captures unhandled exceptions.

Very similar to the CaptureSpan method on ITransaction, but in this case the parent of the newly created span is a span itself.

It has 3 required parameters:

  • name: The name of the span
  • type The type of the span
  • One of the following types which references the code that you want to capture as a transaction:

    • Action
    • Action<ITransaction>
    • Func<T>
    • Func<ITransaction,T>
    • Func<Task>
    • Func<ITransaction,Task>
    • Func<Task<T>>
    • Func<ITransaction,Task<T>>

and 2 optional parameters:

  • supType: The subtype of the span
  • action: The action of the span

The following code is the equivalent of the previous example from the ISpan StartSpan(string name, string type, string subType = null, string action = null) section with the convenient API. It automatically starts and ends the span and reports unhandled exceptions. The s parameter gives you access to the ISpan instance which represents the span that you just created.

span.CaptureSpan("SampleSpan", ApiConstants.TypeDb, (s) =>
{
    //execute db query
}, ApiConstants.SubtypeMssql, ApiConstants.ActionQuery);

Similar to the CaptureTransaction API, this method also supports async methods with the Func<Task> overloads.

The duration of the span will be the timespan between the first and the last line of the async lambda expression.

This example shows you how to track an async code block that returns a result (Task<T>) as a span:

var asyncResult = await span.CaptureSpan("Select FROM customer", ApiConstants.TypeDb, async(s) =>
{
    //application code that is captured as a span
    await Task.Delay(500); //sample async code
    return 42;
});

Return value of CaptureSpan method overloads that accept Task (or Task<T>) is the same Task (or Task<T>) instance as the one passed as the argument so if your application should continue only after the task completes you have to call CaptureSpan with await keyword.

Code samples above use Elastic.Apm.Agent.Tracer.CurrentTransaction. In production code you should make sure the CurrentTransaction is not null.

Filter API ( [1.5] Added in 1.5. )edit

Use Agent.AddFilter(filter) to supply a filter callback.

Each filter callback will be called just before data is sent to the APM Server. This allows you to manipulate the data being sent, like to remove sensitive information such as passwords.

Each filter callback is called in the order they are added and will receive a payload object containing the data about to be sent to the APM Server as the only argument.

The filter callback is synchronous and should return the manipulated payload object. If a filter callback doesn’t return any value or returns a falsy value, the remaining filter callback will not be called and the payload will not be sent to the APM Server.

There are 3 overloads of the Agent.AddFilter method with the following arguments:

  • Func<ITransaction, ITransaction>: A filter called for every transaction.
  • Func<ISpan, ISpan>: A filter called for every span.
  • Func<IError, IError>: A filter called for every error.

Below are some usage examples of the Agent.AddFilter method.

Drop all spans for a specific database:

Agent.AddFilter((ISpan span) =>
{
	if (span.Context?.Db?.Instance == "VerySecretDb")
		return null;
	return span;
});

Hide some data:

Agent.AddFilter((ITransaction transaction) =>
{
	transaction.Context.Request.Url.Protocol = "[HIDDEN]";
	return transaction;
});