using PolyTrader.Web.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.IO;
using System.Globalization;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using PolyTraderSharp.Services;
namespace PolyTrader.Web.Endpoints
{
public static class LogsEndpoints
{
/// Wie im Desktop-Viewer: Schutz bei sehr grossen Tagen.
internal const int MaxMatches = 20000;
/// Etwas mehr als ein Jahr - aeltere Tage lassen sich weiter direkt ueber das Datum abrufen.
internal const int MaxListedDays = 400;
/// Ordner der Tagesdateien. Ohne Registrierung derselbe wie im TerminalLogger.
private static string ResolveLogDirectory(IServiceProvider sp) =>
sp.GetService()?.Path
?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
public static IEndpointRouteBuilder MapLogsEndpoints(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/logs");
group.MapGet("/", (TerminalLogger logger, int? minutes) =>
{
var span = TimeSpan.FromMinutes(minutes.GetValueOrDefault(60));
var history = logger.GetHistory(span)
.Select(e => new
{
time = e.Timestamp.ToString("HH:mm:ss"),
timestamp = e.Timestamp,
level = e.Level.ToString(),
cid = e.CorrelationId,
msg = e.Message
})
.ToList();
return Results.Ok(history);
});
group.MapGet("/stream", async (HttpContext context, TerminalLogger logger, CancellationToken ct) =>
{
context.Response.Headers.ContentType = "text/event-stream";
context.Response.Headers.CacheControl = "no-cache";
context.Response.Headers.Connection = "keep-alive";
var channel = Channel.CreateUnbounded(new UnboundedChannelOptions
{
SingleWriter = false,
SingleReader = true
});
void Handler(object? _, LogMessageEventArgs e)
{
channel.Writer.TryWrite(e);
}
logger.OnLogMessage += Handler;
try
{
await context.Response.WriteAsync(": connected\n\n", ct);
await context.Response.Body.FlushAsync(ct);
while (!ct.IsCancellationRequested)
{
var log = await channel.Reader.ReadAsync(ct);
var payload = JsonSerializer.Serialize(new
{
time = log.Timestamp.ToString("HH:mm:ss"),
level = log.Level.ToString(),
cid = log.CorrelationId,
msg = log.Message
});
await context.Response.WriteAsync($"data: {payload}\n\n", ct);
await context.Response.Body.FlushAsync(ct);
}
}
catch (OperationCanceledException)
{
// Client hat Verbindung getrennt
}
finally
{
logger.OnLogMessage -= Handler;
channel.Writer.TryComplete();
}
});
// ---- Log-Viewer (#564) ---------------------------------------------------------------
// Gegenstueck zum Tab "Log Viewer" im Desktop (TerminalWindow.LoadViewer): Tagesdateien
// Logs/yyyy-MM-dd.jsonl, gefiltert nach Level, Korrelations-ID und Text, hoechstens
// MaxMatches Treffer. Der Live-Stream zeigt nur, was seit dem Prozessstart aufgelaufen ist.
group.MapGet("/files", (IServiceProvider sp) =>
{
string dir = ResolveLogDirectory(sp);
if (!Directory.Exists(dir))
return Results.Ok(new List