HJL WebAPI 项目日志入库实战:从建表到自动清理

HJL WebAPI 项目日志入库实战:从建表到自动清理

一、背景

HJL 项目是一个 .NET 8 WebAPI 程序,原来使用 NLog 写文本日志。随着业务量增长,服务器磁盘 IO 和日志文件管理成为痛点。本文记录将日志全部写入 Oracle 数据库 、并配置自动清理策略的完整过程。


二、技术栈

组件 版本/说明
框架 .NET 8 WebAPI
日志框架 NLog + NLog.Web.AspNetCore
数据库 Oracle
ORM SQLSugar(业务库)
驱动 Oracle.ManagedDataAccess.Core

三、第一步:数据库建表

在 Oracle 中创建专用日志表 HJL_API_APPLOG,表名含义:HJL 项目 - WebAPI - 应用日志

3.1 建表 SQL

sql 复制代码
-- ============================================
-- HJL WebAPI 应用日志表
-- 说明:替代服务器文本日志,所有日志只写入数据库
-- 保留策略:180 天(由 Oracle Job 自动清理)
-- ============================================

CREATE TABLE MESFLXRPT.HJL_API_APPLOG (
    ID              VARCHAR2(32)      PRIMARY KEY,     -- 主键,程序生成 GUID
    LOG_LEVEL       VARCHAR2(20)      NOT NULL,          -- 日志级别:Info/Warning/Error/Critical
    CATEGORY        VARCHAR2(255),                       -- 日志来源(Logger 名称,如 Controller 全名)
    MESSAGE         CLOB,                                -- 日志正文
    EXCEPTION       CLOB,                                -- 异常堆栈(Error 级别时写入)
    CLASS_NAME      VARCHAR2(500),                       -- 产生日志的完整类名
    METHOD_NAME     VARCHAR2(255),                       -- 产生日志的方法名(Action 名称)
    THREAD_ID       VARCHAR2(50),                        -- 线程 ID(排查并发问题)
    TRACE_ID        VARCHAR2(100),                       -- HTTP 请求追踪 ID(同一次请求的所有日志共享)
    IP_ADDRESS      VARCHAR2(50),                        -- 客户端请求 IP
    CREATE_TIME     DATE              DEFAULT SYSDATE NOT NULL  -- 日志产生时间
);

-- 表注释
COMMENT ON TABLE MESFLXRPT.HJL_API_APPLOG IS 'HJL项目WebAPI应用日志表:替代服务器文本日志,保留180天后自动清理';

-- 字段注释
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.ID IS '主键,程序生成的32位大写GUID';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.LOG_LEVEL IS '日志级别:Trace/Debug/Info/Warning/Error/Critical。生产环境建议只存Info及以上';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.CATEGORY IS '日志来源(Logger名称),通常是类的全限定名';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.MESSAGE IS '日志正文内容,CLOB大字段支持长文本';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.EXCEPTION IS '异常堆栈详情,仅Error/Critical级别时可能有值';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.CLASS_NAME IS '产生日志的完整类名(含命名空间)';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.METHOD_NAME IS '产生日志的方法名,WebAPI项目使用aspnet-mvc-action获取Action名称';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.THREAD_ID IS '当前托管线程ID,用于分析并发问题';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.TRACE_ID IS 'HTTP请求追踪标识(HttpContext.TraceIdentifier),实现全链路追踪';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.IP_ADDRESS IS '客户端请求IP地址,用于安全审计';
COMMENT ON COLUMN MESFLXRPT.HJL_API_APPLOG.CREATE_TIME IS '日志产生时间,默认SYSDATE,作为数据清理和查询的时间依据';

-- 索引:按时间查询和清理(最常用)
CREATE INDEX IDX_HJL_LOG_TIME ON MESFLXRPT.HJL_API_APPLOG(CREATE_TIME);

-- 索引:按日志级别筛选(查Error时提速)
CREATE INDEX IDX_HJL_LOG_LEVEL ON MESFLXRPT.HJL_API_APPLOG(LOG_LEVEL);

-- 索引:按业务模块筛选(查特定Controller日志时提速)
CREATE INDEX IDX_HJL_LOG_CATEGORY ON MESFLXRPT.HJL_API_APPLOG(CATEGORY);

3.2 设计说明

  • 为什么用 CLOB? 异常堆栈可能很长,VARCHAR2 存不下。
  • 为什么必须建时间索引? 日志查询和定时清理都按 CREATE_TIME 过滤,没有索引全表扫描性能极差。
  • 为什么保留 180 天? 生产环境半年前的日志参考价值低,定期清理防止表膨胀。

四、第二步:安装 NLog 相关 NuGet 包

在 WebAPI 项目上安装以下包:

