using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MiGu.Server.Configs;
using MiGu.Server.Launcher;
using MiGu.Server.Logs;
namespace MiGu.Server.Controllers;
///
/// 平台「日志管理」后端:把 Simple3 内核 SimpleCore.Library.Diagnosis 的落盘日志
/// (Diagnosis.Post / Diagnosis.Log 写入的 log/**/*.log,俗称 DLog)暴露给
/// platform-vue 的配置中心「日志管理」页查看。对齐 Simple3 桌面端 Simple3/UI/LogViewer.cs
/// 的两区设计(诊断条目表 + 落盘文件表),并在 Web 端额外提供「按标签合订」视图。
///
/// 数据来源:日志是 Simple3 进程在其工作目录写出的历史文件,不依赖 Simple3 是否在运行,
/// MiGu.Server 通过 定位工作目录后直接读
/// {工作目录}/log/。可用 appsettings Logs:Root 显式覆盖日志根目录。
///
/// 落盘行格式(见 Diagnosis.Log):[{prefix}yyyy/MM/dd-HH:mm:ss.fff] >{tag}: {content},
/// 其中 tag 为 / 表示无标签(滚动记录)。不匹配该模式的行视为上一条的续行(多行内容)。
///
/// 鉴权:class 级 [Authorize(Policy = "PlatformScope")] —— 日志为内核落盘文件(可能含文件路径 /
/// 内部运行状态等敏感信息),仅平台后台用户(scope=Platform)可读 / 下载,排除 RCSMonitor 监控大屏 token。
///
[ApiController]
[Authorize(Policy = "PlatformScope")]
[Route("api/logs")]
public sealed class LogsController : ControllerBase
{
/// 文件列表默认上限(与 LogViewer.cs 的 MaxFilesShown=500 对齐)。
private const int DefaultFileLimit = 500;
/// 单次解析的条目硬上限,防超大日志(实测单文件可达 46MB)撑爆内存。
private const int MaxEntries = 200_000;
/// 单次扫描字节上限(超过则截断并标记 truncated)。
private const long MaxScanBytes = 96L * 1024 * 1024;
/// 合订/某天聚合时最多遍历的文件数,避免一次扫描整月日志。
private const int MaxDigestFiles = 64;
/// 跨文件聚合(analyze/digest 的 day 模式)的总条目上限,防某天多个大文件 entries 累加撑爆内存。
private const int MaxAggregateEntries = 300_000;
/// Diagnosis 落盘行:[head] >tag: content;head 内含可选 prefix + 时间戳。
private static readonly Regex LineRegex =
new(@"^\[(?
[^\]]*)\]\s*>(?.*?):\s?(?.*)$", RegexOptions.Compiled);
/// 从 head 里抠出时间戳(prefix = 时间戳之前的部分)。
private static readonly Regex TimeRegex =
new(@"\d{4}/\d{2}/\d{2}-\d{2}:\d{2}:\d{2}\.\d{3}", RegexOptions.Compiled);
private const string TimeFormat = "yyyy/MM/dd-HH:mm:ss.fff";
///
/// 内容里的「数值字段」:key=value 或 key: value,value 为数字(可带小数/负号)。
/// key 必须以字母/下划线/中文开头,避免把 12:30 这类时间误判为字段。供「日志分析器」识别可绘图字段。
///
private static readonly Regex NumericFieldRegex =
new(@"(?[A-Za-z_\u4e00-\u9fff][\w\u4e00-\u9fff\.]*)\s*[=:]\s*(?-?\d+(?:\.\d+)?)", RegexOptions.Compiled);
private readonly Simple3Launcher _launcher;
private readonly IConfiguration _config;
private readonly ILogger _log;
private readonly LogCleanupService _cleanup;
public LogsController(
Simple3Launcher launcher,
IConfiguration config,
ILogger log,
LogCleanupService cleanup)
{
_launcher = launcher;
_config = config;
_log = log;
_cleanup = cleanup;
}
// ─────────────────────────────────────────────────────────── 概览 ──
/// 日志根概览:工作目录、根路径、是否存在、文件/字节总量、按天分组统计。
[HttpGet("overview")]
public IActionResult Overview()
{
var (root, workdir, error) = ResolveLogRoot();
if (root == null)
return Ok(new { exists = false, root = (string?)null, workingDirectory = workdir, message = error, disk = DiskDto() });
if (!Directory.Exists(root))
return Ok(new
{
exists = false, root, workingDirectory = workdir,
totalFiles = 0, totalBytes = 0L, days = Array.Empty