using Seq.Api;
using Seq.Api.Model.Events;

namespace Dpz.Core.Service.RepositoryServiceImpl;

public class AppLogEntryService(IConfiguration configuration, ILogger<AppLogEntryService> logger)
    : IAppLogEntryService
{
    private SeqConnection GetSeqConnection()
    {
        var logApiHost = configuration["LogApiHost"];
        if (string.IsNullOrEmpty(logApiHost))
        {
            throw new Exception("configuration no LogApiHost node");
        }
        var apiKey = configuration["LogSeq:ApiKey"];
        if (string.IsNullOrEmpty(apiKey))
        {
            throw new Exception("configuration no LogSeq.Apikey node");
        }
        var connection =
            configuration["AgileConfig:env"] != "PROD"
                ? new SeqConnection(logApiHost, configuration["LogApiKey"])
                : new SeqConnection(logApiHost, apiKey);
        return connection;
    }

    public async Task<ResultSetPart?> GetPageAsync(
        string? filter = null,
        string? startAtId = null,
        string? afterId = null,
        int pageSize = 20
    )
    {
        using var connection = GetSeqConnection();

        var part = await connection.Events.PageAsync(
            filter: filter,
            startAtId: startAtId,
            afterId: afterId,
            render: true,
            count: pageSize
        );

        return part;
    }

    public async Task<List<AccessSummary>> GetLatestAccessNumberAsync(int? days)
    {
        var startDate = days == null ? DateTime.Now.Date : DateTime.Now.Date.AddDays(days.Value);
        var endDate = days == null ? DateTime.Now : DateTime.Now.Date;
        using var connection = GetSeqConnection();
        var filters = new List<string>
        {
            "Program = 'Dpz.Core.Web'",
            "SourceContext = 'Serilog.AspNetCore.RequestLoggingMiddleware'",
            "RequestPath <> '/chathub'",
            "RequestPath <> '/chatHub'",
            "RequestPath <> '/notification'",
            "RequestPath <> '/notification/negotiate'",
            "RequestPath <> '/chathub/negotiate'",
            "RequestPath <> '/chat/init'",
        };
        var filter = string.Join(" and ", filters);

        try
        {
            var query = $"""
                select count(@Id) from stream
                where {filter}
                group by time(1d)
                """;
            var part = await connection.Data.QueryAsync(query, startDate, endDate);
            var list = part
                .Slices.Select(x => new AccessSummary
                {
                    Date = Convert.ToDateTime(x.Time).ToString("yyyy/MM/dd"),
                    Count = Convert.ToInt32(x.Rows.FirstOrDefault()?.FirstOrDefault() ?? 0),
                })
                .ToList();

            return list;
        }
        catch (Exception e)
        {
            logger.LogError(e, "get latest access number fail");
            return new List<AccessSummary>();
        }
    }
}




// select
// 	@Id,
// 	ToIsoString(@Timestamp) as time,
// 	Elapsed,
// 	@Message
// from stream
// where SourceContext='Serilog.AspNetCore.RequestLoggingMiddleware'
// 	and SourceContext = 'Serilog.AspNetCore.RequestLoggingMiddleware'
// 	and RequestPath <> '/chathub'
// 	and RequestPath <> '/chatHub'
// 	and RequestPath <> '/notification'
// 	and RequestPath <> '/notification/negotiate'
// 	and RequestPath <> '/chathub/negotiate'
// 	and RequestPath <> '/chat/init'
// 	and RequestPath <> '/robotChat'
//  order by Elapsed desc limit 20
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这个代码文件定义了一个名为 AppLogEntryService 的服务类,主要用于与 Seq 日志服务器进行交互,获取和分析应用程序的日志数据。让我详细解释一下:

类的结构和依赖

public class AppLogEntryService(IConfiguration configuration, ILogger<AppLogEntryService> logger)
    : IAppLogEntryService
  • 使用了 C# 12 的主构造函数语法
  • 依赖注入了 IConfiguration(配置服务)和 ILogger(日志服务)
  • 实现了 IAppLogEntryService 接口

核心方法解析

1. GetSeqConnection() - 建立 Seq 连接

private SeqConnection GetSeqConnection()

这个私有方法负责创建与 Seq 日志服务器的连接:

  • 从配置中读取 LogApiHost(Seq 服务器地址)
  • 根据环境(生产环境或非生产环境)选择不同的 API Key
  • 非生产环境使用 LogApiKey,生产环境使用 LogSeq:ApiKey
  • 返回配置好的 SeqConnection 对象

2. GetPageAsync() - 分页获取日志事件

public async Task<ResultSetPart?> GetPageAsync(...)

这个方法提供日志事件的分页查询功能:

  • 参数说明
    • filter: 过滤条件
    • startAtId: 起始ID
    • afterId: 在某个ID之后
    • pageSize: 页面大小(默认20条)
  • 调用 Seq API 的 Events.PageAsync 方法获取数据
  • render: true 表示渲染日志消息模板

3. GetLatestAccessNumberAsync() - 获取访问统计

public async Task<List<AccessSummary>> GetLatestAccessNumberAsync(int? days)

这个方法用于统计网站的访问量:

  • 时间范围:根据 days 参数确定查询的时间范围
  • 过滤条件
    • 只统计 Dpz.Core.Web 程序的日志
    • 只统计来自请求日志中间件的日志
    • 排除 WebSocket 相关的路径(如 /chathub, /notification 等)
  • SQL 查询:使用 Seq 的查询语言按天分组统计访问次数
  • 返回数据:包含日期和访问次数的 AccessSummary 列表

关键特性

  1. 环境区分:根据配置环境使用不同的 API Key
  2. 异常处理:在统计方法中包含了完整的异常处理
  3. 资源管理:使用 using 语句确保连接正确释放
  4. 灵活过滤:支持多种过滤条件来精确查询所需的日志数据

底部的 SQL 注释

文件末尾的注释展示了一个更复杂的 Seq 查询示例,用于获取响应时间最长的 20 个请求,这可能是性能监控的一部分。

这个服务类是一个典型的日志分析服务,为应用程序提供了日志查询和访问统计的功能。

评论加载中...