mirror of
https://github.com/hanzoai/otel-collector.git
synced 2026-11-03 02:20:04 +00:00
- 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
263 lines
7.3 KiB
Go
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
|
|
|
|
}
|