Skip to content

Commit 01cb15b

Browse files
mx-psisteves-canva
authored andcommitted
[service/telemetry] Switch to a factory pattern (open-telemetry#10001)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Switches `service/telemetry` to a factory pattern. To avoid adding a lot of public API in one go: 1. the actual factory builder is in an internal package 2. I have not added the `CreateMeterProvider` method yet There are two goals with this: one is to make progress on open-telemetry#4970, the other is to allow initializing telemetry sooner: <!-- Issue number if applicable --> #### Link to tracking issue Updates open-telemetry#4970. <!--Describe what testing was performed and which tests were added.--> #### Testing Updates existing tests to use `NewFactory`
1 parent 6c7b34c commit 01cb15b

9 files changed

Lines changed: 382 additions & 159 deletions

File tree

.chloggen/mx-psi_tel-factory.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: deprecation
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: service/telemetry
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Deprecate telemetry.New in favor of telemetry.NewFactory
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [4970]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# Optional: The change log or logs in which this entry should be included.
21+
# e.g. '[user]' or '[user, api]'
22+
# Include 'user' if the change is relevant to end users.
23+
# Include 'api' if there is a change to a library API.
24+
# Default: '[user]'
25+
change_logs: [api]

otelcol/unmarshaler.go

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@
44
package otelcol // import "go.opentelemetry.io/collector/otelcol"
55

66
import (
7-
"time"
8-
9-
"go.uber.org/zap/zapcore"
10-
11-
"go.opentelemetry.io/collector/config/configtelemetry"
127
"go.opentelemetry.io/collector/confmap"
138
"go.opentelemetry.io/collector/connector"
149
"go.opentelemetry.io/collector/exporter"
@@ -32,6 +27,10 @@ type configSettings struct {
3227
// unmarshal the configSettings from a confmap.Conf.
3328
// After the config is unmarshalled, `Validate()` must be called to validate.
3429
func unmarshal(v *confmap.Conf, factories Factories) (*configSettings, error) {
30+
31+
telFactory := telemetry.NewFactory()
32+
defaultTelConfig := *telFactory.CreateDefaultConfig().(*telemetry.Config)
33+
3534
// Unmarshal top level sections and validate.
3635
cfg := &configSettings{
3736
Receivers: configunmarshaler.NewConfigs(factories.Receivers),
@@ -41,28 +40,7 @@ func unmarshal(v *confmap.Conf, factories Factories) (*configSettings, error) {
4140
Extensions: configunmarshaler.NewConfigs(factories.Extensions),
4241
// TODO: Add a component.ServiceFactory to allow this to be defined by the Service.
4342
Service: service.Config{
44-
Telemetry: telemetry.Config{
45-
Logs: telemetry.LogsConfig{
46-
Level: zapcore.InfoLevel,
47-
Development: false,
48-
Encoding: "console",
49-
Sampling: &telemetry.LogsSamplingConfig{
50-
Enabled: true,
51-
Tick: 10 * time.Second,
52-
Initial: 10,
53-
Thereafter: 100,
54-
},
55-
OutputPaths: []string{"stderr"},
56-
ErrorOutputPaths: []string{"stderr"},
57-
DisableCaller: false,
58-
DisableStacktrace: false,
59-
InitialFields: map[string]any(nil),
60-
},
61-
Metrics: telemetry.MetricsConfig{
62-
Level: configtelemetry.LevelNormal,
63-
Address: ":8888",
64-
},
65-
},
43+
Telemetry: defaultTelConfig,
6644
},
6745
}
6846

service/service.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,27 @@ func New(ctx context.Context, set Settings, cfg Config) (*Service, error) {
8989
},
9090
collectorConf: set.CollectorConf,
9191
}
92-
tel, err := telemetry.New(ctx, telemetry.Settings{BuildInfo: set.BuildInfo, ZapOptions: set.LoggingOptions}, cfg.Telemetry)
93-
if err != nil {
94-
return nil, fmt.Errorf("failed to get logger: %w", err)
95-
}
9692

9793
// Fetch data for internal telemetry like instance id and sdk version to provide for internal telemetry.
9894
res := resource.New(set.BuildInfo, cfg.Telemetry.Resource)
9995
pcommonRes := pdataFromSdk(res)
10096

101-
logger := tel.Logger()
97+
telFactory := telemetry.NewFactory()
98+
telset := telemetry.Settings{
99+
BuildInfo: set.BuildInfo,
100+
ZapOptions: set.LoggingOptions,
101+
}
102+
103+
logger, err := telFactory.CreateLogger(ctx, telset, &cfg.Telemetry)
104+
if err != nil {
105+
return nil, fmt.Errorf("failed to create logger: %w", err)
106+
}
107+
108+
tracerProvider, err := telFactory.CreateTracerProvider(ctx, telset, &cfg.Telemetry)
109+
if err != nil {
110+
return nil, fmt.Errorf("failed to create tracer provider: %w", err)
111+
}
112+
102113
logger.Info("Setting up own telemetry...")
103114
mp, err := newMeterProvider(
104115
meterProviderSettings{
@@ -116,7 +127,7 @@ func New(ctx context.Context, set Settings, cfg Config) (*Service, error) {
116127
srv.telemetrySettings = servicetelemetry.TelemetrySettings{
117128
Logger: logger,
118129
MeterProvider: mp,
119-
TracerProvider: tel.TracerProvider(),
130+
TracerProvider: tracerProvider,
120131
MetricsLevel: cfg.Telemetry.Metrics.Level,
121132
// Construct telemetry attributes from build info and config's resource attributes.
122133
Resource: pcommonRes,

service/telemetry/factory.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package telemetry // import "go.opentelemetry.io/collector/service/telemetry"
5+
6+
import (
7+
"context"
8+
"time"
9+
10+
"go.opentelemetry.io/otel/trace"
11+
"go.uber.org/zap"
12+
"go.uber.org/zap/zapcore"
13+
14+
"go.opentelemetry.io/collector/component"
15+
"go.opentelemetry.io/collector/config/configtelemetry"
16+
"go.opentelemetry.io/collector/service/telemetry/internal"
17+
)
18+
19+
func createDefaultConfig() component.Config {
20+
return &Config{
21+
Logs: LogsConfig{
22+
Level: zapcore.InfoLevel,
23+
Development: false,
24+
Encoding: "console",
25+
Sampling: &LogsSamplingConfig{
26+
Enabled: true,
27+
Tick: 10 * time.Second,
28+
Initial: 10,
29+
Thereafter: 100,
30+
},
31+
OutputPaths: []string{"stderr"},
32+
ErrorOutputPaths: []string{"stderr"},
33+
DisableCaller: false,
34+
DisableStacktrace: false,
35+
InitialFields: map[string]any(nil),
36+
},
37+
Metrics: MetricsConfig{
38+
Level: configtelemetry.LevelNormal,
39+
Address: ":8888",
40+
},
41+
}
42+
}
43+
44+
// Factory is a telemetry factory.
45+
type Factory = internal.Factory
46+
47+
// NewFactory creates a new Factory.
48+
func NewFactory() Factory {
49+
return internal.NewFactory(createDefaultConfig,
50+
internal.WithLogger(func(_ context.Context, set Settings, cfg component.Config) (*zap.Logger, error) {
51+
c := *cfg.(*Config)
52+
return newLogger(c.Logs, set.ZapOptions)
53+
}),
54+
internal.WithTracerProvider(func(ctx context.Context, _ Settings, cfg component.Config) (trace.TracerProvider, error) {
55+
c := *cfg.(*Config)
56+
return newTracerProvider(ctx, c)
57+
}),
58+
)
59+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package internal // import "go.opentelemetry.io/collector/service/telemetry/internal"
5+
6+
import (
7+
"context"
8+
9+
"go.opentelemetry.io/otel/trace"
10+
tracenoop "go.opentelemetry.io/otel/trace/noop"
11+
"go.uber.org/zap"
12+
13+
"go.opentelemetry.io/collector/component"
14+
)
15+
16+
// CreateSettings holds configuration for building Telemetry.
17+
type CreateSettings struct {
18+
BuildInfo component.BuildInfo
19+
AsyncErrorChannel chan error
20+
ZapOptions []zap.Option
21+
}
22+
23+
// Factory is factory interface for telemetry.
24+
// This interface cannot be directly implemented. Implementations must
25+
// use the NewFactory to implement it.
26+
type Factory interface {
27+
// CreateDefaultConfig creates the default configuration for the telemetry.
28+
// TODO: Should we just inherit from component.Factory?
29+
CreateDefaultConfig() component.Config
30+
31+
// CreateLogger creates a logger.
32+
CreateLogger(ctx context.Context, set CreateSettings, cfg component.Config) (*zap.Logger, error)
33+
34+
// CreateTracerProvider creates a TracerProvider.
35+
CreateTracerProvider(ctx context.Context, set CreateSettings, cfg component.Config) (trace.TracerProvider, error)
36+
37+
// TODO: Add CreateMeterProvider.
38+
39+
// unexportedFactoryFunc is used to prevent external implementations of Factory.
40+
unexportedFactoryFunc()
41+
}
42+
43+
// FactoryOption apply changes to Factory.
44+
type FactoryOption interface {
45+
// applyTelemetryFactoryOption applies the option.
46+
applyTelemetryFactoryOption(o *factory)
47+
}
48+
49+
var _ FactoryOption = (*factoryOptionFunc)(nil)
50+
51+
// factoryOptionFunc is an FactoryOption created through a function.
52+
type factoryOptionFunc func(*factory)
53+
54+
func (f factoryOptionFunc) applyTelemetryFactoryOption(o *factory) {
55+
f(o)
56+
}
57+
58+
var _ Factory = (*factory)(nil)
59+
60+
// factory is the implementation of Factory.
61+
type factory struct {
62+
createDefaultConfig component.CreateDefaultConfigFunc
63+
CreateLoggerFunc
64+
CreateTracerProviderFunc
65+
}
66+
67+
func (f *factory) CreateDefaultConfig() component.Config {
68+
return f.createDefaultConfig()
69+
}
70+
71+
// CreateLoggerFunc is the equivalent of Factory.CreateLogger.
72+
type CreateLoggerFunc func(context.Context, CreateSettings, component.Config) (*zap.Logger, error)
73+
74+
// WithLogger overrides the default no-op logger.
75+
func WithLogger(createLogger CreateLoggerFunc) FactoryOption {
76+
return factoryOptionFunc(func(o *factory) {
77+
o.CreateLoggerFunc = createLogger
78+
})
79+
}
80+
81+
func (f *factory) CreateLogger(ctx context.Context, set CreateSettings, cfg component.Config) (*zap.Logger, error) {
82+
if f.CreateLoggerFunc == nil {
83+
return zap.NewNop(), nil
84+
}
85+
return f.CreateLoggerFunc(ctx, set, cfg)
86+
}
87+
88+
// CreateTracerProviderFunc is the equivalent of Factory.CreateTracerProvider.
89+
type CreateTracerProviderFunc func(context.Context, CreateSettings, component.Config) (trace.TracerProvider, error)
90+
91+
// WithTracerProvider overrides the default no-op tracer provider.
92+
func WithTracerProvider(createTracerProvider CreateTracerProviderFunc) FactoryOption {
93+
return factoryOptionFunc(func(o *factory) {
94+
o.CreateTracerProviderFunc = createTracerProvider
95+
})
96+
}
97+
98+
func (f *factory) CreateTracerProvider(ctx context.Context, set CreateSettings, cfg component.Config) (trace.TracerProvider, error) {
99+
if f.CreateTracerProviderFunc == nil {
100+
return tracenoop.NewTracerProvider(), nil
101+
}
102+
return f.CreateTracerProviderFunc(ctx, set, cfg)
103+
}
104+
105+
func (f *factory) unexportedFactoryFunc() {}
106+
107+
// NewFactory returns a new Factory.
108+
func NewFactory(createDefaultConfig component.CreateDefaultConfigFunc, options ...FactoryOption) Factory {
109+
f := &factory{
110+
createDefaultConfig: createDefaultConfig,
111+
}
112+
for _, op := range options {
113+
op.applyTelemetryFactoryOption(f)
114+
}
115+
return f
116+
}

service/telemetry/logger.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package telemetry // import "go.opentelemetry.io/collector/service/telemetry"
5+
6+
import (
7+
"go.uber.org/zap"
8+
"go.uber.org/zap/zapcore"
9+
)
10+
11+
func newLogger(cfg LogsConfig, options []zap.Option) (*zap.Logger, error) {
12+
// Copied from NewProductionConfig.
13+
zapCfg := &zap.Config{
14+
Level: zap.NewAtomicLevelAt(cfg.Level),
15+
Development: cfg.Development,
16+
Encoding: cfg.Encoding,
17+
EncoderConfig: zap.NewProductionEncoderConfig(),
18+
OutputPaths: cfg.OutputPaths,
19+
ErrorOutputPaths: cfg.ErrorOutputPaths,
20+
DisableCaller: cfg.DisableCaller,
21+
DisableStacktrace: cfg.DisableStacktrace,
22+
InitialFields: cfg.InitialFields,
23+
}
24+
25+
if zapCfg.Encoding == "console" {
26+
// Human-readable timestamps for console format of logs.
27+
zapCfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
28+
}
29+
30+
logger, err := zapCfg.Build(options...)
31+
if err != nil {
32+
return nil, err
33+
}
34+
if cfg.Sampling != nil && cfg.Sampling.Enabled {
35+
logger = newSampledLogger(logger, cfg.Sampling)
36+
}
37+
38+
return logger, nil
39+
}
40+
41+
func newSampledLogger(logger *zap.Logger, sc *LogsSamplingConfig) *zap.Logger {
42+
// Create a logger that samples every Nth message after the first M messages every S seconds
43+
// where N = sc.Thereafter, M = sc.Initial, S = sc.Tick.
44+
opts := zap.WrapCore(func(core zapcore.Core) zapcore.Core {
45+
return zapcore.NewSamplerWithOptions(
46+
core,
47+
sc.Tick,
48+
sc.Initial,
49+
sc.Thereafter,
50+
)
51+
})
52+
return logger.WithOptions(opts)
53+
}

0 commit comments

Comments
 (0)