-
Notifications
You must be signed in to change notification settings - Fork 40
Custom HttpClient
v9 removes the ILokiHttpClient hierarchy (BaseLokiHttpClient, LokiHttpClient, LokiGzipHttpClient). The sink now works with a standard System.Net.Http.HttpClient, so any cross-cutting behaviour — gzip, retries, mTLS, bearer auth — is added with the normal .NET building blocks.
Pass an HttpMessageHandler and the sink builds and owns the HttpClient around it. The sink still applies basic auth (credentialsLogin / credentialsPassword) and the tenant header.
.WriteTo.GrafanaLoki(
"http://localhost:3100",
httpMessageHandler: new GzipHandler())Pass a fully built HttpClient (for example from IHttpClientFactory). The sink never disposes an injected client, and does not add auth/tenant headers to it — configure those yourself.
var httpClient = httpClientFactory.CreateClient("loki");
.WriteTo.GrafanaLoki("http://localhost:3100", httpClient: httpClient)If both are supplied,
httpClientwins andhttpMessageHandleris ignored.
// GzipHandler.cs
using System.IO.Compression;
using System.Net.Http;
using System.Net.Http.Headers;
public class GzipHandler : DelegatingHandler
{
public GzipHandler() : base(new HttpClientHandler()) { }
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
if (request.Content is not null)
{
var bytes = await request.Content.ReadAsByteArrayAsync(ct);
using var ms = new MemoryStream();
using (var gz = new GZipStream(ms, CompressionLevel.Fastest, leaveOpen: true))
await gz.WriteAsync(bytes, ct);
request.Content = new ByteArrayContent(ms.ToArray());
request.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" };
request.Content.Headers.ContentEncoding.Add("gzip");
}
return await base.SendAsync(request, ct);
}
}Delivery-level retry and backoff are already handled by Serilog's batching (retryTimeLimit). If you also want per-request resilience (for example with Polly), add it as a handler:
// Microsoft.Extensions.Http.Polly
var handler = new PolicyHttpMessageHandler(retryPolicy)
{
InnerHandler = new HttpClientHandler()
};
.WriteTo.GrafanaLoki("http://localhost:3100", httpMessageHandler: handler)There is no first-class bearer-token option. Either set a default header on an injected HttpClient, or add a DelegatingHandler that attaches (and refreshes) the token per request:
public class BearerHandler : DelegatingHandler
{
private readonly ITokenProvider _tokens;
public BearerHandler(ITokenProvider tokens) : base(new HttpClientHandler()) => _tokens = tokens;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", await _tokens.GetTokenAsync(ct));
return await base.SendAsync(request, ct);
}
}var handler = new HttpClientHandler();
handler.ClientCertificates.Add(clientCertificate);
.WriteTo.GrafanaLoki("http://localhost:3100", httpMessageHandler: handler)A
DelegatingHandlerpassed to the sink must have an inner handler (the examples above usenew HttpClientHandler()); otherwise the request pipeline has no terminus.