.NET Core 应用后台任务手动埋点最佳实践

前言

在制造业 MES 系统中,有一类非常简单、独立的任务经常使用底层的方法实现。它们通常不由 HTTP 请求触发,而是通过 while 循环、定时器或后台服务持续运行,用于轮询数据库、同步状态、处理队列、扫描工单或执行周期性补偿逻辑。

从自动埋点的视角看,这类任务天然缺少一个外部请求入口。以 ASP.NET Core 应用为例,HTTP 接口、数据库访问、部分中间件调用通常可以由 Datadog .NET Tracer 自动采集,但是类似后台 while 循环的底层方法,其本身不会自动形成一个完整的 Trace 根节点。因此,假设在循环体中包含 Oracle 访问,观测云中可能只能看到零散的数据库 Span,而无法回答“这一轮任务从什么时候开始、执行了什么、是否失败、耗时多久”这些运维和研发都真正关心的问题。

这并不是自动埋点能力不足,而是后台任务的执行模型与传统请求链路存在差异。HTTP 请求天然有入口、出口、状态码和耗时,而 while 循环管理的后台任务只有开发人员知道一轮任务的边界在哪里。对于某大型制造业客户的 MES 应用来说,客户希望通过链路了解定时任务的执行细节,因此就需要在自动埋点之外,为特殊后台任务补充少量手动埋点。

本篇文章通过测试 Demo https://gitee.com/baihr/dotnetcore-trace-test ,讨论一种适合特殊后台任务场景的方案:在任务循环外层增加少量手动埋点,将每一轮后台任务报告为一条可分析的调用链路。

观测云集成方案

在本场景中,观测目标并不是替换已有的自动埋点能力,而是补齐后台任务本身缺失的链路入口。整体方案可以拆成三个部分来看:

  • ASP.NET Core HTTP 请求仍然由 Datadog .NET Tracer 自动采集;
  • Oracle 数据库访问仍然由 Datadog .NET Tracer 自动采集;
  • 后台 while 循环每一轮任务由 .NET 内置的 ActivitySource 手动创建一个父 Span。

Demo 是一个 .NET 8 的 ASP.NET Core Minimal API 应用,提供 /health、/slow、/oracle/ping 三个接口,同时包含一个 FixedIntervalOracleWorker 后台任务。后台任务通过 while 循环按固定间隔访问 Oracle,用来模拟 MES 系统中常见的周期性后台任务。

这里需要特别注意一个边界:我们并不需要为 HTTP 接口或 Oracle 调用重新手写埋点。HTTP 请求和 Oracle 数据库访问已经有成熟的自动埋点能力,手动埋点只补在后台任务每一轮循环的最外层,用来创建一个 job.execute Span,使循环体中产生的 Span 作为任务 Span 的子 Span。最终希望看到的链路结构大致如下:

job.execute / fixed-interval-oracle-probe
  -> Oracle database span

这样,后台任务不再只是进程中的一段循环代码,而是变成了观测平台中可以被查询、聚合、分析和告警的任务执行单元。

为什么使用 .NET 内置 ActivitySource

很多人在谈到手动埋点时,会直接想到引入 OpenTelemetry SDK 或特定厂商的自定义 API。不过在 .NET Core 应用中,一个容易被忽略的事实是:.NET 本身已经提供了手动埋点入口,即 System.Diagnostics.ActivitySource 和 System.Diagnostics.Activity。

ActivitySource 可以理解为 .NET 里的埋点源,Activity 可以理解为一次具体的 Span。应用或类库通过 ActivitySource.StartActivity(...) 创建 Activity,采集器再决定是否监听、采样和上报这些 Activity。对业务代码来说,这种方式的优势非常直接:

  • 不需要额外引入 OpenTelemetry SDK NuGet 包;
  • 不需要在业务代码中绑定某个观测厂商的 API;
  • 可以继续复用 Datadog .NET Tracer 的自动埋点能力;
  • 对高频路径比较友好,没有监听器时 StartActivity(...) 可以返回 null,避免不必要的对象创建。

Demo 的项目文件中,唯一的业务依赖是 Oracle 驱动:

<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.26.200" />

