Skip to main content

.NET

Overview​

The .NET OpenTelemetry SDK is built around Microsoft.Extensions.Logging (MEL), so any logging framework that targets MEL — ILogger<T>, Serilog with the MEL bridge, NLog — flows through to OTLP/HTTP without extra plumbing.

TopologyWhen to use it
OTel SDK direct (this page)The default. Up to a few hundred events / second / process. Simplest setup — no extra hop, no second process.
OTel SDK → local collectorMany services on a node, multi-backend fan-out, central config / secret management, queue-on-outage durability, sampling or redaction. See the OTel SDKs in production guide.
Log to file + agentLanguages without a stable OTel logs SDK, very high throughput, air-gapped environments, or container runtimes that already capture stdout. See the operating-systems page.

Prerequisites​

You need the following before you start:

  • Data region — United States (us), Canada (ca), Europe (eu), United Kingdom (uk), or Australia (au). The app dashboard displays the region of your workspace.
  • Ingest Key ID — short identifier for the Ingest Key that will send these logs.
  • Ingest Key access token — bearer credential for the Ingest Key.

View or create an Ingest Key in the SparkLogs app under Configure → Ingest Keys. Each Ingest Key has its own ID and access token; revoke or rotate either at any time without restarting your application.

Install​

dotnet add package OpenTelemetry.Extensions.Hosting --version 1.15.3
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.15.3

Configure the OTel exporter​

Most OTel SDKs read exporter configuration from environment variables. Set these before your app starts:

export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="https://ingest-us.engine.sparklogs.app/v1/logs"
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer <INGEST-KEY-ID>:<INGEST-KEY-ACCESS-TOKEN>"
export OTEL_EXPORTER_OTLP_LOGS_COMPRESSION=gzip
export OTEL_EXPORTER_OTLP_LOGS_TIMEOUT=25000
tip

Set <INGEST-KEY-ID> and <INGEST-KEY-ACCESS-TOKEN> from Configure → Ingest Keys. Choose the region based on the location of your provisioned workspace.

Other OTLP receivers. The variables above are the standard OpenTelemetry logs exporter settings. Point OTEL_EXPORTER_OTLP_LOGS_ENDPOINT and OTEL_EXPORTER_OTLP_LOGS_HEADERS at SparkLogs as shown, at a local OpenTelemetry Collector (for example http://localhost:4318/v1/logs), or at any OTLP/HTTP-compatible receiver. Swap only the URL and auth headers your target expects; keep OTEL_EXPORTER_OTLP_LOGS_PROTOCOL aligned with what that endpoint accepts.

Why set OTEL_EXPORTER_OTLP_LOGS_TIMEOUT? The OTel default is 10s, and on rare occasion our cloud may delay a request up to 12 seconds (p99.99 latency). 25s leaves headroom for rare request latency and network delays.

Compression. gzip is recommended and what most users should use. CPU-constrained workloads can set OTEL_EXPORTER_OTLP_LOGS_COMPRESSION=none to send uncompressed — SparkLogs does not bill for inbound bytes, so the trade-off is purely network-vs-CPU on your side. See the scaling guide for the full list and important SDK-vs-wire-protocol differences.

Batching. The OTel SDK's BatchLogRecordProcessor defaults (max queue 2048, max batch 512, 1s schedule delay, 30s export timeout) are production-appropriate for most workloads. Higher-throughput pipelines may want to tune them — see the scaling guide.

Set up the OTel SDK​

In a generic-host or ASP.NET Core app, register OTel logging on the host builder:

using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

var builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddOpenTelemetry(options =>
{
options
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(serviceName: "my-service", serviceVersion: "1.0.0")
.AddAttributes(new Dictionary<string, object> {
["deployment.environment"] = "production"
}))
.AddOtlpExporter(); // reads OTEL_EXPORTER_OTLP_LOGS_* from environment
options.IncludeFormattedMessage = true;
options.IncludeScopes = true;
options.ParseStateValues = true;
});

var host = builder.Build();
host.Run();

AddOtlpExporter() defaults to BatchExportProcessor (the .NET equivalent of BatchLogRecordProcessor). Don't switch to SimpleExportProcessor in production.

Set resource attributes​

SparkLogs derives the searchable source, service, and app pivot fields from your OpenTelemetry Resource attributes. Setting these correctly means your events arrive grouped, filterable, and indexed without further configuration:

  • service.name — the logical service identity (e.g. checkout, auth-api). Maps to the service field.
  • service.version — the version / build of the running service (e.g. 1.42.0, abc123def).
  • deployment.environment — the environment label (e.g. production, staging, development). Maps to the app field.

