使用OpenTelemetry Swift向Otel Collector发送指标异常排查
首次使用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:
问题排查与修复建议
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()View选择器与实际仪器不匹配
InstrumentSelector指定的仪器名称为"Gauge",但实际创建的Gauge名称是"battery_level_percentage",导致View规则无法匹配生效,影响指标识别。修改选择器:InstrumentSelector.builder() .setInstrument(name: "battery_level_percentage") .setMeter(name: "battery_monitoring") .build()MetricReader时间单位误解
setInterval(timeInterval: 1000)的单位是毫秒(即1秒采集间隔),但代码仅调用一次record,后续周期采集无新数据。建议持续更新Gauge值,或确保forceFlush等待异步操作完成。主线程阻塞问题
使用sleep()会阻塞MainActor主线程,导致OpenTelemetry后台处理无法正常运转。必须改用异步任务等待,保证事件循环正常执行。GRPC连接配置优化
添加连接超时配置,避免因连接超时导致数据丢失:let exporterChannel = ClientConnection.insecure(group: group) .withConnectTimeout(.seconds(10)) .connect(host: host, port: port)
内容的提问来源于stack exchange,提问作者Dylan LE HIR