手动埋点能力来自 System.Diagnostics 命名空间,而不是额外安装完整的 OpenTelemetry SDK 包。这一点在客户现场非常重要,因为很多生产系统对于新增依赖、版本兼容和发布流程都比较谨慎。相比引入一套新的 SDK,在已有自动注入基础上使用运行时内置 API 补充关键 Span,是一个侵入性更低的方案。

实践步骤

部署 Datakit 并开启必要的采集器

在观测云中注册账号后,进入“集成 - Datakit”,按照主机环境复制安装命令并完成 Datakit 部署。对于本例来说,需要开启 ddtrace 采集器,用于接收 Datadog .NET Tracer 上报的链路数据。

安装 Datadog .NET Tracer

访问 https://github.com/DataDog/dd-trace-dotnet/releases 下载并安装最新的稳定版本。

在代码中埋点

创建 ActivitySource

ActivitySource 适用于 .NET Core 5/6/7/8/9/10/11 .NET Framework 4.6.2/4.7/4.7.1/4.7.2/4.8,细节请参考:ActivitySource 类 (System.Diagnostics) | Microsoft Learn

Demo 中定义了一个稳定的 ActivitySource:

public static class Telemetry
{
    public const string ActivitySourceName = "dotnetcore-trace-test";

    public static readonly ActivitySource ActivitySource =
        new(ActivitySourceName, "1.0.0");
}

ActivitySourceName 是手动埋点的来源名称,建议保持稳定。它和 DD_SERVICE 不是同一个概念,前者表示埋点源,后者表示服务名。在测试项目中二者设置为同名只是为了排查时更加直观。

在 ASP.NET Core 依赖注入中,可以将它注册成单例,供后台任务使用:

builder.Services.AddSingleton(Telemetry.ActivitySource);
为后台 while 循环创建任务 Span

后台任务的核心逻辑在 FixedIntervalOracleWorker 中。每一轮循环开始时,使用 ActivitySource.StartActivity(...) 创建一个 job.execute Span:

using (var activity = _activitySource.StartActivity("job.execute", ActivityKind.Internal))
{
    activity?.SetTag("operation.name", "trace-test.job");
    activity?.SetTag("resource.name", "fixed-interval-oracle-probe");
    activity?.SetTag("span.type", "worker");
    activity?.SetTag("job.name", "fixed-interval-oracle-probe");
    activity?.SetTag("job.iteration", iteration);
    activity?.SetTag("interval.seconds", intervalSeconds);

    var result = await _oracleProbe.PingAsync("worker", stoppingToken);

    activity?.SetTag("oracle.success", result.Success);
    activity?.SetTag("oracle.elapsed_ms", result.ElapsedMs);

    if (result.Success)
    {
        activity?.SetStatus(ActivityStatusCode.Ok);
    }
    else
    {
        activity?.SetStatus(ActivityStatusCode.Error, result.Error ?? "Oracle probe failed.");
        activity?.SetTag("error", 1);
        activity?.SetTag("error.message", result.Error);
        activity?.SetTag("error.type", "OracleProbeFailed");
    }
}

这里有几个细节值得注意:

  • ActivityKind.Internal 表示这是进程内部工作,而不是 HTTP Server、HTTP Client 或消息队列消费者。对于后台定时任务来说,这个语义是合适的;
  • job.name、job.iteration、interval.seconds 这类标签并不是协议强制字段,而是为了便于后续查询和分析。客户真正排查问题时,往往需要按任务名、执行轮次、执行结果、耗时等维度进行过滤,因此这些业务标签非常有价值,根据实际情况增减即可;
  • 错误状态需要手动设置。如果某一轮任务失败,只写日志当然可以帮助排查,但是链路本身如果没有被标记为 Error,就很难在 APM 视图中按错误任务进行检索和聚合。因此 Demo 中在失败时同时设置 ActivityStatusCode.Error 和错误标签。

修改应用启动脚本

