运行状况检查

  运行状况检查由应用程序作为 HTTP 终结点公开。 可以为各种实时监视方案配置运行状况检查终结点:

  • 运行状况探测可以由容器业务流程协调程和负载均衡器用于检查应用的状态。 例如,容器业务流程协调程序可以通过停止滚动部署或重新启动容器来响应失败的运行状况检查。 负载均衡器可以通过将流量从失败的实例路由到正常实例,来应对不正常的应用。
  • 可以监视内存、磁盘和其他物理服务器资源的使用情况来了解是否处于正常状态。
  • 运行状况检查可以测试应用的依赖项(如数据库和外部服务终结点)以确认是否可用和正常工作。

  运行状况检查通常与外部监视服务或容器业务流程协调程序一起用于检查应用的状态。 向应用添加运行状况检查之前,需确定要使用的监视系统。 监视系统决定了要创建的运行状况检查类型以及配置其终结点的方式。

基本运行状况探测

  对于许多应用,报告应用在处理请求方面的可用性(运行情况)的基本运行状况探测配置足以发现应用的状态。

  基本配置会注册运行状况检查服务,并调用运行状况检查中间件以通过运行状况响应在 URL 终结点处进行响应。 默认情况下,不会注册任何特定运行状况检查来测试任何特定依赖项或子系统。 如果能够在运行状况终结点 URL 处进行响应,则应用被视为正常。 默认响应编写器将 HealthStatus 作为纯文本响应写入客户端。 HealthStatus 为 HealthStatus.HealthyHealthStatus.DegradedHealthStatus.Unhealthy

  在 Program.cs 中使用 AddHealthChecks 注册运行状况检查服务。 通过调用 MapHealthChecks 来创建运行状况检查终结点。

1
2
3
4
5
6
7
8
9
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

Docker HEALTHCHECK

  Docker 提供内置 HEALTHCHECK 指令,该指令可以用于检查使用基本运行状况检查配置的应用的状态:

1
HEALTHCHECK CMD curl --fail http://localhost:5000/healthz || exit

  上面的示例使用 curl 向 /healthz 的运行状况检查终结点发出 HTTP 请求。 curl 不包括在 .NET Linux 容器映像中,但可以通过在 Dockerfile 中安装所需的包来添加它。 使用基于 Alpine Linux 的映像的容器可以使用包含的 wget 代替 curl。

创建运行状况检查

  运行状况检查通过实现 IHealthCheck 接口进行创建。 CheckHealthAsync 方法会返回 HealthCheckResult,它以 HealthyDegradedUnhealthy 的形式指示运行状况。 结果被写成带有可配置状态代码的纯文本响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SampleHealthCheck : IHealthCheck
{
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
{
var isHealthy = true;

// ...

if (isHealthy)
{
return Task.FromResult(
HealthCheckResult.Healthy("A healthy result."));
}

return Task.FromResult(
new HealthCheckResult(
context.Registration.FailureStatus, "An unhealthy result."));
}
}

注册运行状况检查服务

  要注册运行状况检查服务,在 Program.cs 中调用 AddCheck

1
2
builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>("Sample");

标记可用于筛选运行状况检查。 筛选运行状况检查部分介绍了标记。

1
2
3
4
5
builder.Services.AddHealthChecks()
.AddCheck<SampleHealthCheck>(
"Sample",
failureStatus: HealthStatus.Degraded,
tags: new[] { "sample" });

AddCheck 还可以执行 lambda 函数。 在下面的示例中,运行状况检查始终返回正常结果:

1
2
builder.Services.AddHealthChecks()
.AddCheck("Sample", () => HealthCheckResult.Healthy("A healthy result."));

调用 AddTypeActivatedCheck 将参数传递到运行状况检查实现。 在下面的示例中,一个类型激活的运行状况检查在其构造函数中接受一个整数和一个字符串:

1
2
3
4
5
6
builder.Services.AddHealthChecks()
.AddTypeActivatedCheck<SampleHealthCheckWithArgs>(
"Sample",
failureStatus: HealthStatus.Degraded,
tags: new[] { "sample" },
args: new object[] { 1, "Arg" }); // 传递参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SampleHealthCheckWithArgs : IHealthCheck
{
private readonly int _arg1;
private readonly string _arg2;

// 这里可以接受到参数
public SampleHealthCheckWithArgs(int arg1, string arg2)
=> (_arg1, _arg2) = (arg1, arg2);

public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
{
// ...

return Task.FromResult(HealthCheckResult.Healthy("A healthy result."));
}
}

使用运行状况检查路由

  在 Program.cs 内,使用终结点 URL 或相对路径在终结点生成器上调用 MapHealthChecks