bash 复制代码
# NLog 核心包
dotnet add package NLog

# ASP.NET Core 扩展(提供 TraceId、IP 等变量)
dotnet add package NLog.Web.AspNetCore

# 数据库支持(DatabaseTarget)
dotnet add package NLog.Database

# Oracle 驱动(.NET 8 用 Core 版本)
dotnet add package Oracle.ManagedDataAccess.Core

五、第三步:配置 appsettings.json

appsettings.json 中配置 Oracle 连接串,不要写死在 NLog 配置文件里

json 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },

  "AllowedHosts": "*",

  "ConnectionStrings": {
    "strConnFlexMesInterface": "Data Source=你的TNS;User Id=用户名;Password=密码;"
  }
}

安全提示 :生产环境使用 appsettings.Production.json,并加入 .gitignore,避免密码提交到版本库。


六、第四步:配置 nlog.config

在项目根目录创建 nlog.config属性设置为"复制到输出目录 → 始终复制"

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Warn"
      internalLogFile="${basedir}/NLog/internal.log">

  <!-- 启用 ASP.NET Core 扩展,提供 TraceId 和 IP 变量 -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <targets>
    <!-- 
      数据库日志目标(三层包装)
      
      第1层:AsyncWrapper(异步缓冲)
      - 日志先存入内存队列,攒够 50 条或每 1 秒批量写入数据库
      - 避免每条日志都等待数据库 IO,提升 API 响应速度
      
      第2层:RetryingWrapper(失败重试)
      - 数据库断线或锁表时自动重试 3 次
      
      第3层:DatabaseTarget(数据库写入)
      - 执行 Oracle INSERT 语句
    -->
    <target xsi:type="AsyncWrapper"
            name="db_async"
            batchSize="50"
            timeToSleepBetweenBatches="1000"
            queueLimit="10000"
            overflowAction="Grow">

      <target xsi:type="RetryingWrapper"
              retryCount="3"
              retryDelayMilliseconds="500">

        <target xsi:type="Database"
                name="db_log"
                
                <!-- 
                  【重要】程序集名必须是 Oracle.ManagedDataAccess
                  NuGet 包名是 Oracle.ManagedDataAccess.Core,但程序集名没有 .Core 后缀!
                  这是最常见的坑,写错会导致 Could not load file or assembly 错误。
                -->
                dbProvider="Oracle.ManagedDataAccess.Client.OracleConnection, Oracle.ManagedDataAccess"
                
                connectionString="${configsetting:item=ConnectionStrings.strConnFlexMesInterface}"
                
                commandText="INSERT INTO MESFLXRPT.HJL_API_APPLOG (
                    ID, LOG_LEVEL, CATEGORY, MESSAGE, EXCEPTION,
                    CLASS_NAME, METHOD_NAME, THREAD_ID, TRACE_ID, IP_ADDRESS, CREATE_TIME
                ) VALUES (
                    :Id, :LogLevel, :Category, :Message, :Exception,
                    :ClassName, :MethodName, :ThreadId, :TraceId, :IpAddress, TO_DATE(:CreateTime, 'YYYY-MM-DD HH24:MI:SS')
                )">

          <!-- 32位大写GUID -->
          <parameter name="Id"         layout="${guid:format=N:uppercase=true}" />
          
          <!-- 日志级别大写:INFO/ERROR/WARNING -->
          <parameter name="LogLevel"   layout="${level:uppercase=true}" />
          
          <!-- 日志来源(Logger名称) -->
          <parameter name="Category"   layout="${logger}" />
          
          <!-- 日志正文 -->
          <parameter name="Message"    layout="${message}" />
          
          <!-- 异常堆栈。Oracle CLOB 足够大,不需要截断 -->
          <parameter name="Exception"  layout="${exception:format=toString,Data}" />
          
          <!-- 完整类名 -->
          <parameter name="ClassName"  layout="${logger}" />
          
          <!-- 
            【重要】WebAPI 项目获取 Action 方法名
            使用 ${aspnet-mvc-action} 直接从 ASP.NET Core 路由上下文读取,
            比 ${callsite} 更适合 async Controller,避免抓到编译器生成的状态机方法名。
          -->
          <parameter name="MethodName" layout="${aspnet-mvc-action}" />
          
          <!-- 线程ID -->
          <parameter name="ThreadId"   layout="${threadid}" />
          
          <!-- 
            HTTP 请求追踪 ID。
            whenEmpty 处理非 HTTP 场景(如后台线程),从 MDLC 上下文读取。
          -->
          <parameter name="TraceId"    layout="${aspnet-TraceIdentifier:whenEmpty=${mdlc:item=TraceId}}" />
          
          <!-- 客户端IP -->
          <parameter name="IpAddress"  layout="${aspnet-request-ip:whenEmpty=${mdlc:item=ClientIp}}" />
          
          <!-- 日志时间 -->
          <parameter name="CreateTime" layout="${date:format=yyyy-MM-dd HHmmss}" />
        </target>
      </target>
    </target>
  </targets>

  <rules>
    <!-- 过滤 Microsoft 框架的噪音日志(如 Kestrel 启动信息) -->
    <logger name="Microsoft.*" minlevel="Trace" final="true" />
    
    <!-- 业务日志 Info 及以上写入数据库 -->
    <logger name="*" minlevel="Info" writeTo="db_async" />
  </rules>