通过在应用启动脚本中增加环境变量启用探针自动注入并对探针进行初始配置:

  • 启用 Datadog .NET Tracer 自动注入:

    set CORECLR_ENABLE_PROFILING=1
    

    这样,HTTP 请求和 Oracle 数据库调用可以继续由自动埋点采集。对于 /oracle/ping 这种由 HTTP 触发的接口来说,请求入口天然会形成 Trace 根节点,Oracle 调用会作为数据库子 Span 出现在链路中,因此不需要额外手写 Span。

  • 配置探针将数据发到本机 Datakit 地址:

     set DD_TRACE_AGENT_URL=http://localhost:9529
    
  • 设置服务名、环境和版本等统一标签:

    set DD_SERVICE=dotnetcore-trace-test
    set DD_ENV=test
    set DD_VERSION=1.0.0
    
  • 启用 ActivitySource 采集

    Datadog .NET Tracer 默认不会采集所有基于 OpenTelemetry/ActivitySource 的手动埋点,需要显式打开:

    set DD_TRACE_OTEL_ENABLED=true
    

最终的完整启动脚本如下:

@echo off

rem Datadog .NET Core 自动注入
set CORECLR_ENABLE_PROFILING=1

rem Datadog 服务标识
set DD_SERVICE=dotnetcore-trace-test
set DD_ENV=test
set DD_VERSION=1.0.0

rem Datadog Agent 地址
set DD_TRACE_AGENT_URL=http://localhost:9529

rem 可选:日志与运行时指标
set DD_LOGS_INJECTION=true
set DD_RUNTIME_METRICS_ENABLED=true

rem 启用 OTel 手动埋点
set DD_TRACE_OTEL_ENABLED=true

rem 启用 Profile
set DD_PROFILING_ENABLED=1

C:\dotnet\dotnet.exe C:\dotnetcore-trace-test\publish\dotnetcore-trace-test.dll

配置生效后,后台任务每一轮执行都会产生一个任务 Span,Oracle 自动埋点产生的数据库 Span 会挂在当前 Activity 下,从而形成完整的后台任务调用链。

一个容易忽略的耗时问题

这类后台任务埋点最容易犯的错误,是把等待下一轮执行的 Task.Delay 也放进 Span 作用域里。这样观测平台中看到的任务耗时会接近调度间隔,例如 30 秒,而不是本轮真实业务执行时间。

错误的写法大致如下:

using (var activity = _activitySource.StartActivity("job.execute", ActivityKind.Internal))
{
    await _oracleProbe.PingAsync("worker", stoppingToken);
    await Task.Delay(interval, stoppingToken);
}

这种写法会让 Span 同时覆盖“业务执行时间”和“等待下一轮调度时间”,最终得到一个不准确的任务耗时。

Demo 中采用的写法是将等待逻辑放在 using Activity 之外:

using (var activity = _activitySource.StartActivity("job.execute", ActivityKind.Internal))
{
    await _oracleProbe.PingAsync("worker", stoppingToken);
}

await Task.Delay(interval, stoppingToken);

这个细节非常重要。后台任务的链路应该描述“一轮任务做了什么”,而不是描述“这个循环多久执行一次”。如果不区分这两个时间,链路数据会误导后续分析。

日志方案的边界

不过,手动埋点导出链路并不是这类后台任务的默认答案。对于简单的任务调度场景,如果我们只需要知道任务是否启动、是否结束、是否失败、耗时多久,其实通过日志就可以完成观测。比如后台任务每轮执行时输出如下结构化日志:

job=fixed-interval-oracle-probe iteration=1024 status=success elapsed_ms=35

或者在失败时输出:

job=fixed-interval-oracle-probe iteration=1025 status=failed error="Oracle probe failed"

这种信息已经足够支持很多任务观测场景。观测云支持通过 DataKit 采集主机日志、容器标准输出和多种第三方日志来源,并通过 Pipeline 对日志做结构化处理,最终通过日志完成任务分析和告警。也就是说,如果 MES 后台任务本身已经输出了任务名、执行结果、耗时、错误原因等日志,那么完全可以不修改代码,直接通过日志采集和处理完成任务观测。

从方案选型上看,可以采用如下判断:

  • 如果只关心任务执行结果、耗时、失败原因,优先使用日志;
  • 如果需要任务级告警,也可以优先使用结构化日志加日志监控器;
  • 如果需要把后台任务、数据库调用、HTTP 调用或消息处理放在同一条 Trace 中分析,再考虑手动链路;
  • 如果需要在 APM 视图中按任务 Span 聚合错误、耗时和下游调用,手动埋点就是更合适的方案。

