diff --git a/go.mod b/go.mod index 2bd163b97f..f6cbe43af5 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 github.com/go-playground/validator/v10 v10.26.0 github.com/go-viper/mapstructure/v2 v2.4.0 - github.com/golang-jwt/jwt/v5 v5.2.3 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/grafana/otel-profiling-go v0.5.1 @@ -37,7 +37,8 @@ require ( github.com/mr-tron/base58 v1.2.0 github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml/v2 v2.2.4 - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/scylladb/go-reflectx v1.0.1 github.com/shopspring/decimal v1.4.0 @@ -54,6 +55,7 @@ require ( github.com/smartcontractkit/libocr v0.0.0-20250912173940-f3ab0246e23d github.com/stellar/go-stellar-sdk v0.5.0 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 @@ -133,9 +135,8 @@ require ( github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect @@ -148,6 +149,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum index a9c0048123..e961bbd66a 100644 --- a/go.sum +++ b/go.sum @@ -88,8 +88,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -242,15 +242,15 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -322,6 +322,8 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 h1:w3zlHYETbDwXyWHZlyyR58ZC39XGi8rAhkBgUgJ9d5w= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0/go.mod h1:GR/mClR2nn7vE8RLwxKjoBNg+QtgdDhRzxVa93koy5o= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= @@ -372,6 +374,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/pkg/beholder/client.go b/pkg/beholder/client.go index e8fce549bc..c13ecea1eb 100644 --- a/pkg/beholder/client.go +++ b/pkg/beholder/client.go @@ -567,16 +567,18 @@ func newMeterProvider(cfg Config, resource *sdkresource.Resource, auth Auth, cre if err != nil { return nil, err } - mp := sdkmetric.NewMeterProvider( - sdkmetric.WithReader( - sdkmetric.NewPeriodicReader( - exporter, - sdkmetric.WithInterval(cfg.MetricReaderInterval), // Default is 10s - )), + + readerOpts := []sdkmetric.PeriodicReaderOption{ + sdkmetric.WithInterval(cfg.MetricReaderInterval), // Default is 10s + } + for _, p := range cfg.MetricProducers { + readerOpts = append(readerOpts, sdkmetric.WithProducer(p)) + } + return sdkmetric.NewMeterProvider( + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exporter, readerOpts...)), sdkmetric.WithResource(resource), sdkmetric.WithView(cfg.MetricViews...), - ) - return mp, nil + ), nil } // newLoggerOpts creates options for a logger exporter diff --git a/pkg/beholder/config.go b/pkg/beholder/config.go index 5e5167f0f1..09ef907ed7 100644 --- a/pkg/beholder/config.go +++ b/pkg/beholder/config.go @@ -42,6 +42,7 @@ type Config struct { MetricViews []metric.View // MetricCompressor sets the gRPC compressor for metrics. Valid values: "gzip" (default), "none". MetricCompressor string + MetricProducers []metric.Producer // For example, a prometheus bridge // Custom Events via Chip Ingress Emitter ChipIngressEmitterEnabled bool diff --git a/pkg/beholder/config_test.go b/pkg/beholder/config_test.go index 318cc94a91..a287af70b3 100644 --- a/pkg/beholder/config_test.go +++ b/pkg/beholder/config_test.go @@ -1,9 +1,16 @@ package beholder_test import ( + _ "embed" + "encoding/json" + "flag" "fmt" + "os" + "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" otelattr "go.opentelemetry.io/otel/attribute" "go.uber.org/zap/zapcore" @@ -14,7 +21,12 @@ const ( packageName = "beholder" ) -func ExampleConfig() { +//go:embed testdata/config-example.json +var configExample string + +var update = flag.Bool("update", false, "update golden test files") + +func TestConfig(t *testing.T) { config := beholder.Config{ InsecureConnection: true, CACertFile: "", @@ -31,7 +43,7 @@ func ExampleConfig() { EmitterExportInterval: 1 * time.Second, EmitterMaxQueueSize: 2048, // true uses batched async export for custom messages. - EmitterBatchProcessor: true, + EmitterBatchProcessor: true, // OTel message log exporter retry config LogRetryConfig: nil, // Trace @@ -60,14 +72,20 @@ func ExampleConfig() { AuthKeySigner: nil, AuthHeadersTTL: 0, } - fmt.Printf("%+v\n", config) + + b, err := json.MarshalIndent(config, "", " ") + require.NoError(t, err) + + if *update { + require.NoError(t, os.WriteFile("testdata/config-example.json", b, 0644)) + } else { + assert.Equal(t, configExample, string(b)) + } + config.LogRetryConfig = &beholder.RetryConfig{ InitialInterval: 5 * time.Second, MaxInterval: 30 * time.Second, MaxElapsedTime: 1 * time.Minute, // Set to zero to disable retry } - fmt.Printf("%+v\n", *config.LogRetryConfig) - // Output: - // {InsecureConnection:true CACertFile: OtelExporterGRPCEndpoint:localhost:4317 OtelExporterHTTPEndpoint:localhost:4318 ResourceAttributes:[{Key:package_name Value:{vtype:4 numeric:0 stringly:beholder slice:}} {Key:sender Value:{vtype:4 numeric:0 stringly:beholderclient slice:}}] EmitterExportTimeout:1s EmitterExportInterval:1s EmitterExportMaxBatchSize:512 EmitterMaxQueueSize:2048 EmitterBatchProcessor:true TraceSampleRatio:1 TraceBatchTimeout:1s TraceSpanExporter: TraceRetryConfig: TraceCompressor:gzip MetricReaderInterval:1s MetricRetryConfig: MetricViews:[] MetricCompressor:gzip ChipIngressEmitterEnabled:false ChipIngressEmitterGRPCEndpoint: ChipIngressInsecureConnection:false ChipIngressBatchEmitterEnabled:false ChipIngressBufferSize:0 ChipIngressMaxBatchSize:0 ChipIngressSendInterval:0s ChipIngressSendTimeout:0s ChipIngressDrainTimeout:0s ChipIngressMaxConcurrentSends:0 ChipIngressLogger: LogExportTimeout:1s LogExportInterval:1s LogExportMaxBatchSize:512 LogMaxQueueSize:2048 LogBatchProcessor:true LogRetryConfig: LogStreamingEnabled:false LogLevel:info LogCompressor:gzip AuthHeaders:map[] AuthHeadersTTL:0s AuthKeySigner: AuthPublicKeyHex:} - // {InitialInterval:5s MaxInterval:30s MaxElapsedTime:1m0s} + assert.Equal(t, "{InitialInterval:5s MaxInterval:30s MaxElapsedTime:1m0s}", fmt.Sprintf("%+v", *config.LogRetryConfig)) } diff --git a/pkg/beholder/testdata/config-example.json b/pkg/beholder/testdata/config-example.json new file mode 100644 index 0000000000..c8aacc6baf --- /dev/null +++ b/pkg/beholder/testdata/config-example.json @@ -0,0 +1,61 @@ +{ + "InsecureConnection": true, + "CACertFile": "", + "OtelExporterGRPCEndpoint": "localhost:4317", + "OtelExporterHTTPEndpoint": "localhost:4318", + "ResourceAttributes": [ + { + "Key": "package_name", + "Value": { + "Type": "STRING", + "Value": "beholder" + } + }, + { + "Key": "sender", + "Value": { + "Type": "STRING", + "Value": "beholderclient" + } + } + ], + "EmitterExportTimeout": 1000000000, + "EmitterExportInterval": 1000000000, + "EmitterExportMaxBatchSize": 512, + "EmitterMaxQueueSize": 2048, + "EmitterBatchProcessor": true, + "TraceSampleRatio": 1, + "TraceBatchTimeout": 1000000000, + "TraceSpanExporter": null, + "TraceRetryConfig": null, + "TraceCompressor": "gzip", + "MetricReaderInterval": 1000000000, + "MetricRetryConfig": null, + "MetricViews": null, + "MetricCompressor": "gzip", + "MetricProducers": null, + "ChipIngressEmitterEnabled": false, + "ChipIngressEmitterGRPCEndpoint": "", + "ChipIngressInsecureConnection": false, + "ChipIngressBatchEmitterEnabled": false, + "ChipIngressBufferSize": 0, + "ChipIngressMaxBatchSize": 0, + "ChipIngressSendInterval": 0, + "ChipIngressSendTimeout": 0, + "ChipIngressDrainTimeout": 0, + "ChipIngressMaxConcurrentSends": 0, + "ChipIngressLogger": null, + "LogExportTimeout": 1000000000, + "LogExportInterval": 1000000000, + "LogExportMaxBatchSize": 512, + "LogMaxQueueSize": 2048, + "LogBatchProcessor": true, + "LogRetryConfig": null, + "LogStreamingEnabled": false, + "LogLevel": "info", + "LogCompressor": "gzip", + "AuthHeaders": {}, + "AuthHeadersTTL": 0, + "AuthKeySigner": null, + "AuthPublicKeyHex": "" +} \ No newline at end of file diff --git a/pkg/loop/config.go b/pkg/loop/config.go index 966be68e66..c91442117e 100644 --- a/pkg/loop/config.go +++ b/pkg/loop/config.go @@ -82,11 +82,13 @@ const ( envTelemetryLogMaxQueueSize = "CL_TELEMETRY_LOG_MAX_QUEUE_SIZE" envTelemetryTraceCompressor = "CL_TELEMETRY_TRACE_COMPRESSOR" envTelemetryMetricCompressor = "CL_TELEMETRY_METRIC_COMPRESSOR" + envTelemetryPrometheusBridgeEnabled = "CL_TELEMETRY_PROMETHEUS_BRIDGE_ENABLED" + envTelemetryPrometheusBridgePrefixes = "CL_TELEMETRY_PROMETHEUS_BRIDGE_PREFIXES" envTelemetryLogCompressor = "CL_TELEMETRY_LOG_COMPRESSOR" - envChipIngressEndpoint = "CL_CHIP_INGRESS_ENDPOINT" - envChipIngressInsecureConnection = "CL_CHIP_INGRESS_INSECURE_CONNECTION" - envChipIngressBatchEmitterEnabled = "CL_CHIP_INGRESS_BATCH_EMITTER_ENABLED" + envChipIngressEndpoint = "CL_CHIP_INGRESS_ENDPOINT" + envChipIngressInsecureConnection = "CL_CHIP_INGRESS_INSECURE_CONNECTION" + envChipIngressBatchEmitterEnabled = "CL_CHIP_INGRESS_BATCH_EMITTER_ENABLED" envCRESettings = cresettings.EnvNameSettings envCRESettingsDefault = cresettings.EnvNameSettingsDefault @@ -97,8 +99,8 @@ const ( type EnvConfig struct { AppID string - ChipIngressEndpoint string - ChipIngressInsecureConnection bool + ChipIngressEndpoint string + ChipIngressInsecureConnection bool ChipIngressBatchEmitterEnabled bool CRESettings string @@ -139,15 +141,15 @@ type EnvConfig struct { PyroscopePPROFBlockProfileRate int PyroscopePPROFMutexProfileFraction int - TelemetryEnabled bool - TelemetryEndpoint string - TelemetryInsecureConnection bool - TelemetryCACertFile string - TelemetryAttributes OtelAttributes - TelemetryTraceSampleRatio float64 - TelemetryAuthHeaders map[string]string - TelemetryAuthPubKeyHex string - TelemetryAuthHeadersTTL time.Duration + TelemetryEnabled bool + TelemetryEndpoint string + TelemetryInsecureConnection bool + TelemetryCACertFile string + TelemetryAttributes OtelAttributes + TelemetryTraceSampleRatio float64 + TelemetryAuthHeaders map[string]string + TelemetryAuthPubKeyHex string + TelemetryAuthHeadersTTL time.Duration // TelemetryEmitterBatchProcessor maps to beholder Config.EmitterBatchProcessor // (batched async custom-message export vs immediate per-record export). TelemetryEmitterBatchProcessor bool @@ -164,6 +166,8 @@ type EnvConfig struct { TelemetryLogMaxQueueSize int TelemetryTraceCompressor string TelemetryMetricCompressor string + TelemetryPrometheusBridgeEnabled bool + TelemetryPrometheusBridgePrefixes []string TelemetryLogCompressor string TracingEnabled bool @@ -255,6 +259,8 @@ func (e *EnvConfig) AsCmdEnv() (env []string) { add(envTelemetryLogMaxQueueSize, strconv.Itoa(e.TelemetryLogMaxQueueSize)) add(envTelemetryTraceCompressor, e.TelemetryTraceCompressor) add(envTelemetryMetricCompressor, e.TelemetryMetricCompressor) + add(envTelemetryPrometheusBridgeEnabled, strconv.FormatBool(e.TelemetryPrometheusBridgeEnabled)) + add(envTelemetryPrometheusBridgePrefixes, strings.Join(e.TelemetryPrometheusBridgePrefixes, ",")) add(envTelemetryLogCompressor, e.TelemetryLogCompressor) add(envChipIngressEndpoint, e.ChipIngressEndpoint) @@ -484,6 +490,11 @@ func (e *EnvConfig) parse() error { } e.TelemetryTraceCompressor = os.Getenv(envTelemetryTraceCompressor) e.TelemetryMetricCompressor = os.Getenv(envTelemetryMetricCompressor) + e.TelemetryPrometheusBridgeEnabled, err = getBool(envTelemetryPrometheusBridgeEnabled) + if err != nil { + return fmt.Errorf("failed to parse %s: %w", envTelemetryPrometheusBridgeEnabled, err) + } + e.TelemetryPrometheusBridgePrefixes = strings.Split(os.Getenv(envTelemetryPrometheusBridgePrefixes), ",") e.TelemetryLogCompressor = os.Getenv(envTelemetryLogCompressor) // Optional e.ChipIngressEndpoint = os.Getenv(envChipIngressEndpoint) diff --git a/pkg/loop/config_test.go b/pkg/loop/config_test.go index 024ff85622..43ed1d4867 100644 --- a/pkg/loop/config_test.go +++ b/pkg/loop/config_test.go @@ -83,6 +83,8 @@ func TestEnvConfig_parse(t *testing.T) { envTelemetryEmitterExportMaxBatchSize: "100", envTelemetryEmitterMaxQueueSize: "1000", envTelemetryLogStreamingEnabled: "false", + envTelemetryPrometheusBridgeEnabled: "true", + envTelemetryPrometheusBridgePrefixes: "foo,bar", envChipIngressEndpoint: "chip-ingress.example.com:50051", envChipIngressInsecureConnection: "true", @@ -195,6 +197,8 @@ var envCfgFull = EnvConfig{ TelemetryEmitterExportMaxBatchSize: 100, TelemetryEmitterMaxQueueSize: 1000, TelemetryLogStreamingEnabled: false, + TelemetryPrometheusBridgeEnabled: true, + TelemetryPrometheusBridgePrefixes: []string{"foo", "bar"}, ChipIngressEndpoint: "chip-ingress.example.com:50051", ChipIngressInsecureConnection: true, @@ -257,6 +261,8 @@ func TestEnvConfig_AsCmdEnv(t *testing.T) { assert.Equal(t, "100", got[envTelemetryEmitterExportMaxBatchSize]) assert.Equal(t, "1000", got[envTelemetryEmitterMaxQueueSize]) assert.Equal(t, "false", got[envTelemetryLogStreamingEnabled]) + assert.Equal(t, "true", got[envTelemetryPrometheusBridgeEnabled]) + assert.Equal(t, "foo,bar", got[envTelemetryPrometheusBridgePrefixes]) // Assert ChipIngress environment variables assert.Equal(t, "chip-ingress.example.com:50051", got[envChipIngressEndpoint]) diff --git a/pkg/loop/server.go b/pkg/loop/server.go index 307603d502..c266ce6308 100644 --- a/pkg/loop/server.go +++ b/pkg/loop/server.go @@ -13,6 +13,8 @@ import ( otelpyroscope "github.com/grafana/otel-profiling-go" "github.com/grafana/pyroscope-go" "github.com/jmoiron/sqlx" + "github.com/prometheus/client_golang/prometheus" + prombridge "go.opentelemetry.io/contrib/bridges/prometheus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" @@ -29,6 +31,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/promutil" ) // NewStartedServer returns a started Server. @@ -186,6 +189,14 @@ func (s *Server) start(opts ...ServerOpt) error { MetricCompressor: s.EnvConfig.TelemetryMetricCompressor, } + if s.EnvConfig.TelemetryPrometheusBridgeEnabled { + var bridgeOpts []prombridge.Option + if prefixes := s.EnvConfig.TelemetryPrometheusBridgePrefixes; len(prefixes) > 0 { + bridgeOpts = append(bridgeOpts, prombridge.WithGatherer(promutil.NewPrefixGatherer(prometheus.DefaultGatherer, prefixes))) + } + beholderCfg.MetricProducers = append(beholderCfg.MetricProducers, prombridge.NewMetricProducer(bridgeOpts...)) + } + // Configure beholder auth - the client will determine rotating vs static mode // Rotating mode: when AuthHeadersTTL is set, client creates internal lazySigner // Static mode: no TTL is provided it is assumed that the headers are static diff --git a/pkg/sqlutil/promutil/promutil.go b/pkg/sqlutil/promutil/promutil.go new file mode 100644 index 0000000000..db9692cd17 --- /dev/null +++ b/pkg/sqlutil/promutil/promutil.go @@ -0,0 +1,44 @@ +package promutil + +import ( + "slices" + "strings" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +type prefixGatherer struct { + gatherer prometheus.Gatherer + prefixes []string +} + +// NewPrefixGatherer returns a prometheus gatherer that will only produce metrics matching one of the prefixes. +func NewPrefixGatherer(gatherer prometheus.Gatherer, prefixes []string) prometheus.Gatherer { + return &prefixGatherer{gatherer, slices.DeleteFunc(prefixes, func(s string) bool { + return s == "" // ignore empty, which would match everything + })} +} + +func (g *prefixGatherer) Gather() ([]*dto.MetricFamily, error) { + if len(g.prefixes) == 0 { + return g.gatherer.Gather() + } + var ret []*dto.MetricFamily + all, err := g.gatherer.Gather() + if err != nil { + return nil, err + } + for _, m := range all { + if m.Name == nil { + continue + } + for _, prefix := range g.prefixes { + if strings.HasPrefix(*m.Name, prefix) { + ret = append(ret, m) + break + } + } + } + return ret, nil +}