Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1505901
copy v0.3.0 and update import definition
codeboten Sep 25, 2025
06b2c86
update AttributeNameValueType to AttributeType
codeboten Sep 25, 2025
1304252
update Zipkin to ZipkinSpanExporter
codeboten Sep 25, 2025
591bbbf
rename LanguageSpecificInstrumentation -> ExperimentalLanguageSpecifi…
codeboten Sep 25, 2025
077e5f7
Prometheus -> ExperimentalPrometheusMetricExporter
codeboten Sep 25, 2025
62f1131
Console -> ConsoleExporter
codeboten Sep 25, 2025
9a071ad
update OTLP exporter config
codeboten Sep 25, 2025
3239854
add testdata
codeboten Sep 25, 2025
b6b18ea
update config
codeboten Sep 25, 2025
8b5c049
update signals
codeboten Sep 25, 2025
6584ab6
remove unnecessary tests
codeboten Sep 26, 2025
acf0b05
update resource parsing
codeboten Oct 9, 2025
abbbb9d
incremental improvements
codeboten Oct 15, 2025
e304ca2
add 1.0.0-rc1 test file
codeboten Oct 15, 2025
3c94ac1
set defaults for disabled/log_level
codeboten Oct 15, 2025
4092411
add json parsing/tests
codeboten Oct 15, 2025
1277ee1
refactor common logic
codeboten Oct 16, 2025
b0c943c
add env parsing test
codeboten Oct 16, 2025
6020e04
fix test
codeboten Oct 16, 2025
171ba24
fix license check
codeboten Oct 16, 2025
8eebdfd
add changelog
codeboten Oct 16, 2025
a04a4c5
more lint fixes
codeboten Oct 16, 2025
07178d1
more lint fixes
codeboten Oct 16, 2025
d113796
move common code
codeboten Oct 16, 2025
55434f7
add validation tests for BatchLogRecordProcessor
codeboten Oct 16, 2025
1aac8e7
add validation tests for BatchSpanProcessor
codeboten Oct 16, 2025
70054a6
add validation/tests for CardinalityLimits
codeboten Oct 16, 2025
05c96fd
add validation/tests for SpanLimits
codeboten Oct 16, 2025
2ab45fe
these have been moved
codeboten Oct 21, 2025
22f700c
remove dupe files, update test data to add detectors TODO
codeboten Oct 22, 2025
9a6a1f9
remove createTLSConfig, reuse refactored code
codeboten Oct 23, 2025
d547412
clean up
codeboten Oct 27, 2025
0cbb92c
test cleanup
codeboten Oct 28, 2025
bbbe0c6
remove dead code
codeboten Oct 28, 2025
c746207
lint
codeboten Oct 29, 2025
14afea5
clean up
codeboten Oct 30, 2025
1c48ed5
otelconf: add support for OTEL_EXPERIMENTAL_CONFIG_FILE
codeboten Oct 30, 2025
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 @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add unmarshaling and validation for `CardinalityLimits` and `SpanLimits` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8043)
- Add unmarshaling and validation for `BatchLogRecordProcessor`, `BatchSpanProcessor`, and `PeriodicMetricReader` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8049)
- Add unmarshaling and validation for `TextMapPropagator` to v1.0.0 model in `go.opentelemetry.io/contrib/otelconf`. (#8052)
- Updated `go.opentelemetry.io/contrib/otelconf` to include the [v1.0.0-rc2](https://github.com/open-telemetry/opentelemetry-configuration/releases/tag/v1.0.0-rc.2) release candidate of schema which includes backwards incompatible changes. (#8026)

### Changed

Expand Down
219 changes: 219 additions & 0 deletions otelconf/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package otelconf provides an OpenTelemetry declarative configuration SDK.
package otelconf // import "go.opentelemetry.io/contrib/otelconf"

import (
"context"
"errors"
"log"
"os"

"go.opentelemetry.io/otel"
apilog "go.opentelemetry.io/otel/log"
"go.opentelemetry.io/otel/log/global"
nooplog "go.opentelemetry.io/otel/log/noop"
"go.opentelemetry.io/otel/metric"
noopmetric "go.opentelemetry.io/otel/metric/noop"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
nooptrace "go.opentelemetry.io/otel/trace/noop"
yaml "go.yaml.in/yaml/v3"

"go.opentelemetry.io/contrib/otelconf/internal/provider"
)

// SDK is a struct that contains all the providers
// configured via the configuration model.
type SDK struct {
meterProvider metric.MeterProvider
tracerProvider trace.TracerProvider
loggerProvider apilog.LoggerProvider
shutdown shutdownFunc
}

// TracerProvider returns a configured trace.TracerProvider.
func (s *SDK) TracerProvider() trace.TracerProvider {
return s.tracerProvider
}

// MeterProvider returns a configured metric.MeterProvider.
func (s *SDK) MeterProvider() metric.MeterProvider {
return s.meterProvider
}

// LoggerProvider returns a configured apilog.LoggerProvider.
func (s *SDK) LoggerProvider() apilog.LoggerProvider {
return s.loggerProvider
}

// Shutdown calls shutdown on all configured providers.
func (s *SDK) Shutdown(ctx context.Context) error {
return s.shutdown(ctx)
}

var noopSDK = SDK{
loggerProvider: nooplog.LoggerProvider{},
meterProvider: noopmetric.MeterProvider{},
tracerProvider: nooptrace.TracerProvider{},
shutdown: func(context.Context) error { return nil },
}

var sdk *SDK

// init checks the local environment and uses the file set in the variable
// `OTEL_EXPERIMENTAL_CONFIG_FILE` to configure the SDK automatically.
func init() {
// look for the env variable
filename, ok := os.LookupEnv("OTEL_EXPERIMENTAL_CONFIG_FILE")
if !ok {
return
}
b, err := os.ReadFile(filename)
if err != nil {
log.Fatal(err)
}

// Parse a configuration file into an OpenTelemetryConfiguration model.
c, err := ParseYAML(b)
if err != nil {
log.Fatal(err)
}

// Create SDK components with the parsed configuration.
s, err := NewSDK(WithOpenTelemetryConfiguration(*c))
if err != nil {
log.Fatal(err)
}

// Set the global providers.
otel.SetTracerProvider(s.TracerProvider())
otel.SetMeterProvider(s.MeterProvider())
global.SetLoggerProvider(s.LoggerProvider())
sdk = &s
}

// Shutdown calls the shutdown function of the global SDK instantiated if

Check failure on line 99 in otelconf/config.go

View workflow job for this annotation

GitHub Actions / lint

Comment should end in a period (godot)
func Shutdown(ctx context.Context) {
if sdk == nil {
return
}
if err := sdk.Shutdown(ctx); err != nil {
log.Fatal(err)

Check failure on line 105 in otelconf/config.go

View workflow job for this annotation

GitHub Actions / lint

deep-exit: calls to log.Fatal only in main() or init() functions (revive)
}
}

// NewSDK creates SDK providers based on the configuration model.
func NewSDK(opts ...ConfigurationOption) (SDK, error) {
o := configOptions{
ctx: context.Background(),
}
for _, opt := range opts {
o = opt.apply(o)
}
if o.opentelemetryConfig.Disabled != nil && *o.opentelemetryConfig.Disabled {
return noopSDK, nil
}

r, err := newResource(o.opentelemetryConfig.Resource)
if err != nil {
return noopSDK, err
}

mp, mpShutdown, err := meterProvider(o, r)
if err != nil {
return noopSDK, err
}

tp, tpShutdown, err := tracerProvider(o, r)
if err != nil {
return noopSDK, err
}

lp, lpShutdown, err := loggerProvider(o, r)
if err != nil {
return noopSDK, err
}

return SDK{
meterProvider: mp,
tracerProvider: tp,
loggerProvider: lp,
shutdown: func(ctx context.Context) error {
return errors.Join(mpShutdown(ctx), tpShutdown(ctx), lpShutdown(ctx))
},
}, nil
}

// ConfigurationOption configures options for providers.
type ConfigurationOption interface {
apply(configOptions) configOptions
}

type configurationOptionFunc func(configOptions) configOptions

func (fn configurationOptionFunc) apply(cfg configOptions) configOptions {
return fn(cfg)
}

// WithContext sets the context.Context for the SDK.
func WithContext(ctx context.Context) ConfigurationOption {
return configurationOptionFunc(func(c configOptions) configOptions {
c.ctx = ctx
return c
})
}

// WithOpenTelemetryConfiguration sets the OpenTelemetryConfiguration used
// to produce the SDK.
func WithOpenTelemetryConfiguration(cfg OpenTelemetryConfiguration) ConfigurationOption {
return configurationOptionFunc(func(c configOptions) configOptions {
c.opentelemetryConfig = cfg
return c
})
}

// WithLoggerProviderOptions appends LoggerProviderOptions used for constructing
// the LoggerProvider. OpenTelemetryConfiguration takes precedence over these options.
func WithLoggerProviderOptions(opts ...sdklog.LoggerProviderOption) ConfigurationOption {
return configurationOptionFunc(func(c configOptions) configOptions {
c.loggerProviderOptions = append(c.loggerProviderOptions, opts...)
return c
})
}

// WithMeterProviderOptions appends metric.Options used for constructing the
// MeterProvider. OpenTelemetryConfiguration takes precedence over these options.
func WithMeterProviderOptions(opts ...sdkmetric.Option) ConfigurationOption {
return configurationOptionFunc(func(c configOptions) configOptions {
c.meterProviderOptions = append(c.meterProviderOptions, opts...)
return c
})
}

// WithTracerProviderOptions appends TracerProviderOptions used for constructing
// the TracerProvider. OpenTelemetryConfiguration takes precedence over these options.
func WithTracerProviderOptions(opts ...sdktrace.TracerProviderOption) ConfigurationOption {
return configurationOptionFunc(func(c configOptions) configOptions {
c.tracerProviderOptions = append(c.tracerProviderOptions, opts...)
return c
})
}

// ParseYAML parses a YAML configuration file into an OpenTelemetryConfiguration.
func ParseYAML(file []byte) (*OpenTelemetryConfiguration, error) {
file, err := provider.ReplaceEnvVars(file)
if err != nil {
return nil, err
}
var cfg OpenTelemetryConfiguration
err = yaml.Unmarshal(file, &cfg)
if err != nil {
return nil, err
}

return &cfg, nil
}
118 changes: 118 additions & 0 deletions otelconf/config_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ package otelconf // import "go.opentelemetry.io/contrib/otelconf"

import (
"context"
"encoding/json"
"errors"
"fmt"
"reflect"

"go.opentelemetry.io/otel/baggage"
sdklog "go.opentelemetry.io/otel/sdk/log"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
yaml "go.yaml.in/yaml/v3"
)

const (
Expand All @@ -22,6 +26,8 @@ type configOptions struct {
ctx context.Context
opentelemetryConfig OpenTelemetryConfiguration
loggerProviderOptions []sdklog.LoggerProviderOption
meterProviderOptions []sdkmetric.Option
tracerProviderOptions []sdktrace.TracerProviderOption
}

type shutdownFunc func(context.Context) error
Expand Down Expand Up @@ -123,6 +129,30 @@ func newErrInvalid(id string) error {
return &errInvalid{Identifier: id}
}

// unmarshalSamplerTypes handles always_on and always_off sampler unmarshaling.
func unmarshalSamplerTypes(raw map[string]any, plain *Sampler) {
// always_on can be nil, must check and set here
if _, ok := raw["always_on"]; ok {
plain.AlwaysOn = AlwaysOnSampler{}
}
// always_off can be nil, must check and set here
if _, ok := raw["always_off"]; ok {
plain.AlwaysOff = AlwaysOffSampler{}
}
}

// unmarshalMetricProducer handles opencensus metric producer unmarshaling.
func unmarshalMetricProducer(raw map[string]any, plain *MetricProducer) {
// opencensus can be nil, must check and set here
if v, ok := raw["opencensus"]; ok && v == nil {
delete(raw, "opencensus")
plain.Opencensus = OpenCensusMetricProducer{}
}
if len(raw) > 0 {
plain.AdditionalProperties = raw
}
}

// validatePeriodicMetricReader handles validation for PeriodicMetricReader.
func validatePeriodicMetricReader(plain *PeriodicMetricReader) error {
if plain.Timeout != nil && 0 > *plain.Timeout {
Expand Down Expand Up @@ -245,3 +275,91 @@ func createHeadersConfig(headers []NameStringValuePair, headersList *string) (ma
}
return result, nil
}

var enumValuesAttributeType = []any{
nil,
"string",
"bool",
"int",
"double",
"string_array",
"bool_array",
"int_array",
"double_array",
}

// MarshalUnmarshaler combines marshal and unmarshal operations.
type MarshalUnmarshaler interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
}

// jsonCodec implements MarshalUnmarshaler for JSON.
type jsonCodec struct{}

func (jsonCodec) Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}

func (jsonCodec) Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}

// yamlCodec implements MarshalUnmarshaler for YAML.
type yamlCodec struct{}

func (yamlCodec) Marshal(v any) ([]byte, error) {
return yaml.Marshal(v)
}

func (yamlCodec) Unmarshal(data []byte, v any) error {
return yaml.Unmarshal(data, v)
}

// setConfigDefaults sets default values for disabled and log_level.
func setConfigDefaults(raw map[string]any, plain *OpenTelemetryConfiguration, codec MarshalUnmarshaler) error {
// Configure if the SDK is disabled or not.
// If omitted or null, false is used.
plain.Disabled = ptr(false)
if v, ok := raw["disabled"]; ok && v != nil {
marshaled, err := codec.Marshal(v)
if err != nil {
return err
}
var disabled bool
if err := codec.Unmarshal(marshaled, &disabled); err != nil {
return err
}
plain.Disabled = &disabled
}

// Configure the log level of the internal logger used by the SDK.
// If omitted, info is used.
plain.LogLevel = ptr("info")
if v, ok := raw["log_level"]; ok && v != nil {
marshaled, err := codec.Marshal(v)
if err != nil {
return err
}
var logLevel string
if err := codec.Unmarshal(marshaled, &logLevel); err != nil {
return err
}
plain.LogLevel = &logLevel
}

return nil
}

// validateStringField validates a string field is present and correct type.
func validateStringField(raw map[string]any, fieldName string) (string, error) {
v, ok := raw[fieldName]
if !ok {
return "", fmt.Errorf("cannot unmarshal field %s in NameStringValuePair required", fieldName)
}
str, ok := v.(string)
if !ok {
return "", fmt.Errorf("cannot unmarshal field %s in NameStringValuePair must be string", fieldName)
}
return str, nil
}
Loading
Loading