换句话说,手动埋点解决的是“后台任务作为调用链入口”的问题,而日志解决的是“后台任务作为事件记录”的问题。两者不是互斥关系,但也不应该混用成一种不加判断的默认动作。

模拟验证

应用启动后,可以访问以下接口验证 HTTP 自动埋点:

Invoke-RestMethod http://127.0.0.1:5088/health
Invoke-RestMethod http://127.0.0.1:5088/oracle/ping
Invoke-RestMethod http://127.0.0.1:5088/slow

其中 /oracle/ping 会产生 HTTP 请求 Span 和 Oracle 数据库 Span。后台 FixedIntervalOracleWorker 则不需要外部请求触发,它会按照 Worker:IntervalSeconds 配置周期性执行 Oracle 探测。稍等片刻后,在观测云“应用性能监测 - 链路”中可以观察到两类链路:

  • 由 HTTP 请求触发的接口链路;
  • 由 job.execute 触发的后台任务链路。

对于后台任务链路来说,重点观察以下信息:

  • 根 Span 是否为 job.execute;
  • 资源名是否为 fixed-interval-oracle-probe;
  • Oracle 数据库调用是否作为子 Span 出现;
  • job.iteration、oracle.success、oracle.elapsed_ms 等标签是否存在;
  • 失败任务是否被标记为 Error。

实际数据如下,注意,请按需增减数据标签:

总结

.NET Core 后台 while 任务不会天然产生完整调用链,这不是采集器能力不足,而是这类任务本身缺少外部请求入口。对于制造业 MES 中确实需要链路化分析的后台任务,可以使用 .NET 内置的 ActivitySource 在任务外层补一个 Span,并继续让自动埋点负责数据库、HTTP 等下游调用。

这个方案的价值在于:埋点足够少,不引入额外 OpenTelemetry SDK 依赖,也不破坏原有自动埋点路径。对于客户现场来说,它既能满足“通过链路了解定时任务细节”的诉求,又能把代码改造控制在非常小的范围内。

不过,从可观测方案设计的角度看,我们仍然需要保留一个更朴素的判断:简单任务优先用日志观测,复杂任务再用链路补齐结构。观测云已经支持日志采集、处理、分析和告警,很多任务调度场景完全可以不修改代码,仅通过日志完成观测。只有当任务执行过程需要与数据库、HTTP、消息等下游调用形成一条完整分析路径时,手动埋点才是更合适的选择。

在良好集成的基础上,开发和运维角色能够从任务 Span 进入链路细节,也能够从日志进入事件上下文,两条路径互相补充,最终形成对后台任务更加确定的分析能力。

相关推荐

技术实战:观测云告警到飞书群并精准@相关人

本文介绍观测云如何通过 Webhook 将告警推送至 DataFlux Func,完成消息格式转换并 @责任人,实现精准提醒。

观测云的博客 346

基于 OpenTelemetry 实现 LangChain 观测云全链路可观测

文章介绍基于 OpenTelemetry 为 LangChain 应用构建 Zero-Code 全链路可观测方案,无需修改业务代码即可自动采集工作流、Prompt 与模型调用链路,帮助快速定位性能瓶颈与故障,并分析模型输入输出及 Token 消耗。

观测云的博客 343

Spring AI + Spring AI Alibaba 接入观测云最佳实践

文章介绍 Spring AI 与 Spring AI Alibaba 应用接入观测云的实践方案,通过 OpenTelemetry 自动采集 Agent、LLM 与 Tool 调用链路,实现性能、Token、模型及工具调用的统一观测,提升 AI 应用故障排查与性能优化效率。

观测云的博客 463

观测云 Agent 输出质量评估上线!智能体答得好不好别再靠感觉

观测云上线Agent质量评估系统,支持Prompt Eval和Schema Eval,自动检测输出质量与结构,提升智能化服务可靠性。

观测云的博客 665