</nlog>

6.1 关键踩坑点

坑点 错误写法 正确写法
Oracle 程序集名 Oracle.ManagedDataAccess.Core Oracle.ManagedDataAccess没有 .Core
async 方法名获取 ${callsite:methodName=true} ${aspnet-mvc-action}(WebAPI 专用)
时间格式冒号 HH:mm:ss HHmmss(避免 NLog 解析冲突)

七、第五步:配置 Program.cs

csharp 复制代码
using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

// 注册 HttpContext 访问器(NLog 获取 TraceId 和 IP 必须)
builder.Services.AddHttpContextAccessor();

// 清除默认日志提供器,启用 NLog
builder.Logging.ClearProviders();
builder.Host.UseNLog();

var app = builder.Build();
app.Run();

八、第六步:Oracle 定时清理 Job

8.1 创建 Job

sql 复制代码
-- ============================================
-- 创建 HJL 应用日志定时清理 Job
-- 每天凌晨 2 点,删除 180 天前的日志
-- ============================================

-- 安全删除旧 Job(如果存在)
BEGIN
    DBMS_SCHEDULER.drop_job('JOB_CLEAN_HJL_APPLOG');
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE != -27475 THEN
            RAISE;
        END IF;
END;
/

-- 创建新 Job
BEGIN
    DBMS_SCHEDULER.create_job (
        job_name        => 'JOB_CLEAN_HJL_APPLOG',
        job_type        => 'PLSQL_BLOCK',
        
        -- 清理逻辑:删除 180 天前的数据
        job_action      => 'BEGIN
                                DELETE FROM MESFLXRPT.HJL_API_APPLOG 
                                WHERE CREATE_TIME < SYSDATE - 180;
                                COMMIT;
                            END;',
        
        -- 首次执行:今天凌晨 2 点
        start_date      => TRUNC(SYSDATE) + INTERVAL '2' HOUR,
        
        -- 重复规则:每天凌晨 2 点
        repeat_interval => 'FREQ=DAILY; BYHOUR=2; BYMINUTE=0; BYSECOND=0',
        
        enabled         => TRUE,
        comments        => 'HJL WebAPI 日志自动清理:每天凌晨2点删除180天前的应用日志'
    );
END;
/

8.2 验证 Job 状态

sql 复制代码
-- 查看 Job 是否创建成功
SELECT job_name, state, next_run_date, comments
FROM user_scheduler_jobs
WHERE job_name = 'JOB_CLEAN_HJL_APPLOG';

预期结果

  • state = SCHEDULED
  • next_run_date = 明天凌晨 2 点

九、第七步:测试清理 Job

9.1 插入测试数据

sql 复制代码
-- 今天的数据(应该保留)
INSERT INTO MESFLXRPT.HJL_API_APPLOG (
    ID, LOG_LEVEL, CATEGORY, MESSAGE, CLASS_NAME, 
    METHOD_NAME, THREAD_ID, TRACE_ID, IP_ADDRESS, CREATE_TIME
) VALUES (
    'TEST_KEEP', 'INFO', 'Test', '今天的数据,应该保留', 'Test',
    'Test', '1', 'TRACE001', '127.0.0.1', SYSDATE
);

-- 5 天前的数据(应该被删除)
INSERT INTO MESFLXRPT.HJL_API_APPLOG (
    ID, LOG_LEVEL, CATEGORY, MESSAGE, CLASS_NAME, 
    METHOD_NAME, THREAD_ID, TRACE_ID, IP_ADDRESS, CREATE_TIME
) VALUES (
    'TEST_DEL1', 'INFO', 'Test', '5天前的数据,应该被删除', 'Test',
    'Test', '1', 'TRACE002', '127.0.0.1', SYSDATE - 5
);

INSERT INTO MESFLXRPT.HJL_API_APPLOG (
    ID, LOG_LEVEL, CATEGORY, MESSAGE, CLASS_NAME, 
    METHOD_NAME, THREAD_ID, TRACE_ID, IP_ADDRESS, CREATE_TIME
) VALUES (
    'TEST_DEL2', 'INFO', 'Test', '也是5天前的数据,应该被删除', 'Test',
    'Test', '1', 'TRACE003', '127.0.0.1', SYSDATE - 5
);

