Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion telemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 25 additions & 30 deletions telemetry/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ func InitializeOpenTelemetry(filePath string) error {
if openTelemetrySDK != nil {
return nil
}
var err error

var opts []otelconf.ConfigurationOption

Expand Down Expand Up @@ -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...)
Expand Down Expand Up @@ -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.
Expand Down
128 changes: 57 additions & 71 deletions telemetry/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,72 +7,66 @@ 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,
},
},
},
},
{
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:
Expand All @@ -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:
Expand All @@ -149,7 +135,7 @@ meter_provider:
host: 0.0.0.0
port: 9464

cosmos_extra:
extensions:
instruments:
host: {}
runtime: {}
Expand All @@ -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)
Expand Down
Loading