1
app.MapHealthChecks("/healthz");

需要主机

  调用 RequireHost 以便为运行状况检查终结点指定一个或多个允许的主机。 主机应为 Unicode 而不是 punycode,且可以包含端口。 如果未提供集合,则接受任何主机:

1
2
app.MapHealthChecks("/healthz")
.RequireHost("www.contoso.com:5001");

  若要将运行状况检查终结点限制为仅响应特定端口,请在对 RequireHost 的调用中指定端口。 此方法通常用于在容器环境中公开用于监视服务的端口:

1
2
app.MapHealthChecks("/healthz")
.RequireHost("*:5001");

依赖于主机头的 API(如 HttpRequest.Host 和 RequireHost)可能会受到客户端的欺骗。
若要防止主机和端口欺骗,请使用以下方法之一:

  • 使用检查端口的 HttpContext.Connection (ConnectionInfo.LocalPort)。
  • 采用主机筛选。

需要授权

  调用 RequireAuthorization 以在状况检查请求终结点上运行身份验证中间件。 RequireAuthorization 重载接受一个或多个授权策略。 如果未提供策略,则使用默认的授权策略:

1
2
app.MapHealthChecks("/healthz")
.RequireAuthorization();

需要CORS跨域请求

  尽管从浏览器手动运行运行状况检查不是常见的使用方案,但可以通过在运行状况检查终结点上调用 RequireCors 来启用 CORS 中间件。 RequireCors 重载接受 CORS 策略生成器委托 (CorsPolicyBuilder) 或策略名称。

1
2
app.MapHealthChecks("/healthz")
.RequireCors("policyName"); // 可通过AddCors添加跨域策略

运行状况检查选项

  HealthCheckOptions 使你可以自定义运行状况检查行为:

  • 筛选运行状况检查
  • 自定义 HTTP 状态代码
  • 取消缓存标头
  • 自定义输出

筛选运行状况检查

  默认情况下,运行状况检查中间件会运行所有已注册的运行状况检查。 若要运行运行状况检查的子集,请提供向 Predicate 选项返回布尔值的函数。

下面的示例筛选运行状况检查,以便仅运行带有 sample 标记的运行状况检查:

1
2
3
4
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = healthCheck => healthCheck.Tags.Contains("sample")
});

自定义HTTP状态代码

  使用 ResultStatusCodes 可自定义运行状况状态到 HTTP 状态代码的映射。 以下 StatusCodes 分配是中间件所使用的默认值。 更改状态代码值以满足要求:

1
2
3
4
5
6
7
8
9
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
ResultStatusCodes =
{
[HealthStatus.Healthy] = StatusCodes.Status200OK,
[HealthStatus.Degraded] = StatusCodes.Status200OK,
[HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
}
});

取消缓存标头

  AllowCachingResponses 控制运行状况检查中间件是否将 HTTP 标头添加到探测响应以防止响应缓存。 如果值为 false(默认值),则中间件会设置或替代 Cache-Control、Expires 和 Pragma 标头以防止响应缓存。 如果值为 true,则中间件不会修改响应的缓存标头:

1
2
3
4
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
AllowCachingResponses = true
});

自定义输出

  若要自定义运行状况检查报告的输出,请将 HealthCheckOptions.ResponseWriter 属性设置为写入响应的委托:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
app.MapHealthChecks("/healthz", new HealthCheckOptions
{
ResponseWriter = WriteResponse
});

private static Task WriteResponse(HttpContext context, HealthReport healthReport)
{
context.Response.ContentType = "application/json; charset=utf-8";

var options = new JsonWriterOptions { Indented = true };

using var memoryStream = new MemoryStream();
using (var jsonWriter = new Utf8JsonWriter(memoryStream, options))
{
jsonWriter.WriteStartObject();
jsonWriter.WriteString("status", healthReport.Status.ToString());
jsonWriter.WriteStartObject("results");

foreach (var healthReportEntry in healthReport.Entries)
{
jsonWriter.WriteStartObject(healthReportEntry.Key);
jsonWriter.WriteString("status",
healthReportEntry.Value.Status.ToString());
jsonWriter.WriteString("description",
healthReportEntry.Value.Description);
jsonWriter.WriteStartObject("data");

foreach (var item in healthReportEntry.Value.Data)
{
jsonWriter.WritePropertyName(item.Key);

JsonSerializer.Serialize(jsonWriter, item.Value,
item.Value?.GetType() ?? typeof(object));
}

jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}

jsonWriter.WriteEndObject();
jsonWriter.WriteEndObject();
}

return context.Response.WriteAsync(
Encoding.UTF8.GetString(memoryStream.ToArray()));
}

  运行状况检查 API 不为复杂 JSON 返回格式提供内置支持,因为该格式特定于你选择的监视系统。 必要时自定义上述示例中的响应。