COMMIT;

9.2 查看测试数据

sql 复制代码
SELECT ID, MESSAGE, CREATE_TIME,
       CASE WHEN CREATE_TIME < SYSDATE - 1 THEN '会被删除' ELSE '保留' END AS 预期
FROM MESFLXRPT.HJL_API_APPLOG
WHERE ID LIKE 'TEST%'
ORDER BY CREATE_TIME;

9.3 手动执行清理 SQL(模拟 Job 逻辑)

sql 复制代码
-- 模拟 Job 的清理逻辑(把时间改成 1 天,方便验证)
DELETE FROM MESFLXRPT.HJL_API_APPLOG 
WHERE CREATE_TIME < SYSDATE - 1;

COMMIT;

9.4 验证结果

sql 复制代码
SELECT ID, MESSAGE, CREATE_TIME
FROM MESFLXRPT.HJL_API_APPLOG
WHERE ID LIKE 'TEST%'
ORDER BY CREATE_TIME;

预期结果

  • TEST_KEEP(今天)保留
  • TEST_DEL1TEST_DEL2(5天前)已删除

9.5 清理测试数据

sql 复制代码
DELETE FROM MESFLXRPT.HJL_API_APPLOG WHERE ID LIKE 'TEST%';
COMMIT;

十、第八步:验证日志写入

启动 WebAPI 项目,调用任意接口(如登录接口),然后查询数据库:

sql 复制代码
SELECT ID, LOG_LEVEL, MESSAGE, METHOD_NAME, TRACE_ID, IP_ADDRESS, CREATE_TIME
FROM MESFLXRPT.HJL_API_APPLOG
ORDER BY CREATE_TIME DESC
FETCH FIRST 5 ROWS ONLY;

预期结果

  • METHOD_NAME 显示 AdminLogin(或你的 Action 名称)
  • TRACE_ID 有值(如 0HNM4V...
  • IP_ADDRESS 有值(本机调试显示 ::1

十一、总结

步骤 操作 关键注意点
1 数据库建表 表名 HJL_API_APPLOG,字段用 CLOB 存长文本
2 安装 NuGet 包 4 个包:NLog、NLog.Web.AspNetCore、NLog.Database、Oracle 驱动
3 配置 appsettings.json 连接串集中管理,不暴露密码
4 配置 nlog.config 程序集名没有 .Core 、用 aspnet-mvc-action 取方法名
5 配置 Program.cs 必须加 AddHttpContextAccessor()
6 创建 Oracle Job 每天凌晨 2 点清理 180 天前数据
7 测试 Job 造测试数据 → 执行 DELETE → 验证 → 清理
8 验证日志写入 调接口后查数据库,确认字段完整

十二、后续维护

  • 查看 Job 执行历史

    sql 复制代码
    SELECT job_name, status, actual_start_date, run_duration
    FROM user_scheduler_job_run_details
    WHERE job_name = 'JOB_CLEAN_HJL_APPLOG'
    ORDER BY actual_start_date DESC;
  • 手动触发 Job(排查问题时用):

    sql 复制代码
    BEGIN DBMS_SCHEDULER.run_job('JOB_CLEAN_HJL_APPLOG'); END;
  • 删除 Job(如需停用):

    sql 复制代码
    BEGIN DBMS_SCHEDULER.drop_job('JOB_CLEAN_HJL_APPLOG'); END;

以上即为 HJL WebAPI 项目日志入库的完整操作流程。

相关推荐
孟陬2 小时前
国外技术周刊 #140:在 Jeff Bezos 的私密 Campfire 峰会上,我学到了关于亿万富翁的事
前端·后端
小闹5492 小时前
CLAUDE CODE生成可视化数据库工具
后端
星星电灯猴2 小时前
全面解决Charles抓取HTTPS请求响应中文乱码问题的方法与技巧
后端·ios
道友可好3 小时前
写给 AI 的入职手册,AGENTS.md
前端·人工智能·后端
sandnes3 小时前
把ToolUse循环做到生产级-错误处理与可靠性五件套
后端
掘金者阿豪3 小时前
全维度拆解具身智能:底层技术 + 实战落地 + 全球产业竞争
后端
秋天的一阵风3 小时前
✨ 代码秒跳转、自动补全?全靠 LSP 和 AST!
前端·后端·ai编程
用户298698530143 小时前
Java 中的 HTML 解析:从文件读取、URL 抓取到数据提取
java·后端
AskHarries3 小时前
ZJF.AI:简单、稳定、免费的图片托管与外链分享平台
后端