added missing data

This commit is contained in:
Daniel Schick 2025-05-26 09:01:59 +02:00
parent 7bf2994cda
commit 22ddf238ef
3 changed files with 125 additions and 23 deletions

View File

@ -1,59 +1,155 @@
namespace bsmd.util // Copyright (c) 2025 schick Informatik
// Description: Appender for Loki/Grafana remote logging
//
namespace bsmd.util
{ {
using log4net.Appender; using log4net.Appender;
using log4net.Core; using log4net.Core;
using Newtonsoft.Json; using Newtonsoft.Json;
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
public class LokiAppender : AppenderSkeleton public class LokiAppender : AppenderSkeleton
{ {
#region Fields
private static readonly HttpClient _httpClient = new HttpClient();
private readonly ConcurrentQueue<Tuple<string, string>> _logQueue = new ConcurrentQueue<Tuple<string, string>>();
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private bool _batchingStarted = false;
private readonly object _batchLock = new object();
#endregion
#region Properties
public string LokiUrl { get; set; } public string LokiUrl { get; set; }
public string ApplicationName { get; set; } = "log4net-app"; public string ApplicationName { get; set; } = "log4net-app";
private static readonly HttpClient httpClient = new HttpClient(); public int BatchSize { get; set; } = 10;
public int BatchIntervalMs { get; set; } = 2000;
public int MaxRetries { get; set; } = 5;
#endregion
#region overrides
protected override void Append(LoggingEvent loggingEvent) protected override void Append(LoggingEvent loggingEvent)
{ {
try string timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + "000000";
{ string message = RenderLoggingEvent(loggingEvent);
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() + "000000"; // nanoseconds
var message = RenderLoggingEvent(loggingEvent);
var payload = new _logQueue.Enqueue(new Tuple<string, string>(timestamp, message));
// Start background batch loop once
if (!_batchingStarted)
{
lock (_batchLock)
{ {
streams = new[] if (!_batchingStarted)
{ {
_ = Task.Run(() => BatchLoop(_cts.Token));
_batchingStarted = true;
}
}
}
}
private async Task BatchLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await Task.Delay(BatchIntervalMs, ct);
var batch = new List<Tuple<string, string>>();
while (batch.Count < BatchSize && _logQueue.TryDequeue(out var entry))
batch.Add(entry);
if (batch.Count == 0) continue;
var payload = new
{
streams = new[]
{
new new
{ {
stream = new stream = new
{ {
app = ApplicationName, app = ApplicationName,
level = loggingEvent.Level.Name.ToLower() level = "info" // Optional: capture log level from batch
}, },
values = new[] values = batch.ConvertAll(e => new[] { e.Item1, e.Item2 })
{
new[] { timestamp, message }
}
} }
} }
}; };
var json = JsonConvert.SerializeObject(payload); string json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var content = new StringContent(json, Encoding.UTF8, "application/json"); await SendWithRetry(content, MaxRetries, ct);
}
// Fire and forget catch (OperationCanceledException)
Task.Run(() => httpClient.PostAsync(LokiUrl, content)); {
} // Graceful shutdown
catch (Exception ex) }
{ catch (Exception ex)
ErrorHandler.Error("Error sending log to Loki", ex); {
ErrorHandler.Error("Error in Loki batch loop", ex);
}
} }
} }
private async Task SendWithRetry(HttpContent content, int maxRetries, CancellationToken ct)
{
int attempt = 0;
TimeSpan delay = TimeSpan.FromSeconds(1);
while (attempt <= maxRetries && !ct.IsCancellationRequested)
{
try
{
var response = await _httpClient.PostAsync(LokiUrl, content, ct);
if (response.IsSuccessStatusCode)
return;
attempt++;
await Task.Delay(delay, ct);
delay = TimeSpan.FromSeconds(delay.TotalSeconds * 2); // exponential backoff
}
catch
{
attempt++;
await Task.Delay(delay, ct);
delay = TimeSpan.FromSeconds(delay.TotalSeconds * 2);
}
}
ErrorHandler.Error($"Failed to send logs to Loki after {attempt} retries.");
}
protected override void OnClose()
{
_cts.Cancel();
base.OnClose();
}
#endregion
} }
} }

View File

@ -53,6 +53,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="bsmd.util.licenseheader" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -0,0 +1,5 @@
extensions: designer.cs generated.cs
extensions: .cs .cpp .h
// Copyright (c) 2025 schick Informatik
// Description:
//