diff --git a/CHANGELOG.md b/CHANGELOG.md index 5320ad96a219..07b5a51ba213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* (telemetry) [#26006](https://github.com/cosmos/cosmos-sdk/pull/26006) Export `ExtensionOptions` type for programmatic otel.yaml generation. * [#25955](https://github.com/cosmos/cosmos-sdk/pull/25955) Use cosmos/btree directly instead of replacing it in go.mods * (types) [#25342](https://github.com/cosmos/cosmos-sdk/pull/25342) Undeprecated `EmitEvent` and `EmitEvents` on the `EventManager`. These functions will continue to be maintained. * (types) [#24668](https://github.com/cosmos/cosmos-sdk/pull/24668) Scope the global config to a particular binary so that multiple SDK binaries can be properly run on the same machine. diff --git a/telemetry/README.md b/telemetry/README.md index 7b6e4f4af0aa..bdd684524424 100644 --- a/telemetry/README.md +++ b/telemetry/README.md @@ -56,7 +56,7 @@ logger_provider: endpoint: http://localhost:4317 -cosmos_extra: +extensions: instruments: host: {} # enable optional host instrumentation with go.opentelemetry.io/contrib/instrumentation/host runtime: {} # enable optional runtime instrumentation with go.opentelemetry.io/contrib/instrumentation/runtime diff --git a/telemetry/config.go b/telemetry/config.go index 7f6cc1cacd84..de40bcac1422 100644 --- a/telemetry/config.go +++ b/telemetry/config.go @@ -71,7 +71,6 @@ func InitializeOpenTelemetry(filePath string) error { if openTelemetrySDK != nil { return nil } - var err error var opts []otelconf.ConfigurationOption @@ -99,29 +98,28 @@ func InitializeOpenTelemetry(filePath string) error { opts = append(opts, otelconf.WithOpenTelemetryConfiguration(*cfg)) - // parse cosmos extra config - var extraCfg extraConfig - err = yaml.Unmarshal(bz, &extraCfg) - if err == nil { - if extraCfg.CosmosExtra != nil { - extra := *extraCfg.CosmosExtra - for name, cfg := range extra.Instruments { - inst := registry.Get(name) - if inst == nil { - return fmt.Errorf("unknown instrument: %s", name) - } - fmt.Printf("Initializing %s instrumentation\n", name) - if err := inst.Start(cfg); err != nil { - return fmt.Errorf("failed to start %s instrumentation: %w", name, err) - } + // parse extensions config (features not yet supported by otelconf) + var supplemental struct { + Extensions *ExtensionOptions `yaml:"extensions"` + } + if err := yaml.Unmarshal(bz, &supplemental); err == nil && supplemental.Extensions != nil { + extra := *supplemental.Extensions + for name, cfg := range extra.Instruments { + inst := registry.Get(name) + if inst == nil { + return fmt.Errorf("unknown instrument: %s", name) } - - // TODO: this code should be removed once propagation is properly supported by otelconf. - if len(extra.Propagators) > 0 { - propagator := initPropagator(extra.Propagators) - otel.SetTextMapPropagator(propagator) + fmt.Printf("Initializing %s instrumentation\n", name) + if err := inst.Start(cfg); err != nil { + return fmt.Errorf("failed to start %s instrumentation: %w", name, err) } } + + // TODO: this code should be removed once propagation is properly supported by otelconf. + if len(extra.Propagators) > 0 { + propagator := initPropagator(extra.Propagators) + otel.SetTextMapPropagator(propagator) + } } otelSDK, err := otelconf.NewSDK(opts...) @@ -175,19 +173,16 @@ func setNoop() { logglobal.SetLoggerProvider(lognoop.NewLoggerProvider()) } -type extraConfig struct { - CosmosExtra *cosmosExtra `json:"cosmos_extra" yaml:"cosmos_extra" mapstructure:"cosmos_extra"` -} - -// cosmosExtra provides extensions to the OpenTelemetry declarative configuration. -// These options allow features not yet supported by otelconf, such as writing traces/metrics/logs to local -// files, enabling additional host/runtime instrumentation, and configuring custom propagators. +// ExtensionOptions provides configuration for OpenTelemetry features not yet +// supported by [otelconf], such as writing traces/metrics/logs to local files, +// enabling additional host/runtime instrumentation, and configuring custom +// propagators. // -// When present in otel.yaml under the `cosmos_extra` key, these fields +// When present in otel.yaml under the `extensions` key, these fields // augment/override portions of the OpenTelemetry SDK initialization. // // For an example configuration, see the README in this package. -type cosmosExtra struct { +type ExtensionOptions struct { // TraceFile is an optional path to a file where spans should be exported // using the stdouttrace exporter. If empty, no file-based trace export is // configured. diff --git a/telemetry/config_test.go b/telemetry/config_test.go index ebe3b186203f..f557992cad08 100644 --- a/telemetry/config_test.go +++ b/telemetry/config_test.go @@ -7,45 +7,41 @@ import ( "go.yaml.in/yaml/v3" ) -func TestCosmosExtraUnmarshal(t *testing.T) { +func TestExtensionOptionsUnmarshal(t *testing.T) { tests := []struct { name string yaml string - expected extraConfig + expected *ExtensionOptions }{ { name: "instruments with empty config", yaml: ` -cosmos_extra: +extensions: instruments: host: {} runtime: {} `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{ - Instruments: map[string]map[string]any{ - "host": {}, - "runtime": {}, - }, + expected: &ExtensionOptions{ + Instruments: map[string]map[string]any{ + "host": {}, + "runtime": {}, }, }, }, { name: "instruments with options", yaml: ` -cosmos_extra: +extensions: instruments: host: {} diskio: disable_virtual_device_filter: true `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{ - Instruments: map[string]map[string]any{ - "host": {}, - "diskio": { - "disable_virtual_device_filter": true, - }, + expected: &ExtensionOptions{ + Instruments: map[string]map[string]any{ + "host": {}, + "diskio": { + "disable_virtual_device_filter": true, }, }, }, @@ -53,26 +49,24 @@ cosmos_extra: { name: "instruments with propagators", yaml: ` -cosmos_extra: +extensions: instruments: host: {} propagators: - tracecontext - baggage `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{ - Instruments: map[string]map[string]any{ - "host": {}, - }, - Propagators: []string{"tracecontext", "baggage"}, + expected: &ExtensionOptions{ + Instruments: map[string]map[string]any{ + "host": {}, }, + Propagators: []string{"tracecontext", "baggage"}, }, }, { name: "full config", yaml: ` -cosmos_extra: +extensions: trace_file: /tmp/traces.json metrics_file: /tmp/metrics.json instruments: @@ -83,50 +77,42 @@ cosmos_extra: propagators: - tracecontext `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{ - TraceFile: "/tmp/traces.json", - MetricsFile: "/tmp/metrics.json", - Instruments: map[string]map[string]any{ - "host": {}, - "runtime": {}, - "diskio": { - "disable_virtual_device_filter": true, - }, + expected: &ExtensionOptions{ + TraceFile: "/tmp/traces.json", + MetricsFile: "/tmp/metrics.json", + Instruments: map[string]map[string]any{ + "host": {}, + "runtime": {}, + "diskio": { + "disable_virtual_device_filter": true, }, - Propagators: []string{"tracecontext"}, }, + Propagators: []string{"tracecontext"}, }, }, { - name: "empty cosmos_extra (null)", + name: "empty extensions (null)", yaml: ` -cosmos_extra: +extensions: `, - expected: extraConfig{ - CosmosExtra: nil, // YAML with just "cosmos_extra:" and no value results in nil - }, + expected: nil, }, { - name: "empty cosmos_extra (empty object)", + name: "empty extensions (empty object)", yaml: ` -cosmos_extra: {} +extensions: {} `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{}, - }, + expected: &ExtensionOptions{}, }, { - name: "no cosmos_extra", + name: "no extensions", yaml: ` some_other_key: value `, - expected: extraConfig{ - CosmosExtra: nil, - }, + expected: nil, }, { - name: "realistic otel.yaml with cosmos_extra", + name: "realistic otel.yaml with extensions", yaml: ` file_format: "1.0-rc.3" resource: @@ -149,7 +135,7 @@ meter_provider: host: 0.0.0.0 port: 9464 -cosmos_extra: +extensions: instruments: host: {} runtime: {} @@ -158,42 +144,42 @@ cosmos_extra: propagators: - tracecontext `, - expected: extraConfig{ - CosmosExtra: &cosmosExtra{ - Instruments: map[string]map[string]any{ - "host": {}, - "runtime": {}, - "diskio": { - "disable_virtual_device_filter": true, - }, + expected: &ExtensionOptions{ + Instruments: map[string]map[string]any{ + "host": {}, + "runtime": {}, + "diskio": { + "disable_virtual_device_filter": true, }, - Propagators: []string{"tracecontext"}, }, + Propagators: []string{"tracecontext"}, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - var cfg extraConfig + var cfg struct { + Extensions *ExtensionOptions `yaml:"extensions"` + } err := yaml.Unmarshal([]byte(tc.yaml), &cfg) require.NoError(t, err) - if tc.expected.CosmosExtra == nil { - require.Nil(t, cfg.CosmosExtra) + if tc.expected == nil { + require.Nil(t, cfg.Extensions) return } - require.NotNil(t, cfg.CosmosExtra) - require.Equal(t, tc.expected.CosmosExtra.TraceFile, cfg.CosmosExtra.TraceFile) - require.Equal(t, tc.expected.CosmosExtra.MetricsFile, cfg.CosmosExtra.MetricsFile) - require.Equal(t, tc.expected.CosmosExtra.Propagators, cfg.CosmosExtra.Propagators) + require.NotNil(t, cfg.Extensions) + require.Equal(t, tc.expected.TraceFile, cfg.Extensions.TraceFile) + require.Equal(t, tc.expected.MetricsFile, cfg.Extensions.MetricsFile) + require.Equal(t, tc.expected.Propagators, cfg.Extensions.Propagators) - require.Equal(t, len(tc.expected.CosmosExtra.Instruments), len(cfg.CosmosExtra.Instruments), + require.Equal(t, len(tc.expected.Instruments), len(cfg.Extensions.Instruments), "instruments count mismatch") - for name, expectedOpts := range tc.expected.CosmosExtra.Instruments { - actualOpts, ok := cfg.CosmosExtra.Instruments[name] + for name, expectedOpts := range tc.expected.Instruments { + actualOpts, ok := cfg.Extensions.Instruments[name] require.True(t, ok, "missing instrument: %s", name) require.Equal(t, len(expectedOpts), len(actualOpts), "options count mismatch for instrument %s", name)