观测云产品更新 | Agent 监测、故障中心、应用性能监测、日志、基础设施等

观测云新增AI API计费、告警策略优化、Agent Skills提升、故障作战室、LangChain兼容、性能监测与日志优化等功能。

观测云的博客 361

AI时代的用户访问监测:观测云带你身临其境体验用户与前端UI交互旅程

观测云 RUM 通过真实用户访问数据呈现应用整体体验情况。在应用列表和分析看板中,团队可以查看用户访问量、页面性能、错误情况等关键指标,判断问题影响范围。

观测云的博客 297

观测云全新上线「全局工作区」:进一步统一数据查询

观测云全局工作区上线,支持多模块Tab切换,提升故障排查效率。

观测云的博客 412

Fastjson 高危 RCE(CVE-2026-16723):如何用观测云快速发现并完成修复

本文针对 Fastjson 高危 RCE 漏洞 CVE‑2026‑16723,剖析其影响与利用条件,指出 Maven 静态排查的局限。观测云依托 ddtrace 运行时数据,完成漏洞检测定位告警修复闭环,提供升级、应急防护、迁移 fastjson2 处置方案。

观测云的博客 450

观测云应用性能监测 :帮助团队定位性能瓶颈

观测云 APM 将应用运行过程中的关键性能信息连接起来,帮助团队从发现异常服务,到定位请求链路,再到深入分析代码性能,逐步还原问题发生的过程。

观测云的博客 355

基于 DataFlux Func 构建 Vibe Coding 运行底座

DataFlux Func 可以作为 Vibe Coding 的运行底座。Func 本身提供脚本管理、函数执行、定时任务、函数 API、连接器、环境变量和运行日志等能力;MCP 则让 AI Agent 能够以标准化方式连接 Func,把“写代码”和“运行代码”的链路打通。这套实践不限定某一个 AI 客户端。只要客户端支持 MCP 或具备等价的工具接入能力,例如 Cursor、Claude Desktop、OpenCode、Codex 等,都可以按相同思路接入 Func。

观测云的博客 303

AI时代日志分析:观测云让你从海量日志中找到关键线索

观测云日志围绕日志采集、处理、存储、查询分析以及异常定位等场景,将分散在不同来源的日志信息集中管理。通过 Pipeline 对日志进行解析和结构化处理,通过 GuanceDB 3.1 支撑海量数据存储与分析,帮助团队将原始日志转化为可查询、可分析的数据基础。

观测云的博客 198

AI时代基础设施监控:观测云让你的系统指标清晰可见

通过统一的数据视角,将基础设施、应用和业务信息连接起来,让工程团队从发现问题、分析原因到定位影响范围,形成更加完整的问题排查路径。

观测云的博客 182

观测云会话重放:把玩家游戏中的那一秒卡顿完整回放给研发团队

观测云 Browser SDK 3.3.7 WebGL / WebGL2 Session Replay,让游戏画面、用户操作、错误和性能数据回到同一条时间轴。

观测云的博客 335

观测云集成飞书 SSO 最佳实践

本文介绍如何基于 OIDC 协议,通过飞书集成平台、飞书开放平台两大渠道对接观测云实现 SSO 单点登录的完整方案,清晰区分了两大飞书平台的能力差异、适用场景与配置规范,形成了一套完整、可落地的跨系统身份集成流程。

观测云的博客 217

观测云 AI Agent Teams 安全能力漏扫与修复实践场景

一句话标准:这个 Agent 不能只是“会扫”,而要像一名资深 AppSec 工程师一样,知道问题在哪里、为什么危险、先修哪个、怎么安全地修、修完如何证明真的修好了漏洞扫描不是终点,修复闭环才是安全能力真正落地的标志。观测云 AI Agent Teams 的最佳实践,是让 Agent 在安全团队、研发团队和运维团队之间承担“证据整理、优先级判断、修复建议、协同跟踪、复测复盘”的工作,同时把扫描只读、修复审批、官方依据、脱敏输出和证据关闭作为治理底线。

观测云的博客 381

零码改造!Go 语言应用上报观测云完整最佳实践

本文详解 OpenTelemetry 标准协议接入观测云 DataKit 实操,零码无侵入搭建可观测体系,实现日志、调用链路联动分析。

