
1. 先看本质日志记录器到底在记录什么1.1 一条好日志必备的字段后端服务里最容易被人忽略但又最关键的东西往往是日志。平时一切正常时没人看它一旦出问题所有人都在问你“日志里有什么”。我维护过好几个 ASP.NET Core 服务对这一点深有体会。今天想分享的这个日志记录器 WatchDog.NET是我最近在 .NET 10 预演项目里用得比较顺手的一个它把 HTTP 请求、异常、自定义业务日志统一收进来再通过一个自带的面板做实时查询MIT 开源、商业友好接入成本和替换成本都比想象中低。先聊一个基础问题一条日志到底应该记录什么很多团队写的日志就是一句话“登录成功”“订单创建成功”这放在排查问题时基本没用。真正可用的日志至少应该包含时间戳、日志级别、消息模板、来源组件、请求路径、状态码、耗时、TraceId如果是异常还要有完整堆栈。WatchDog.NET 在存储层把 HTTP 请求、异常、自定义日志三类分开每类都有固定的字段模型。比如 HTTP 请求日志会记录 Method、Path、QueryString、RequestBody、ResponseBody、客户端 IP、StatusCode、ResponseTime异常日志则记录 Source、StackTrace、Message 和触发位置。这些字段看起来琐碎但线上出问题时缺了任何一个都可能要多花几个小时定位。我自己有个习惯判断一套日志方案好不好用先不看它支持多少种存储而是看它能不能在一条日志里把“谁在什么时间、调用了什么接口、拿到了什么结果、花了多久”完整还原出来。很多传统方案不是做不到而是要么需要额外开发中间件要么需要把文件日志手动翻出来再拼上下文。WatchDog.NET 在这方面做得比较省事它直接以中间件的形式挂到请求管道里请求一进来就开始记录响应结束后落库你不需要自己拼字段。1.2 日志级别与筛选原则日志级别是很容易被忽略但影响很大的设计。Debug 用于开发调试Trace 更细Information 记录常规业务操作Warning 表示潜在问题但不影响主流程Error 是已经发生的异常Critical 是可能导致服务不可用的致命错误。日常排障时如果全项目都只写 Information 和 Error一旦出问题你面对的就是大量无关噪音真正有用的错误信息被淹没。WatchDog.NET 的面板里可以对日志级别做筛选不同级别还有不同的颜色标识这一点看起来只是界面细节实际用起来特别舒服。有一次我在现场排查一个偶发 500打开面板直接把日志过滤到 Error再把时间范围缩到精确的几分钟内问题堆栈一下就出来了。如果日志全部混在一起根本没有这个效率。我建议大家在项目启动时就把日志级别规范定下来外部调用入口记录 Information业务规则校验失败记录 Warning捕获到异常记录 Error只有服务起停、配置加载这类事件才用 Critical。Debug 日志不是不能写而是要用严格的条件控制避免在生产环境刷屏。WatchDog.NET 对级别的处理比较直观你在代码里用标准ILogger输出它就会按级别收进对应的标签页不需要额外做映射。1.3 传统日志方案的不足传统方案不是不能用而是各有各的别扭。内置ILogger本身只是一个抽象接口默认 Provider 只把日志写到控制台、事件源或文件团队小的时候还能接受服务一多就开始头疼。Serilog 非常强大结构化日志支持也很好但要用得舒服通常还要配套搭建一个日志采集和查询系统这一套下来至少多维护两个组件。NLog 胜在配置灵活、路由规则丰富但它的强项是文件日志和格式化输出查看体验依然停留在“打开文件、手动搜索”的层面。不是说这些方案不好而是它们解决的重点可能和你的需求不完全匹配。对于大多数 ASP.NET Core 10 单体服务或中小型系统你真正需要的可能不是再引入一套重量级日志平台而是一个开箱即用、能看能查、不用维护太多额外组件的日志记录器。WatchDog.NET 正好卡在这个位置上这也是我一开始会持续关注它的原因。2. 为什么我会盯上 WatchDog.NET2.1 接入成本是真的低我有过为了看日志搭了一套完整采集链路的经历那套方案在大型项目里很合理但对一个团队只有几个人的模块来说运维压力反而超过了日志本身带来的价值。所以看到 WatchDog.NET 的接入方式时我第一反应是“这玩意儿居然这么轻”。它就是一个 .NET 的日志组件通过 NuGet 包引用进项目然后注册相关服务再在请求管道里挂上两个中间件就完成了最基本的接入。不需要单独部署数据库服务默认采用本地存储方案也不需要配置文件采集器更不需要维护额外的日志面板站点。对单体应用和微服务中的单个服务来说这是在“没有任何日志可视化”到“有面板能实时查日志”之间成本最低的一条路。我实测过的最小接入代码大概只需要改动Program.cs里的几行再加上一个appsettings.json里的连接配置。从新建项目到面板出现日志十分钟内可以走完。对比我之前搭那套采集链路时花的两三天这个差距非常明显。2.2 自带面板解决“看日志难”问题日志组件最容易被低估的能力是“查看”。很多方案负责把日志写下来但你怎么看、怎么看快是完全另一回事。容器化部署之后我见过不少同事遇到问题第一反应是docker logs然后在一大堆无结构输出里艰难找线索。如果服务还扩容了多副本想看某一次请求的完整上下文几乎要靠运气。WatchDog.NET 自带一个管理面板服务启动后通过浏览器访问固定地址就能进入。面板里可以按时间范围、日志级别、关键字、状态码等条件过滤日志HTTP 请求、异常、自定义日志分开展示。对于开发环境联调和线上问题回溯这个“自带 UI”的价值非常大相当于省掉了一套独立的日志查询平台。我自己的经验是日志工具一定要能让“发现问题的人”直接上手看而不是每次都要叫运维帮忙导文件。WatchDog.NET 的面板在团队里推广起来几乎没有学习成本打开、登录、筛选三步就完事。这一点比很多功能华丽但配置复杂的方案实在得多。2.3 MIT 许可证带来的安全感再聊一下标题里那个很显眼的“MIT 开源商业友好”。作为开发人员我们选第三方组件时不能只看功能还要看许可证。WatchDog.NET 走的是 MIT 许可证这意味着你可以自由使用、复制、修改、合并、发布、分发、再许可和销售副本唯一的主要义务是保留原版权声明和免责声明。对于商业项目来说MIT 许可证意味着你可以放心把它集成到自己的产品中不需要担心开源传染性也不需要因为用了这个组件就去公开整个项目的源代码。对于内部系统、外包项目或者商业化软件这种商业友好非常重要。我选组件时一般会优先看许可证如果可选方案许可证很严格哪怕功能再合适我也会尽量绕开。WatchDog.NET 在这点上几乎没有额外的法务负担。3. 核心功能拆解它到底能做什么3.1 HTTP 请求与响应记录WatchDog.NET 最核心的能力之一就是把经过应用的 HTTP 请求自动记录下来。你在浏览器里点击一次页面或者调用一个 API中间件会记录请求的 Method、路径、查询字符串、请求体、响应体、状态码、耗时、来源 IP 等信息并全部写入存储。面板里可以按路径、状态码、耗时区间做筛选定位慢接口和异常状态码非常高效。这里有一个操作禁忌记录请求体和响应体虽然是好功能但也意味着所有经过的数据都可能被持久化。比如登录接口的密码字段、支付接口的卡号信息如果原样记录不仅可能违反数据保护要求还会成为黑客攻击后的敏感数据泄露点。我建议要么只针对部分接口记录 Body要么在记录之前做脱敏处理要么干脆关闭请求体和响应体的记录只保留状态码、耗时和路径。WatchDog.NET 的相关配置项是有的务必要看一眼。另外接口耗时这个字段对性能排查特别有用。我处理过一次“某些用户反馈页面很慢”的问题通过面板按耗时排序很快就发现是某个导出接口在数据量大时超过 10 秒。如果只靠代码走查这个接口混在一堆正常接口里根本不知道从哪下手。3.2 异常捕获与告警WatchDog.NET 提供了异常日志中间件可以捕获应用处理过程中没有被处理的异常并记录完整堆栈。配合面板里的异常标签页你能看到异常发生的时间、类型、消息、来源源码位置以及调用链信息。对于定位偶发 500 这类问题这个功能比从控制台翻日志靠谱得多。有一点要注意它捕获的主要是未被上层 try-catch 处理的异常。如果你在代码里习惯性地把所有异常用try-catch包住然后只是Console.WriteLine一下那么这些异常并不会自动进入面板。正确的做法是捕获后通过ILogger.LogError明确记录或者把异常重新抛出交给全局处理。我自己踩过这个坑之前有一个内部接口的异常被吞掉日志面板里一直是空的后来排查才发现异常在某个工具类里被 catch 后只记到了系统日志没有走统一日志管道。如果你需要主动告警能力可以配合面板轮询或者外部监控来消费异常记录。WatchDog.NET 本身更偏重记录和查询不擅长复杂告警但是作为异常事件的可靠来源已经足够支撑后续告警体系。3.3 自定义业务日志与 ILogger 集成除了 HTTP 请求和异常WatchDog.NET 还实现了自定义日志的收集接入了标准ILogger接口。你在业务代码里写的_logger.LogInformation、_logger.LogError等调用会统一进入面板的自定义日志页签。这样业务日志、异常日志、请求日志可以在同一个界面上交叉查看排查上下文时不用来回切换系统。我个人比较习惯在关键业务节点打日志时带上业务编号比如订单号、用户 ID。WatchDog.NET 支持结构化日志模板也就是说可以用LogInformation(处理订单 {OrderNo}, orderNo)这种写法面板里搜索订单号时就能快速定位到对应的日志。这个习惯一旦养成线上排查效率会明显提升。举个例子在一个支付回调场景里我只在回调入口、验签成功、更新订单状态、发送通知这四个节点各写一行日志并把订单号带进去。回调出问题时在面板里搜订单号整个调用链一目了然几乎不需要再去翻代码看逻辑。3.4 内置面板的查询与过滤能力内置面板不只是展示还支持按时间范围、日志级别、关键字、状态码、接口路径这些维度过滤。开发环境里我习惯按当前正在调试的接口路径筛选日志只关注那一条请求链线上环境则按错误码或异常类型过滤快速缩小范围到可疑点。相比用 grep 翻文件日志这种过滤体验接近专用日志查询系统。面板的自动刷新能力也很实用。前端联调时你不需要来回刷新页面日志会实时滚动接口请求响应后马上就能看到记录。对开发人员来说这种反馈速度会直接影响调试效率。4. 把它装进 ASP.NET Core 10 项目的完整步骤4.1 环境准备与包引用在动手之前先准备好环境。这里讨论的场景是 ASP.NET Core 10如果你还在使用更早的 .NET 8 或 .NET 9接入逻辑也基本一致只是项目模板和部分 API 名称可能有差异。我建议直接用一个最小 Web 项目来做实验避免被已有项目的复杂配置干扰。创建项目可以选择命令行或集成开发环境命令大致是dotnet new web -n DemoWatchDog cd DemoWatchDog然后通过 NuGet 添加 WatchDog.NET 包dotnet add package WatchDog.NET不建议在没有任何概念的情况下直接拉最新预览版稳定版本就好。引用包之后Program.cs顶部需要包含 WatchDog 的命名空间通常能看到using WatchDog;这样的代码。4.2 最小配置Program.cs 需要改哪几行个典型的最小配置可以长这样using WatchDog; var builder WebApplication.CreateBuilder(args); builder.Services.AddWatchDogServices(config { config.IsAutoClear true; config.ClearTimeSchedule 0 */6 * * *; config.WatchDogUsername admin; config.WatchDogPassword your-strong-password; }); var app builder.Build(); app.UseWatchDogExceptionLogger(); app.UseWatchDog(); app.MapGet(/, () Hello WatchDog.NET); app.Run();这段代码做了三件事注册 WatchDog.NET 服务、开启异常日志中间件、开启 HTTP 记录和面板。AddWatchDogServices里的配置项里WatchDogUsername和WatchDogPassword用于登录面板建议务必显式配置不要依赖默认值IsAutoClear表示按计划自动清理过期的日志避免存储膨胀ClearTimeSchedule用 cron 表达式控制清理频率。我之前见过有人把这段配置写到一半账号密码用了弱口令结果面板被同事传得到处都是。不管项目是不是跑在公网面板登录凭证都不建议用默认密码或者太简单的组合。4.3 配置外部数据库持久化WatchDog.NET 默认采用本地嵌入式存储开发环境完全够用。但如果你希望日志存储到共享数据库或者服务有多个实例我建议在注册服务时指定数据库连接。类似这样的逻辑var connectionString builder.Configuration.GetConnectionString(WatchDogConnection); builder.Services.AddWatchDogServices(config { config.SetExternalDbConnectionString(connectionString); // 其他配置省略 });在appsettings.json里需要补一个连接字符串{ ConnectionStrings: { WatchDogConnection: Server.;DatabaseWatchDog;User Idsa;Passwordyour-password;TrustServerCertificateTrue; } }连接字符串的写法和你选择的数据库有关这里只是示例。配置外部库以后多个实例可以共享一份日志数据面板查到的内容不再是单机视角。不过要提醒一句此时数据库的备份、权限和连接数都变成你需要承担的责任别把日志库当成不需要维护的附属设施。4.4 面板访问与登录认证程序跑起来后正常情况下面板地址是/watchdog具体以你实际版本为准启动日志通常会有提示。浏览器访问该地址输入配置的账号密码就能看到面板。如果访问遇到 404先检查中间件是否注册成功再确认路由前缀是否被项目自己的约定覆盖。面板本质上是应用内部的一个 UI如果服务跑在公网环境我建议用网关层做一层访问限制至少加 IP 白名单或反向代理认证不要直接把面板完全裸露。日志包含的信息量很大虽然不是绝对敏感但权限控制做好能避免很多不必要的风险。这是实操中很容易被忽略的一步。4.5 日志清理与保留策略再展开说一下日志清理。日志组件接入后最容易犯的错误是“只记录不清理”存储里的日志越来越多最后面板查询变得非常慢。IsAutoClear和ClearTimeSchedule这两个配置就是用来控制这个问题的。ClearTimeSchedule支持 cron 表达式比如0 0 * * *表示每天零点清理一次0 */6 * * *表示每六小时清理一次。我一般建议开发环境保留最近 3 到 7 天的日志生产环境根据项目要求保留 30 天左右。清理太频繁会把排障时需要的历史证据删掉清理太慢则影响面板性能。这个节奏没有绝对标准建议按项目实际流量调整。config.IsAutoClear true; config.ClearTimeSchedule 0 0 * * *;这里的 cron 表达式是标准的五段格式如果你不熟悉可以先用在线工具验证再填进去。5. 和其他日志方案放在一起比5.1 各自擅长什么没有万能的日志方案只有适不适合。内置ILogger是所有 ASP.NET Core 应用自带的基础设施优点是零成本缺点是默认输出能力有限。Serilog 是结构化日志里的老牌选手Sink 生态非常丰富Kafka、Elasticsearch、文件、数据库都能接。NLog 的特点是配置灵活、性能稳定老项目里出现频率很高。WatchDog.NET 的特点则是轻量、自带面板、HTTP 请求和异常日志开箱即用。我整理了下面这张对比表算是个人使用感受对比维度内置 ILoggerSerilogNLogWatchDog.NET接入成本最低中等中等低结构化日志基础支持非常强较强支持标准模板可视化面板无需配套组件需配套组件自带HTTP 请求日志需手写中间件需第三方实现需自己开发自动记录异常日志需手写全局处理需配置 Sink需配置自动捕获外部存储扩展自己实现丰富丰富可选外部数据库许可证内置宽松BSDMIT5.2 什么时候可以替换什么时候要共存如果你所在的项目已经有一套完整的日志平台比如日志都统一写到 Elasticsearch由 Kibana 或 Grafana 做可视化那么 WatchDog.NET 不是必需的硬加进去反而会造成日志多头管理。但如果你只是几个服务不想为了看日志就引入一套采集和分析链路那么 WatchDog.NET 可以在几分钟内给你一个靠谱的查看界面。还有一种组合方式保留 Serilog 作为主力日志管道负责把日志采集到文件或 Kafka同时利用 WatchDog.NET 的面板做开发联调时的实时查看。这个场景下两者可以共存不冲突。不过我一般不推荐在生产环境同时把两份日志都完整落库那会带来双倍的存储和写入开销。5.3 什么时候不建议使用有几种情况不建议用 WatchDog.NET。一是你已经有了严格的日志权限体系要求每个角色只能看指定范围的日志而你的部署规模又很大这时候可能还是上专业日志平台更合适。二是你只是想把日志写进文件完全不需要 UI那多带一个面板反而多一个暴露面。三是微服务实例特别多时每个实例各带一个面板统一查询体验会很差建议要嘛共享数据库要嘛在更上层做日志聚合。选型不是越强越好而是越匹配越好。WatchDog.NET 在单体应用、小型系统、内部工具、开发联调环境这些场景下性价比很高但把它硬塞进一个已经成型的大型日志架构中意义并不大。6. 实战中容易踩的坑和排查实录6.1 中间件顺序不对导致日志缺失中间件注册顺序直接影响日志捕获效果。UseWatchDogExceptionLogger()要尽早注册最好放在其它自定义中间件之前这样未被处理的异常才能被它截获。如果项目里还有自己的全局异常处理中间件要注意两个中间件的先后顺序否则异常可能在到达 WatchDog 之前就被转成了普通响应面板里自然看不到。我遇到过一种情况某个服务把全局异常处理写在最前面异常被它处理之后直接返回了友好提示WatchDog 的异常中间件完全没机会看到原始异常。后来调整了顺序让 WatchDog 异常中间件先执行才看到真实堆栈。这里的关键是理解异常日志中间件的职责是“观察并记录”而不是“处理并响应”。6.2 面板打不开、404 或白屏面板访问不了时先确认应用是否处于运行状态再检查app.UseWatchDog()是否真的执行到了。如果项目里配置了 URL 前缀、虚拟目录或者反向代理重写规则面板的默认路径可能被改变。启动日志和控制台输出通常会有提示顺着这个信息找最快。白屏问题很多时候和静态资源路径有关。我的排查步骤一般是先直接访问面板 URL 看返回什么状态码再用浏览器开发者工具看是否有资源加载失败最后确认 Web 项目是否启用了静态文件支持。UseStaticFiles()如果被项目特意关闭或顺序不对面板的样式和脚本资源可能加载不出来。6.3 数据库连接字符串写错配置外部数据库时连接字符串写错是一个典型问题。服务可能正常启动HTTP 接口也正常但你访问面板时一直转圈或报数据库连接失败。原因是日志组件往往是延迟写库的配置错误不会在启动时立刻暴露。我的建议是切换数据库之前先用一个最小连接测试确认连接信息可用。另外在开发环境不要一上来就用外部库先用默认嵌入模式跑通功能再切换到外部存储这样能把问题维度拆开。连接串里的账号权限也要注意只给它日志库的读写权限就可以不要给过高权限。6.4 日志量过大导致接口变慢记录请求体和响应体听起来很方便但流量大的时候每一个请求的参数和响应内容都要在内存里读取、序列化、再写入存储性能开销肉眼可见。如果某个接口是文件上传或大报表导出记录 Body 甚至会直接拖慢接口响应。我在实际项目中就遇到过一个日志量很大的服务接入前面板之后接口平均耗时涨了不少。排查下来发现是记录了所有的上传请求体几 MB 的文件内容被反复处理。后来的处理方式是关闭这些接口的 Body 记录让中间件只记录路径、状态码和耗时性能恢复到了正常水平。生产环境建议慎重开启 Body 记录优先保证业务链路的性能。6.5 敏感信息记录与脱敏这一点单独拿出来说是因为很容易被低估。如果你开着 RequestBody 和 ResponseBody 记录登录密码、Token、身份证号等字段都有机会落到数据库。一旦日志库被未授权访问后果比日志本身要严重得多。我的处理原则是默认不开 Body 记录确有必要时只针对特定路径开启并且在进入日志组件之前对字段做脱敏。脱敏逻辑可以放在业务层也可以在你自己的中间件里提前把敏感字段替换掉。哪怕内部系统也不建议原样保存明文密码和令牌。这个习惯养成之后无论用哪一款日志组件都不会踩到数据记录的坑。6.6 多实例部署时日志分散多个实例同时跑的情况下如果每个实例都用本地默认存储你会在两个不同的地方看到两份日志排查问题时要记住自己在看哪一台机器。把存储切到外部共享数据库后日志就能在同一个面板里出现。但是要注意面板仍然部署在每个实例上只是数据源统一了。如果实例多了更合理的方式是在网关层统一入口或者在日志记录的同时主动推送到集中式日志系统。我们当时为了解决多实例查看问题短期方案是把连接串改成共享数据库长期方案还是把关键业务日志往统一的日志通道同步。这两个阶段并不冲突先解决当下能不能看到日志再考虑要不要建设更完整的链路。6.7 注意包版本和 API 变化开源组件迭代快你参考的配置写法可能会因为版本变化而略有差异。我第一次接入时部分配置字段名就和当时网上看到的不太一样。遇到这种情况不要慌看一下包自带的文档和智能提示基本上都能找到对应的新字段。这个问题在 .NET 生态里很常见不能因为编译报错就断定组件不行。7. 个人经验和使用建议说了这么多最后分享一点我个人的体会。日志组件选型不用追求大而全但要把“看得见”放在第一位。很多团队花了不少精力在日志的采集和存储上最后发现排查问题时最难的不是日志没存下来而是根本看不到或者看到了找不到重点。WatchDog.NET 对很多 ASP.NET Core 10 项目来说正好补上了“看得见”这一块。它自带面板、MIT 开源、商业友好用来作为项目的日志记录器是省心省事的选择。再分享一个小技巧新项目接入时我一般会先把它当作唯一日志面板用几天确认 HTTP 请求日志、异常日志和自定义日志能覆盖日常排查的主要场景再决定要不要接外部日志平台。很多项目其实到了这一步已经够用了不需要为了“显得专业”就去引入一套重平台。等你真的遇到跨服务链路追踪、海量日志分析这类问题再建设集中式日志体系也不迟。先把每天的日志看得清清楚楚比什么都重要。