LogTide

C# / .NET SDK

Official .NET SDK for LogTide with automatic batching, retry logic, circuit breaker, W3C distributed tracing, span tracking, breadcrumbs, Serilog sink, and ASP.NET Core middleware support.

Installation

.NET CLI

dotnet add package LogTide.SDK

Serilog Integration (optional)

dotnet add package LogTide.SDK.Serilog

Quick Start

using LogTide.SDK.Core;
using LogTide.SDK.Models;

// Create client with DSN
await using var client = LogTideClient.FromDsn("https://[email protected]");

// Send logs
client.Info("api-gateway", "Server started", new() { ["port"] = 3000 });
client.Error("database", "Connection failed", new Exception("Timeout"));

// Scoped tracing — all logs get the same W3C trace ID
using (var scope = LogTideScope.Create())
{
    client.Info("api", "Request received");
    client.Info("db", "Query executed");
}

Features

  • ✅ Automatic batching with configurable size and interval
  • ✅ Retry logic with exponential backoff
  • ✅ Circuit breaker pattern for fault tolerance
  • ✅ W3C traceparent distributed tracing
  • ✅ Span tracking with OTLP-compatible export
  • ✅ AsyncLocal scope for ambient trace context
  • ✅ Breadcrumbs for event tracking within scopes
  • ✅ Composable transport layer
  • ✅ Integration system (global error handler)
  • ✅ Serilog sink (LogTide.SDK.Serilog)
  • ✅ DSN connection string support
  • ✅ ASP.NET Core middleware with sensitive header filtering
  • ILogTideClient interface for DI and testability
  • ✅ Query API for searching and filtering logs
  • ✅ Full async/await support, thread-safe

Configuration

// Option 1: DSN (simplest)
await using var client = LogTideClient.FromDsn("https://[email protected]");

