第一章:从47分钟到92秒:一场Go可观测性体系的五年进化论
五年前,某核心支付网关服务在一次凌晨告警中耗时47分钟才定位到根本原因——日志散落在三台不同节点的/var/log目录下,metrics缺失关键维度,trace链路因采样率设为0.1%而彻底断裂。今天,同一类故障平均响应时间压缩至92秒,背后是Go语言生态与工程实践协同演进的缩影。
日志体系的语义化跃迁
早期使用fmt.Printf拼接字符串,导致结构化解析失败;如今统一采用zerolog,配合预定义字段规范:
// 推荐:结构化、无堆分配、支持上下文传递
logger.Info().
Str("payment_id", pid).
Int64("amount_cents", amount).
Str("gateway", "alipay").
Msg("payment processed")
该写法生成JSON日志,可被Loki原生索引,字段自动转为Prometheus labels。
Metrics采集的精细化治理
放弃全局计数器,转向基于业务域的指标命名空间隔离:
payment_success_total{gateway="wxpay",region="sh"}payment_latency_seconds_bucket{gateway="alipay",le="0.5"}
通过go.opentelemetry.io/otel/metric初始化Meter,绑定service.name资源属性,避免指标混杂。
分布式追踪的零侵入增强
从手动注入spanCtx演进为基于HTTP中间件的自动传播:
func traceMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
span := trace.SpanFromContext(ctx) // 自动提取W3C TraceContext
span.AddEvent("request_received")
next.ServeHTTP(w, r)
})
}
结合Jaeger exporter与OTLP协议,端到端trace采样率动态调整至5%,兼顾性能与可观测深度。
告警闭环的根因推断能力
| 当前可观测栈已支持跨信号关联分析: | 信号类型 | 工具链 | 关联能力示例 |
|---|---|---|---|
| Logs | Loki + LogQL | 提取error日志中的traceID | |
| Metrics | Prometheus | 查询该traceID对应服务的P99延迟突增 | |
| Traces | Tempo + Grafana | 渲染完整调用链并高亮慢节点 |
这套协同机制使MTTR(平均修复时间)下降96.8%,真正实现从“大海捞针”到“精准制导”的质变。
第二章:可观测性三大支柱在Go工程中的深度落地
2.1 指标(Metrics)采集:Prometheus + OpenTelemetry Go SDK的零侵入埋点实践
零侵入的关键在于自动 instrumentation与指标语义解耦。OpenTelemetry Go SDK 提供 otelhttp 和 otelgrpc 等封装器,无需修改业务逻辑即可注入观测能力。
自动 HTTP 指标采集示例
import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", otelhttp.NewHandler(mux, "user-service"))
该代码将自动采集 http.server.duration, http.server.requests 等标准 Prometheus 指标,并通过 OTLP exporter 推送至 Prometheus(经 OpenTelemetry Collector 转换)。
核心优势对比
| 方式 | 代码侵入性 | 指标标准化 | 扩展性 |
|---|---|---|---|
手动 promauto.NewCounter() |
高(需散落埋点) | 弱(命名不一) | 差 |
| OTel Auto-Instrumentation | 零 | 强(遵循 Semantic Conventions) | 极佳 |
数据同步机制
graph TD
A[Go App] -->|OTLP over gRPC| B[OTel Collector]
B -->|Prometheus Remote Write| C[Prometheus Server]
C --> D[Grafana 可视化]
2.2 日志(Logs)统一治理:结构化日志设计、采样策略与ELK/Grafana Loki协同方案
结构化日志设计原则
采用 JSON 格式输出,强制包含 timestamp、level、service、trace_id、span_id 字段,避免自由文本解析瓶颈。
{
"timestamp": "2024-06-15T10:23:45.123Z",
"level": "ERROR",
"service": "payment-api",
"trace_id": "a1b2c3d4e5f67890",
"span_id": "x9y8z7",
"message": "Failed to commit transaction",
"error_code": "PAYMENT_TIMEOUT"
}
逻辑分析:
trace_id/span_id支持分布式链路追踪对齐;error_code替代模糊描述,便于告警规则精准匹配;时间戳采用 ISO 8601 UTC 格式,规避时区歧义。
采样策略分层控制
- 全量采集:
level=ERROR或含error_code的日志 - 动态采样:
level=WARN按service维度启用 5% 随机采样(Loki 支持sample_rate参数) - 降噪过滤:剔除健康检查
/health和静态资源*.js日志
ELK 与 Loki 协同定位
| 场景 | ELK(Elasticsearch) | Grafana Loki |
|---|---|---|
| 实时告警与全文检索 | ✅(倒排索引强项) | ❌(仅标签查询) |
| 高基数指标关联分析 | ⚠️(存储成本高) | ✅(基于标签的低成本聚合) |
| 分布式追踪上下文补全 | 需手动 join trace_id | 原生支持 traceID 标签过滤 |
graph TD
A[应用日志] -->|JSON + trace_id| B{采样网关}
B -->|ERROR/panic| C[ELK:全量索引+告警]
B -->|WARN + sample_rate=0.05| D[Loki:标签索引+Grafana可视化]
C & D --> E[统一TraceID跳转]
2.3 链路追踪(Traces)全链路贯通:gRPC/HTTP中间件自动注入+Span生命周期精准控制
自动注入机制设计
通过统一拦截器在 gRPC ServerInterceptor 与 HTTP Middleware 中透明植入 StartSpan 和 Inject 逻辑,避免业务代码侵入。
// HTTP 中间件示例:自动提取并延续 trace context
func TraceMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 从 HTTP header 提取 traceparent 并生成 SpanContext
sc, _ := propagation.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
span := tracer.Start(ctx, "http.server", trace.WithSpanKind(trace.SpanKindServer), trace.WithSpanContext(sc))
defer span.End() // 精准控制生命周期:仅在请求结束时终止
r = r.WithContext(trace.ContextWithSpan(ctx, span))
next.ServeHTTP(w, r)
})
}
逻辑分析:
propagation.Extract解析 W3Ctraceparent,trace.WithSpanContext(sc)实现跨进程上下文延续;defer span.End()确保 Span 在 handler 返回前关闭,杜绝泄漏。
Span 生命周期关键控制点
- ✅ 请求进入时创建 Span(
Start) - ✅ 上下文透传至下游服务(
Inject) - ✅ 异步任务需显式
Span.WithContext()绑定 - ❌ 禁止跨 goroutine 复用 Span(无
context.WithValue隐式传递)
gRPC 与 HTTP 调用链对齐对比
| 维度 | gRPC | HTTP |
|---|---|---|
| Context 注入 | metadata.MD + grpc.SetTracing |
HeaderCarrier + traceparent |
| Span Kind | SpanKindClient/Server |
同上,自动识别 |
| 错误标记 | span.RecordError(err) |
同机制,统一错误语义 |
graph TD
A[Client Request] --> B{HTTP Middleware}
B --> C[Extract traceparent]
C --> D[Start Server Span]
D --> E[Business Logic]
E --> F[Call gRPC Service]
F --> G[gRPC Client Interceptor]
G --> H[Inject into metadata]
H --> I[Server Interceptor]
I --> J[Continue Span]
2.4 上下文传播一致性:context.Context与OpenTelemetry propagation的Go原生适配陷阱与规避
Go上下文传播的隐式契约
context.Context 本身不携带传播协议语义,仅提供键值存储与取消信号。OpenTelemetry 的 propagation.TextMapPropagator 则要求显式注入/提取 W3C TraceContext(如 traceparent)等标准字段——二者语义层存在天然错位。
常见陷阱:手动包装导致丢失 span context
// ❌ 错误:直接将 otel span context 注入 context.Value,未触发 propagation
ctx := context.WithValue(parent, "span", span.SpanContext())
// ✅ 正确:使用 otelhttp 或 otelgrpc 等 instrumented 客户端自动传播
ctx = otel.GetTextMapPropagator().Inject(context.Background(), carrier)
Inject()将traceparent、tracestate等写入TextMapCarrier(如http.Header),而WithCancel或WithValue不参与此流程,导致下游服务无法解析 trace ID。
关键适配原则
- 永远优先使用
otel.GetTextMapPropagator().Inject/Extract,而非context.WithValue - 自定义中间件必须显式调用 propagator,不可依赖 context 透传
| 传播方式 | 是否触发 OpenTelemetry 提取 | 是否兼容 W3C 标准 |
|---|---|---|
context.WithValue |
否 | 否 |
propagator.Extract |
是 | 是 |
2.5 可观测性数据管道:基于Go Channel+Worker Pool的高吞吐、低延迟指标聚合与转发架构
核心设计哲学
以“背压可控、处理解耦、资源可界”为原则,避免缓冲区无限膨胀与 Goroutine 泄漏。
数据流拓扑(mermaid)
graph TD
A[Metrics Ingest] --> B[Input Channel]
B --> C{Worker Pool}
C --> D[Aggregation Logic]
D --> E[Output Channel]
E --> F[Forwarder e.g. Prometheus Remote Write]
Worker Pool 初始化示例
type WorkerPool struct {
jobs <-chan *MetricBatch
workers int
}
func NewWorkerPool(jobs <-chan *MetricBatch, workers int) *WorkerPool {
return &WorkerPool{jobs: jobs, workers: workers}
}
func (wp *WorkerPool) Start() {
for i := 0; i < wp.workers; i++ {
go func() {
for batch := range wp.jobs {
batch.Aggregate() // 原地聚合,零拷贝
outputChan <- batch // 转发至下游
}
}()
}
}
jobs 为带缓冲的 chan *MetricBatch(建议 cap=1024),workers 需根据 CPU 核心数与批处理耗时动态调优(通常设为 runtime.NumCPU() * 2);Aggregate() 方法采用滑动窗口+基数树实现毫秒级聚合。
性能关键参数对照表
| 参数 | 推荐值 | 影响维度 |
|---|---|---|
| Input channel cap | 1024–4096 | 抗突发流量能力 |
| Worker count | 4–16 | CPU 利用率 & 延迟 |
| Batch size | 100–500 pts | 网络/序列化开销 |
第三章:Go运行时深度洞察与故障根因定位体系构建
3.1 Goroutine泄漏与内存毛刺:pprof + runtime.ReadMemStats + 自研Goroutine Dump分析器实战
Goroutine泄漏常表现为持续增长的 Goroutines 数量与周期性GC后仍无法回落的堆内存,引发服务响应毛刺。
诊断三板斧协同定位
pprof获取实时 goroutine profile(/debug/pprof/goroutine?debug=2)runtime.ReadMemStats捕获精确内存快照(含NumGC,HeapInuse,Goroutines字段)- 自研 Dump 分析器解析 goroutine stack trace,自动聚类阻塞模式(如
select{}阻塞、chan send/receive悬挂)
关键代码片段
var m runtime.MemStats
runtime.ReadMemStats(&m)
log.Printf("Goroutines: %d, HeapInuse: %v MB, NumGC: %d",
runtime.NumGoroutine(),
m.HeapInuse/1024/1024,
m.NumGC)
HeapInuse反映当前已分配且正在使用的堆内存(不含 GC 回收区);NumGC突增配合Goroutines持续上升,是泄漏强信号。
| 指标 | 健康阈值 | 风险表现 |
|---|---|---|
runtime.NumGoroutine() |
> 2000 且单调递增 | |
m.HeapInuse |
波动幅度 | 毛刺峰值超均值 300%+ |
graph TD
A[HTTP 请求触发] --> B[启动 goroutine]
B --> C{channel 是否已关闭?}
C -- 否 --> D[阻塞在 recv]
C -- 是 --> E[正常退出]
D --> F[泄漏累积]
3.2 GC行为建模与预警:基于GODEBUG=gctrace+GC pause duration分布图的MTTR前置干预机制
GODEBUG=gctrace 实时采集示例
GODEBUG=gctrace=1 ./myapp
# 输出示例:
# gc 1 @0.021s 0%: 0.010+0.84+0.012 ms clock, 0.040+0.36/0.75/0.25+0.048 ms cpu, 4->4->2 MB, 5 MB goal, 4 P
0.010+0.84+0.012 分别表示 mark setup / mark assist / sweep 时间;4->4->2 MB 表示堆大小变化;5 MB goal 是下一轮GC目标堆大小。该输出为毫秒级精度的GC生命周期快照,是建模的基础信号源。
GC暂停时长分布建模关键指标
| 指标 | 含义 | 预警阈值 |
|---|---|---|
| p99 pause | 99%分位暂停时长 | >10ms(低延迟服务) |
| pause frequency | 每秒GC次数 | >2次/s |
| pause skew | p99/p50比值 | >3.0(表明长尾恶化) |
MTTR前置干预流程
graph TD
A[实时采集gctrace] --> B[聚合pause duration序列]
B --> C[滑动窗口拟合Weibull分布]
C --> D{p99 > 阈值?}
D -->|是| E[触发内存压力信号]
D -->|否| F[持续监控]
E --> G[自动降低GOGC或扩容P]
- 干预动作需在p99首次越界前5个GC周期内完成;
- Weibull拟合可提前1–3轮GC预测长尾概率上升趋势。
3.3 网络连接状态可观测:net.Conn包装器、TCP状态机监控及TIME_WAIT异常聚类识别
封装可观测的连接接口
通过实现 net.Conn 接口包装器,注入连接生命周期钩子:
type ObservableConn struct {
net.Conn
onStateChange func(state string)
startTime time.Time
}
func (c *ObservableConn) Read(b []byte) (int, error) {
n, err := c.Conn.Read(b)
if err == nil && n > 0 {
c.onStateChange("ESTABLISHED")
}
return n, err
}
该包装器不修改底层行为,仅在 Read/Write/Close 时触发状态上报;onStateChange 回调支持对接 Prometheus 或 OpenTelemetry。
TCP状态机与异常聚类
TIME_WAIT 过量常源于短连接高频创建。可通过 ss -tan state time-wait 采集后聚类分析:
| 源IP段 | TIME_WAIT 数量 | 持续时间中位数 | 是否异常 |
|---|---|---|---|
| 192.168.1.0/24 | 1247 | 32s | ✅ |
| 10.0.5.0/24 | 8 | 62s | ❌ |
状态流转可视化
graph TD
CLOSED --> SYN_SENT --> ESTABLISHED --> FIN_WAIT_1 --> FIN_WAIT_2 --> TIME_WAIT --> CLOSED
ESTABLISHED --> CLOSE_WAIT --> LAST_ACK --> CLOSED
状态跃迁路径映射至指标标签,支撑动态阈值告警。
第四章:SLO驱动的告警与自动化响应闭环建设
4.1 Go服务SLO定义与Error Budget量化:基于SLI计算引擎的实时误差预算消耗看板
SLO需锚定可观测、可聚合的SLI。典型Go服务常以http_success_rate(2xx/3xx响应占比)和p99_latency_ms ≤ 300为双核心SLI。
SLI采集与聚合逻辑
使用Prometheus客户端暴露指标,并通过预聚合规则降噪:
// metrics.go:定义并注册SLI指标
var (
httpSuccessRate = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: "http_success_rate",
Help: "HTTP success rate per route (value in [0,1])",
},
[]string{"route"},
)
)
// 注:此处不直接打点,而由中间件在请求结束时按status code分类计算后Set()
该代码声明了带route标签的实时成功率指标;Set()调用由HTTP中间件在defer中执行,确保仅统计已响应请求,规避超时漏计问题。
Error Budget实时计算公式
| 维度 | 公式 | 示例(7d SLO=99.9%) |
|---|---|---|
| 初始预算 | 1 - SLO × 总请求量 |
0.001 × 10M = 10k 错误容限 |
| 实时消耗 | 累计错误数 / 总请求数 |
当前消耗率 0.00082 → 剩余预算 1.8k |
预算看板数据流
graph TD
A[Go HTTP Handler] --> B[Middleware: status/code tagging]
B --> C[Prometheus Client: Set http_success_rate]
C --> D[SLI Rule: avg_over_time(http_success_rate[1h]) ]
D --> E[ErrorBudget Engine: 1 - avg_rate(errors[7d]) / avg_rate(requests[7d])]
E --> F[Realtime Dashboard: Gauge + Burn Rate Trend]
4.2 告警去噪与分级:基于时间序列相似性聚类的重复告警抑制与P0/P1/P2动态分级策略
核心思想
将告警时间戳、指标波动模式与上下文标签(如服务名、实例ID、错误码)构造成多维时间序列,通过DTW(Dynamic Time Warping)度量序列相似性,再以谱聚类实现无监督去重。
相似性计算示例
from dtaidistance import dtw
# 告警序列:每条为过去5分钟内CPU使用率采样点(归一化后)
seq_a = [0.82, 0.85, 0.91, 0.89, 0.87]
seq_b = [0.83, 0.86, 0.90, 0.88, 0.86]
distance = dtw.distance(seq_a, seq_b) # DTW容忍时序偏移,比欧氏距离更鲁棒
dtw.distance自动对齐非同步波动峰谷;阈值设为0.12可合并92%重复告警(实测A/B集群均值)。
动态分级规则
| 级别 | 触发条件 | 响应时效 |
|---|---|---|
| P0 | 聚类中心告警 + 关键服务 + 持续>3min | ≤30s |
| P1 | 聚类内告警 + 非核心服务 | ≤5min |
| P2 | 孤立告警或低频波动 | 异步聚合 |
流程概览
graph TD
A[原始告警流] --> B[提取时序特征]
B --> C[DTW相似度矩阵]
C --> D[谱聚类分组]
D --> E[按服务SLA+持续时长动态打标]
E --> F[P0/P1/P2路由至不同处置通道]
4.3 自动化诊断剧本(Runbook)执行:Go编写的轻量级Operator联动K8s Event与Prometheus Alertmanager
核心架构设计
Operator监听 Alertmanager 的 alert Webhook 事件,并捕获 Kubernetes Event 中的异常上下文(如 FailedScheduling、ContainerCreating),触发预定义的 YAML 格式 Runbook。
Runbook 执行流程
// runbook.go:基于 alert labels 匹配并加载对应诊断脚本
func (r *RunbookReconciler) Execute(ctx context.Context, alert *v1.Alert) error {
runbookName := alert.Labels["runbook"] // 如 "etcd-unhealthy"
rb, _ := r.store.Get(runbookName)
return r.execScript(ctx, rb.Script, alert.Annotations)
}
alert.Labels["runbook"]是 Prometheus Alert 规则中显式注入的剧本标识;rb.Script为嵌入 Go 模板的 Bash/Python 片段,支持{{ .Labels.instance }}动态渲染。
关键能力对比
| 能力 | 原生 Alertmanager | 本 Operator |
|---|---|---|
| 事件上下文关联 | ❌ | ✅(K8s Event + Pod/Node 元数据) |
| Runbook 版本化管理 | ❌ | ✅(GitOps 同步 ConfigMap) |
graph TD
A[Prometheus Alert] --> B[Alertmanager Webhook]
C[K8s Event] --> D[Operator 聚合上下文]
B --> D
D --> E[匹配 Runbook]
E --> F[执行诊断脚本]
4.4 MTTR压缩验证体系:基于混沌工程+Golden Signal回放的可观测性有效性度量框架
核心验证闭环
将混沌注入(如延迟、错误率突增)与黄金信号(Latency、Error、Traffic、Saturation)的历史时序回放耦合,构建“扰动-响应-归因”验证环。
Golden Signal 回放示例
# 基于Prometheus Remote Write API回放7天前同负载窗口的指标流
replay_job = {
"query": 'rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m])',
"start": "2024-05-10T14:00:00Z", # 真实故障发生时刻
"step": "30s",
"source": "prod-archive" # 归档TSDB实例
}
该配置确保复现真实业务压力下的SLO敏感路径,step=30s匹配告警收敛粒度,避免采样失真。
验证维度矩阵
| 维度 | 指标 | 合格阈值 |
|---|---|---|
| 定位时效 | 从告警触发到根因标签生成耗时 | ≤90s |
| 诊断准确率 | Top3推荐根因中含真实原因占比 | ≥85% |
| 自愈触发率 | 符合预设策略的自动修复执行率 | ≥92% |
混沌-可观测性协同流程
graph TD
A[混沌注入:Pod CPU Throttle] --> B[Golden Signal 实时比对]
B --> C{偏差超阈值?}
C -->|是| D[启动Trace/Log/Profile 关联分析]
C -->|否| E[标记为可观测盲区]
D --> F[生成MTTR压缩归因报告]
第五章:面向云原生演进的Go可观测性新范式
从单体埋点到声明式可观测性注入
在Kubernetes集群中运行的Go微服务(如基于Gin构建的订单服务)已普遍采用OpenTelemetry SDK v1.22+,通过otelhttp.NewHandler和otelgrpc.Interceptor实现零侵入HTTP/gRPC链路追踪。某电商中台项目将原有手动span.AddEvent()调用全部替换为结构化日志+自动上下文传播,使Trace采样率从5%提升至100%且P99延迟降低37ms。关键改造点在于利用Go 1.21+的context.WithValue与otel.GetTextMapPropagator().Inject()深度集成,在HTTP中间件层统一注入traceparent头。
动态指标采集策略驱动资源优化
生产环境观测数据爆炸式增长倒逼指标采集精细化。某金融风控服务基于Prometheus Operator部署了自定义ServiceMonitor,并结合Go runtime指标(runtime/metrics包)动态调整采集频率:当/debug/metrics中memstats:heap_alloc:bytes超过2GB时,自动将go_gc_duration_seconds采集间隔从15s缩短至5s;内存回落至1.2GB以下则恢复默认策略。该机制通过CRD ObservabilityPolicy定义,由Operator监听并热重载配置,避免重启Pod。
| 组件类型 | 传统方式 | 云原生新范式 | 资源节省效果 |
|---|---|---|---|
| 日志采集 | Filebeat轮询本地文件 | OpenTelemetry Collector DaemonSet + Kubernetes log API直采 | 磁盘IO下降62%,CPU占用减少1.8核/Pod |
| 分布式追踪 | Jaeger Agent Sidecar | eBPF增强型OTel Collector(使用libbpf-go捕获TLS握手事件) | 追踪Span生成开销降低44%,支持加密流量元数据提取 |
基于eBPF的无侵入性能诊断实践
某CDN边缘节点Go服务(使用fasthttp)遭遇偶发性goroutine泄漏。运维团队部署了基于cilium/ebpf开发的探针,通过kprobe挂载runtime.newproc1函数,实时统计每秒新建goroutine数,并将异常峰值(>5000/s)自动触发pprof堆栈快照上传至对象存储。该方案无需修改应用代码,且规避了runtime.NumGoroutine()的采样延迟问题——实测定位到第三方JWT库在密钥轮换时未释放协程池。
// OTel自动仪表化核心代码片段(Go 1.22)
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
func initTracer() {
exp, _ := otlptracegrpc.New(context.Background())
tp := trace.NewTracerProvider(
trace.WithBatcher(exp),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("payment-service"),
semconv.ServiceVersionKey.String("v2.4.0"),
)),
)
otel.SetTracerProvider(tp)
}
多维关联分析替代单点告警
在混合云环境中,某跨AZ部署的Go服务出现5xx错误率突增。传统监控仅触发HTTP状态码告警,而新范式通过OpenTelemetry Collector的transform处理器,将Span中的http.status_code、net.peer.ip、k8s.pod.name与Prometheus指标container_cpu_usage_seconds_total进行时间窗口内Join,发现错误集中发生在特定GPU节点上的TensorFlow推理服务Pod——最终定位为CUDA驱动版本不兼容导致的进程崩溃。此分析流程通过Grafana Loki+Tempo+Prometheus三元组联动完成,查询语句支持LogQL与PromQL混合语法。
graph LR
A[Go应用] -->|OTLP gRPC| B[OTel Collector]
B --> C{Processor Pipeline}
C --> D[Metrics:Prometheus Exporter]
C --> E[Traces:Jaeger Backend]
C --> F[Logs:Loki Sink]
D --> G[Prometheus Server]
E --> H[Tempo Query Layer]
F --> I[Loki Indexing]
G & H & I --> J[Grafana Unified Dashboard] 