Official .NET SDK for LogTide with automatic batching, retry logic, circuit breaker, query API, distributed tracing, and ASP.NET Core middleware support.
- Automatic batching with configurable size and interval
- Retry logic with exponential backoff
- Circuit breaker pattern for fault tolerance
- Max buffer size with drop policy to prevent memory leaks
- Query API for searching and filtering logs
- Trace ID context for distributed tracing
- Global metadata added to all logs
- Structured error serialization
- Internal metrics (logs sent, errors, latency, etc.)
- ASP.NET Core middleware for auto-logging HTTP requests
- Dependency injection support
- Full async/await support
- Thread-safe
- .NET 6.0, 7.0, or 8.0
dotnet add package LogTide.SDKOr via Package Manager:
Install-Package LogTide.SDKusing LogTide.SDK;
using LogTide.SDK.Models;
var client = new LogTideClient(new ClientOptions
{
ApiUrl = "http://localhost:8080",
ApiKey = "lp_your_api_key_here"
});
// Send logs
client.Info("api-gateway", "Server started", new() { ["port"] = 3000 });
client.Error("database", "Connection failed", new Exception("Timeout"));
// Graceful shutdown
await client.DisposeAsync();| Option | Type | Default | Description |
|---|---|---|---|
ApiUrl |
string |
required | Base URL of your LogTide instance |
ApiKey |
string |
required | Project API key (starts with lp_) |
BatchSize |
int |
100 |
Number of logs to batch before sending |
FlushIntervalMs |
int |
5000 |
Interval in ms to auto-flush logs |
| Option | Type | Default | Description |
|---|---|---|---|
MaxBufferSize |
int |
10000 |
Max logs in buffer (prevents memory leak) |
MaxRetries |
int |
3 |
Max retry attempts on failure |
RetryDelayMs |
int |
1000 |
Initial retry delay (exponential backoff) |
CircuitBreakerThreshold |
int |
5 |
Failures before opening circuit |
CircuitBreakerResetMs |
int |
30000 |
Time before retrying after circuit opens |
EnableMetrics |
bool |
true |
Track internal metrics |
Debug |
bool |
false |
Enable debug logging to console |
GlobalMetadata |
Dictionary |
{} |
Metadata added to all logs |
AutoTraceId |
bool |
false |
Auto-generate trace IDs for logs |
HttpTimeoutSeconds |
int |
30 |
HTTP request timeout |
var client = new LogTideClient(new ClientOptions
{
ApiUrl = "http://localhost:8080",
ApiKey = "lp_your_api_key_here",
// Batching
BatchSize = 100,
FlushIntervalMs = 5000,
// Buffer management
MaxBufferSize = 10000,
// Retry with exponential backoff (1s -> 2s -> 4s)
MaxRetries = 3,
RetryDelayMs = 1000,
// Circuit breaker
CircuitBreakerThreshold = 5,
CircuitBreakerResetMs = 30000,
// Metrics & debugging
EnableMetrics = true,
Debug = true,
// Global context
GlobalMetadata = new()
{
["env"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
["version"] = "1.0.0",
["hostname"] = Environment.MachineName
},
// Auto trace IDs
AutoTraceId = false
});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");The SDK automatically serializes exceptions:
try
{
throw new InvalidOperationException("Database timeout");
}
catch (Exception ex)
{
// Automatically serializes error with stack trace
client.Error("database", "Query failed", ex);
}Generated log metadata:
{
"error": {
"name": "InvalidOperationException",
"message": "Database timeout",
"stack": "at Program.Main() in ..."
}
}Track requests across services with trace IDs.
client.SetTraceId("request-123");
client.Info("api", "Request received");
client.Info("database", "Querying users");
client.Info("api", "Response sent");
client.SetTraceId(null); // Clear contextclient.WithTraceId("request-456", () =>
{
client.Info("api", "Processing in context");
client.Warn("cache", "Cache miss");
});
// Trace ID automatically restored after blockclient.WithNewTraceId(() =>
{
client.Info("worker", "Background job started");
client.Info("worker", "Job completed");
});Search and retrieve logs programmatically.
var result = await client.QueryAsync(new QueryOptions
{
Service = "api-gateway",
Level = LogLevel.Error,
From = DateTime.UtcNow.AddDays(-1),
To = DateTime.UtcNow,
Limit = 100,
Offset = 0
});
Console.WriteLine($"Found {result.Total} logs");
foreach (var log in result.Logs)
{
Console.WriteLine($"{log.Time}: {log.Message}");
}var result = await client.QueryAsync(new QueryOptions
{
Query = "timeout",
Limit = 50
});var logs = await client.GetByTraceIdAsync("trace-123");
Console.WriteLine($"Trace has {logs.Count} logs");var stats = await client.GetAggregatedStatsAsync(new AggregatedStatsOptions
{
From = DateTime.UtcNow.AddDays(-7),
To = DateTime.UtcNow,
Interval = "1h", // "1m" | "5m" | "1h" | "1d"
Service = "api-gateway" // Optional
});
Console.WriteLine("Time series:");
foreach (var entry in stats.Timeseries)
{
Console.WriteLine($" {entry.Bucket}: {entry.Total} logs");
}Track SDK performance and health.
var metrics = client.GetMetrics();
Console.WriteLine($"Logs sent: {metrics.LogsSent}");
Console.WriteLine($"Logs dropped: {metrics.LogsDropped}");
Console.WriteLine($"Errors: {metrics.Errors}");
Console.WriteLine($"Retries: {metrics.Retries}");
Console.WriteLine($"Avg latency: {metrics.AvgLatencyMs}ms");
Console.WriteLine($"Circuit breaker trips: {metrics.CircuitBreakerTrips}");
// Get circuit breaker state
Console.WriteLine($"Circuit state: {client.GetCircuitBreakerState()}"); // Closed | Open | HalfOpen
// Reset metrics
client.ResetMetrics();Program.cs:
using LogTide.SDK;
using LogTide.SDK.Middleware;
using LogTide.SDK.Models;
var builder = WebApplication.CreateBuilder(args);
// Add LogTide
builder.Services.AddLogTide(new ClientOptions
{
ApiUrl = builder.Configuration["LogTide:ApiUrl"]!,
ApiKey = builder.Configuration["LogTide:ApiKey"]!,
GlobalMetadata = new()
{
["env"] = builder.Environment.EnvironmentName
}
});
var app = builder.Build();
// Add middleware for auto-logging HTTP requests
app.UseLogTide(options =>
{
options.ServiceName = "my-api";
options.LogRequests = true;
options.LogResponses = true;
options.LogErrors = true;
options.SkipHealthCheck = true;
options.SkipPaths.Add("/metrics");
});
app.MapGet("/", () => "Hello World!");
app.Run();| Option | Type | Default | Description |
|---|---|---|---|
ServiceName |
string |
"aspnet-api" |
Service name in logs |
LogRequests |
bool |
true |
Log incoming requests |
LogResponses |
bool |
true |
Log outgoing responses |
LogErrors |
bool |
true |
Log unhandled exceptions |
IncludeHeaders |
bool |
false |
Include request headers |
SkipHealthCheck |
bool |
true |
Skip /health endpoints |
SkipPaths |
HashSet<string> |
{} |
Paths to skip |
TraceIdHeader |
string |
"X-Trace-Id" |
Header for trace ID |
[ApiController]
[Route("[controller]")]
public class WeatherController : ControllerBase
{
private readonly LogTideClient _logger;
public WeatherController(LogTideClient logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult Get()
{
_logger.Info("weather-api", "Fetching weather data");
try
{
// ... business logic
return Ok(new { Temperature = 25 });
}
catch (Exception ex)
{
_logger.Error("weather-api", "Failed to fetch weather", ex);
throw;
}
}
}var client = new LogTideClient(options);
// ... use client
// Dispose flushes remaining logs
await client.DisposeAsync();Or with ASP.NET Core:
var app = builder.Build();
app.Lifetime.ApplicationStopping.Register(async () =>
{
var logger = app.Services.GetRequiredService<LogTideClient>();
await logger.FlushAsync();
});var client = new LogTideClient(new ClientOptions
{
ApiUrl = "...",
ApiKey = "...",
GlobalMetadata = new()
{
["env"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"),
["version"] = typeof(Program).Assembly.GetName().Version?.ToString(),
["machine"] = Environment.MachineName
}
});var client = new LogTideClient(new ClientOptions
{
ApiUrl = "...",
ApiKey = "...",
Debug = builder.Environment.IsDevelopment()
});See the examples/ directory for complete working examples.
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