// Option 2: Full configuration
await using var client = LogTideClient.FromDsn("https://[email protected]", new ClientOptions
{
    ServiceName = "my-api",         // Service name for tracing (default: "app")

    // Batching
    BatchSize = 100,                // Max logs per batch (default: 100)
    FlushIntervalMs = 5000,         // Flush interval in ms (default: 5000)

    // Buffer management
    MaxBufferSize = 10000,          // Max logs in buffer (default: 10000)

    // Retry with exponential backoff (1s -> 2s -> 4s)
    MaxRetries = 3,                 // Max retry attempts (default: 3)
    RetryDelayMs = 1000,            // Initial retry delay (default: 1000)

    // Circuit breaker
    CircuitBreakerThreshold = 5,    // Failures before circuit opens (default: 5)
    CircuitBreakerResetMs = 30000,  // Circuit reset timeout (default: 30000)

    // Tracing
    TracesSampleRate = 1.0,         // Sample rate 0.0-1.0 (default: 1.0)

    // Integrations
    Integrations = [new GlobalErrorIntegration()],

    // Global context
    GlobalMetadata = new()
    {
        ["env"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
        ["version"] = "1.0.0"
    },

    Debug = false                   // Enable debug logging (default: false)
});

Logging Methods

Basic Logging

client.Debug("service-name", "Debug message");
client.Info("service-name", "Info message", new() { ["userId"] = 123 });
client.Warn("service-name", "Warning message");
client.Error("service-name", "Error message", new() { ["custom"] = "data" });
client.Critical("service-name", "Critical message");

Error Logging with Auto-Serialization

try
{
    throw new InvalidOperationException("Database timeout");
}
catch (Exception ex)
{
    // Automatically serializes error with stack trace
    client.Error("database", "Query failed", ex);
}

Distributed Tracing

Track requests across services with W3C traceparent compliant trace IDs using LogTideScope.

AsyncLocal Scope

// All logs within the scope share the same trace ID
using (var scope = LogTideScope.Create())
{
    client.Info("api", "Request received");     // auto trace ID
    client.Info("database", "Querying users");  // same trace ID
    client.Info("api", "Response sent");
}

// Or pass an existing trace ID
using (var scope = LogTideScope.Create("4bf92f3577b34da6a3ce929d0e0e4736"))
{
    client.Info("api", "Processing with known trace");
}

Nested Scopes

using var outer = LogTideScope.Create("outer-trace");
client.Info("api", "Outer scope");

using (var inner = LogTideScope.Create("inner-trace"))
{
    client.Info("api", "Inner scope"); // trace = "inner-trace"
}
// Automatically restored to "outer-trace"

Span Tracking

Track operations with spans, exported in OTLP format.

using var scope = LogTideScope.Create();

var span = client.StartSpan("process-order");
span.SetAttribute("order.id", "ORD-123");
span.SetAttribute("http.method", "POST");

// ... do work ...

span.AddEvent("validation-complete");

client.FinishSpan(span, SpanStatus.Ok);

Track user actions and events within a scope. Breadcrumbs are automatically attached to logs as metadata.

using var scope = LogTideScope.Create();

client.AddBreadcrumb(new Breadcrumb { Message = "User clicked button", Type = "ui" });
client.AddBreadcrumb(new Breadcrumb { Message = "API call started", Type = "http" });

// Breadcrumbs are automatically included in log metadata
client.Error("app", "Something failed");

Query API

Basic Query

var result = await client.QueryAsync(new QueryOptions
{
    Service = "api-gateway",
    Level = LogLevel.Error,
    From = DateTime.UtcNow.AddDays(-1),
    To = DateTime.UtcNow,
    Limit = 100
});

foreach (var log in result.Logs)
{
    Console.WriteLine($"{log.Time}: {log.Message}");
}

Get Logs by Trace ID

var logs = await client.GetByTraceIdAsync("4bf92f3577b34da6a3ce929d0e0e4736");

Metrics

var metrics = client.GetMetrics();

Console.WriteLine($"Logs sent: {metrics.LogsSent}");
Console.WriteLine($"Logs dropped: {metrics.LogsDropped}");
Console.WriteLine($"Errors: {metrics.Errors}");
Console.WriteLine($"Avg latency: {metrics.AvgLatencyMs}ms");
Console.WriteLine($"Circuit state: {client.GetCircuitBreakerState()}");

ASP.NET Core Integration

Setup with Dependency Injection

// Program.cs
using LogTide.SDK.Core;
using LogTide.SDK.Middleware;
using LogTide.SDK.Models;

var builder = WebApplication.CreateBuilder(args);

// Register LogTide with IHttpClientFactory
builder.Services.AddLogTide(new ClientOptions
{
    ApiUrl = builder.Configuration["LogTide:ApiUrl"]!,
    ApiKey = builder.Configuration["LogTide:ApiKey"]!,
    ServiceName = "my-api",
    GlobalMetadata = new()
    {
        ["env"] = builder.Environment.EnvironmentName
    }
});

var app = builder.Build();

// Catch unhandled exceptions
app.UseLogTideErrors();

// Auto-log HTTP requests with W3C traceparent
app.UseLogTide(o => o.ServiceName = "my-api");

app.MapGet("/", () => "Hello World!");
app.Run();

Middleware Features

  • ✅ Parses incoming W3C traceparent headers
  • ✅ Creates LogTideScope per request
  • ✅ Starts and finishes a span per request
  • ✅ Emits traceparent response header
  • ✅ Filters sensitive headers (Authorization, Cookie, X-API-Key)
  • ✅ Skips health check endpoints

Using in Controllers

[ApiController]
[Route("[controller]")]
public class WeatherController : ControllerBase
{
    private readonly ILogTideClient _logger;

    public WeatherController(ILogTideClient logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {
        _logger.Info("weather-api", "Fetching weather data");

        try
        {
            return Ok(new { Temperature = 25 });
        }
        catch (Exception ex)
        {
            _logger.Error("weather-api", "Failed to fetch weather", ex);
            throw;
        }
    }
}

Serilog Integration

Forward Serilog events to LogTide with the LogTide.SDK.Serilog package.

using LogTide.SDK.Core;
using LogTide.SDK.Serilog;
using Serilog;

await using var logtideClient = LogTideClient.FromDsn("https://[email protected]");

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.LogTide(logtideClient, serviceName: "my-service")
    .CreateLogger();

// Use Serilog as normal — logs are forwarded to LogTide
Log.Information("User {UserId} logged in from {IpAddress}", 42, "192.168.1.1");

Best Practices

1. Always Use await using
Always dispose the client with await using to ensure buffered logs are flushed before shutdown.
await using var client = LogTideClient.FromDsn("https://[email protected]");
// logs are flushed when client is disposed
2. Use Global Metadata
Set global metadata (environment, version, hostname) at initialization to avoid repeating it in every log call.
3. Use Scopes for Tracing
Wrap request handlers with LogTideScope.Create() to automatically correlate all logs within the same request. The middleware does this for you in ASP.NET Core.
4. Monitor Metrics in Production
Check client.GetMetrics() periodically. Watch for LogsDropped > 0 or CircuitBreakerState == Open.

Supported Frameworks

  • .NET 8.0
  • .NET 9.0
Esc

Type to search across all documentation pages