Files
zeekay 7baf52d980 fix: align Go package names with directory names, fix broken identifiers
- Fix 'Hanzo O11y' (space) → 'HanzoO11y' in Go struct/func names
- Package o11yclickhousemetrics → o11ydatastoremetrics (matches dir)
- Package o11yclickhousemeter → o11ydatastoremeter (matches dir)
- Import paths aligned with actual directory structure
- Component type names kept as signozclickhousemetrics for config compat
2026-03-12 02:01:15 -07:00

263 lines
7.3 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package o11ykafkaexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter"
import (
"context"
"errors"
"fmt"
"github.com/Shopify/sarama"
"go.opentelemetry.io/collector/client"
"go.opentelemetry.io/collector/consumer/consumererror"
"go.opentelemetry.io/collector/exporter"
"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.uber.org/zap"
)
var errUnrecognizedEncoding = fmt.Errorf("unrecognized encoding")
// kafkaTracesProducer uses sarama to produce trace messages to Kafka.
type kafkaTracesProducer struct {
producer sarama.SyncProducer
topic string
marshaler TracesMarshaler
logger *zap.Logger
}
type kafkaErrors struct {
count int
err string
}
func (ke kafkaErrors) Error() string {
return fmt.Sprintf("Failed to deliver %d messages due to %s", ke.count, ke.err)
}
func (e *kafkaTracesProducer) tracesPusher(ctx context.Context, td ptrace.Traces) error {
kafkaTopicPrefix := getKafkaTopicPrefixFromClientMetadata(client.FromContext(ctx).Metadata)
kafkaTopic := fmt.Sprintf("%s_traces", kafkaTopicPrefix)
messages, err := e.marshaler.Marshal(td, kafkaTopic)
if err != nil {
return consumererror.NewPermanent(err)
}
err = e.producer.SendMessages(messages)
if err != nil {
var prodErr sarama.ProducerErrors
if errors.As(err, &prodErr) {
if len(prodErr) > 0 {
return kafkaErrors{len(prodErr), prodErr[0].Err.Error()}
}
}
return err
}
return nil
}
func (e *kafkaTracesProducer) Close(context.Context) error {
return e.producer.Close()
}
// kafkaMetricsProducer uses sarama to produce metrics messages to kafka
type kafkaMetricsProducer struct {
producer sarama.SyncProducer
topic string
marshaler MetricsMarshaler
logger *zap.Logger
}
func (e *kafkaMetricsProducer) metricsDataPusher(ctx context.Context, md pmetric.Metrics) error {
kafkaTopicPrefix := getKafkaTopicPrefixFromClientMetadata(client.FromContext(ctx).Metadata)
kafkaTopic := fmt.Sprintf("%s_metrics", kafkaTopicPrefix)
messages, err := e.marshaler.Marshal(md, kafkaTopic)
if err != nil {
return consumererror.NewPermanent(err)
}
err = e.producer.SendMessages(messages)
if err != nil {
var prodErr sarama.ProducerErrors
if errors.As(err, &prodErr) {
if len(prodErr) > 0 {
return kafkaErrors{len(prodErr), prodErr[0].Err.Error()}
}
}
return err
}
return nil
}
func (e *kafkaMetricsProducer) Close(context.Context) error {
return e.producer.Close()
}
// kafkaLogsProducer uses sarama to produce logs messages to kafka
type kafkaLogsProducer struct {
producer sarama.SyncProducer
topic string
marshaler LogsMarshaler
logger *zap.Logger
}
func (e *kafkaLogsProducer) logsDataPusher(ctx context.Context, ld plog.Logs) error {
e.normalizeLogData(&ld)
kafkaTopicPrefix := getKafkaTopicPrefixFromClientMetadata(client.FromContext(ctx).Metadata)
kafkaTopic := fmt.Sprintf("%s_logs", kafkaTopicPrefix)
messages, err := e.marshaler.Marshal(ld, kafkaTopic)
if err != nil {
return consumererror.NewPermanent(err)
}
err = e.producer.SendMessages(messages)
if err != nil {
var prodErr sarama.ProducerErrors
if errors.As(err, &prodErr) {
if len(prodErr) > 0 {
e.logger.Error("Failed to deliver messages", zap.String("topic", kafkaTopic), zap.Int("logCount", ld.LogRecordCount()), zap.String("error", prodErr[0].Err.Error()))
return kafkaErrors{len(prodErr), prodErr[0].Err.Error()}
}
}
return err
}
return nil
}
func (e *kafkaLogsProducer) normalizeLogData(ld *plog.Logs) {
for rlIdx := 0; rlIdx < ld.ResourceLogs().Len(); rlIdx++ {
rl := ld.ResourceLogs().At(rlIdx)
for slIdx := 0; slIdx < rl.ScopeLogs().Len(); slIdx++ {
sl := rl.ScopeLogs().At(slIdx)
for lrIdx := 0; lrIdx < sl.LogRecords().Len(); lrIdx++ {
lr := sl.LogRecords().At(lrIdx)
// log body is always expected to be string in HanzoO11y
if lr.Body().Type() != pcommon.ValueTypeStr {
var strBody string
if lr.Body().Type() == pcommon.ValueTypeBytes {
strBody = string(lr.Body().Bytes().AsRaw())
} else {
strBody = lr.Body().AsString()
}
lr.Body().SetStr(strBody)
}
}
}
}
}
func (e *kafkaLogsProducer) Close(context.Context) error {
return e.producer.Close()
}
func newSaramaProducer(config Config) (sarama.SyncProducer, error) {
c := sarama.NewConfig()
// These setting are required by the sarama.SyncProducer implementation.
c.Producer.Return.Successes = true
c.Producer.Return.Errors = true
c.Producer.RequiredAcks = config.Producer.RequiredAcks
// Because sarama does not accept a Context for every message, set the Timeout here.
c.Producer.Timeout = config.Timeout
c.Metadata.Full = config.Metadata.Full
c.Metadata.Retry.Max = config.Metadata.Retry.Max
c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff
c.Producer.MaxMessageBytes = config.Producer.MaxMessageBytes
c.Producer.Flush.MaxMessages = config.Producer.FlushMaxMessages
if int32(config.Producer.MaxMessageBytes) > sarama.MaxRequestSize {
// If the user has set a MaxMessageBytes that is larger than the MaxRequestSize, then
// set the MaxRequestSize to the MaxMessageBytes.
// If we dont do this, then sarama will reject messages > 100MB with a packet encoding error.
sarama.MaxRequestSize = int32(config.Producer.MaxMessageBytes)
}
if config.ProtocolVersion != "" {
version, err := sarama.ParseKafkaVersion(config.ProtocolVersion)
if err != nil {
return nil, err
}
c.Version = version
}
if err := ConfigureAuthentication(config.Authentication, c); err != nil {
return nil, err
}
compression, err := saramaProducerCompressionCodec(config.Producer.Compression)
if err != nil {
return nil, err
}
c.Producer.Compression = compression
producer, err := sarama.NewSyncProducer(config.Brokers, c)
if err != nil {
return nil, err
}
return producer, nil
}
func newMetricsExporter(config Config, set exporter.Settings, marshalers map[string]MetricsMarshaler) (*kafkaMetricsProducer, error) {
marshaler := marshalers[config.Encoding]
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
producer, err := newSaramaProducer(config)
if err != nil {
return nil, err
}
return &kafkaMetricsProducer{
producer: producer,
topic: config.Topic,
marshaler: marshaler,
logger: set.Logger,
}, nil
}
// newTracesExporter creates Kafka exporter.
func newTracesExporter(config Config, set exporter.Settings, marshalers map[string]TracesMarshaler) (*kafkaTracesProducer, error) {
marshaler := marshalers[config.Encoding]
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
producer, err := newSaramaProducer(config)
if err != nil {
return nil, err
}
return &kafkaTracesProducer{
producer: producer,
topic: config.Topic,
marshaler: marshaler,
logger: set.Logger,
}, nil
}
func newLogsExporter(config Config, set exporter.Settings, marshalers map[string]LogsMarshaler) (*kafkaLogsProducer, error) {
marshaler := marshalers[config.Encoding]
if marshaler == nil {
return nil, errUnrecognizedEncoding
}
producer, err := newSaramaProducer(config)
if err != nil {
return nil, err
}
return &kafkaLogsProducer{
producer: producer,
topic: config.Topic,
marshaler: marshaler,
logger: set.Logger,
}, nil
}