Skip to content

Commit 7cd1579

Browse files
authored
Move exporter builder into internal service (#10783)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description This moves the exporter builder out of the `exporter` package, and into `service/internal/builders`. There's no real reason for this struct to be public (folks shouldn't call it), and making it private will allow us to add profiling support to it. <!-- Issue number if applicable --> #### Link to tracking issue #10375 (review)
1 parent 549ee72 commit 7cd1579

File tree

13 files changed

+361
-18
lines changed

13 files changed

+361
-18
lines changed
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: exporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Deprecate exporter.Builder, and move it into an internal package of the service module
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [10783]
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]

exporter/builder.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ import (
1313
)
1414

1515
// Builder exporter is a helper struct that given a set of Configs and Factories helps with creating exporters.
16+
//
17+
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
18+
// and will be removed soon.
1619
type Builder struct {
1720
cfgs map[component.ID]component.Config
1821
factories map[component.Type]Factory
1922
}
2023

2124
// NewBuilder creates a new exporter.Builder to help with creating components form a set of configs and factories.
25+
//
26+
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
27+
// and will be removed soon.
2228
func NewBuilder(cfgs map[component.ID]component.Config, factories map[component.Type]Factory) *Builder {
2329
return &Builder{cfgs: cfgs, factories: factories}
2430
}

exporter/exportertest/nop_exporter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type nopExporter struct {
6262
}
6363

