feat(instrumentation): use new config factory (#6834)

### Summary

Use new config factory and remove redundant configuration possibilities from the upstream
This commit is contained in:
Vibhu Pandey 2025-01-17 14:54:33 +05:30 committed by GitHub
parent 42525b6067
commit 939ab5270e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 265 additions and 226 deletions

View File

@ -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{},
}
)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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(),
}
}

View File

@ -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))
}

View File

@ -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
}

137
pkg/instrumentation/sdk.go Normal file
View File

@ -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(),
}
}

View File

@ -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
}