252 lines
8.3 KiB
C#
252 lines
8.3 KiB
C#
using EonaCat.LogStack.Status.Data;
|
|
using EonaCat.LogStack.Status.Models;
|
|
using EonaCat.LogStack.Status.Services;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using System.Text.Json;
|
|
|
|
namespace EonaCat.LogStack.Status.Controllers;
|
|
|
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
|
|
[ApiController]
|
|
[Route("api")]
|
|
public class ApiController : ControllerBase
|
|
{
|
|
private readonly DatabaseContext _database;
|
|
private readonly MonitoringService _monitorService;
|
|
private readonly IngestionService _ingestionService;
|
|
|
|
public ApiController(DatabaseContext database, MonitoringService monitorService, IngestionService ingestionService)
|
|
{
|
|
_database = database;
|
|
_monitorService = monitorService;
|
|
_ingestionService = ingestionService;
|
|
}
|
|
|
|
[HttpGet("status/summary")]
|
|
public async Task<IActionResult> GetSummary()
|
|
{
|
|
var isAdmin = HttpContext.Session.GetString("IsAdmin") == "true";
|
|
var stats = await _monitorService.GetStatsAsync(isAdmin);
|
|
return Ok(stats);
|
|
}
|
|
|
|
[HttpGet("monitors/{id}/check")]
|
|
public async Task<IActionResult> CheckMonitor(int id)
|
|
{
|
|
var monitor = await _database.Monitors.FindAsync(id);
|
|
if (monitor == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var check = await _monitorService.CheckMonitorAsync(monitor);
|
|
return Ok(check);
|
|
}
|
|
|
|
[HttpGet("monitors")]
|
|
public async Task<IActionResult> GetMonitors()
|
|
{
|
|
var isAdmin = HttpContext.Session.GetString("IsAdmin") == "true";
|
|
var query = _database.Monitors.Where(m => m.IsActive);
|
|
|
|
if (!isAdmin)
|
|
{
|
|
query = query.Where(m => m.IsPublic);
|
|
}
|
|
|
|
return Ok(await query.ToListAsync());
|
|
}
|
|
|
|
[HttpPost("logs/ingest")]
|
|
public async Task<IActionResult> IngestLog([FromBody] LogEntry entry)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(entry.Message))
|
|
{
|
|
return BadRequest("message required");
|
|
}
|
|
|
|
entry.Level = (entry.Level ?? "info").ToLower();
|
|
entry.Timestamp = DateTime.UtcNow;
|
|
await _ingestionService.IngestAsync(entry);
|
|
return Ok(new { success = true });
|
|
}
|
|
|
|
[HttpPost("logs/batch")]
|
|
public async Task<IActionResult> IngestBatch([FromBody] List<LogEntry> entries)
|
|
{
|
|
if (entries == null || !entries.Any())
|
|
{
|
|
return BadRequest("entries required");
|
|
}
|
|
|
|
foreach (var entry in entries)
|
|
{
|
|
entry.Level = (entry.Level ?? "info").ToLower();
|
|
|
|
if (entry.Timestamp == default)
|
|
{
|
|
entry.Timestamp = DateTime.UtcNow;
|
|
}
|
|
}
|
|
await _ingestionService.IngestBatchAsync(entries);
|
|
return Ok(new { success = true, count = entries.Count });
|
|
}
|
|
|
|
[HttpPost("logs/eonacat")]
|
|
public async Task<IActionResult> IngestEonaCat([FromBody] object[] events)
|
|
{
|
|
var entries = new List<LogEntry>();
|
|
|
|
foreach (var evtObj in events)
|
|
{
|
|
// Use System.Text.Json to parse the dictionary dynamically
|
|
var json = JsonSerializer.Serialize(evtObj);
|
|
var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json)!;
|
|
|
|
var logEntry = new LogEntry
|
|
{
|
|
Source = dict.TryGetValue("properties", out var props) &&
|
|
props.ValueKind == JsonValueKind.Object &&
|
|
props.Deserialize<Dictionary<string, object?>>().TryGetValue("Application", out var appObj)
|
|
? appObj?.ToString() ?? "EonaCat.LogStack"
|
|
: "EonaCat.LogStack",
|
|
|
|
Level = dict.TryGetValue("level", out var level) ? level.GetString() ?? "Info" : "Info",
|
|
Message = dict.TryGetValue("message", out var msg) ? msg.GetString() ?? "" : "",
|
|
Exception = dict.TryGetValue("exception", out var ex) ? ex.ToString() : null,
|
|
Host = dict.TryGetValue("host", out var host) ? host.GetString() : null,
|
|
TraceId = dict.TryGetValue("traceId", out var traceId) ? traceId.GetString() : null,
|
|
Properties = dict.TryGetValue("properties", out var properties) ? properties.GetRawText() : null,
|
|
Timestamp = dict.TryGetValue("timestamp", out var ts) && DateTime.TryParse(ts.GetString(), out var dt)
|
|
? dt
|
|
: DateTime.UtcNow
|
|
};
|
|
|
|
logEntry.Level = MapEonaCatLevel(logEntry.Level);
|
|
|
|
entries.Add(logEntry);
|
|
}
|
|
|
|
if (entries.Any())
|
|
{
|
|
await _ingestionService.IngestBatchAsync(entries);
|
|
}
|
|
|
|
return Ok(new { success = true, count = entries.Count });
|
|
}
|
|
|
|
[HttpPost("logs/serilog")]
|
|
public async Task<IActionResult> IngestSerilog([FromBody] SerilogPayload payload)
|
|
{
|
|
var entries = payload.Events?.Select(e => new LogEntry
|
|
{
|
|
Source = e.Properties?.TryGetValue("Application", out var app) == true ? app?.ToString() ?? "serilog" : "serilog",
|
|
Level = MapSerilogLevel(e.Level),
|
|
Message = e.RenderedMessage ?? e.MessageTemplate ?? "",
|
|
Exception = e.Exception,
|
|
Properties = e.Properties != null ? System.Text.Json.JsonSerializer.Serialize(e.Properties) : null,
|
|
Timestamp = e.Timestamp == default ? DateTime.UtcNow : e.Timestamp
|
|
}).ToList() ?? new List<LogEntry>();
|
|
|
|
if (entries.Any())
|
|
{
|
|
await _ingestionService.IngestBatchAsync(entries);
|
|
}
|
|
|
|
return Ok(new { success = true, count = entries.Count });
|
|
}
|
|
|
|
private static string MapSerilogLevel(string? l) => l?.ToLower() switch {
|
|
"verbose" or "debug" => "debug",
|
|
"information" => "info",
|
|
"warning" => "warn",
|
|
"error" => "error",
|
|
"fatal" => "critical",
|
|
_ => "info"
|
|
};
|
|
|
|
private static string MapEonaCatLevel(string? l) => l?.ToLower() switch
|
|
{
|
|
"trace" or "debug" => "debug",
|
|
"information" => "info",
|
|
"warning" => "warn",
|
|
"error" => "error",
|
|
"critical" => "critical",
|
|
_ => "info"
|
|
};
|
|
|
|
[HttpGet("logs")]
|
|
public async Task<IActionResult> QueryLogs([FromQuery] string? level, [FromQuery] string? source, [FromQuery] string? search, [FromQuery] int page = 1, [FromQuery] int pageSize = 100)
|
|
{
|
|
if (HttpContext.Session.GetString("IsAdmin") != "true")
|
|
{
|
|
return Unauthorized();
|
|
}
|
|
|
|
var query = _database.Logs.AsQueryable();
|
|
if (!string.IsNullOrWhiteSpace(level))
|
|
{
|
|
query = query.Where(x => x.Level == level.ToLower());
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(source))
|
|
{
|
|
query = query.Where(x => x.Source == source);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
{
|
|
query = query.Where(x => x.Message.Contains(search));
|
|
}
|
|
|
|
var total = await query.CountAsync();
|
|
var entries = await query.OrderByDescending(x => x.Timestamp).Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
|
|
return Ok(new { total, page, pageSize, entries });
|
|
}
|
|
}
|
|
|
|
public class EonaCatPayLoad
|
|
{
|
|
public List<EonaCatLogEvent>? Events { get; set; }
|
|
}
|
|
|
|
public class EonaCatLogEvent
|
|
{
|
|
public string Timestamp { get; set; } = default!;
|
|
public string Level { get; set; } = default!;
|
|
public string Message { get; set; } = default!;
|
|
public string Category { get; set; } = default!;
|
|
public int ThreadId { get; set; }
|
|
|
|
public string? TraceId { get; set; }
|
|
public string? SpanId { get; set; }
|
|
|
|
public ExceptionDto? Exception { get; set; }
|
|
public Dictionary<string, object?>? Properties { get; set; }
|
|
}
|
|
|
|
public class ExceptionDto
|
|
{
|
|
public string Type { get; set; } = default!;
|
|
public string Message { get; set; } = default!;
|
|
public string? StackTrace { get; set; }
|
|
}
|
|
|
|
public class SerilogPayload
|
|
{
|
|
public List<SerilogEvent>? Events { get; set; }
|
|
}
|
|
|
|
public class SerilogEvent
|
|
{
|
|
public DateTime Timestamp { get; set; }
|
|
public string? Level { get; set; }
|
|
public string? MessageTemplate { get; set; }
|
|
public string? RenderedMessage { get; set; }
|
|
public string? Exception { get; set; }
|
|
public Dictionary<string, object?>? Properties { get; set; }
|
|
}
|