6464
// NewNopBuilder returns an exporter.Builder that constructs nop receivers.
65+
//
66+
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
67+
// and will be removed soon.
6568
func NewNopBuilder() *exporter.Builder {
6669
nopFactory := NewNopFactory()
6770
return exporter.NewBuilder(

internal/e2e/status_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"go.opentelemetry.io/collector/connector"
2121
"go.opentelemetry.io/collector/connector/connectortest"
2222
"go.opentelemetry.io/collector/consumer"
23+
"go.opentelemetry.io/collector/exporter"
2324
"go.opentelemetry.io/collector/exporter/exportertest"
2425
"go.opentelemetry.io/collector/extension"
2526
"go.opentelemetry.io/collector/internal/sharedcomponent"
@@ -35,6 +36,7 @@ var nopType = component.MustNewType("nop")
3536

3637
func Test_ComponentStatusReporting_SharedInstance(t *testing.T) {
3738
eventsReceived := make(map[*componentstatus.InstanceID][]*componentstatus.Event)
39+
exporterFactory := exportertest.NewNopFactory()
3840
connectorFactory := connectortest.NewNopFactory()
3941
// Use a different ID than receivertest and exportertest to avoid ambiguous
4042
// configuration scenarios. Ambiguous IDs are detected in the 'otelcol' package,
@@ -51,7 +53,12 @@ func Test_ComponentStatusReporting_SharedInstance(t *testing.T) {
5153
component.MustNewType("test"): newReceiverFactory(),
5254
},
5355
Processors: processortest.NewNopBuilder(),
54-
Exporters: exportertest.NewNopBuilder(),
56+
ExportersConfigs: map[component.ID]component.Config{
57+
component.NewID(nopType): exporterFactory.CreateDefaultConfig(),
58+
},
59+
ExportersFactories: map[component.Type]exporter.Factory{
60+
nopType: exporterFactory,
61+
},
5562
ConnectorsConfigs: map[component.ID]component.Config{
5663
connID: connectorFactory.CreateDefaultConfig(),
5764
},

otelcol/collector.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121

2222
"go.opentelemetry.io/collector/component"
2323
"go.opentelemetry.io/collector/confmap"
24-
"go.opentelemetry.io/collector/exporter"
2524
"go.opentelemetry.io/collector/extension"
2625
"go.opentelemetry.io/collector/otelcol/internal/grpclog"
2726
"go.opentelemetry.io/collector/processor"
@@ -189,7 +188,8 @@ func (col *Collector) setupConfigurationComponents(ctx context.Context) error {
189188
ReceiversConfigs: cfg.Receivers,
190189
ReceiversFactories: factories.Receivers,
191190
Processors: processor.NewBuilder(cfg.Processors, factories.Processors),
192-
Exporters: exporter.NewBuilder(cfg.Exporters, factories.Exporters),
191+
ExportersConfigs: cfg.Exporters,
192+
ExportersFactories: factories.Exporters,
193193
ConnectorsConfigs: cfg.Connectors,
194194
ConnectorsFactories: factories.Connectors,
195195
ExtensionsConfigs: cfg.Extensions,

service/internal/builders/exporter.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package builders // import "go.opentelemetry.io/collector/service/internal/builders"
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"go.opentelemetry.io/collector/component"
11+
"go.opentelemetry.io/collector/exporter"
12+
"go.opentelemetry.io/collector/exporter/exportertest"
13+
)
14+
15+
// Exporter is an interface that allows using implementations of the builder
16+
// from different packages.
17+
type Exporter interface {
18+
CreateTraces(context.Context, exporter.Settings) (exporter.Traces, error)
19+
CreateMetrics(context.Context, exporter.Settings) (exporter.Metrics, error)
20+
CreateLogs(context.Context, exporter.Settings) (exporter.Logs, error)
21+
Factory(component.Type) component.Factory
22+
}
23+
24+
// ExporterBuilder is a helper struct that given a set of Configs and Factories helps with creating exporters.
25+
type ExporterBuilder struct {
26+
cfgs map[component.ID]component.Config
27+
factories map[component.Type]exporter.Factory
28+
}
29+
30+
// NewExporter creates a new ExporterBuilder to help with creating components form a set of configs and factories.
31+
func NewExporter(cfgs map[component.ID]component.Config, factories map[component.Type]exporter.Factory) *ExporterBuilder {
32+
return &ExporterBuilder{cfgs: cfgs, factories: factories}
33+
}
34+
35+
// CreateTraces creates a Traces exporter based on the settings and config.
36+
func (b *ExporterBuilder) CreateTraces(ctx context.Context, set exporter.Settings) (exporter.Traces, error) {
37+
cfg, existsCfg := b.cfgs[set.ID]
38+
if !existsCfg {
39+
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
40+
}
41+
42+
f, existsFactory := b.factories[set.ID.Type()]
43+
if !existsFactory {
44+
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
45+
}
46+
47+
logStabilityLevel(set.Logger, f.TracesExporterStability())
48+
return f.CreateTracesExporter(ctx, set, cfg)
49+
}
50+
51+
// CreateMetrics creates a Metrics exporter based on the settings and config.
52+
func (b *ExporterBuilder) CreateMetrics(ctx context.Context, set exporter.Settings) (exporter.Metrics, error) {
53+
cfg, existsCfg := b.cfgs[set.ID]
54+
if !existsCfg {
55+
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
56+
}
57+
58+
f, existsFactory := b.factories[set.ID.Type()]
59+
if !existsFactory {
60+
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
61+
}
62+
63+
logStabilityLevel(set.Logger, f.MetricsExporterStability())
64+
return f.CreateMetricsExporter(ctx, set, cfg)
65+
}
66+
67+
// CreateLogs creates a Logs exporter based on the settings and config.
68+
func (b *ExporterBuilder) CreateLogs(ctx context.Context, set exporter.Settings) (exporter.Logs, error) {
69+
cfg, existsCfg := b.cfgs[set.ID]
70+
if !existsCfg {
71+
return nil, fmt.Errorf("exporter %q is not configured", set.ID)
72+
}
73+
74+
f, existsFactory := b.factories[set.ID.Type()]
75+
if !existsFactory {
76+
return nil, fmt.Errorf("exporter factory not available for: %q", set.ID)
77+
}
78+
79+
logStabilityLevel(set.Logger, f.LogsExporterStability())
80+
return f.CreateLogsExporter(ctx, set, cfg)
81+
}
82+
83+
func (b *ExporterBuilder) Factory(componentType component.Type) component.Factory {
84+
return b.factories[componentType]
85+
}
86+
87+
// NewNopExporterConfigsAndFactories returns a configuration and factories that allows building a new nop exporter.
88+
func NewNopExporterConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]exporter.Factory) {
89+
nopFactory := exportertest.NewNopFactory()
90+
configs := map[component.ID]component.Config{
91+
component.NewID(nopType): nopFactory.CreateDefaultConfig(),
92+
}
93+
factories := map[component.Type]exporter.Factory{
94+
nopType: nopFactory,
95+
}
96+
97+
return configs, factories
98+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package builders
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"go.opentelemetry.io/collector/component"
14+
"go.opentelemetry.io/collector/component/componenttest"
15+
"go.opentelemetry.io/collector/consumer/consumertest"
16+
"go.opentelemetry.io/collector/exporter"
17+
"go.opentelemetry.io/collector/exporter/exportertest"
18+
)
19+
20+
func TestExporterBuilder(t *testing.T) {
21+
defaultCfg := struct{}{}
22+
factories, err := exporter.MakeFactoryMap([]exporter.Factory{
23+
exporter.NewFactory(component.MustNewType("err"), nil),
24+
exporter.NewFactory(
25+
component.MustNewType("all"),
26+
func() component.Config { return &defaultCfg },
27+
exporter.WithTraces(createExporterTraces, component.StabilityLevelDevelopment),
28+
exporter.WithMetrics(createExporterMetrics, component.StabilityLevelAlpha),
29+
exporter.WithLogs(createExporterLogs, component.StabilityLevelDeprecated),
30+
),
31+
}...)
32+
require.NoError(t, err)
33+
34+
testCases := []struct {
35+
name string
36+
id component.ID
37+
err string
38+
}{
39+
{
40+
name: "unknown",
41+
id: component.MustNewID("unknown"),
42+
err: "exporter factory not available for: \"unknown\"",
43+
},
44+
{
45+
name: "err",
46+
id: component.MustNewID("err"),
47+
err: "telemetry type is not supported",
48+
},
49+
{
50+
name: "all",
51+
id: component.MustNewID("all"),
52+
},
53+
{
54+
name: "all/named",
55+
id: component.MustNewIDWithName("all", "named"),
56+
},
57+
}
58+
59+
for _, tt := range testCases {
60+
t.Run(tt.name, func(t *testing.T) {
61+
cfgs := map[component.ID]component.Config{tt.id: defaultCfg}
62+
b := NewExporter(cfgs, factories)
63+
64+
te, err := b.CreateTraces(context.Background(), createExporterSettings(tt.id))
65+
if tt.err != "" {
66+
assert.EqualError(t, err, tt.err)
67+
assert.Nil(t, te)
68+
} else {
69+
assert.NoError(t, err)
70+
assert.Equal(t, nopExporterInstance, te)
71+
}
72+
73+
me, err := b.CreateMetrics(context.Background(), createExporterSettings(tt.id))
74+
if tt.err != "" {
75+
assert.EqualError(t, err, tt.err)
76+
assert.Nil(t, me)
77+
} else {
78+
assert.NoError(t, err)
79+
assert.Equal(t, nopExporterInstance, me)
80+
}
81+
82+
le, err := b.CreateLogs(context.Background(), createExporterSettings(tt.id))
83+
if tt.err != "" {
84+
assert.EqualError(t, err, tt.err)
85+
assert.Nil(t, le)
86+
} else {
87+
assert.NoError(t, err)
88+
assert.Equal(t, nopExporterInstance, le)
89+
}
90+
})
91+
}
92+
}
93+
94+
func TestExporterBuilderMissingConfig(t *testing.T) {
95+
defaultCfg := struct{}{}
96+
factories, err := exporter.MakeFactoryMap([]exporter.Factory{
97+
exporter.NewFactory(
98+
component.MustNewType("all"),
99+
func() component.Config { return &defaultCfg },
100+
exporter.WithTraces(createExporterTraces, component.StabilityLevelDevelopment),
101+
exporter.WithMetrics(createExporterMetrics, component.StabilityLevelAlpha),
102+
exporter.WithLogs(createExporterLogs, component.StabilityLevelDeprecated),
103+
),
104+
}...)
105+
106+
require.NoError(t, err)
107+
108+
bErr := NewExporter(map[component.ID]component.Config{}, factories)
109+
missingID := component.MustNewIDWithName("all", "missing")
110+
111+
te, err := bErr.CreateTraces(context.Background(), createExporterSettings(missingID))
112+
assert.EqualError(t, err, "exporter \"all/missing\" is not configured")
113+
assert.Nil(t, te)
114+
115+
me, err := bErr.CreateMetrics(context.Background(), createExporterSettings(missingID))
116+
assert.EqualError(t, err, "exporter \"all/missing\" is not configured")
117+
assert.Nil(t, me)
118+
119+
le, err := bErr.CreateLogs(context.Background(), createExporterSettings(missingID))
120+
assert.EqualError(t, err, "exporter \"all/missing\" is not configured")
121+
assert.Nil(t, le)
122+
}
123+
124+
func TestExporterBuilderFactory(t *testing.T) {
125+
factories, err := exporter.MakeFactoryMap([]exporter.Factory{exporter.NewFactory(component.MustNewType("foo"), nil)}...)
126+
require.NoError(t, err)
127+
128+
cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
129+
b := NewExporter(cfgs, factories)
130+
131+
assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
132+
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
133+
}
134+
135+
func TestNewNopExporterConfigsAndFactories(t *testing.T) {
136+
configs, factories := NewNopExporterConfigsAndFactories()
137+
builder := NewExporter(configs, factories)
138+
require.NotNil(t, builder)
139+
140+
factory := exportertest.NewNopFactory()
141+
cfg := factory.CreateDefaultConfig()
142+
set := exportertest.NewNopSettings()
143+
set.ID = component.NewID(nopType)
144+
145+
traces, err := factory.CreateTracesExporter(context.Background(), set, cfg)
146+
require.NoError(t, err)
147+
bTraces, err := builder.CreateTraces(context.Background(), set)
148+
require.NoError(t, err)
149+
assert.IsType(t, traces, bTraces)
150+
151+
metrics, err := factory.CreateMetricsExporter(context.Background(), set, cfg)
152+
require.NoError(t, err)
153+
bMetrics, err := builder.CreateMetrics(context.Background(), set)
154+
require.NoError(t, err)
155+
assert.IsType(t, metrics, bMetrics)
156+
157+
logs, err := factory.CreateLogsExporter(context.Background(), set, cfg)
158+
require.NoError(t, err)
159+
bLogs, err := builder.CreateLogs(context.Background(), set)
160+
require.NoError(t, err)
161+
assert.IsType(t, logs, bLogs)
162+
}
163+
164+
var nopExporterInstance = &nopExporter{
165+
Consumer: consumertest.NewNop(),
166+
}
167+
168+
// nopExporter stores consumed traces and metrics for testing purposes.
169+
type nopExporter struct {
170+
component.StartFunc
171+
component.ShutdownFunc
172+
consumertest.Consumer
173+
}
174+
175+
func createExporterTraces(context.Context, exporter.Settings, component.Config) (exporter.Traces, error) {
176+
return nopExporterInstance, nil
177+
}
178+
179+
func createExporterMetrics(context.Context, exporter.Settings, component.Config) (exporter.Metrics, error) {
180+
return nopExporterInstance, nil
181+
}
182+
183+
func createExporterLogs(context.Context, exporter.Settings, component.Config) (exporter.Logs, error) {
184+
return nopExporterInstance, nil
185+
}
186+
187+
func createExporterSettings(id component.ID) exporter.Settings {
188+
return exporter.Settings{
189+
ID: id,
190+
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
191+
BuildInfo: component.NewDefaultBuildInfo(),
192+
}
193+
}

0 commit comments

Comments
 (0)