在.NET Core Web API中集成Grafana生态系统的监控工具链,实现全栈可观测性(日志、指标、追踪)
本文在此使用下面组件:
- Grafana:用于监控和观测数据
- Prometheus:指标(Metrics)收集
- Loki:日志收集
- Alloy:统一观测数据收集器
Grafana 是可视化层,用于展示 Prometheus(指标)、Loki(日志) 和 Tempo(追踪) 的数据。
Alloy 是数据收集层,统一采集指标、日志、追踪并转发到对应后端(Prometheus/Loki/Tempo)。
它们共同构成完整的 观测性栈(Observability Stack):Alloy(收集) → Prometheus/Loki/Tempo(存储) → Grafana(可视化)
部署Grafana服务

使用docker compose编排grafana相关服务:
yaml
services:
grafana-alloy:
image: grafana/alloy:latest
container_name: grafana-alloy
restart: unless-stopped
volumes:
- /etc/grafana-config/alloy/config.alloy:/etc/alloy/config.alloy
- /etc/grafana-config/alloy/data:/var/lib/alloy
ports:
- "12345:12345" # 用于 Alloy 的 HTTP 服务器
- "4317:4317" # OpenTelemetry gRPC 接收器
- "4318:4318" # OpenTelemetry HTTP 接收器
- "8888:8888" # 用于 Prometheus 远程写入
command: ["run", "--server.http.listen-addr=0.0.0.0:12345", "/etc/alloy/config.alloy"]
loki:
image: grafana/loki:3.0.0
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
prometheus:
image: prom/prometheus:v2.47.0
command:
- --web.enable-remote-write-receiver
- --config.file=/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
environment:
- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: false
version: 1
editable: false
- name: Prometheus
type: prometheus
orgId: 1
url: http://prometheus:9090
basicAuth: false
isDefault: true
version: 1
editable: false
EOF
/run.sh
image: grafana/grafana:latest
ports:
- "13000:3000"
volumes:
grafana-storage: {}
构建容器

通过ip:13000(端口根据自己的配置调整)来访问grafana

grafana已经根据我们的配置预先设置好了Loki和Prometheus。
查看Loki:

查看Prometheus:

配置Alloy
找到alloy配置文件config.alloy,如本文配置文件挂载在/etc/grafana-config/alloy/config.alloy,打开并修改:
yaml
//配置收集/var/log/ 目录下的所有.log文件
local.file_match "local_files" {
path_targets = [{"__path__" = "/var/log/*.log"}]
sync_period = "5s"
}
loki.source.file "log_scrape" {
targets = local.file_match.local_files.targets
forward_to = [loki.process.filter_logs.receiver]
tail_from_end = true
}
loki.process "filter_logs" {
stage.drop {
source = ""
expression = ".*Connection closed by authenticating user root"
drop_counter_reason = "noisy"
}
forward_to = [loki.write.grafana_loki.receiver]
}
//配置收集/tmp/alloy-logs/ 目录下的所有.log文件
local.file_match "tmplogs" {
path_targets = [{"__path__" = "/tmp/alloy-logs/*.log"}]
}
loki.source.file "local_files" {
targets = local.file_match.tmplogs.targets
forward_to = [loki.write.grafana_loki.receiver]
}
//将收集的日志写入loki
loki.write "grafana_loki" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
// basic_auth {
// username = "admin"
// password = "admin"
// }
}
}
//配置本地系统指标导出器
prometheus.exporter.unix "local_system" { }
//配置发送源与时间间隔
prometheus.scrape "scrape_metrics" {
targets = prometheus.exporter.unix.local_system.targets
forward_to = [prometheus.relabel.filter_metrics.receiver]
scrape_interval = "10s"
}
prometheus.relabel "filter_metrics" {
rule {
action = "drop"
source_labels = ["env"]
regex = "dev"
}
forward_to = [prometheus.remote_write.metrics_service.receiver]
}
//将收集的指标写入prometheus
prometheus.remote_write "metrics_service" {
endpoint {
url = "http://prometheus:9090/api/v1/write"
// basic_auth {
// username = "admin"
// password = "admin"
// }
}
}
访问Alloy UI查看配置间的关系图:

测试收集数据
直接向loki端点发送数据:
plain
NOW=$(date +%s%N)
curl -v -XPOST "http://localhost:3100/loki/api/v1/push" \
-H "Content-Type: application/json" \
-d '{
"streams": [{
"stream": { "job": "test" },
"values": [ [ "'"$NOW"'", "test log" ] ]
}]
}'
在Grafana中查看:

向本地文件写入log日志echo "New log line" >> /tmp/alloy-logs/log.log
可以查到收集的日志信息:

在Net Core应用程序中收集指标与日志
- 添加相应的包
plain
dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Instrumentation.Http
dotnet add package OpenTelemetry.Instrumentation.SqlClient
dotnet add package OpenTelemetry.Instrumentation.Runtime
//使用 Serilog 将日志发送到 Loki
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Grafana.Loki
- 编辑启动程序代码:
plain
// 1. 配置 OpenTelemetry 资源(服务名、版本等)
var resourceBuilder = ResourceBuilder
.CreateDefault()
.AddService(serviceName: "my-webapi", serviceVersion: "1.0.0");
// 2. 配置 Tracing(分布式追踪)
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.SetResourceBuilder(resourceBuilder)
.AddAspNetCoreInstrumentation() // 监控 ASP.NET Core 请求
.AddHttpClientInstrumentation() // 监控 HttpClient 请求
.AddSqlClientInstrumentation() // 监控 SQL 查询
.AddOtlpExporter(options => // 发送到 Alloy 的 OTLP 接收端
{
options.Endpoint = new Uri("http://grafana-alloy:4317"); // Docker 内部网络
});
});
// 3. 配置 Metrics(指标)
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics
.SetResourceBuilder(resourceBuilder)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation() // 监控 .NET 运行时指标
.AddOtlpExporter(options => // 发送到 Alloy
{
options.Endpoint = new Uri("http://grafana-alloy:4317");
});
});
// 4. 配置 Logging(日志)
builder.Logging.AddOpenTelemetry(logging =>
{
logging.SetResourceBuilder(resourceBuilder)
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://grafana-alloy:4317");
});
});
builder.Host.UseSerilog(); // 启用 Serilog
- 在alloy.config中添加下面配置
plain
otelcol.receiver.otlp "default" {
grpc { endpoint = "0.0.0.0:4317" }
http { endpoint = "0.0.0.0:4318" }
output {
metrics = [otelcol.exporter.prometheus.default.input]
logs = [otelcol.exporter.loki.default.input]
traces = []
}
}
otelcol.exporter.loki "default" {
forward_to = [loki.write.grafana_loki.receiver] // 转发到 Loki Write
}
otelcol.exporter.prometheus "default" {
forward_to = [prometheus.remote_write.metrics_service.receiver] // 转发到 Prometheus
}
- 选择一个仪表盘模板展示数据

- 查看仪表盘