数据库探测

  运行状况检查可以指定数据库查询作为布尔测试来运行,以指示数据库是否在正常响应。

  AspNetCore.Diagnostics.HealthChecks 是一个适用于 ASP.NET Core 应用的运行状况检查库,包括针对 SQL Server 数据库运行的运行状况检查。 AspNetCore.Diagnostics.HealthChecks 对数据库执行 SELECT 1 查询以确认与数据库的连接是否正常。

使用查询检查数据库连接时,请选择快速返回的查询。 查询方法会面临使数据库过载和降低其性能的风险。 在大多数情况下,无需运行测试查询。 只需建立成功的数据库连接便足矣。 如果发现需要运行查询,请选择简单的 SELECT 查询,如 SELECT 1。

  若要使用此 SQL Server 运行状况检查,请包含对 AspNetCore.HealthChecks.SqlServer NuGet 包的包引用。 以下示例注册了 SQL Server 运行状况检查:

EFCore DBContext探测

  DbContext 检查确认应用可以与为 EF CoreDbContext 配置的数据库通信。 满足以下条件的应用支持 DbContext 检查:

  • 使用 Entity Framework (EF) Core
  • 包括对 Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore NuGet 包的包引用。

  AddDbContextCheck 为 DbContext 注册运行状况检查。 DbContext 作为 TContext 提供给方法。 重载可用于配置失败状态、标记和自定义测试查询。

  默认情况下:

  • DbContextHealthCheck 调用 EF Core 的 CanConnectAsync 方法。 可以自定义在使用 AddDbContextCheck 方法重载检查运行状况时运行的操作。
  • 运行状况检查的名称是 TContext 类型的名称。

以下示例注册了一个 DbContext 和一个关联的 DbContextHealthCheck:

