You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenTelemetry Swift向Otel Collector发送指标异常排查

OpenTelemetry Swift 指标发送不稳定问题排查

首次使用OpenTelemetry Swift,尝试将SwiftUI应用的指标发送至OpenTelemetry Collector,再流转到Prometheus/Grafana。已通过Docker Compose部署Otel Collector、Prometheus、Grafana,确认部署环节正常,但代码实现的指标发送不稳定,怀疑是代码中向Otel Collector发送数据的部分存在问题。以下是当前的Swift代码及相关配置文件:

Swift 代码

import UIKit
import Foundation
import OpenTelemetryApi
import OpenTelemetrySdk
import OpenTelemetryProtocolExporterGrpc
import GRPC
import NIO

@MainActor
public class BatteryManager {
    
    private var gauge: DoubleGauge?
    
    public init() {}
        
    public func basicConfiguration(host: String, port: Int) {
        
        let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        let exporterChannel = ClientConnection.insecure(group: group)
            .connect(host: host, port: port)

        let meterProvider = StableMeterProviderBuilder()
            .registerView(
                selector: InstrumentSelector.builder().setInstrument(name: "Gauge").setMeter(version: "1").build(),
                view: StableView.builder().withName(name: "Niveau de batterie de l'iPhone").withDescription(description: "On récupère le niveau de la batterie afin de suivre son évolution").build()
            )
            .registerMetricReader(
                reader: StablePeriodicMetricReaderBuilder(exporter: StableOtlpMetricExporter(channel: exporterChannel)).setInterval(timeInterval: 1000).build()
            )
            .build()

        OpenTelemetry.registerStableMeterProvider(meterProvider: meterProvider)
        
        let meter = meterProvider.meterBuilder(name: "battery_monitoring").build()
        
        gauge = meter.gaugeBuilder(name: "battery_level_percentage").build()
        
        print("📡 Envoi métrique: BatteryLevelGauge -> \(10.0)")
        gauge?.record(value: Double(10.0), attributes: ["device": AttributeValue.string(UIDevice.current.name)])
        
        sleep(60)
        _ = meterProvider.forceFlush()
        sleep(60)
        print("Toutes les étapes de la configuration sont terminées ! 🎉🚴")
        
    }
}

Prometheus.yml 配置

global:
  scrape_interval: 10s

scrape_configs:
  - job_name: "otel-collector"
    static_configs:
      - targets: ["otel-collector:9464"]

otel-collector-config.yaml 配置

receivers:
  otlp:
    protocols:
      grpc:
      http:
        cors:
          allowed_origins:
            - http://*
            - https://*

exporters:
  prometheus:
    endpoint: "0.0.0.0:9464"
  debug:

service:
  pipelines:
    metrics:
      receivers: [otlp]
      exporters: [prometheus, debug]

docker-compose.yml 配置

services:
  otel-collector:
    image: otel/opentelemetry-collector:latest
    container_name: otel-collector
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "9464:9464"   # Endpoint Prometheus (scrape)

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"  # Prometheus Web UI

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"  # Grafana Web UI
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana

volumes:
  grafana-storage:


问题排查与修复建议

  1. EventLoopGroup 生命周期管理缺失
    创建的MultiThreadedEventLoopGroup未正确关闭,且sleep会阻塞主线程,导致GRPC连接无法正常完成数据发送。建议改用异步等待,并在操作结束后关闭Group:

    // 替换sleep(60)为异步等待
    try await Task.sleep(nanoseconds: 60_000_000_000)
    // 等待forceFlush异步完成
    let flushResult = await meterProvider.forceFlush()
    print("强制刷新结果: \(flushResult)")
    try await Task.sleep(nanoseconds: 60_000_000_000)
    // 关闭事件循环组
    try? group.syncShutdownGracefully()
    
  2. View选择器与实际仪器不匹配
    InstrumentSelector指定的仪器名称为"Gauge",但实际创建的Gauge名称是"battery_level_percentage",导致View规则无法匹配生效,影响指标识别。修改选择器:

    InstrumentSelector.builder()
        .setInstrument(name: "battery_level_percentage")
        .setMeter(name: "battery_monitoring")
        .build()
    
  3. MetricReader时间单位误解
    setInterval(timeInterval: 1000)的单位是毫秒(即1秒采集间隔),但代码仅调用一次record,后续周期采集无新数据。建议持续更新Gauge值,或确保forceFlush等待异步操作完成。

  4. 主线程阻塞问题
    使用sleep()会阻塞MainActor主线程,导致OpenTelemetry后台处理无法正常运转。必须改用异步任务等待,保证事件循环正常执行。

  5. GRPC连接配置优化
    添加连接超时配置,避免因连接超时导致数据丢失:

    let exporterChannel = ClientConnection.insecure(group: group)
        .withConnectTimeout(.seconds(10))
        .connect(host: host, port: port)
    

内容的提问来源于stack exchange,提问作者Dylan LE HIR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 19:16:04