观测云的博客 357

观测云产品更新 | Agent Teams、Agent 监测、日志、基础设施等

本期更新主要为观测云更新和 DataKit 更新。

观测云的博客 366

观测云 AI 智能体为什么能精准排查并修复故障?把排障经验写进 Runbook

立刻在观测云免费开通 Obsy AI 智能服务,体验 Obsy AI Copilot 和 AI Agent Teams 强大的故障排查能力;开通观测 Agent Teams,让 观测 AI 智能体团队 7×24 安全的在你生产环境里跑起来,

观测云的博客 543

万卡级算力集群 GPU 监控:基于观测云全链路观测实践

本文介绍如何通过观测云监控万卡级算力集群 GPU,与应用性能 Trace、业务日志、基础设施指标在统一平台中实现全栈关联与跨层下钻,真正构建起从硬件到业务的完整观测链路。

观测云的博客 490

观测云基于 AI Agent Teams 能力的场景实践

本文通过三个场景实践,介绍观测云 AI Agent Teams 如何将可观测性数据、智能分析与自动化运维能力深度结合,帮助用户更快发现问题、定位问题并解决问题。

观测云的博客 581

观测云产品更新 | Agent Teams、Agent 监测、统一目录、基础设施、应用性能监测等

观测云新增企业微信消息渠道、任务管理优化、容器化部署、Agent监测升级、分析看板改进、时间轴视图、联动变更、拓扑筛选、日志优化、性能监测等功能。

观测云的博客 238

观测云入选 OpenTelemetry 官方 Vendors

观测云面向 OpenTelemetry 构建的原生接入与可观测能力,正式进入全球 OpenTelemetry 生态视野。

观测云的博客 453

当 Agent 写代码快过人类审查:软件工程的信任链必须重建

下一阶段的软件工程,不是让人类追着每一行 AI 代码跑,而是让 Agent 提交证据,并让线上系统持续产生可用于纠错的信号。

观测云的博客 357

带你用观测 AI 智能体:自主分析与修复故障

观测云通过 Guance AI Agent Teams,为业务调查与恢复提供了统一的数据基座、受控的执行边界、结构化的证据输出。

观测云的博客 221

观测云正式支持 Qoder 可观测

在「观测云控制台」-「Agent 监测」,点击右上角「接入 Agent」,选择「Qoder」即可快速接入。

观测云的博客 296

带你用观测 AI 智能体:接入你的工作群聊

观测云通过 Guance AI Agent Teams,为生产侧的可观测 Agent 提供了统一的数据基座:日志、指标、Trace、事件,全部统一采集、统一标签、统一存储。

观测云的博客 205

观测云正式支持 Claude Code 可观测

在「观测云控制台」-「Agent 监测」,点击右上角「接入 Agent」,选择「Claude Code」即可快速接入。

观测云的博客 338

观测云产品更新 | Obsy AI、Agent Teams、Agent 监测、安全监测、故障中心等

本期更新主要为观测云更新和 DataKit 更新。详情见官网文档:https://docs.guance.com/release-notes/

观测云的博客 233

带你用观测 AI 智能体:与本地 Coding Agent 协作排障

观测云通过 Guance AI Agent Teams,为生产侧的可观测 Agent 提供了统一的数据基座日志、指标、Trace、事件,全部统一采集、统一标签、统一存储。

观测云的博客 384

观测云 6 月产品升级报告 | 观测 AI Agent Teams 全面升级、观测云安全功能强化、拨测与跨空间协同能力增强

2026 年 6 月,我们收到不少用户反馈。我们把这些声音,一条条变成了产品更新,以下是本月最值得关注的 9 大升级亮点,每一个都为了让你的观测体验更顺滑、更贴合实际业务场景。

观测云的博客 369
上一篇: 观测云产品更新 | API Key、日志、Agent 监测、故障中心、可用性监测等
下一篇: 观测云产品更新 | 日志、应用性能监测、管理等
观测云
观测云 上海驻云信息科技有限公司官方账号 上海驻云信息科技有限公司官方账号
博客等级 码龄5年 2589粉丝 560原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值