1
2
3
4
5
6
builder.Services.AddDbContext<SampleDbContext>(options =>
options.UseSqlServer(
builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddHealthChecks()
.AddDbContextCheck<SampleDbContext>();

单独的就绪/运行情况探测

  在某些托管方案中,使用一对运行状况检查来区分两种应用状态:

  • “就绪情况”指示情况是否为应用正常运行,但未准备好接收请求。
  • “运行情况”指示情况是否为应用已崩溃且必须重启。

  请看下面的示例:应用必须下载大型配置文件才能处理请求。 如果初始下载失败,我们不希望重启该应用,因为该应用可以多次尝试下载该文件。 我们使用运行情况探测来描述进程的运行情况,不运行其他检查。 我们还想要在配置文件下载成功之前阻止将请求发送到应用。 我们使用就绪情况探测指示“未就绪”状态,直到下载成功并且应用已准备好接收请求。

以下后台任务模拟了一个启动过程,大约需要 15 秒。 完成后,任务将 StartupHealthCheck.StartupCompleted 属性设置为 true:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StartupHealthCheck : IHealthCheck
{
private volatile bool _isReady;

public bool StartupCompleted
{
get => _isReady;
set => _isReady = value;
}

public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
{
if (StartupCompleted)
{
return Task.FromResult(HealthCheckResult.Healthy("The startup task has completed."));
}

return Task.FromResult(HealthCheckResult.Unhealthy("That startup task is still running."));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class StartupBackgroundService : BackgroundService
{
private readonly StartupHealthCheck _healthCheck;

public StartupBackgroundService(StartupHealthCheck healthCheck)
=> _healthCheck = healthCheck;

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Simulate the effect of a long-running task.
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);

_healthCheck.StartupCompleted = true;
}
}

  运行状况检查在 Program.cs 中使用 AddCheck 与托管服务一起注册。 因为托管服务必须对运行状况检查设置该属性,所以运行状况检查也会在服务容器中注册为单一实例:

  • /healthz/ready(用于就绪状态检查)。 就绪检查将运行状况检查筛选为带有 ready 标记的运行状况检查。
  • /healthz/live(用于运行情况检查)。 运行情况检查通过在 HealthCheckOptions.Predicate 委托中返回 false 来筛选出所有运行状况检查。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
builder.Services.AddHostedService<StartupBackgroundService>();
builder.Services.AddSingleton<StartupHealthCheck>();

builder.Services.AddHealthChecks()
.AddCheck<StartupHealthCheck>(
"Startup",
tags: new[] { "ready" });

var app = builder.Build();

// 若要创建两个不同的运行状况检查终结点,请调用 MapHealthChecks 两次:
app.MapHealthChecks("/healthz/ready", new HealthCheckOptions
{
Predicate = healthCheck => healthCheck.Tags.Contains("ready")
});

app.MapHealthChecks("/healthz/live", new HealthCheckOptions
{
Predicate = _ => false
});

  启动任务完成之前,/healthz/ready 终结点将报告 Unhealthy 状态。 启动任务完成之后,此终结点将报告 Healthy 状态。 /healthz/live 终结点将排除所有检查并报告所有调用的 Healthy 状态。

Kubernetes示例

  在诸如 Kubernetes 这类环境中,使用单独的就绪情况和运行情况检查会十分有用。 在 Kubernetes 中,应用可能需要在接受请求之前运行耗时的启动工作,如基础数据库可用性测试。 使用单独检查使业务流程协调程序可以区分应用是否正常运行但尚未准备就绪,或是应用程序是否未能启动。

  以下示例演示如何使用 Kubernetes 就绪情况探测配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spec:
template:
spec:
readinessProbe:
# an http probe
httpGet:
path: /healthz/ready
port: 80
# length of time to wait for a pod to initialize
# after pod startup, before applying health checking
initialDelaySeconds: 30
timeoutSeconds: 1
ports:
- containerPort: 80

运行状况检查发布服务器

  当 IHealthCheckPublisher 添加到服务容器时,运行状况检查系统,会定期执行运行状况检查并使用结果调用 PublishAsync。 在期望每个进程定期调用监视系统以便确定运行状况的基于推送的运行状况监视系统方案中,此过程十分有用。

  使用 HealthCheckPublisherOptions 可设置:

  • Delay :在应用启动后且在应用执行 IHealthCheckPublisher 实例之前所应用的初始延迟。 延迟在启动时应用一次,不适用于后续迭代。 默认值为 5 秒。
  • Period :IHealthCheckPublisher 执行的时间。 默认值为 30 秒。
  • Predicate :如果 Predicate 为 null(默认值),则运行状况检查发布服务器服务运行所有已注册的运行状况检查。 若要运行运行状况检查的子集,请提供用于筛选检查集的函数。 每个时间段都会评估谓词。
  • Timeout :执行所有 IHealthCheckPublisher 实例的运行状况检查的超时时间。 在不超时的情况下,使用 InfiniteTimeSpan 执行。 默认值为 30 秒。

下面的示例演示运行状况发布服务器的布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SampleHealthCheckPublisher : IHealthCheckPublisher
{
public Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
{
if (report.Status == HealthStatus.Healthy)
{
// ...
}
else
{
// ...
}

return Task.CompletedTask;
}
}

  HealthCheckPublisherOptions 类提供了用于配置运行状况检查发布服务器的行为的属性。

以下示例将运行状况检查发布服务器注册为单一实例并配置 HealthCheckPublisherOptions

1
2
3
4
5
6
7
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = healthCheck => healthCheck.Tags.Contains("sample");
});

builder.Services.AddSingleton<IHealthCheckPublisher, SampleHealthCheckPublisher>();

指标

  指标是一段时间内报告的数字度量值。它们通常用于监视应用的运行状况并生成警报。例如,Web服务可能会跟踪以下数量:

  • 每秒接收的请求数
  • 响应所花费的毫秒数
  • 响应发送了错误

  可以定期向监视系统报告这些指标。 仪表板可以设置为查看创建的指标和警报,以通知用户出现问题。 如果 Web 服务打算在 400 毫秒内响应请求,并在 600 毫秒后开始响应,则监视系统可以通知操作人员应用响应速度比平时慢。

使用指标

  在.NET应用中使用指标涉及两个部分:

  • 检测 : .NET 库中的代码采用度量值,并将这些度量值与指标名称关联起来。 .NET 和 ASP.NET Core 包括许多内置指标。
  • 收集 :由一个 .NET 应用来配置要从应用传输的命名指标以用于外部存储和分析。 某些工具可能会使用配置文件或 UI 工具在应用外部执行配置。

  检测的代码可以记录数值度量值,但需要聚合、传输和存储度量值,以创建用于监视的有用指标。 聚合、传输和存储数据的过程称为集合。 本教程演示收集指标的几个示例:

  度量值还可以与被称为标记的键值对相关联,从而能对数据进行分类以进行分析。

OpenTelemetry

  1. 引用nuget包
1
2
dotnet add package OpenTelemetry.Exporter.Prometheus.AspNetCore --prerelease
dotnet add package OpenTelemetry.Extensions.Hosting
  1. Program.cs的内容替换为以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using OpenTelemetry.Metrics;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenTelemetry()