Most OTel SDKs accept these via the OTEL_RESOURCE_ATTRIBUTES environment variable as a comma-separated list:

export OTEL_RESOURCE_ATTRIBUTES="service.name=my-service,service.version=1.0.0,deployment.environment=production"

The SDK setup snippets below show the in-code equivalent for each language.

For the full mapping (including container, host, and Kubernetes attributes that derive source), see OTLP/HTTP API → Resource attributes.

Integrate the OTel SDK with your logging library​

Option 1: Microsoft.Extensions.Logging (default)

With OTel registered on builder.Logging as shown above, every ILogger<T> injected into your services flows through OTel automatically:

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

public CheckoutService(ILogger<CheckoutService> logger) => _logger = logger;

public void Process(int orderId)
{
_logger.LogInformation("processing order {OrderId}", orderId);
try
{
// …
}
catch (Exception ex)
{
_logger.LogError(ex, "checkout failed for {OrderId}", orderId);
}
}
}

Structured properties ({OrderId}) and exception info are preserved as searchable fields on the OTel log record.

Option 2: Serilog

Two ways to wire Serilog through OTel:

A. Serilog → MEL → OTel (recommended for hosted apps). Configure Serilog as the MEL provider, and OTel's AddOpenTelemetry() on builder.Logging picks it up:

builder.Host.UseSerilog((ctx, config) => config
.ReadFrom.Configuration(ctx.Configuration)
.Enrich.FromLogContext());

builder.Logging.AddOpenTelemetry(options => options.AddOtlpExporter());

B. Serilog OTLP sink (direct, for non-hosted apps). Use Serilog.Sinks.OpenTelemetry (pin 4.1.1 to match sparklogs-otel-serilog):

dotnet add package Serilog.Sinks.OpenTelemetry --version 4.1.1
Log.Logger = new LoggerConfiguration()
.WriteTo.OpenTelemetry(options =>
{
options.Endpoint = "https://ingest-us.engine.sparklogs.app/v1/logs";
options.Protocol = OtlpProtocol.HttpProtobuf;
options.Headers = new Dictionary<string, string> {
["Authorization"] = "Bearer <INGEST-KEY-ID>:<INGEST-KEY-ACCESS-TOKEN>"
};
options.ResourceAttributes = new Dictionary<string, object> {
["service.name"] = "my-service",
["service.version"] = "1.0.0",
["deployment.environment"] = "production",
};
})
.CreateLogger();
Option 3: NLog

Use NLog.Extensions.Logging to bridge NLog to MEL, then OTel picks it up the same way as Option 1. Pin NLog / NLog.Extensions.Logging to 5.3.4 / 5.3.14 if you want to match sparklogs-otel-nlog.

dotnet add package NLog --version 5.3.4
dotnet add package NLog.Extensions.Logging --version 5.3.14
builder.Logging.ClearProviders();
builder.Logging.AddNLog();
builder.Logging.AddOpenTelemetry(options => options.AddOtlpExporter());

Flush on shutdown​

In a hosted app, the OTel SDK flushes automatically on IHost.StopAsync(). For console apps:

using var host = builder.Build();
await host.RunAsync();
// At process exit, IHost.Dispose() flushes the OTel logger provider.

If you build the SDK manually with Sdk.CreateLoggerProviderBuilder(), dispose the resulting provider:

loggerProvider.Dispose();

See graceful shutdown.

Runnable examples​

Tested examples (no cloud credentials)

The public sparklogs-ingest-examples repo includes matching projects for this page. In each project directory, run make mock-test to send OTLP batches to a local mock receiver (no SparkLogs agent token required). Use make test with agent credentials when you want to verify against a real workspace.

Tested projects in sparklogs-ingest-examples:

Frequently asked questions​

Where to next​

  • Production deployments — batching, queue tuning, backpressure, when to add a collector, graceful shutdown: see OTel SDKs in production.
  • OTLP/HTTP transport details — full encoding / compression / auth / retry status code reference: see the OTLP/HTTP API page.
  • Add an OTel Collector — when one of your services on a node should aggregate, sample, redact, or fan out to multiple backends: see the OpenTelemetry Collector guide.
  • Log to a file + agent — for very high throughput, languages without a stable OTel logs SDK, or container runtimes that already capture stdout: see operating-system agents.