.NET 8应用中使用OTEL实现长时间运行作业的可观测性方案咨询
.NET 8应用中使用OTEL实现长时间运行作业的可观测性方案咨询
嘿,针对你这个.NET 8里长时间运行集成作业的可观测性需求,我结合Azure Monitor OTEL的使用经验给你梳理下可行的方案,刚好能解决你提到的那些痛点:
先明确你的核心需求
- 能够在Azure Insights中实时看到作业启动状态,不用等它跑完
- 能追踪作业运行中的进度,比如“开始执行步骤X”这类细节
- 作业相关的所有数据必须100%上报,不能被采样过滤
- 常规Web请求流量遵循配置的采样率,不影响整体成本
- 非作业类的日志只上报Warning及以上级别,减少冗余数据
你提到的核心挑战确实戳中了痛点
用单个Span/Activity包裹整个长作业确实不行,因为OTEL的Span默认是在结束时才上报,这样就没法实时看到进度了。那咱们换个思路来解决:
方案1:拆分作业为实时上报的事件/小Span
既然整段Span等不及,那就把作业拆成多个阶段的实时事件:
- 作业启动瞬间,立刻打一条作业启动日志,带上唯一的
JobId作为标识,这样Insights里马上就能看到作业已启动 - 每个步骤开始、完成时都打对应的进度日志,同样带上
JobId,方便后续通过这个ID关联整个作业的所有日志 - 作业最终完成或失败时,打一条收尾日志,标记作业的最终状态
方案2:自定义采样规则,区分作业和Web流量
要实现作业数据不被采样、Web请求按配置采样,咱们可以给OTEL加个自定义采样器:
- 给所有作业相关的Activity/日志打上专属标签,比如
is_integration_job: true - 配置一个复合采样器:对带这个标签的流量强制
RecordAndSample(全部上报);对Web请求用你预设的概率采样率(比如10%)
方案3:日志级别过滤,精准控制上报内容
针对非作业日志只上报Warning及以上的需求,可以通过OTEL的日志处理器来过滤:
- 自定义一个日志处理器,检查每条日志的上下文:如果没有
JobId标识(非作业日志),就把级别低于Warning的日志给屏蔽掉 - 作业相关的日志则不受限制,所有级别都能上报,保证进度追踪的完整性
给你贴点实用的代码参考
OTEL采样配置
builder.Services.AddOpenTelemetry() .WithTracing(tracing => tracing .AddAspNetCoreInstrumentation() .AddAzureMonitorInstrumentation() // 复合采样器:Web请求用10%采样,作业流量强制全量 .SetSampler(new CompositeSampler( new ParentBasedSampler(new TraceIdRatioBasedSampler(0.1)), new AttributeBasedSampler("is_integration_job", "true", SamplingDecision.RecordAndSample) )));
作业执行时的日志示例
public async Task RunLongRunningIntegrationJob(Guid jobId) { // 用日志Scope绑定JobId,后续所有日志都会自动带上这个标识 using var jobScope = _logger.BeginScope(new Dictionary<string, object> { ["JobId"] = jobId }); _logger.LogInformation("集成作业[{JobId}]已启动,开始执行", jobId); // 步骤1执行 _logger.LogInformation("集成作业[{JobId}]开始执行步骤1:数据同步", jobId); await ExecuteDataSyncStep(jobId); _logger.LogInformation("集成作业[{JobId}]完成步骤1:数据同步", jobId); // 步骤2执行 _logger.LogInformation("集成作业[{JobId}]开始执行步骤2:校验数据", jobId); var validationResult = await ValidateDataStep(jobId); if (!validationResult.IsValid) { _logger.LogWarning("集成作业[{JobId}]步骤2校验不通过:{ErrorMessage}", jobId, validationResult.ErrorMessage); } _logger.LogInformation("集成作业[{JobId}]完成步骤2:校验数据", jobId); // 后续步骤... _logger.LogInformation("集成作业[{JobId}]全部执行完成", jobId); }
日志过滤处理器
builder.Logging.AddOpenTelemetry(options => { options.AddAzureMonitorLogExporter(exporterOptions => { // 这里配置你的Azure Monitor导出器参数 }); // 添加自定义过滤处理器 options.AddProcessor(new NonJobLogFilterProcessor()); }); public class NonJobLogFilterProcessor : BaseProcessor<LogRecord> { public override void OnEnd(LogRecord logRecord) { // 非作业日志(没有JobId标识)只保留Warning及以上级别 if (!logRecord.Properties.TryGetValue("JobId", out _) && logRecord.LogLevel < LogLevel.Warning) { logRecord.Suppress = true; // 屏蔽这条日志,不发送到Azure Monitor } base.OnEnd(logRecord); } }
最后给你提个小建议
在Azure Monitor里,你可以用Kusto查询通过JobId快速定位整个作业的所有日志,比如:
traces | where customDimensions.JobId == "你的作业ID" | order by timestamp asc
这样就能完整看到作业从启动到结束的全流程进度啦。
备注:内容来源于stack exchange,提问作者mortenbock
相关产品推荐
相关产品推荐

