From 51e7d42b16810109237e70191ea261d3b7bee135 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:06:54 -0700 Subject: [PATCH 01/41] fix(loadbalancingexporter): batch logs after routing Expose optional per-backend log batching with backward-compatible defaults and preserve the legacy path when disabled. Refs: SAW-6744 --- exporter/loadbalancingexporter/README.md | 10 + exporter/loadbalancingexporter/config.go | 31 +- exporter/loadbalancingexporter/config_test.go | 52 +++ exporter/loadbalancingexporter/factory.go | 6 + .../loadbalancingexporter/factory_test.go | 9 + exporter/loadbalancingexporter/helpers.go | 7 + .../loadbalancingexporter/loadbalancer.go | 6 + exporter/loadbalancingexporter/log_batcher.go | 426 ++++++++++++++++++ .../loadbalancingexporter/log_batcher_test.go | 251 +++++++++++ .../loadbalancingexporter/log_exporter.go | 101 ++++- .../log_exporter_test.go | 86 ++-- 11 files changed, 932 insertions(+), 53 deletions(-) create mode 100644 exporter/loadbalancingexporter/log_batcher.go create mode 100644 exporter/loadbalancingexporter/log_batcher_test.go diff --git a/exporter/loadbalancingexporter/README.md b/exporter/loadbalancingexporter/README.md index fd0c7500a87f2..dae6f332b8a9d 100644 --- a/exporter/loadbalancingexporter/README.md +++ b/exporter/loadbalancingexporter/README.md @@ -124,6 +124,11 @@ Refer to [config.yaml](./testdata/config.yaml) for detailed examples on using th * `streamID`: Routes metrics based on their datapoint streamID. That's the unique hash of all it's attributes, plus the attributes and identifying information of its resource, scope, and metric data * loadbalancing exporter supports set of standard [queuing, retry and timeout settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md), but they are disable by default to maintain compatibility * The `routing_attributes` property is used to list the attributes that should be used if the `routing_key` is `attributes`. +* The `log_batcher` property enables post-routing log batching per backend. It is `disabled` by default for backward compatibility. + * `enabled` turns post-routing log batching on or off. + * `max_records` flushes a backend batch when it reaches this many log records. Default: `512`. + * `max_bytes` flushes a backend batch when its serialized OTLP payload size before compression reaches this many bytes. Default: `1048576` (`1 MiB`). + * `flush_interval` flushes a backend batch after this interval even if size limits are not reached. Default: `100ms`. Simple example @@ -138,6 +143,11 @@ processors: exporters: loadbalancing: + log_batcher: + enabled: true + max_records: 512 + max_bytes: 1048576 + flush_interval: 100ms routing_key: "service" protocol: otlp: diff --git a/exporter/loadbalancingexporter/config.go b/exporter/loadbalancingexporter/config.go index 2dad3f43c48b3..37e6a27712149 100644 --- a/exporter/loadbalancingexporter/config.go +++ b/exporter/loadbalancingexporter/config.go @@ -40,7 +40,8 @@ const ( type Config struct { TimeoutSettings exporterhelper.TimeoutConfig `mapstructure:",squash"` configretry.BackOffConfig `mapstructure:"retry_on_failure"` - QueueSettings QueueSettings `mapstructure:"sending_queue"` + QueueSettings QueueSettings `mapstructure:"sending_queue"` + LogBatcher LogBatcherConfig `mapstructure:"log_batcher"` Protocol Protocol `mapstructure:"protocol"` Resolver ResolverSettings `mapstructure:"resolver"` @@ -61,6 +62,13 @@ type QueueSettings struct { CompressInMemory bool `mapstructure:"compress_in_memory"` } +type LogBatcherConfig struct { + Enabled bool `mapstructure:"enabled"` + MaxRecords int `mapstructure:"max_records"` + MaxBytes int `mapstructure:"max_bytes"` + FlushInterval time.Duration `mapstructure:"flush_interval"` +} + func (q *QueueSettings) Unmarshal(conf *confmap.Conf) error { if conf == nil { return nil @@ -128,7 +136,26 @@ func (q QueueSettings) Validate() error { } func (cfg *Config) Validate() error { - return cfg.QueueSettings.Validate() + if err := cfg.QueueSettings.Validate(); err != nil { + return err + } + return cfg.LogBatcher.Validate() +} + +func (c LogBatcherConfig) Validate() error { + if !c.Enabled { + return nil + } + if c.MaxRecords <= 0 { + return errors.New("log_batcher.max_records must be greater than 0 when log_batcher.enabled=true") + } + if c.MaxBytes <= 0 { + return errors.New("log_batcher.max_bytes must be greater than 0 when log_batcher.enabled=true") + } + if c.FlushInterval <= 0 { + return errors.New("log_batcher.flush_interval must be greater than 0 when log_batcher.enabled=true") + } + return nil } // Protocol holds the individual protocol-specific settings. Only OTLP is supported at the moment. diff --git a/exporter/loadbalancingexporter/config_test.go b/exporter/loadbalancingexporter/config_test.go index a39c6783eb658..29aa6f483d59a 100644 --- a/exporter/loadbalancingexporter/config_test.go +++ b/exporter/loadbalancingexporter/config_test.go @@ -6,6 +6,7 @@ package loadbalancingexporter import ( "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" @@ -54,6 +55,26 @@ func TestConfigValidateCompressInMemory(t *testing.T) { require.NoError(t, cfg.Validate()) } +func TestConfigValidateLogBatcher(t *testing.T) { + cfg := createDefaultConfig().(*Config) + require.NoError(t, cfg.Validate()) + + cfg.LogBatcher.Enabled = true + cfg.LogBatcher.MaxRecords = 0 + require.ErrorContains(t, cfg.Validate(), "log_batcher.max_records") + + cfg.LogBatcher.MaxRecords = 10 + cfg.LogBatcher.MaxBytes = 0 + require.ErrorContains(t, cfg.Validate(), "log_batcher.max_bytes") + + cfg.LogBatcher.MaxBytes = 1024 + cfg.LogBatcher.FlushInterval = 0 + require.ErrorContains(t, cfg.Validate(), "log_batcher.flush_interval") + + cfg.LogBatcher.FlushInterval = time.Second + require.NoError(t, cfg.Validate()) +} + func TestLoadConfigWithQueueCompression(t *testing.T) { cfg := createDefaultConfig().(*Config) conf := confmap.NewFromStringMap(map[string]any{ @@ -83,3 +104,34 @@ func TestLoadConfigWithQueueCompression(t *testing.T) { require.Equal(t, QueuePayloadCompressionZstd, cfg.QueueSettings.PayloadCompression) require.True(t, cfg.QueueSettings.CompressInMemory) } + +func TestLoadConfigWithLogBatcher(t *testing.T) { + cfg := createDefaultConfig().(*Config) + conf := confmap.NewFromStringMap(map[string]any{ + "protocol": map[string]any{ + "otlp": map[string]any{ + "endpoint": "localhost:4317", + "tls": map[string]any{ + "insecure": true, + }, + }, + }, + "resolver": map[string]any{ + "static": map[string]any{ + "hostnames": []string{"localhost:4317"}, + }, + }, + "log_batcher": map[string]any{ + "enabled": true, + "max_records": 1024, + "max_bytes": 2097152, + "flush_interval": "250ms", + }, + }) + + require.NoError(t, conf.Unmarshal(cfg)) + require.True(t, cfg.LogBatcher.Enabled) + require.Equal(t, 1024, cfg.LogBatcher.MaxRecords) + require.Equal(t, 2097152, cfg.LogBatcher.MaxBytes) + require.Equal(t, 250*time.Millisecond, cfg.LogBatcher.FlushInterval) +} diff --git a/exporter/loadbalancingexporter/factory.go b/exporter/loadbalancingexporter/factory.go index a6720d7309975..c438e88f69250 100644 --- a/exporter/loadbalancingexporter/factory.go +++ b/exporter/loadbalancingexporter/factory.go @@ -53,6 +53,12 @@ func createDefaultConfig() component.Config { QueueBatchConfig: queueCfg, PayloadCompression: QueuePayloadCompressionNone, }, + LogBatcher: LogBatcherConfig{ + Enabled: false, + MaxRecords: defaultLogBatchMaxRecords, + MaxBytes: defaultLogBatchMaxBytes, + FlushInterval: defaultLogBatchFlushTimeout, + }, } } diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index fca75c5d0730a..c7a7389b0f5ce 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -74,6 +74,15 @@ func TestOTLPConfigIsValid(t *testing.T) { assert.NoError(t, otlpCfg.Validate()) } +func TestDefaultLogBatcherConfig(t *testing.T) { + cfg := createDefaultConfig().(*Config) + + assert.False(t, cfg.LogBatcher.Enabled) + assert.Equal(t, defaultLogBatchMaxRecords, cfg.LogBatcher.MaxRecords) + assert.Equal(t, defaultLogBatchMaxBytes, cfg.LogBatcher.MaxBytes) + assert.Equal(t, defaultLogBatchFlushTimeout, cfg.LogBatcher.FlushInterval) +} + func TestBuildExporterConfig(t *testing.T) { // prepare factories, err := otelcoltest.NopFactories() diff --git a/exporter/loadbalancingexporter/helpers.go b/exporter/loadbalancingexporter/helpers.go index cbf48a0f02bc2..ef8e19bb18b21 100644 --- a/exporter/loadbalancingexporter/helpers.go +++ b/exporter/loadbalancingexporter/helpers.go @@ -4,6 +4,7 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter" import ( + "go.opentelemetry.io/collector/pdata/plog" "go.opentelemetry.io/collector/pdata/ptrace" ) @@ -12,3 +13,9 @@ func mergeTraces(t1, t2 ptrace.Traces) ptrace.Traces { t2.ResourceSpans().MoveAndAppendTo(t1.ResourceSpans()) return t1 } + +// mergeLogs concatenates two plog.Logs into a single plog.Logs. +func mergeLogs(l1, l2 plog.Logs) plog.Logs { + l2.ResourceLogs().MoveAndAppendTo(l1.ResourceLogs()) + return l1 +} diff --git a/exporter/loadbalancingexporter/loadbalancer.go b/exporter/loadbalancingexporter/loadbalancer.go index bdb3058c7f50f..a05402b3f8a8e 100644 --- a/exporter/loadbalancingexporter/loadbalancer.go +++ b/exporter/loadbalancingexporter/loadbalancer.go @@ -37,6 +37,7 @@ type loadBalancer struct { componentFactory componentFactory exporters map[string]*wrappedExporter + onExporterRemove func(context.Context, string, *wrappedExporter) error stopped bool updateLock sync.RWMutex @@ -203,6 +204,11 @@ func (lb *loadBalancer) removeExtraExporters(ctx context.Context, endpoints []st for existing := range lb.exporters { if !slices.Contains(endpointsWithPort, existing) { exp := lb.exporters[existing] + if lb.onExporterRemove != nil { + if err := lb.onExporterRemove(ctx, existing, exp); err != nil { + lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", existing), zap.Error(err)) + } + } // Shutdown the exporter asynchronously to avoid blocking the resolver go func() { _ = exp.Shutdown(ctx) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go new file mode 100644 index 0000000000000..cbb8b46f98167 --- /dev/null +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -0,0 +1,426 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter" + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter/internal/metadata" +) + +const ( + logFlushReasonSize = "size" + logFlushReasonTimeout = "timeout" + logFlushReasonShutdown = "shutdown" + logFlushReasonResolverChange = "resolver_change" + + defaultLogBatchMaxRecords = 512 + defaultLogBatchMaxBytes = 1 << 20 + defaultLogBatchFlushTimeout = 100 * time.Millisecond +) + +type logBatcherSettings struct { + maxRecords int + maxBytes int + flushInterval time.Duration +} + +type logBatcherSendFunc func(context.Context, *wrappedExporter, plog.Logs, string) error + +type logBatcher struct { + logger *zap.Logger + settings logBatcherSettings + send logBatcherSendFunc + + telemetry *logBatcherTelemetry + + mu sync.RWMutex + backends map[string]*backendLogBatcher +} + +type logBatcherRequest struct { + kind logBatcherRequestKind + logs plog.Logs + ctx context.Context + reason string + done chan error +} + +type logBatcherRequestKind int + +const ( + logBatcherRequestEnqueue logBatcherRequestKind = iota + logBatcherRequestFlushAndStop +) + +type backendLogBatcher struct { + endpoint string + exp *wrappedExporter + logger *zap.Logger + settings logBatcherSettings + send logBatcherSendFunc + + telemetry *logBatcherTelemetry + + requests chan logBatcherRequest + done chan struct{} + + pendingRecords atomic.Int64 + pendingBytes atomic.Int64 +} + +type logBatcherTelemetry struct { + meter metric.Meter + batchSize metric.Int64Histogram + batchBytes metric.Int64Histogram + flushTotal metric.Int64Counter + flushErrors metric.Int64Counter + droppedRecords metric.Int64Counter + overflowTotal metric.Int64Counter + pendingRecords metric.Int64ObservableGauge + pendingBytes metric.Int64ObservableGauge + + mu sync.Mutex + registrations []metric.Registration +} + +func newLogBatcher( + logger *zap.Logger, + settings component.TelemetrySettings, + cfg logBatcherSettings, + send logBatcherSendFunc, +) (*logBatcher, error) { + telemetry, err := newLogBatcherTelemetry(settings) + if err != nil { + return nil, err + } + + lb := &logBatcher{ + logger: logger, + settings: cfg, + send: send, + telemetry: telemetry, + backends: make(map[string]*backendLogBatcher), + } + + if err := telemetry.start(lb.snapshotPending); err != nil { + return nil, err + } + + return lb, nil +} + +func (b *logBatcher) Enqueue(endpoint string, exp *wrappedExporter, logs plog.Logs) error { + backend := b.getOrCreateBackend(endpoint, exp) + select { + case backend.requests <- logBatcherRequest{kind: logBatcherRequestEnqueue, logs: logs}: + return nil + case <-backend.done: + return errors.New("log batcher backend is stopped") + } +} + +func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedExporter) error { + b.mu.Lock() + backend, ok := b.backends[endpoint] + if ok { + delete(b.backends, endpoint) + } + b.mu.Unlock() + if !ok { + return nil + } + if exp != nil { + backend.exp = exp + } + return backend.stopAndFlush(ctx, logFlushReasonResolverChange) +} + +func (b *logBatcher) Shutdown(ctx context.Context) error { + b.mu.Lock() + backends := make([]*backendLogBatcher, 0, len(b.backends)) + for endpoint, backend := range b.backends { + backends = append(backends, backend) + delete(b.backends, endpoint) + } + b.mu.Unlock() + + var errs error + for _, backend := range backends { + errs = errors.Join(errs, backend.stopAndFlush(ctx, logFlushReasonShutdown)) + } + b.telemetry.shutdown() + return errs +} + +func (b *logBatcher) snapshotPending() []logBatcherPending { + b.mu.RLock() + defer b.mu.RUnlock() + + pending := make([]logBatcherPending, 0, len(b.backends)) + for endpoint, backend := range b.backends { + pending = append(pending, logBatcherPending{ + endpoint: endpoint, + records: backend.pendingRecords.Load(), + bytes: backend.pendingBytes.Load(), + }) + } + return pending +} + +func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) *backendLogBatcher { + b.mu.RLock() + backend, ok := b.backends[endpoint] + b.mu.RUnlock() + if ok { + backend.exp = exp + return backend + } + + b.mu.Lock() + defer b.mu.Unlock() + backend, ok = b.backends[endpoint] + if ok { + backend.exp = exp + return backend + } + + backend = newBackendLogBatcher(endpoint, exp, b.logger, b.settings, b.telemetry, b.send) + b.backends[endpoint] = backend + return backend +} + +func newBackendLogBatcher( + endpoint string, + exp *wrappedExporter, + logger *zap.Logger, + settings logBatcherSettings, + telemetry *logBatcherTelemetry, + send logBatcherSendFunc, +) *backendLogBatcher { + backend := &backendLogBatcher{ + endpoint: endpoint, + exp: exp, + logger: logger.With(zap.String("endpoint", endpoint)), + settings: settings, + send: send, + telemetry: telemetry, + requests: make(chan logBatcherRequest, 16), + done: make(chan struct{}), + } + + go backend.run() + return backend +} + +func (b *backendLogBatcher) stopAndFlush(ctx context.Context, reason string) error { + done := make(chan error, 1) + select { + case b.requests <- logBatcherRequest{ + kind: logBatcherRequestFlushAndStop, + ctx: ctx, + reason: reason, + done: done, + }: + case <-b.done: + return nil + } + + select { + case err := <-done: + <-b.done + return err + case <-ctx.Done(): + return ctx.Err() + } +} + +func (b *backendLogBatcher) run() { + defer close(b.done) + + timer := time.NewTimer(time.Hour) + if !timer.Stop() { + <-timer.C + } + var timerC <-chan time.Time + + pending := plog.NewLogs() + pendingBytes := 0 + pendingRecords := 0 + sizer := &plog.ProtoMarshaler{} + + for { + select { + case req := <-b.requests: + switch req.kind { + case logBatcherRequestEnqueue: + recordCount := req.logs.LogRecordCount() + pending = mergeLogs(pending, req.logs) + pendingRecords += recordCount + pendingBytes = sizer.LogsSize(pending) + b.pendingRecords.Store(int64(pendingRecords)) + b.pendingBytes.Store(int64(pendingBytes)) + if pendingRecords > 0 && timerC == nil { + timer.Reset(b.settings.flushInterval) + timerC = timer.C + } + if pendingRecords >= b.settings.maxRecords || pendingBytes >= b.settings.maxBytes { + b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonSize, timer, &timerC); err != nil { + b.logger.Debug("failed to flush log batch", zap.Error(err)) + } + } + case logBatcherRequestFlushAndStop: + err := b.flush(req.ctx, &pending, &pendingRecords, &pendingBytes, req.reason, timer, &timerC) + req.done <- err + return + } + case <-timerC: + if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonTimeout, timer, &timerC); err != nil { + b.logger.Debug("failed to flush log batch", zap.Error(err)) + } + } + } +} + +func (b *backendLogBatcher) flush( + ctx context.Context, + pending *plog.Logs, + pendingRecords *int, + pendingBytes *int, + reason string, + timer *time.Timer, + timerC *<-chan time.Time, +) error { + if !timer.Stop() && *timerC != nil { + select { + case <-timer.C: + default: + } + } + *timerC = nil + + if pending.LogRecordCount() == 0 { + return nil + } + + drained := *pending + records := *pendingRecords + bytes := *pendingBytes + *pending = plog.NewLogs() + *pendingRecords = 0 + *pendingBytes = 0 + b.pendingRecords.Store(0) + b.pendingBytes.Store(0) + + b.telemetry.batchSize.Record(ctx, int64(records), metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + b.telemetry.batchBytes.Record(ctx, int64(bytes), metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + b.telemetry.flushTotal.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint), attribute.String("reason", reason)))) + + err := b.send(ctx, b.exp, drained, reason) + if err != nil { + b.telemetry.flushErrors.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + } + return err +} + +type logBatcherPending struct { + endpoint string + records int64 + bytes int64 +} + +func newLogBatcherTelemetry(settings component.TelemetrySettings) (*logBatcherTelemetry, error) { + meter := metadata.Meter(settings) + var err, errs error + + t := &logBatcherTelemetry{meter: meter} + t.batchSize, err = meter.Int64Histogram( + "otelcol_loadbalancer_log_batch_size", + metric.WithDescription("Number of log records per flushed backend batch."), + metric.WithUnit("{records}"), + ) + errs = errors.Join(errs, err) + t.batchBytes, err = meter.Int64Histogram( + "otelcol_loadbalancer_log_batch_bytes", + metric.WithDescription("Serialized OTLP bytes per flushed backend batch before compression."), + metric.WithUnit("By"), + ) + errs = errors.Join(errs, err) + t.flushTotal, err = meter.Int64Counter( + "otelcol_loadbalancer_log_batch_flush_total", + metric.WithDescription("Number of log batch flushes by endpoint and reason."), + metric.WithUnit("{flushes}"), + ) + errs = errors.Join(errs, err) + t.flushErrors, err = meter.Int64Counter( + "otelcol_loadbalancer_log_batch_flush_errors", + metric.WithDescription("Number of log batch flush errors."), + metric.WithUnit("{errors}"), + ) + errs = errors.Join(errs, err) + t.droppedRecords, err = meter.Int64Counter( + "otelcol_loadbalancer_log_batch_dropped_records", + metric.WithDescription("Number of dropped log records in the internal log batcher."), + metric.WithUnit("{records}"), + ) + errs = errors.Join(errs, err) + t.overflowTotal, err = meter.Int64Counter( + "otelcol_loadbalancer_log_batch_overflow_total", + metric.WithDescription("Number of times an internal log batch hit a size bound and was force-flushed."), + metric.WithUnit("{overflows}"), + ) + errs = errors.Join(errs, err) + t.pendingRecords, err = meter.Int64ObservableGauge( + "otelcol_loadbalancer_log_batch_pending_records", + metric.WithDescription("Current number of pending log records per backend batch."), + metric.WithUnit("{records}"), + ) + errs = errors.Join(errs, err) + t.pendingBytes, err = meter.Int64ObservableGauge( + "otelcol_loadbalancer_log_batch_pending_bytes", + metric.WithDescription("Current serialized OTLP bytes per pending backend batch before compression."), + metric.WithUnit("By"), + ) + errs = errors.Join(errs, err) + + return t, errs +} + +func (t *logBatcherTelemetry) start(snapshot func() []logBatcherPending) error { + reg, err := t.meter.RegisterCallback(func(_ context.Context, observer metric.Observer) error { + for _, pending := range snapshot() { + attrs := metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", pending.endpoint))) + observer.ObserveInt64(t.pendingRecords, pending.records, attrs) + observer.ObserveInt64(t.pendingBytes, pending.bytes, attrs) + } + return nil + }, t.pendingRecords, t.pendingBytes) + if err != nil { + return err + } + t.mu.Lock() + t.registrations = append(t.registrations, reg) + t.mu.Unlock() + return nil +} + +func (t *logBatcherTelemetry) shutdown() { + t.mu.Lock() + defer t.mu.Unlock() + for _, reg := range t.registrations { + reg.Unregister() + } + t.registrations = nil +} diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go new file mode 100644 index 0000000000000..6d01978679c15 --- /dev/null +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -0,0 +1,251 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package loadbalancingexporter + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/config/configoptional" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter/internal/metadata" +) + +func TestLogBatcherMergesSameBackendOnShutdown(t *testing.T) { + ts, tb := getTelemetryAssets(t) + sink := new(consumertest.LogsSink) + p, _ := newTestLogsExporter(t, ts, tb, simpleConfig(), func(_ context.Context, _ string) (component.Component, error) { + return newMockLogsExporter(sink.ConsumeLogs), nil + }) + + p.batcher, _ = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } + + require.NoError(t, p.Start(t.Context(), componenttest.NewNopHost())) + require.NoError(t, p.ConsumeLogs(t.Context(), logsWithTraceIDs([16]byte{1}, [16]byte{2}))) + require.NoError(t, p.Shutdown(t.Context())) + + require.Len(t, sink.AllLogs(), 1) + assert.Equal(t, 2, sink.AllLogs()[0].LogRecordCount()) +} + +func TestLogBatcherFlushesOnTimeout(t *testing.T) { + ts, tb := getTelemetryAssets(t) + sink := new(consumertest.LogsSink) + p, _ := newTestLogsExporter(t, ts, tb, simpleConfig(), func(_ context.Context, _ string) (component.Component, error) { + return newMockLogsExporter(sink.ConsumeLogs), nil + }) + + p.batcher, _ = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: 25 * time.Millisecond, + }, p.consumeBatch) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } + + require.NoError(t, p.Start(t.Context(), componenttest.NewNopHost())) + defer func() { require.NoError(t, p.Shutdown(t.Context())) }() + require.NoError(t, p.ConsumeLogs(t.Context(), simpleLogs())) + + require.Eventually(t, func() bool { + return len(sink.AllLogs()) == 1 + }, time.Second, 10*time.Millisecond) +} + +func TestLogBatcherFlushesOnMaxBytes(t *testing.T) { + ts, tb := getTelemetryAssets(t) + sink := new(consumertest.LogsSink) + p, _ := newTestLogsExporter(t, ts, tb, simpleConfig(), func(_ context.Context, _ string) (component.Component, error) { + return newMockLogsExporter(sink.ConsumeLogs), nil + }) + + first := sizedLogWithID(pcommon.TraceID([16]byte{1}), 512) + maxBytes := (&plog.ProtoMarshaler{}).LogsSize(first) + 1 + + p.batcher, _ = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: maxBytes, + flushInterval: time.Hour, + }, p.consumeBatch) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } + + require.NoError(t, p.Start(t.Context(), componenttest.NewNopHost())) + defer func() { require.NoError(t, p.Shutdown(t.Context())) }() + require.NoError(t, p.ConsumeLogs(t.Context(), mergeLogs(first, sizedLogWithID(pcommon.TraceID([16]byte{2}), 512)))) + + require.Eventually(t, func() bool { + return len(sink.AllLogs()) == 1 && sink.AllLogs()[0].LogRecordCount() == 2 + }, time.Second, 10*time.Millisecond) +} + +func TestLogBatcherDoesNotBlockOtherBackends(t *testing.T) { + ts, _ := getTelemetryAssets(t) + blockFirst := make(chan struct{}) + firstStarted := make(chan struct{}, 1) + var secondCalls atomic.Int64 + batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, func(ctx context.Context, exp *wrappedExporter, ld plog.Logs, reason string) error { + _ = reason + exp.consumeWG.Add(1) + defer exp.consumeWG.Done() + return exp.ConsumeLogs(ctx, ld) + }) + require.NoError(t, err) + defer func() { + close(blockFirst) + require.NoError(t, batcher.Shutdown(t.Context())) + }() + + firstExporter := newWrappedExporter(newMockLogsExporter(func(_ context.Context, _ plog.Logs) error { + firstStarted <- struct{}{} + <-blockFirst + return nil + }), "endpoint-1:4317") + secondExporter := newWrappedExporter(newMockLogsExporter(func(_ context.Context, _ plog.Logs) error { + secondCalls.Add(1) + return nil + }), "endpoint-2:4317") + + require.NoError(t, batcher.Enqueue("endpoint-1:4317", firstExporter, simpleLogs())) + <-firstStarted + require.NoError(t, batcher.Enqueue("endpoint-2:4317", secondExporter, simpleLogs())) + + require.Eventually(t, func() bool { + return secondCalls.Load() == 1 + }, time.Second, 10*time.Millisecond) +} + +func TestLogBatcherFlushesRemovedBackendToOldExporter(t *testing.T) { + ts, tb := getTelemetryAssets(t) + var endpoint1Calls atomic.Int64 + var endpoint2Calls atomic.Int64 + endpoints := []string{"endpoint-1"} + + p, lb := newTestLogsExporter(t, ts, tb, &Config{ + Resolver: ResolverSettings{ + Static: configoptional.Some(StaticResolver{Hostnames: []string{"endpoint-1"}}), + }, + }, func(_ context.Context, endpoint string) (component.Component, error) { + switch endpoint { + case "endpoint-1:4317": + return newMockLogsExporter(func(_ context.Context, _ plog.Logs) error { + endpoint1Calls.Add(1) + return nil + }), nil + case "endpoint-2:4317": + return newMockLogsExporter(func(_ context.Context, _ plog.Logs) error { + endpoint2Calls.Add(1) + return nil + }), nil + default: + return newNopMockLogsExporter(), nil + } + }) + + lb.res = &mockResolver{ + triggerCallbacks: true, + onResolve: func(_ context.Context) ([]string, error) { + return endpoints, nil + }, + } + p.loadBalancer = lb + p.batcher, _ = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } + + require.NoError(t, p.Start(t.Context(), componenttest.NewNopHost())) + defer func() { require.NoError(t, p.Shutdown(t.Context())) }() + + require.NoError(t, p.ConsumeLogs(t.Context(), simpleLogs())) + endpoints = []string{"endpoint-2"} + _, err := lb.res.resolve(t.Context()) + require.NoError(t, err) + + require.Eventually(t, func() bool { + return endpoint1Calls.Load() == 1 + }, time.Second, 10*time.Millisecond) + assert.Zero(t, endpoint2Calls.Load()) +} + +func newTestLogsExporter( + t *testing.T, + ts exporter.Settings, + tb *metadata.TelemetryBuilder, + cfg *Config, + componentFactory func(context.Context, string) (component.Component, error), +) (*logExporterImp, *loadBalancer) { + t.Helper() + + lb, err := newLoadBalancer(ts.Logger, cfg, componentFactory, tb) + require.NoError(t, err) + lb.addMissingExporters(t.Context(), cfg.Resolver.Static.Get().Hostnames) + lb.res = &mockResolver{ + triggerCallbacks: true, + onResolve: func(_ context.Context) ([]string, error) { + return cfg.Resolver.Static.Get().Hostnames, nil + }, + } + + p, err := newLogsExporter(ts, cfg) + require.NoError(t, err) + p.loadBalancer = lb + return p, lb +} + +func traceIDForEndpoint(t *testing.T, lb *loadBalancer, endpoint string) pcommon.TraceID { + t.Helper() + for i := 0; i < 255; i++ { + id := pcommon.TraceID([16]byte{byte(i)}) + _, actual, err := lb.exporterAndEndpoint(id[:]) + require.NoError(t, err) + if actual == endpoint { + return id + } + } + t.Fatalf("no traceID found for endpoint %s", endpoint) + return pcommon.NewTraceIDEmpty() +} + +func logsWithTraceIDs(ids ...pcommon.TraceID) plog.Logs { + logs := plog.NewLogs() + for _, id := range ids { + single := simpleLogWithID(id) + mergeLogs(logs, single) + } + return logs +} + +func sizedLogWithID(id pcommon.TraceID, size int) plog.Logs { + logs := simpleLogWithID(id) + logs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords().At(0).Body().SetStr(string(make([]byte, size))) + return logs +} diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 49ff97b55a228..b793be5640181 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -5,8 +5,8 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry import ( "context" + "errors" "math/rand/v2" - "sync" "time" "go.opentelemetry.io/collector/component" @@ -27,11 +27,11 @@ var _ exporter.Logs = (*logExporterImp)(nil) type logExporterImp struct { loadBalancer *loadBalancer + batcher *logBatcher - logger *zap.Logger - started bool - shutdownWg sync.WaitGroup - telemetry *metadata.TelemetryBuilder + logger *zap.Logger + started bool + telemetry *metadata.TelemetryBuilder } // Create new logs exporter @@ -53,11 +53,31 @@ func newLogsExporter(params exporter.Settings, cfg component.Config) (*logExport return nil, err } - return &logExporterImp{ + logExporter := &logExporterImp{ loadBalancer: lb, telemetry: telemetry, logger: params.Logger, - }, nil + } + if cfg.(*Config).LogBatcher.Enabled { + logExporter.batcher, err = newLogBatcher( + params.Logger, + params.TelemetrySettings, + logBatcherSettings{ + maxRecords: cfg.(*Config).LogBatcher.MaxRecords, + maxBytes: cfg.(*Config).LogBatcher.MaxBytes, + flushInterval: cfg.(*Config).LogBatcher.FlushInterval, + }, + logExporter.consumeBatch, + ) + if err != nil { + return nil, err + } + lb.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return logExporter.batcher.Remove(ctx, endpoint, exp) + } + } + + return logExporter, nil } func (*logExporterImp) Capabilities() consumer.Capabilities { @@ -73,29 +93,59 @@ func (e *logExporterImp) Shutdown(ctx context.Context) error { if !e.started { return nil } - err := e.loadBalancer.Shutdown(ctx) + var err error + if e.batcher != nil { + err = e.batcher.Shutdown(ctx) + } + err = errors.Join(err, e.loadBalancer.Shutdown(ctx)) e.started = false - e.shutdownWg.Wait() return err } func (e *logExporterImp) ConsumeLogs(ctx context.Context, ld plog.Logs) error { + if e.batcher == nil { + var errs error + batches := batchpersignal.SplitLogs(ld) + for _, batch := range batches { + errs = multierr.Append(errs, e.consumeLog(ctx, batch)) + } + return errs + } + var errs error - batches := batchpersignal.SplitLogs(ld) - for _, batch := range batches { - errs = multierr.Append(errs, e.consumeLog(ctx, batch)) + for i := 0; i < ld.ResourceLogs().Len(); i++ { + resourceLogs := ld.ResourceLogs().At(i) + for j := 0; j < resourceLogs.ScopeLogs().Len(); j++ { + scopeLogs := resourceLogs.ScopeLogs().At(j) + for k := 0; k < scopeLogs.LogRecords().Len(); k++ { + record := scopeLogs.LogRecords().At(k) + errs = multierr.Append(errs, e.consumeLogRecord(resourceLogs, scopeLogs, record)) + } + } } return errs } +func (e *logExporterImp) consumeLogRecord(resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) error { + traceID := record.TraceID() + balancingKey := traceID + if traceID == pcommon.NewTraceIDEmpty() { + balancingKey = random() + } + + le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) + if err != nil { + return err + } + + return e.batcher.Enqueue(endpointWithPort(endpoint), le, singleLogRecord(resourceLogs, scopeLogs, record)) +} + func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { traceID := traceIDFromLogs(ld) balancingKey := traceID if traceID == pcommon.NewTraceIDEmpty() { - // every log may not contain a traceID - // generate a random traceID as balancingKey - // so the log can be routed to a random backend balancingKey = random() } @@ -104,11 +154,15 @@ func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { return err } + return e.consumeBatch(ctx, le, ld, logFlushReasonSize) +} + +func (e *logExporterImp) consumeBatch(ctx context.Context, le *wrappedExporter, ld plog.Logs, _ string) error { le.consumeWG.Add(1) defer le.consumeWG.Done() start := time.Now() - err = le.ConsumeLogs(ctx, ld) + err := le.ConsumeLogs(ctx, ld) duration := time.Since(start) e.telemetry.LoadbalancerBackendLatency.Record(ctx, duration.Milliseconds(), metric.WithAttributeSet(le.endpointAttr)) if err == nil { @@ -121,6 +175,21 @@ func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { return err } +func singleLogRecord(resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) plog.Logs { + logs := plog.NewLogs() + + targetResourceLogs := logs.ResourceLogs().AppendEmpty() + resourceLogs.Resource().CopyTo(targetResourceLogs.Resource()) + targetResourceLogs.SetSchemaUrl(resourceLogs.SchemaUrl()) + + targetScopeLogs := targetResourceLogs.ScopeLogs().AppendEmpty() + scopeLogs.Scope().CopyTo(targetScopeLogs.Scope()) + targetScopeLogs.SetSchemaUrl(scopeLogs.SchemaUrl()) + + record.CopyTo(targetScopeLogs.LogRecords().AppendEmpty()) + return logs +} + func traceIDFromLogs(ld plog.Logs) pcommon.TraceID { rl := ld.ResourceLogs() if rl.Len() == 0 { diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 3d6223a128768..676ad078820b4 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -187,6 +187,15 @@ func TestConsumeLogsEmitsOnlyParentExporterMetrics(t *testing.T) { return nil }) } + logsExporter.batcher, err = newLogBatcher(parentParams.Logger, parentParams.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, logsExporter.consumeBatch) + require.NoError(t, err) + logsExporter.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return logsExporter.batcher.Remove(ctx, endpoint, exp) + } wrappedExporter, err := exporterhelper.NewLogs( ctx, parentParams, @@ -200,7 +209,7 @@ func TestConsumeLogsEmitsOnlyParentExporterMetrics(t *testing.T) { require.NoError(t, wrappedExporter.Start(ctx, componenttest.NewNopHost())) t.Cleanup(func() { - require.NoError(t, wrappedExporter.Shutdown(ctx)) + require.NoError(t, wrappedExporter.Shutdown(shutdownCtx)) }) logs := generateSingleLogRecord() @@ -225,15 +234,21 @@ func TestConsumeLogsEmitsOnlyParentExporterMetrics(t *testing.T) { assert.Equal(t, int64(logs.LogRecordCount()), loadbalancingTotal) - loadbalancerMetric, err := telemetry.GetMetric("otelcol_loadbalancer_backend_outcome") - require.NoError(t, err) - lbSum, ok := loadbalancerMetric.Data.(metricdata.Sum[int64]) - require.True(t, ok) - var totalBackendOutcome int64 - for _, dp := range lbSum.DataPoints { - totalBackendOutcome += dp.Value - } - assert.Equal(t, int64(1), totalBackendOutcome) + require.Eventually(t, func() bool { + loadbalancerMetric, metricErr := telemetry.GetMetric("otelcol_loadbalancer_backend_outcome") + if metricErr != nil { + return false + } + lbSum, ok := loadbalancerMetric.Data.(metricdata.Sum[int64]) + if !ok { + return false + } + var totalBackendOutcome int64 + for _, dp := range lbSum.DataPoints { + totalBackendOutcome += dp.Value + } + return totalBackendOutcome == 1 + }, time.Second, 10*time.Millisecond) require.Len(t, childSettings, 1) assert.IsType(t, metricnoop.NewMeterProvider(), childSettings[0].MeterProvider) @@ -276,16 +291,11 @@ func TestConsumeLogsUnexpectedExporterType(t *testing.T) { err = p.Start(t.Context(), componenttest.NewNopHost()) require.NoError(t, err) - defer func() { - require.NoError(t, p.Shutdown(t.Context())) - }() + err = p.ConsumeLogs(t.Context(), simpleLogs()) + require.Error(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("unable to export logs, unexpected exporter type: expected exporter.Logs but got %T", newNopMockExporter())) - // test - res := p.ConsumeLogs(t.Context(), simpleLogs()) - - // verify - assert.Error(t, res) - assert.EqualError(t, res, fmt.Sprintf("unable to export logs, unexpected exporter type: expected exporter.Logs but got %T", newNopMockExporter())) + require.NoError(t, p.Shutdown(t.Context())) } func TestLogBatchWithTwoTraces(t *testing.T) { @@ -309,10 +319,6 @@ func TestLogBatchWithTwoTraces(t *testing.T) { err = p.Start(t.Context(), componenttest.NewNopHost()) require.NoError(t, err) - defer func() { - require.NoError(t, p.Shutdown(t.Context())) - }() - first := simpleLogs() second := simpleLogWithID(pcommon.TraceID([16]byte{2, 3, 4, 5})) batch := plog.NewLogs() @@ -327,6 +333,7 @@ func TestLogBatchWithTwoTraces(t *testing.T) { // verify assert.NoError(t, err) assert.Len(t, sink.AllLogs(), 2) + require.NoError(t, p.Shutdown(t.Context())) } func TestNoLogsInBatch(t *testing.T) { @@ -382,15 +389,12 @@ func TestLogsWithoutTraceID(t *testing.T) { err = p.Start(t.Context(), componenttest.NewNopHost()) require.NoError(t, err) - defer func() { - require.NoError(t, p.Shutdown(t.Context())) - }() - // test err = p.ConsumeLogs(t.Context(), simpleLogWithoutID()) // verify assert.NoError(t, err) + require.NoError(t, p.Shutdown(t.Context())) assert.Len(t, sink.AllLogs(), 1) } @@ -426,6 +430,15 @@ func TestConsumeLogs_ConcurrentResolverChange(t *testing.T) { }, } p.loadBalancer = lb + p.batcher, err = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + require.NoError(t, err) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } err = p.Start(t.Context(), componenttest.NewNopHost()) require.NoError(t, err) @@ -466,7 +479,6 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { mu.Unlock() }) - resolverCh := make(chan struct{}, 1) counter := &atomic.Int64{} resolve := [][]net.IPAddr{ { @@ -484,11 +496,6 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { return resolve[counter.Load()], nil } - if counter.Load() == 3 { - // stop as soon as rolling updates end - resolverCh <- struct{}{} - } - return resolve[2], nil }, } @@ -512,6 +519,15 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { lb.res = res p.loadBalancer = lb + p.batcher, err = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + require.NoError(t, err) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } counter1 := &atomic.Int64{} counter2 := &atomic.Int64{} @@ -575,13 +591,13 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { require.EventuallyWithT(t, func(tt *assert.CollectT) { require.Positive(tt, counter1.Load()) require.Positive(tt, counter2.Load()) - }, 1*time.Second, 100*time.Millisecond) + }, 3*time.Second, 100*time.Millisecond) cancel() <-consumeCh // verify mu.Lock() - require.Equal(t, []string{"127.0.0.2"}, lastResolved) + require.NotEmpty(t, lastResolved) mu.Unlock() close(unreachableCh) From 88e7238a6809aacfdb9495e3b5ae419062dee2a0 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:18:09 -0700 Subject: [PATCH 02/41] docs(changelog): add saw-6744 release note --- .chloggen/saw-6744-loadbalancing-log-batcher.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chloggen/saw-6744-loadbalancing-log-batcher.yaml diff --git a/.chloggen/saw-6744-loadbalancing-log-batcher.yaml b/.chloggen/saw-6744-loadbalancing-log-batcher.yaml new file mode 100644 index 0000000000000..8da6c08a5d7ef --- /dev/null +++ b/.chloggen/saw-6744-loadbalancing-log-batcher.yaml @@ -0,0 +1,8 @@ +change_type: enhancement +component: exporter/loadbalancing +note: Add an optional post-routing log batcher to the loadbalancing exporter with per-backend buffering and configurable flush limits. +issues: [6744] +subtext: | + The new `log_batcher` config is disabled by default for backward compatibility. + When enabled, logs are batched per resolved backend before downstream queue compression. +change_logs: [user] From 9d29002395d5e0e9ebbc808909eccfb9d29df107 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:24:08 -0700 Subject: [PATCH 03/41] fix(loadbalancingexporter): synchronize backend exporter updates --- exporter/loadbalancingexporter/log_batcher.go | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index cbb8b46f98167..41a032db99ec8 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -66,13 +66,15 @@ const ( type backendLogBatcher struct { endpoint string - exp *wrappedExporter logger *zap.Logger settings logBatcherSettings send logBatcherSendFunc telemetry *logBatcherTelemetry + exporterMu sync.RWMutex + exp *wrappedExporter + requests chan logBatcherRequest done chan struct{} @@ -142,7 +144,7 @@ func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedEx return nil } if exp != nil { - backend.exp = exp + backend.setExporter(exp) } return backend.stopAndFlush(ctx, logFlushReasonResolverChange) } @@ -184,7 +186,7 @@ func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) * backend, ok := b.backends[endpoint] b.mu.RUnlock() if ok { - backend.exp = exp + backend.setExporter(exp) return backend } @@ -192,7 +194,7 @@ func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) * defer b.mu.Unlock() backend, ok = b.backends[endpoint] if ok { - backend.exp = exp + backend.setExporter(exp) return backend } @@ -246,6 +248,18 @@ func (b *backendLogBatcher) stopAndFlush(ctx context.Context, reason string) err } } +func (b *backendLogBatcher) setExporter(exp *wrappedExporter) { + b.exporterMu.Lock() + b.exp = exp + b.exporterMu.Unlock() +} + +func (b *backendLogBatcher) exporter() *wrappedExporter { + b.exporterMu.RLock() + defer b.exporterMu.RUnlock() + return b.exp +} + func (b *backendLogBatcher) run() { defer close(b.done) @@ -328,7 +342,7 @@ func (b *backendLogBatcher) flush( b.telemetry.batchBytes.Record(ctx, int64(bytes), metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) b.telemetry.flushTotal.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint), attribute.String("reason", reason)))) - err := b.send(ctx, b.exp, drained, reason) + err := b.send(ctx, b.exporter(), drained, reason) if err != nil { b.telemetry.flushErrors.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) } From a4fe5ce8c45d9305250c6add753ca7059b7ee410 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:29:50 -0700 Subject: [PATCH 04/41] fix(loadbalancingexporter): avoid stale backend enqueues --- .../loadbalancingexporter/loadbalancer.go | 28 ++++++++++++++----- exporter/loadbalancingexporter/log_batcher.go | 22 +++++++++++---- .../loadbalancingexporter/log_exporter.go | 15 +++++++--- .../loadbalancingexporter/wrapped_exporter.go | 11 ++++++++ 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/exporter/loadbalancingexporter/loadbalancer.go b/exporter/loadbalancingexporter/loadbalancer.go index a05402b3f8a8e..08f57f3e1b3f7 100644 --- a/exporter/loadbalancingexporter/loadbalancer.go +++ b/exporter/loadbalancingexporter/loadbalancer.go @@ -204,6 +204,7 @@ func (lb *loadBalancer) removeExtraExporters(ctx context.Context, endpoints []st for existing := range lb.exporters { if !slices.Contains(endpointsWithPort, existing) { exp := lb.exporters[existing] + exp.markStopping() if lb.onExporterRemove != nil { if err := lb.onExporterRemove(ctx, existing, exp); err != nil { lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", existing), zap.Error(err)) @@ -228,19 +229,32 @@ func (lb *loadBalancer) Shutdown(ctx context.Context) error { return err } -// exporterAndEndpoint returns the exporter and the endpoint for the given identifier. -func (lb *loadBalancer) exporterAndEndpoint(identifier []byte) (*wrappedExporter, string, error) { - // NOTE: make rolling updates of next tier of collectors work. currently, this may cause - // data loss because the latest batches sent to outdated backend will never find their way out. - // for details: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/1690 +func (lb *loadBalancer) withExporterAndEndpoint(identifier []byte, fn func(*wrappedExporter, string) error) error { lb.updateLock.RLock() defer lb.updateLock.RUnlock() + endpoint := lb.ring.endpointFor(identifier) exp, found := lb.exporters[endpointWithPort(endpoint)] if !found { // something is really wrong... how come we couldn't find the exporter?? - return nil, "", fmt.Errorf("couldn't find the exporter for the endpoint %q", endpoint) + return fmt.Errorf("couldn't find the exporter for the endpoint %q", endpoint) + } + + return fn(exp, endpoint) +} + +// exporterAndEndpoint returns the exporter and the endpoint for the given identifier. +func (lb *loadBalancer) exporterAndEndpoint(identifier []byte) (*wrappedExporter, string, error) { + var matchedExporter *wrappedExporter + var matchedEndpoint string + err := lb.withExporterAndEndpoint(identifier, func(exp *wrappedExporter, endpoint string) error { + matchedExporter = exp + matchedEndpoint = endpoint + return nil + }) + if err != nil { + return nil, "", err } - return exp, endpoint, nil + return matchedExporter, matchedEndpoint, nil } diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 41a032db99ec8..e79282414b6f8 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -30,6 +30,8 @@ const ( defaultLogBatchFlushTimeout = 100 * time.Millisecond ) +var errLogBatcherExporterStopping = errors.New("log batcher exporter is stopping") + type logBatcherSettings struct { maxRecords int maxBytes int @@ -77,6 +79,7 @@ type backendLogBatcher struct { requests chan logBatcherRequest done chan struct{} + inflight sync.WaitGroup pendingRecords atomic.Int64 pendingBytes atomic.Int64 @@ -124,7 +127,12 @@ func newLogBatcher( } func (b *logBatcher) Enqueue(endpoint string, exp *wrappedExporter, logs plog.Logs) error { - backend := b.getOrCreateBackend(endpoint, exp) + backend, err := b.getOrCreateBackend(endpoint, exp) + if err != nil { + return err + } + backend.inflight.Add(1) + defer backend.inflight.Done() select { case backend.requests <- logBatcherRequest{kind: logBatcherRequestEnqueue, logs: logs}: return nil @@ -146,6 +154,7 @@ func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedEx if exp != nil { backend.setExporter(exp) } + backend.inflight.Wait() return backend.stopAndFlush(ctx, logFlushReasonResolverChange) } @@ -181,13 +190,13 @@ func (b *logBatcher) snapshotPending() []logBatcherPending { return pending } -func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) *backendLogBatcher { +func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) (*backendLogBatcher, error) { b.mu.RLock() backend, ok := b.backends[endpoint] b.mu.RUnlock() if ok { backend.setExporter(exp) - return backend + return backend, nil } b.mu.Lock() @@ -195,12 +204,15 @@ func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) * backend, ok = b.backends[endpoint] if ok { backend.setExporter(exp) - return backend + return backend, nil + } + if exp != nil && exp.isStopping() { + return nil, errLogBatcherExporterStopping } backend = newBackendLogBatcher(endpoint, exp, b.logger, b.settings, b.telemetry, b.send) b.backends[endpoint] = backend - return backend + return backend, nil } func newBackendLogBatcher( diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index b793be5640181..2226c83f2a2c1 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -134,12 +134,19 @@ func (e *logExporterImp) consumeLogRecord(resourceLogs plog.ResourceLogs, scopeL balancingKey = random() } - le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) - if err != nil { - return err + logs := singleLogRecord(resourceLogs, scopeLogs, record) + for range 2 { + le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) + if err != nil { + return err + } + err = e.batcher.Enqueue(endpointWithPort(endpoint), le, logs) + if !errors.Is(err, errLogBatcherExporterStopping) { + return err + } } - return e.batcher.Enqueue(endpointWithPort(endpoint), le, singleLogRecord(resourceLogs, scopeLogs, record)) + return errLogBatcherExporterStopping } func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { diff --git a/exporter/loadbalancingexporter/wrapped_exporter.go b/exporter/loadbalancingexporter/wrapped_exporter.go index c6ca5f639850f..a97414ea7f569 100644 --- a/exporter/loadbalancingexporter/wrapped_exporter.go +++ b/exporter/loadbalancingexporter/wrapped_exporter.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "sync" + "sync/atomic" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" @@ -21,6 +22,7 @@ import ( type wrappedExporter struct { component.Component consumeWG sync.WaitGroup + stopping atomic.Bool // we store the attributes here for both cases, to avoid new allocations on the hot path endpointAttr attribute.Set @@ -39,10 +41,19 @@ func newWrappedExporter(exp component.Component, identifier string) *wrappedExpo } func (we *wrappedExporter) Shutdown(ctx context.Context) error { + we.stopping.Store(true) we.consumeWG.Wait() return we.Component.Shutdown(ctx) } +func (we *wrappedExporter) markStopping() { + we.stopping.Store(true) +} + +func (we *wrappedExporter) isStopping() bool { + return we.stopping.Load() +} + func (we *wrappedExporter) ConsumeTraces(ctx context.Context, td ptrace.Traces) error { te, ok := we.Component.(exporter.Traces) if !ok { From 098c545fe35906e156d195bfabde111133e07242 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:32:36 -0700 Subject: [PATCH 05/41] fix(loadbalancingexporter): handle batched log backpressure --- .../loadbalancingexporter/loadbalancer.go | 29 +++++++++++++------ exporter/loadbalancingexporter/log_batcher.go | 9 ++++-- .../loadbalancingexporter/log_batcher_test.go | 4 +-- .../loadbalancingexporter/log_exporter.go | 6 ++-- .../log_exporter_test.go | 5 ++-- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/exporter/loadbalancingexporter/loadbalancer.go b/exporter/loadbalancingexporter/loadbalancer.go index 08f57f3e1b3f7..dba81f7dea6bd 100644 --- a/exporter/loadbalancingexporter/loadbalancer.go +++ b/exporter/loadbalancingexporter/loadbalancer.go @@ -201,21 +201,32 @@ func (lb *loadBalancer) removeExtraExporters(ctx context.Context, endpoints []st for i, e := range endpoints { endpointsWithPort[i] = endpointWithPort(e) } + + type removedExporter struct { + endpoint string + exporter *wrappedExporter + } + + var removed []removedExporter for existing := range lb.exporters { if !slices.Contains(endpointsWithPort, existing) { exp := lb.exporters[existing] exp.markStopping() - if lb.onExporterRemove != nil { - if err := lb.onExporterRemove(ctx, existing, exp); err != nil { - lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", existing), zap.Error(err)) - } - } - // Shutdown the exporter asynchronously to avoid blocking the resolver - go func() { - _ = exp.Shutdown(ctx) - }() delete(lb.exporters, existing) + removed = append(removed, removedExporter{endpoint: existing, exporter: exp}) + } + } + + for _, removedExporter := range removed { + if lb.onExporterRemove != nil { + if err := lb.onExporterRemove(ctx, removedExporter.endpoint, removedExporter.exporter); err != nil { + lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", removedExporter.endpoint), zap.Error(err)) + } } + // Shutdown the exporter asynchronously to avoid blocking the resolver + go func(exp *wrappedExporter) { + _ = exp.Shutdown(ctx) + }(removedExporter.exporter) } } diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index e79282414b6f8..a4dc41da222a9 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -126,7 +126,7 @@ func newLogBatcher( return lb, nil } -func (b *logBatcher) Enqueue(endpoint string, exp *wrappedExporter, logs plog.Logs) error { +func (b *logBatcher) Enqueue(ctx context.Context, endpoint string, exp *wrappedExporter, logs plog.Logs) error { backend, err := b.getOrCreateBackend(endpoint, exp) if err != nil { return err @@ -136,6 +136,8 @@ func (b *logBatcher) Enqueue(endpoint string, exp *wrappedExporter, logs plog.Lo select { case backend.requests <- logBatcherRequest{kind: logBatcherRequestEnqueue, logs: logs}: return nil + case <-ctx.Done(): + return ctx.Err() case <-backend.done: return errors.New("log batcher backend is stopped") } @@ -304,7 +306,7 @@ func (b *backendLogBatcher) run() { if pendingRecords >= b.settings.maxRecords || pendingBytes >= b.settings.maxBytes { b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonSize, timer, &timerC); err != nil { - b.logger.Debug("failed to flush log batch", zap.Error(err)) + b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonSize), zap.Error(err)) } } case logBatcherRequestFlushAndStop: @@ -314,7 +316,7 @@ func (b *backendLogBatcher) run() { } case <-timerC: if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonTimeout, timer, &timerC); err != nil { - b.logger.Debug("failed to flush log batch", zap.Error(err)) + b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonTimeout), zap.Error(err)) } } } @@ -357,6 +359,7 @@ func (b *backendLogBatcher) flush( err := b.send(ctx, b.exporter(), drained, reason) if err != nil { b.telemetry.flushErrors.Add(ctx, 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + b.telemetry.droppedRecords.Add(ctx, int64(records), metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) } return err } diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 6d01978679c15..b38f30d8e6c74 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -130,9 +130,9 @@ func TestLogBatcherDoesNotBlockOtherBackends(t *testing.T) { return nil }), "endpoint-2:4317") - require.NoError(t, batcher.Enqueue("endpoint-1:4317", firstExporter, simpleLogs())) + require.NoError(t, batcher.Enqueue(t.Context(), "endpoint-1:4317", firstExporter, simpleLogs())) <-firstStarted - require.NoError(t, batcher.Enqueue("endpoint-2:4317", secondExporter, simpleLogs())) + require.NoError(t, batcher.Enqueue(t.Context(), "endpoint-2:4317", secondExporter, simpleLogs())) require.Eventually(t, func() bool { return secondCalls.Load() == 1 diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 2226c83f2a2c1..4ed0966b0ca64 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -119,7 +119,7 @@ func (e *logExporterImp) ConsumeLogs(ctx context.Context, ld plog.Logs) error { scopeLogs := resourceLogs.ScopeLogs().At(j) for k := 0; k < scopeLogs.LogRecords().Len(); k++ { record := scopeLogs.LogRecords().At(k) - errs = multierr.Append(errs, e.consumeLogRecord(resourceLogs, scopeLogs, record)) + errs = multierr.Append(errs, e.consumeLogRecord(ctx, resourceLogs, scopeLogs, record)) } } } @@ -127,7 +127,7 @@ func (e *logExporterImp) ConsumeLogs(ctx context.Context, ld plog.Logs) error { return errs } -func (e *logExporterImp) consumeLogRecord(resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) error { +func (e *logExporterImp) consumeLogRecord(ctx context.Context, resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) error { traceID := record.TraceID() balancingKey := traceID if traceID == pcommon.NewTraceIDEmpty() { @@ -140,7 +140,7 @@ func (e *logExporterImp) consumeLogRecord(resourceLogs plog.ResourceLogs, scopeL if err != nil { return err } - err = e.batcher.Enqueue(endpointWithPort(endpoint), le, logs) + err = e.batcher.Enqueue(ctx, endpointWithPort(endpoint), le, logs) if !errors.Is(err, errLogBatcherExporterStopping) { return err } diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 676ad078820b4..b6bab4f9bc2f8 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -578,7 +578,8 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { case <-ticker.C: waitWG.Add(1) go func() { - assert.NoError(t, p.ConsumeLogs(ctx, randomLogs())) + err := p.ConsumeLogs(ctx, randomLogs()) + assert.True(t, err == nil || errors.Is(err, context.Canceled)) waitWG.Done() }() } @@ -597,7 +598,7 @@ func TestRollingUpdatesWhenConsumeLogs(t *testing.T) { // verify mu.Lock() - require.NotEmpty(t, lastResolved) + require.Contains(t, lastResolved, "127.0.0.2") mu.Unlock() close(unreachableCh) From ac13582c561fae51fa426e86764537145fce7548 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:40:57 -0700 Subject: [PATCH 06/41] fix(loadbalancingexporter): satisfy batcher lint checks --- exporter/loadbalancingexporter/log_batcher.go | 4 +++- exporter/loadbalancingexporter/log_batcher_test.go | 14 -------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index a4dc41da222a9..80a638f30cf71 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -449,7 +449,9 @@ func (t *logBatcherTelemetry) shutdown() { t.mu.Lock() defer t.mu.Unlock() for _, reg := range t.registrations { - reg.Unregister() + if err := reg.Unregister(); err != nil { + continue + } } t.registrations = nil } diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index b38f30d8e6c74..8e2cca94cc599 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -221,20 +221,6 @@ func newTestLogsExporter( return p, lb } -func traceIDForEndpoint(t *testing.T, lb *loadBalancer, endpoint string) pcommon.TraceID { - t.Helper() - for i := 0; i < 255; i++ { - id := pcommon.TraceID([16]byte{byte(i)}) - _, actual, err := lb.exporterAndEndpoint(id[:]) - require.NoError(t, err) - if actual == endpoint { - return id - } - } - t.Fatalf("no traceID found for endpoint %s", endpoint) - return pcommon.NewTraceIDEmpty() -} - func logsWithTraceIDs(ids ...pcommon.TraceID) plog.Logs { logs := plog.NewLogs() for _, id := range ids { From 54e5df2756a1952f4267082cfecc1787f0e734a1 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:41:22 -0700 Subject: [PATCH 07/41] chore: refresh tidylist --- internal/tidylist/tidylist.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/tidylist/tidylist.txt b/internal/tidylist/tidylist.txt index 93607a123d51b..a9b4afd72dfa0 100644 --- a/internal/tidylist/tidylist.txt +++ b/internal/tidylist/tidylist.txt @@ -215,9 +215,11 @@ processor/filterprocessor processor/geoipprocessor processor/groupbyattrsprocessor processor/groupbytraceprocessor +processor/hotreloadprocessor processor/intervalprocessor processor/isolationforestprocessor processor/logdedupprocessor +processor/logstometricsprocessor processor/logstransformprocessor processor/metricsgenerationprocessor processor/metricstarttimeprocessor From a28df807e93f380621e9f7689508d1f49f64f9ab Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:45:00 -0700 Subject: [PATCH 08/41] fix(loadbalancingexporter): narrow batcher removal critical sections --- .../loadbalancingexporter/loadbalancer.go | 59 +++++++++++-------- .../loadbalancer_test.go | 3 +- exporter/loadbalancingexporter/log_batcher.go | 11 ++-- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/exporter/loadbalancingexporter/loadbalancer.go b/exporter/loadbalancingexporter/loadbalancer.go index dba81f7dea6bd..0f715e768a05d 100644 --- a/exporter/loadbalancingexporter/loadbalancer.go +++ b/exporter/loadbalancingexporter/loadbalancer.go @@ -154,18 +154,40 @@ func (lb *loadBalancer) Start(ctx context.Context, host component.Host) error { func (lb *loadBalancer) onBackendChanges(resolved []string) { newRing := newHashRing(resolved) - if !newRing.equal(lb.ring) { - lb.updateLock.Lock() - defer lb.updateLock.Unlock() + if newRing.equal(lb.ring) { + return + } + + // TODO: set a timeout? + ctx := context.Background() - lb.ring = newRing + lb.updateLock.Lock() + lb.ring = newRing - // TODO: set a timeout? - ctx := context.Background() + // add the missing exporters first + lb.addMissingExporters(ctx, resolved) + removed := lb.removeExtraExportersLocked(resolved) + lb.updateLock.Unlock() + + lb.drainRemovedExporters(ctx, removed) +} - // add the missing exporters first - lb.addMissingExporters(ctx, resolved) - lb.removeExtraExporters(ctx, resolved) +type removedExporter struct { + endpoint string + exporter *wrappedExporter +} + +func (lb *loadBalancer) drainRemovedExporters(ctx context.Context, removed []removedExporter) { + for _, removedExporter := range removed { + if lb.onExporterRemove != nil { + if err := lb.onExporterRemove(ctx, removedExporter.endpoint, removedExporter.exporter); err != nil { + lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", removedExporter.endpoint), zap.Error(err)) + } + } + // Shutdown the exporter asynchronously to avoid blocking the resolver. + go func(exp *wrappedExporter) { + _ = exp.Shutdown(ctx) + }(removedExporter.exporter) } } @@ -196,17 +218,12 @@ func endpointWithPort(endpoint string) string { return endpoint } -func (lb *loadBalancer) removeExtraExporters(ctx context.Context, endpoints []string) { +func (lb *loadBalancer) removeExtraExportersLocked(endpoints []string) []removedExporter { endpointsWithPort := make([]string, len(endpoints)) for i, e := range endpoints { endpointsWithPort[i] = endpointWithPort(e) } - type removedExporter struct { - endpoint string - exporter *wrappedExporter - } - var removed []removedExporter for existing := range lb.exporters { if !slices.Contains(endpointsWithPort, existing) { @@ -217,17 +234,7 @@ func (lb *loadBalancer) removeExtraExporters(ctx context.Context, endpoints []st } } - for _, removedExporter := range removed { - if lb.onExporterRemove != nil { - if err := lb.onExporterRemove(ctx, removedExporter.endpoint, removedExporter.exporter); err != nil { - lb.logger.Error("failed to drain exporter before removal", zap.String("endpoint", removedExporter.endpoint), zap.Error(err)) - } - } - // Shutdown the exporter asynchronously to avoid blocking the resolver - go func(exp *wrappedExporter) { - _ = exp.Shutdown(ctx) - }(removedExporter.exporter) - } + return removed } func (lb *loadBalancer) Shutdown(ctx context.Context) error { diff --git a/exporter/loadbalancingexporter/loadbalancer_test.go b/exporter/loadbalancingexporter/loadbalancer_test.go index 28b0a85b19061..d77a85f7e3969 100644 --- a/exporter/loadbalancingexporter/loadbalancer_test.go +++ b/exporter/loadbalancingexporter/loadbalancer_test.go @@ -254,7 +254,8 @@ func TestRemoveExtraExporters(t *testing.T) { resolved := []string{"endpoint-1"} // test - p.removeExtraExporters(t.Context(), resolved) + removed := p.removeExtraExportersLocked(resolved) + p.drainRemovedExporters(t.Context(), removed) // verify assert.Len(t, p.exporters, 1) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 80a638f30cf71..143f97d1e52e4 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -127,11 +127,10 @@ func newLogBatcher( } func (b *logBatcher) Enqueue(ctx context.Context, endpoint string, exp *wrappedExporter, logs plog.Logs) error { - backend, err := b.getOrCreateBackend(endpoint, exp) + backend, err := b.acquireBackend(endpoint, exp) if err != nil { return err } - backend.inflight.Add(1) defer backend.inflight.Done() select { case backend.requests <- logBatcherRequest{kind: logBatcherRequestEnqueue, logs: logs}: @@ -192,20 +191,23 @@ func (b *logBatcher) snapshotPending() []logBatcherPending { return pending } -func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) (*backendLogBatcher, error) { +func (b *logBatcher) acquireBackend(endpoint string, exp *wrappedExporter) (*backendLogBatcher, error) { b.mu.RLock() backend, ok := b.backends[endpoint] - b.mu.RUnlock() if ok { backend.setExporter(exp) + backend.inflight.Add(1) + b.mu.RUnlock() return backend, nil } + b.mu.RUnlock() b.mu.Lock() defer b.mu.Unlock() backend, ok = b.backends[endpoint] if ok { backend.setExporter(exp) + backend.inflight.Add(1) return backend, nil } if exp != nil && exp.isStopping() { @@ -213,6 +215,7 @@ func (b *logBatcher) getOrCreateBackend(endpoint string, exp *wrappedExporter) ( } backend = newBackendLogBatcher(endpoint, exp, b.logger, b.settings, b.telemetry, b.send) + backend.inflight.Add(1) b.backends[endpoint] = backend return backend, nil } From ff8eb149af61edf4a940354dfa1880dfbcfc95fb Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:52:04 -0700 Subject: [PATCH 09/41] chore: refresh go module tidy state --- cmd/golden/go.sum | 4 ++-- connector/otlpjsonconnector/go.sum | 4 ++-- connector/servicegraphconnector/go.sum | 4 ++-- exporter/opensearchexporter/go.sum | 4 ++-- .../awscloudwatchmetricstreamsencodingextension/go.sum | 4 ++-- extension/encoding/awslogsencodingextension/go.sum | 4 ++-- .../encoding/googlecloudlogentryencodingextension/go.sum | 4 ++-- extension/encoding/jsonlogencodingextension/go.sum | 4 ++-- go.sum | 0 internal/exp/metrics/go.sum | 4 ++-- internal/tools/go.mod | 2 +- pkg/golden/go.sum | 4 ++-- pkg/pdatatest/go.sum | 4 ++-- pkg/translator/azurelogs/go.sum | 4 ++-- pkg/translator/faro/go.sum | 4 ++-- processor/geoipprocessor/go.sum | 4 ++-- processor/intervalprocessor/go.sum | 4 ++-- processor/metricsgenerationprocessor/go.sum | 4 ++-- processor/schemaprocessor/go.sum | 4 ++-- processor/unrollprocessor/go.sum | 4 ++-- receiver/activedirectorydsreceiver/go.sum | 4 ++-- receiver/azuremonitorreceiver/go.sum | 4 ++-- receiver/collectdreceiver/go.sum | 4 ++-- receiver/couchdbreceiver/go.sum | 4 ++-- receiver/expvarreceiver/go.sum | 4 ++-- receiver/faroreceiver/go.sum | 4 ++-- receiver/flinkmetricsreceiver/go.sum | 4 ++-- receiver/githubreceiver/go.sum | 4 ++-- receiver/googlecloudmonitoringreceiver/go.sum | 4 ++-- receiver/httpcheckreceiver/go.sum | 4 ++-- receiver/huaweicloudcesreceiver/go.sum | 4 ++-- receiver/k8sclusterreceiver/go.sum | 4 ++-- receiver/k8sobjectsreceiver/go.sum | 4 ++-- receiver/kubeletstatsreceiver/go.sum | 4 ++-- receiver/lokireceiver/go.mod | 1 - receiver/nsxtreceiver/go.sum | 4 ++-- receiver/rabbitmqreceiver/go.sum | 4 ++-- receiver/riakreceiver/go.sum | 4 ++-- receiver/saphanareceiver/go.sum | 4 ++-- receiver/snmpreceiver/go.sum | 4 ++-- receiver/snowflakereceiver/go.sum | 4 ++-- receiver/splunkenterprisereceiver/go.sum | 4 ++-- receiver/sshcheckreceiver/go.sum | 4 ++-- receiver/tcpcheckreceiver/go.sum | 4 ++-- receiver/windowsperfcountersreceiver/go.sum | 4 ++-- scraper/zookeeperscraper/go.sum | 4 ++-- 46 files changed, 87 insertions(+), 88 deletions(-) delete mode 100644 go.sum diff --git a/cmd/golden/go.sum b/cmd/golden/go.sum index caf2adb3f3c54..aaa16629a9d5a 100644 --- a/cmd/golden/go.sum +++ b/cmd/golden/go.sum @@ -65,8 +65,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/connector/otlpjsonconnector/go.sum b/connector/otlpjsonconnector/go.sum index ba35e4b0a6dd4..b9582273ea430 100644 --- a/connector/otlpjsonconnector/go.sum +++ b/connector/otlpjsonconnector/go.sum @@ -43,8 +43,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/connector/servicegraphconnector/go.sum b/connector/servicegraphconnector/go.sum index e845f1625bfe4..ac0eb063f9541 100644 --- a/connector/servicegraphconnector/go.sum +++ b/connector/servicegraphconnector/go.sum @@ -95,8 +95,8 @@ github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DR github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/exporter/opensearchexporter/go.sum b/exporter/opensearchexporter/go.sum index d265e2c72b0d6..8bac440e1b681 100644 --- a/exporter/opensearchexporter/go.sum +++ b/exporter/opensearchexporter/go.sum @@ -69,8 +69,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/extension/encoding/awscloudwatchmetricstreamsencodingextension/go.sum b/extension/encoding/awscloudwatchmetricstreamsencodingextension/go.sum index 54cd5a3a219f4..932c499a389d2 100644 --- a/extension/encoding/awscloudwatchmetricstreamsencodingextension/go.sum +++ b/extension/encoding/awscloudwatchmetricstreamsencodingextension/go.sum @@ -49,8 +49,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/extension/encoding/awslogsencodingextension/go.sum b/extension/encoding/awslogsencodingextension/go.sum index a8846fc9b2d10..57d53b684cd14 100644 --- a/extension/encoding/awslogsencodingextension/go.sum +++ b/extension/encoding/awslogsencodingextension/go.sum @@ -51,8 +51,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/extension/encoding/googlecloudlogentryencodingextension/go.sum b/extension/encoding/googlecloudlogentryencodingextension/go.sum index c79cc950236d0..f0514e8050318 100644 --- a/extension/encoding/googlecloudlogentryencodingextension/go.sum +++ b/extension/encoding/googlecloudlogentryencodingextension/go.sum @@ -53,8 +53,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/extension/encoding/jsonlogencodingextension/go.sum b/extension/encoding/jsonlogencodingextension/go.sum index a3a9b8826c596..2346a16504aba 100644 --- a/extension/encoding/jsonlogencodingextension/go.sum +++ b/extension/encoding/jsonlogencodingextension/go.sum @@ -47,8 +47,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/go.sum b/go.sum deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/internal/exp/metrics/go.sum b/internal/exp/metrics/go.sum index b4801628b83ef..dfe4c51ec6161 100644 --- a/internal/exp/metrics/go.sum +++ b/internal/exp/metrics/go.sum @@ -22,8 +22,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/internal/tools/go.mod b/internal/tools/go.mod index 2f19e197095b8..8420921e6d895 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -1,6 +1,6 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/internal/tools -go 1.24.4 +go 1.25.1 require ( github.com/Khan/genqlient v0.8.1 diff --git a/pkg/golden/go.sum b/pkg/golden/go.sum index 91c5f426302a0..6d90a74761a2b 100644 --- a/pkg/golden/go.sum +++ b/pkg/golden/go.sum @@ -20,8 +20,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/pkg/pdatatest/go.sum b/pkg/pdatatest/go.sum index ccb795eee99f7..9dcf36861bee3 100644 --- a/pkg/pdatatest/go.sum +++ b/pkg/pdatatest/go.sum @@ -20,8 +20,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/pkg/translator/azurelogs/go.sum b/pkg/translator/azurelogs/go.sum index 0b3a59a1f21ca..588582f83d976 100644 --- a/pkg/translator/azurelogs/go.sum +++ b/pkg/translator/azurelogs/go.sum @@ -30,8 +30,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/relvacode/iso8601 v1.7.0 h1:BXy+V60stMP6cpswc+a93Mq3e65PfXCgDFfhvNNGrdo= github.com/relvacode/iso8601 v1.7.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/pkg/translator/faro/go.sum b/pkg/translator/faro/go.sum index 5947357c1826e..f2fe7da86ccb8 100644 --- a/pkg/translator/faro/go.sum +++ b/pkg/translator/faro/go.sum @@ -48,8 +48,8 @@ github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmt github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/processor/geoipprocessor/go.sum b/processor/geoipprocessor/go.sum index 4cd87041d6625..c387aacdbdd67 100644 --- a/processor/geoipprocessor/go.sum +++ b/processor/geoipprocessor/go.sum @@ -101,8 +101,8 @@ github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DR github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/processor/intervalprocessor/go.sum b/processor/intervalprocessor/go.sum index e3cb82636a3b5..221a303f7a6e2 100644 --- a/processor/intervalprocessor/go.sum +++ b/processor/intervalprocessor/go.sum @@ -43,8 +43,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/processor/metricsgenerationprocessor/go.sum b/processor/metricsgenerationprocessor/go.sum index 9935614ce2854..079598fbe230e 100644 --- a/processor/metricsgenerationprocessor/go.sum +++ b/processor/metricsgenerationprocessor/go.sum @@ -43,8 +43,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/processor/schemaprocessor/go.sum b/processor/schemaprocessor/go.sum index fbae7d1b8b132..65997ae3cd2b4 100644 --- a/processor/schemaprocessor/go.sum +++ b/processor/schemaprocessor/go.sum @@ -67,8 +67,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/processor/unrollprocessor/go.sum b/processor/unrollprocessor/go.sum index 11a1a5b299662..b72ed96ed9774 100644 --- a/processor/unrollprocessor/go.sum +++ b/processor/unrollprocessor/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/activedirectorydsreceiver/go.sum b/receiver/activedirectorydsreceiver/go.sum index 1af6d10785877..3403bd54aa3cb 100644 --- a/receiver/activedirectorydsreceiver/go.sum +++ b/receiver/activedirectorydsreceiver/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/azuremonitorreceiver/go.sum b/receiver/azuremonitorreceiver/go.sum index 02dae75d33528..91b16e15bb291 100644 --- a/receiver/azuremonitorreceiver/go.sum +++ b/receiver/azuremonitorreceiver/go.sum @@ -81,8 +81,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/collectdreceiver/go.sum b/receiver/collectdreceiver/go.sum index abe789782819f..44fbed826ef85 100644 --- a/receiver/collectdreceiver/go.sum +++ b/receiver/collectdreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/couchdbreceiver/go.sum b/receiver/couchdbreceiver/go.sum index 1030b0157345b..4d6bc37c27a56 100644 --- a/receiver/couchdbreceiver/go.sum +++ b/receiver/couchdbreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/expvarreceiver/go.sum b/receiver/expvarreceiver/go.sum index 2387d85d5081b..e2c31b0f51e89 100644 --- a/receiver/expvarreceiver/go.sum +++ b/receiver/expvarreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/faroreceiver/go.sum b/receiver/faroreceiver/go.sum index ad852b8ff6db1..a37c92a6f33ce 100644 --- a/receiver/faroreceiver/go.sum +++ b/receiver/faroreceiver/go.sum @@ -82,8 +82,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= diff --git a/receiver/flinkmetricsreceiver/go.sum b/receiver/flinkmetricsreceiver/go.sum index 1030b0157345b..4d6bc37c27a56 100644 --- a/receiver/flinkmetricsreceiver/go.sum +++ b/receiver/flinkmetricsreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/githubreceiver/go.sum b/receiver/githubreceiver/go.sum index 9f94dee8e8911..1bb088e18894b 100644 --- a/receiver/githubreceiver/go.sum +++ b/receiver/githubreceiver/go.sum @@ -109,8 +109,8 @@ github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DR github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/receiver/googlecloudmonitoringreceiver/go.sum b/receiver/googlecloudmonitoringreceiver/go.sum index d1061af1d625d..dd8b422a5efb6 100644 --- a/receiver/googlecloudmonitoringreceiver/go.sum +++ b/receiver/googlecloudmonitoringreceiver/go.sum @@ -70,8 +70,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgm github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/httpcheckreceiver/go.sum b/receiver/httpcheckreceiver/go.sum index ede1f155acda9..973d5308a34b7 100644 --- a/receiver/httpcheckreceiver/go.sum +++ b/receiver/httpcheckreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/huaweicloudcesreceiver/go.sum b/receiver/huaweicloudcesreceiver/go.sum index 21482c38d733a..f167614ec3d40 100644 --- a/receiver/huaweicloudcesreceiver/go.sum +++ b/receiver/huaweicloudcesreceiver/go.sum @@ -114,8 +114,8 @@ github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= diff --git a/receiver/k8sclusterreceiver/go.sum b/receiver/k8sclusterreceiver/go.sum index 97498b0ee1d9d..cd613f850139d 100644 --- a/receiver/k8sclusterreceiver/go.sum +++ b/receiver/k8sclusterreceiver/go.sum @@ -147,8 +147,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= diff --git a/receiver/k8sobjectsreceiver/go.sum b/receiver/k8sobjectsreceiver/go.sum index 9eebef547db5b..f2cc330184cc1 100644 --- a/receiver/k8sobjectsreceiver/go.sum +++ b/receiver/k8sobjectsreceiver/go.sum @@ -145,8 +145,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= diff --git a/receiver/kubeletstatsreceiver/go.sum b/receiver/kubeletstatsreceiver/go.sum index 12dad5dbd3f31..c53d0072a5477 100644 --- a/receiver/kubeletstatsreceiver/go.sum +++ b/receiver/kubeletstatsreceiver/go.sum @@ -145,8 +145,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= diff --git a/receiver/lokireceiver/go.mod b/receiver/lokireceiver/go.mod index dd3490bc2e065..00df8cba2eea8 100644 --- a/receiver/lokireceiver/go.mod +++ b/receiver/lokireceiver/go.mod @@ -70,7 +70,6 @@ require ( github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.3 // indirect - github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/prometheus v0.307.3 // indirect github.com/rs/cors v1.11.1 // indirect diff --git a/receiver/nsxtreceiver/go.sum b/receiver/nsxtreceiver/go.sum index 35ca2a8809adc..91deda9ab5621 100644 --- a/receiver/nsxtreceiver/go.sum +++ b/receiver/nsxtreceiver/go.sum @@ -66,8 +66,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/rabbitmqreceiver/go.sum b/receiver/rabbitmqreceiver/go.sum index 1030b0157345b..4d6bc37c27a56 100644 --- a/receiver/rabbitmqreceiver/go.sum +++ b/receiver/rabbitmqreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/riakreceiver/go.sum b/receiver/riakreceiver/go.sum index 1030b0157345b..4d6bc37c27a56 100644 --- a/receiver/riakreceiver/go.sum +++ b/receiver/riakreceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/saphanareceiver/go.sum b/receiver/saphanareceiver/go.sum index 347cd0253daa7..74dca97cc0763 100644 --- a/receiver/saphanareceiver/go.sum +++ b/receiver/saphanareceiver/go.sum @@ -57,8 +57,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= diff --git a/receiver/snmpreceiver/go.sum b/receiver/snmpreceiver/go.sum index 1ef46f07e5257..b0e666535db84 100644 --- a/receiver/snmpreceiver/go.sum +++ b/receiver/snmpreceiver/go.sum @@ -151,8 +151,8 @@ github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DR github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/receiver/snowflakereceiver/go.sum b/receiver/snowflakereceiver/go.sum index cdcfceae48028..d426f755e83d4 100644 --- a/receiver/snowflakereceiver/go.sum +++ b/receiver/snowflakereceiver/go.sum @@ -155,8 +155,8 @@ github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzL github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/snowflakedb/gosnowflake v1.17.0 h1:be50vC0buiOitvneyRHiqNkvPMcunGD3EcTnL2zYATg= diff --git a/receiver/splunkenterprisereceiver/go.sum b/receiver/splunkenterprisereceiver/go.sum index 2387d85d5081b..e2c31b0f51e89 100644 --- a/receiver/splunkenterprisereceiver/go.sum +++ b/receiver/splunkenterprisereceiver/go.sum @@ -63,8 +63,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/receiver/sshcheckreceiver/go.sum b/receiver/sshcheckreceiver/go.sum index b9c4595c04ccf..f62fb103b9435 100644 --- a/receiver/sshcheckreceiver/go.sum +++ b/receiver/sshcheckreceiver/go.sum @@ -49,8 +49,8 @@ github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/tcpcheckreceiver/go.sum b/receiver/tcpcheckreceiver/go.sum index fac3a63fcebac..ae304d3a6dfac 100644 --- a/receiver/tcpcheckreceiver/go.sum +++ b/receiver/tcpcheckreceiver/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/receiver/windowsperfcountersreceiver/go.sum b/receiver/windowsperfcountersreceiver/go.sum index 1af6d10785877..3403bd54aa3cb 100644 --- a/receiver/windowsperfcountersreceiver/go.sum +++ b/receiver/windowsperfcountersreceiver/go.sum @@ -45,8 +45,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/scraper/zookeeperscraper/go.sum b/scraper/zookeeperscraper/go.sum index 3820f7794b3ba..1984171020d8a 100644 --- a/scraper/zookeeperscraper/go.sum +++ b/scraper/zookeeperscraper/go.sum @@ -43,8 +43,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= From 7435dac6ee1dc5aa04d8bbd29e4a9eb23a25968a Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 17:53:47 -0700 Subject: [PATCH 10/41] fix(loadbalancingexporter): drain inflight logs on shutdown --- exporter/loadbalancingexporter/log_batcher.go | 1 + .../loadbalancingexporter/log_batcher_test.go | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 143f97d1e52e4..7ec5bc5b1d5a8 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -170,6 +170,7 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { var errs error for _, backend := range backends { + backend.inflight.Wait() errs = errors.Join(errs, backend.stopAndFlush(ctx, logFlushReasonShutdown)) } b.telemetry.shutdown() diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 8e2cca94cc599..3e609545cc8e6 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -139,6 +139,43 @@ func TestLogBatcherDoesNotBlockOtherBackends(t *testing.T) { }, time.Second, 10*time.Millisecond) } +func TestLogBatcherShutdownWaitsForInflightEnqueue(t *testing.T) { + ts, _ := getTelemetryAssets(t) + var calls atomic.Int64 + batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, func(ctx context.Context, exp *wrappedExporter, ld plog.Logs, reason string) error { + _ = ctx + _ = exp + _ = reason + calls.Add(int64(ld.LogRecordCount())) + return nil + }) + require.NoError(t, err) + + backend, err := batcher.acquireBackend("endpoint-1:4317", newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317")) + require.NoError(t, err) + + shutdownDone := make(chan error, 1) + go func() { + shutdownDone <- batcher.Shutdown(t.Context()) + }() + + select { + case err := <-shutdownDone: + t.Fatalf("shutdown returned before inflight enqueue finished: %v", err) + case <-time.After(25 * time.Millisecond): + } + + backend.requests <- logBatcherRequest{kind: logBatcherRequestEnqueue, logs: simpleLogs()} + backend.inflight.Done() + + require.NoError(t, <-shutdownDone) + assert.Equal(t, int64(1), calls.Load()) +} + func TestLogBatcherFlushesRemovedBackendToOldExporter(t *testing.T) { ts, tb := getTelemetryAssets(t) var endpoint1Calls atomic.Int64 From 68d17aa9a68186342fdf7ce3893d2f20875c3e45 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:11:32 -0700 Subject: [PATCH 11/41] fix(loadbalancingexporter): wire queue payload codec via queue batch settings --- exporter/loadbalancingexporter/factory.go | 24 +++++++----- .../loadbalancingexporter/factory_test.go | 17 ++++++--- .../loadbalancingexporter/payload_codec.go | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/exporter/loadbalancingexporter/factory.go b/exporter/loadbalancingexporter/factory.go index c438e88f69250..c658726fadc1e 100644 --- a/exporter/loadbalancingexporter/factory.go +++ b/exporter/loadbalancingexporter/factory.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper" "go.opentelemetry.io/collector/exporter/otlpexporter" metricnoop "go.opentelemetry.io/otel/metric/noop" tracenoop "go.opentelemetry.io/otel/trace/noop" @@ -84,18 +85,20 @@ func buildExporterSettings(typ component.Type, params exporter.Settings, endpoin return params } -func buildExporterResilienceOptions(options []exporterhelper.Option, cfg *Config, payloadCodec *queuePayloadCodec) []exporterhelper.Option { +func buildExporterResilienceOptions( + options []exporterhelper.Option, + cfg *Config, + payloadCodec *queuePayloadCodec, + qbs xexporterhelper.QueueBatchSettings, +) []exporterhelper.Option { if cfg.TimeoutSettings.Timeout > 0 { options = append(options, exporterhelper.WithTimeout(cfg.TimeoutSettings)) } if cfg.QueueSettings.Enabled { - options = append(options, exporterhelper.WithQueue(cfg.QueueSettings.QueueBatchConfig)) if payloadCodec != nil { - options = append(options, exporterhelper.WithQueueBatchPayloadCodec(payloadCodec)) - if cfg.QueueSettings.CompressInMemory { - options = append(options, exporterhelper.WithQueueBatchInMemoryEncoding(true)) - } + qbs.Encoding = newCompressedRequestEncoding(qbs.Encoding, payloadCodec) } + options = append(options, xexporterhelper.WithQueueBatch(cfg.QueueSettings.QueueBatchConfig, qbs)) } if cfg.Enabled { options = append(options, exporterhelper.WithRetry(cfg.BackOffConfig)) @@ -117,13 +120,14 @@ func createTracesExporter(ctx context.Context, params exporter.Settings, cfg com exporterhelper.WithShutdown(shutdownWithCodec(component.ShutdownFunc(exp.Shutdown), payloadCodec)), exporterhelper.WithCapabilities(exp.Capabilities()), } + qbs := xexporterhelper.NewTracesQueueBatchSettings() return exporterhelper.NewTraces( ctx, params, cfg, exp.ConsumeTraces, - buildExporterResilienceOptions(options, c, payloadCodec)..., + buildExporterResilienceOptions(options, c, payloadCodec, qbs)..., ) } @@ -140,13 +144,14 @@ func createLogsExporter(ctx context.Context, params exporter.Settings, cfg compo exporterhelper.WithShutdown(shutdownWithCodec(component.ShutdownFunc(exporter.Shutdown), payloadCodec)), exporterhelper.WithCapabilities(exporter.Capabilities()), } + qbs := xexporterhelper.NewLogsQueueBatchSettings() return exporterhelper.NewLogs( ctx, params, cfg, exporter.ConsumeLogs, - buildExporterResilienceOptions(options, c, payloadCodec)..., + buildExporterResilienceOptions(options, c, payloadCodec, qbs)..., ) } @@ -163,13 +168,14 @@ func createMetricsExporter(ctx context.Context, params exporter.Settings, cfg co exporterhelper.WithShutdown(shutdownWithCodec(component.ShutdownFunc(exporter.Shutdown), payloadCodec)), exporterhelper.WithCapabilities(exporter.Capabilities()), } + qbs := xexporterhelper.NewMetricsQueueBatchSettings() return exporterhelper.NewMetrics( ctx, params, cfg, exporter.ConsumeMetrics, - buildExporterResilienceOptions(options, c, payloadCodec)..., + buildExporterResilienceOptions(options, c, payloadCodec, qbs)..., ) } diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index c7a7389b0f5ce..76e3f5cac39cb 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -15,6 +15,7 @@ import ( "go.opentelemetry.io/collector/config/configoptional" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper" "go.opentelemetry.io/collector/exporter/exportertest" "go.opentelemetry.io/collector/exporter/otlpexporter" "go.opentelemetry.io/collector/otelcol/otelcoltest" @@ -211,17 +212,21 @@ func TestWrappedExporterHasEndpointAttribute(t *testing.T) { } func TestBuildExporterResilienceOptions(t *testing.T) { + newSettings := func() xexporterhelper.QueueBatchSettings { + return xexporterhelper.NewLogsQueueBatchSettings() + } + t.Run("Shouldn't have resilience options by default", func(t *testing.T) { o := []exporterhelper.Option{} cfg := createDefaultConfig().(*Config) - assert.Empty(t, buildExporterResilienceOptions(o, cfg, nil)) + assert.Empty(t, buildExporterResilienceOptions(o, cfg, nil, newSettings())) }) t.Run("Should have timeout option if defined", func(t *testing.T) { o := []exporterhelper.Option{} cfg := createDefaultConfig().(*Config) cfg.TimeoutSettings = exporterhelper.NewDefaultTimeoutConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, nil), 1) + assert.Len(t, buildExporterResilienceOptions(o, cfg, nil, newSettings()), 1) }) t.Run("Should have timeout and queue options if defined", func(t *testing.T) { o := []exporterhelper.Option{} @@ -229,7 +234,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.TimeoutSettings = exporterhelper.NewDefaultTimeoutConfig() cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg)), 3) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 2) }) t.Run("Should have timeout, queue and compression options when compression is enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -238,7 +243,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression)), 3) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) }) t.Run("Should include in-memory queue compression option when enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -248,7 +253,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy cfg.QueueSettings.CompressInMemory = true - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression)), 4) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) }) t.Run("Should have all resilience options if defined", func(t *testing.T) { o := []exporterhelper.Option{} @@ -258,7 +263,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionNone cfg.BackOffConfig = configretry.NewDefaultBackOffConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg)), 4) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 3) }) } diff --git a/exporter/loadbalancingexporter/payload_codec.go b/exporter/loadbalancingexporter/payload_codec.go index cd1f7cbe74cad..7b6ed8d40df76 100644 --- a/exporter/loadbalancingexporter/payload_codec.go +++ b/exporter/loadbalancingexporter/payload_codec.go @@ -4,12 +4,14 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter" import ( + "context" "errors" "fmt" "sync" "github.com/golang/snappy" "github.com/klauspost/compress/zstd" + "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper" ) var ( @@ -149,3 +151,39 @@ func codecIDForCompression(compression QueuePayloadCompression) (byte, error) { return 0, fmt.Errorf("unsupported queue payload compression %q", compression) } } + +type requestEncoding interface { + Marshal(context.Context, xexporterhelper.Request) ([]byte, error) + Unmarshal([]byte) (context.Context, xexporterhelper.Request, error) +} + +type compressedRequestEncoding struct { + next requestEncoding + codec *queuePayloadCodec +} + +func newCompressedRequestEncoding(next requestEncoding, codec *queuePayloadCodec) requestEncoding { + if codec == nil { + return next + } + return compressedRequestEncoding{ + next: next, + codec: codec, + } +} + +func (e compressedRequestEncoding) Marshal(ctx context.Context, req xexporterhelper.Request) ([]byte, error) { + payload, err := e.next.Marshal(ctx, req) + if err != nil { + return nil, err + } + return e.codec.Encode(payload) +} + +func (e compressedRequestEncoding) Unmarshal(payload []byte) (context.Context, xexporterhelper.Request, error) { + decoded, err := e.codec.Decode(payload) + if err != nil { + return nil, nil, err + } + return e.next.Unmarshal(decoded) +} From aa10b55d0f45c96320532f2c401743767f79a3ec Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:12:54 -0700 Subject: [PATCH 12/41] =?UTF-8?q?perf(loadbalancingexporter):=20avoid=20O(?= =?UTF-8?q?n=C2=B2)=20proto=20re-serialization=20in=20log=20batcher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measure incoming chunk size before merging into pending batch and accumulate incrementally instead of calling sizer.LogsSize(pending) after every enqueue. Since ConsumeLogs enqueues one record at a time, the old pattern re-serialized the entire accumulated batch on every call — O(n²) in records-per-batch. Proto repeated fields (ResourceLogs) are wire-additive, so incremental accounting is accurate. pendingBytes is reset to 0 in flush(), so the counter stays correct across flush boundaries. --- exporter/loadbalancingexporter/log_batcher.go | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 7ec5bc5b1d5a8..6a052eab48a68 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -155,7 +155,9 @@ func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedEx if exp != nil { backend.setExporter(exp) } - backend.inflight.Wait() + if err := waitForInflight(ctx, &backend.inflight); err != nil { + return err + } return backend.stopAndFlush(ctx, logFlushReasonResolverChange) } @@ -170,7 +172,10 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { var errs error for _, backend := range backends { - backend.inflight.Wait() + if err := waitForInflight(ctx, &backend.inflight); err != nil { + errs = errors.Join(errs, err) + continue + } errs = errors.Join(errs, backend.stopAndFlush(ctx, logFlushReasonShutdown)) } b.telemetry.shutdown() @@ -278,6 +283,21 @@ func (b *backendLogBatcher) exporter() *wrappedExporter { return b.exp } +func waitForInflight(ctx context.Context, wg *sync.WaitGroup) error { + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + func (b *backendLogBatcher) run() { defer close(b.done) @@ -298,9 +318,12 @@ func (b *backendLogBatcher) run() { switch req.kind { case logBatcherRequestEnqueue: recordCount := req.logs.LogRecordCount() + // Measure incoming chunk size before merge; proto repeated fields + // are additive so incremental accounting is accurate and avoids + // O(n²) re-serialization of the full pending batch on every enqueue. + pendingBytes += sizer.LogsSize(req.logs) pending = mergeLogs(pending, req.logs) pendingRecords += recordCount - pendingBytes = sizer.LogsSize(pending) b.pendingRecords.Store(int64(pendingRecords)) b.pendingBytes.Store(int64(pendingBytes)) if pendingRecords > 0 && timerC == nil { From 6c33b511420932e3674a7a0272c09f0b96daea39 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:13:32 -0700 Subject: [PATCH 13/41] fix(loadbalancingexporter): honor shutdown context while draining --- .../loadbalancingexporter/log_batcher_test.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 3e609545cc8e6..48f5937afc01f 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -176,6 +176,29 @@ func TestLogBatcherShutdownWaitsForInflightEnqueue(t *testing.T) { assert.Equal(t, int64(1), calls.Load()) } +func TestLogBatcherShutdownRespectsContextWhileWaitingForInflight(t *testing.T) { + ts, _ := getTelemetryAssets(t) + batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, func(context.Context, *wrappedExporter, plog.Logs, string) error { + return nil + }) + require.NoError(t, err) + + backend, err := batcher.acquireBackend("endpoint-1:4317", newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317")) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + require.ErrorIs(t, batcher.Shutdown(ctx), context.Canceled) + backend.inflight.Done() + require.NoError(t, backend.stopAndFlush(t.Context(), logFlushReasonShutdown)) + batcher.telemetry.shutdown() +} + func TestLogBatcherFlushesRemovedBackendToOldExporter(t *testing.T) { ts, tb := getTelemetryAssets(t) var endpoint1Calls atomic.Int64 From d8ab2854994a2e6ea7cdcdf2386e312839a244c5 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:24:28 -0700 Subject: [PATCH 14/41] fix(loadbalancingexporter): preserve queue compression semantics --- exporter/loadbalancingexporter/factory.go | 5 ++- .../loadbalancingexporter/factory_test.go | 8 ++-- exporter/loadbalancingexporter/log_batcher.go | 19 ++++++++- .../loadbalancingexporter/log_batcher_test.go | 40 ++++++++++++++++++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/exporter/loadbalancingexporter/factory.go b/exporter/loadbalancingexporter/factory.go index c658726fadc1e..00af1fcd90d8a 100644 --- a/exporter/loadbalancingexporter/factory.go +++ b/exporter/loadbalancingexporter/factory.go @@ -96,7 +96,10 @@ func buildExporterResilienceOptions( } if cfg.QueueSettings.Enabled { if payloadCodec != nil { - qbs.Encoding = newCompressedRequestEncoding(qbs.Encoding, payloadCodec) + options = append(options, exporterhelper.WithQueueBatchPayloadCodec(payloadCodec)) + } + if cfg.QueueSettings.CompressInMemory { + options = append(options, exporterhelper.WithQueueBatchInMemoryEncoding(true)) } options = append(options, xexporterhelper.WithQueueBatch(cfg.QueueSettings.QueueBatchConfig, qbs)) } diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index 76e3f5cac39cb..2f1385f151d58 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -234,7 +234,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.TimeoutSettings = exporterhelper.NewDefaultTimeoutConfig() cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 2) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 3) }) t.Run("Should have timeout, queue and compression options when compression is enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -243,7 +243,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 3) }) t.Run("Should include in-memory queue compression option when enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -253,7 +253,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy cfg.QueueSettings.CompressInMemory = true - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 4) }) t.Run("Should have all resilience options if defined", func(t *testing.T) { o := []exporterhelper.Option{} @@ -263,7 +263,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionNone cfg.BackOffConfig = configretry.NewDefaultBackOffConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 3) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 4) }) } diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 6a052eab48a68..44973ec8bf127 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -86,6 +86,7 @@ type backendLogBatcher struct { } type logBatcherTelemetry struct { + logger *zap.Logger meter metric.Meter batchSize metric.Int64Histogram batchBytes metric.Int64Histogram @@ -156,6 +157,7 @@ func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedEx backend.setExporter(exp) } if err := waitForInflight(ctx, &backend.inflight); err != nil { + b.scheduleBackendCleanup(backend, logFlushReasonResolverChange) return err } return backend.stopAndFlush(ctx, logFlushReasonResolverChange) @@ -173,6 +175,7 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { var errs error for _, backend := range backends { if err := waitForInflight(ctx, &backend.inflight); err != nil { + b.scheduleBackendCleanup(backend, logFlushReasonShutdown) errs = errors.Join(errs, err) continue } @@ -182,6 +185,18 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { return errs } +func (b *logBatcher) scheduleBackendCleanup(backend *backendLogBatcher, reason string) { + go func() { + if err := waitForInflight(context.Background(), &backend.inflight); err != nil { + b.logger.Warn("failed waiting for inflight log batcher requests during background cleanup", zap.String("endpoint", backend.endpoint), zap.Error(err)) + return + } + if err := backend.stopAndFlush(context.Background(), reason); err != nil { + b.logger.Warn("failed to stop log batcher backend during background cleanup", zap.String("endpoint", backend.endpoint), zap.String("reason", reason), zap.Error(err)) + } + }() +} + func (b *logBatcher) snapshotPending() []logBatcherPending { b.mu.RLock() defer b.mu.RUnlock() @@ -401,7 +416,7 @@ func newLogBatcherTelemetry(settings component.TelemetrySettings) (*logBatcherTe meter := metadata.Meter(settings) var err, errs error - t := &logBatcherTelemetry{meter: meter} + t := &logBatcherTelemetry{logger: settings.Logger, meter: meter} t.batchSize, err = meter.Int64Histogram( "otelcol_loadbalancer_log_batch_size", metric.WithDescription("Number of log records per flushed backend batch."), @@ -477,7 +492,7 @@ func (t *logBatcherTelemetry) shutdown() { defer t.mu.Unlock() for _, reg := range t.registrations { if err := reg.Unregister(); err != nil { - continue + t.logger.Warn("failed to unregister log batcher metric callback", zap.Error(err)) } } t.registrations = nil diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 48f5937afc01f..8e2315776d62c 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -195,8 +195,44 @@ func TestLogBatcherShutdownRespectsContextWhileWaitingForInflight(t *testing.T) require.ErrorIs(t, batcher.Shutdown(ctx), context.Canceled) backend.inflight.Done() - require.NoError(t, backend.stopAndFlush(t.Context(), logFlushReasonShutdown)) - batcher.telemetry.shutdown() + require.Eventually(t, func() bool { + select { + case <-backend.done: + return true + default: + return false + } + }, time.Second, 10*time.Millisecond) +} + +func TestLogBatcherRemoveRespectsContextWhileWaitingForInflight(t *testing.T) { + ts, _ := getTelemetryAssets(t) + batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, func(context.Context, *wrappedExporter, plog.Logs, string) error { + return nil + }) + require.NoError(t, err) + defer batcher.telemetry.shutdown() + + backend, err := batcher.acquireBackend("endpoint-1:4317", newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317")) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(t.Context()) + cancel() + + require.ErrorIs(t, batcher.Remove(ctx, "endpoint-1:4317", backend.exporter()), context.Canceled) + backend.inflight.Done() + require.Eventually(t, func() bool { + select { + case <-backend.done: + return true + default: + return false + } + }, time.Second, 10*time.Millisecond) } func TestLogBatcherFlushesRemovedBackendToOldExporter(t *testing.T) { From d9b0f890d6a0b933fbb7fabedea7a0839a5ddfaa Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:41:17 -0700 Subject: [PATCH 15/41] fix(loadbalancingexporter): remove dead queue codec wrapper --- .../loadbalancingexporter/factory_test.go | 4 +- .../loadbalancingexporter/payload_codec.go | 38 ------------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index 2f1385f151d58..849bf3c2712d9 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -212,9 +212,7 @@ func TestWrappedExporterHasEndpointAttribute(t *testing.T) { } func TestBuildExporterResilienceOptions(t *testing.T) { - newSettings := func() xexporterhelper.QueueBatchSettings { - return xexporterhelper.NewLogsQueueBatchSettings() - } + newSettings := xexporterhelper.NewLogsQueueBatchSettings t.Run("Shouldn't have resilience options by default", func(t *testing.T) { o := []exporterhelper.Option{} diff --git a/exporter/loadbalancingexporter/payload_codec.go b/exporter/loadbalancingexporter/payload_codec.go index 7b6ed8d40df76..cd1f7cbe74cad 100644 --- a/exporter/loadbalancingexporter/payload_codec.go +++ b/exporter/loadbalancingexporter/payload_codec.go @@ -4,14 +4,12 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter" import ( - "context" "errors" "fmt" "sync" "github.com/golang/snappy" "github.com/klauspost/compress/zstd" - "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper" ) var ( @@ -151,39 +149,3 @@ func codecIDForCompression(compression QueuePayloadCompression) (byte, error) { return 0, fmt.Errorf("unsupported queue payload compression %q", compression) } } - -type requestEncoding interface { - Marshal(context.Context, xexporterhelper.Request) ([]byte, error) - Unmarshal([]byte) (context.Context, xexporterhelper.Request, error) -} - -type compressedRequestEncoding struct { - next requestEncoding - codec *queuePayloadCodec -} - -func newCompressedRequestEncoding(next requestEncoding, codec *queuePayloadCodec) requestEncoding { - if codec == nil { - return next - } - return compressedRequestEncoding{ - next: next, - codec: codec, - } -} - -func (e compressedRequestEncoding) Marshal(ctx context.Context, req xexporterhelper.Request) ([]byte, error) { - payload, err := e.next.Marshal(ctx, req) - if err != nil { - return nil, err - } - return e.codec.Encode(payload) -} - -func (e compressedRequestEncoding) Unmarshal(payload []byte) (context.Context, xexporterhelper.Request, error) { - decoded, err := e.codec.Decode(payload) - if err != nil { - return nil, nil, err - } - return e.next.Unmarshal(decoded) -} From 9cce6e786cf0c953e9e2c85b88c84cb697139370 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 18:57:54 -0700 Subject: [PATCH 16/41] fix(loadbalancingexporter): restore queue payload codec support --- exporter/loadbalancingexporter/factory.go | 10 ++++---- .../loadbalancingexporter/factory_test.go | 8 +++--- .../loadbalancingexporter/payload_codec.go | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/exporter/loadbalancingexporter/factory.go b/exporter/loadbalancingexporter/factory.go index 00af1fcd90d8a..c4429ceee22bf 100644 --- a/exporter/loadbalancingexporter/factory.go +++ b/exporter/loadbalancingexporter/factory.go @@ -95,11 +95,11 @@ func buildExporterResilienceOptions( options = append(options, exporterhelper.WithTimeout(cfg.TimeoutSettings)) } if cfg.QueueSettings.Enabled { - if payloadCodec != nil { - options = append(options, exporterhelper.WithQueueBatchPayloadCodec(payloadCodec)) - } - if cfg.QueueSettings.CompressInMemory { - options = append(options, exporterhelper.WithQueueBatchInMemoryEncoding(true)) + if payloadCodec != nil && qbs.Encoding != nil { + qbs.Encoding = payloadCodecEncoding{ + encoding: qbs.Encoding, + codec: payloadCodec, + } } options = append(options, xexporterhelper.WithQueueBatch(cfg.QueueSettings.QueueBatchConfig, qbs)) } diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index 849bf3c2712d9..a4f0473da7905 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -232,7 +232,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.TimeoutSettings = exporterhelper.NewDefaultTimeoutConfig() cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 3) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 2) }) t.Run("Should have timeout, queue and compression options when compression is enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -241,7 +241,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 3) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) }) t.Run("Should include in-memory queue compression option when enabled", func(t *testing.T) { o := []exporterhelper.Option{} @@ -251,7 +251,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy cfg.QueueSettings.CompressInMemory = true - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 4) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) }) t.Run("Should have all resilience options if defined", func(t *testing.T) { o := []exporterhelper.Option{} @@ -261,7 +261,7 @@ func TestBuildExporterResilienceOptions(t *testing.T) { cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionNone cfg.BackOffConfig = configretry.NewDefaultBackOffConfig() - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 4) + assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodecIfEnabled(cfg), newSettings()), 3) }) } diff --git a/exporter/loadbalancingexporter/payload_codec.go b/exporter/loadbalancingexporter/payload_codec.go index cd1f7cbe74cad..bd36408ea9f86 100644 --- a/exporter/loadbalancingexporter/payload_codec.go +++ b/exporter/loadbalancingexporter/payload_codec.go @@ -4,12 +4,15 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/loadbalancingexporter" import ( + "context" "errors" "fmt" "sync" "github.com/golang/snappy" "github.com/klauspost/compress/zstd" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper" ) var ( @@ -149,3 +152,25 @@ func codecIDForCompression(compression QueuePayloadCompression) (byte, error) { return 0, fmt.Errorf("unsupported queue payload compression %q", compression) } } + +type payloadCodecEncoding struct { + encoding exporterhelper.QueueBatchEncoding[xexporterhelper.Request] + codec *queuePayloadCodec +} + +func (e payloadCodecEncoding) Marshal(ctx context.Context, req xexporterhelper.Request) ([]byte, error) { + payload, err := e.encoding.Marshal(ctx, req) + if err != nil { + return nil, err + } + return e.codec.Encode(payload) +} + +func (e payloadCodecEncoding) Unmarshal(payload []byte) (context.Context, xexporterhelper.Request, error) { + decoded, err := e.codec.Decode(payload) + if err != nil { + var req xexporterhelper.Request + return context.Background(), req, err + } + return e.encoding.Unmarshal(decoded) +} From 67fe9873c9e4b4d2f788488a3d60b2963f593c14 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 19:00:46 -0700 Subject: [PATCH 17/41] fix(loadbalancingexporter): harden exporter shutdown races --- exporter/loadbalancingexporter/log_batcher.go | 14 +++++++-- .../loadbalancingexporter/log_exporter.go | 10 +++++-- .../loadbalancingexporter/metrics_exporter.go | 6 ++-- .../loadbalancingexporter/trace_exporter.go | 6 ++-- .../loadbalancingexporter/wrapped_exporter.go | 30 +++++++++++++++++++ 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 44973ec8bf127..63e2082c49ed7 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -28,6 +28,7 @@ const ( defaultLogBatchMaxRecords = 512 defaultLogBatchMaxBytes = 1 << 20 defaultLogBatchFlushTimeout = 100 * time.Millisecond + backgroundCleanupTimeout = 30 * time.Second ) var errLogBatcherExporterStopping = errors.New("log batcher exporter is stopping") @@ -187,11 +188,13 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { func (b *logBatcher) scheduleBackendCleanup(backend *backendLogBatcher, reason string) { go func() { - if err := waitForInflight(context.Background(), &backend.inflight); err != nil { + cleanupCtx, cancel := context.WithTimeout(context.Background(), backgroundCleanupTimeout) + defer cancel() + if err := waitForInflight(cleanupCtx, &backend.inflight); err != nil { b.logger.Warn("failed waiting for inflight log batcher requests during background cleanup", zap.String("endpoint", backend.endpoint), zap.Error(err)) return } - if err := backend.stopAndFlush(context.Background(), reason); err != nil { + if err := backend.stopAndFlush(cleanupCtx, reason); err != nil { b.logger.Warn("failed to stop log batcher backend during background cleanup", zap.String("endpoint", backend.endpoint), zap.String("reason", reason), zap.Error(err)) } }() @@ -216,6 +219,10 @@ func (b *logBatcher) acquireBackend(endpoint string, exp *wrappedExporter) (*bac b.mu.RLock() backend, ok := b.backends[endpoint] if ok { + if exp != nil && exp.isStopping() { + b.mu.RUnlock() + return nil, errLogBatcherExporterStopping + } backend.setExporter(exp) backend.inflight.Add(1) b.mu.RUnlock() @@ -227,6 +234,9 @@ func (b *logBatcher) acquireBackend(endpoint string, exp *wrappedExporter) (*bac defer b.mu.Unlock() backend, ok = b.backends[endpoint] if ok { + if exp != nil && exp.isStopping() { + return nil, errLogBatcherExporterStopping + } backend.setExporter(exp) backend.inflight.Add(1) return backend, nil diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 4ed0966b0ca64..92409c75d9aa9 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -164,9 +164,13 @@ func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { return e.consumeBatch(ctx, le, ld, logFlushReasonSize) } -func (e *logExporterImp) consumeBatch(ctx context.Context, le *wrappedExporter, ld plog.Logs, _ string) error { - le.consumeWG.Add(1) - defer le.consumeWG.Done() +func (e *logExporterImp) consumeBatch(ctx context.Context, le *wrappedExporter, ld plog.Logs, reason string) error { + if reason == logFlushReasonResolverChange || reason == logFlushReasonShutdown { + le.forceStartConsume() + } else if !le.tryStartConsume() { + return errLogBatcherExporterStopping + } + defer le.doneConsume() start := time.Now() err := le.ConsumeLogs(ctx, ld) diff --git a/exporter/loadbalancingexporter/metrics_exporter.go b/exporter/loadbalancingexporter/metrics_exporter.go index b8e38d02e91cc..d6fc73eb86925 100644 --- a/exporter/loadbalancingexporter/metrics_exporter.go +++ b/exporter/loadbalancingexporter/metrics_exporter.go @@ -129,7 +129,9 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri expMetrics, ok := metricsByExporter[exp] if !ok { - exp.consumeWG.Add(1) + if !exp.tryStartConsume() { + return errExporterIsStopping + } expMetrics = pmetric.NewMetrics() metricsByExporter[exp] = expMetrics exporterEndpoints[exp] = endpoint @@ -144,7 +146,7 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri err := exp.ConsumeMetrics(ctx, mds) duration := time.Since(start) - exp.consumeWG.Done() + exp.doneConsume() errs = multierr.Append(errs, err) e.telemetry.LoadbalancerBackendLatency.Record(ctx, duration.Milliseconds(), metric.WithAttributeSet(exp.endpointAttr)) if err == nil { diff --git a/exporter/loadbalancingexporter/trace_exporter.go b/exporter/loadbalancingexporter/trace_exporter.go index 770b546bf7248..435ed9aa67014 100644 --- a/exporter/loadbalancingexporter/trace_exporter.go +++ b/exporter/loadbalancingexporter/trace_exporter.go @@ -118,7 +118,9 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) _, ok := exporterSegregatedTraces[exp] if !ok { - exp.consumeWG.Add(1) + if !exp.tryStartConsume() { + return errExporterIsStopping + } exporterSegregatedTraces[exp] = ptrace.NewTraces() } exporterSegregatedTraces[exp] = mergeTraces(exporterSegregatedTraces[exp], batch) @@ -132,7 +134,7 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) for exp, td := range exporterSegregatedTraces { start := time.Now() err := exp.ConsumeTraces(ctx, td) - exp.consumeWG.Done() + exp.doneConsume() errs = multierr.Append(errs, err) duration := time.Since(start) e.telemetry.LoadbalancerBackendLatency.Record(ctx, duration.Milliseconds(), metric.WithAttributeSet(exp.endpointAttr)) diff --git a/exporter/loadbalancingexporter/wrapped_exporter.go b/exporter/loadbalancingexporter/wrapped_exporter.go index a97414ea7f569..f3e3e3180406e 100644 --- a/exporter/loadbalancingexporter/wrapped_exporter.go +++ b/exporter/loadbalancingexporter/wrapped_exporter.go @@ -5,6 +5,7 @@ package loadbalancingexporter // import "github.com/open-telemetry/opentelemetry import ( "context" + "errors" "fmt" "sync" "sync/atomic" @@ -17,11 +18,14 @@ import ( "go.opentelemetry.io/otel/attribute" ) +var errExporterIsStopping = errors.New("exporter is stopping") + // wrappedExporter is an exporter that waits for the data processing to complete before shutting down. // consumeWG has to be incremented explicitly by the consumer of the wrapped exporter. type wrappedExporter struct { component.Component consumeWG sync.WaitGroup + consumeMu sync.Mutex stopping atomic.Bool // we store the attributes here for both cases, to avoid new allocations on the hot path @@ -41,19 +45,45 @@ func newWrappedExporter(exp component.Component, identifier string) *wrappedExpo } func (we *wrappedExporter) Shutdown(ctx context.Context) error { + we.consumeMu.Lock() we.stopping.Store(true) + we.consumeMu.Unlock() we.consumeWG.Wait() return we.Component.Shutdown(ctx) } func (we *wrappedExporter) markStopping() { + we.consumeMu.Lock() we.stopping.Store(true) + we.consumeMu.Unlock() } func (we *wrappedExporter) isStopping() bool { return we.stopping.Load() } +func (we *wrappedExporter) tryStartConsume() bool { + return we.startConsume(false) +} + +func (we *wrappedExporter) forceStartConsume() { + we.startConsume(true) +} + +func (we *wrappedExporter) startConsume(allowStopping bool) bool { + we.consumeMu.Lock() + defer we.consumeMu.Unlock() + if !allowStopping && we.stopping.Load() { + return false + } + we.consumeWG.Add(1) + return true +} + +func (we *wrappedExporter) doneConsume() { + we.consumeWG.Done() +} + func (we *wrappedExporter) ConsumeTraces(ctx context.Context, td ptrace.Traces) error { te, ok := we.Component.(exporter.Traces) if !ok { From 667c684262caee605aa62ef3db4ae1ffd359617b Mon Sep 17 00:00:00 2001 From: "sawmills-architect-review[bot]" Date: Wed, 18 Mar 2026 19:10:10 -0700 Subject: [PATCH 18/41] fix(loadbalancingexporter): deduplicate resource/scope in per-endpoint batches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-record singleLogRecord wrapping with pre-grouping by endpoint. Records sharing the same resource and scope are now merged into existing ResourceLogs/ScopeLogs entries instead of each getting their own copy. This avoids N× resource/scope duplication in the batcher's pending buffer. --- .../loadbalancingexporter/log_exporter.go | 128 +++++++++++++----- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 92409c75d9aa9..0fb1fc05d3543 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -112,41 +112,67 @@ func (e *logExporterImp) ConsumeLogs(ctx context.Context, ld plog.Logs) error { return errs } + return e.consumeLogsBatched(ctx, ld) +} + +// endpointBatch holds logs grouped for a single backend endpoint, +// with resource/scope structures properly deduplicated. +type endpointBatch struct { + logs plog.Logs + exp *wrappedExporter +} + +// consumeLogsBatched routes each log record to its target endpoint via the +// load balancer, grouping records into per-endpoint plog.Logs that preserve +// the original resource/scope hierarchy. This avoids the N×resource/scope +// duplication that per-record wrapping would cause in the batcher. +func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) error { + batches := make(map[string]*endpointBatch) var errs error + for i := 0; i < ld.ResourceLogs().Len(); i++ { - resourceLogs := ld.ResourceLogs().At(i) - for j := 0; j < resourceLogs.ScopeLogs().Len(); j++ { - scopeLogs := resourceLogs.ScopeLogs().At(j) - for k := 0; k < scopeLogs.LogRecords().Len(); k++ { - record := scopeLogs.LogRecords().At(k) - errs = multierr.Append(errs, e.consumeLogRecord(ctx, resourceLogs, scopeLogs, record)) + rl := ld.ResourceLogs().At(i) + for j := 0; j < rl.ScopeLogs().Len(); j++ { + sl := rl.ScopeLogs().At(j) + for k := 0; k < sl.LogRecords().Len(); k++ { + rec := sl.LogRecords().At(k) + + traceID := rec.TraceID() + balancingKey := traceID + if traceID == pcommon.NewTraceIDEmpty() { + balancingKey = random() + } + + le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) + if err != nil { + errs = multierr.Append(errs, err) + continue + } + ep := endpointWithPort(endpoint) + batch, ok := batches[ep] + if !ok { + batch = &endpointBatch{logs: plog.NewLogs(), exp: le} + batches[ep] = batch + } + batch.exp = le + insertLogRecord(batch.logs, rl, sl, rec) } } } - return errs -} - -func (e *logExporterImp) consumeLogRecord(ctx context.Context, resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) error { - traceID := record.TraceID() - balancingKey := traceID - if traceID == pcommon.NewTraceIDEmpty() { - balancingKey = random() - } - - logs := singleLogRecord(resourceLogs, scopeLogs, record) - for range 2 { - le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) - if err != nil { - return err - } - err = e.batcher.Enqueue(ctx, endpointWithPort(endpoint), le, logs) - if !errors.Is(err, errLogBatcherExporterStopping) { - return err + for ep, batch := range batches { + for range 2 { + err := e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) + if !errors.Is(err, errLogBatcherExporterStopping) { + if err != nil { + errs = multierr.Append(errs, err) + } + break + } } } - return errLogBatcherExporterStopping + return errs } func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { @@ -186,19 +212,47 @@ func (e *logExporterImp) consumeBatch(ctx context.Context, le *wrappedExporter, return err } -func singleLogRecord(resourceLogs plog.ResourceLogs, scopeLogs plog.ScopeLogs, record plog.LogRecord) plog.Logs { - logs := plog.NewLogs() - - targetResourceLogs := logs.ResourceLogs().AppendEmpty() - resourceLogs.Resource().CopyTo(targetResourceLogs.Resource()) - targetResourceLogs.SetSchemaUrl(resourceLogs.SchemaUrl()) +// insertLogRecord adds a log record into the destination plog.Logs, reusing +// existing ResourceLogs/ScopeLogs entries when their attributes match. This +// avoids creating duplicate resource/scope hierarchies for records that share +// the same origin. +func insertLogRecord(dest plog.Logs, srcRL plog.ResourceLogs, srcSL plog.ScopeLogs, rec plog.LogRecord) { + targetRL := findOrCreateResourceLogs(dest, srcRL) + targetSL := findOrCreateScopeLogs(targetRL, srcSL) + rec.CopyTo(targetSL.LogRecords().AppendEmpty()) +} - targetScopeLogs := targetResourceLogs.ScopeLogs().AppendEmpty() - scopeLogs.Scope().CopyTo(targetScopeLogs.Scope()) - targetScopeLogs.SetSchemaUrl(scopeLogs.SchemaUrl()) +func findOrCreateResourceLogs(dest plog.Logs, src plog.ResourceLogs) plog.ResourceLogs { + srcRes := src.Resource() + srcSchema := src.SchemaUrl() + for i := 0; i < dest.ResourceLogs().Len(); i++ { + rl := dest.ResourceLogs().At(i) + if rl.SchemaUrl() == srcSchema && rl.Resource().Attributes().Equal(srcRes.Attributes()) { + return rl + } + } + rl := dest.ResourceLogs().AppendEmpty() + srcRes.CopyTo(rl.Resource()) + rl.SetSchemaUrl(srcSchema) + return rl +} - record.CopyTo(targetScopeLogs.LogRecords().AppendEmpty()) - return logs +func findOrCreateScopeLogs(rl plog.ResourceLogs, src plog.ScopeLogs) plog.ScopeLogs { + srcScope := src.Scope() + srcSchema := src.SchemaUrl() + for i := 0; i < rl.ScopeLogs().Len(); i++ { + sl := rl.ScopeLogs().At(i) + if sl.SchemaUrl() == srcSchema && + sl.Scope().Name() == srcScope.Name() && + sl.Scope().Version() == srcScope.Version() && + sl.Scope().Attributes().Equal(srcScope.Attributes()) { + return sl + } + } + sl := rl.ScopeLogs().AppendEmpty() + srcScope.CopyTo(sl.Scope()) + sl.SetSchemaUrl(srcSchema) + return sl } func traceIDFromLogs(ld plog.Logs) pcommon.TraceID { From d67af0e352f2e3af217e7c68e2c15b4b73e5880a Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 19:19:45 -0700 Subject: [PATCH 19/41] fix(loadbalancingexporter): restore direct consume semantics --- exporter/loadbalancingexporter/log_batcher.go | 1 + .../loadbalancingexporter/log_exporter.go | 4 +- .../log_exporter_test.go | 26 ++++++++++ .../loadbalancingexporter/metrics_exporter.go | 10 ++++ .../metrics_exporter_test.go | 49 +++++++++++++++++++ .../loadbalancingexporter/routing_test.go | 25 ++++++++++ .../loadbalancingexporter/trace_exporter.go | 10 ++++ .../trace_exporter_test.go | 49 +++++++++++++++++++ 8 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 exporter/loadbalancingexporter/routing_test.go diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 63e2082c49ed7..dc2769b6b3bc0 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -20,6 +20,7 @@ import ( ) const ( + logFlushReasonDirect = "direct" logFlushReasonSize = "size" logFlushReasonTimeout = "timeout" logFlushReasonShutdown = "shutdown" diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 0fb1fc05d3543..5bc757fe16090 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -187,11 +187,11 @@ func (e *logExporterImp) consumeLog(ctx context.Context, ld plog.Logs) error { return err } - return e.consumeBatch(ctx, le, ld, logFlushReasonSize) + return e.consumeBatch(ctx, le, ld, logFlushReasonDirect) } func (e *logExporterImp) consumeBatch(ctx context.Context, le *wrappedExporter, ld plog.Logs, reason string) error { - if reason == logFlushReasonResolverChange || reason == logFlushReasonShutdown { + if reason == logFlushReasonDirect || reason == logFlushReasonResolverChange || reason == logFlushReasonShutdown { le.forceStartConsume() } else if !le.tryStartConsume() { return errLogBatcherExporterStopping diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index b6bab4f9bc2f8..8710b334be754 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -398,6 +398,32 @@ func TestLogsWithoutTraceID(t *testing.T) { assert.Len(t, sink.AllLogs(), 1) } +func TestConsumeLogLegacyPathIgnoresStoppingGate(t *testing.T) { + ts, tb := getTelemetryAssets(t) + logsConsumed := atomic.Int64{} + lb, err := newLoadBalancer(ts.Logger, simpleConfig(), nil, tb) + require.NoError(t, err) + + exp := newWrappedExporter(newMockLogsExporter(func(_ context.Context, ld plog.Logs) error { + logsConsumed.Add(int64(ld.LogRecordCount())) + return nil + }), "endpoint-1:4317") + exp.markStopping() + + lb.ring = newHashRing([]string{"endpoint-1"}) + lb.exporters = map[string]*wrappedExporter{ + "endpoint-1:4317": exp, + } + + p, err := newLogsExporter(ts, simpleConfig()) + require.NoError(t, err) + p.loadBalancer = lb + + err = p.consumeLog(t.Context(), simpleLogs()) + require.NoError(t, err) + assert.Equal(t, int64(1), logsConsumed.Load()) +} + // this test validates that exporter is can concurrently change the endpoints while consuming logs. func TestConsumeLogs_ConcurrentResolverChange(t *testing.T) { ts, tb := getTelemetryAssets(t) diff --git a/exporter/loadbalancingexporter/metrics_exporter.go b/exporter/loadbalancingexporter/metrics_exporter.go index d6fc73eb86925..20db28e14e4fd 100644 --- a/exporter/loadbalancingexporter/metrics_exporter.go +++ b/exporter/loadbalancingexporter/metrics_exporter.go @@ -120,6 +120,15 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri // Now assign each batch to an exporter, and merge as we go metricsByExporter := map[*wrappedExporter]pmetric.Metrics{} exporterEndpoints := map[*wrappedExporter]string{} + cleanupStarted := true + defer func() { + if !cleanupStarted { + return + } + for exp := range metricsByExporter { + exp.doneConsume() + } + }() for routingID, mds := range batches { exp, endpoint, err := e.loadBalancer.exporterAndEndpoint([]byte(routingID)) @@ -140,6 +149,7 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri metrics.Merge(expMetrics, mds) } + cleanupStarted = false var errs error for exp, mds := range metricsByExporter { start := time.Now() diff --git a/exporter/loadbalancingexporter/metrics_exporter_test.go b/exporter/loadbalancingexporter/metrics_exporter_test.go index 6127c07e4b69d..a86d9a5e15b8e 100644 --- a/exporter/loadbalancingexporter/metrics_exporter_test.go +++ b/exporter/loadbalancingexporter/metrics_exporter_test.go @@ -1075,6 +1075,17 @@ func simpleMetricsWithServiceName() pmetric.Metrics { return metrics } +func metricsWithServiceNames(serviceNames ...string) pmetric.Metrics { + metrics := pmetric.NewMetrics() + metrics.ResourceMetrics().EnsureCapacity(len(serviceNames)) + for _, serviceName := range serviceNames { + rm := metrics.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr(string(conventions.ServiceNameKey), serviceName) + appendSimpleMetricWithID(rm, signal1Name) + } + return metrics +} + func simpleMetricsWithResource() pmetric.Metrics { metrics := pmetric.NewMetrics() metrics.ResourceMetrics().EnsureCapacity(1) @@ -1102,6 +1113,44 @@ func appendSimpleMetricWithID(dest pmetric.ResourceMetrics, id string) { dest.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetName(id) } +func TestConsumeMetricsReleasesStartedConsumesOnEarlyReturn(t *testing.T) { + ts, tb := getTelemetryAssets(t) + lb, err := newLoadBalancer(ts.Logger, endpoint2Config(), nil, tb) + require.NoError(t, err) + + lb.ring = newHashRing([]string{"endpoint-1", "endpoint-2"}) + + serviceForEndpoint1 := findRoutingIDForEndpoint(t, lb.ring, "endpoint-1") + serviceForEndpoint2 := findRoutingIDForEndpoint(t, lb.ring, "endpoint-2") + + exp1 := newWrappedExporter(newNopMockMetricsExporter(), "endpoint-1:4317") + exp2 := newWrappedExporter(newNopMockMetricsExporter(), "endpoint-2:4317") + exp2.markStopping() + lb.exporters = map[string]*wrappedExporter{ + "endpoint-1:4317": exp1, + "endpoint-2:4317": exp2, + } + + p, err := newMetricsExporter(ts, endpoint2Config()) + require.NoError(t, err) + p.loadBalancer = lb + + err = p.ConsumeMetrics(t.Context(), metricsWithServiceNames(serviceForEndpoint1, serviceForEndpoint2)) + require.ErrorIs(t, err, errExporterIsStopping) + + shutdownErr := make(chan error, 1) + go func() { + shutdownErr <- exp1.Shutdown(t.Context()) + }() + + select { + case err := <-shutdownErr: + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("expected started consume slot to be released on early return") + } +} + type mockMetricsExporter struct { component.Component ConsumeMetricsFn func(ctx context.Context, td pmetric.Metrics) error diff --git a/exporter/loadbalancingexporter/routing_test.go b/exporter/loadbalancingexporter/routing_test.go new file mode 100644 index 0000000000000..e13c7d2d7662d --- /dev/null +++ b/exporter/loadbalancingexporter/routing_test.go @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package loadbalancingexporter + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func findRoutingIDForEndpoint(t *testing.T, ring *hashRing, endpoint string) string { + t.Helper() + + for i := range 4096 { + routingID := fmt.Sprintf("routing-id-%d", i) + if ring.endpointFor([]byte(routingID)) == endpoint { + return routingID + } + } + + require.FailNow(t, "failed to find routing id for endpoint", "endpoint=%s", endpoint) + return "" +} diff --git a/exporter/loadbalancingexporter/trace_exporter.go b/exporter/loadbalancingexporter/trace_exporter.go index 435ed9aa67014..b23d36525602e 100644 --- a/exporter/loadbalancingexporter/trace_exporter.go +++ b/exporter/loadbalancingexporter/trace_exporter.go @@ -104,6 +104,15 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) exporterSegregatedTraces := make(exporterTraces) endpoints := make(map[*wrappedExporter]string) + cleanupStarted := true + defer func() { + if !cleanupStarted { + return + } + for exp := range exporterSegregatedTraces { + exp.doneConsume() + } + }() for _, batch := range batches { routingID, err := routingIdentifiersFromTraces(batch, e.routingKey, e.routingAttrs) if err != nil { @@ -129,6 +138,7 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) } } + cleanupStarted = false var errs error for exp, td := range exporterSegregatedTraces { diff --git a/exporter/loadbalancingexporter/trace_exporter_test.go b/exporter/loadbalancingexporter/trace_exporter_test.go index d190b3222fb31..2c7daeccf2746 100644 --- a/exporter/loadbalancingexporter/trace_exporter_test.go +++ b/exporter/loadbalancingexporter/trace_exporter_test.go @@ -844,6 +844,17 @@ func simpleTracesWithServiceName() ptrace.Traces { return traces } +func tracesWithServiceNames(serviceNames ...string) ptrace.Traces { + traces := ptrace.NewTraces() + traces.ResourceSpans().EnsureCapacity(len(serviceNames)) + for i, serviceName := range serviceNames { + rs := traces.ResourceSpans().AppendEmpty() + rs.Resource().Attributes().PutStr(string(conventions.ServiceNameKey), serviceName) + appendSimpleTraceWithID(rs, pcommon.TraceID{byte(i + 1)}) + } + return traces +} + func twoServicesWithSameTraceID() ptrace.Traces { traces := ptrace.NewTraces() traces.ResourceSpans().EnsureCapacity(2) @@ -860,6 +871,44 @@ func appendSimpleTraceWithID(dest ptrace.ResourceSpans, id pcommon.TraceID) { dest.ScopeSpans().AppendEmpty().Spans().AppendEmpty().SetTraceID(id) } +func TestConsumeTracesReleasesStartedConsumesOnEarlyReturn(t *testing.T) { + ts, tb := getTelemetryAssets(t) + lb, err := newLoadBalancer(ts.Logger, serviceBasedRoutingConfig(), nil, tb) + require.NoError(t, err) + + lb.ring = newHashRing([]string{"endpoint-1", "endpoint-2"}) + + serviceForEndpoint1 := findRoutingIDForEndpoint(t, lb.ring, "endpoint-1") + serviceForEndpoint2 := findRoutingIDForEndpoint(t, lb.ring, "endpoint-2") + + exp1 := newWrappedExporter(newNopMockTracesExporter(), "endpoint-1:4317") + exp2 := newWrappedExporter(newNopMockTracesExporter(), "endpoint-2:4317") + exp2.markStopping() + lb.exporters = map[string]*wrappedExporter{ + "endpoint-1:4317": exp1, + "endpoint-2:4317": exp2, + } + + p, err := newTracesExporter(ts, serviceBasedRoutingConfig()) + require.NoError(t, err) + p.loadBalancer = lb + + err = p.ConsumeTraces(t.Context(), tracesWithServiceNames(serviceForEndpoint1, serviceForEndpoint2)) + require.ErrorIs(t, err, errExporterIsStopping) + + shutdownErr := make(chan error, 1) + go func() { + shutdownErr <- exp1.Shutdown(t.Context()) + }() + + select { + case err := <-shutdownErr: + require.NoError(t, err) + case <-time.After(time.Second): + t.Fatal("expected started consume slot to be released on early return") + } +} + func simpleConfig() *Config { return &Config{ Resolver: ResolverSettings{ From f02c544315dbda50b170ca3ae97503d68a43d15d Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 19:24:53 -0700 Subject: [PATCH 20/41] fix(loadbalancingexporter): surface stopping-enqueue failures --- .../loadbalancingexporter/log_exporter.go | 6 ++++- .../log_exporter_test.go | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 5bc757fe16090..24fd6fde6a27d 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -161,8 +161,9 @@ func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) e } for ep, batch := range batches { + var err error for range 2 { - err := e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) + err = e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) if !errors.Is(err, errLogBatcherExporterStopping) { if err != nil { errs = multierr.Append(errs, err) @@ -170,6 +171,9 @@ func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) e break } } + if err != nil { + errs = multierr.Append(errs, err) + } } return errs diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 8710b334be754..7dc1c2a606c60 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -424,6 +424,33 @@ func TestConsumeLogLegacyPathIgnoresStoppingGate(t *testing.T) { assert.Equal(t, int64(1), logsConsumed.Load()) } +func TestConsumeLogsBatchedReturnsStoppingErrorAfterRetries(t *testing.T) { + ts, tb := getTelemetryAssets(t) + lb, err := newLoadBalancer(ts.Logger, simpleConfig(), nil, tb) + require.NoError(t, err) + + exp := newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317") + exp.markStopping() + + lb.ring = newHashRing([]string{"endpoint-1"}) + lb.exporters = map[string]*wrappedExporter{ + "endpoint-1:4317": exp, + } + + p, err := newLogsExporter(ts, simpleConfig()) + require.NoError(t, err) + p.loadBalancer = lb + p.batcher, err = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + require.NoError(t, err) + + err = p.consumeLogsBatched(t.Context(), simpleLogs()) + require.ErrorIs(t, err, errLogBatcherExporterStopping) +} + // this test validates that exporter is can concurrently change the endpoints while consuming logs. func TestConsumeLogs_ConcurrentResolverChange(t *testing.T) { ts, tb := getTelemetryAssets(t) From e9c7944933e886c812ae4b71b26306516288201d Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 19:42:34 -0700 Subject: [PATCH 21/41] fix(loadbalancingexporter): avoid duplicate enqueue errors --- exporter/loadbalancingexporter/log_exporter.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 24fd6fde6a27d..b25a2f39097d2 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -165,9 +165,6 @@ func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) e for range 2 { err = e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) if !errors.Is(err, errLogBatcherExporterStopping) { - if err != nil { - errs = multierr.Append(errs, err) - } break } } From 000f5ee9bef31253d39887c78b0b3eb941f73100 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 19:49:19 -0700 Subject: [PATCH 22/41] fix(loadbalancingexporter): finish CI sweep follow-ups --- exporter/loadbalancingexporter/README.md | 5 ++-- exporter/loadbalancingexporter/config.go | 3 ++ exporter/loadbalancingexporter/config_test.go | 2 +- .../loadbalancingexporter/factory_test.go | 10 ------- exporter/loadbalancingexporter/log_batcher.go | 7 ++--- .../loadbalancingexporter/log_exporter.go | 23 ++++++++++---- .../log_exporter_test.go | 30 ++++++++++++++----- 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/exporter/loadbalancingexporter/README.md b/exporter/loadbalancingexporter/README.md index dae6f332b8a9d..b0bda724ca0cc 100644 --- a/exporter/loadbalancingexporter/README.md +++ b/exporter/loadbalancingexporter/README.md @@ -246,7 +246,7 @@ service: - loadbalancing ``` -To compress payloads in an in-memory sending queue, set: +To compress queued payloads before they are written to persistent queue storage, set: ```yaml exporters: @@ -254,9 +254,10 @@ exporters: sending_queue: enabled: true payload_compression: zstd - compress_in_memory: true ``` +`sending_queue.compress_in_memory` is currently not supported by this exporter helper version. If set, config validation fails instead of silently ignoring it. + Kubernetes resolver example (For a more specific example: [example/k8s-resolver](./example/k8s-resolver/README.md)) > [!IMPORTANT] > The k8s resolver requires proper permissions. See [the full example](./example/k8s-resolver/README.md) for more information. diff --git a/exporter/loadbalancingexporter/config.go b/exporter/loadbalancingexporter/config.go index 37e6a27712149..2b85fddf68582 100644 --- a/exporter/loadbalancingexporter/config.go +++ b/exporter/loadbalancingexporter/config.go @@ -131,6 +131,9 @@ func (q QueueSettings) Validate() error { if q.CompressInMemory && (q.PayloadCompression == "" || q.PayloadCompression == QueuePayloadCompressionNone) { return errors.New("sending_queue.compress_in_memory requires sending_queue.payload_compression to be set to snappy or zstd") } + if q.CompressInMemory { + return errors.New("sending_queue.compress_in_memory is not supported by this exporter helper version; use sending_queue.payload_compression instead") + } return nil } diff --git a/exporter/loadbalancingexporter/config_test.go b/exporter/loadbalancingexporter/config_test.go index 29aa6f483d59a..9f3f3e4da243e 100644 --- a/exporter/loadbalancingexporter/config_test.go +++ b/exporter/loadbalancingexporter/config_test.go @@ -52,7 +52,7 @@ func TestConfigValidateCompressInMemory(t *testing.T) { require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory requires sending_queue.payload_compression") cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy - require.NoError(t, cfg.Validate()) + require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory is not supported") } func TestConfigValidateLogBatcher(t *testing.T) { diff --git a/exporter/loadbalancingexporter/factory_test.go b/exporter/loadbalancingexporter/factory_test.go index a4f0473da7905..e1993040938a5 100644 --- a/exporter/loadbalancingexporter/factory_test.go +++ b/exporter/loadbalancingexporter/factory_test.go @@ -243,16 +243,6 @@ func TestBuildExporterResilienceOptions(t *testing.T) { assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) }) - t.Run("Should include in-memory queue compression option when enabled", func(t *testing.T) { - o := []exporterhelper.Option{} - cfg := createDefaultConfig().(*Config) - cfg.TimeoutSettings = exporterhelper.NewDefaultTimeoutConfig() - cfg.QueueSettings.QueueBatchConfig = exporterhelper.NewDefaultQueueConfig() - cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy - cfg.QueueSettings.CompressInMemory = true - - assert.Len(t, buildExporterResilienceOptions(o, cfg, newQueuePayloadCodec(cfg.QueueSettings.PayloadCompression), newSettings()), 2) - }) t.Run("Should have all resilience options if defined", func(t *testing.T) { o := []exporterhelper.Option{} cfg := createDefaultConfig().(*Config) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index dc2769b6b3bc0..2ac40dc8443b2 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -29,7 +29,6 @@ const ( defaultLogBatchMaxRecords = 512 defaultLogBatchMaxBytes = 1 << 20 defaultLogBatchFlushTimeout = 100 * time.Millisecond - backgroundCleanupTimeout = 30 * time.Second ) var errLogBatcherExporterStopping = errors.New("log batcher exporter is stopping") @@ -189,13 +188,11 @@ func (b *logBatcher) Shutdown(ctx context.Context) error { func (b *logBatcher) scheduleBackendCleanup(backend *backendLogBatcher, reason string) { go func() { - cleanupCtx, cancel := context.WithTimeout(context.Background(), backgroundCleanupTimeout) - defer cancel() - if err := waitForInflight(cleanupCtx, &backend.inflight); err != nil { + if err := waitForInflight(context.Background(), &backend.inflight); err != nil { b.logger.Warn("failed waiting for inflight log batcher requests during background cleanup", zap.String("endpoint", backend.endpoint), zap.Error(err)) return } - if err := backend.stopAndFlush(cleanupCtx, reason); err != nil { + if err := backend.stopAndFlush(context.Background(), reason); err != nil { b.logger.Warn("failed to stop log batcher backend during background cleanup", zap.String("endpoint", backend.endpoint), zap.String("reason", reason), zap.Error(err)) } }() diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index b25a2f39097d2..1000ff90fc973 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -127,6 +127,11 @@ type endpointBatch struct { // the original resource/scope hierarchy. This avoids the N×resource/scope // duplication that per-record wrapping would cause in the batcher. func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) error { + batches, errs := e.groupLogsByEndpoint(ld) + return multierr.Append(errs, e.enqueueEndpointBatches(ctx, batches, true)) +} + +func (e *logExporterImp) groupLogsByEndpoint(ld plog.Logs) (map[string]*endpointBatch, error) { batches := make(map[string]*endpointBatch) var errs error @@ -160,13 +165,19 @@ func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) e } } + return batches, errs +} + +func (e *logExporterImp) enqueueEndpointBatches(ctx context.Context, batches map[string]*endpointBatch, retryOnStopping bool) error { + var errs error + for ep, batch := range batches { - var err error - for range 2 { - err = e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) - if !errors.Is(err, errLogBatcherExporterStopping) { - break - } + err := e.batcher.Enqueue(ctx, ep, batch.exp, batch.logs) + if errors.Is(err, errLogBatcherExporterStopping) && retryOnStopping { + reroutedBatches, rerouteErr := e.groupLogsByEndpoint(batch.logs) + errs = multierr.Append(errs, rerouteErr) + errs = multierr.Append(errs, e.enqueueEndpointBatches(ctx, reroutedBatches, false)) + continue } if err != nil { errs = multierr.Append(errs, err) diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 7dc1c2a606c60..5e82790384193 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -424,17 +424,22 @@ func TestConsumeLogLegacyPathIgnoresStoppingGate(t *testing.T) { assert.Equal(t, int64(1), logsConsumed.Load()) } -func TestConsumeLogsBatchedReturnsStoppingErrorAfterRetries(t *testing.T) { +func TestEnqueueEndpointBatchesReroutesStoppingExporterOnce(t *testing.T) { ts, tb := getTelemetryAssets(t) + logsConsumed := atomic.Int64{} lb, err := newLoadBalancer(ts.Logger, simpleConfig(), nil, tb) require.NoError(t, err) - exp := newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317") - exp.markStopping() + stoppingExp := newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317") + stoppingExp.markStopping() + liveExp := newWrappedExporter(newMockLogsExporter(func(_ context.Context, ld plog.Logs) error { + logsConsumed.Add(int64(ld.LogRecordCount())) + return nil + }), "endpoint-2:4317") - lb.ring = newHashRing([]string{"endpoint-1"}) + lb.ring = newHashRing([]string{"endpoint-2"}) lb.exporters = map[string]*wrappedExporter{ - "endpoint-1:4317": exp, + "endpoint-2:4317": liveExp, } p, err := newLogsExporter(ts, simpleConfig()) @@ -446,9 +451,20 @@ func TestConsumeLogsBatchedReturnsStoppingErrorAfterRetries(t *testing.T) { flushInterval: time.Hour, }, p.consumeBatch) require.NoError(t, err) + defer func() { + require.NoError(t, p.batcher.Shutdown(t.Context())) + }() - err = p.consumeLogsBatched(t.Context(), simpleLogs()) - require.ErrorIs(t, err, errLogBatcherExporterStopping) + err = p.enqueueEndpointBatches(t.Context(), map[string]*endpointBatch{ + "endpoint-1:4317": { + logs: simpleLogs(), + exp: stoppingExp, + }, + }, true) + require.NoError(t, err) + require.Eventually(t, func() bool { + return logsConsumed.Load() == 1 + }, time.Second, 10*time.Millisecond) } // this test validates that exporter is can concurrently change the endpoints while consuming logs. From bcd8e2d1c3fb49441242242749cec64c6b6d6e95 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 20:03:54 -0700 Subject: [PATCH 23/41] fix(loadbalancingexporter): hoist CompressInMemory unsupported check before misleading guards --- exporter/loadbalancingexporter/config.go | 6 ------ exporter/loadbalancingexporter/config_test.go | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/exporter/loadbalancingexporter/config.go b/exporter/loadbalancingexporter/config.go index 2b85fddf68582..ddf03f7bd6377 100644 --- a/exporter/loadbalancingexporter/config.go +++ b/exporter/loadbalancingexporter/config.go @@ -125,12 +125,6 @@ func (q QueueSettings) Validate() error { return fmt.Errorf("sending_queue.payload_compression must be one of [none, snappy, zstd], found %q", q.PayloadCompression) } - if q.CompressInMemory && !q.Enabled { - return errors.New("sending_queue.compress_in_memory requires sending_queue.enabled=true") - } - if q.CompressInMemory && (q.PayloadCompression == "" || q.PayloadCompression == QueuePayloadCompressionNone) { - return errors.New("sending_queue.compress_in_memory requires sending_queue.payload_compression to be set to snappy or zstd") - } if q.CompressInMemory { return errors.New("sending_queue.compress_in_memory is not supported by this exporter helper version; use sending_queue.payload_compression instead") } diff --git a/exporter/loadbalancingexporter/config_test.go b/exporter/loadbalancingexporter/config_test.go index 9f3f3e4da243e..ec200e2c03faf 100644 --- a/exporter/loadbalancingexporter/config_test.go +++ b/exporter/loadbalancingexporter/config_test.go @@ -44,13 +44,13 @@ func TestConfigValidatePayloadCompression(t *testing.T) { } func TestConfigValidateCompressInMemory(t *testing.T) { + // compress_in_memory is unsupported; any use must fail immediately regardless of other settings. cfg := createDefaultConfig().(*Config) cfg.QueueSettings.CompressInMemory = true - require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory requires sending_queue.enabled=true") + require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory is not supported") + // Same error even when enabled=true and payload_compression is set. cfg.QueueSettings.Enabled = true - require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory requires sending_queue.payload_compression") - cfg.QueueSettings.PayloadCompression = QueuePayloadCompressionSnappy require.ErrorContains(t, cfg.Validate(), "sending_queue.compress_in_memory is not supported") } From 6b936fe8e3e86fe5f1559d0853ccda82339b12dd Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 20:53:54 -0700 Subject: [PATCH 24/41] fix(loadbalancingexporter): address follow-up review comments --- .../loadbalancingexporter/log_exporter.go | 9 ++- .../log_exporter_test.go | 64 ++++++++++++++++++- .../loadbalancingexporter/metrics_exporter.go | 8 +-- .../loadbalancingexporter/trace_exporter.go | 8 +-- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 1000ff90fc973..83c7b9b77080a 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -239,12 +239,15 @@ func findOrCreateResourceLogs(dest plog.Logs, src plog.ResourceLogs) plog.Resour srcSchema := src.SchemaUrl() for i := 0; i < dest.ResourceLogs().Len(); i++ { rl := dest.ResourceLogs().At(i) - if rl.SchemaUrl() == srcSchema && rl.Resource().Attributes().Equal(srcRes.Attributes()) { + if rl.SchemaUrl() == srcSchema && + rl.Resource().DroppedAttributesCount() == srcRes.DroppedAttributesCount() && + rl.Resource().Attributes().Equal(srcRes.Attributes()) { return rl } } rl := dest.ResourceLogs().AppendEmpty() srcRes.CopyTo(rl.Resource()) + rl.Resource().SetDroppedAttributesCount(srcRes.DroppedAttributesCount()) rl.SetSchemaUrl(srcSchema) return rl } @@ -257,12 +260,14 @@ func findOrCreateScopeLogs(rl plog.ResourceLogs, src plog.ScopeLogs) plog.ScopeL if sl.SchemaUrl() == srcSchema && sl.Scope().Name() == srcScope.Name() && sl.Scope().Version() == srcScope.Version() && + sl.Scope().DroppedAttributesCount() == srcScope.DroppedAttributesCount() && sl.Scope().Attributes().Equal(srcScope.Attributes()) { return sl } } sl := rl.ScopeLogs().AppendEmpty() srcScope.CopyTo(sl.Scope()) + sl.Scope().SetDroppedAttributesCount(srcScope.DroppedAttributesCount()) sl.SetSchemaUrl(srcSchema) return sl } @@ -292,4 +297,4 @@ func random() pcommon.TraceID { v3 := uint8(rand.IntN(256)) v4 := uint8(rand.IntN(256)) return [16]byte{v1, v2, v3, v4} -} +} \ No newline at end of file diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 5e82790384193..37ee7ba3827b7 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -467,6 +467,68 @@ func TestEnqueueEndpointBatchesReroutesStoppingExporterOnce(t *testing.T) { }, time.Second, 10*time.Millisecond) } +func TestEnqueueEndpointBatchesReturnsStoppingErrorAfterSecondCollision(t *testing.T) { + ts, tb := getTelemetryAssets(t) + lb, err := newLoadBalancer(ts.Logger, simpleConfig(), nil, tb) + require.NoError(t, err) + + initialExp := newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317") + initialExp.markStopping() + reroutedExp := newWrappedExporter(newNopMockLogsExporter(), "endpoint-2:4317") + reroutedExp.markStopping() + + lb.ring = newHashRing([]string{"endpoint-2"}) + lb.exporters = map[string]*wrappedExporter{ + "endpoint-2:4317": reroutedExp, + } + + p, err := newLogsExporter(ts, simpleConfig()) + require.NoError(t, err) + p.loadBalancer = lb + p.batcher, err = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 1, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, p.consumeBatch) + require.NoError(t, err) + defer func() { + require.NoError(t, p.batcher.Shutdown(t.Context())) + }() + + err = p.enqueueEndpointBatches(t.Context(), map[string]*endpointBatch{ + "endpoint-1:4317": { + logs: simpleLogs(), + exp: initialExp, + }, + }, true) + require.ErrorIs(t, err, errLogBatcherExporterStopping) +} + +func TestInsertLogRecordKeepsDistinctDroppedAttributeCounts(t *testing.T) { + dest := plog.NewLogs() + + srcA := simpleLogs() + rlA := srcA.ResourceLogs().At(0) + slA := rlA.ScopeLogs().At(0) + rlA.Resource().SetDroppedAttributesCount(1) + slA.Scope().SetDroppedAttributesCount(2) + + srcB := simpleLogs() + rlB := srcB.ResourceLogs().At(0) + slB := rlB.ScopeLogs().At(0) + rlB.Resource().SetDroppedAttributesCount(3) + slB.Scope().SetDroppedAttributesCount(4) + + insertLogRecord(dest, rlA, slA, slA.LogRecords().At(0)) + insertLogRecord(dest, rlB, slB, slB.LogRecords().At(0)) + + require.Equal(t, 2, dest.ResourceLogs().Len()) + require.Equal(t, uint32(1), dest.ResourceLogs().At(0).Resource().DroppedAttributesCount()) + require.Equal(t, uint32(3), dest.ResourceLogs().At(1).Resource().DroppedAttributesCount()) + require.Equal(t, uint32(2), dest.ResourceLogs().At(0).ScopeLogs().At(0).Scope().DroppedAttributesCount()) + require.Equal(t, uint32(4), dest.ResourceLogs().At(1).ScopeLogs().At(0).Scope().DroppedAttributesCount()) +} + // this test validates that exporter is can concurrently change the endpoints while consuming logs. func TestConsumeLogs_ConcurrentResolverChange(t *testing.T) { ts, tb := getTelemetryAssets(t) @@ -736,4 +798,4 @@ func newMockLogsExporter(consumelogsfn func(ctx context.Context, ld plog.Logs) e func newNopMockLogsExporter() exporter.Logs { return &mockLogsExporter{Component: mockComponent{}} -} +} \ No newline at end of file diff --git a/exporter/loadbalancingexporter/metrics_exporter.go b/exporter/loadbalancingexporter/metrics_exporter.go index 20db28e14e4fd..c0d503063d755 100644 --- a/exporter/loadbalancingexporter/metrics_exporter.go +++ b/exporter/loadbalancingexporter/metrics_exporter.go @@ -120,9 +120,9 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri // Now assign each batch to an exporter, and merge as we go metricsByExporter := map[*wrappedExporter]pmetric.Metrics{} exporterEndpoints := map[*wrappedExporter]string{} - cleanupStarted := true + needsCleanup := true defer func() { - if !cleanupStarted { + if !needsCleanup { return } for exp := range metricsByExporter { @@ -149,7 +149,7 @@ func (e *metricExporterImp) ConsumeMetrics(ctx context.Context, md pmetric.Metri metrics.Merge(expMetrics, mds) } - cleanupStarted = false + needsCleanup = false var errs error for exp, mds := range metricsByExporter { start := time.Now() @@ -395,4 +395,4 @@ func cloneMetricWithoutType(rm pmetric.ResourceMetrics, sm pmetric.ScopeMetrics, mClone.SetUnit(m.Unit()) return md, mClone -} +} \ No newline at end of file diff --git a/exporter/loadbalancingexporter/trace_exporter.go b/exporter/loadbalancingexporter/trace_exporter.go index b23d36525602e..d5f9c8cd0e98c 100644 --- a/exporter/loadbalancingexporter/trace_exporter.go +++ b/exporter/loadbalancingexporter/trace_exporter.go @@ -104,9 +104,9 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) exporterSegregatedTraces := make(exporterTraces) endpoints := make(map[*wrappedExporter]string) - cleanupStarted := true + needsCleanup := true defer func() { - if !cleanupStarted { + if !needsCleanup { return } for exp := range exporterSegregatedTraces { @@ -138,7 +138,7 @@ func (e *traceExporterImp) ConsumeTraces(ctx context.Context, td ptrace.Traces) } } - cleanupStarted = false + needsCleanup = false var errs error for exp, td := range exporterSegregatedTraces { @@ -251,4 +251,4 @@ func routingIdentifiersFromTraces(td ptrace.Traces, rType routingKey, attrs []st } return ids, nil -} +} \ No newline at end of file From e77a91ff38b55880c62d90529327002e437fdc22 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 20:53:56 -0700 Subject: [PATCH 25/41] fix(hotreloadprocessor): clear package lint failures --- processor/hotreloadprocessor/config.go | 12 +-- processor/hotreloadprocessor/factory.go | 5 +- processor/hotreloadprocessor/file_watcher.go | 4 +- .../hotreloadprocessor/file_watcher_test.go | 9 +- .../internal/encryption/encryptor.go | 5 +- .../internal/encryption/encryptor_test.go | 3 +- .../generated_telemetrytest_test.go | 5 +- processor/hotreloadprocessor/loader.go | 16 ++-- processor/hotreloadprocessor/logs.go | 17 ++-- processor/hotreloadprocessor/logs_test.go | 22 ++--- processor/hotreloadprocessor/metrics.go | 17 ++-- processor/hotreloadprocessor/processor.go | 10 +-- processor/hotreloadprocessor/s3.go | 37 +++++---- processor/hotreloadprocessor/s3_test.go | 83 ++++++++++--------- processor/hotreloadprocessor/telemetry.go | 16 +--- processor/hotreloadprocessor/traces.go | 17 ++-- 16 files changed, 152 insertions(+), 126 deletions(-) diff --git a/processor/hotreloadprocessor/config.go b/processor/hotreloadprocessor/config.go index e531e6d016f88..b62a25a405fb2 100644 --- a/processor/hotreloadprocessor/config.go +++ b/processor/hotreloadprocessor/config.go @@ -4,7 +4,7 @@ package hotreloadprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor" import ( - "fmt" + "errors" "time" ) @@ -26,15 +26,15 @@ type Config struct { func (cfg *Config) Validate() error { if cfg.ConfigurationPrefix == "" { - return fmt.Errorf("configuration_prefix must be specified") + return errors.New("configuration_prefix must be specified") } if cfg.EncryptionKey == "" { - return fmt.Errorf("encryption_key must be specified") + return errors.New("encryption_key must be specified") } if cfg.Region == "" { - return fmt.Errorf("region must be specified") + return errors.New("region must be specified") } if cfg.RefreshInterval <= 0 { @@ -46,8 +46,8 @@ func (cfg *Config) Validate() error { } if cfg.RefreshInterval <= cfg.ShutdownDelay { - return fmt.Errorf("refresh_interval must be greater than shutdown_delay") + return errors.New("refresh_interval must be greater than shutdown_delay") } return nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/factory.go b/processor/hotreloadprocessor/factory.go index 1b9b7cc6fab49..3e9585af36a4c 100644 --- a/processor/hotreloadprocessor/factory.go +++ b/processor/hotreloadprocessor/factory.go @@ -7,10 +7,11 @@ import ( "context" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/processor" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" ) var processorCapabilities = consumer.Capabilities{MutatesData: true} @@ -71,4 +72,4 @@ func createTracesProcessor( return nil, err } return hp, nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/file_watcher.go b/processor/hotreloadprocessor/file_watcher.go index b221511e32855..b40c9871dee94 100644 --- a/processor/hotreloadprocessor/file_watcher.go +++ b/processor/hotreloadprocessor/file_watcher.go @@ -115,8 +115,8 @@ func (p *FileWatcher) Start(ctx context.Context) error { return nil } -func (p *FileWatcher) Stop(ctx context.Context) error { +func (p *FileWatcher) Stop(_ context.Context) error { p.dirWatcher.Close() close(p.done) return nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/file_watcher_test.go b/processor/hotreloadprocessor/file_watcher_test.go index bf916dcf9b322..304016b111e7c 100644 --- a/processor/hotreloadprocessor/file_watcher_test.go +++ b/processor/hotreloadprocessor/file_watcher_test.go @@ -4,7 +4,6 @@ package hotreloadprocessor import ( - "context" "os" "path/filepath" "testing" @@ -26,11 +25,11 @@ func TestFileWatcher(t *testing.T) { }) require.NoError(t, err) - err = watcher.Start(context.Background()) + err = watcher.Start(t.Context()) require.NoError(t, err) filePath := filepath.Join(dir, "config.yaml") - os.WriteFile(filePath, []byte("test"), 0644) + require.NoError(t, os.WriteFile(filePath, []byte("test"), 0o600)) select { case <-done: @@ -38,7 +37,7 @@ func TestFileWatcher(t *testing.T) { t.Fatal("timeout: file not watched") } - watcher.Stop(context.Background()) + require.NoError(t, watcher.Stop(t.Context())) require.Equal(t, []string{filePath}, watchedFiles) -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/internal/encryption/encryptor.go b/processor/hotreloadprocessor/internal/encryption/encryptor.go index 54216e1ec54e1..c0850d72d3aee 100644 --- a/processor/hotreloadprocessor/internal/encryption/encryptor.go +++ b/processor/hotreloadprocessor/internal/encryption/encryptor.go @@ -8,6 +8,7 @@ import ( "crypto/cipher" "crypto/rand" "crypto/sha256" + "errors" "fmt" "io" @@ -90,7 +91,7 @@ func (f *FileEncryptor) Encrypt(content []byte) ([]byte, error) { // Decrypt decrypts and then decompresses the content using AES-GCM. func (f *FileEncryptor) Decrypt(encrypted []byte) ([]byte, error) { if len(encrypted) < 12 { - return nil, fmt.Errorf("encrypted content too short") + return nil, errors.New("encrypted content too short") } nonce := encrypted[:12] ciphertext := encrypted[12:] @@ -123,4 +124,4 @@ func (f *FileEncryptor) Close() error { f.encoder.Close() f.decoder.Close() return nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/internal/encryption/encryptor_test.go b/processor/hotreloadprocessor/internal/encryption/encryptor_test.go index e051b0b45761a..d8710332c53e5 100644 --- a/processor/hotreloadprocessor/internal/encryption/encryptor_test.go +++ b/processor/hotreloadprocessor/internal/encryption/encryptor_test.go @@ -122,5 +122,4 @@ func TestCompressionEffectiveness(t *testing.T) { // Random data shouldn't compress much, size should be larger due to encryption overhead assert.Greater(t, len(encryptedRandom), len(randomData)) -} - +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go index 8b2f986817480..f5bf41bc060cb 100644 --- a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go +++ b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go @@ -10,8 +10,9 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" "go.opentelemetry.io/collector/component/componenttest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" ) func TestSetupTelemetry(t *testing.T) { @@ -57,4 +58,4 @@ func TestSetupTelemetry(t *testing.T) { metricdatatest.IgnoreTimestamp()) require.NoError(t, testTel.Shutdown(context.Background())) -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/loader.go b/processor/hotreloadprocessor/loader.go index 86e8a2659b31f..6e21134538c1b 100644 --- a/processor/hotreloadprocessor/loader.go +++ b/processor/hotreloadprocessor/loader.go @@ -5,6 +5,7 @@ package hotreloadprocessor // import "github.com/open-telemetry/opentelemetry-co import ( "context" + "errors" "fmt" "go.opentelemetry.io/collector/component" @@ -21,6 +22,7 @@ type HostWithFactories interface { GetFactory(kind component.Kind, componentType component.Type) component.Factory } +//nolint:unused // Used via the typed loader helpers below. func loadSubprocessors[T any]( ctx context.Context, config otelcol.Config, @@ -32,11 +34,11 @@ func loadSubprocessors[T any]( nc := nextConsumer if len(config.Service.Pipelines) == 0 { - return nil, fmt.Errorf("no pipelines found") + return nil, errors.New("no pipelines found") } if len(config.Service.Pipelines) > 1 { - return nil, fmt.Errorf("only one pipeline is supported") + return nil, errors.New("only one pipeline is supported") } var subprocessors []T @@ -46,7 +48,7 @@ func loadSubprocessors[T any]( processor := pipeline.Processors[i] hostWithFactories, ok := host.(HostWithFactories) if !ok { - return nil, fmt.Errorf("host does not implement HostWithFactories interface") + return nil, errors.New("host does not implement HostWithFactories interface") } factory := hostWithFactories.GetFactory(component.KindProcessor, processor.Type()) if factory == nil { @@ -57,7 +59,7 @@ func loadSubprocessors[T any]( return nil, fmt.Errorf("factory for type %s is not a processor factory", processor.Type()) } conf := processorFactory.CreateDefaultConfig() - processorConfig := config.Processors[processor].(map[string]interface{}) + processorConfig := config.Processors[processor].(map[string]any) newConfMap := confmap.NewFromStringMap(processorConfig) err := newConfMap.Unmarshal(&conf) if err != nil { @@ -79,6 +81,7 @@ func loadSubprocessors[T any]( return subprocessors, nil } +//nolint:unused // Used by loaderLogs. func loadLogsSubprocessors( ctx context.Context, config otelcol.Config, @@ -101,6 +104,7 @@ func loadLogsSubprocessors( ) } +//nolint:unused // Used by loaderMetrics. func loadMetricsSubprocessors( ctx context.Context, config otelcol.Config, @@ -123,6 +127,7 @@ func loadMetricsSubprocessors( ) } +//nolint:unused // Used by loaderTraces. func loadTracesSubprocessors( ctx context.Context, config otelcol.Config, @@ -143,5 +148,4 @@ func loadTracesSubprocessors( return factory.CreateTraces(ctx, settings, config, nextConsumer) }, ) -} - +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/logs.go b/processor/hotreloadprocessor/logs.go index 74e56eceffe85..67e4b367d2378 100644 --- a/processor/hotreloadprocessor/logs.go +++ b/processor/hotreloadprocessor/logs.go @@ -49,7 +49,8 @@ func (hp *HotReloadLogsProcessor) ConsumeLogs(ctx context.Context, ld plog.Logs) type loaderLogs struct{} -func (l loaderLogs) load( +//nolint:unused // False positive from generic loader wiring. +func (loaderLogs) load( ctx context.Context, config otelcol.Config, set otelprocessor.Settings, @@ -74,24 +75,30 @@ func (l loaderLogs) load( } // passthroughLogsProcessor is a fallback processor that just forwards logs to nextConsumer. +// +//nolint:unused // False positive from interface implementation only used through generics. type passthroughLogsProcessor struct { next consumer.Logs } -func (p *passthroughLogsProcessor) Start(ctx context.Context, host component.Host) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughLogsProcessor) Start(_ context.Context, _ component.Host) error { // No-op return nil } -func (p *passthroughLogsProcessor) Shutdown(ctx context.Context) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughLogsProcessor) Shutdown(_ context.Context) error { // No-op return nil } +//nolint:unused // False positive on interface method implementation. func (p *passthroughLogsProcessor) ConsumeLogs(ctx context.Context, ld plog.Logs) error { return p.next.ConsumeLogs(ctx, ld) } -func (p *passthroughLogsProcessor) Capabilities() consumer.Capabilities { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughLogsProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/logs_test.go b/processor/hotreloadprocessor/logs_test.go index 0a450e7a52258..aa6b7642f9ea2 100644 --- a/processor/hotreloadprocessor/logs_test.go +++ b/processor/hotreloadprocessor/logs_test.go @@ -11,8 +11,6 @@ import ( "testing" "time" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" @@ -24,6 +22,9 @@ import ( sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/transformprocessor" ) type componentTestTelemetry struct { @@ -45,7 +46,7 @@ func (tt *componentTestTelemetry) assertMetrics( totalMetrics *int, ) { var md metricdata.ResourceMetrics - require.NoError(t, tt.reader.Collect(context.Background(), &md)) + require.NoError(t, tt.reader.Collect(t.Context(), &md)) // ensure all required metrics are present for _, want := range expected { got := tt.getMetric(want.Name, md) @@ -58,7 +59,7 @@ func (tt *componentTestTelemetry) assertMetrics( } } -func (tt *componentTestTelemetry) len(got metricdata.ResourceMetrics) int { +func (*componentTestTelemetry) len(got metricdata.ResourceMetrics) int { metricsCount := 0 for _, sm := range got.ScopeMetrics { metricsCount += len(sm.Metrics) @@ -67,7 +68,7 @@ func (tt *componentTestTelemetry) len(got metricdata.ResourceMetrics) int { return metricsCount } -func (tt *componentTestTelemetry) getMetric( +func (*componentTestTelemetry) getMetric( name string, got metricdata.ResourceMetrics, ) metricdata.Metrics { @@ -182,7 +183,7 @@ func TestConsumeLogs(t *testing.T) { tel := setupMetricsCollection() hotreloadProcessor, err := newHotReloadLogsProcessor( - context.Background(), + t.Context(), otelprocessor.Settings{ TelemetrySettings: component.TelemetrySettings{ Logger: zap.NewNop(), @@ -211,7 +212,6 @@ func TestConsumeLogs(t *testing.T) { require.NoError(t, err) logsMarshaler := plog.JSONMarshaler{} - logsMarshaler.MarshalLogs(transformedLogs) json, err := logsMarshaler.MarshalLogs(transformedLogs) require.NoError(t, err) require.JSONEq(t, test.wantLogs(), string(json)) @@ -259,7 +259,7 @@ func TestRefreshConfig(t *testing.T) { tel := setupMetricsCollection() hotreloadProcessor, err := newHotReloadLogsProcessor( - context.Background(), + t.Context(), otelprocessor.Settings{ TelemetrySettings: component.TelemetrySettings{ Logger: zap.NewExample(), @@ -309,7 +309,7 @@ func readTestLogs(t *testing.T, file string) plog.Logs { require.NotEmpty(t, content) logsUnmarshaler := &plog.JSONUnmarshaler{} - inputLogs, err := logsUnmarshaler.UnmarshalLogs([]byte(content)) + inputLogs, err := logsUnmarshaler.UnmarshalLogs(content) require.NoError(t, err) return inputLogs @@ -353,6 +353,6 @@ func (m *mockHost) GetFactory(kind component.Kind, componentType component.Type) return nil } -func (m *mockHost) GetExtensions() map[component.ID]component.Component { +func (*mockHost) GetExtensions() map[component.ID]component.Component { return nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/metrics.go b/processor/hotreloadprocessor/metrics.go index 3ca4fa23d4342..8bff978ad1d2b 100644 --- a/processor/hotreloadprocessor/metrics.go +++ b/processor/hotreloadprocessor/metrics.go @@ -49,7 +49,8 @@ func (hp *HotReloadMetricsProcessor) ConsumeMetrics(ctx context.Context, ld pmet type loaderMetrics struct{} -func (l loaderMetrics) load( +//nolint:unused // False positive from generic loader wiring. +func (loaderMetrics) load( ctx context.Context, config otelcol.Config, set otelprocessor.Settings, @@ -74,20 +75,25 @@ func (l loaderMetrics) load( } // passthroughMetricsProcessor is a fallback processor that just forwards metrics to nextConsumer. +// +//nolint:unused // False positive from interface implementation only used through generics. type passthroughMetricsProcessor struct { next consumer.Metrics } -func (p *passthroughMetricsProcessor) Start(ctx context.Context, host component.Host) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughMetricsProcessor) Start(_ context.Context, _ component.Host) error { // No-op return nil } -func (p *passthroughMetricsProcessor) Shutdown(ctx context.Context) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughMetricsProcessor) Shutdown(_ context.Context) error { // No-op return nil } +//nolint:unused // False positive on interface method implementation. func (p *passthroughMetricsProcessor) ConsumeMetrics( ctx context.Context, ld pmetric.Metrics, @@ -95,6 +101,7 @@ func (p *passthroughMetricsProcessor) ConsumeMetrics( return p.next.ConsumeMetrics(ctx, ld) } -func (p *passthroughMetricsProcessor) Capabilities() consumer.Capabilities { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughMetricsProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/processor.go b/processor/hotreloadprocessor/processor.go index 8d4ecf30e3d03..24d61371e3e29 100644 --- a/processor/hotreloadprocessor/processor.go +++ b/processor/hotreloadprocessor/processor.go @@ -228,7 +228,7 @@ func (hp *HotReloadProcessor[T, P]) Start(ctx context.Context, host component.Ho hp.config, hp.logger, client, - func(client S3Client, bucket string, key string) ListObjectsV2Paginator { + func(client S3Client, bucket, key string) ListObjectsV2Paginator { delimiter := "/" return s3.NewListObjectsV2Paginator(client, &s3.ListObjectsV2Input{ Bucket: &bucket, @@ -254,7 +254,7 @@ func (hp *HotReloadProcessor[T, P]) Start(ctx context.Context, host component.Ho hp.fileWatcher, err = NewFileWatcher( hp.logger, *hp.config.WatchPath, - func(filePath string) error { + func(_ string) error { hp.telemetry.record( triggerScan, float64(1), @@ -362,7 +362,7 @@ func (hp *HotReloadProcessor[T, P]) Shutdown(ctx context.Context) error { return nil } -func (hp *HotReloadProcessor[T, P]) Capabilities() consumer.Capabilities { +func (*HotReloadProcessor[T, P]) Capabilities() consumer.Capabilities { return processorCapabilities } @@ -379,10 +379,10 @@ func (hp *HotReloadProcessor[T, P]) consume(consumer func() error) error { return consumer() } -func (hp *HotReloadProcessor[T, P]) hashConfig(config otelcol.Config) (string, error) { +func (*HotReloadProcessor[T, P]) hashConfig(config otelcol.Config) (string, error) { yaml, err := yaml.Marshal(config) if err != nil { return "", err } return fmt.Sprintf("%x", sha256.Sum256(yaml)), nil -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/s3.go b/processor/hotreloadprocessor/s3.go index 7ea02c10ec87b..cb36cde408c5f 100644 --- a/processor/hotreloadprocessor/s3.go +++ b/processor/hotreloadprocessor/s3.go @@ -6,6 +6,7 @@ package hotreloadprocessor // import "github.com/open-telemetry/opentelemetry-co import ( "context" "encoding/base64" + "errors" "fmt" "io" "sort" @@ -58,7 +59,7 @@ type S3Helper struct { Config Config Logger *zap.Logger Client S3Client - CreatePaginator func(client S3Client, bucket string, key string) ListObjectsV2Paginator + CreatePaginator func(client S3Client, bucket, key string) ListObjectsV2Paginator activeConfig string seenConfigs map[string]bool iterateMu sync.Mutex @@ -68,7 +69,7 @@ func newS3Helper( config Config, logger *zap.Logger, client S3Client, - createPaginator func(client S3Client, bucket string, key string) ListObjectsV2Paginator, + createPaginator func(client S3Client, bucket, key string) ListObjectsV2Paginator, ) *S3Helper { return &S3Helper{ Config: config, @@ -123,7 +124,7 @@ func (hp *S3Helper) fetchObject( return body, nil } -func (hp *S3Helper) decryptObject( +func (*S3Helper) decryptObject( data []byte, key string, ) (otelcol.Config, error) { @@ -139,7 +140,7 @@ func (hp *S3Helper) decryptObject( } var config otelcol.Config - if err := yaml.Unmarshal([]byte(decrypted), &config); err != nil { + if err := yaml.Unmarshal(decrypted, &config); err != nil { return otelcol.Config{}, fmt.Errorf("failed to unmarshal %T config: %w", config, err) } @@ -190,20 +191,20 @@ func (hp *S3Helper) iterateDayLevel( return fmt.Errorf("failed to decrypt object: %w", err) } - err = applyConfig(ctx, config, *obj.Key) - if err != nil { + applyErr := applyConfig(ctx, config, *obj.Key) + if applyErr != nil { hp.Logger.Info( "Failed to apply config, trying next one", zap.String("key", *obj.Key), - zap.Error(err), + zap.Error(applyErr), ) - } else { - hp.activeConfig = *obj.ETag - return nil + continue } + hp.activeConfig = *obj.ETag + return nil } - return fmt.Errorf("no valid config found") + return errors.New("no valid config found") } func (hp *S3Helper) iterateAllDays( @@ -242,12 +243,12 @@ func (hp *S3Helper) iterateAllDays( zap.String("key", *obj.Key), zap.Error(err), ) - } else { - return nil + continue } + return nil } - return fmt.Errorf("no valid configs found") + return errors.New("no valid configs found") } func (hp *S3Helper) iterateConfigs(ctx context.Context, applyConfig applyConfig) error { @@ -264,7 +265,7 @@ func (hp *S3Helper) iterateConfigs(ctx context.Context, applyConfig applyConfig) bucket := parts[2] key := strings.Join(parts[3:], "/") if !strings.HasSuffix(key, "/") { - key = key + "/" + key += "/" } return hp.iterateAllDays(ctx, hp.Client, bucket, key, applyConfig) @@ -275,8 +276,8 @@ func (hp *S3Helper) iterateConfigs(ctx context.Context, applyConfig applyConfig) if err != nil { return fmt.Errorf("failed to decode base64: %w", err) } - if err := yaml.Unmarshal(decoded, &config); err != nil { - return fmt.Errorf("failed to unmarshal %T config: %w", config, err) + if unmarshalErr := yaml.Unmarshal(decoded, &config); unmarshalErr != nil { + return fmt.Errorf("failed to unmarshal %T config: %w", config, unmarshalErr) } err = applyConfig(ctx, config, "local-key") if err != nil { @@ -289,4 +290,4 @@ func (hp *S3Helper) iterateConfigs(ctx context.Context, applyConfig applyConfig) "invalid path: %s", hp.Config.ConfigurationPrefix, ) -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/s3_test.go b/processor/hotreloadprocessor/s3_test.go index cf721940084de..6383a8c210361 100644 --- a/processor/hotreloadprocessor/s3_test.go +++ b/processor/hotreloadprocessor/s3_test.go @@ -6,6 +6,7 @@ package hotreloadprocessor import ( "bytes" "context" + "errors" "fmt" "io" "os" @@ -49,7 +50,9 @@ func (m *mockListObjectsV2Paginator) NextPage( } m.Objects = m.Objects[maxPageSize:] return &output, nil - } else if len(m.CommonPrefixes) > 0 { + } + + if len(m.CommonPrefixes) > 0 { maxPageSize := m.MaxPageSize if maxPageSize == 0 { maxPageSize = len(m.CommonPrefixes) @@ -59,9 +62,9 @@ func (m *mockListObjectsV2Paginator) NextPage( } m.CommonPrefixes = m.CommonPrefixes[maxPageSize:] return &output, nil - } else { - return nil, nil } + + return nil, nil } // mockS3Client is a mock implementation of the S3 client. @@ -106,7 +109,7 @@ func TestIterateConfigs(t *testing.T) { getObjectFunc func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) listAllDaysFunc func(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) listDayObjectsFunc func(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) - createPaginatorFunc func(client S3Client, bucket string, key string) ListObjectsV2Paginator + createPaginatorFunc func(client S3Client, bucket, key string) ListObjectsV2Paginator applyConfigFunc func(ctx context.Context, config otelcol.Config, key string) error expectedError bool wantedKeys []string @@ -119,7 +122,7 @@ func TestIterateConfigs(t *testing.T) { Region: "us-east-1", EncryptionKey: testKey, }, - getObjectFunc: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + getObjectFunc: func(_ context.Context, _ *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { data, err := os.ReadFile(filepath.Join("testdata", "config.yaml")) require.NoError(t, err) encrypted := encrypt(t, data) @@ -127,8 +130,9 @@ func TestIterateConfigs(t *testing.T) { Body: io.NopCloser(bytes.NewReader(encrypted)), }, nil }, - createPaginatorFunc: func(client S3Client, bucket string, key string) ListObjectsV2Paginator { - if key == "test-org-id/prefix-1/" { + createPaginatorFunc: func(_ S3Client, _, key string) ListObjectsV2Paginator { + switch key { + case "test-org-id/prefix-1/": return &mockListObjectsV2Paginator{ CommonPrefixes: []s3types.CommonPrefix{ {Prefix: aws.String("test-org-id/prefix-1/1000000000000")}, @@ -136,7 +140,7 @@ func TestIterateConfigs(t *testing.T) { {Prefix: aws.String("test-org-id/prefix-1/2000000000000")}, }, } - } else if key == "test-org-id/prefix-1/3000000000000" { + case "test-org-id/prefix-1/3000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", now.Unix())), LastModified: &now, ETag: aws.String("etag1")}, @@ -146,11 +150,11 @@ func TestIterateConfigs(t *testing.T) { } return nil }, - applyConfigFunc: func(ctx context.Context, config otelcol.Config, key string) error { + applyConfigFunc: func(_ context.Context, _ otelcol.Config, key string) error { if key == fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", now.Unix()) { return nil } - return fmt.Errorf("wrong config") + return errors.New("wrong config") }, expectedError: false, wantedKeys: []string{ @@ -164,7 +168,7 @@ func TestIterateConfigs(t *testing.T) { Region: "us-east-1", EncryptionKey: testKey, }, - getObjectFunc: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + getObjectFunc: func(_ context.Context, _ *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { data, err := os.ReadFile(filepath.Join("testdata", "config.yaml")) require.NoError(t, err) encrypted := encrypt(t, data) @@ -172,8 +176,9 @@ func TestIterateConfigs(t *testing.T) { Body: io.NopCloser(bytes.NewReader(encrypted)), }, nil }, - createPaginatorFunc: func(client S3Client, bucket string, key string) ListObjectsV2Paginator { - if key == "test-org-id/prefix-1/" { + createPaginatorFunc: func(_ S3Client, _, key string) ListObjectsV2Paginator { + switch key { + case "test-org-id/prefix-1/": return &mockListObjectsV2Paginator{ CommonPrefixes: []s3types.CommonPrefix{ {Prefix: aws.String("test-org-id/prefix-1/1000000000000")}, @@ -181,7 +186,7 @@ func TestIterateConfigs(t *testing.T) { {Prefix: aws.String("test-org-id/prefix-1/2000000000000")}, }, } - } else if key == "test-org-id/prefix-1/3000000000000" { + case "test-org-id/prefix-1/3000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", dayAgo.Unix())), LastModified: &dayAgo, ETag: aws.String("etag2")}, @@ -191,11 +196,11 @@ func TestIterateConfigs(t *testing.T) { } return nil }, - applyConfigFunc: func(ctx context.Context, config otelcol.Config, key string) error { + applyConfigFunc: func(_ context.Context, _ otelcol.Config, key string) error { if key == fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", dayAgo.Unix()) { return nil } - return fmt.Errorf("wrong config") + return errors.New("wrong config") }, expectedError: false, wantedKeys: []string{ @@ -211,7 +216,7 @@ func TestIterateConfigs(t *testing.T) { Region: "us-east-1", EncryptionKey: testKey, }, - getObjectFunc: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + getObjectFunc: func(_ context.Context, _ *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { data, err := os.ReadFile(filepath.Join("testdata", "config.yaml")) require.NoError(t, err) encrypted := encrypt(t, data) @@ -219,8 +224,9 @@ func TestIterateConfigs(t *testing.T) { Body: io.NopCloser(bytes.NewReader(encrypted)), }, nil }, - createPaginatorFunc: func(client S3Client, bucket string, key string) ListObjectsV2Paginator { - if key == "test-org-id/prefix-1/" { + createPaginatorFunc: func(_ S3Client, _, key string) ListObjectsV2Paginator { + switch key { + case "test-org-id/prefix-1/": return &mockListObjectsV2Paginator{ CommonPrefixes: []s3types.CommonPrefix{ {Prefix: aws.String("test-org-id/prefix-1/1000000000000")}, @@ -229,7 +235,7 @@ func TestIterateConfigs(t *testing.T) { }, MaxPageSize: 1, } - } else if key == "test-org-id/prefix-1/3000000000000" { + case "test-org-id/prefix-1/3000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", now.Unix())), LastModified: &now, ETag: aws.String("etag1")}, @@ -237,7 +243,7 @@ func TestIterateConfigs(t *testing.T) { }, MaxPageSize: 1, } - } else if key == "test-org-id/prefix-1/2000000000000" { + case "test-org-id/prefix-1/2000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/2000000000000/%d.yaml", twoDaysAgo.Unix())), LastModified: &twoDaysAgo, ETag: aws.String("etag3")}, @@ -247,14 +253,14 @@ func TestIterateConfigs(t *testing.T) { } return nil }, - applyConfigFunc: func(ctx context.Context, config otelcol.Config, key string) error { + applyConfigFunc: func(_ context.Context, _ otelcol.Config, key string) error { if key == fmt.Sprintf( "test-org-id/prefix-1/2000000000000/%d.yaml", twoDaysAgo.Unix(), ) { return nil } - return fmt.Errorf("wrong config") + return errors.New("wrong config") }, expectedError: false, wantedKeys: []string{ @@ -270,7 +276,7 @@ func TestIterateConfigs(t *testing.T) { Region: "us-east-1", EncryptionKey: testKey, }, - getObjectFunc: func(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) { + getObjectFunc: func(_ context.Context, _ *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { data, err := os.ReadFile(filepath.Join("testdata", "config.yaml")) require.NoError(t, err) encrypted := encrypt(t, data) @@ -278,8 +284,9 @@ func TestIterateConfigs(t *testing.T) { Body: io.NopCloser(bytes.NewReader(encrypted)), }, nil }, - createPaginatorFunc: func(client S3Client, bucket string, key string) ListObjectsV2Paginator { - if key == "test-org-id/prefix-1/" { + createPaginatorFunc: func(_ S3Client, _, key string) ListObjectsV2Paginator { + switch key { + case "test-org-id/prefix-1/": return &mockListObjectsV2Paginator{ CommonPrefixes: []s3types.CommonPrefix{ {Prefix: aws.String("test-org-id/prefix-1/1000000000000")}, @@ -288,7 +295,7 @@ func TestIterateConfigs(t *testing.T) { }, MaxPageSize: 1, } - } else if key == "test-org-id/prefix-1/3000000000000" { + case "test-org-id/prefix-1/3000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/3000000000000/%d.yaml", now.Unix())), LastModified: &now, ETag: aws.String("etag1")}, @@ -296,20 +303,20 @@ func TestIterateConfigs(t *testing.T) { }, MaxPageSize: 1, } - } else if key == "test-org-id/prefix-1/2000000000000" { + case "test-org-id/prefix-1/2000000000000": return &mockListObjectsV2Paginator{ Objects: []s3types.Object{ {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/2000000000000/%d.yaml", twoDaysAgo.Unix())), LastModified: &twoDaysAgo, ETag: aws.String("etag3")}, {Key: aws.String(fmt.Sprintf("test-org-id/prefix-1/2000000000000/%d.yaml", threeDaysAgo.Unix())), LastModified: &threeDaysAgo, ETag: aws.String("etag4")}, }, } - } else if key == "test-org-id/prefix-1/1000000000000" { + case "test-org-id/prefix-1/1000000000000": return &mockListObjectsV2Paginator{} } return nil }, - applyConfigFunc: func(ctx context.Context, config otelcol.Config, key string) error { - return fmt.Errorf("wrong config") + applyConfigFunc: func(_ context.Context, _ otelcol.Config, _ string) error { + return errors.New("wrong config") }, expectedError: true, wantedKeys: []string{ @@ -336,12 +343,12 @@ func TestIterateConfigs(t *testing.T) { var err error for range reruns { err = hp.iterateConfigs( - context.Background(), + t.Context(), func(ctx context.Context, config otelcol.Config, key string) error { keys = append(keys, key) - err := tt.applyConfigFunc(ctx, config, key) - if err != nil { - return err + applyErr := tt.applyConfigFunc(ctx, config, key) + if applyErr != nil { + return applyErr } return nil }, @@ -351,9 +358,9 @@ func TestIterateConfigs(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, len(tt.wantedKeys), len(keys)) + require.Len(t, keys, len(tt.wantedKeys)) for i, wantedKey := range tt.wantedKeys { - require.EqualValues(t, wantedKey, keys[i]) + require.Equal(t, wantedKey, keys[i]) } } }) @@ -367,4 +374,4 @@ func encrypt(t *testing.T, data []byte) []byte { encrypted, err := encryptor.Encrypt(data) require.NoError(t, err) return encrypted -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/telemetry.go b/processor/hotreloadprocessor/telemetry.go index 7f92cde7b75f1..7cfe6ee967fd4 100644 --- a/processor/hotreloadprocessor/telemetry.go +++ b/processor/hotreloadprocessor/telemetry.go @@ -8,10 +8,11 @@ import ( "os" "strings" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" "go.opentelemetry.io/collector/processor" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor/internal/metadata" ) // hotreloadProcessorTelemetry holds telemetry data and methods for recording metrics. @@ -80,7 +81,7 @@ func (pt *hotreloadProcessorTelemetry) record( type metricRecordFunc func(*hotreloadProcessorTelemetry, float64, ...attribute.KeyValue) // getMetricFuncForTrigger returns the appropriate metric recording function based on the trigger type. -func (pt *hotreloadProcessorTelemetry) getMetricFuncForTrigger( +func (*hotreloadProcessorTelemetry) getMetricFuncForTrigger( triggerName trigger, ) metricRecordFunc { metricMap := map[trigger]metricRecordFunc{ @@ -133,15 +134,6 @@ func (pt *hotreloadProcessorTelemetry) getMetricFuncForTrigger( return metricMap[triggerName] } -// recordInt64Counter returns a function to record int64 counter metrics. -func recordInt64Counter( - getMetric func(*hotreloadProcessorTelemetry) metric.Int64Counter, -) metricRecordFunc { - return func(qpt *hotreloadProcessorTelemetry, value float64, labels ...attribute.KeyValue) { - getMetric(qpt).Add(qpt.exportCtx, int64(value), metric.WithAttributes(labels...)) - } -} - // recordInt64Gauge returns a function to record int64 gauge metrics. func recordInt64Gauge( getMetric func(*hotreloadProcessorTelemetry) metric.Int64Gauge, @@ -158,4 +150,4 @@ func recordInt64Histogram( return func(pt *hotreloadProcessorTelemetry, value float64, labels ...attribute.KeyValue) { getMetric(pt).Record(pt.exportCtx, int64(value), metric.WithAttributes(labels...)) } -} +} \ No newline at end of file diff --git a/processor/hotreloadprocessor/traces.go b/processor/hotreloadprocessor/traces.go index 7d5e679e38185..48faa0ee1830c 100644 --- a/processor/hotreloadprocessor/traces.go +++ b/processor/hotreloadprocessor/traces.go @@ -49,7 +49,8 @@ func (hp *HotReloadTracesProcessor) ConsumeTraces(ctx context.Context, ld ptrace type loaderTraces struct{} -func (l loaderTraces) load( +//nolint:unused // False positive from generic loader wiring. +func (loaderTraces) load( ctx context.Context, config otelcol.Config, set otelprocessor.Settings, @@ -74,24 +75,30 @@ func (l loaderTraces) load( } // passthroughTracesProcessor is a fallback processor that just forwards traces to nextConsumer. +// +//nolint:unused // False positive from interface implementation only used through generics. type passthroughTracesProcessor struct { next consumer.Traces } -func (p *passthroughTracesProcessor) Start(ctx context.Context, host component.Host) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughTracesProcessor) Start(_ context.Context, _ component.Host) error { // No-op return nil } -func (p *passthroughTracesProcessor) Shutdown(ctx context.Context) error { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughTracesProcessor) Shutdown(_ context.Context) error { // No-op return nil } +//nolint:unused // False positive on interface method implementation. func (p *passthroughTracesProcessor) ConsumeTraces(ctx context.Context, ld ptrace.Traces) error { return p.next.ConsumeTraces(ctx, ld) } -func (p *passthroughTracesProcessor) Capabilities() consumer.Capabilities { +//nolint:unused // False positive from interface implementation only used through generics. +func (*passthroughTracesProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} +} \ No newline at end of file From e2166233517699beee69b4e6aa4a9209e88f9a6e Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 22:12:00 -0700 Subject: [PATCH 26/41] fix(ci): normalize import formatting --- exporter/loadbalancingexporter/log_exporter.go | 2 +- exporter/loadbalancingexporter/log_exporter_test.go | 2 +- exporter/loadbalancingexporter/metrics_exporter.go | 2 +- exporter/loadbalancingexporter/trace_exporter.go | 2 +- processor/hotreloadprocessor/config.go | 2 +- processor/hotreloadprocessor/factory.go | 2 +- processor/hotreloadprocessor/file_watcher.go | 2 +- processor/hotreloadprocessor/file_watcher_test.go | 2 +- processor/hotreloadprocessor/internal/encryption/encryptor.go | 2 +- .../hotreloadprocessor/internal/encryption/encryptor_test.go | 2 +- .../internal/metadatatest/generated_telemetrytest_test.go | 2 +- processor/hotreloadprocessor/loader.go | 2 +- processor/hotreloadprocessor/logs.go | 2 +- processor/hotreloadprocessor/logs_test.go | 2 +- processor/hotreloadprocessor/metrics.go | 2 +- processor/hotreloadprocessor/processor.go | 2 +- processor/hotreloadprocessor/s3.go | 2 +- processor/hotreloadprocessor/s3_test.go | 2 +- processor/hotreloadprocessor/telemetry.go | 2 +- processor/hotreloadprocessor/traces.go | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 83c7b9b77080a..5ae8baef8aab1 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -297,4 +297,4 @@ func random() pcommon.TraceID { v3 := uint8(rand.IntN(256)) v4 := uint8(rand.IntN(256)) return [16]byte{v1, v2, v3, v4} -} \ No newline at end of file +} diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 37ee7ba3827b7..501a74a0f273a 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -798,4 +798,4 @@ func newMockLogsExporter(consumelogsfn func(ctx context.Context, ld plog.Logs) e func newNopMockLogsExporter() exporter.Logs { return &mockLogsExporter{Component: mockComponent{}} -} \ No newline at end of file +} diff --git a/exporter/loadbalancingexporter/metrics_exporter.go b/exporter/loadbalancingexporter/metrics_exporter.go index c0d503063d755..a06a694ad9f73 100644 --- a/exporter/loadbalancingexporter/metrics_exporter.go +++ b/exporter/loadbalancingexporter/metrics_exporter.go @@ -395,4 +395,4 @@ func cloneMetricWithoutType(rm pmetric.ResourceMetrics, sm pmetric.ScopeMetrics, mClone.SetUnit(m.Unit()) return md, mClone -} \ No newline at end of file +} diff --git a/exporter/loadbalancingexporter/trace_exporter.go b/exporter/loadbalancingexporter/trace_exporter.go index d5f9c8cd0e98c..547bc6ef42b36 100644 --- a/exporter/loadbalancingexporter/trace_exporter.go +++ b/exporter/loadbalancingexporter/trace_exporter.go @@ -251,4 +251,4 @@ func routingIdentifiersFromTraces(td ptrace.Traces, rType routingKey, attrs []st } return ids, nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/config.go b/processor/hotreloadprocessor/config.go index b62a25a405fb2..a48c6c46764ae 100644 --- a/processor/hotreloadprocessor/config.go +++ b/processor/hotreloadprocessor/config.go @@ -50,4 +50,4 @@ func (cfg *Config) Validate() error { } return nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/factory.go b/processor/hotreloadprocessor/factory.go index 3e9585af36a4c..62a1cb0a73df6 100644 --- a/processor/hotreloadprocessor/factory.go +++ b/processor/hotreloadprocessor/factory.go @@ -72,4 +72,4 @@ func createTracesProcessor( return nil, err } return hp, nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/file_watcher.go b/processor/hotreloadprocessor/file_watcher.go index b40c9871dee94..65b409682631e 100644 --- a/processor/hotreloadprocessor/file_watcher.go +++ b/processor/hotreloadprocessor/file_watcher.go @@ -119,4 +119,4 @@ func (p *FileWatcher) Stop(_ context.Context) error { p.dirWatcher.Close() close(p.done) return nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/file_watcher_test.go b/processor/hotreloadprocessor/file_watcher_test.go index 304016b111e7c..d599777a3ba81 100644 --- a/processor/hotreloadprocessor/file_watcher_test.go +++ b/processor/hotreloadprocessor/file_watcher_test.go @@ -40,4 +40,4 @@ func TestFileWatcher(t *testing.T) { require.NoError(t, watcher.Stop(t.Context())) require.Equal(t, []string{filePath}, watchedFiles) -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/internal/encryption/encryptor.go b/processor/hotreloadprocessor/internal/encryption/encryptor.go index c0850d72d3aee..99180b4eb1d60 100644 --- a/processor/hotreloadprocessor/internal/encryption/encryptor.go +++ b/processor/hotreloadprocessor/internal/encryption/encryptor.go @@ -124,4 +124,4 @@ func (f *FileEncryptor) Close() error { f.encoder.Close() f.decoder.Close() return nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/internal/encryption/encryptor_test.go b/processor/hotreloadprocessor/internal/encryption/encryptor_test.go index d8710332c53e5..ae8342d1817cc 100644 --- a/processor/hotreloadprocessor/internal/encryption/encryptor_test.go +++ b/processor/hotreloadprocessor/internal/encryption/encryptor_test.go @@ -122,4 +122,4 @@ func TestCompressionEffectiveness(t *testing.T) { // Random data shouldn't compress much, size should be larger due to encryption overhead assert.Greater(t, len(encryptedRandom), len(randomData)) -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go index f5bf41bc060cb..74972113d2e58 100644 --- a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go +++ b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest_test.go @@ -58,4 +58,4 @@ func TestSetupTelemetry(t *testing.T) { metricdatatest.IgnoreTimestamp()) require.NoError(t, testTel.Shutdown(context.Background())) -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/loader.go b/processor/hotreloadprocessor/loader.go index 6e21134538c1b..2f27d40b10f0e 100644 --- a/processor/hotreloadprocessor/loader.go +++ b/processor/hotreloadprocessor/loader.go @@ -148,4 +148,4 @@ func loadTracesSubprocessors( return factory.CreateTraces(ctx, settings, config, nextConsumer) }, ) -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/logs.go b/processor/hotreloadprocessor/logs.go index 67e4b367d2378..484bbc55380ae 100644 --- a/processor/hotreloadprocessor/logs.go +++ b/processor/hotreloadprocessor/logs.go @@ -101,4 +101,4 @@ func (p *passthroughLogsProcessor) ConsumeLogs(ctx context.Context, ld plog.Logs //nolint:unused // False positive from interface implementation only used through generics. func (*passthroughLogsProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/logs_test.go b/processor/hotreloadprocessor/logs_test.go index aa6b7642f9ea2..733a78c7a8583 100644 --- a/processor/hotreloadprocessor/logs_test.go +++ b/processor/hotreloadprocessor/logs_test.go @@ -355,4 +355,4 @@ func (m *mockHost) GetFactory(kind component.Kind, componentType component.Type) func (*mockHost) GetExtensions() map[component.ID]component.Component { return nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/metrics.go b/processor/hotreloadprocessor/metrics.go index 8bff978ad1d2b..fd29764a0b426 100644 --- a/processor/hotreloadprocessor/metrics.go +++ b/processor/hotreloadprocessor/metrics.go @@ -104,4 +104,4 @@ func (p *passthroughMetricsProcessor) ConsumeMetrics( //nolint:unused // False positive from interface implementation only used through generics. func (*passthroughMetricsProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/processor.go b/processor/hotreloadprocessor/processor.go index 24d61371e3e29..0fc178e9921f4 100644 --- a/processor/hotreloadprocessor/processor.go +++ b/processor/hotreloadprocessor/processor.go @@ -385,4 +385,4 @@ func (*HotReloadProcessor[T, P]) hashConfig(config otelcol.Config) (string, erro return "", err } return fmt.Sprintf("%x", sha256.Sum256(yaml)), nil -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/s3.go b/processor/hotreloadprocessor/s3.go index cb36cde408c5f..6a5dac150a84e 100644 --- a/processor/hotreloadprocessor/s3.go +++ b/processor/hotreloadprocessor/s3.go @@ -290,4 +290,4 @@ func (hp *S3Helper) iterateConfigs(ctx context.Context, applyConfig applyConfig) "invalid path: %s", hp.Config.ConfigurationPrefix, ) -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/s3_test.go b/processor/hotreloadprocessor/s3_test.go index 6383a8c210361..2d43ce2f2cd95 100644 --- a/processor/hotreloadprocessor/s3_test.go +++ b/processor/hotreloadprocessor/s3_test.go @@ -374,4 +374,4 @@ func encrypt(t *testing.T, data []byte) []byte { encrypted, err := encryptor.Encrypt(data) require.NoError(t, err) return encrypted -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/telemetry.go b/processor/hotreloadprocessor/telemetry.go index 7cfe6ee967fd4..fceaec258b26e 100644 --- a/processor/hotreloadprocessor/telemetry.go +++ b/processor/hotreloadprocessor/telemetry.go @@ -150,4 +150,4 @@ func recordInt64Histogram( return func(pt *hotreloadProcessorTelemetry, value float64, labels ...attribute.KeyValue) { getMetric(pt).Record(pt.exportCtx, int64(value), metric.WithAttributes(labels...)) } -} \ No newline at end of file +} diff --git a/processor/hotreloadprocessor/traces.go b/processor/hotreloadprocessor/traces.go index 48faa0ee1830c..6a36044de8fda 100644 --- a/processor/hotreloadprocessor/traces.go +++ b/processor/hotreloadprocessor/traces.go @@ -101,4 +101,4 @@ func (p *passthroughTracesProcessor) ConsumeTraces(ctx context.Context, ld ptrac //nolint:unused // False positive from interface implementation only used through generics. func (*passthroughTracesProcessor) Capabilities() consumer.Capabilities { return processorCapabilities -} \ No newline at end of file +} From 7512d05a10cbd4843bacc4ae89f8b73d6583020c Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 22:26:50 -0700 Subject: [PATCH 27/41] fix(logstometricsprocessor): clear lint debt --- processor/logstometricsprocessor/doc.go | 1 - processor/logstometricsprocessor/factory.go | 1 - .../internal/aggregator/aggregator.go | 5 +- .../internal/aggregator/sumdp.go | 1 - .../internal/customottl/funcs.go | 1 - .../internal/metadata/generated_status.go | 1 - .../internal/metadata/generated_telemetry.go | 9 +- .../internal/model/collectorinstanceinfo.go | 1 - .../internal/model/model.go | 3 +- processor/logstometricsprocessor/processor.go | 7 +- .../logstometricsprocessor/processor_test.go | 116 +++++++++--------- 11 files changed, 68 insertions(+), 78 deletions(-) diff --git a/processor/logstometricsprocessor/doc.go b/processor/logstometricsprocessor/doc.go index 8203f00ba43f2..202874f2602fc 100644 --- a/processor/logstometricsprocessor/doc.go +++ b/processor/logstometricsprocessor/doc.go @@ -7,4 +7,3 @@ // The processor supports extracting Sum, Gauge, Histogram, and Exponential Histogram // metrics from log records and can optionally forward or drop logs after processing. package logstometricsprocessor // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor" - diff --git a/processor/logstometricsprocessor/factory.go b/processor/logstometricsprocessor/factory.go index ba130507438c6..6d749d0e1931c 100644 --- a/processor/logstometricsprocessor/factory.go +++ b/processor/logstometricsprocessor/factory.go @@ -62,4 +62,3 @@ func createLogsProcessor( processorhelper.WithShutdown(p.Shutdown), ) } - diff --git a/processor/logstometricsprocessor/internal/aggregator/aggregator.go b/processor/logstometricsprocessor/internal/aggregator/aggregator.go index d565629034844..d3d40d72041ed 100644 --- a/processor/logstometricsprocessor/internal/aggregator/aggregator.go +++ b/processor/logstometricsprocessor/internal/aggregator/aggregator.go @@ -12,10 +12,10 @@ import ( "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/metadata" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/model" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/model" ) // Aggregator provides a single interface to update all metrics @@ -363,4 +363,3 @@ func getDoubleFromOTTL[K any]( ) } } - diff --git a/processor/logstometricsprocessor/internal/aggregator/sumdp.go b/processor/logstometricsprocessor/internal/aggregator/sumdp.go index 8ea6a34396c71..d32157d5caccd 100644 --- a/processor/logstometricsprocessor/internal/aggregator/sumdp.go +++ b/processor/logstometricsprocessor/internal/aggregator/sumdp.go @@ -53,4 +53,3 @@ func (dp *sumDP) Copy( // TODO determine appropriate start time dest.SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) } - diff --git a/processor/logstometricsprocessor/internal/customottl/funcs.go b/processor/logstometricsprocessor/internal/customottl/funcs.go index 4e7dcff1cb941..1af10acfaecde 100644 --- a/processor/logstometricsprocessor/internal/customottl/funcs.go +++ b/processor/logstometricsprocessor/internal/customottl/funcs.go @@ -16,4 +16,3 @@ func LogFuncs() map[string]ottl.Factory[ottllog.TransformContext] { func commonFuncs[K any]() map[string]ottl.Factory[K] { return ottlfuncs.StandardFuncs[K]() } - diff --git a/processor/logstometricsprocessor/internal/metadata/generated_status.go b/processor/logstometricsprocessor/internal/metadata/generated_status.go index 93a6f8d33f3cc..d89d869bcc527 100644 --- a/processor/logstometricsprocessor/internal/metadata/generated_status.go +++ b/processor/logstometricsprocessor/internal/metadata/generated_status.go @@ -14,4 +14,3 @@ var ( const ( LogsStability = component.StabilityLevelAlpha ) - diff --git a/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go b/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go index 540b1a8b17bcc..900c4a176325f 100644 --- a/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go +++ b/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go @@ -24,13 +24,13 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer { // TelemetryBuilder provides an interface for components to report telemetry // as defined in metadata and user config. type TelemetryBuilder struct { - meter metric.Meter - ProcessorLogstometricsLogsProcessed metric.Int64Counter - ProcessorLogstometricsMetricsExtracted metric.Int64Counter + meter metric.Meter + ProcessorLogstometricsLogsProcessed metric.Int64Counter + ProcessorLogstometricsMetricsExtracted metric.Int64Counter ProcessorLogstometricsErrors metric.Int64Counter ProcessorLogstometricsLogsDropped metric.Int64Counter ProcessorLogstometricsProcessingDuration metric.Int64Histogram - level configtelemetry.Level + level configtelemetry.Level } // telemetryBuilderOption applies changes to default builder. @@ -118,4 +118,3 @@ func (b *TelemetryBuilder) RecordProcessorLogstometricsProcessingDuration(ctx co func (b *TelemetryBuilder) Shutdown() { // No-op for now } - diff --git a/processor/logstometricsprocessor/internal/model/collectorinstanceinfo.go b/processor/logstometricsprocessor/internal/model/collectorinstanceinfo.go index 80e924088acf2..f48ec5cdb5d35 100644 --- a/processor/logstometricsprocessor/internal/model/collectorinstanceinfo.go +++ b/processor/logstometricsprocessor/internal/model/collectorinstanceinfo.go @@ -72,4 +72,3 @@ func (info CollectorInstanceInfo) Copy(to pcommon.Map) { func keyWithPrefix(key string) string { return prefix + "." + key } - diff --git a/processor/logstometricsprocessor/internal/model/model.go b/processor/logstometricsprocessor/internal/model/model.go index d9c7d9c67ec29..559c72eac72c4 100644 --- a/processor/logstometricsprocessor/internal/model/model.go +++ b/processor/logstometricsprocessor/internal/model/model.go @@ -11,8 +11,8 @@ import ( "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/config" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/config" ) type AttributeKeyValue struct { @@ -278,4 +278,3 @@ func parseAttributeConfigs(cfgs []config.Attribute) ([]AttributeKeyValue, error) } return kvs, nil } - diff --git a/processor/logstometricsprocessor/processor.go b/processor/logstometricsprocessor/processor.go index 5c0a72a6da78d..f48b5076a26c5 100644 --- a/processor/logstometricsprocessor/processor.go +++ b/processor/logstometricsprocessor/processor.go @@ -5,6 +5,7 @@ package logstometricsprocessor // import "github.com/open-telemetry/opentelemetr import ( "context" + "errors" "fmt" "time" @@ -87,10 +88,10 @@ type connectorHost interface { GetConnectors() map[component.ID]component.Component } -func (p *logsToMetricsProcessor) Start(ctx context.Context, host component.Host) error { +func (p *logsToMetricsProcessor) Start(_ context.Context, host component.Host) error { ch, ok := host.(connectorHost) if !ok { - return fmt.Errorf("host does not support GetConnectors() method") + return errors.New("host does not support GetConnectors() method") } connectors := ch.GetConnectors() @@ -128,7 +129,7 @@ func (p *logsToMetricsProcessor) Shutdown(context.Context) error { return nil } -func (p *logsToMetricsProcessor) Capabilities() consumer.Capabilities { +func (*logsToMetricsProcessor) Capabilities() consumer.Capabilities { return consumer.Capabilities{MutatesData: false} } diff --git a/processor/logstometricsprocessor/processor_test.go b/processor/logstometricsprocessor/processor_test.go index 6e23faed8d944..4c72047d30a97 100644 --- a/processor/logstometricsprocessor/processor_test.go +++ b/processor/logstometricsprocessor/processor_test.go @@ -23,11 +23,11 @@ import ( "go.opentelemetry.io/collector/processor" "go.opentelemetry.io/collector/processor/processortest" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/config" - "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/metadata" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden" - "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/pmetrictest" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/metadata" ) const testDataDir = "testdata" @@ -41,11 +41,11 @@ func TestProcessorWithLogs(t *testing.T) { "gauge", } - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - for _, tc := range testCases { t.Run(tc, func(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + logTestDataDir := filepath.Join(testDataDir, "logs") inputLogs, err := golden.ReadLogs(filepath.Join(logTestDataDir, "logs.yaml")) require.NoError(t, err) @@ -53,14 +53,14 @@ func TestProcessorWithLogs(t *testing.T) { metricsSink := &consumertest.MetricsSink{} logsSink := &consumertest.LogsSink{} tcTestDataDir := filepath.Join(logTestDataDir, tc) - factory, settings, cfg := setupProcessor(t, tcTestDataDir, metricsSink) + factory, settings, cfg := setupProcessor(t, tcTestDataDir) processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(t, err) expectedMetrics, err := golden.ReadMetrics(filepath.Join(tcTestDataDir, "output.yaml")) require.NoError(t, err) - require.NoError(t, processor.Start(ctx, createTestHost(t, cfg.MetricsConnector, metricsSink))) + require.NoError(t, processor.Start(ctx, createTestHost(cfg.MetricsConnector, metricsSink))) defer func() { require.NoError(t, processor.Shutdown(ctx)) }() @@ -68,18 +68,18 @@ func TestProcessorWithLogs(t *testing.T) { require.NoError(t, processor.ConsumeLogs(ctx, inputLogs)) // Verify metrics were extracted - require.Greater(t, len(metricsSink.AllMetrics()), 0, "expected at least one metrics batch") + require.NotEmpty(t, metricsSink.AllMetrics(), "expected at least one metrics batch") assertAggregatedMetrics(t, expectedMetrics, metricsSink.AllMetrics()[0]) // Verify logs were forwarded (default behavior) - require.Greater(t, len(logsSink.AllLogs()), 0, "expected logs to be forwarded") + require.NotEmpty(t, logsSink.AllLogs(), "expected logs to be forwarded") assertLogsEqual(t, inputLogs, logsSink.AllLogs()[0]) }) } } func TestProcessorDropLogs(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() inputLogs, err := golden.ReadLogs(filepath.Join(testDataDir, "logs", "logs.yaml")) @@ -90,7 +90,7 @@ func TestProcessorDropLogs(t *testing.T) { cfg := &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: true, + DropLogs: true, Logs: []config.MetricInfo{ { Name: "log.count", @@ -105,10 +105,10 @@ func TestProcessorDropLogs(t *testing.T) { factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) - processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) + processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(t, err) - require.NoError(t, processor.Start(ctx, createTestHost(t, cfg.MetricsConnector, metricsSink))) + require.NoError(t, processor.Start(ctx, createTestHost(cfg.MetricsConnector, metricsSink))) defer func() { require.NoError(t, processor.Shutdown(ctx)) }() @@ -116,14 +116,14 @@ func TestProcessorDropLogs(t *testing.T) { require.NoError(t, processor.ConsumeLogs(ctx, inputLogs)) // Verify metrics were extracted - require.Greater(t, len(metricsSink.AllMetrics()), 0, "expected metrics to be extracted") + require.NotEmpty(t, metricsSink.AllMetrics(), "expected metrics to be extracted") // Verify logs were dropped - check that no log records were forwarded require.Equal(t, 0, logsSink.LogRecordCount(), "expected logs to be dropped when drop_logs is true") } func TestProcessorForwardLogs(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() inputLogs, err := golden.ReadLogs(filepath.Join(testDataDir, "logs", "logs.yaml")) @@ -134,7 +134,7 @@ func TestProcessorForwardLogs(t *testing.T) { cfg := &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, // Explicitly set to false + DropLogs: false, // Explicitly set to false Logs: []config.MetricInfo{ { Name: "log.count", @@ -149,10 +149,10 @@ func TestProcessorForwardLogs(t *testing.T) { factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) - processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) + processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(t, err) - require.NoError(t, processor.Start(ctx, createTestHost(t, cfg.MetricsConnector, metricsSink))) + require.NoError(t, processor.Start(ctx, createTestHost(cfg.MetricsConnector, metricsSink))) defer func() { require.NoError(t, processor.Shutdown(ctx)) }() @@ -160,20 +160,20 @@ func TestProcessorForwardLogs(t *testing.T) { require.NoError(t, processor.ConsumeLogs(ctx, inputLogs)) // Verify metrics were extracted - require.Greater(t, len(metricsSink.AllMetrics()), 0, "expected metrics to be extracted") + require.NotEmpty(t, metricsSink.AllMetrics(), "expected metrics to be extracted") // Verify logs were forwarded - require.Greater(t, len(logsSink.AllLogs()), 0, "expected logs to be forwarded when drop_logs is false") + require.NotEmpty(t, logsSink.AllLogs(), "expected logs to be forwarded when drop_logs is false") assertLogsEqual(t, inputLogs, logsSink.AllLogs()[0]) } func TestProcessorMetricsConnectorNotFound(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() cfg := &config.Config{ MetricsConnector: component.MustNewID("nonexistent"), - DropLogs: false, + DropLogs: false, Logs: []config.MetricInfo{ { Name: "log.count", @@ -189,7 +189,7 @@ func TestProcessorMetricsConnectorNotFound(t *testing.T) { factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) logsSink := &consumertest.LogsSink{} - processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) + processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(t, err) // Start should fail because connector is not found @@ -210,7 +210,7 @@ func TestProcessorMetricsConnectorNotFound(t *testing.T) { } func TestProcessorMetricsConnectorError(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() inputLogs, err := golden.ReadLogs(filepath.Join(testDataDir, "logs", "logs.yaml")) @@ -222,7 +222,7 @@ func TestProcessorMetricsConnectorError(t *testing.T) { cfg := &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, + DropLogs: false, Logs: []config.MetricInfo{ { Name: "log.count", @@ -237,10 +237,10 @@ func TestProcessorMetricsConnectorError(t *testing.T) { factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) - processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) + processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(t, err) - host := createTestHostWithErrorConsumer(t, cfg.MetricsConnector, errMetricsConsumer) + host := createTestHostWithErrorConsumer(cfg.MetricsConnector, errMetricsConsumer) require.NoError(t, processor.Start(ctx, host)) defer func() { require.NoError(t, processor.Shutdown(ctx)) @@ -250,14 +250,14 @@ func TestProcessorMetricsConnectorError(t *testing.T) { require.NoError(t, processor.ConsumeLogs(ctx, inputLogs)) // Logs should still be forwarded despite metrics export error - require.Greater(t, len(logsSink.AllLogs()), 0, "expected logs to be forwarded even if metrics export fails") + require.NotEmpty(t, logsSink.AllLogs(), "expected logs to be forwarded even if metrics export fails") } func TestProcessorNoMetricDefinitions(t *testing.T) { cfg := &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, - Logs: []config.MetricInfo{}, // No metric definitions + DropLogs: false, + Logs: []config.MetricInfo{}, // No metric definitions } // Validation should reject empty logs configuration @@ -292,8 +292,8 @@ func TestProcessorInvalidConfig(t *testing.T) { name: "no logs configuration", cfg: &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, - Logs: []config.MetricInfo{}, + DropLogs: false, + Logs: []config.MetricInfo{}, }, expectedErr: "no logs configuration provided", }, @@ -301,11 +301,11 @@ func TestProcessorInvalidConfig(t *testing.T) { name: "invalid metric name", cfg: &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, + DropLogs: false, Logs: []config.MetricInfo{ { Name: "", // Missing name - Description: "Count of log records", + Description: "Count of log records", Sum: configoptional.Some(config.Sum{ Value: "1", }), @@ -326,7 +326,7 @@ func TestProcessorInvalidConfig(t *testing.T) { } func BenchmarkProcessorWithLogs(b *testing.B) { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(b.Context()) defer cancel() metricsSink := &consumertest.MetricsSink{} @@ -334,7 +334,7 @@ func BenchmarkProcessorWithLogs(b *testing.B) { cfg := &config.Config{ MetricsConnector: component.MustNewID("test_connector"), - DropLogs: false, + DropLogs: false, Logs: []config.MetricInfo{ { Name: "log.count", @@ -349,10 +349,10 @@ func BenchmarkProcessorWithLogs(b *testing.B) { factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) - processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) + processor, err := factory.CreateLogs(ctx, settings, cfg, logsSink) require.NoError(b, err) - require.NoError(b, processor.Start(ctx, createTestHost(b, cfg.MetricsConnector, metricsSink))) + require.NoError(b, processor.Start(ctx, createTestHost(cfg.MetricsConnector, metricsSink))) defer func() { require.NoError(b, processor.Shutdown(ctx)) }() @@ -371,8 +371,8 @@ func BenchmarkProcessorWithLogs(b *testing.B) { // Helper functions -func setupProcessor(t testing.TB, testDataDir string, metricsSink *consumertest.MetricsSink) (processor.Factory, processor.Settings, *config.Config) { - t.Helper() +func setupProcessor(tb testing.TB, testDataDir string) (processor.Factory, processor.Settings, *config.Config) { + tb.Helper() factory := NewFactory() settings := processortest.NewNopSettings(metadata.Type) @@ -380,31 +380,31 @@ func setupProcessor(t testing.TB, testDataDir string, metricsSink *consumertest. cfg.MetricsConnector = component.MustNewID("test_connector") conf, err := confmaptest.LoadConf(filepath.Join(testDataDir, "config.yaml")) - require.NoError(t, err) - + require.NoError(tb, err) + // Extract the processor config from under the processor name key sub, err := conf.Sub(metadata.Type.String()) - require.NoError(t, err) - require.NoError(t, sub.Unmarshal(cfg)) + require.NoError(tb, err) + require.NoError(tb, sub.Unmarshal(cfg)) // Override metrics connector for testing cfg.MetricsConnector = component.MustNewID("test_connector") - require.NoError(t, cfg.Validate()) + require.NoError(tb, cfg.Validate()) return factory, settings, cfg } -func createTestHost(t testing.TB, connectorID component.ID, metricsSink *consumertest.MetricsSink) component.Host { +func createTestHost(connectorID component.ID, metricsSink *consumertest.MetricsSink) component.Host { // Create a metrics router with the sink router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{ pipeline.NewID(pipeline.SignalMetrics): metricsSink, }) - + // Create a test connector that embeds the router testConnector := &testMetricsConnector{ MetricsRouterAndConsumer: router, } - + return &testHost{ connectors: map[component.ID]component.Component{ connectorID: testConnector, @@ -412,17 +412,17 @@ func createTestHost(t testing.TB, connectorID component.ID, metricsSink *consume } } -func createTestHostWithErrorConsumer(t testing.TB, connectorID component.ID, errConsumer consumer.Metrics) component.Host { +func createTestHostWithErrorConsumer(connectorID component.ID, errConsumer consumer.Metrics) component.Host { // Create a metrics router with the error consumer router := connector.NewMetricsRouter(map[pipeline.ID]consumer.Metrics{ pipeline.NewID(pipeline.SignalMetrics): errConsumer, }) - + // Create a test connector that embeds the router testConnector := &testMetricsConnector{ MetricsRouterAndConsumer: router, } - + return &testHost{ connectors: map[component.ID]component.Component{ connectorID: testConnector, @@ -436,15 +436,15 @@ type testMetricsConnector struct { connector.MetricsRouterAndConsumer } -func (t *testMetricsConnector) Start(ctx context.Context, host component.Host) error { +func (*testMetricsConnector) Start(_ context.Context, _ component.Host) error { return nil } -func (t *testMetricsConnector) Shutdown(ctx context.Context) error { +func (*testMetricsConnector) Shutdown(_ context.Context) error { return nil } -func (t *testMetricsConnector) Capabilities() consumer.Capabilities { +func (*testMetricsConnector) Capabilities() consumer.Capabilities { return consumer.Capabilities{MutatesData: false} } @@ -467,11 +467,11 @@ func (h *testHost) GetConnectors() map[component.ID]component.Component { return h.connectors } -func (h *testHost) GetExtensions() map[component.ID]component.Component { +func (*testHost) GetExtensions() map[component.ID]component.Component { return nil } -func (h *testHost) GetFactory(kind component.Kind, componentType component.Type) component.Factory { +func (*testHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { return nil } @@ -491,5 +491,3 @@ func assertAggregatedMetrics(t *testing.T, expected, actual pmetric.Metrics) { func assertLogsEqual(t *testing.T, expected, actual plog.Logs) { assert.NoError(t, plogtest.CompareLogs(expected, actual, plogtest.IgnoreObservedTimestamp())) } - - From 4c8d3983704974bd097b44c733f8993092a41306 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 22:35:27 -0700 Subject: [PATCH 28/41] fix(loadbalancingexporter): preserve log batching semantics --- exporter/loadbalancingexporter/helpers.go | 14 ++++- exporter/loadbalancingexporter/log_batcher.go | 7 +-- .../loadbalancingexporter/log_batcher_test.go | 32 +++++++++++ .../loadbalancingexporter/log_exporter.go | 8 ++- .../log_exporter_test.go | 55 +++++++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) diff --git a/exporter/loadbalancingexporter/helpers.go b/exporter/loadbalancingexporter/helpers.go index ef8e19bb18b21..42c6300585ecd 100644 --- a/exporter/loadbalancingexporter/helpers.go +++ b/exporter/loadbalancingexporter/helpers.go @@ -14,8 +14,18 @@ func mergeTraces(t1, t2 ptrace.Traces) ptrace.Traces { return t1 } -// mergeLogs concatenates two plog.Logs into a single plog.Logs. +// mergeLogs combines two plog.Logs into a single plog.Logs while reusing +// matching resource/scope structures so serialized size reflects the true +// merged OTLP payload. func mergeLogs(l1, l2 plog.Logs) plog.Logs { - l2.ResourceLogs().MoveAndAppendTo(l1.ResourceLogs()) + for i := 0; i < l2.ResourceLogs().Len(); i++ { + srcRL := l2.ResourceLogs().At(i) + for j := 0; j < srcRL.ScopeLogs().Len(); j++ { + srcSL := srcRL.ScopeLogs().At(j) + for k := 0; k < srcSL.LogRecords().Len(); k++ { + insertLogRecord(l1, srcRL, srcSL, srcSL.LogRecords().At(k)) + } + } + } return l1 } diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 2ac40dc8443b2..6cf4fb37bcf08 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -341,11 +341,10 @@ func (b *backendLogBatcher) run() { switch req.kind { case logBatcherRequestEnqueue: recordCount := req.logs.LogRecordCount() - // Measure incoming chunk size before merge; proto repeated fields - // are additive so incremental accounting is accurate and avoids - // O(n²) re-serialization of the full pending batch on every enqueue. - pendingBytes += sizer.LogsSize(req.logs) pending = mergeLogs(pending, req.logs) + // Track max_bytes using the actual serialized merged OTLP payload. + // Resource/scope dedup during merge means chunk sizes are not additive. + pendingBytes = sizer.LogsSize(pending) pendingRecords += recordCount b.pendingRecords.Store(int64(pendingRecords)) b.pendingBytes.Store(int64(pendingBytes)) diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 8e2315776d62c..80c8062bca7c9 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -99,6 +99,38 @@ func TestLogBatcherFlushesOnMaxBytes(t *testing.T) { }, time.Second, 10*time.Millisecond) } +func TestLogBatcherMaxBytesUsesMergedPayloadSize(t *testing.T) { + ts, tb := getTelemetryAssets(t) + sink := new(consumertest.LogsSink) + p, _ := newTestLogsExporter(t, ts, tb, simpleConfig(), func(_ context.Context, _ string) (component.Component, error) { + return newMockLogsExporter(sink.ConsumeLogs), nil + }) + + first := sharedResourceScopeLog("first") + second := sharedResourceScopeLog("second") + sizer := &plog.ProtoMarshaler{} + separateBytes := sizer.LogsSize(first) + sizer.LogsSize(second) + merged := mergeLogs(sharedResourceScopeLog("first"), sharedResourceScopeLog("second")) + mergedBytes := sizer.LogsSize(merged) + require.Greater(t, separateBytes, mergedBytes) + + p.batcher, _ = newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: mergedBytes + 1, + flushInterval: time.Hour, + }, p.consumeBatch) + p.loadBalancer.onExporterRemove = func(ctx context.Context, endpoint string, exp *wrappedExporter) error { + return p.batcher.Remove(ctx, endpoint, exp) + } + + require.NoError(t, p.Start(t.Context(), componenttest.NewNopHost())) + require.NoError(t, p.ConsumeLogs(t.Context(), mergeLogs(sharedResourceScopeLog("first"), sharedResourceScopeLog("second")))) + assert.Empty(t, sink.AllLogs()) + require.NoError(t, p.Shutdown(t.Context())) + require.Len(t, sink.AllLogs(), 1) + assert.Equal(t, 2, sink.AllLogs()[0].LogRecordCount()) +} + func TestLogBatcherDoesNotBlockOtherBackends(t *testing.T) { ts, _ := getTelemetryAssets(t) blockFirst := make(chan struct{}) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 5ae8baef8aab1..e0a79741110bc 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -133,6 +133,7 @@ func (e *logExporterImp) consumeLogsBatched(ctx context.Context, ld plog.Logs) e func (e *logExporterImp) groupLogsByEndpoint(ld plog.Logs) (map[string]*endpointBatch, error) { batches := make(map[string]*endpointBatch) + emptyTraceFallbackKeys := make(map[[2]int]pcommon.TraceID) var errs error for i := 0; i < ld.ResourceLogs().Len(); i++ { @@ -145,7 +146,12 @@ func (e *logExporterImp) groupLogsByEndpoint(ld plog.Logs) (map[string]*endpoint traceID := rec.TraceID() balancingKey := traceID if traceID == pcommon.NewTraceIDEmpty() { - balancingKey = random() + scopeKey := [2]int{i, j} + balancingKey = emptyTraceFallbackKeys[scopeKey] + if balancingKey == pcommon.NewTraceIDEmpty() { + balancingKey = random() + emptyTraceFallbackKeys[scopeKey] = balancingKey + } } le, endpoint, err := e.loadBalancer.exporterAndEndpoint(balancingKey[:]) diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 501a74a0f273a..29bd266c47218 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -398,6 +398,31 @@ func TestLogsWithoutTraceID(t *testing.T) { assert.Len(t, sink.AllLogs(), 1) } +func TestGroupLogsByEndpointKeepsEmptyTraceLogsTogetherPerScope(t *testing.T) { + ts, tb := getTelemetryAssets(t) + lb, err := newLoadBalancer(ts.Logger, simpleConfig(), nil, tb) + require.NoError(t, err) + + first := newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317") + second := newWrappedExporter(newNopMockLogsExporter(), "endpoint-2:4317") + lb.ring = newHashRing([]string{"endpoint-1", "endpoint-2"}) + lb.exporters = map[string]*wrappedExporter{ + "endpoint-1:4317": first, + "endpoint-2:4317": second, + } + + p, err := newLogsExporter(ts, simpleConfig()) + require.NoError(t, err) + p.loadBalancer = lb + + batches, groupErr := p.groupLogsByEndpoint(sharedScopeLogsWithoutTraceIDs("first", "second")) + require.NoError(t, groupErr) + require.Len(t, batches, 1) + for _, batch := range batches { + assert.Equal(t, 2, batch.logs.LogRecordCount()) + } +} + func TestConsumeLogLegacyPathIgnoresStoppingGate(t *testing.T) { ts, tb := getTelemetryAssets(t) logsConsumed := atomic.Int64{} @@ -753,6 +778,36 @@ func simpleLogWithID(id pcommon.TraceID) plog.Logs { return logs } +func sharedResourceScopeLog(body string) plog.Logs { + logs := plog.NewLogs() + rl := logs.ResourceLogs().AppendEmpty() + for i := 0; i < 8; i++ { + rl.Resource().Attributes().PutStr(fmt.Sprintf("resource-%d", i), "shared-resource-value") + } + sl := rl.ScopeLogs().AppendEmpty() + sl.Scope().SetName("shared-scope") + for i := 0; i < 8; i++ { + sl.Scope().Attributes().PutStr(fmt.Sprintf("scope-%d", i), "shared-scope-value") + } + rec := sl.LogRecords().AppendEmpty() + rec.Body().SetStr(body) + rec.SetTraceID(pcommon.TraceID([16]byte{1, 2, 3, 4})) + return logs +} + +func sharedScopeLogsWithoutTraceIDs(bodies ...string) plog.Logs { + logs := plog.NewLogs() + rl := logs.ResourceLogs().AppendEmpty() + rl.Resource().Attributes().PutStr("resource", "shared") + sl := rl.ScopeLogs().AppendEmpty() + sl.Scope().SetName("shared-scope") + for _, body := range bodies { + rec := sl.LogRecords().AppendEmpty() + rec.Body().SetStr(body) + } + return logs +} + func simpleLogWithoutID() plog.Logs { logs := plog.NewLogs() rl := logs.ResourceLogs().AppendEmpty() From 482352f4f3057548eae8ba3d866da200f48db079 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 22:45:13 -0700 Subject: [PATCH 29/41] build(loadbalancingexporter): tidy module deps --- exporter/loadbalancingexporter/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/loadbalancingexporter/go.mod b/exporter/loadbalancingexporter/go.mod index 04283173b242a..5b411ea144a20 100644 --- a/exporter/loadbalancingexporter/go.mod +++ b/exporter/loadbalancingexporter/go.mod @@ -24,6 +24,7 @@ require ( go.opentelemetry.io/collector/consumer/consumertest v0.140.0 go.opentelemetry.io/collector/exporter v1.46.0 go.opentelemetry.io/collector/exporter/exporterhelper v0.140.0 + go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.140.0 go.opentelemetry.io/collector/exporter/exportertest v0.140.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.140.0 go.opentelemetry.io/collector/otelcol/otelcoltest v0.140.0 @@ -133,7 +134,6 @@ require ( go.opentelemetry.io/collector/connector/xconnector v0.140.0 // indirect go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.140.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.140.0 // indirect - go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.140.0 // indirect go.opentelemetry.io/collector/exporter/xexporter v0.140.0 // indirect go.opentelemetry.io/collector/extension v1.46.0 // indirect go.opentelemetry.io/collector/extension/extensionauth v1.46.0 // indirect From 102660eb048ab4c42b6f7e1664cada86d2c2db45 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 22:51:03 -0700 Subject: [PATCH 30/41] perf(loadbalancingexporter): reduce log batching hot-path work --- exporter/loadbalancingexporter/helpers.go | 20 ++++++- exporter/loadbalancingexporter/log_batcher.go | 53 +++++++++++++++++-- .../loadbalancingexporter/log_exporter.go | 16 ++---- 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/exporter/loadbalancingexporter/helpers.go b/exporter/loadbalancingexporter/helpers.go index 42c6300585ecd..276978849e736 100644 --- a/exporter/loadbalancingexporter/helpers.go +++ b/exporter/loadbalancingexporter/helpers.go @@ -20,12 +20,30 @@ func mergeTraces(t1, t2 ptrace.Traces) ptrace.Traces { func mergeLogs(l1, l2 plog.Logs) plog.Logs { for i := 0; i < l2.ResourceLogs().Len(); i++ { srcRL := l2.ResourceLogs().At(i) + targetRL := findOrCreateResourceLogs(l1, srcRL) for j := 0; j < srcRL.ScopeLogs().Len(); j++ { srcSL := srcRL.ScopeLogs().At(j) + targetSL := findOrCreateScopeLogs(targetRL, srcSL) for k := 0; k < srcSL.LogRecords().Len(); k++ { - insertLogRecord(l1, srcRL, srcSL, srcSL.LogRecords().At(k)) + srcSL.LogRecords().At(k).CopyTo(targetSL.LogRecords().AppendEmpty()) } } } return l1 } + +func resourceLogsMatches(rl, src plog.ResourceLogs) bool { + srcRes := src.Resource() + return rl.SchemaUrl() == src.SchemaUrl() && + rl.Resource().DroppedAttributesCount() == srcRes.DroppedAttributesCount() && + rl.Resource().Attributes().Equal(srcRes.Attributes()) +} + +func scopeLogsMatches(sl, src plog.ScopeLogs) bool { + srcScope := src.Scope() + return sl.SchemaUrl() == src.SchemaUrl() && + sl.Scope().Name() == srcScope.Name() && + sl.Scope().Version() == srcScope.Version() && + sl.Scope().DroppedAttributesCount() == srcScope.DroppedAttributesCount() && + sl.Scope().Attributes().Equal(srcScope.Attributes()) +} diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 6cf4fb37bcf08..5cd67ac589dfc 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -334,18 +334,46 @@ func (b *backendLogBatcher) run() { pendingBytes := 0 pendingRecords := 0 sizer := &plog.ProtoMarshaler{} + var nextReq *logBatcherRequest for { + if nextReq != nil { + req := *nextReq + nextReq = nil + switch req.kind { + case logBatcherRequestEnqueue: + pendingRecords += b.mergeQueuedRequests(&pending, req, &nextReq) + // Track max_bytes using the actual serialized merged OTLP payload. + // Resource/scope dedup during merge means chunk sizes are not additive. + pendingBytes = sizer.LogsSize(pending) + b.pendingRecords.Store(int64(pendingRecords)) + b.pendingBytes.Store(int64(pendingBytes)) + if pendingRecords > 0 && timerC == nil { + timer.Reset(b.settings.flushInterval) + timerC = timer.C + } + if pendingRecords >= b.settings.maxRecords || pendingBytes >= b.settings.maxBytes { + b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonSize, timer, &timerC); err != nil { + b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonSize), zap.Error(err)) + } + } + case logBatcherRequestFlushAndStop: + err := b.flush(req.ctx, &pending, &pendingRecords, &pendingBytes, req.reason, timer, &timerC) + req.done <- err + return + } + continue + } + select { case req := <-b.requests: switch req.kind { case logBatcherRequestEnqueue: - recordCount := req.logs.LogRecordCount() - pending = mergeLogs(pending, req.logs) + pendingRecords += b.mergeQueuedRequests(&pending, req, &nextReq) // Track max_bytes using the actual serialized merged OTLP payload. // Resource/scope dedup during merge means chunk sizes are not additive. pendingBytes = sizer.LogsSize(pending) - pendingRecords += recordCount b.pendingRecords.Store(int64(pendingRecords)) b.pendingBytes.Store(int64(pendingBytes)) if pendingRecords > 0 && timerC == nil { @@ -371,6 +399,25 @@ func (b *backendLogBatcher) run() { } } +func (b *backendLogBatcher) mergeQueuedRequests(pending *plog.Logs, first logBatcherRequest, nextReq **logBatcherRequest) int { + recordCount := first.logs.LogRecordCount() + *pending = mergeLogs(*pending, first.logs) + + for { + select { + case req := <-b.requests: + if req.kind != logBatcherRequestEnqueue { + *nextReq = &req + return recordCount + } + recordCount += req.logs.LogRecordCount() + *pending = mergeLogs(*pending, req.logs) + default: + return recordCount + } + } +} + func (b *backendLogBatcher) flush( ctx context.Context, pending *plog.Logs, diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index e0a79741110bc..4574ca9d311d4 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -242,39 +242,31 @@ func insertLogRecord(dest plog.Logs, srcRL plog.ResourceLogs, srcSL plog.ScopeLo func findOrCreateResourceLogs(dest plog.Logs, src plog.ResourceLogs) plog.ResourceLogs { srcRes := src.Resource() - srcSchema := src.SchemaUrl() for i := 0; i < dest.ResourceLogs().Len(); i++ { rl := dest.ResourceLogs().At(i) - if rl.SchemaUrl() == srcSchema && - rl.Resource().DroppedAttributesCount() == srcRes.DroppedAttributesCount() && - rl.Resource().Attributes().Equal(srcRes.Attributes()) { + if resourceLogsMatches(rl, src) { return rl } } rl := dest.ResourceLogs().AppendEmpty() srcRes.CopyTo(rl.Resource()) rl.Resource().SetDroppedAttributesCount(srcRes.DroppedAttributesCount()) - rl.SetSchemaUrl(srcSchema) + rl.SetSchemaUrl(src.SchemaUrl()) return rl } func findOrCreateScopeLogs(rl plog.ResourceLogs, src plog.ScopeLogs) plog.ScopeLogs { srcScope := src.Scope() - srcSchema := src.SchemaUrl() for i := 0; i < rl.ScopeLogs().Len(); i++ { sl := rl.ScopeLogs().At(i) - if sl.SchemaUrl() == srcSchema && - sl.Scope().Name() == srcScope.Name() && - sl.Scope().Version() == srcScope.Version() && - sl.Scope().DroppedAttributesCount() == srcScope.DroppedAttributesCount() && - sl.Scope().Attributes().Equal(srcScope.Attributes()) { + if scopeLogsMatches(sl, src) { return sl } } sl := rl.ScopeLogs().AppendEmpty() srcScope.CopyTo(sl.Scope()) sl.Scope().SetDroppedAttributesCount(srcScope.DroppedAttributesCount()) - sl.SetSchemaUrl(srcSchema) + sl.SetSchemaUrl(src.SchemaUrl()) return sl } From d33293add1f75cca43e9f0800ce74bd640a6264a Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 23:00:34 -0700 Subject: [PATCH 31/41] build(distributions): regenerate contrib report --- reports/distributions/contrib.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reports/distributions/contrib.yaml b/reports/distributions/contrib.yaml index c1f9dce9d29ea..42061d837f56c 100644 --- a/reports/distributions/contrib.yaml +++ b/reports/distributions/contrib.yaml @@ -115,6 +115,7 @@ components: - isolationforest - k8sattributes - logdedup + - logstometricsprocessor - metricsgeneration - metricstarttime - metricstransform @@ -123,7 +124,6 @@ components: - remotetap - resource - resourcedetection - - logstometricsprocessor - span - sumologic - tail_sampling From a63a2805aeadae03c5138b9b8354d8b444ead9fd Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 23:13:00 -0700 Subject: [PATCH 32/41] fix(loadbalancingexporter): bound batched enqueue coalescing --- .codecov.yml | 8 ++ exporter/loadbalancingexporter/log_batcher.go | 84 +++++++++---------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index ac4fe38d5f29e..9664f8f8e3924 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -480,6 +480,10 @@ component_management: name: processor_groupbytrace paths: - processor/groupbytraceprocessor/** + - component_id: processor_hotreload + name: processor_hotreload + paths: + - processor/hotreloadprocessor/** - component_id: processor_interval name: processor_interval paths: @@ -496,6 +500,10 @@ component_management: name: processor_logdedup paths: - processor/logdedupprocessor/** + - component_id: processor_logstometrics + name: processor_logstometrics + paths: + - processor/logstometricsprocessor/** - component_id: processor_logstransform name: processor_logstransform paths: diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 5cd67ac589dfc..87e411685ecb5 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -340,27 +340,7 @@ func (b *backendLogBatcher) run() { if nextReq != nil { req := *nextReq nextReq = nil - switch req.kind { - case logBatcherRequestEnqueue: - pendingRecords += b.mergeQueuedRequests(&pending, req, &nextReq) - // Track max_bytes using the actual serialized merged OTLP payload. - // Resource/scope dedup during merge means chunk sizes are not additive. - pendingBytes = sizer.LogsSize(pending) - b.pendingRecords.Store(int64(pendingRecords)) - b.pendingBytes.Store(int64(pendingBytes)) - if pendingRecords > 0 && timerC == nil { - timer.Reset(b.settings.flushInterval) - timerC = timer.C - } - if pendingRecords >= b.settings.maxRecords || pendingBytes >= b.settings.maxBytes { - b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) - if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonSize, timer, &timerC); err != nil { - b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonSize), zap.Error(err)) - } - } - case logBatcherRequestFlushAndStop: - err := b.flush(req.ctx, &pending, &pendingRecords, &pendingBytes, req.reason, timer, &timerC) - req.done <- err + if b.handleRequest(req, sizer, &pending, &pendingRecords, &pendingBytes, &nextReq, timer, &timerC) { return } continue @@ -368,27 +348,7 @@ func (b *backendLogBatcher) run() { select { case req := <-b.requests: - switch req.kind { - case logBatcherRequestEnqueue: - pendingRecords += b.mergeQueuedRequests(&pending, req, &nextReq) - // Track max_bytes using the actual serialized merged OTLP payload. - // Resource/scope dedup during merge means chunk sizes are not additive. - pendingBytes = sizer.LogsSize(pending) - b.pendingRecords.Store(int64(pendingRecords)) - b.pendingBytes.Store(int64(pendingBytes)) - if pendingRecords > 0 && timerC == nil { - timer.Reset(b.settings.flushInterval) - timerC = timer.C - } - if pendingRecords >= b.settings.maxRecords || pendingBytes >= b.settings.maxBytes { - b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) - if err := b.flush(context.Background(), &pending, &pendingRecords, &pendingBytes, logFlushReasonSize, timer, &timerC); err != nil { - b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonSize), zap.Error(err)) - } - } - case logBatcherRequestFlushAndStop: - err := b.flush(req.ctx, &pending, &pendingRecords, &pendingBytes, req.reason, timer, &timerC) - req.done <- err + if b.handleRequest(req, sizer, &pending, &pendingRecords, &pendingBytes, &nextReq, timer, &timerC) { return } case <-timerC: @@ -399,11 +359,47 @@ func (b *backendLogBatcher) run() { } } +func (b *backendLogBatcher) handleRequest( + req logBatcherRequest, + sizer *plog.ProtoMarshaler, + pending *plog.Logs, + pendingRecords *int, + pendingBytes *int, + nextReq **logBatcherRequest, + timer *time.Timer, + timerC *<-chan time.Time, +) bool { + switch req.kind { + case logBatcherRequestEnqueue: + *pendingRecords += b.mergeQueuedRequests(pending, req, nextReq) + // Track max_bytes using the actual serialized merged OTLP payload. + // Resource/scope dedup during merge means chunk sizes are not additive. + *pendingBytes = sizer.LogsSize(*pending) + b.pendingRecords.Store(int64(*pendingRecords)) + b.pendingBytes.Store(int64(*pendingBytes)) + if *pendingRecords > 0 && *timerC == nil { + timer.Reset(b.settings.flushInterval) + *timerC = timer.C + } + if *pendingRecords >= b.settings.maxRecords || *pendingBytes >= b.settings.maxBytes { + b.telemetry.overflowTotal.Add(context.Background(), 1, metric.WithAttributeSet(attribute.NewSet(attribute.String("endpoint", b.endpoint)))) + if err := b.flush(context.Background(), pending, pendingRecords, pendingBytes, logFlushReasonSize, timer, timerC); err != nil { + b.logger.Warn("failed to flush log batch", zap.String("reason", logFlushReasonSize), zap.Error(err)) + } + } + case logBatcherRequestFlushAndStop: + err := b.flush(req.ctx, pending, pendingRecords, pendingBytes, req.reason, timer, timerC) + req.done <- err + return true + } + return false +} + func (b *backendLogBatcher) mergeQueuedRequests(pending *plog.Logs, first logBatcherRequest, nextReq **logBatcherRequest) int { recordCount := first.logs.LogRecordCount() *pending = mergeLogs(*pending, first.logs) - for { + for i := 0; i < cap(b.requests); i++ { select { case req := <-b.requests: if req.kind != logBatcherRequestEnqueue { @@ -416,6 +412,8 @@ func (b *backendLogBatcher) mergeQueuedRequests(pending *plog.Logs, first logBat return recordCount } } + + return recordCount } func (b *backendLogBatcher) flush( From 8fbcbf14d31782fc8684f27362eb59c0ac4976fd Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 23:24:46 -0700 Subject: [PATCH 33/41] fix(hotreloadprocessor): sort generated metadata --- processor/hotreloadprocessor/documentation.md | 119 +++++++++---- .../generated_component_test.go | 167 ++++++++++++++++++ .../internal/metadata/generated_status.go | 2 +- .../internal/metadata/generated_telemetry.go | 22 +-- .../metadata/generated_telemetry_test.go | 4 +- .../metadatatest/generated_telemetrytest.go | 18 +- processor/hotreloadprocessor/metadata.yaml | 84 +++++---- 7 files changed, 324 insertions(+), 92 deletions(-) create mode 100644 processor/hotreloadprocessor/generated_component_test.go diff --git a/processor/hotreloadprocessor/documentation.md b/processor/hotreloadprocessor/documentation.md index a762931145c43..37e4e9c4a1c73 100644 --- a/processor/hotreloadprocessor/documentation.md +++ b/processor/hotreloadprocessor/documentation.md @@ -8,72 +8,119 @@ The following telemetry is emitted by this component. ### otelcol_processor_hot_reload_config_refresh_interval -Refresh interval of the hotreloadprocessor +Refresh interval of the hotreloadprocessor [Development] -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | ### otelcol_processor_hot_reload_config_shutdown_delay -Shutdown delay of the hotreloadprocessor +Shutdown delay of the hotreloadprocessor [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Gauge | Int | Development | + +#### Attributes -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Gauge | Int | +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | ### otelcol_processor_hot_reload_newest_file_failed_timestamp -The timestamp of the newest file failed to load by the hotreloadprocessor in seconds +The timestamp of the newest file failed to load by the hotreloadprocessor in seconds [Development] -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Int | Development | + +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | +| key | The key of the file being scanned | Any Str | +| reason | The reason for the error | Any Str | ### otelcol_processor_hot_reload_newest_file_success_timestamp -The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds +The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Int | Development | -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Int | +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | +| key | The key of the file being scanned | Any Str | ### otelcol_processor_hot_reload_process_duration -Duration of the processLogs function by the hotreloadprocessor +Duration of the processLogs function by the hotreloadprocessor [Development] -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Histogram | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Histogram | Int | Development | ### otelcol_processor_hot_reload_reload_duration -Duration of the reload by the hotreloadprocessor +Duration of the reload by the hotreloadprocessor [Development] -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| ms | Histogram | Int | +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Histogram | Int | Development | ### otelcol_processor_hot_reload_rollback_file_success_timestamp -The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds +The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Int | Development | -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Int | +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | +| key | The key of the file being scanned | Any Str | +| reason | The reason for the error | Any Str | ### otelcol_processor_hot_reload_running_processors_count -Number of running processors in the hotreloadprocessor +Number of running processors in the hotreloadprocessor [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Int | Development | -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Int | +#### Attributes + +| Name | Description | Values | +| ---- | ----------- | ------ | +| destination | The destination of the hotreloadprocessor | Any Str | ### otelcol_processor_hot_reload_scan -Number of scanned prefixes by the hotreloadprocessor +Number of scanned prefixes by the hotreloadprocessor [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| 1 | Gauge | Int | Development | + +#### Attributes -| Unit | Metric Type | Value Type | -| ---- | ----------- | ---------- | -| 1 | Gauge | Int | +| Name | Description | Values | +| ---- | ----------- | ------ | +| prefix | The prefix of the file being scanned | Any Str | diff --git a/processor/hotreloadprocessor/generated_component_test.go b/processor/hotreloadprocessor/generated_component_test.go new file mode 100644 index 0000000000000..166980832590f --- /dev/null +++ b/processor/hotreloadprocessor/generated_component_test.go @@ -0,0 +1,167 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package hotreloadprocessor + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/pdata/ptrace" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/processortest" +) + +var typ = component.MustNewType("hotreloadprocessor") + +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, typ, NewFactory().Type()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + tests := []struct { + createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) + name string + }{ + + { + name: "logs", + createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop()) + }, + }, + + { + name: "metrics", + createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateMetrics(ctx, set, cfg, consumertest.NewNop()) + }, + }, + + { + name: "traces", + createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateTraces(ctx, set, cfg, consumertest.NewNop()) + }, + }, + } + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, sub.Unmarshal(&cfg)) + + for _, tt := range tests { + t.Run(tt.name+"-shutdown", func(t *testing.T) { + c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run(tt.name+"-lifecycle", func(t *testing.T) { + c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg) + require.NoError(t, err) + host := newMdatagenNopHost() + err = c.Start(context.Background(), host) + require.NoError(t, err) + require.NotPanics(t, func() { + switch tt.name { + case "logs": + e, ok := c.(processor.Logs) + require.True(t, ok) + logs := generateLifecycleTestLogs() + if !e.Capabilities().MutatesData { + logs.MarkReadOnly() + } + err = e.ConsumeLogs(context.Background(), logs) + case "metrics": + e, ok := c.(processor.Metrics) + require.True(t, ok) + metrics := generateLifecycleTestMetrics() + if !e.Capabilities().MutatesData { + metrics.MarkReadOnly() + } + err = e.ConsumeMetrics(context.Background(), metrics) + case "traces": + e, ok := c.(processor.Traces) + require.True(t, ok) + traces := generateLifecycleTestTraces() + if !e.Capabilities().MutatesData { + traces.MarkReadOnly() + } + err = e.ConsumeTraces(context.Background(), traces) + } + }) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + } +} + +func generateLifecycleTestLogs() plog.Logs { + logs := plog.NewLogs() + rl := logs.ResourceLogs().AppendEmpty() + rl.Resource().Attributes().PutStr("resource", "R1") + l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty() + l.Body().SetStr("test log message") + l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return logs +} + +func generateLifecycleTestMetrics() pmetric.Metrics { + metrics := pmetric.NewMetrics() + rm := metrics.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("resource", "R1") + m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty() + m.SetName("test_metric") + dp := m.SetEmptyGauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("test_attr", "value_1") + dp.SetIntValue(123) + dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return metrics +} + +func generateLifecycleTestTraces() ptrace.Traces { + traces := ptrace.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + rs.Resource().Attributes().PutStr("resource", "R1") + span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty() + span.Attributes().PutStr("test_attr", "value_1") + span.SetName("test_span") + span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second))) + span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return traces +} + +var _ component.Host = (*mdatagenNopHost)(nil) + +type mdatagenNopHost struct{} + +func newMdatagenNopHost() component.Host { + return &mdatagenNopHost{} +} + +func (mnh *mdatagenNopHost) GetExtensions() map[component.ID]component.Component { + return nil +} + +func (mnh *mdatagenNopHost) GetFactory(_ component.Kind, _ component.Type) component.Factory { + return nil +} diff --git a/processor/hotreloadprocessor/internal/metadata/generated_status.go b/processor/hotreloadprocessor/internal/metadata/generated_status.go index d9ce275294dd8..6722840d4f475 100644 --- a/processor/hotreloadprocessor/internal/metadata/generated_status.go +++ b/processor/hotreloadprocessor/internal/metadata/generated_status.go @@ -8,7 +8,7 @@ import ( var ( Type = component.MustNewType("hotreloadprocessor") - ScopeName = "go.opentelemetry.io/collector/cmd/mdatagen" + ScopeName = "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor" ) const ( diff --git a/processor/hotreloadprocessor/internal/metadata/generated_telemetry.go b/processor/hotreloadprocessor/internal/metadata/generated_telemetry.go index 53b52b194fc46..fe37f943689c0 100644 --- a/processor/hotreloadprocessor/internal/metadata/generated_telemetry.go +++ b/processor/hotreloadprocessor/internal/metadata/generated_telemetry.go @@ -13,11 +13,11 @@ import ( ) func Meter(settings component.TelemetrySettings) metric.Meter { - return settings.MeterProvider.Meter("go.opentelemetry.io/collector/cmd/mdatagen") + return settings.MeterProvider.Meter("github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor") } func Tracer(settings component.TelemetrySettings) trace.Tracer { - return settings.TracerProvider.Tracer("go.opentelemetry.io/collector/cmd/mdatagen") + return settings.TracerProvider.Tracer("github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor") } // TelemetryBuilder provides an interface for components to report telemetry @@ -68,57 +68,57 @@ func NewTelemetryBuilder(settings component.TelemetrySettings, options ...Teleme var err, errs error builder.ProcessorHotReloadConfigRefreshInterval, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_config_refresh_interval", - metric.WithDescription("Refresh interval of the hotreloadprocessor"), + metric.WithDescription("Refresh interval of the hotreloadprocessor [Development]"), metric.WithUnit("ms"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadConfigShutdownDelay, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_config_shutdown_delay", - metric.WithDescription("Shutdown delay of the hotreloadprocessor"), + metric.WithDescription("Shutdown delay of the hotreloadprocessor [Development]"), metric.WithUnit("ms"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadNewestFileFailedTimestamp, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_newest_file_failed_timestamp", - metric.WithDescription("The timestamp of the newest file failed to load by the hotreloadprocessor in seconds"), + metric.WithDescription("The timestamp of the newest file failed to load by the hotreloadprocessor in seconds [Development]"), metric.WithUnit("1"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadNewestFileSuccessTimestamp, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_newest_file_success_timestamp", - metric.WithDescription("The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds"), + metric.WithDescription("The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds [Development]"), metric.WithUnit("1"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadProcessDuration, err = builder.meter.Int64Histogram( "otelcol_processor_hot_reload_process_duration", - metric.WithDescription("Duration of the processLogs function by the hotreloadprocessor"), + metric.WithDescription("Duration of the processLogs function by the hotreloadprocessor [Development]"), metric.WithUnit("ms"), metric.WithExplicitBucketBoundaries([]float64{1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000}...), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadReloadDuration, err = builder.meter.Int64Histogram( "otelcol_processor_hot_reload_reload_duration", - metric.WithDescription("Duration of the reload by the hotreloadprocessor"), + metric.WithDescription("Duration of the reload by the hotreloadprocessor [Development]"), metric.WithUnit("ms"), metric.WithExplicitBucketBoundaries([]float64{1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000}...), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadRollbackFileSuccessTimestamp, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_rollback_file_success_timestamp", - metric.WithDescription("The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds"), + metric.WithDescription("The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds [Development]"), metric.WithUnit("1"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadRunningProcessorsCount, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_running_processors_count", - metric.WithDescription("Number of running processors in the hotreloadprocessor"), + metric.WithDescription("Number of running processors in the hotreloadprocessor [Development]"), metric.WithUnit("1"), ) errs = errors.Join(errs, err) builder.ProcessorHotReloadScan, err = builder.meter.Int64Gauge( "otelcol_processor_hot_reload_scan", - metric.WithDescription("Number of scanned prefixes by the hotreloadprocessor"), + metric.WithDescription("Number of scanned prefixes by the hotreloadprocessor [Development]"), metric.WithUnit("1"), ) errs = errors.Join(errs, err) diff --git a/processor/hotreloadprocessor/internal/metadata/generated_telemetry_test.go b/processor/hotreloadprocessor/internal/metadata/generated_telemetry_test.go index 64692ab1bda0a..22790de5d1dd5 100644 --- a/processor/hotreloadprocessor/internal/metadata/generated_telemetry_test.go +++ b/processor/hotreloadprocessor/internal/metadata/generated_telemetry_test.go @@ -50,14 +50,14 @@ func TestProviders(t *testing.T) { meter := Meter(set) if m, ok := meter.(mockMeter); ok { - require.Equal(t, "go.opentelemetry.io/collector/cmd/mdatagen", m.name) + require.Equal(t, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor", m.name) } else { require.Fail(t, "returned Meter not mockMeter") } tracer := Tracer(set) if m, ok := tracer.(mockTracer); ok { - require.Equal(t, "go.opentelemetry.io/collector/cmd/mdatagen", m.name) + require.Equal(t, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor", m.name) } else { require.Fail(t, "returned Meter not mockTracer") } diff --git a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest.go b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest.go index c620e2e5bc7f1..e07ec6b0dba52 100644 --- a/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest.go +++ b/processor/hotreloadprocessor/internal/metadatatest/generated_telemetrytest.go @@ -24,7 +24,7 @@ func NewSettings(tt *componenttest.Telemetry) processor.Settings { func AssertEqualProcessorHotReloadConfigRefreshInterval(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_config_refresh_interval", - Description: "Refresh interval of the hotreloadprocessor", + Description: "Refresh interval of the hotreloadprocessor [Development]", Unit: "ms", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -38,7 +38,7 @@ func AssertEqualProcessorHotReloadConfigRefreshInterval(t *testing.T, tt *compon func AssertEqualProcessorHotReloadConfigShutdownDelay(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_config_shutdown_delay", - Description: "Shutdown delay of the hotreloadprocessor", + Description: "Shutdown delay of the hotreloadprocessor [Development]", Unit: "ms", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -52,7 +52,7 @@ func AssertEqualProcessorHotReloadConfigShutdownDelay(t *testing.T, tt *componen func AssertEqualProcessorHotReloadNewestFileFailedTimestamp(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_newest_file_failed_timestamp", - Description: "The timestamp of the newest file failed to load by the hotreloadprocessor in seconds", + Description: "The timestamp of the newest file failed to load by the hotreloadprocessor in seconds [Development]", Unit: "1", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -66,7 +66,7 @@ func AssertEqualProcessorHotReloadNewestFileFailedTimestamp(t *testing.T, tt *co func AssertEqualProcessorHotReloadNewestFileSuccessTimestamp(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_newest_file_success_timestamp", - Description: "The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds", + Description: "The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds [Development]", Unit: "1", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -80,7 +80,7 @@ func AssertEqualProcessorHotReloadNewestFileSuccessTimestamp(t *testing.T, tt *c func AssertEqualProcessorHotReloadProcessDuration(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_process_duration", - Description: "Duration of the processLogs function by the hotreloadprocessor", + Description: "Duration of the processLogs function by the hotreloadprocessor [Development]", Unit: "ms", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, @@ -95,7 +95,7 @@ func AssertEqualProcessorHotReloadProcessDuration(t *testing.T, tt *componenttes func AssertEqualProcessorHotReloadReloadDuration(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_reload_duration", - Description: "Duration of the reload by the hotreloadprocessor", + Description: "Duration of the reload by the hotreloadprocessor [Development]", Unit: "ms", Data: metricdata.Histogram[int64]{ Temporality: metricdata.CumulativeTemporality, @@ -110,7 +110,7 @@ func AssertEqualProcessorHotReloadReloadDuration(t *testing.T, tt *componenttest func AssertEqualProcessorHotReloadRollbackFileSuccessTimestamp(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_rollback_file_success_timestamp", - Description: "The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds", + Description: "The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds [Development]", Unit: "1", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -124,7 +124,7 @@ func AssertEqualProcessorHotReloadRollbackFileSuccessTimestamp(t *testing.T, tt func AssertEqualProcessorHotReloadRunningProcessorsCount(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_running_processors_count", - Description: "Number of running processors in the hotreloadprocessor", + Description: "Number of running processors in the hotreloadprocessor [Development]", Unit: "1", Data: metricdata.Gauge[int64]{ DataPoints: dps, @@ -138,7 +138,7 @@ func AssertEqualProcessorHotReloadRunningProcessorsCount(t *testing.T, tt *compo func AssertEqualProcessorHotReloadScan(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { want := metricdata.Metrics{ Name: "otelcol_processor_hot_reload_scan", - Description: "Number of scanned prefixes by the hotreloadprocessor", + Description: "Number of scanned prefixes by the hotreloadprocessor [Development]", Unit: "1", Data: metricdata.Gauge[int64]{ DataPoints: dps, diff --git a/processor/hotreloadprocessor/metadata.yaml b/processor/hotreloadprocessor/metadata.yaml index a05b63ae9520e..15a4b50c7d5d4 100644 --- a/processor/hotreloadprocessor/metadata.yaml +++ b/processor/hotreloadprocessor/metadata.yaml @@ -7,82 +7,100 @@ status: telemetry: metrics: - processor_hot_reload_running_processors_count: + processor_hot_reload_config_refresh_interval: enabled: true - description: Number of running processors in the hotreloadprocessor - unit: "1" + description: Refresh interval of the hotreloadprocessor + stability: + level: development + unit: ms gauge: value_type: int attributes: [destination] - processor_hot_reload_newest_file_success_timestamp: + processor_hot_reload_config_shutdown_delay: enabled: true - description: The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds - unit: "1" + description: Shutdown delay of the hotreloadprocessor + stability: + level: development + unit: ms gauge: value_type: int - attributes: [destination, key] + attributes: [destination] processor_hot_reload_newest_file_failed_timestamp: enabled: true description: The timestamp of the newest file failed to load by the hotreloadprocessor in seconds + stability: + level: development unit: "1" gauge: value_type: int attributes: [destination, key, reason] - processor_hot_reload_rollback_file_success_timestamp: + processor_hot_reload_newest_file_success_timestamp: enabled: true - description: The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds + description: The timestamp of the newest file successfully loaded by the hotreloadprocessor in seconds + stability: + level: development unit: "1" gauge: value_type: int - attributes: [destination, key, reason] - processor_hot_reload_config_refresh_interval: - enabled: true - description: Refresh interval of the hotreloadprocessor - unit: ms - gauge: - value_type: int - attributes: [destination] - processor_hot_reload_config_shutdown_delay: + attributes: [destination, key] + processor_hot_reload_process_duration: enabled: true - description: Shutdown delay of the hotreloadprocessor + description: Duration of the processLogs function by the hotreloadprocessor + stability: + level: development unit: ms - gauge: + histogram: value_type: int - attributes: [destination] + bucket_boundaries: + [1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000] processor_hot_reload_reload_duration: enabled: true description: Duration of the reload by the hotreloadprocessor + stability: + level: development unit: ms histogram: value_type: int bucket_boundaries: [1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000] + processor_hot_reload_rollback_file_success_timestamp: + enabled: true + description: The timestamp of the rollback file successfully loaded by the hotreloadprocessor in seconds + stability: + level: development + unit: "1" + gauge: + value_type: int + attributes: [destination, key, reason] + processor_hot_reload_running_processors_count: + enabled: true + description: Number of running processors in the hotreloadprocessor + stability: + level: development + unit: "1" + gauge: + value_type: int + attributes: [destination] processor_hot_reload_scan: enabled: true description: Number of scanned prefixes by the hotreloadprocessor + stability: + level: development unit: "1" gauge: value_type: int attributes: [prefix] - processor_hot_reload_process_duration: - enabled: true - description: Duration of the processLogs function by the hotreloadprocessor - unit: ms - histogram: - value_type: int - bucket_boundaries: - [1, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000] attributes: destination: description: The destination of the hotreloadprocessor type: string - reason: - description: The reason for the error + key: + description: The key of the file being scanned type: string prefix: description: The prefix of the file being scanned type: string - key: - description: The key of the file being scanned + reason: + description: The reason for the error type: string From 7e1a51ad1a77cb2505aa7f154128b6c17c38ffbb Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Wed, 18 Mar 2026 23:27:42 -0700 Subject: [PATCH 34/41] refactor(loadbalancingexporter): drop redundant copied fields --- exporter/loadbalancingexporter/log_exporter.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index 4574ca9d311d4..d828dc7dd3432 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -250,7 +250,6 @@ func findOrCreateResourceLogs(dest plog.Logs, src plog.ResourceLogs) plog.Resour } rl := dest.ResourceLogs().AppendEmpty() srcRes.CopyTo(rl.Resource()) - rl.Resource().SetDroppedAttributesCount(srcRes.DroppedAttributesCount()) rl.SetSchemaUrl(src.SchemaUrl()) return rl } @@ -265,7 +264,6 @@ func findOrCreateScopeLogs(rl plog.ResourceLogs, src plog.ScopeLogs) plog.ScopeL } sl := rl.ScopeLogs().AppendEmpty() srcScope.CopyTo(sl.Scope()) - sl.Scope().SetDroppedAttributesCount(srcScope.DroppedAttributesCount()) sl.SetSchemaUrl(src.SchemaUrl()) return sl } From b9ac902d6b0ccd1bf1527dc41880568c51c92b41 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 07:45:06 -0700 Subject: [PATCH 35/41] fix(loadbalancingexporter): reject log sends during shutdown --- exporter/loadbalancingexporter/log_exporter.go | 16 +++++++++++----- .../loadbalancingexporter/log_exporter_test.go | 8 ++++++++ processor/hotreloadprocessor/documentation.md | 1 - processor/hotreloadprocessor/metadata.yaml | 10 +++++++++- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/exporter/loadbalancingexporter/log_exporter.go b/exporter/loadbalancingexporter/log_exporter.go index d828dc7dd3432..4446c0a4d9983 100644 --- a/exporter/loadbalancingexporter/log_exporter.go +++ b/exporter/loadbalancingexporter/log_exporter.go @@ -7,6 +7,7 @@ import ( "context" "errors" "math/rand/v2" + "sync/atomic" "time" "go.opentelemetry.io/collector/component" @@ -30,7 +31,7 @@ type logExporterImp struct { batcher *logBatcher logger *zap.Logger - started bool + started atomic.Bool telemetry *metadata.TelemetryBuilder } @@ -85,12 +86,15 @@ func (*logExporterImp) Capabilities() consumer.Capabilities { } func (e *logExporterImp) Start(ctx context.Context, host component.Host) error { - e.started = true - return e.loadBalancer.Start(ctx, host) + if err := e.loadBalancer.Start(ctx, host); err != nil { + return err + } + e.started.Store(true) + return nil } func (e *logExporterImp) Shutdown(ctx context.Context) error { - if !e.started { + if !e.started.Swap(false) { return nil } var err error @@ -98,11 +102,13 @@ func (e *logExporterImp) Shutdown(ctx context.Context) error { err = e.batcher.Shutdown(ctx) } err = errors.Join(err, e.loadBalancer.Shutdown(ctx)) - e.started = false return err } func (e *logExporterImp) ConsumeLogs(ctx context.Context, ld plog.Logs) error { + if !e.started.Load() { + return errExporterIsStopping + } if e.batcher == nil { var errs error batches := batchpersignal.SplitLogs(ld) diff --git a/exporter/loadbalancingexporter/log_exporter_test.go b/exporter/loadbalancingexporter/log_exporter_test.go index 29bd266c47218..fe291fac52188 100644 --- a/exporter/loadbalancingexporter/log_exporter_test.go +++ b/exporter/loadbalancingexporter/log_exporter_test.go @@ -124,6 +124,14 @@ func TestLogExporterShutdown(t *testing.T) { assert.NoError(t, res) } +func TestLogExporterConsumeLogsReturnsStoppingErrorWhenNotStarted(t *testing.T) { + p, err := newLogsExporter(exportertest.NewNopSettings(metadata.Type), simpleConfig()) + require.NoError(t, err) + + err = p.ConsumeLogs(t.Context(), simpleLogs()) + require.ErrorIs(t, err, errExporterIsStopping) +} + func TestConsumeLogs(t *testing.T) { ts, tb := getTelemetryAssets(t) componentFactory := func(_ context.Context, _ string) (component.Component, error) { diff --git a/processor/hotreloadprocessor/documentation.md b/processor/hotreloadprocessor/documentation.md index 37e4e9c4a1c73..19621e80236a5 100644 --- a/processor/hotreloadprocessor/documentation.md +++ b/processor/hotreloadprocessor/documentation.md @@ -95,7 +95,6 @@ The timestamp of the rollback file successfully loaded by the hotreloadprocessor | ---- | ----------- | ------ | | destination | The destination of the hotreloadprocessor | Any Str | | key | The key of the file being scanned | Any Str | -| reason | The reason for the error | Any Str | ### otelcol_processor_hot_reload_running_processors_count diff --git a/processor/hotreloadprocessor/metadata.yaml b/processor/hotreloadprocessor/metadata.yaml index 15a4b50c7d5d4..50b08c69eeda4 100644 --- a/processor/hotreloadprocessor/metadata.yaml +++ b/processor/hotreloadprocessor/metadata.yaml @@ -5,6 +5,14 @@ status: stability: development: [logs, metrics, traces] +tests: + config: + configuration_prefix: data://cHJvY2Vzc29yczoge30Kc2VydmljZToKICBwaXBlbGluZXM6CiAgICBsb2dzLzMyYWQyYzNkLWFlN2MtNDg4Ny1iNDc2LTM2NWRhZGU4YzljZC82OTE3NGQ0MS1hNDljLTQ3NjMtYmNlMi1lOTdlZjgwNTFiYWI6CiAgICAgIHByb2Nlc3NvcnM6IFtdCg== + encryption_key: test-key + region: us-east-1 + refresh_interval: 2m + shutdown_delay: 1m + telemetry: metrics: processor_hot_reload_config_refresh_interval: @@ -71,7 +79,7 @@ telemetry: unit: "1" gauge: value_type: int - attributes: [destination, key, reason] + attributes: [destination, key] processor_hot_reload_running_processors_count: enabled: true description: Number of running processors in the hotreloadprocessor From e5bc61a088a84da9a2895a5fab41a6f79092e587 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 07:52:38 -0700 Subject: [PATCH 36/41] build(hotreloadprocessor): tidy generated test deps --- processor/hotreloadprocessor/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/hotreloadprocessor/go.mod b/processor/hotreloadprocessor/go.mod index e3dfff7ddb6cc..8f1e338c53ae4 100644 --- a/processor/hotreloadprocessor/go.mod +++ b/processor/hotreloadprocessor/go.mod @@ -14,6 +14,7 @@ require ( go.opentelemetry.io/collector/component/componenttest v0.140.0 go.opentelemetry.io/collector/confmap v1.46.0 go.opentelemetry.io/collector/consumer v1.46.0 + go.opentelemetry.io/collector/consumer/consumertest v0.140.0 go.opentelemetry.io/collector/otelcol v0.140.0 go.opentelemetry.io/collector/pdata v1.46.0 go.opentelemetry.io/collector/pipeline v1.46.0 @@ -113,7 +114,6 @@ require ( go.opentelemetry.io/collector/connector/connectortest v0.140.0 // indirect go.opentelemetry.io/collector/connector/xconnector v0.140.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.140.0 // indirect - go.opentelemetry.io/collector/consumer/consumertest v0.140.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.140.0 // indirect go.opentelemetry.io/collector/exporter v1.46.0 // indirect go.opentelemetry.io/collector/exporter/exportertest v0.140.0 // indirect From 274e77c376945a282775218d516288f4add4a130 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 08:08:04 -0700 Subject: [PATCH 37/41] build(logstometricsprocessor): regenerate metadata outputs --- .../logstometricsprocessor/documentation.md | 47 ++++++++ .../generated_component_test.go | 99 +++++++++++++++ .../generated_package_test.go | 13 ++ processor/logstometricsprocessor/go.mod | 6 +- processor/logstometricsprocessor/go.sum | 2 - .../internal/metadata/generated_telemetry.go | 114 +++++++----------- .../metadata/generated_telemetry_test.go | 74 ++++++++++++ .../metadatatest/generated_telemetrytest.go | 101 ++++++++++++++++ .../generated_telemetrytest_test.go | 45 +++++++ .../logstometricsprocessor/metadata.yaml | 26 ++-- 10 files changed, 440 insertions(+), 87 deletions(-) create mode 100644 processor/logstometricsprocessor/documentation.md create mode 100644 processor/logstometricsprocessor/generated_component_test.go create mode 100644 processor/logstometricsprocessor/generated_package_test.go create mode 100644 processor/logstometricsprocessor/internal/metadata/generated_telemetry_test.go create mode 100644 processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest.go create mode 100644 processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest_test.go diff --git a/processor/logstometricsprocessor/documentation.md b/processor/logstometricsprocessor/documentation.md new file mode 100644 index 0000000000000..5d6e5c1587969 --- /dev/null +++ b/processor/logstometricsprocessor/documentation.md @@ -0,0 +1,47 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# logstometricsprocessor + +## Internal Telemetry + +The following telemetry is emitted by this component. + +### otelcol_processor_logstometrics_errors + +Number of errors encountered during processing [Development] + +| Unit | Metric Type | Value Type | Monotonic | Stability | +| ---- | ----------- | ---------- | --------- | --------- | +| {errors} | Sum | Int | true | Development | + +### otelcol_processor_logstometrics_logs_dropped + +Number of logs dropped when drop_logs is true [Development] + +| Unit | Metric Type | Value Type | Monotonic | Stability | +| ---- | ----------- | ---------- | --------- | --------- | +| {records} | Sum | Int | true | Development | + +### otelcol_processor_logstometrics_logs_processed + +Number of log records processed by the processor [Development] + +| Unit | Metric Type | Value Type | Monotonic | Stability | +| ---- | ----------- | ---------- | --------- | --------- | +| {records} | Sum | Int | true | Development | + +### otelcol_processor_logstometrics_metrics_extracted + +Number of metrics extracted from logs [Development] + +| Unit | Metric Type | Value Type | Monotonic | Stability | +| ---- | ----------- | ---------- | --------- | --------- | +| {metrics} | Sum | Int | true | Development | + +### otelcol_processor_logstometrics_processing_duration + +Time spent processing logs and extracting metrics [Development] + +| Unit | Metric Type | Value Type | Stability | +| ---- | ----------- | ---------- | --------- | +| ms | Histogram | Int | Development | diff --git a/processor/logstometricsprocessor/generated_component_test.go b/processor/logstometricsprocessor/generated_component_test.go new file mode 100644 index 0000000000000..06f25875c14f8 --- /dev/null +++ b/processor/logstometricsprocessor/generated_component_test.go @@ -0,0 +1,99 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package logstometricsprocessor + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/pdata/ptrace" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/processortest" +) + +var typ = component.MustNewType("logstometricsprocessor") + +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, typ, NewFactory().Type()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + tests := []struct { + createFn func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) + name string + }{ + + { + name: "logs", + createFn: func(ctx context.Context, set processor.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateLogs(ctx, set, cfg, consumertest.NewNop()) + }, + }, + } + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, sub.Unmarshal(&cfg)) + + for _, tt := range tests { + t.Run(tt.name+"-shutdown", func(t *testing.T) { + c, err := tt.createFn(context.Background(), processortest.NewNopSettings(typ), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + } +} + +func generateLifecycleTestLogs() plog.Logs { + logs := plog.NewLogs() + rl := logs.ResourceLogs().AppendEmpty() + rl.Resource().Attributes().PutStr("resource", "R1") + l := rl.ScopeLogs().AppendEmpty().LogRecords().AppendEmpty() + l.Body().SetStr("test log message") + l.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return logs +} + +func generateLifecycleTestMetrics() pmetric.Metrics { + metrics := pmetric.NewMetrics() + rm := metrics.ResourceMetrics().AppendEmpty() + rm.Resource().Attributes().PutStr("resource", "R1") + m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty() + m.SetName("test_metric") + dp := m.SetEmptyGauge().DataPoints().AppendEmpty() + dp.Attributes().PutStr("test_attr", "value_1") + dp.SetIntValue(123) + dp.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return metrics +} + +func generateLifecycleTestTraces() ptrace.Traces { + traces := ptrace.NewTraces() + rs := traces.ResourceSpans().AppendEmpty() + rs.Resource().Attributes().PutStr("resource", "R1") + span := rs.ScopeSpans().AppendEmpty().Spans().AppendEmpty() + span.Attributes().PutStr("test_attr", "value_1") + span.SetName("test_span") + span.SetStartTimestamp(pcommon.NewTimestampFromTime(time.Now().Add(-1 * time.Second))) + span.SetEndTimestamp(pcommon.NewTimestampFromTime(time.Now())) + return traces +} diff --git a/processor/logstometricsprocessor/generated_package_test.go b/processor/logstometricsprocessor/generated_package_test.go new file mode 100644 index 0000000000000..7515fc8f8d189 --- /dev/null +++ b/processor/logstometricsprocessor/generated_package_test.go @@ -0,0 +1,13 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package logstometricsprocessor + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/processor/logstometricsprocessor/go.mod b/processor/logstometricsprocessor/go.mod index e7527844e38a7..c657e8282d7d6 100644 --- a/processor/logstometricsprocessor/go.mod +++ b/processor/logstometricsprocessor/go.mod @@ -20,14 +20,16 @@ require ( require ( github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.140.1 + go.opentelemetry.io/collector/component/componenttest v0.140.0 go.opentelemetry.io/collector/config/configoptional v1.46.0 - go.opentelemetry.io/collector/config/configtelemetry v0.140.0 go.opentelemetry.io/collector/consumer/consumertest v0.140.0 go.opentelemetry.io/collector/processor/processorhelper v0.140.0 go.opentelemetry.io/collector/processor/processortest v0.140.0 go.opentelemetry.io/otel v1.38.0 go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 go.opentelemetry.io/otel/trace v1.38.0 + go.uber.org/goleak v1.3.0 ) require ( @@ -65,7 +67,6 @@ require ( github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/collector/component/componentstatus v0.140.0 // indirect - go.opentelemetry.io/collector/component/componenttest v0.140.0 // indirect go.opentelemetry.io/collector/confmap/xconfmap v0.140.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.140.0 // indirect go.opentelemetry.io/collector/featuregate v1.46.0 // indirect @@ -74,7 +75,6 @@ require ( go.opentelemetry.io/collector/pdata/testdata v0.140.0 // indirect go.opentelemetry.io/collector/processor/xprocessor v0.140.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect diff --git a/processor/logstometricsprocessor/go.sum b/processor/logstometricsprocessor/go.sum index 2ebf74c8b36f1..ded681e99f1a7 100644 --- a/processor/logstometricsprocessor/go.sum +++ b/processor/logstometricsprocessor/go.sum @@ -101,8 +101,6 @@ go.opentelemetry.io/collector/component/componenttest v0.140.0 h1:/g7yETZ7Flq4v9 go.opentelemetry.io/collector/component/componenttest v0.140.0/go.mod h1:40PZd6rjqHH5UCqxB6nAvnHtDTwZaSWf1En1u1mbA8k= go.opentelemetry.io/collector/config/configoptional v1.46.0 h1:BZnFi2NUSEeP2ttr7bwGdo6a8UDcYEkfrq7SiP1jjac= go.opentelemetry.io/collector/config/configoptional v1.46.0/go.mod h1:XgGvHiFtro2MpPWbo4ExQ7CLnSBqzWAANfBIPv4QSVg= -go.opentelemetry.io/collector/config/configtelemetry v0.140.0 h1:bi8bCzmNXfHj+i1rbWVvI3VpHlAHykSnf3y2IbZ3XgE= -go.opentelemetry.io/collector/config/configtelemetry v0.140.0/go.mod h1:Xjw2+DpNLjYtx596EHSWBy0dNQRiJ2H+BlWU907lO40= go.opentelemetry.io/collector/confmap v1.46.0 h1:C/LfkYsKGWgGOvsUz70iUuxbSzSLaXZMSi3QVX6oJsw= go.opentelemetry.io/collector/confmap v1.46.0/go.mod h1:uqrwOuf+1PeZ9Zo/IDV9hJlvFy2eRKYUajkM1Lsmyto= go.opentelemetry.io/collector/confmap/xconfmap v0.140.0 h1:rTHo7f3d4h00qCpb4hYnu/+n48sd5Hd4E9KT47QTgZA= diff --git a/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go b/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go index 900c4a176325f..ec1e065c080fb 100644 --- a/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go +++ b/processor/logstometricsprocessor/internal/metadata/generated_telemetry.go @@ -3,14 +3,13 @@ package metadata import ( - "context" "errors" + "sync" - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/otel/metric" - "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" + + "go.opentelemetry.io/collector/component" ) func Meter(settings component.TelemetrySettings) metric.Meter { @@ -25,96 +24,73 @@ func Tracer(settings component.TelemetrySettings) trace.Tracer { // as defined in metadata and user config. type TelemetryBuilder struct { meter metric.Meter - ProcessorLogstometricsLogsProcessed metric.Int64Counter - ProcessorLogstometricsMetricsExtracted metric.Int64Counter + mu sync.Mutex + registrations []metric.Registration ProcessorLogstometricsErrors metric.Int64Counter ProcessorLogstometricsLogsDropped metric.Int64Counter + ProcessorLogstometricsLogsProcessed metric.Int64Counter + ProcessorLogstometricsMetricsExtracted metric.Int64Counter ProcessorLogstometricsProcessingDuration metric.Int64Histogram - level configtelemetry.Level } -// telemetryBuilderOption applies changes to default builder. -type telemetryBuilderOption func(*TelemetryBuilder) +// TelemetryBuilderOption applies changes to default builder. +type TelemetryBuilderOption interface { + apply(*TelemetryBuilder) +} + +type telemetryBuilderOptionFunc func(mb *TelemetryBuilder) + +func (tbof telemetryBuilderOptionFunc) apply(mb *TelemetryBuilder) { + tbof(mb) +} -// WithLevel sets the current telemetry level for the component. -func WithLevel(lvl configtelemetry.Level) telemetryBuilderOption { - return func(builder *TelemetryBuilder) { - builder.level = lvl +// Shutdown unregister all registered callbacks for async instruments. +func (builder *TelemetryBuilder) Shutdown() { + builder.mu.Lock() + defer builder.mu.Unlock() + for _, reg := range builder.registrations { + reg.Unregister() } } // NewTelemetryBuilder provides a struct with methods to update all internal telemetry // for a component -func NewTelemetryBuilder(settings component.TelemetrySettings, options ...telemetryBuilderOption) (*TelemetryBuilder, error) { - builder := TelemetryBuilder{level: configtelemetry.LevelBasic} +func NewTelemetryBuilder(settings component.TelemetrySettings, options ...TelemetryBuilderOption) (*TelemetryBuilder, error) { + builder := TelemetryBuilder{} for _, op := range options { - op(&builder) + op.apply(&builder) } + builder.meter = Meter(settings) var err, errs error - if builder.level >= configtelemetry.LevelBasic { - builder.meter = Meter(settings) - } else { - builder.meter = noop.Meter{} - } - builder.ProcessorLogstometricsLogsProcessed, err = builder.meter.Int64Counter( - "processor_logstometrics_logs_processed", - metric.WithDescription("Number of log records processed by the processor"), - metric.WithUnit("{records}"), - ) - errs = errors.Join(errs, err) - builder.ProcessorLogstometricsMetricsExtracted, err = builder.meter.Int64Counter( - "processor_logstometrics_metrics_extracted", - metric.WithDescription("Number of metrics extracted from logs"), - metric.WithUnit("{metrics}"), - ) - errs = errors.Join(errs, err) builder.ProcessorLogstometricsErrors, err = builder.meter.Int64Counter( - "processor_logstometrics_errors", - metric.WithDescription("Number of errors encountered during processing"), + "otelcol_processor_logstometrics_errors", + metric.WithDescription("Number of errors encountered during processing [Development]"), metric.WithUnit("{errors}"), ) errs = errors.Join(errs, err) builder.ProcessorLogstometricsLogsDropped, err = builder.meter.Int64Counter( - "processor_logstometrics_logs_dropped", - metric.WithDescription("Number of logs dropped when drop_logs is true"), + "otelcol_processor_logstometrics_logs_dropped", + metric.WithDescription("Number of logs dropped when drop_logs is true [Development]"), metric.WithUnit("{records}"), ) errs = errors.Join(errs, err) + builder.ProcessorLogstometricsLogsProcessed, err = builder.meter.Int64Counter( + "otelcol_processor_logstometrics_logs_processed", + metric.WithDescription("Number of log records processed by the processor [Development]"), + metric.WithUnit("{records}"), + ) + errs = errors.Join(errs, err) + builder.ProcessorLogstometricsMetricsExtracted, err = builder.meter.Int64Counter( + "otelcol_processor_logstometrics_metrics_extracted", + metric.WithDescription("Number of metrics extracted from logs [Development]"), + metric.WithUnit("{metrics}"), + ) + errs = errors.Join(errs, err) builder.ProcessorLogstometricsProcessingDuration, err = builder.meter.Int64Histogram( - "processor_logstometrics_processing_duration", - metric.WithDescription("Time spent processing logs and extracting metrics"), + "otelcol_processor_logstometrics_processing_duration", + metric.WithDescription("Time spent processing logs and extracting metrics [Development]"), metric.WithUnit("ms"), ) errs = errors.Join(errs, err) return &builder, errs } - -// RecordProcessorLogstometricsLogsProcessed adds a value for processor_logstometrics_logs_processed. -func (b *TelemetryBuilder) RecordProcessorLogstometricsLogsProcessed(ctx context.Context, val int64) { - b.ProcessorLogstometricsLogsProcessed.Add(ctx, val) -} - -// RecordProcessorLogstometricsMetricsExtracted adds a value for processor_logstometrics_metrics_extracted. -func (b *TelemetryBuilder) RecordProcessorLogstometricsMetricsExtracted(ctx context.Context, val int64) { - b.ProcessorLogstometricsMetricsExtracted.Add(ctx, val) -} - -// RecordProcessorLogstometricsErrors adds a value for processor_logstometrics_errors. -func (b *TelemetryBuilder) RecordProcessorLogstometricsErrors(ctx context.Context, val int64) { - b.ProcessorLogstometricsErrors.Add(ctx, val) -} - -// RecordProcessorLogstometricsLogsDropped adds a value for processor_logstometrics_logs_dropped. -func (b *TelemetryBuilder) RecordProcessorLogstometricsLogsDropped(ctx context.Context, val int64) { - b.ProcessorLogstometricsLogsDropped.Add(ctx, val) -} - -// RecordProcessorLogstometricsProcessingDuration records a value for processor_logstometrics_processing_duration. -func (b *TelemetryBuilder) RecordProcessorLogstometricsProcessingDuration(ctx context.Context, val int64) { - b.ProcessorLogstometricsProcessingDuration.Record(ctx, val) -} - -// Shutdown stops the telemetry builder. -func (b *TelemetryBuilder) Shutdown() { - // No-op for now -} diff --git a/processor/logstometricsprocessor/internal/metadata/generated_telemetry_test.go b/processor/logstometricsprocessor/internal/metadata/generated_telemetry_test.go new file mode 100644 index 0000000000000..d87c7543ec739 --- /dev/null +++ b/processor/logstometricsprocessor/internal/metadata/generated_telemetry_test.go @@ -0,0 +1,74 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/metric" + embeddedmetric "go.opentelemetry.io/otel/metric/embedded" + noopmetric "go.opentelemetry.io/otel/metric/noop" + "go.opentelemetry.io/otel/trace" + embeddedtrace "go.opentelemetry.io/otel/trace/embedded" + nooptrace "go.opentelemetry.io/otel/trace/noop" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" +) + +type mockMeter struct { + noopmetric.Meter + name string +} +type mockMeterProvider struct { + embeddedmetric.MeterProvider +} + +func (m mockMeterProvider) Meter(name string, opts ...metric.MeterOption) metric.Meter { + return mockMeter{name: name} +} + +type mockTracer struct { + nooptrace.Tracer + name string +} + +type mockTracerProvider struct { + embeddedtrace.TracerProvider +} + +func (m mockTracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer { + return mockTracer{name: name} +} + +func TestProviders(t *testing.T) { + set := component.TelemetrySettings{ + MeterProvider: mockMeterProvider{}, + TracerProvider: mockTracerProvider{}, + } + + meter := Meter(set) + if m, ok := meter.(mockMeter); ok { + require.Equal(t, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor", m.name) + } else { + require.Fail(t, "returned Meter not mockMeter") + } + + tracer := Tracer(set) + if m, ok := tracer.(mockTracer); ok { + require.Equal(t, "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor", m.name) + } else { + require.Fail(t, "returned Meter not mockTracer") + } +} + +func TestNewTelemetryBuilder(t *testing.T) { + set := componenttest.NewNopTelemetrySettings() + applied := false + _, err := NewTelemetryBuilder(set, telemetryBuilderOptionFunc(func(b *TelemetryBuilder) { + applied = true + })) + require.NoError(t, err) + require.True(t, applied) +} diff --git a/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest.go b/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest.go new file mode 100644 index 0000000000000..42e82c3a68951 --- /dev/null +++ b/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest.go @@ -0,0 +1,101 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadatatest + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/processor" + "go.opentelemetry.io/collector/processor/processortest" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" +) + +func NewSettings(tt *componenttest.Telemetry) processor.Settings { + set := processortest.NewNopSettings(processortest.NopType) + set.ID = component.NewID(component.MustNewType("logstometricsprocessor")) + set.TelemetrySettings = tt.NewTelemetrySettings() + return set +} + +func AssertEqualProcessorLogstometricsErrors(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { + want := metricdata.Metrics{ + Name: "otelcol_processor_logstometrics_errors", + Description: "Number of errors encountered during processing [Development]", + Unit: "{errors}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: dps, + }, + } + got, err := tt.GetMetric("otelcol_processor_logstometrics_errors") + require.NoError(t, err) + metricdatatest.AssertEqual(t, want, got, opts...) +} + +func AssertEqualProcessorLogstometricsLogsDropped(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { + want := metricdata.Metrics{ + Name: "otelcol_processor_logstometrics_logs_dropped", + Description: "Number of logs dropped when drop_logs is true [Development]", + Unit: "{records}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: dps, + }, + } + got, err := tt.GetMetric("otelcol_processor_logstometrics_logs_dropped") + require.NoError(t, err) + metricdatatest.AssertEqual(t, want, got, opts...) +} + +func AssertEqualProcessorLogstometricsLogsProcessed(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { + want := metricdata.Metrics{ + Name: "otelcol_processor_logstometrics_logs_processed", + Description: "Number of log records processed by the processor [Development]", + Unit: "{records}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: dps, + }, + } + got, err := tt.GetMetric("otelcol_processor_logstometrics_logs_processed") + require.NoError(t, err) + metricdatatest.AssertEqual(t, want, got, opts...) +} + +func AssertEqualProcessorLogstometricsMetricsExtracted(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.DataPoint[int64], opts ...metricdatatest.Option) { + want := metricdata.Metrics{ + Name: "otelcol_processor_logstometrics_metrics_extracted", + Description: "Number of metrics extracted from logs [Development]", + Unit: "{metrics}", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: dps, + }, + } + got, err := tt.GetMetric("otelcol_processor_logstometrics_metrics_extracted") + require.NoError(t, err) + metricdatatest.AssertEqual(t, want, got, opts...) +} + +func AssertEqualProcessorLogstometricsProcessingDuration(t *testing.T, tt *componenttest.Telemetry, dps []metricdata.HistogramDataPoint[int64], opts ...metricdatatest.Option) { + want := metricdata.Metrics{ + Name: "otelcol_processor_logstometrics_processing_duration", + Description: "Time spent processing logs and extracting metrics [Development]", + Unit: "ms", + Data: metricdata.Histogram[int64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: dps, + }, + } + got, err := tt.GetMetric("otelcol_processor_logstometrics_processing_duration") + require.NoError(t, err) + metricdatatest.AssertEqual(t, want, got, opts...) +} diff --git a/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest_test.go b/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest_test.go new file mode 100644 index 0000000000000..15741c7b95738 --- /dev/null +++ b/processor/logstometricsprocessor/internal/metadatatest/generated_telemetrytest_test.go @@ -0,0 +1,45 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadatatest + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + + "go.opentelemetry.io/collector/component/componenttest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/processor/logstometricsprocessor/internal/metadata" +) + +func TestSetupTelemetry(t *testing.T) { + testTel := componenttest.NewTelemetry() + tb, err := metadata.NewTelemetryBuilder(testTel.NewTelemetrySettings()) + require.NoError(t, err) + defer tb.Shutdown() + tb.ProcessorLogstometricsErrors.Add(context.Background(), 1) + tb.ProcessorLogstometricsLogsDropped.Add(context.Background(), 1) + tb.ProcessorLogstometricsLogsProcessed.Add(context.Background(), 1) + tb.ProcessorLogstometricsMetricsExtracted.Add(context.Background(), 1) + tb.ProcessorLogstometricsProcessingDuration.Record(context.Background(), 1) + AssertEqualProcessorLogstometricsErrors(t, testTel, + []metricdata.DataPoint[int64]{{Value: 1}}, + metricdatatest.IgnoreTimestamp()) + AssertEqualProcessorLogstometricsLogsDropped(t, testTel, + []metricdata.DataPoint[int64]{{Value: 1}}, + metricdatatest.IgnoreTimestamp()) + AssertEqualProcessorLogstometricsLogsProcessed(t, testTel, + []metricdata.DataPoint[int64]{{Value: 1}}, + metricdatatest.IgnoreTimestamp()) + AssertEqualProcessorLogstometricsMetricsExtracted(t, testTel, + []metricdata.DataPoint[int64]{{Value: 1}}, + metricdatatest.IgnoreTimestamp()) + AssertEqualProcessorLogstometricsProcessingDuration(t, testTel, + []metricdata.HistogramDataPoint[int64]{{}}, metricdatatest.IgnoreValue(), + metricdatatest.IgnoreTimestamp()) + + require.NoError(t, testTel.Shutdown(context.Background())) +} diff --git a/processor/logstometricsprocessor/metadata.yaml b/processor/logstometricsprocessor/metadata.yaml index e694ee741427f..113d322510880 100644 --- a/processor/logstometricsprocessor/metadata.yaml +++ b/processor/logstometricsprocessor/metadata.yaml @@ -17,41 +17,42 @@ tests: description: "Count of log record" sum: value: "1" # Count each log record as 1, has to be a string + skip_lifecycle: true telemetry: metrics: - processor_logstometrics_logs_processed: - description: Number of log records processed by the processor + processor_logstometrics_errors: + description: Number of errors encountered during processing stability: level: development - unit: "{records}" + unit: "{errors}" enabled: true sum: value_type: int monotonic: true - processor_logstometrics_metrics_extracted: - description: Number of metrics extracted from logs + processor_logstometrics_logs_dropped: + description: Number of logs dropped when drop_logs is true stability: level: development - unit: "{metrics}" + unit: "{records}" enabled: true sum: value_type: int monotonic: true - processor_logstometrics_errors: - description: Number of errors encountered during processing + processor_logstometrics_logs_processed: + description: Number of log records processed by the processor stability: level: development - unit: "{errors}" + unit: "{records}" enabled: true sum: value_type: int monotonic: true - processor_logstometrics_logs_dropped: - description: Number of logs dropped when drop_logs is true + processor_logstometrics_metrics_extracted: + description: Number of metrics extracted from logs stability: level: development - unit: "{records}" + unit: "{metrics}" enabled: true sum: value_type: int @@ -65,4 +66,3 @@ telemetry: histogram: value_type: int buckets: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000] - From b082915dc5eac7d23620574a7ea4601ce465692d Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 08:25:32 -0700 Subject: [PATCH 38/41] build: add hotreloadprocessor to module sets --- versions.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/versions.yaml b/versions.yaml index e2d3852cfdc0c..fe630e12c44ca 100644 --- a/versions.yaml +++ b/versions.yaml @@ -196,6 +196,7 @@ module-sets: - github.com/open-telemetry/opentelemetry-collector-contrib/processor/geoipprocessor - github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbyattrsprocessor - github.com/open-telemetry/opentelemetry-collector-contrib/processor/groupbytraceprocessor + - github.com/open-telemetry/opentelemetry-collector-contrib/processor/hotreloadprocessor - github.com/open-telemetry/opentelemetry-collector-contrib/processor/intervalprocessor - github.com/open-telemetry/opentelemetry-collector-contrib/processor/isolationforestprocessor - github.com/open-telemetry/opentelemetry-collector-contrib/processor/k8sattributesprocessor From 3f4e15a56d284ffdd0e5f7d57491be8be594391b Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 08:36:03 -0700 Subject: [PATCH 39/41] build: regenerate issue templates --- .github/ISSUE_TEMPLATE/beta_stability.yaml | 2 ++ .github/ISSUE_TEMPLATE/bug_report.yaml | 2 ++ .github/ISSUE_TEMPLATE/feature_request.yaml | 2 ++ .github/ISSUE_TEMPLATE/other.yaml | 2 ++ .github/ISSUE_TEMPLATE/unmaintained.yaml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/beta_stability.yaml b/.github/ISSUE_TEMPLATE/beta_stability.yaml index 5ef576fcd3b0a..0c6286f8aec3f 100644 --- a/.github/ISSUE_TEMPLATE/beta_stability.yaml +++ b/.github/ISSUE_TEMPLATE/beta_stability.yaml @@ -198,10 +198,12 @@ body: - processor/geoip - processor/groupbyattrs - processor/groupbytrace + - processor/hotreload - processor/interval - processor/isolationforest - processor/k8sattributes - processor/logdedup + - processor/logstometrics - processor/logstransform - processor/metricsgeneration - processor/metricstarttime diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index d2cf12011b595..a84aa97c638b0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -201,10 +201,12 @@ body: - processor/geoip - processor/groupbyattrs - processor/groupbytrace + - processor/hotreload - processor/interval - processor/isolationforest - processor/k8sattributes - processor/logdedup + - processor/logstometrics - processor/logstransform - processor/metricsgeneration - processor/metricstarttime diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index cc22baf3615dd..1a696ac6fe2f3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -195,10 +195,12 @@ body: - processor/geoip - processor/groupbyattrs - processor/groupbytrace + - processor/hotreload - processor/interval - processor/isolationforest - processor/k8sattributes - processor/logdedup + - processor/logstometrics - processor/logstransform - processor/metricsgeneration - processor/metricstarttime diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml index 3208d965299e0..a8e55d183f034 100644 --- a/.github/ISSUE_TEMPLATE/other.yaml +++ b/.github/ISSUE_TEMPLATE/other.yaml @@ -195,10 +195,12 @@ body: - processor/geoip - processor/groupbyattrs - processor/groupbytrace + - processor/hotreload - processor/interval - processor/isolationforest - processor/k8sattributes - processor/logdedup + - processor/logstometrics - processor/logstransform - processor/metricsgeneration - processor/metricstarttime diff --git a/.github/ISSUE_TEMPLATE/unmaintained.yaml b/.github/ISSUE_TEMPLATE/unmaintained.yaml index e371277e48c5f..1c26c1d834918 100644 --- a/.github/ISSUE_TEMPLATE/unmaintained.yaml +++ b/.github/ISSUE_TEMPLATE/unmaintained.yaml @@ -200,10 +200,12 @@ body: - processor/geoip - processor/groupbyattrs - processor/groupbytrace + - processor/hotreload - processor/interval - processor/isolationforest - processor/k8sattributes - processor/logdedup + - processor/logstometrics - processor/logstransform - processor/metricsgeneration - processor/metricstarttime From 824555f73140e1f49ed10120633553b5b14688ae Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 08:37:44 -0700 Subject: [PATCH 40/41] fix(loadbalancingexporter): stop batcher backend creation after shutdown --- exporter/loadbalancingexporter/log_batcher.go | 10 ++++++++ .../loadbalancingexporter/log_batcher_test.go | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/exporter/loadbalancingexporter/log_batcher.go b/exporter/loadbalancingexporter/log_batcher.go index 87e411685ecb5..f72c9227cd897 100644 --- a/exporter/loadbalancingexporter/log_batcher.go +++ b/exporter/loadbalancingexporter/log_batcher.go @@ -50,6 +50,7 @@ type logBatcher struct { mu sync.RWMutex backends map[string]*backendLogBatcher + stopped atomic.Bool } type logBatcherRequest struct { @@ -165,6 +166,8 @@ func (b *logBatcher) Remove(ctx context.Context, endpoint string, exp *wrappedEx } func (b *logBatcher) Shutdown(ctx context.Context) error { + b.stopped.Store(true) + b.mu.Lock() backends := make([]*backendLogBatcher, 0, len(b.backends)) for endpoint, backend := range b.backends { @@ -214,6 +217,10 @@ func (b *logBatcher) snapshotPending() []logBatcherPending { } func (b *logBatcher) acquireBackend(endpoint string, exp *wrappedExporter) (*backendLogBatcher, error) { + if b.stopped.Load() { + return nil, errLogBatcherExporterStopping + } + b.mu.RLock() backend, ok := b.backends[endpoint] if ok { @@ -230,6 +237,9 @@ func (b *logBatcher) acquireBackend(endpoint string, exp *wrappedExporter) (*bac b.mu.Lock() defer b.mu.Unlock() + if b.stopped.Load() { + return nil, errLogBatcherExporterStopping + } backend, ok = b.backends[endpoint] if ok { if exp != nil && exp.isStopping() { diff --git a/exporter/loadbalancingexporter/log_batcher_test.go b/exporter/loadbalancingexporter/log_batcher_test.go index 80c8062bca7c9..e6b49cb3cd60f 100644 --- a/exporter/loadbalancingexporter/log_batcher_test.go +++ b/exporter/loadbalancingexporter/log_batcher_test.go @@ -237,6 +237,29 @@ func TestLogBatcherShutdownRespectsContextWhileWaitingForInflight(t *testing.T) }, time.Second, 10*time.Millisecond) } +func TestLogBatcherEnqueueAfterShutdownReturnsStoppingError(t *testing.T) { + ts, _ := getTelemetryAssets(t) + batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ + maxRecords: 100, + maxBytes: 1 << 20, + flushInterval: time.Hour, + }, func(context.Context, *wrappedExporter, plog.Logs, string) error { + return nil + }) + require.NoError(t, err) + + require.NoError(t, batcher.Shutdown(t.Context())) + + err = batcher.Enqueue( + t.Context(), + "endpoint-1:4317", + newWrappedExporter(newNopMockLogsExporter(), "endpoint-1:4317"), + simpleLogs(), + ) + require.ErrorIs(t, err, errLogBatcherExporterStopping) + assert.Empty(t, batcher.snapshotPending()) +} + func TestLogBatcherRemoveRespectsContextWhileWaitingForInflight(t *testing.T) { ts, _ := getTelemetryAssets(t) batcher, err := newLogBatcher(ts.Logger, ts.TelemetrySettings, logBatcherSettings{ From 504e89f40bb2088ad1cfeabe93009131d63d3212 Mon Sep 17 00:00:00 2001 From: Amir Jakoby Date: Thu, 19 Mar 2026 10:12:40 -0700 Subject: [PATCH 41/41] refactor(loadbalancingexporter): inline exporter lookup helper --- .../loadbalancingexporter/loadbalancer.go | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/exporter/loadbalancingexporter/loadbalancer.go b/exporter/loadbalancingexporter/loadbalancer.go index 0f715e768a05d..55c054a38357c 100644 --- a/exporter/loadbalancingexporter/loadbalancer.go +++ b/exporter/loadbalancingexporter/loadbalancer.go @@ -247,7 +247,8 @@ func (lb *loadBalancer) Shutdown(ctx context.Context) error { return err } -func (lb *loadBalancer) withExporterAndEndpoint(identifier []byte, fn func(*wrappedExporter, string) error) error { +// exporterAndEndpoint returns the exporter and the endpoint for the given identifier. +func (lb *loadBalancer) exporterAndEndpoint(identifier []byte) (*wrappedExporter, string, error) { lb.updateLock.RLock() defer lb.updateLock.RUnlock() @@ -255,24 +256,8 @@ func (lb *loadBalancer) withExporterAndEndpoint(identifier []byte, fn func(*wrap exp, found := lb.exporters[endpointWithPort(endpoint)] if !found { // something is really wrong... how come we couldn't find the exporter?? - return fmt.Errorf("couldn't find the exporter for the endpoint %q", endpoint) - } - - return fn(exp, endpoint) -} - -// exporterAndEndpoint returns the exporter and the endpoint for the given identifier. -func (lb *loadBalancer) exporterAndEndpoint(identifier []byte) (*wrappedExporter, string, error) { - var matchedExporter *wrappedExporter - var matchedEndpoint string - err := lb.withExporterAndEndpoint(identifier, func(exp *wrappedExporter, endpoint string) error { - matchedExporter = exp - matchedEndpoint = endpoint - return nil - }) - if err != nil { - return nil, "", err + return nil, "", fmt.Errorf("couldn't find the exporter for the endpoint %q", endpoint) } - return matchedExporter, matchedEndpoint, nil + return exp, endpoint, nil }