.NET
Send logs from .NET applications to Parseable
Send logs from .NET applications to Parseable using HTTP or logging libraries.
Overview
Integrate .NET with Parseable to:
- Application Logs - Send structured logs from .NET apps
- ASP.NET Core - Native integration with Microsoft.Extensions.Logging
- Serilog Support - Use with Serilog sink
- Structured Logging - JSON-formatted log entries
Prerequisites
- .NET 6.0+
- Parseable instance accessible
- HttpClient or Serilog
Basic HTTP Integration
Using HttpClient
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
public class ParseableLogger : IDisposable
{
private readonly HttpClient _client;
private readonly string _stream;
private readonly List<object> _buffer = new();
private readonly object _lock = new();
private readonly int _batchSize;
private readonly Timer _flushTimer;
public ParseableLogger(string url, string dataset, string username, string password, int batchSize = 100)
{
_stream = dataset;
_batchSize = batchSize;
_client = new HttpClient { BaseAddress = new Uri(url) };
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
_flushTimer = new Timer(_ => Flush(), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
public void Log(string level, string message, object? data = null)
{
var entry = new
{
timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
level,
message,
data
};
lock (_lock)
{
_buffer.Add(entry);
if (_buffer.Count >= _batchSize)
{
FlushInternal();
}
}
}
public void Info(string message, object? data = null) => Log("info", message, data);
public void Error(string message, object? data = null) => Log("error", message, data);
public void Warning(string message, object? data = null) => Log("warning", message, data);
public void Flush()
{
lock (_lock)
{
FlushInternal();
}
}
private void FlushInternal()
{
if (_buffer.Count == 0) return;
var entries = _buffer.ToList();
_buffer.Clear();
Task.Run(async () =>
{
try
{
var json = JsonSerializer.Serialize(entries);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.Add("X-P-Stream", _stream);
await _client.PostAsync("/api/v1/ingest", content);
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to send logs: {ex.Message}");
}
});
}
public void Dispose()
{
_flushTimer.Dispose();
Flush();
_client.Dispose();
}
}
// Usage
using var logger = new ParseableLogger(
"http://parseable:8000",
"dotnet-app",
"admin",
"admin"
);
logger.Info("Application started", new { Version = "1.0.0" });
logger.Error("Database error", new { Error = "Connection refused" });Serilog Integration
Install Package
dotnet add package Serilog
dotnet add package Serilog.Sinks.HttpCustom Sink
using Serilog;
using Serilog.Core;
using Serilog.Events;
using System.Text;
using System.Text.Json;
public class ParseableSink : ILogEventSink, IDisposable
{
private readonly HttpClient _client;
private readonly string _stream;
private readonly List<object> _buffer = new();
private readonly object _lock = new();
private readonly Timer _flushTimer;
public ParseableSink(string url, string dataset, string username, string password)
{
_stream = dataset;
_client = new HttpClient { BaseAddress = new Uri(url) };
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
_client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", auth);
_flushTimer = new Timer(_ => Flush(), null,
TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
public void Emit(LogEvent logEvent)
{
var entry = new
{
timestamp = logEvent.Timestamp.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
level = logEvent.Level.ToString().ToLower(),
message = logEvent.RenderMessage(),
exception = logEvent.Exception?.ToString(),
properties = logEvent.Properties.ToDictionary(
p => p.Key,
p => p.Value.ToString().Trim('"'))
};
lock (_lock)
{
_buffer.Add(entry);
if (_buffer.Count >= 100)
{
FlushInternal();
}
}
}
public void Flush()
{
lock (_lock) { FlushInternal(); }
}
private void FlushInternal()
{
if (_buffer.Count == 0) return;
var entries = _buffer.ToList();
_buffer.Clear();
Task.Run(async () =>
{
var json = JsonSerializer.Serialize(entries);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.Add("X-P-Stream", _stream);
await _client.PostAsync("/api/v1/ingest", content);
});
}
public void Dispose()
{
_flushTimer.Dispose();
Flush();
_client.Dispose();
}
}
// Extension method
public static class ParseableSinkExtensions
{
public static LoggerConfiguration Parseable(
this LoggerSinkConfiguration config,
string url,
string dataset,
string username,
string password)
{
return config.Sink(new ParseableSink(url, dataset, username, password));
}
}Usage
Log.Logger = new LoggerConfiguration()
.WriteTo.Parseable(
url: "http://parseable:8000",
dataset: "dotnet-app",
username: "admin",
password: "admin")
.CreateLogger();
Log.Information("Application started");
Log.Error("Something went wrong", new { ErrorCode = 500 });ASP.NET Core Integration
Configure in Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add Serilog with Parseable
builder.Host.UseSerilog((context, config) =>
{
config
.ReadFrom.Configuration(context.Configuration)
.WriteTo.Parseable(
url: context.Configuration["Parseable:Url"]!,
dataset: context.Configuration["Parseable:Stream"]!,
username: context.Configuration["Parseable:Username"]!,
password: context.Configuration["Parseable:Password"]!);
});
var app = builder.Build();
app.UseSerilogRequestLogging();
app.Run();Configuration
{
"Parseable": {
"Url": "http://parseable:8000",
"Stream": "aspnet-app",
"Username": "admin",
"Password": "admin"
}
}OpenTelemetry Integration
using OpenTelemetry.Logs;
builder.Logging.AddOpenTelemetry(options =>
{
options.AddOtlpExporter(otlp =>
{
otlp.Endpoint = new Uri("http://parseable:8000/v1/logs");
otlp.Headers = "Authorization=Basic YWRtaW46YWRtaW4=,X-P-Stream=dotnet-otel";
});
});Best Practices
- Use Batching - Buffer logs and send in batches
- Async Sending - Don't block application threads
- Handle Failures - Log locally on send failures
- Add Context - Include correlation ID, user ID
- Dispose Properly - Flush on application shutdown
Troubleshooting
Connection Errors
- Verify Parseable URL is accessible
- Check SSL certificate if using HTTPS
- Verify credentials
Missing Logs
- Ensure Flush is called on shutdown
- Check for exceptions in background tasks
- Verify dataset name
Next Steps
- Configure alerts for error patterns
- Create dashboards for .NET metrics
- Explore OpenTelemetry for tracing
Was this page helpful?