-
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 (credentials) 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.