diff --git a/pkg/config/config.go b/pkg/config/config.go index a1333a89da..768792258b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -12,9 +12,8 @@ import ( // This map contains the default values of all config structs var ( defaults = map[string]signozconfmap.Config{ - "instrumentation": &instrumentation.Config{}, - "web": &web.Config{}, - "cache": &cache.Config{}, + "web": &web.Config{}, + "cache": &cache.Config{}, } ) diff --git a/pkg/config/unmarshaler_test.go b/pkg/config/unmarshaler_test.go deleted file mode 100644 index 651510eba8..0000000000 --- a/pkg/config/unmarshaler_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package config - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/confmap" - "go.signoz.io/signoz/pkg/instrumentation" -) - -func TestUnmarshalForInstrumentation(t *testing.T) { - input := confmap.NewFromStringMap( - map[string]any{ - "instrumentation": map[string]any{ - "logs": map[string]bool{ - "enabled": true, - }, - }, - }, - ) - expected := &Config{ - Instrumentation: instrumentation.Config{ - Logs: instrumentation.LogsConfig{ - Enabled: true, - }, - }, - } - cfg, err := unmarshal(input) - require.NoError(t, err) - - assert.Equal(t, expected.Instrumentation, cfg.Instrumentation) -} diff --git a/pkg/instrumentation/config.go b/pkg/instrumentation/config.go index 20eb9b97bb..31919ebfde 100644 --- a/pkg/instrumentation/config.go +++ b/pkg/instrumentation/config.go @@ -2,13 +2,10 @@ package instrumentation import ( contribsdkconfig "go.opentelemetry.io/contrib/config" - "go.signoz.io/signoz/pkg/confmap" + "go.signoz.io/signoz/pkg/factory" "go.uber.org/zap/zapcore" ) -// Config satisfies the confmap.Config interface -var _ confmap.Config = (*Config)(nil) - // Config holds the configuration for all instrumentation components. type Config struct { Logs LogsConfig `mapstructure:"logs"` @@ -24,39 +21,69 @@ type Resource struct { // LogsConfig holds the configuration for the logging component. type LogsConfig struct { - Enabled bool `mapstructure:"enabled"` - Level zapcore.Level `mapstructure:"level"` - contribsdkconfig.LoggerProvider `mapstructure:",squash"` + Enabled bool `mapstructure:"enabled"` + Level zapcore.Level `mapstructure:"level"` + Processors LogsProcessors `mapstructure:"processors"` +} + +type LogsProcessors struct { + Batch contribsdkconfig.BatchLogRecordProcessor `mapstructure:"batch"` } // TracesConfig holds the configuration for the tracing component. type TracesConfig struct { - Enabled bool `mapstructure:"enabled"` - contribsdkconfig.TracerProvider `mapstructure:",squash"` + Enabled bool `mapstructure:"enabled"` + Processors TracesProcessors `mapstructure:"processors"` + Sampler contribsdkconfig.Sampler `mapstructure:"sampler"` +} + +type TracesProcessors struct { + Batch contribsdkconfig.BatchSpanProcessor `mapstructure:"batch"` } // MetricsConfig holds the configuration for the metrics component. type MetricsConfig struct { - Enabled bool `mapstructure:"enabled"` - contribsdkconfig.MeterProvider `mapstructure:",squash"` + Enabled bool `mapstructure:"enabled"` + Readers MetricsReaders `mapstructure:"readers"` } -func (c *Config) NewWithDefaults() confmap.Config { - return &Config{ +type MetricsReaders struct { + Pull contribsdkconfig.PullMetricReader `mapstructure:"pull"` +} + +func NewConfigFactory() factory.ConfigFactory { + return factory.NewConfigFactory(factory.MustNewName("instrumentation"), newConfig) +} + +func newConfig() factory.Config { + host := "0.0.0.0" + port := 9090 + + return Config{ Logs: LogsConfig{ Enabled: false, - Level: zapcore.InfoLevel, + Level: zapcore.DebugLevel, }, Traces: TracesConfig{ Enabled: false, }, Metrics: MetricsConfig{ - Enabled: false, + Enabled: true, + Readers: MetricsReaders{ + Pull: contribsdkconfig.PullMetricReader{ + Exporter: contribsdkconfig.MetricExporter{ + Prometheus: &contribsdkconfig.Prometheus{ + Host: &host, + Port: &port, + }, + }, + }, + }, }, } } -func (c *Config) Validate() error { +func (c Config) Validate() error { return nil } diff --git a/pkg/instrumentation/instrumentation.go b/pkg/instrumentation/instrumentation.go index 3f12fc9d63..37797e0822 100644 --- a/pkg/instrumentation/instrumentation.go +++ b/pkg/instrumentation/instrumentation.go @@ -1,85 +1,34 @@ package instrumentation import ( - "context" - "fmt" + "os" - contribsdkconfig "go.opentelemetry.io/contrib/config" + "go.opentelemetry.io/contrib/bridges/otelzap" sdklog "go.opentelemetry.io/otel/log" sdkmetric "go.opentelemetry.io/otel/metric" sdkresource "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" sdktrace "go.opentelemetry.io/otel/trace" - "go.signoz.io/signoz/pkg/version" + "go.signoz.io/signoz/pkg/factory" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) -// Instrumentation holds the core components for application instrumentation. -type Instrumentation struct { - LoggerProvider sdklog.LoggerProvider - Logger *zap.Logger - MeterProvider sdkmetric.MeterProvider - TracerProvider sdktrace.TracerProvider +// Instrumentation provides the core components for application instrumentation. +type Instrumentation interface { + // LoggerProvider returns the OpenTelemetry logger provider. + LoggerProvider() sdklog.LoggerProvider + // Logger returns the Zap logger. + Logger() *zap.Logger + // MeterProvider returns the OpenTelemetry meter provider. + MeterProvider() sdkmetric.MeterProvider + // TracerProvider returns the OpenTelemetry tracer provider. + TracerProvider() sdktrace.TracerProvider + // ToProviderSettings converts instrumentation to provider settings. + ToProviderSettings() factory.ProviderSettings } -// New creates a new Instrumentation instance with configured providers. -// It sets up logging, tracing, and metrics based on the provided configuration. -func New(ctx context.Context, build version.Build, cfg Config) (*Instrumentation, error) { - // Set default resource attributes if not provided - if cfg.Resource.Attributes == nil { - cfg.Resource.Attributes = map[string]any{ - string(semconv.ServiceNameKey): build.Name, - string(semconv.ServiceVersionKey): build.Version, - } - } - - // Create a new resource with default detectors. - // The upstream contrib repository is not taking detectors into account. - // We are, therefore, using some sensible defaults here. - resource, err := sdkresource.New( - ctx, - sdkresource.WithContainer(), - sdkresource.WithFromEnv(), - sdkresource.WithHost(), - ) - if err != nil { - return nil, err - } - - // Prepare the resource configuration by merging - // resource and attributes. - sch := semconv.SchemaURL - configResource := contribsdkconfig.Resource{ - Attributes: attributes(cfg.Resource.Attributes, resource), - Detectors: nil, - SchemaUrl: &sch, - } - - loggerProvider, err := newLoggerProvider(ctx, cfg, configResource) - if err != nil { - return nil, fmt.Errorf("cannot create logger provider: %w", err) - } - - tracerProvider, err := newTracerProvider(ctx, cfg, configResource) - if err != nil { - return nil, fmt.Errorf("cannot create tracer provider: %w", err) - } - - meterProvider, err := newMeterProvider(ctx, cfg, configResource) - if err != nil { - return nil, fmt.Errorf("cannot create meter provider: %w", err) - } - - return &Instrumentation{ - LoggerProvider: loggerProvider, - TracerProvider: tracerProvider, - MeterProvider: meterProvider, - Logger: newLogger(cfg, loggerProvider), - }, nil -} - -// attributes merges the input attributes with the resource attributes. -func attributes(input map[string]any, resource *sdkresource.Resource) map[string]any { +// Merges the input attributes with the resource attributes. +func mergeAttributes(input map[string]any, resource *sdkresource.Resource) map[string]any { output := make(map[string]any) for k, v := range input { @@ -93,3 +42,14 @@ func attributes(input map[string]any, resource *sdkresource.Resource) map[string return output } + +// newLogger creates a new Zap logger with the configured level and output. +// It combines a JSON encoder for stdout and an OpenTelemetry bridge. +func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger { + core := zapcore.NewTee( + zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), cfg.Logs.Level), + otelzap.NewCore("go.signoz.io/pkg/instrumentation", otelzap.WithLoggerProvider(provider)), + ) + + return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)) +} diff --git a/pkg/instrumentation/instrumentationtest/instrumentation.go b/pkg/instrumentation/instrumentationtest/instrumentation.go new file mode 100644 index 0000000000..3b2fe4864e --- /dev/null +++ b/pkg/instrumentation/instrumentationtest/instrumentation.go @@ -0,0 +1,54 @@ +package instrumentationtest + +import ( + sdklog "go.opentelemetry.io/otel/log" + nooplog "go.opentelemetry.io/otel/log/noop" + sdkmetric "go.opentelemetry.io/otel/metric" + noopmetric "go.opentelemetry.io/otel/metric/noop" + sdktrace "go.opentelemetry.io/otel/trace" + nooptrace "go.opentelemetry.io/otel/trace/noop" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/instrumentation" + "go.uber.org/zap" +) + +type noopInstrumentation struct { + logger *zap.Logger + loggerProvider sdklog.LoggerProvider + meterProvider sdkmetric.MeterProvider + tracerProvider sdktrace.TracerProvider +} + +func New() instrumentation.Instrumentation { + return &noopInstrumentation{ + logger: zap.NewNop(), + loggerProvider: nooplog.NewLoggerProvider(), + meterProvider: noopmetric.NewMeterProvider(), + tracerProvider: nooptrace.NewTracerProvider(), + } +} + +func (i *noopInstrumentation) LoggerProvider() sdklog.LoggerProvider { + return i.loggerProvider +} + +func (i *noopInstrumentation) Logger() *zap.Logger { + return i.logger +} + +func (i *noopInstrumentation) MeterProvider() sdkmetric.MeterProvider { + return i.meterProvider +} + +func (i *noopInstrumentation) TracerProvider() sdktrace.TracerProvider { + return i.tracerProvider +} + +func (i *noopInstrumentation) ToProviderSettings() factory.ProviderSettings { + return factory.ProviderSettings{ + LoggerProvider: i.LoggerProvider(), + ZapLogger: i.Logger(), + MeterProvider: i.MeterProvider(), + TracerProvider: i.TracerProvider(), + } +} diff --git a/pkg/instrumentation/logger.go b/pkg/instrumentation/logger.go deleted file mode 100644 index 255323bc1a..0000000000 --- a/pkg/instrumentation/logger.go +++ /dev/null @@ -1,45 +0,0 @@ -package instrumentation - -import ( - "context" - "os" - - "go.opentelemetry.io/contrib/bridges/otelzap" - contribsdkconfig "go.opentelemetry.io/contrib/config" - sdklog "go.opentelemetry.io/otel/log" - nooplog "go.opentelemetry.io/otel/log/noop" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" -) - -// newLoggerProvider creates a new logger provider based on the configuration. -// If logging is disabled, it returns a no-op logger provider. -func newLoggerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdklog.LoggerProvider, error) { - if !cfg.Logs.Enabled { - return nooplog.NewLoggerProvider(), nil - } - - sdk, err := contribsdkconfig.NewSDK( - contribsdkconfig.WithContext(ctx), - contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{ - LoggerProvider: &cfg.Logs.LoggerProvider, - Resource: &cfgResource, - }), - ) - if err != nil { - return nil, err - } - - return sdk.LoggerProvider(), nil -} - -// newLogger creates a new Zap logger with the configured level and output. -// It combines a JSON encoder for stdout and an OpenTelemetry bridge. -func newLogger(cfg Config, provider sdklog.LoggerProvider) *zap.Logger { - core := zapcore.NewTee( - zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), zapcore.AddSync(os.Stdout), cfg.Logs.Level), - otelzap.NewCore("go.signoz.io/pkg/instrumentation", otelzap.WithLoggerProvider(provider)), - ) - - return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.ErrorLevel)) -} diff --git a/pkg/instrumentation/meter.go b/pkg/instrumentation/meter.go deleted file mode 100644 index f1433ee29f..0000000000 --- a/pkg/instrumentation/meter.go +++ /dev/null @@ -1,30 +0,0 @@ -package instrumentation - -import ( - "context" - - contribsdkconfig "go.opentelemetry.io/contrib/config" - sdkmetric "go.opentelemetry.io/otel/metric" - noopmetric "go.opentelemetry.io/otel/metric/noop" -) - -// newMeterProvider creates a new meter provider based on the configuration. -// If metrics are disabled, it returns a no-op meter provider. -func newMeterProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdkmetric.MeterProvider, error) { - if !cfg.Metrics.Enabled { - return noopmetric.NewMeterProvider(), nil - } - - sdk, err := contribsdkconfig.NewSDK( - contribsdkconfig.WithContext(ctx), - contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{ - MeterProvider: &cfg.Metrics.MeterProvider, - Resource: &cfgResource, - }), - ) - if err != nil { - return nil, err - } - - return sdk.MeterProvider(), nil -} diff --git a/pkg/instrumentation/sdk.go b/pkg/instrumentation/sdk.go new file mode 100644 index 0000000000..7eb08daaf1 --- /dev/null +++ b/pkg/instrumentation/sdk.go @@ -0,0 +1,137 @@ +package instrumentation + +import ( + "context" + + contribsdkconfig "go.opentelemetry.io/contrib/config" + sdklog "go.opentelemetry.io/otel/log" + sdkmetric "go.opentelemetry.io/otel/metric" + sdkresource "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + sdktrace "go.opentelemetry.io/otel/trace" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/version" + "go.uber.org/zap" +) + +var _ factory.Service = (*SDK)(nil) +var _ Instrumentation = (*SDK)(nil) + +// SDK holds the core components for application instrumentation. +type SDK struct { + sdk contribsdkconfig.SDK + logger *zap.Logger +} + +// New creates a new Instrumentation instance with configured providers. +// It sets up logging, tracing, and metrics based on the provided configuration. +func New(ctx context.Context, build version.Build, cfg Config) (*SDK, error) { + // Set default resource attributes if not provided + if cfg.Resource.Attributes == nil { + cfg.Resource.Attributes = map[string]any{ + string(semconv.ServiceNameKey): build.Name, + string(semconv.ServiceVersionKey): build.Version, + } + } + + // Create a new resource with default detectors. + // The upstream contrib repository is not taking detectors into account. + // We are, therefore, using some sensible defaults here. + resource, err := sdkresource.New( + ctx, + sdkresource.WithContainer(), + sdkresource.WithFromEnv(), + sdkresource.WithHost(), + ) + if err != nil { + return nil, err + } + + // Prepare the resource configuration by merging + // resource and attributes. + sch := semconv.SchemaURL + configResource := contribsdkconfig.Resource{ + Attributes: mergeAttributes(cfg.Resource.Attributes, resource), + Detectors: nil, + SchemaUrl: &sch, + } + + var loggerProvider *contribsdkconfig.LoggerProvider + if cfg.Logs.Enabled { + loggerProvider = &contribsdkconfig.LoggerProvider{ + Processors: []contribsdkconfig.LogRecordProcessor{ + {Batch: &cfg.Logs.Processors.Batch}, + }, + } + } + + var tracerProvider *contribsdkconfig.TracerProvider + if cfg.Traces.Enabled { + tracerProvider = &contribsdkconfig.TracerProvider{ + Processors: []contribsdkconfig.SpanProcessor{ + {Batch: &cfg.Traces.Processors.Batch}, + }, + Sampler: &cfg.Traces.Sampler, + } + } + + var meterProvider *contribsdkconfig.MeterProvider + if cfg.Metrics.Enabled { + meterProvider = &contribsdkconfig.MeterProvider{ + Readers: []contribsdkconfig.MetricReader{ + {Pull: &cfg.Metrics.Readers.Pull}, + }, + } + } + + sdk, err := contribsdkconfig.NewSDK( + contribsdkconfig.WithContext(ctx), + contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{ + LoggerProvider: loggerProvider, + TracerProvider: tracerProvider, + MeterProvider: meterProvider, + Resource: &configResource, + }), + ) + if err != nil { + return nil, err + } + + return &SDK{ + sdk: sdk, + logger: newLogger(cfg, sdk.LoggerProvider()), + }, nil +} + +func (i *SDK) Start(ctx context.Context) error { + return nil +} + +func (i *SDK) Stop(ctx context.Context) error { + return i.sdk.Shutdown(ctx) +} + +func (i *SDK) LoggerProvider() sdklog.LoggerProvider { + return i.sdk.LoggerProvider() +} + +func (i *SDK) Logger() *zap.Logger { + return i.logger +} + +func (i *SDK) MeterProvider() sdkmetric.MeterProvider { + return i.sdk.MeterProvider() +} + +func (i *SDK) TracerProvider() sdktrace.TracerProvider { + return i.sdk.TracerProvider() +} + +func (i *SDK) ToProviderSettings() factory.ProviderSettings { + return factory.ProviderSettings{ + LoggerProvider: i.LoggerProvider(), + ZapLogger: i.Logger(), + MeterProvider: i.MeterProvider(), + TracerProvider: i.TracerProvider(), + } +} diff --git a/pkg/instrumentation/tracer.go b/pkg/instrumentation/tracer.go deleted file mode 100644 index a2231217dc..0000000000 --- a/pkg/instrumentation/tracer.go +++ /dev/null @@ -1,30 +0,0 @@ -package instrumentation - -import ( - "context" - - contribsdkconfig "go.opentelemetry.io/contrib/config" - sdktrace "go.opentelemetry.io/otel/trace" - nooptrace "go.opentelemetry.io/otel/trace/noop" -) - -// newTracerProvider creates a new tracer provider based on the configuration. -// If tracing is disabled, it returns a no-op tracer provider. -func newTracerProvider(ctx context.Context, cfg Config, cfgResource contribsdkconfig.Resource) (sdktrace.TracerProvider, error) { - if !cfg.Traces.Enabled { - return nooptrace.NewTracerProvider(), nil - } - - sdk, err := contribsdkconfig.NewSDK( - contribsdkconfig.WithContext(ctx), - contribsdkconfig.WithOpenTelemetryConfiguration(contribsdkconfig.OpenTelemetryConfiguration{ - TracerProvider: &cfg.Traces.TracerProvider, - Resource: &cfgResource, - }), - ) - if err != nil { - return nil, err - } - - return sdk.TracerProvider(), nil -}