mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-11-12 21:44:22 +08:00
fix(otel): resolve exporter protocol through startup configuration
This commit is contained in:
@@ -95,6 +95,7 @@ worker:
|
||||
|
||||
# ─── OpenTelemetry Tracing ──────────────────────────────────────────────────────
|
||||
otel:
|
||||
export_protocol: "grpc" # grpc 或 http/protobuf;OTEL_EXPORTER_OTLP_TRACES_PROTOCOL 优先于通用 OTLP 协议变量
|
||||
sampling_rate: 0.0 # Trace sampling rate (0.0 – 1.0)
|
||||
tracer_name: "github.com/Rain-kl/OpenFlare" # Global tracer instrumentation name
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ sidebar: false
|
||||
|
||||
### ✨ 新功能
|
||||
|
||||
- OpenTelemetry 链路追踪新增 OTLP/HTTP protobuf 导出,通过标准环境变量配置接收端地址和自定义请求头,保留原有 OTLP/gRPC 默认模式;未支持的协议在启动时明确报错。
|
||||
- OpenTelemetry 链路追踪新增 OTLP/HTTP protobuf 导出,通过标准环境变量配置接收端地址和自定义请求头,协议由统一启动配置解析并支持 `otel.export_protocol`,保留原有 OTLP/gRPC 默认模式;未支持的协议在启动时明确报错。
|
||||
- Cloudflare 指向分组支持选择自动 TTL 或手动填写 60~86400 秒,适用于节点绑定与自定义 A、AAAA、CNAME;灰云成员使用分组配置,橙云成员始终自动,旧分组保留原有 300 秒行为。
|
||||
|
||||
## [v3.5.9] - 2026-11-07
|
||||
|
||||
@@ -282,7 +282,7 @@ OTEL_EXPORTER_OTLP_ENDPOINT: https://collector.example.com
|
||||
OTEL_SAMPLING_RATE: "0.1"
|
||||
```
|
||||
|
||||
标准 SDK 会在通用 `OTEL_EXPORTER_OTLP_ENDPOINT` 的路径后追加 `/v1/traces`;例如上述配置发送到 `https://collector.example.com/v1/traces`。如果接收端使用自定义路径,请设置完整的 `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,SDK 将直接使用该 URL,不再追加路径。链路专用的 `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` 优先于通用协议配置。
|
||||
标准 SDK 会在通用 `OTEL_EXPORTER_OTLP_ENDPOINT` 的路径后追加 `/v1/traces`;例如上述配置发送到 `https://collector.example.com/v1/traces`。如果接收端使用自定义路径,请设置完整的 `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,SDK 将直接使用该 URL,不再追加路径。链路专用的 `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL` 优先于通用协议配置;两者均未配置时使用 `otel.export_protocol`,缺省为 `grpc`。协议在启动配置层统一解析,再传入追踪组件。
|
||||
|
||||
需要鉴权或其他请求头时,使用可选的 `OTEL_EXPORTER_OTLP_HEADERS`,格式为 `key=value,key2=value2`,值按标准进行百分号编码,例如 `X-API-Key=example-key,X-Client-Name=example%20client`。链路专用的 `OTEL_EXPORTER_OTLP_TRACES_HEADERS` 优先于通用请求头配置。应用不预设鉴权方案;实际请求头与凭据由部署环境提供,不要将真实凭据写入仓库。
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ var rootCmd = &cobra.Command{
|
||||
AppName: config.Config.App.AppName,
|
||||
SamplingRate: config.Config.Otel.SamplingRate,
|
||||
TracerName: config.Config.Otel.TracerName,
|
||||
ExportProtocol: config.Config.Otel.ExportProtocol,
|
||||
})
|
||||
},
|
||||
PreRun: func(_ *cobra.Command, _ []string) {
|
||||
|
||||
@@ -273,6 +273,14 @@ func applyEnvOverrides(c *configModel) {
|
||||
c.Log.Output = envStr("LOG_OUTPUT", c.Log.Output)
|
||||
|
||||
// ─── OTel ───
|
||||
protocol := strings.TrimSpace(envStr("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", ""))
|
||||
if protocol == "" {
|
||||
protocol = strings.TrimSpace(envStr("OTEL_EXPORTER_OTLP_PROTOCOL", ""))
|
||||
}
|
||||
if protocol == "" {
|
||||
protocol = c.Otel.ExportProtocol
|
||||
}
|
||||
c.Otel.ExportProtocol = strings.ToLower(strings.TrimSpace(protocol))
|
||||
c.Otel.SamplingRate = envFloat64("OTEL_SAMPLING_RATE", c.Otel.SamplingRate)
|
||||
c.Otel.TracerName = envStr("OTEL_TRACER_NAME", c.Otel.TracerName)
|
||||
|
||||
|
||||
@@ -47,3 +47,24 @@ func TestApplyClickHouseDefaultsEnablesWhenConfigured(t *testing.T) {
|
||||
t.Fatalf("hosts default not applied: %v", cfg.ClickHouse.Hosts)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOTLPProtocolPrecedence covers signal-specific, general, YAML and default settings.
|
||||
func TestOTLPProtocolPrecedence(t *testing.T) {
|
||||
for _, tt := range []struct{ name, signal, general, configured, want string }{
|
||||
{name: "default"},
|
||||
{name: "yaml", configured: "http/protobuf", want: "http/protobuf"},
|
||||
{name: "general", general: "http/protobuf", configured: "grpc", want: "http/protobuf"},
|
||||
{name: "signal", signal: " HTTP/PROTOBUF ", general: "grpc", want: "http/protobuf"},
|
||||
{name: "empty_signal", signal: " ", general: "grpc", configured: "http/protobuf", want: "grpc"},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", tt.signal)
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", tt.general)
|
||||
cfg := &configModel{Otel: otelConfig{ExportProtocol: tt.configured}}
|
||||
applyEnvOverrides(cfg)
|
||||
if cfg.Otel.ExportProtocol != tt.want {
|
||||
t.Errorf("OTLP protocol = %q, want %q", cfg.Otel.ExportProtocol, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ type QueueConfig struct {
|
||||
|
||||
// otelConfig OpenTelemetry 配置
|
||||
type otelConfig struct {
|
||||
ExportProtocol string `mapstructure:"export_protocol"`
|
||||
SamplingRate float64 `mapstructure:"sampling_rate"`
|
||||
TracerName string `mapstructure:"tracer_name"`
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ type Config struct {
|
||||
AppName string
|
||||
SamplingRate float64
|
||||
TracerName string
|
||||
ExportProtocol string
|
||||
}
|
||||
|
||||
// Init 初始化 Tracer Provider 并关联全局 Tracer 实例
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
// newTracerProvider combines SDK and application resources with a batched exporter.
|
||||
func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
||||
// 获取主机名和容器信息
|
||||
hostname, err := os.Hostname()
|
||||
@@ -40,7 +41,7 @@ func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
||||
}
|
||||
|
||||
// 初始化 Exporter
|
||||
traceExporter, err := newTraceExporter(context.Background())
|
||||
traceExporter, err := newTraceExporter(context.Background(), cfg.ExportProtocol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -56,11 +57,8 @@ func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
||||
|
||||
// newTraceExporter selects the OTLP wire transport without changing existing gRPC defaults.
|
||||
// The SDK exporters read standard OTEL_EXPORTER_OTLP_* endpoint and header settings.
|
||||
func newTraceExporter(ctx context.Context) (sdktrace.SpanExporter, error) {
|
||||
protocol := strings.ToLower(strings.TrimSpace(os.Getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")))
|
||||
if protocol == "" {
|
||||
protocol = strings.ToLower(strings.TrimSpace(os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL")))
|
||||
}
|
||||
func newTraceExporter(ctx context.Context, protocol string) (sdktrace.SpanExporter, error) {
|
||||
protocol = strings.ToLower(strings.TrimSpace(protocol))
|
||||
switch protocol {
|
||||
case "", "grpc":
|
||||
return otlptracegrpc.New(ctx)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// TestOTLPHTTPTraceExportHeadersAndResources verifies wire payloads against a local collector.
|
||||
func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||
type received struct {
|
||||
headers http.Header
|
||||
@@ -37,10 +38,8 @@ func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||
_, _ = w.Write(responseBody)
|
||||
}))
|
||||
defer server.Close()
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "")
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "")
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "")
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/protobuf")
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", server.URL)
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_HEADERS", "X-API-Key=test-key,X-Client-Name=test%20client")
|
||||
t.Setenv("OTEL_RESOURCE_ATTRIBUTES", "service.namespace=example,deployment.environment.name=test")
|
||||
@@ -65,8 +64,6 @@ func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||
{
|
||||
name: "trace_specific_overrides",
|
||||
env: map[string]string{
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": "http/protobuf",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT": server.URL + "/custom/ingest",
|
||||
"OTEL_EXPORTER_OTLP_TRACES_HEADERS": "X-API-Key=trace-key,X-Client-Name=trace%20client",
|
||||
},
|
||||
@@ -79,7 +76,7 @@ func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||
for key, value := range tt.env {
|
||||
t.Setenv(key, value)
|
||||
}
|
||||
provider, err := newTracerProvider(Config{AppName: "openflare-control-plane", SamplingRate: 1})
|
||||
provider, err := newTracerProvider(Config{AppName: "openflare-control-plane", SamplingRate: 1, ExportProtocol: "http/protobuf"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -122,6 +119,7 @@ func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// protoBytes encodes a collector response for the HTTP test server.
|
||||
func protoBytes(t *testing.T, v proto.Message) []byte {
|
||||
t.Helper()
|
||||
data, err := proto.Marshal(v)
|
||||
@@ -131,11 +129,23 @@ func protoBytes(t *testing.T, v proto.Message) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// TestUnknownOTLPProtocolFails rejects unsupported wire formats during initialization.
|
||||
func TestUnknownOTLPProtocolFails(t *testing.T) {
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/json")
|
||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "")
|
||||
_, err := newTraceExporter(context.Background())
|
||||
_, err := newTraceExporter(context.Background(), "http/json")
|
||||
if err == nil || !strings.Contains(err.Error(), "unsupported OTLP") {
|
||||
t.Fatalf("expected protocol error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDefaultOTLPProtocolGRPC keeps empty and explicit gRPC configuration working.
|
||||
func TestDefaultOTLPProtocolGRPC(t *testing.T) {
|
||||
for _, protocol := range []string{"", "grpc"} {
|
||||
exporter, err := newTraceExporter(context.Background(), protocol)
|
||||
if err != nil {
|
||||
t.Fatalf("newTraceExporter(%q): %v", protocol, err)
|
||||
}
|
||||
if err := exporter.Shutdown(context.Background()); err != nil {
|
||||
t.Errorf("exporter.Shutdown(%q): %v", protocol, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user