Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Splunk.Extensions.Logging

A Microsoft.Extensions.Logging provider that sends log events to Splunk.

Usage

builder.Logging.AddSplunk(o =>
{
    o.Endpoint = new Uri("https://splunk.example.com:8088");
    o.Token = "<HEC token>";
    o.Index = "main";
    o.Source = "my-app";
});

Events are queued, batched and sent to the HTTP Event Collector (/services/collector/event) on a background task. Pending events are flushed when the provider is disposed.

Retries

By default the provider creates its own HttpClient with a RetryDelegatingHandler that retries failed requests (exceptions and non-success status codes) up to MaxRetries times (default 3), starting with RetryDelay (default 1 second) and doubling it on each retry. Batches that still fail are dropped and reported through OnError.

Custom HttpClient

Use the overload that receives the IServiceProvider to supply your own HttpClient, for example one created by IHttpClientFactory. The Authorization: Splunk <token> header is added from Token unless the client already has one. The provider never disposes the client, and MaxRetries/RetryDelay are not applied to it.

builder.Services.AddHttpClient("splunk");

builder.Logging.AddSplunk((sp, o) =>
{
    o.Endpoint = new Uri("https://splunk.example.com:8088");
    o.Token = "<HEC token>";
    o.HttpClient = sp.GetRequiredService<IHttpClientFactory>().CreateClient("splunk");
});

Retries with Polly

To use Polly instead of the built-in retries, add a resilience handler to the named client (package Microsoft.Extensions.Http.Resilience, or Microsoft.Extensions.Http.Polly for the legacy API):

builder.Services.AddHttpClient("splunk")
    .AddResilienceHandler("splunk", pipeline => pipeline
        .AddRetry(new HttpRetryStrategyOptions
        {
            MaxRetryAttempts = 5,
            Delay = TimeSpan.FromSeconds(1),
            BackoffType = DelayBackoffType.Exponential,
            UseJitter = true,
        })
        .AddTimeout(TimeSpan.FromSeconds(30)));

AddStandardResilienceHandler() is a ready-made alternative (retry, circuit breaker and timeouts). Then pass the client through o.HttpClient as shown above.

Event format

Events follow the layout produced by the splunk-otel-collector HEC exporter: the formatted message is the event, and all other data is flattened into fields.

{
  "time": 1760135438.834,
  "host": "my-host",
  "source": "my-app",
  "index": "main",
  "event": "Hello world",
  "fields": {
    "otel.log.severity.text": "Warning",
    "otel.log.severity.number": 13,
    "scope.name": "MyApp.Category",
    "logrecord.event.id": 5,
    "otel.log.name": "Evt",
    "exception.type": "InvalidOperationException",
    "exception.message": "boom",
    "exception.stacktrace": "...",
    "trace_id": "...",
    "span_id": "...",
    "Name": "world"
  }
}

Mapping

Source (MEL) HEC location
Formatted message event
Time of logging (UTC) time (epoch seconds)
SplunkLoggerOptions.Host / Source / SourceType / Index host / source / sourcetype / index
LogLevel fields["otel.log.severity.text"]: Trace=Trace, Debug=Debug, Information=Information, Warning=Warning, Error=Error, Critical=Critical
LogLevel fields["otel.log.severity.number"]: 1, 5, 9, 13, 17, 21 respectively
Category name fields["scope.name"]
EventId.Id (when non-zero) fields["logrecord.event.id"]
EventId.Name fields["otel.log.name"]
Exception fields["exception.type"], ["exception.message"], ["exception.stacktrace"]
Activity.Current fields["trace_id"], fields["span_id"]
Structured message properties fields[<name>] (including {OriginalFormat})
Resource attributes fields["service.name"] (ServiceName option, default unknown_service:<process>), fields["telemetry.sdk.name"]/language/version
Scopes (key/value pairs, when IncludeScopes is on) fields[<name>]; non-structured scopes are ignored

Property value formatting

Strings, booleans and finite numbers keep their JSON types. Everything else (including NaN/Infinity, Guid, dates, collections and custom types) is written as its invariant ToString() text by default.

Set SerializeComplexValues = true to serialize every property and scope value (primitives, enums, Guid, collections, dictionaries, custom types, ...) as JSON with SplunkLoggerOptions.JsonSerializerOptions. By default these options allow named floating point literals ("NaN", "Infinity"). The options' Encoder (default UnsafeRelaxedJsonEscaping) and writer settings (WriteIndented, IndentCharacter, IndentSize, NewLine, MaxDepth) are also used for the event itself. If serializing an event fails, the event is dropped and OnError is called. Reflection-based serialization is not trim/AOT-safe; supply a source-generated TypeInfoResolver in the options for such apps.

Integration tests

tests/Splunk.Extensions.Logging.IntegrationTests sends the same log calls through this provider (directly to HEC) and through the OpenTelemetry .NET logger (OTLP -> splunk-otel-collector -> HEC), then queries Splunk and compares the indexed events. They are skipped when Splunk is not running.

docker compose -f tests/integration/docker-compose.yml up -d   # Splunk takes ~1-2 minutes to become healthy
dotnet test tests/Splunk.Extensions.Logging.IntegrationTests
docker compose -f tests/integration/docker-compose.yml down

Event text, severity, exception, properties, scopes and otel.log.name are identical. Intentional differences: this provider also emits scope.name and logrecord.event.id, sets host to the machine name, and reports its own telemetry.sdk.version.

Solution Structure

Project Path Purpose
Splunk.Extensions.Logging src/Splunk.Extensions.Logging/ Main library
Splunk.Extensions.Logging.Tests tests/Splunk.Extensions.Logging.Tests/ Unit tests

Build & Test

# Build the solution
dotnet build

# Run all tests
dotnet test

License

MIT, see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages