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 ──────────────────────────────────────────────────────
|
# ─── OpenTelemetry Tracing ──────────────────────────────────────────────────────
|
||||||
otel:
|
otel:
|
||||||
|
export_protocol: "grpc" # grpc 或 http/protobuf;OTEL_EXPORTER_OTLP_TRACES_PROTOCOL 优先于通用 OTLP 协议变量
|
||||||
sampling_rate: 0.0 # Trace sampling rate (0.0 – 1.0)
|
sampling_rate: 0.0 # Trace sampling rate (0.0 – 1.0)
|
||||||
tracer_name: "github.com/Rain-kl/OpenFlare" # Global tracer instrumentation name
|
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 秒行为。
|
- Cloudflare 指向分组支持选择自动 TTL 或手动填写 60~86400 秒,适用于节点绑定与自定义 A、AAAA、CNAME;灰云成员使用分组配置,橙云成员始终自动,旧分组保留原有 300 秒行为。
|
||||||
|
|
||||||
## [v3.5.9] - 2026-11-07
|
## [v3.5.9] - 2026-11-07
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ OTEL_EXPORTER_OTLP_ENDPOINT: https://collector.example.com
|
|||||||
OTEL_SAMPLING_RATE: "0.1"
|
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` 优先于通用请求头配置。应用不预设鉴权方案;实际请求头与凭据由部署环境提供,不要将真实凭据写入仓库。
|
需要鉴权或其他请求头时,使用可选的 `OTEL_EXPORTER_OTLP_HEADERS`,格式为 `key=value,key2=value2`,值按标准进行百分号编码,例如 `X-API-Key=example-key,X-Client-Name=example%20client`。链路专用的 `OTEL_EXPORTER_OTLP_TRACES_HEADERS` 优先于通用请求头配置。应用不预设鉴权方案;实际请求头与凭据由部署环境提供,不要将真实凭据写入仓库。
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ var rootCmd = &cobra.Command{
|
|||||||
Compress: config.Config.Log.Compress,
|
Compress: config.Config.Log.Compress,
|
||||||
})
|
})
|
||||||
trace.Init(trace.Config{
|
trace.Init(trace.Config{
|
||||||
AppName: config.Config.App.AppName,
|
AppName: config.Config.App.AppName,
|
||||||
SamplingRate: config.Config.Otel.SamplingRate,
|
SamplingRate: config.Config.Otel.SamplingRate,
|
||||||
TracerName: config.Config.Otel.TracerName,
|
TracerName: config.Config.Otel.TracerName,
|
||||||
|
ExportProtocol: config.Config.Otel.ExportProtocol,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
PreRun: func(_ *cobra.Command, _ []string) {
|
PreRun: func(_ *cobra.Command, _ []string) {
|
||||||
|
|||||||
@@ -273,6 +273,14 @@ func applyEnvOverrides(c *configModel) {
|
|||||||
c.Log.Output = envStr("LOG_OUTPUT", c.Log.Output)
|
c.Log.Output = envStr("LOG_OUTPUT", c.Log.Output)
|
||||||
|
|
||||||
// ─── OTel ───
|
// ─── 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.SamplingRate = envFloat64("OTEL_SAMPLING_RATE", c.Otel.SamplingRate)
|
||||||
c.Otel.TracerName = envStr("OTEL_TRACER_NAME", c.Otel.TracerName)
|
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)
|
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 配置
|
// otelConfig OpenTelemetry 配置
|
||||||
type otelConfig struct {
|
type otelConfig struct {
|
||||||
SamplingRate float64 `mapstructure:"sampling_rate"`
|
ExportProtocol string `mapstructure:"export_protocol"`
|
||||||
TracerName string `mapstructure:"tracer_name"`
|
SamplingRate float64 `mapstructure:"sampling_rate"`
|
||||||
|
TracerName string `mapstructure:"tracer_name"`
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -27,9 +27,10 @@ func init() {
|
|||||||
|
|
||||||
// Config 链路追踪配置
|
// Config 链路追踪配置
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppName string
|
AppName string
|
||||||
SamplingRate float64
|
SamplingRate float64
|
||||||
TracerName string
|
TracerName string
|
||||||
|
ExportProtocol string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init 初始化 Tracer Provider 并关联全局 Tracer 实例
|
// Init 初始化 Tracer Provider 并关联全局 Tracer 实例
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// newTracerProvider combines SDK and application resources with a batched exporter.
|
||||||
func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
||||||
// 获取主机名和容器信息
|
// 获取主机名和容器信息
|
||||||
hostname, err := os.Hostname()
|
hostname, err := os.Hostname()
|
||||||
@@ -40,7 +41,7 @@ func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化 Exporter
|
// 初始化 Exporter
|
||||||
traceExporter, err := newTraceExporter(context.Background())
|
traceExporter, err := newTraceExporter(context.Background(), cfg.ExportProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// newTraceExporter selects the OTLP wire transport without changing existing gRPC defaults.
|
||||||
// The SDK exporters read standard OTEL_EXPORTER_OTLP_* endpoint and header settings.
|
// The SDK exporters read standard OTEL_EXPORTER_OTLP_* endpoint and header settings.
|
||||||
func newTraceExporter(ctx context.Context) (sdktrace.SpanExporter, error) {
|
func newTraceExporter(ctx context.Context, protocol string) (sdktrace.SpanExporter, error) {
|
||||||
protocol := strings.ToLower(strings.TrimSpace(os.Getenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL")))
|
protocol = strings.ToLower(strings.TrimSpace(protocol))
|
||||||
if protocol == "" {
|
|
||||||
protocol = strings.ToLower(strings.TrimSpace(os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL")))
|
|
||||||
}
|
|
||||||
switch protocol {
|
switch protocol {
|
||||||
case "", "grpc":
|
case "", "grpc":
|
||||||
return otlptracegrpc.New(ctx)
|
return otlptracegrpc.New(ctx)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestOTLPHTTPTraceExportHeadersAndResources verifies wire payloads against a local collector.
|
||||||
func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
||||||
type received struct {
|
type received struct {
|
||||||
headers http.Header
|
headers http.Header
|
||||||
@@ -37,10 +38,8 @@ func TestOTLPHTTPTraceExportHeadersAndResources(t *testing.T) {
|
|||||||
_, _ = w.Write(responseBody)
|
_, _ = w.Write(responseBody)
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "")
|
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "")
|
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "")
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_HEADERS", "")
|
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_ENDPOINT", server.URL)
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_HEADERS", "X-API-Key=test-key,X-Client-Name=test%20client")
|
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")
|
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",
|
name: "trace_specific_overrides",
|
||||||
env: map[string]string{
|
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_ENDPOINT": server.URL + "/custom/ingest",
|
||||||
"OTEL_EXPORTER_OTLP_TRACES_HEADERS": "X-API-Key=trace-key,X-Client-Name=trace%20client",
|
"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 {
|
for key, value := range tt.env {
|
||||||
t.Setenv(key, value)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
func protoBytes(t *testing.T, v proto.Message) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
data, err := proto.Marshal(v)
|
data, err := proto.Marshal(v)
|
||||||
@@ -131,11 +129,23 @@ func protoBytes(t *testing.T, v proto.Message) []byte {
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUnknownOTLPProtocolFails rejects unsupported wire formats during initialization.
|
||||||
func TestUnknownOTLPProtocolFails(t *testing.T) {
|
func TestUnknownOTLPProtocolFails(t *testing.T) {
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http/json")
|
_, err := newTraceExporter(context.Background(), "http/json")
|
||||||
t.Setenv("OTEL_EXPORTER_OTLP_TRACES_PROTOCOL", "")
|
|
||||||
_, err := newTraceExporter(context.Background())
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "unsupported OTLP") {
|
if err == nil || !strings.Contains(err.Error(), "unsupported OTLP") {
|
||||||
t.Fatalf("expected protocol error, got %v", err)
|
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