Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions components/exporters/otlphttp/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestConfig_Validate_DefaultsFromFactoryAreInvalid(t *testing.T) {
t.Parallel()
// CreateDefaultConfig() should be invalid until Endpoint is set.
// operators must explicitly choose where to send data.
cfg, ok := otlphttp.Factory.CreateDefaultConfig().(*otlphttp.Config)
cfg, ok := otlphttp.NewFactory().CreateDefaultConfig().(*otlphttp.Config)
require.True(t, ok)
require.Error(t, cfg.Validate(),
"default config has no endpoint; operators must set one")
Expand Down Expand Up @@ -99,7 +99,7 @@ func TestExampleConfig_Parses(t *testing.T) {
}
require.NoError(t, yaml.Unmarshal(bs, &doc))

cfg, ok := otlphttp.Factory.CreateDefaultConfig().(*otlphttp.Config)
cfg, ok := otlphttp.NewFactory().CreateDefaultConfig().(*otlphttp.Config)
require.True(t, ok)
require.NoError(t, doc.Exporters.Otlphttp.Decode(cfg))
require.NoError(t, cfg.Validate())
Expand Down
68 changes: 47 additions & 21 deletions components/exporters/otlphttp/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,76 @@ import (
"context"
"fmt"

"github.com/tracecoreai/tracecore/internal/pipeline"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter"
)

func componentType() pipeline.Type { return pipeline.MustNewType("otlphttp") }
// componentType is the kind name OCB registers this exporter under.
// Wrapped in a function so the MustNewType call is not a top-level
// side effect (mirrors the nccl_fr / clockreceiver pattern).
func componentType() component.Type { return component.MustNewType("otlphttp") }

// Factory is the package-scoped ExporterFactory. Mirrors
// stdoutexporter.Factory in shape; the dual `Factory` var +
// `NewFactory()` func pattern is required by tools/components-gen,
// which calls `<alias>.NewFactory()` to populate the runtime's
// factory map.
var Factory pipeline.ExporterFactory = &factory{}
// stability is the OCB-surfaced stability level for the otlphttp
// exporter. Beta tracks "metrics + label shape pinned; behavior may
// evolve" — same level the in-tree exporter has carried since the
// v0.1.x internal/pipeline factory; this port preserves it across
// the upstream-API swap.
const stability = component.StabilityLevelBeta

// NewFactory returns the package-var Factory. Required by
// tools/components-gen.
func NewFactory() pipeline.ExporterFactory { return Factory }

type factory struct{}

func (*factory) Type() pipeline.Type { return componentType() }

func (*factory) CreateDefaultConfig() pipeline.Config {
return &Config{}
// NewFactory returns the upstream exporter.Factory for otlphttp.
// Mirrors the upstream-contrib pattern (otlpexporter, fileexporter) —
// callers construct via `otlphttp.NewFactory()` rather than a package
// var, so each OCB-stitched pipeline gets a freshly-built factory
// and the package surface stays a single exported symbol.
//
// All three signals are wired: the exporter POSTs metrics, traces,
// and logs against the OTLP/HTTP per-signal path suffix.
func NewFactory() exporter.Factory {
return exporter.NewFactory(
componentType(),
createDefaultConfig,
exporter.WithMetrics(createMetrics, stability),
exporter.WithTraces(createTraces, stability),
exporter.WithLogs(createLogs, stability),
)
}

func (*factory) CreateMetrics(ctx context.Context, set pipeline.CreateSettings, cfg pipeline.Config) (pipeline.Exporter, error) {
// createDefaultConfig matches upstream component.CreateDefaultConfigFunc.
func createDefaultConfig() component.Config { return &Config{} }

// createMetrics is the exporter.CreateMetricsFunc wired by WithMetrics.
func createMetrics(ctx context.Context, set exporter.Settings, cfg component.Config) (exporter.Metrics, error) {
c, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("otlphttp: unexpected config type %T", cfg)
}
return newExporter(ctx, set, c, signalMetrics)
}

func (*factory) CreateTraces(ctx context.Context, set pipeline.CreateSettings, cfg pipeline.Config) (pipeline.Exporter, error) {
// createTraces is the exporter.CreateTracesFunc wired by WithTraces.
func createTraces(ctx context.Context, set exporter.Settings, cfg component.Config) (exporter.Traces, error) {
c, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("otlphttp: unexpected config type %T", cfg)
}
return newExporter(ctx, set, c, signalTraces)
}

func (*factory) CreateLogs(ctx context.Context, set pipeline.CreateSettings, cfg pipeline.Config) (pipeline.Exporter, error) {
// createLogs is the exporter.CreateLogsFunc wired by WithLogs.
func createLogs(ctx context.Context, set exporter.Settings, cfg component.Config) (exporter.Logs, error) {
c, ok := cfg.(*Config)
if !ok {
return nil, fmt.Errorf("otlphttp: unexpected config type %T", cfg)
}
return newExporter(ctx, set, c, signalLogs)
}

// Compile-time assertion that the wired exporter satisfies the
// upstream per-signal interfaces. A breaking shape change in upstream
// surfaces here at build time rather than at runtime when OCB stitches
// the pipeline.
var (
_ exporter.Metrics = (*otlpExporter)(nil)
_ exporter.Traces = (*otlpExporter)(nil)
_ exporter.Logs = (*otlpExporter)(nil)
)
59 changes: 30 additions & 29 deletions components/exporters/otlphttp/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,83 +6,84 @@ import (
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/exporter/exportertest"

"github.com/tracecoreai/tracecore/components/exporters/otlphttp"
"github.com/tracecoreai/tracecore/internal/consumer"
"github.com/tracecoreai/tracecore/internal/pipeline"
"github.com/tracecoreai/tracecore/internal/pipeline/pipelinetest"
)

// componentType mirrors the production-package constant so external
// tests can construct exportertest settings keyed on the same Type
// the factory registers.
func componentType() component.Type { return component.MustNewType("otlphttp") }

func TestOtlphttp_TypeIsOtlphttp(t *testing.T) {
t.Parallel()
require.Equal(t, "otlphttp", otlphttp.Factory.Type().String())
require.Equal(t, "otlphttp", otlphttp.NewFactory().Type().String())
}

func TestOtlphttp_CreateDefaultConfig_ReturnsConfigPointer(t *testing.T) {
t.Parallel()
cfg := otlphttp.Factory.CreateDefaultConfig()
cfg := otlphttp.NewFactory().CreateDefaultConfig()
_, ok := cfg.(*otlphttp.Config)
require.True(t, ok, "factory must produce *Config")
}

func TestOtlphttp_NewFactoryReturnsSameInstance(t *testing.T) {
func TestOtlphttp_NewFactoryReturnsFreshInstancePerCall(t *testing.T) {
t.Parallel()
// tools/components-gen calls NewFactory(); the package also
// exposes Factory directly. They must be the same value so
// in-code references and generated references stay aligned.
require.Same(t, otlphttp.Factory, otlphttp.NewFactory())
// Each NewFactory() call returns its own Factory value so OCB-
// stitched pipelines can hold per-pipeline factory state without
// alias surprises. The Type the factory advertises stays stable.
f1 := otlphttp.NewFactory()
f2 := otlphttp.NewFactory()
require.Equal(t, f1.Type(), f2.Type())
}

func TestOtlphttp_CreateMetrics_ReturnsExporter(t *testing.T) {
t.Parallel()
fx := pipelinetest.New(t)
set := exportertest.NewNopSettings(componentType())
cfg := &otlphttp.Config{Endpoint: "http://localhost:4318"}

exp, err := otlphttp.Factory.CreateMetrics(t.Context(), fx.CreateSettings, cfg)
exp, err := otlphttp.NewFactory().CreateMetrics(t.Context(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
_, ok := exp.(consumer.Metrics)
require.True(t, ok, "exporter must implement consumer.Metrics")
}

func TestOtlphttp_CreateTraces_ReturnsExporter(t *testing.T) {
t.Parallel()
fx := pipelinetest.New(t)
set := exportertest.NewNopSettings(componentType())
cfg := &otlphttp.Config{Endpoint: "http://localhost:4318"}

exp, err := otlphttp.Factory.CreateTraces(t.Context(), fx.CreateSettings, cfg)
exp, err := otlphttp.NewFactory().CreateTraces(t.Context(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
_, ok := exp.(consumer.Traces)
require.True(t, ok, "exporter must implement consumer.Traces")
}

func TestOtlphttp_CreateLogs_ReturnsExporter(t *testing.T) {
t.Parallel()
fx := pipelinetest.New(t)
set := exportertest.NewNopSettings(componentType())
cfg := &otlphttp.Config{Endpoint: "http://localhost:4318"}

exp, err := otlphttp.Factory.CreateLogs(t.Context(), fx.CreateSettings, cfg)
exp, err := otlphttp.NewFactory().CreateLogs(t.Context(), set, cfg)
require.NoError(t, err)
require.NotNil(t, exp)
_, ok := exp.(consumer.Logs)
require.True(t, ok, "exporter must implement consumer.Logs")
}

func TestOtlphttp_CreateMetrics_RejectsWrongConfigType(t *testing.T) {
t.Parallel()
fx := pipelinetest.New(t)
set := exportertest.NewNopSettings(componentType())
// Passing a config of the wrong type should fail with a clear
// operator-actionable error.
_, err := otlphttp.Factory.CreateMetrics(t.Context(), fx.CreateSettings, &otlphttp.Config{Endpoint: "http://localhost:4318"})
_, err := otlphttp.NewFactory().CreateMetrics(t.Context(), set, &otlphttp.Config{Endpoint: "http://localhost:4318"})
require.NoError(t, err) // sanity: correct type passes
_, err = otlphttp.Factory.CreateMetrics(t.Context(), fx.CreateSettings, &wrongConfig{})
_, err = otlphttp.NewFactory().CreateMetrics(t.Context(), set, &wrongConfig{})
require.ErrorContains(t, err, "unexpected config type")
}

// wrongConfig satisfies component.Config (just an empty struct);
// the factory rejects it with an operator-actionable error.
type wrongConfig struct{}

func (*wrongConfig) Validate() error { return nil }

// Compile-time assertion that wrongConfig implements pipeline.Config.
var _ pipeline.Config = (*wrongConfig)(nil)
// Compile-time assertion that wrongConfig satisfies component.Config
// (which is any in upstream v1.59.0; the constraint is documentary).
var _ component.Config = (*wrongConfig)(nil)
Loading
Loading