Files
zeekay df7d6cb470 feat: rebrand signoz-otel-collector → Hanzo OTEL Collector
- Go module: github.com/SigNoz/signoz-otel-collector → github.com/hanzoai/otel-collector
- All signoz-prefixed packages → o11y-prefixed
- Docker: signoz/* → ghcr.io/hanzoai/otel-collector
- ClickHouse exporter → Hanzo Datastore
- Env vars: SIGNOZ_* → HANZO_*
- Directory renames for all processor/receiver/exporter packages
2026-03-12 00:21:24 -07:00

897 lines
27 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package o11ykafkareceiver // import "github.com/hanzoai/otel-collector/receiver/o11ykafkareceiver"
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/IBM/sarama"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/receiver/receiverhelper"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"go.uber.org/zap"
"github.com/hanzoai/otel-collector/internal/kafka"
"github.com/hanzoai/otel-collector/receiver/o11ykafkareceiver/internal/metadata"
)
const (
transport = "kafka"
)
var errUnrecognizedEncoding = fmt.Errorf("unrecognized encoding")
var errInvalidInitialOffset = fmt.Errorf("invalid initial offset")
// errHighMemoryUsage is a sentinel error for high memory usage
var errHighMemoryUsage = errors.New("data refused due to high memory usage")
// kafkaTracesConsumer uses sarama to consume and handle messages from kafka.
type kafkaTracesConsumer struct {
consumerGroup sarama.ConsumerGroup
nextConsumer consumer.Traces
topics []string
cancelConsumeLoop context.CancelFunc
unmarshaler TracesUnmarshaler
settings receiver.Settings
autocommitEnabled bool
messageMarking MessageMarking
}
// kafkaMetricsConsumer uses sarama to consume and handle messages from kafka.
type kafkaMetricsConsumer struct {
consumerGroup sarama.ConsumerGroup
nextConsumer consumer.Metrics
topics []string
cancelConsumeLoop context.CancelFunc
unmarshaler MetricsUnmarshaler
settings receiver.Settings
autocommitEnabled bool
messageMarking MessageMarking
}
// kafkaLogsConsumer uses sarama to consume and handle messages from kafka.
type kafkaLogsConsumer struct {
consumerGroup sarama.ConsumerGroup
nextConsumer consumer.Logs
topics []string
cancelConsumeLoop context.CancelFunc
unmarshaler LogsUnmarshaler
settings receiver.Settings
autocommitEnabled bool
messageMarking MessageMarking
}
var _ receiver.Traces = (*kafkaTracesConsumer)(nil)
var _ receiver.Metrics = (*kafkaMetricsConsumer)(nil)
var _ receiver.Logs = (*kafkaLogsConsumer)(nil)
func newTracesReceiver(config Config, set receiver.Settings, unmarshalers map[string]TracesUnmarshaler, nextConsumer consumer.Traces) (*kafkaTracesConsumer, error) {
unmarshaler := unmarshalers[config.Encoding]
if unmarshaler == nil {
return nil, errUnrecognizedEncoding
}
// set sarama library's logger to get detailed logs from the library
sarama.Logger = zap.NewStdLog(set.Logger)
c := sarama.NewConfig()
c = setSaramaConsumerConfig(c, &config.SaramaConsumerConfig)
c.ClientID = config.ClientID
c.Metadata.Full = config.Metadata.Full
c.Metadata.Retry.Max = config.Metadata.Retry.Max
c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff
c.Consumer.Offsets.AutoCommit.Enable = config.AutoCommit.Enable
c.Consumer.Offsets.AutoCommit.Interval = config.AutoCommit.Interval
if initialOffset, err := toSaramaInitialOffset(config.InitialOffset); err == nil {
c.Consumer.Offsets.Initial = initialOffset
} else {
return nil, err
}
if config.ProtocolVersion != "" {
version, err := sarama.ParseKafkaVersion(config.ProtocolVersion)
if err != nil {
return nil, err
}
c.Version = version
}
if err := kafka.ConfigureAuthentication(config.Authentication, c); err != nil {
return nil, err
}
client, err := sarama.NewConsumerGroup(config.Brokers, config.GroupID, c)
if err != nil {
return nil, err
}
return &kafkaTracesConsumer{
consumerGroup: client,
topics: []string{config.Topic},
nextConsumer: nextConsumer,
unmarshaler: unmarshaler,
settings: set,
autocommitEnabled: config.AutoCommit.Enable,
messageMarking: config.MessageMarking,
}, nil
}
func (c *kafkaTracesConsumer) Start(_ context.Context, host component.Host) error {
ctx, cancel := context.WithCancel(context.Background())
c.cancelConsumeLoop = cancel
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: c.settings.ID,
Transport: transport,
ReceiverCreateSettings: c.settings,
})
if err != nil {
return err
}
metrics, err := NewKafkaReceiverMetrics(c.settings.MeterProvider.Meter(metadata.ScopeName))
if err != nil {
return err
}
consumerGroup := &tracesConsumerGroupHandler{
id: c.settings.ID,
logger: c.settings.Logger,
unmarshaler: c.unmarshaler,
nextConsumer: c.nextConsumer,
ready: make(chan bool),
obsrecv: obsrecv,
autocommitEnabled: c.autocommitEnabled,
messageMarking: c.messageMarking,
baseConsumerGroupHandler: baseConsumerGroupHandler{
group: c.consumerGroup,
logger: c.settings.Logger,
retryInterval: 1 * time.Second,
pausePartition: make(chan struct{}),
resumePartition: make(chan struct{}),
metrics: metrics,
},
}
go func() {
if err := c.consumeLoop(ctx, consumerGroup); !errors.Is(err, context.Canceled) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
}
}()
<-consumerGroup.ready
return nil
}
func (c *kafkaTracesConsumer) consumeLoop(ctx context.Context, handler sarama.ConsumerGroupHandler) error {
for {
// `Consume` should be called inside an infinite loop, when a
// server-side rebalance happens, the consumer session will need to be
// recreated to get the new claims
if err := c.consumerGroup.Consume(ctx, c.topics, handler); err != nil {
c.settings.Logger.Error("Error from consumer", zap.Error(err))
}
// check if context was cancelled, signaling that the consumer should stop
if ctx.Err() != nil {
c.settings.Logger.Info("Consumer stopped", zap.Error(ctx.Err()))
return ctx.Err()
}
}
}
func (c *kafkaTracesConsumer) Shutdown(context.Context) error {
c.cancelConsumeLoop()
return c.consumerGroup.Close()
}
func newMetricsReceiver(config Config, set receiver.Settings, unmarshalers map[string]MetricsUnmarshaler, nextConsumer consumer.Metrics) (*kafkaMetricsConsumer, error) {
unmarshaler := unmarshalers[config.Encoding]
if unmarshaler == nil {
return nil, errUnrecognizedEncoding
}
// set sarama library's logger to get detailed logs from the library
sarama.Logger = zap.NewStdLog(set.Logger)
c := sarama.NewConfig()
c = setSaramaConsumerConfig(c, &config.SaramaConsumerConfig)
c.ClientID = config.ClientID
c.Metadata.Full = config.Metadata.Full
c.Metadata.Retry.Max = config.Metadata.Retry.Max
c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff
c.Consumer.Offsets.AutoCommit.Enable = config.AutoCommit.Enable
c.Consumer.Offsets.AutoCommit.Interval = config.AutoCommit.Interval
if initialOffset, err := toSaramaInitialOffset(config.InitialOffset); err == nil {
c.Consumer.Offsets.Initial = initialOffset
} else {
return nil, err
}
if config.ProtocolVersion != "" {
version, err := sarama.ParseKafkaVersion(config.ProtocolVersion)
if err != nil {
return nil, err
}
c.Version = version
}
if err := kafka.ConfigureAuthentication(config.Authentication, c); err != nil {
return nil, err
}
client, err := sarama.NewConsumerGroup(config.Brokers, config.GroupID, c)
if err != nil {
return nil, err
}
return &kafkaMetricsConsumer{
consumerGroup: client,
topics: []string{config.Topic},
nextConsumer: nextConsumer,
unmarshaler: unmarshaler,
settings: set,
autocommitEnabled: config.AutoCommit.Enable,
messageMarking: config.MessageMarking,
}, nil
}
func (c *kafkaMetricsConsumer) Start(_ context.Context, host component.Host) error {
ctx, cancel := context.WithCancel(context.Background())
c.cancelConsumeLoop = cancel
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: c.settings.ID,
Transport: transport,
ReceiverCreateSettings: c.settings,
})
if err != nil {
return err
}
metrics, err := NewKafkaReceiverMetrics(c.settings.MeterProvider.Meter(metadata.ScopeName))
if err != nil {
return err
}
metricsConsumerGroup := &metricsConsumerGroupHandler{
id: c.settings.ID,
logger: c.settings.Logger,
unmarshaler: c.unmarshaler,
nextConsumer: c.nextConsumer,
ready: make(chan bool),
obsrecv: obsrecv,
autocommitEnabled: c.autocommitEnabled,
messageMarking: c.messageMarking,
baseConsumerGroupHandler: baseConsumerGroupHandler{
group: c.consumerGroup,
logger: c.settings.Logger,
retryInterval: 1 * time.Second,
pausePartition: make(chan struct{}),
resumePartition: make(chan struct{}),
metrics: metrics,
},
}
go func() {
if err := c.consumeLoop(ctx, metricsConsumerGroup); !errors.Is(err, context.Canceled) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
}
}()
<-metricsConsumerGroup.ready
return nil
}
func (c *kafkaMetricsConsumer) consumeLoop(ctx context.Context, handler sarama.ConsumerGroupHandler) error {
for {
// `Consume` should be called inside an infinite loop, when a
// server-side rebalance happens, the consumer session will need to be
// recreated to get the new claims
if err := c.consumerGroup.Consume(ctx, c.topics, handler); err != nil {
c.settings.Logger.Error("Error from consumer", zap.Error(err))
}
// check if context was cancelled, signaling that the consumer should stop
if ctx.Err() != nil {
c.settings.Logger.Info("Consumer stopped", zap.Error(ctx.Err()))
return ctx.Err()
}
}
}
func (c *kafkaMetricsConsumer) Shutdown(context.Context) error {
c.cancelConsumeLoop()
return c.consumerGroup.Close()
}
func newLogsReceiver(config Config, set receiver.Settings, unmarshalers map[string]LogsUnmarshaler, nextConsumer consumer.Logs) (*kafkaLogsConsumer, error) {
// set sarama library's logger to get detailed logs from the library
sarama.Logger = zap.NewStdLog(set.Logger)
c := sarama.NewConfig()
c = setSaramaConsumerConfig(c, &config.SaramaConsumerConfig)
c.ClientID = config.ClientID
c.Metadata.Full = config.Metadata.Full
c.Metadata.Retry.Max = config.Metadata.Retry.Max
c.Metadata.Retry.Backoff = config.Metadata.Retry.Backoff
c.Consumer.Offsets.AutoCommit.Enable = config.AutoCommit.Enable
c.Consumer.Offsets.AutoCommit.Interval = config.AutoCommit.Interval
if initialOffset, err := toSaramaInitialOffset(config.InitialOffset); err == nil {
c.Consumer.Offsets.Initial = initialOffset
} else {
return nil, err
}
unmarshaler, err := getLogsUnmarshaler(config.Encoding, unmarshalers)
if err != nil {
return nil, err
}
if config.ProtocolVersion != "" {
var version sarama.KafkaVersion
version, err = sarama.ParseKafkaVersion(config.ProtocolVersion)
if err != nil {
return nil, err
}
c.Version = version
}
if err = kafka.ConfigureAuthentication(config.Authentication, c); err != nil {
return nil, err
}
client, err := sarama.NewConsumerGroup(config.Brokers, config.GroupID, c)
if err != nil {
return nil, err
}
return &kafkaLogsConsumer{
consumerGroup: client,
topics: []string{config.Topic},
nextConsumer: nextConsumer,
unmarshaler: unmarshaler,
settings: set,
autocommitEnabled: config.AutoCommit.Enable,
messageMarking: config.MessageMarking,
}, nil
}
func getLogsUnmarshaler(encoding string, unmarshalers map[string]LogsUnmarshaler) (LogsUnmarshaler, error) {
var enc string
unmarshaler, ok := unmarshalers[encoding]
if !ok {
split := strings.SplitN(encoding, "_", 2)
prefix := split[0]
if len(split) > 1 {
enc = split[1]
}
unmarshaler, ok = unmarshalers[prefix].(LogsUnmarshalerWithEnc)
if !ok {
return nil, errUnrecognizedEncoding
}
}
if unmarshalerWithEnc, ok := unmarshaler.(LogsUnmarshalerWithEnc); ok {
// This should be called even when enc is an empty string to initialize the encoding.
unmarshaler, err := unmarshalerWithEnc.WithEnc(enc)
if err != nil {
return nil, err
}
return unmarshaler, nil
}
return unmarshaler, nil
}
func (c *kafkaLogsConsumer) Start(_ context.Context, host component.Host) error {
ctx, cancel := context.WithCancel(context.Background())
c.cancelConsumeLoop = cancel
obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{
ReceiverID: c.settings.ID,
Transport: transport,
ReceiverCreateSettings: c.settings,
})
if err != nil {
return err
}
metrics, err := NewKafkaReceiverMetrics(c.settings.MeterProvider.Meter(metadata.ScopeName))
if err != nil {
return err
}
logsConsumerGroup := &logsConsumerGroupHandler{
id: c.settings.ID,
logger: c.settings.Logger,
unmarshaler: c.unmarshaler,
nextConsumer: c.nextConsumer,
ready: make(chan bool),
obsrecv: obsrecv,
autocommitEnabled: c.autocommitEnabled,
messageMarking: c.messageMarking,
baseConsumerGroupHandler: baseConsumerGroupHandler{
group: c.consumerGroup,
logger: c.settings.Logger,
retryInterval: 1 * time.Second,
pausePartition: make(chan struct{}),
resumePartition: make(chan struct{}),
metrics: metrics,
},
}
go func() {
if err := c.consumeLoop(ctx, logsConsumerGroup); !errors.Is(err, context.Canceled) {
componentstatus.ReportStatus(host, componentstatus.NewFatalErrorEvent(err))
}
}()
<-logsConsumerGroup.ready
return nil
}
func (c *kafkaLogsConsumer) consumeLoop(ctx context.Context, handler sarama.ConsumerGroupHandler) error {
for {
// `Consume` should be called inside an infinite loop, when a
// server-side rebalance happens, the consumer session will need to be
// recreated to get the new claims
if err := c.consumerGroup.Consume(ctx, c.topics, handler); err != nil {
c.settings.Logger.Error("Error from consumer", zap.Error(err))
}
// check if context was cancelled, signaling that the consumer should stop
if ctx.Err() != nil {
c.settings.Logger.Info("Consumer stopped", zap.Error(ctx.Err()))
return ctx.Err()
}
}
}
func (c *kafkaLogsConsumer) Shutdown(context.Context) error {
c.cancelConsumeLoop()
return c.consumerGroup.Close()
}
type baseConsumerGroupHandler struct {
group sarama.ConsumerGroup
logger *zap.Logger
retryInterval time.Duration
pausePartition chan struct{}
resumePartition chan struct{}
metrics *KafkaReceiverMetrics
}
// wrap is now a method of BaseConsumerGroupHandler
func (b *baseConsumerGroupHandler) WithMemoryLimiter(ctx context.Context, claim sarama.ConsumerGroupClaim, consume func() error) error {
// Execute f() immediately
err := consume()
if err == nil || !(err.Error() == errHighMemoryUsage.Error()) {
return err
}
// If errHighMemoryUsage is encountered, enter the retry loop
ticker := time.NewTicker(b.retryInterval)
defer ticker.Stop()
b.logger.Info("applying initial backpressure on Kafka due to high memory usage", zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
b.group.PauseAll()
for {
select {
case <-ticker.C:
err := consume()
if err == nil {
b.logger.Info("resuming normal operation", zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
b.group.ResumeAll()
return nil
}
if !(err.Error() == errHighMemoryUsage.Error()) {
b.group.ResumeAll()
return err
}
b.logger.Info("continuing to apply backpressure on Kafka due to high memory usage", zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
case <-ctx.Done():
if ctx.Err() != nil {
b.logger.Error("session ended before message could be processed", zap.Error(ctx.Err()))
}
return ctx.Err()
}
}
}
type tracesConsumerGroupHandler struct {
baseConsumerGroupHandler
id component.ID
unmarshaler TracesUnmarshaler
nextConsumer consumer.Traces
ready chan bool
readyCloser sync.Once
logger *zap.Logger
obsrecv *receiverhelper.ObsReport
autocommitEnabled bool
messageMarking MessageMarking
}
type metricsConsumerGroupHandler struct {
baseConsumerGroupHandler
id component.ID
unmarshaler MetricsUnmarshaler
nextConsumer consumer.Metrics
ready chan bool
readyCloser sync.Once
logger *zap.Logger
obsrecv *receiverhelper.ObsReport
autocommitEnabled bool
messageMarking MessageMarking
}
type logsConsumerGroupHandler struct {
baseConsumerGroupHandler
id component.ID
unmarshaler LogsUnmarshaler
nextConsumer consumer.Logs
ready chan bool
readyCloser sync.Once
logger *zap.Logger
obsrecv *receiverhelper.ObsReport
autocommitEnabled bool
messageMarking MessageMarking
}
var _ sarama.ConsumerGroupHandler = (*tracesConsumerGroupHandler)(nil)
var _ sarama.ConsumerGroupHandler = (*metricsConsumerGroupHandler)(nil)
var _ sarama.ConsumerGroupHandler = (*logsConsumerGroupHandler)(nil)
func (c *tracesConsumerGroupHandler) Setup(session sarama.ConsumerGroupSession) error {
c.readyCloser.Do(func() {
close(c.ready)
})
c.metrics.partitionStart.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *tracesConsumerGroupHandler) Cleanup(session sarama.ConsumerGroupSession) error {
c.metrics.partitionClose.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *tracesConsumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
c.logger.Info("Starting consumer group", zap.Int32("partition", claim.Partition()))
if !c.autocommitEnabled {
defer session.Commit()
}
for {
select {
case message, ok := <-claim.Messages():
if !ok {
return nil
}
start := time.Now()
c.logger.Debug("Kafka message claimed",
zap.String("value", string(message.Value)),
zap.Time("timestamp", message.Timestamp),
zap.String("topic", message.Topic),
zap.Int32("partition", message.Partition),
zap.Int64("offset", message.Offset),
)
if !c.messageMarking.After {
session.MarkMessage(message, "")
}
ctx := c.obsrecv.StartTracesOp(session.Context())
c.metrics.messagesCount.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffset.Record(
session.Context(),
int64(message.Offset),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffsetLag.Record(
session.Context(),
int64(claim.HighWaterMarkOffset()-message.Offset-1),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
traces, err := c.unmarshaler.Unmarshal(message.Value)
if err != nil {
c.logger.Error("failed to unmarshal message", zap.Error(err))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
spanCount := traces.SpanCount()
err = c.WithMemoryLimiter(session.Context(), claim, func() error { return c.nextConsumer.ConsumeTraces(session.Context(), traces) })
c.obsrecv.EndTracesOp(ctx, c.unmarshaler.Encoding(), spanCount, err)
if err != nil {
c.logger.Error("kafka receiver: failed to export traces", zap.Error(err), zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
if c.messageMarking.After {
session.MarkMessage(message, "")
}
if !c.autocommitEnabled {
session.Commit()
}
c.metrics.processingTime.Record(session.Context(), float64(time.Since(start).Milliseconds()))
if err != nil {
c.logger.Error("failed to record processing time", zap.Error(err))
}
// Should return when `session.Context()` is done.
// If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see:
// https://github.com/Shopify/sarama/issues/1192
case <-session.Context().Done():
return nil
}
}
}
func (c *metricsConsumerGroupHandler) Setup(session sarama.ConsumerGroupSession) error {
c.readyCloser.Do(func() {
close(c.ready)
})
c.metrics.partitionStart.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *metricsConsumerGroupHandler) Cleanup(session sarama.ConsumerGroupSession) error {
c.metrics.partitionClose.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *metricsConsumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
c.logger.Info("Starting consumer group", zap.Int32("partition", claim.Partition()))
if !c.autocommitEnabled {
defer session.Commit()
}
for {
select {
case message, ok := <-claim.Messages():
if !ok {
return nil
}
start := time.Now()
c.logger.Debug("Kafka message claimed",
zap.String("value", string(message.Value)),
zap.Time("timestamp", message.Timestamp),
zap.String("topic", message.Topic))
if !c.messageMarking.After {
session.MarkMessage(message, "")
}
ctx := c.obsrecv.StartMetricsOp(session.Context())
c.metrics.messagesCount.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffset.Record(
session.Context(),
int64(message.Offset),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffsetLag.Record(
session.Context(),
int64(claim.HighWaterMarkOffset()-message.Offset-1),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
metrics, err := c.unmarshaler.Unmarshal(message.Value)
if err != nil {
c.logger.Error("failed to unmarshal message", zap.Error(err))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
dataPointCount := metrics.DataPointCount()
err = c.WithMemoryLimiter(session.Context(), claim, func() error { return c.nextConsumer.ConsumeMetrics(session.Context(), metrics) })
c.obsrecv.EndMetricsOp(ctx, c.unmarshaler.Encoding(), dataPointCount, err)
if err != nil {
c.logger.Error("kafka receiver: failed to export metrics", zap.Error(err), zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
if c.messageMarking.After {
session.MarkMessage(message, "")
}
if !c.autocommitEnabled {
session.Commit()
}
c.metrics.processingTime.Record(
session.Context(),
float64(time.Since(start).Milliseconds()),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
if err != nil {
c.logger.Error("failed to record processing time", zap.Error(err))
}
// Should return when `session.Context()` is done.
// If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see:
// https://github.com/Shopify/sarama/issues/1192
case <-session.Context().Done():
return nil
}
}
}
func (c *logsConsumerGroupHandler) Setup(session sarama.ConsumerGroupSession) error {
c.readyCloser.Do(func() {
close(c.ready)
})
c.metrics.partitionStart.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *logsConsumerGroupHandler) Cleanup(session sarama.ConsumerGroupSession) error {
c.metrics.partitionClose.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
return nil
}
func (c *logsConsumerGroupHandler) ConsumeClaim(session sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
c.logger.Info("Starting consumer group", zap.Int32("partition", claim.Partition()))
if !c.autocommitEnabled {
defer session.Commit()
}
for {
select {
case message, ok := <-claim.Messages():
if !ok {
return nil
}
start := time.Now()
c.logger.Debug("Kafka message claimed",
zap.String("value", string(message.Value)),
zap.Time("timestamp", message.Timestamp),
zap.String("topic", message.Topic),
zap.Int32("partition", message.Partition),
zap.Int64("offset", message.Offset))
if !c.messageMarking.After {
session.MarkMessage(message, "")
}
ctx := c.obsrecv.StartLogsOp(session.Context())
c.metrics.messagesCount.Add(
session.Context(),
1,
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffset.Record(
session.Context(),
int64(message.Offset),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
c.metrics.messageOffsetLag.Record(
session.Context(),
int64(claim.HighWaterMarkOffset()-message.Offset-1),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
logs, err := c.unmarshaler.Unmarshal(message.Value)
if err != nil {
c.logger.Error("failed to unmarshal message", zap.Error(err))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
logCount := logs.LogRecordCount()
err = c.WithMemoryLimiter(session.Context(), claim, func() error { return c.nextConsumer.ConsumeLogs(session.Context(), logs) })
c.obsrecv.EndLogsOp(ctx, c.unmarshaler.Encoding(), logCount, err)
if err != nil {
c.logger.Error("kafka receiver: failed to export logs", zap.Error(err), zap.Int32("partition", claim.Partition()), zap.String("topic", claim.Topic()))
if c.messageMarking.After && c.messageMarking.OnError {
session.MarkMessage(message, "")
}
return err
}
if c.messageMarking.After {
session.MarkMessage(message, "")
}
if !c.autocommitEnabled {
session.Commit()
}
c.metrics.processingTime.Record(
session.Context(),
float64(time.Since(start).Milliseconds()),
metric.WithAttributes(attribute.String("name", c.id.String())),
)
if err != nil {
c.logger.Error("failed to record processing time", zap.Error(err))
}
// Should return when `session.Context()` is done.
// If not, will raise `ErrRebalanceInProgress` or `read tcp <ip>:<port>: i/o timeout` when kafka rebalance. see:
// https://github.com/Shopify/sarama/issues/1192
case <-session.Context().Done():
return nil
}
}
}
func toSaramaInitialOffset(initialOffset string) (int64, error) {
switch initialOffset {
case offsetEarliest:
return sarama.OffsetOldest, nil
case offsetLatest:
fallthrough
case "":
return sarama.OffsetNewest, nil
default:
return 0, errInvalidInitialOffset
}
}
func setSaramaConsumerConfig(sc *sarama.Config, c *SaramaConsumerConfig) *sarama.Config {
if c.ConsumerFetchMinBytes != 0 {
sc.Consumer.Fetch.Min = c.ConsumerFetchMinBytes
}
if c.ConsumerFetchDefaultBytes != 0 {
sc.Consumer.Fetch.Default = c.ConsumerFetchDefaultBytes
}
if c.ConsumerFetchMaxBytes != 0 {
sc.Consumer.Fetch.Max = c.ConsumerFetchMaxBytes
if c.ConsumerFetchMaxBytes > sarama.MaxResponseSize {
// If the user has set a Consumer.Fetch.Max that is larger than the MaxResponseSize, then
// set the MaxResponseSize to the Consumer.Fetch.Max.
// If we dont do this, then sarama will reject messages > 100MB with a packet decoding error.
sarama.MaxResponseSize = c.ConsumerFetchMaxBytes
}
}
if c.MaxProcessingTime != 0 {
sc.Consumer.MaxProcessingTime = c.MaxProcessingTime
}
if c.GroupSessionTimeout != 0 {
sc.Consumer.Group.Session.Timeout = c.GroupSessionTimeout
}
if c.MessagesChannelSize != 0 {
sc.ChannelBufferSize = c.MessagesChannelSize
}
return sc
}