.WithMetrics(builder =>
{
builder.AddPrometheusExporter();

builder.AddMeter("Microsoft.AspNetCore.Hosting",
"Microsoft.AspNetCore.Server.Kestrel");
builder.AddView("http.server.request.duration",
new ExplicitBucketHistogramConfiguration
{
Boundaries = new double[] { 0, 0.005, 0.01, 0.025, 0.05,
0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 }
});
});
var app = builder.Build();

app.MapPrometheusScrapingEndpoint();

app.MapGet("/", () => "Hello OpenTelemetry! ticks:"
+ DateTime.Now.Ticks.ToString()[^3..]);

app.Run();

dotnet-counters

  dotnet-counters 是一个命令行工具,可按需查看任何 .NET Core 应用的实时指标。 它不需要设置,因此可用于临时调查或验证指标检测是否正常工作。 它与基于 System.Diagnostics.Metrics 的 API 和 EventCounters 配合运作。

  1. 安装dotnet-counters工具
1
dotnet tool update -g dotnet-counters
  1. 运行测试应用时,启动 dotnet-counters。 以下命令显示了一个 dotnet-counters 示例,它监视来自 Microsoft.AspNetCore.Hosting 计量的所有指标。(WebMetric是项目的名称)
1
dotnet-counters monitor -n WebMetric --counters Microsoft.AspNetCore.Hosting
  1. 输出示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
Press p to pause, r to resume, q to quit.
Status: Running

[Microsoft.AspNetCore.Hosting]
http-server-current-requests
host=localhost,method=GET,port=5045,scheme=http 0
http-server-request-duration (s)
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0.001
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0
host=localhost,method=GET,port=5045,protocol=HTTP/1.1,ro 0

扩充请求指标

  ASP.NET Core 具有许多内置指标。 http.server.request.duration指标:

  • 记录服务器上 HTTP 请求的持续时间。
  • 捕获标记中的请求信息,例如匹配的路由和响应状态代码。

  http.server.request.duration指标支持使用IHttpMetricsTagsFeature进行标记扩充。 在库或应用将自己的标记添加到指标时进行扩充。 如果应用要向使用指标生成的仪表板或警报添加自定义分类,这非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Microsoft.AspNetCore.Http.Features;

var builder = WebApplication.CreateBuilder();
var app = builder.Build();

app.Use(async (context, next) =>
{
var tagsFeature = context.Features.Get<IHttpMetricsTagsFeature>();
if (tagsFeature != null)
{
var source = context.Request.Query["utm_medium"].ToString() switch
{
"" => "none",
"social" => "social",
"email" => "email",
"organic" => "organic",
_ => "other"
};
tagsFeature.Tags.Add(new KeyValuePair<string, object?>("mkt_medium", source));
}

await next.Invoke();
});

app.MapGet("/", () => "Hello World!");

app.Run();
  • 添加中间件以扩充 ASP.NET 核心请求指标。
  • HttpContext中获取IHttpMetricsTagsFeature。 只有当有人正在侦听指标时,该功能才会出现在上下文中。 在使用IHttpMetricsTagsFeature之前,验证它是否不是null。
  • 将包含请求的营销源的自定义标记添加到http.server.request.duration指标。
    • 该标记具有名称mkt_medium和基于utm_medium查询字符串值的值。 utm_medium值解析为已知值范围。
    • 该标记允许按营销媒体类型对请求进行分类,这在分析 Web 应用流量时可能很有用。

创建自定义指标

IMeterFactory

  默认情况下,ASP.NET Core 在依赖项注入 (DI) 中注册IMeterFactory。 计量工厂将指标与 DI 集成,使隔离和收集指标变得简单。 IMeterFactory 对测试非常有用。 它允许多个测试并行运行,并且仅收集测试中记录的指标值。

  若要在应用中使用 IMeterFactory,请创建使用 IMeterFactory 创建应用的自定义指标的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ContosoMetrics
{
private readonly Counter<int> _productSoldCounter;

public ContosoMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("Contoso.Web");
_productSoldCounter = meter.CreateCounter<int>("contoso.product.sold");
}

public void ProductSold(string productName, int quantity)
{
_productSoldCounter.Add(quantity,
new KeyValuePair<string, object?>("contoso.product.name", productName));
}
}
1
2
3
4
5
6
7
8
9
10
11
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ContosoMetrics>();

var app = builder.Build();

app.MapPost("/complete-sale", (SaleModel model, ContosoMetrics metrics) =>
{
// ... business logic such as saving the sale to a database ...

metrics.ProductSold(model.ProductName, model.QuantitySold);
});