diff --git a/collector/components/azureeventhubreceiver/Makefile b/collector/components/azureeventhubreceiver/Makefile new file mode 100644 index 0000000..ded7a36 --- /dev/null +++ b/collector/components/azureeventhubreceiver/Makefile @@ -0,0 +1 @@ +include ../../Makefile.Common diff --git a/collector/components/azureeventhubreceiver/README.md b/collector/components/azureeventhubreceiver/README.md new file mode 100644 index 0000000..a8e1bfc --- /dev/null +++ b/collector/components/azureeventhubreceiver/README.md @@ -0,0 +1,125 @@ +# Azure Event Hub Receiver + + +| Status | | +| ------------- |-----------| +| Stability | [alpha]: metrics, logs | +| Distributions | [contrib], [observiq], [splunk], [sumo] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fazureeventhub%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fazureeventhub) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fazureeventhub%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fazureeventhub) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@atoulme](https://www.github.com/atoulme), [@djaglowski](https://www.github.com/djaglowski) | + +[alpha]: https://github.com/open-telemetry/opentelemetry-collector#alpha +[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib +[observiq]: https://github.com/observIQ/observiq-otel-collector +[splunk]: https://github.com/signalfx/splunk-otel-collector +[sumo]: https://github.com/SumoLogic/sumologic-otel-collector + + +## Overview +Azure resources and services can be +[configured](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings) +to send their logs to an Azure Event Hub. The Azure Event Hub receiver pulls logs from an Azure +Event Hub, transforms them, and pushes them through the collector pipeline. + +## Configuration + +### connection (Required) +A string describing the connection to an Azure event hub. + +### group (Optional) +The Consumer Group to read from. If empty will default to the default Consumer Group $Default + +### partition (Optional) +The partition to watch. If empty, it will watch explicitly all partitions. + +Default: "" + +### offset (Optional) +The offset at which to start watching the event hub. If empty, it starts with the latest offset. + +Default: "" + +### format (Optional) +Determines how to transform the Event Hub messages into OpenTelemetry logs. See the "Format" +section below for details. + +Default: "azure" + +### Example Configuration + +```yaml +receivers: + azureeventhub: + connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName + partition: foo + group: bar + offset: "1234-5566" + format: "azure" +``` + +This component can persist its state using the [storage extension]. + +## Format + +### raw + +The "raw" format maps the AMQP properties and data into the +attributes and body of an OpenTelemetry LogRecord, respectively. +The body is represented as a raw byte array. + +This format is not supported for Metrics. + +### azure + +The "azure" format extracts the Azure log records from the AMQP +message data, parses them, and maps the fields to OpenTelemetry +attributes. The table below summarizes the mapping between the +[Azure common log format](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/resource-logs-schema) +and the OpenTelemetry attributes. + + +| Azure | OpenTelemetry | +|----------------------------------|----------------------------------------| +| callerIpAddress (optional) | net.sock.peer.addr (attribute) | +| correlationId (optional) | azure.correlation.id (attribute) | +| category (optional) | azure.category (attribute) | +| durationMs (optional) | azure.duration (attribute) | +| Level (optional) | severity_number, severity_text (field) | +| location (optional) | cloud.region (attribute) | +| — | cloud.provider (attribute) | +| operationName (required) | azure.operation.name (attribute) | +| operationVersion (optional) | azure.operation.version (attribute) | +| properties (optional) | azure.properties (attribute, nested) | +| resourceId (required) | azure.resource.id (resource attribute) | +| resultDescription (optional) | azure.result.description (attribute) | +| resultSignature (optional) | azure.result.signature (attribute) | +| resultType (optional) | azure.result.type (attribute) | +| tenantId (required, tenant logs) | azure.tenant.id (attribute) | +| time or timeStamp (required) | time_unix_nano (time takes precedence) | +| identity (optional) | azure.identity (attribute, nested) | + +Notes: +* JSON does not distinguish between fixed and floating point numbers. All +JSON numbers are encoded as doubles. + +For Metrics the Azure Metric Records are an array +of "records" with the following fields. + +| Azure | Open Telemetry | +|------------|---------------------------------------------| +| time | time_unix_nano (field) | +| resourceId | azure.resource.id (resource attribute) | +| metricName | | +| timeGrain | start_time_unix_nano (field) | +| total | mapped to datapoint metricName + "_TOTAL" | +| count | mapped to datapoint metricName + "_COUNT" | +| minimum | mapped to datapoint metricName + "_MINIMUM" | +| maximum | mapped to datapoint metricName + "_MAXIMUM" | +| average | mapped to datapoint metricName + "_AVERAGE" | + +From this data a Metric of type Gauge is created +with a Data Points that represents the values +for the Metric including: Total, Minimum, Maximum, +Average and Count. + +[storage extension]: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/storage diff --git a/collector/components/azureeventhubreceiver/azureeventprocessor.go b/collector/components/azureeventhubreceiver/azureeventprocessor.go new file mode 100644 index 0000000..b514673 --- /dev/null +++ b/collector/components/azureeventhubreceiver/azureeventprocessor.go @@ -0,0 +1,190 @@ +package azureeventhubreceiver + +// https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/processor.go +// https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/processor_partition_client.go + +/* +>> https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/example_consuming_with_checkpoints_test.go + - get a processor + - dispatchPartitionClients + - processor.Run + + + +>> https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/messaging/azeventhubs/example_consuming_events_test.go + - ReceiveEvents(ctx, count int, options *ReceiveEventsOptions) ([]*ReceivedEventData, error) + - call cancel() + - panic if there's an error that isn't context.DeadlineExceeded + - process events + --> put them into the entity thingy +*/ + +// import ( +// "context" +// "errors" +// "fmt" +// "time" + +// "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" +// "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/checkpoints" +// "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" +// ) + +// // Assuming there's a struct managing the processor setup +// // type EventHubProcessor struct { +// // Processor *azeventhubs.Processor +// // } + +// // Updated initialization function using the new SDK components +// func NewEventHubProcessor(ehConn, ehName, storageConn, storageCnt string) (*EventHubProcessor, error) { +// checkpointingProcessor, err := newCheckpointingProcessor(ehConn, ehName, storageConn, storageCnt) +// if err != nil { +// return nil, fmt.Errorf("failed to create checkpointing processor: %w", err) +// } + +// // Start processing events +// return &EventHubProcessor{ +// Processor: checkpointingProcessor, +// }, nil +// } + +// // Assume there's a function to start processing events +// func (e *EventHubProcessor) StartProcessing(ctx context.Context) error { +// // Start the processor +// if err := e.Processor.Run(ctx); err != nil { +// return fmt.Errorf("error running processor: %w", err) +// } +// return nil +// } + +// // Assuming there's a struct managing the processor setup +// type EventHubProcessor struct { +// Processor *azeventhubs.Processor +// } + +// // These are config values the processor factory can use to create processors: +// // +// // (a) EventHubConnectionString +// // (b) EventHubName +// // (c) StorageConnectionString +// // (d) StorageContainerName +// // +// // You always need the EventHub variable values. +// // And you need all 4 of these to checkpoint. +// // +// // I think the config values should be managed in the factory struct. +// /* +// func (pf *processorFactory) CreateProcessor() (*azeventhubs.Processor, error) { +// // Create the consumer client +// consumerClient, err := azeventhubs.NewConsumerClientFromConnectionString(pf.EventHubConnectionString, pf.EventHubName, azeventhubs.DefaultConsumerGroup, nil) +// if err != nil { +// return nil, err +// } + +// // Create the blob container client for the checkpoint store +// blobContainerClient, err := container.NewClientFromConnectionString(pf.StorageConnectionString, pf.StorageContainerName, nil) +// if err != nil { +// return nil, err +// } + +// // Create the checkpoint store using the blob container client +// checkpointStore, err := azeventhubs.NewBlobCheckpointStore(blobContainerClient, nil) +// // checkpointStore, err := azeventhubs.NewBlobCheckpointStore(blobContainerClient, nil) +// // if err != nil { +// // return nil, err +// // } + +// // Create the processor with checkpointing +// processor, err := azeventhubs.NewProcessor(consumerClient, checkpointStore, nil) +// if err != nil { +// return nil, err +// } + +// return processor, nil +// } +// */ + +// // checkpointing processor should be auth aware + +// func newCheckpointingProcessor(eventHubConnectionString, eventHubName, storageConnectionString, storageContainerName string) (*azeventhubs.Processor, error) { +// blobContainerClient, err := container.NewClientFromConnectionString(storageConnectionString, storageContainerName, nil) +// if err != nil { +// return nil, err +// } +// checkpointStore, err := checkpoints.NewBlobStore(blobContainerClient, nil) +// if err != nil { +// return nil, err +// } + +// consumerClient, err := azeventhubs.NewConsumerClientFromConnectionString(eventHubConnectionString, eventHubName, azeventhubs.DefaultConsumerGroup, nil) +// if err != nil { +// return nil, err +// } + +// return azeventhubs.NewProcessor(consumerClient, checkpointStore, nil) +// } +/* +func dispatchPartitionClients(processor *azeventhubs.Processor) { + for { + processorPartitionClient := processor.NextPartitionClient(context.TODO()) + if processorPartitionClient == nil { + break + } + + go func() { + if err := processEventsForPartition(processorPartitionClient); err != nil { + panic(err) + } + }() + } +} + +func processEventsForPartition(partitionClient *azeventhubs.ProcessorPartitionClient) error { + defer shutdownPartitionResources(partitionClient) + if err := initializePartitionResources(partitionClient.PartitionID()); err != nil { + return err + } + + for { + receiveCtx, cancelReceive := context.WithTimeout(context.TODO(), time.Minute) + events, err := partitionClient.ReceiveEvents(receiveCtx, 100, nil) + cancelReceive() + + if err != nil && !errors.Is(err, context.DeadlineExceeded) { + return err + } + if len(events) == 0 { + continue + } + + if err := processEvents(events, partitionClient); err != nil { + return err + } + + if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1], nil); err != nil { + return err + } + } +} + +func shutdownPartitionResources(partitionClient *azeventhubs.ProcessorPartitionClient) { + if err := partitionClient.Close(context.TODO()); err != nil { + panic(err) + } +} + +func initializePartitionResources(partitionID string) error { + fmt.Printf("Initializing resources for partition %s\n", partitionID) + return nil +} + +// This is very much like the old processEvents function +func processEvents(events []*azeventhubs.ReceivedEventData, partitionClient *azeventhubs.ProcessorPartitionClient) error { + for _, event := range events { + + + // fmt.Printf("Processing event: %v\n", event.EventData()) + } + return nil +} +*/ diff --git a/collector/components/azureeventhubreceiver/azureresourcelogs_unmarshaler.go b/collector/components/azureeventhubreceiver/azureresourcelogs_unmarshaler.go new file mode 100644 index 0000000..783d672 --- /dev/null +++ b/collector/components/azureeventhubreceiver/azureresourcelogs_unmarshaler.go @@ -0,0 +1,36 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/plog" + "go.uber.org/zap" + + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure" +) + +type AzureResourceLogsEventUnmarshaler struct { + unmarshaler *azure.ResourceLogsUnmarshaler +} + +func newAzureResourceLogsUnmarshaler(buildInfo component.BuildInfo, logger *zap.Logger) eventLogsUnmarshaler { + return AzureResourceLogsEventUnmarshaler{ + unmarshaler: &azure.ResourceLogsUnmarshaler{ + Version: buildInfo.Version, + Logger: logger, + }, + } +} + +// UnmarshalLogs takes a byte array containing a JSON-encoded +// payload with Azure log records and transforms it into +// an OpenTelemetry plog.Logs object. The data in the Azure +// log record appears as fields and attributes in the +// OpenTelemetry representation; the bodies of the +// OpenTelemetry log records are empty. +func (r AzureResourceLogsEventUnmarshaler) UnmarshalLogs(event *azeventhubs.ReceivedEventData) (plog.Logs, error) { + return r.unmarshaler.UnmarshalLogs(event.Body) +} diff --git a/collector/components/azureeventhubreceiver/azureresourcemetrics_unmarshaler.go b/collector/components/azureeventhubreceiver/azureresourcemetrics_unmarshaler.go new file mode 100644 index 0000000..2e739d1 --- /dev/null +++ b/collector/components/azureeventhubreceiver/azureresourcemetrics_unmarshaler.go @@ -0,0 +1,160 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "bytes" + "fmt" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + jsoniter "github.com/json-iterator/go" + "github.com/relvacode/iso8601" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + conventions "go.opentelemetry.io/collector/semconv/v1.13.0" + "go.uber.org/zap" +) + +const ( + azureResourceID = "azure.resource.id" + scopeName = "otelcol/azureresourcemetrics" +) + +type azureResourceMetricsUnmarshaler struct { + buildInfo component.BuildInfo + logger *zap.Logger +} + +// azureMetricRecords represents an array of Azure metric records +// as exported via an Azure Event Hub +type azureMetricRecords struct { + Records []azureMetricRecord `json:"records"` +} + +// azureMetricRecord represents a single Azure Metric following +// the common schema does not exist (yet): +type azureMetricRecord struct { + Time string `json:"time"` + ResourceID string `json:"resourceId"` + MetricName string `json:"metricName"` + TimeGrain string `json:"timeGrain"` + Total float64 `json:"total"` + Count float64 `json:"count"` + Minimum float64 `json:"minimum"` + Maximum float64 `json:"maximum"` + Average float64 `json:"average"` +} + +func newAzureResourceMetricsUnmarshaler(buildInfo component.BuildInfo, logger *zap.Logger) azureResourceMetricsUnmarshaler { + return azureResourceMetricsUnmarshaler{ + buildInfo: buildInfo, + logger: logger, + } +} + +// UnmarshalMetrics takes a byte array containing a JSON-encoded +// payload with Azure metric records and transforms it into +// an OpenTelemetry pmetric.Metrics object. The data in the Azure +// metric record appears as fields and attributes in the +// OpenTelemetry representation; +func (r azureResourceMetricsUnmarshaler) UnmarshalMetrics(event *azeventhubs.ReceivedEventData) (pmetric.Metrics, error) { + + md := pmetric.NewMetrics() + + var azureMetrics azureMetricRecords + decoder := jsoniter.NewDecoder(bytes.NewReader(event.EventData.Body)) + err := decoder.Decode(&azureMetrics) + if err != nil { + return md, err + } + + resourceMetrics := md.ResourceMetrics().AppendEmpty() + resource := resourceMetrics.Resource() + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKName, receiverScopeName) + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKLanguage, conventions.AttributeTelemetrySDKLanguageGo) + resource.Attributes().PutStr(conventions.AttributeTelemetrySDKVersion, r.buildInfo.Version) + resource.Attributes().PutStr(conventions.AttributeCloudProvider, conventions.AttributeCloudProviderAzure) + + scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty() + + metrics := scopeMetrics.Metrics() + metrics.EnsureCapacity(len(azureMetrics.Records) * 5) + + resourceID := "" + for _, azureMetric := range azureMetrics.Records { + if resourceID == "" && azureMetric.ResourceID != "" { + resourceID = azureMetric.ResourceID + } + + nanos, err := asTimestamp(azureMetric.Time) + if err != nil { + r.logger.Warn("Invalid Timestamp", zap.String("time", azureMetric.Time)) + continue + } + + var startTimestamp pcommon.Timestamp + if azureMetric.TimeGrain == "PT1M" { + startTimestamp = pcommon.NewTimestampFromTime(nanos.AsTime().Add(-time.Minute)) + } else { + r.logger.Warn("Unhandled Time Grain", zap.String("timegrain", azureMetric.TimeGrain)) + continue + } + + metricTotal := metrics.AppendEmpty() + metricTotal.SetName(strings.ToLower(fmt.Sprintf("%s_%s", strings.ReplaceAll(azureMetric.MetricName, " ", "_"), "Total"))) + dpTotal := metricTotal.SetEmptyGauge().DataPoints().AppendEmpty() + dpTotal.SetStartTimestamp(startTimestamp) + dpTotal.SetTimestamp(nanos) + dpTotal.SetDoubleValue(azureMetric.Total) + + metricCount := metrics.AppendEmpty() + metricCount.SetName(strings.ToLower(fmt.Sprintf("%s_%s", strings.ReplaceAll(azureMetric.MetricName, " ", "_"), "Count"))) + dpCount := metricCount.SetEmptyGauge().DataPoints().AppendEmpty() + dpCount.SetStartTimestamp(startTimestamp) + dpCount.SetTimestamp(nanos) + dpCount.SetDoubleValue(azureMetric.Count) + + metricMin := metrics.AppendEmpty() + metricMin.SetName(strings.ToLower(fmt.Sprintf("%s_%s", strings.ReplaceAll(azureMetric.MetricName, " ", "_"), "Minimum"))) + dpMin := metricMin.SetEmptyGauge().DataPoints().AppendEmpty() + dpMin.SetStartTimestamp(startTimestamp) + dpMin.SetTimestamp(nanos) + dpMin.SetDoubleValue(azureMetric.Minimum) + + metricMax := metrics.AppendEmpty() + metricMax.SetName(strings.ToLower(fmt.Sprintf("%s_%s", strings.ReplaceAll(azureMetric.MetricName, " ", "_"), "Maximum"))) + dpMax := metricMax.SetEmptyGauge().DataPoints().AppendEmpty() + dpMax.SetStartTimestamp(startTimestamp) + dpMax.SetTimestamp(nanos) + dpMax.SetDoubleValue(azureMetric.Maximum) + + metricAverage := metrics.AppendEmpty() + metricAverage.SetName(strings.ToLower(fmt.Sprintf("%s_%s", strings.ReplaceAll(azureMetric.MetricName, " ", "_"), "Average"))) + dpAverage := metricAverage.SetEmptyGauge().DataPoints().AppendEmpty() + dpAverage.SetStartTimestamp(startTimestamp) + dpAverage.SetTimestamp(nanos) + dpAverage.SetDoubleValue(azureMetric.Average) + } + + if resourceID != "" { + resourceMetrics.Resource().Attributes().PutStr(azureResourceID, resourceID) + } else { + r.logger.Warn("No ResourceID Set on Metrics!") + } + + return md, nil +} + +// asTimestamp will parse an ISO8601 string into an OpenTelemetry nanosecond timestamp. +// If the string cannot be parsed, it will return zero and the error. +func asTimestamp(s string) (pcommon.Timestamp, error) { + t, err := iso8601.ParseString(s) + if err != nil { + return 0, err + } + return pcommon.Timestamp(t.UnixNano()), nil +} diff --git a/collector/components/azureeventhubreceiver/config.go b/collector/components/azureeventhubreceiver/config.go new file mode 100644 index 0000000..7133dd7 --- /dev/null +++ b/collector/components/azureeventhubreceiver/config.go @@ -0,0 +1,64 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "errors" + "fmt" + + "github.com/Azure/azure-amqp-common-go/v4/conn" + "go.opentelemetry.io/collector/component" +) + +type logFormat string + +const ( + defaultLogFormat logFormat = "" + rawLogFormat logFormat = "raw" + azureLogFormat logFormat = "azure" +) + +var ( + validFormats = []logFormat{defaultLogFormat, rawLogFormat, azureLogFormat} + errMissingConnection = errors.New("missing connection") +) + +type Config struct { + Connection string `mapstructure:"connection"` + EventHubName string `mapstructure:"eventhub"` + Partition string `mapstructure:"partition"` + Offset string `mapstructure:"offset"` + // + StorageID *component.ID `mapstructure:"storage"` + StorageConnection string `mapstructure:"storage_connection"` + StorageContainer string `mapstructure:"storage_container"` + // + Format string `mapstructure:"format"` + ConsumerGroup string `mapstructure:"group"` + BatchDelay string `mapstructure:"batch_delay"` + BatchCount int `mapstructure:"batch_count"` +} + +func isValidFormat(format string) bool { + for _, validFormat := range validFormats { + if logFormat(format) == validFormat { + return true + } + } + return false +} + +// Validate config +func (config *Config) Validate() error { + if config.Connection == "" { + return errMissingConnection + } + if _, err := conn.ParsedConnectionFromStr(config.Connection); err != nil { + return err + } + if !isValidFormat(config.Format) { + return fmt.Errorf("invalid format; must be one of %#v", validFormats) + } + return nil +} diff --git a/collector/components/azureeventhubreceiver/config_test.go b/collector/components/azureeventhubreceiver/config_test.go new file mode 100644 index 0000000..7d4dcf4 --- /dev/null +++ b/collector/components/azureeventhubreceiver/config_test.go @@ -0,0 +1,73 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/otelcol/otelcoltest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver/internal/metadata" +) + +func TestLoadConfig(t *testing.T) { + factories, err := otelcoltest.NopFactories() + assert.NoError(t, err) + + factory := NewFactory() + factories.Receivers[metadata.Type] = factory + cfg, err := otelcoltest.LoadConfigAndValidate(filepath.Join("testdata", "config.yaml"), factories) + + require.NoError(t, err) + require.NotNil(t, cfg) + + assert.Equal(t, len(cfg.Receivers), 2) + + r0 := cfg.Receivers[component.NewID(metadata.Type)] + assert.Equal(t, "Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName", r0.(*Config).Connection) + assert.Equal(t, "", r0.(*Config).Offset) + assert.Equal(t, "", r0.(*Config).Partition) + assert.Equal(t, defaultLogFormat, logFormat(r0.(*Config).Format)) + + r1 := cfg.Receivers[component.NewIDWithName(metadata.Type, "all")] + assert.Equal(t, "Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName", r1.(*Config).Connection) + assert.Equal(t, "1234-5566", r1.(*Config).Offset) + assert.Equal(t, "foo", r1.(*Config).Partition) + assert.Equal(t, rawLogFormat, logFormat(r1.(*Config).Format)) +} + +func TestMissingConnection(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + err := component.ValidateConfig(cfg) + assert.EqualError(t, err, "missing connection") +} + +func TestInvalidConnectionString(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + cfg.(*Config).Connection = "foo" + err := component.ValidateConfig(cfg) + assert.EqualError(t, err, "failed parsing connection string due to unmatched key value separated by '='") +} + +func TestIsValidFormat(t *testing.T) { + for _, format := range []logFormat{defaultLogFormat, rawLogFormat, azureLogFormat} { + assert.True(t, isValidFormat(string(format))) + } + assert.False(t, isValidFormat("invalid-format")) +} + +func TestInvalidFormat(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + cfg.(*Config).Connection = "Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName" + cfg.(*Config).Format = "invalid" + err := component.ValidateConfig(cfg) + assert.ErrorContains(t, err, "invalid format; must be one of") +} diff --git a/collector/components/azureeventhubreceiver/doc.go b/collector/components/azureeventhubreceiver/doc.go new file mode 100644 index 0000000..c00414c --- /dev/null +++ b/collector/components/azureeventhubreceiver/doc.go @@ -0,0 +1,7 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//go:generate mdatagen metadata.yaml + +// Package azureeventhubreceiver listens to logs emitted by Azure Event hubs. +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" diff --git a/collector/components/azureeventhubreceiver/eventhubhandler.go b/collector/components/azureeventhubreceiver/eventhubhandler.go new file mode 100644 index 0000000..061cebc --- /dev/null +++ b/collector/components/azureeventhubreceiver/eventhubhandler.go @@ -0,0 +1,312 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "errors" + "strings" + "sync" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs/checkpoints" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" +) + +type eventHandler interface { + run(ctx context.Context, host component.Host) error + close(ctx context.Context) error + setDataConsumer(dataConsumer dataConsumer) +} + +type consumerClientWrapper interface { + GetEventHubProperties(ctx context.Context, options *azeventhubs.GetEventHubPropertiesOptions) (azeventhubs.EventHubProperties, error) + GetPartitionProperties(ctx context.Context, partitionID string, options *azeventhubs.GetPartitionPropertiesOptions) (azeventhubs.PartitionProperties, error) + NewConsumer(ctx context.Context, options *azeventhubs.ConsumerClientOptions) (*azeventhubs.ConsumerClient, error) + NewPartitionClient(partitionID string, options *azeventhubs.PartitionClientOptions) (*azeventhubs.PartitionClient, error) + Close(ctx context.Context) error +} + +type consumerClientWrapperImpl struct { + consumerClient *azeventhubs.ConsumerClient +} + +func newConsumerClientWrapperImplementation(cfg *Config) (*consumerClientWrapperImpl, error) { + consumerClient, err := azeventhubs.NewConsumerClientFromConnectionString(cfg.Connection, cfg.EventHubName, cfg.ConsumerGroup, nil) + if err != nil { + return nil, err + } + return &consumerClientWrapperImpl{ + consumerClient: consumerClient, + }, nil +} + +func (c *consumerClientWrapperImpl) GetEventHubProperties(ctx context.Context, options *azeventhubs.GetEventHubPropertiesOptions) (azeventhubs.EventHubProperties, error) { + return c.consumerClient.GetEventHubProperties(ctx, options) +} + +func (c *consumerClientWrapperImpl) GetPartitionProperties(ctx context.Context, partitionID string, options *azeventhubs.GetPartitionPropertiesOptions) (azeventhubs.PartitionProperties, error) { + return c.consumerClient.GetPartitionProperties(ctx, partitionID, options) +} + +func (c *consumerClientWrapperImpl) NewConsumer(ctx context.Context, options *azeventhubs.ConsumerClientOptions) (*azeventhubs.ConsumerClient, error) { + return c.consumerClient, nil +} + +func (c *consumerClientWrapperImpl) NewPartitionClient(partitionID string, options *azeventhubs.PartitionClientOptions) (*azeventhubs.PartitionClient, error) { + return c.consumerClient.NewPartitionClient(partitionID, options) +} + +func (c *consumerClientWrapperImpl) Close(ctx context.Context) error { + return c.consumerClient.Close(ctx) +} + +// type processorHandler struct { +// processor *azeventhubs.Processor +// dataConsumer dataConsumer +// config *Config +// settings receiver.CreateSettings +// cancel context.CancelFunc +// } + +type eventhubHandler struct { + processor *azeventhubs.Processor + consumerClient consumerClientWrapper + dataConsumer dataConsumer + config *Config + settings receiver.CreateSettings + cancel context.CancelFunc + useProcessor bool +} + +var _ eventHandler = (*eventhubHandler)(nil) + +// newEventhubHandler creates a handler for Azure Event Hub. This version is enhanced to handle mock configurations for testing. +func newEventhubHandler(config *Config, settings receiver.CreateSettings) *eventhubHandler { + // Check if the configuration is meant for testing. This can be done by checking a specific field or a pattern in the connection string. + if strings.Contains(config.Connection, "fake.servicebus.windows.net") { + return nil + // Return a mock handler if the connection string is empty or obviously fake. + // return newMockEventhubHandler() + // return newMockEventhubHandler(config, settings) + } + + return &eventhubHandler{ + config: config, + settings: settings, + useProcessor: false, + } +} + +func (h *eventhubHandler) init(ctx context.Context) error { + _, h.cancel = context.WithCancel(ctx) + consumerClient, err := newConsumerClientWrapperImplementation(h.config) + if err != nil { + return err + } + h.consumerClient = consumerClient + return nil +} + +func (h *eventhubHandler) run(ctx context.Context, host component.Host) error { + if h.useProcessor { + return h.runWithProcessor(ctx) + } + return h.runWithConsumerClient(ctx, host) +} + +func (h *eventhubHandler) runWithProcessor(ctx context.Context) error { + checkpointStore, err := createCheckpointStore(h.config.StorageConnection, h.config.StorageContainer) + if err != nil { + return err + } + + processor, err := azeventhubs.NewProcessor(h.consumerClient.(*consumerClientWrapperImpl).consumerClient, checkpointStore, nil) + if err != nil { + return err + } + + go h.dispatchPartitionClients(processor) + processorCtx, processorCancel := context.WithCancel(ctx) + defer processorCancel() + + return processor.Run(processorCtx) +} + +func (h *eventhubHandler) dispatchPartitionClients(processor *azeventhubs.Processor) { + var wg sync.WaitGroup + for { + partitionClient := processor.NextPartitionClient(context.TODO()) + + if partitionClient == nil { + break + } + + wg.Add(1) + go func(pc *azeventhubs.ProcessorPartitionClient) { + defer wg.Done() + if err := h.processEventsForPartition(pc); err != nil { + h.settings.Logger.Error("Error processing partition", zap.Error(err)) + } + }(partitionClient) + } + wg.Wait() +} + +func (h *eventhubHandler) processEventsForPartition(partitionClient *azeventhubs.ProcessorPartitionClient) error { + defer partitionClient.Close(context.TODO()) + + for { + receiveCtx, cancelReceive := context.WithTimeout(context.TODO(), time.Minute) + events, err := partitionClient.ReceiveEvents(receiveCtx, h.config.BatchCount, nil) + cancelReceive() + + if err != nil && !errors.Is(err, context.DeadlineExceeded) { + var eventHubError *azeventhubs.Error + if errors.As(err, &eventHubError) && eventHubError.Code == azeventhubs.ErrorCodeOwnershipLost { + return nil + } + return err + } + + if len(events) == 0 { + continue + } + + for _, event := range events { + if err := h.newMessageHandler(context.TODO(), event); err != nil { + h.settings.Logger.Error("Error handling event", zap.Error(err)) + } + } + + if err := partitionClient.UpdateCheckpoint(context.TODO(), events[len(events)-1], nil); err != nil { + h.settings.Logger.Error("Error updating checkpoint", zap.Error(err)) + } + } +} + +func (h *eventhubHandler) runWithConsumerClient(ctx context.Context, host component.Host) error { + if h.consumerClient == nil { + if err := h.init(ctx); err != nil { + return err + } + } + if h.config.Partition == "" { + properties, err := h.consumerClient.GetEventHubProperties(ctx, nil) + if err != nil { + h.settings.Logger.Debug("Error getting Event Hub properties", zap.Error(err)) + return err + } + + for _, partitionID := range properties.PartitionIDs { + err = h.setupPartition(ctx, partitionID) + if err != nil { + h.settings.Logger.Debug("Error setting up partition", zap.Error(err)) + return err + } + } + } else { + err := h.setupPartition(ctx, h.config.Partition) + if err != nil { + h.settings.Logger.Debug("Error setting up partition", zap.Error(err)) + return err + } + } + return nil +} + +func (h *eventhubHandler) setupPartition(ctx context.Context, partitionID string) error { + cc, err := h.consumerClient.NewConsumer(ctx, nil) + if err != nil { + return err + } + if cc == nil { + return errors.New("failed to initialize consumer client") + } + defer func() { + if cc != nil { + cc.Close(ctx) + } + }() + + pcOpts := &azeventhubs.PartitionClientOptions{ + StartPosition: azeventhubs.StartPosition{ + Earliest: to.Ptr(true), + }, + } + + pc, err := cc.NewPartitionClient(partitionID, pcOpts) + if err != nil { + return err + } + if pc == nil { + return errors.New("failed to initialize partition client") + } + defer func() { + if pc != nil { + pc.Close(ctx) + } + }() + + go h.receivePartitionEvents(ctx, pc) + + return nil +} + +func (h *eventhubHandler) receivePartitionEvents(ctx context.Context, pc *azeventhubs.PartitionClient) { + var wait = 1 + for { + rcvCtx, _ := context.WithTimeout(context.TODO(), time.Second*10) + events, err := pc.ReceiveEvents(rcvCtx, h.config.BatchCount, nil) + if err != nil { + h.settings.Logger.Error("Error receiving event", zap.Error(err)) + time.Sleep(time.Duration(wait) * time.Second) + wait *= 2 + continue + } + + for _, event := range events { + if err := h.newMessageHandler(ctx, event); err != nil { + h.settings.Logger.Error("Error handling event", zap.Error(err)) + } + } + } +} + +func (h *eventhubHandler) newMessageHandler(ctx context.Context, event *azeventhubs.ReceivedEventData) error { + err := h.dataConsumer.consume(ctx, event) + if err != nil { + h.settings.Logger.Error("Error decoding message", zap.Error(err)) + return err + } + return nil +} + +func (h *eventhubHandler) close(ctx context.Context) error { + if h.consumerClient != nil { + err := h.consumerClient.Close(ctx) + if err != nil { + return err + } + h.consumerClient = nil + } + return nil +} + +func (h *eventhubHandler) setDataConsumer(dataConsumer dataConsumer) { + h.dataConsumer = dataConsumer +} + +func createCheckpointStore(storageConnectionString, containerName string) (azeventhubs.CheckpointStore, error) { + azBlobContainerClient, err := container.NewClientFromConnectionString(storageConnectionString, containerName, nil) + if err != nil { + return nil, err + } + return checkpoints.NewBlobStore(azBlobContainerClient, nil) +} diff --git a/collector/components/azureeventhubreceiver/eventhubhandler_test.go b/collector/components/azureeventhubreceiver/eventhubhandler_test.go new file mode 100644 index 0000000..cb1c95f --- /dev/null +++ b/collector/components/azureeventhubreceiver/eventhubhandler_test.go @@ -0,0 +1,134 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "time" + + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receiverhelper" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver/internal/metadata" +) + +type mockProcessor struct{} + +func (m *mockProcessor) Run(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Millisecond): + return nil + } +} + +func (m *mockProcessor) NextPartitionClient(ctx context.Context) *azeventhubs.ProcessorPartitionClient { + return &azeventhubs.ProcessorPartitionClient{} +} + +type mockCheckpointStore struct{} + +func (m *mockCheckpointStore) SetCheckpoint(ctx context.Context, checkpoint azeventhubs.Checkpoint, options *azeventhubs.SetCheckpointOptions) error { + return nil +} + +func (m *mockCheckpointStore) GetCheckpoint(ctx context.Context, partitionID string) (azeventhubs.Checkpoint, error) { + return azeventhubs.Checkpoint{}, nil +} + +func (m *mockCheckpointStore) GetCheckpoints(ctx context.Context) ([]azeventhubs.Checkpoint, error) { + return []azeventhubs.Checkpoint{}, nil +} + +func newMockProcessor(*eventhubHandler) (*mockProcessor, error) { + return &mockProcessor{}, nil +} + +type mockconsumerClientWrapper struct { +} + +func (m mockconsumerClientWrapper) GetEventHubProperties(_ context.Context, _ *azeventhubs.GetEventHubPropertiesOptions) (azeventhubs.EventHubProperties, error) { + return azeventhubs.EventHubProperties{ + Name: "mynameis", + PartitionIDs: []string{"foo", "bar"}, + }, nil +} + +func (m mockconsumerClientWrapper) GetPartitionProperties(ctx context.Context, partitionID string, options *azeventhubs.GetPartitionPropertiesOptions) (azeventhubs.PartitionProperties, error) { + return azeventhubs.PartitionProperties{ + PartitionID: "abc123", + LastEnqueuedOffset: 1111, + }, nil +} + +func (m mockconsumerClientWrapper) NextConsumer(ctx context.Context, options azeventhubs.ConsumerClientOptions) (*azeventhubs.ConsumerClient, error) { + return &azeventhubs.ConsumerClient{}, nil +} + +func (m mockconsumerClientWrapper) NewConsumer(ctx context.Context, options *azeventhubs.ConsumerClientOptions) (*azeventhubs.ConsumerClient, error) { + return &azeventhubs.ConsumerClient{}, nil +} + +func (m mockconsumerClientWrapper) NewPartitionClient(partitionID string, options *azeventhubs.PartitionClientOptions) (*azeventhubs.PartitionClient, error) { + return &azeventhubs.PartitionClient{}, nil +} + +func (m mockconsumerClientWrapper) Close(_ context.Context) error { + return nil +} + +// Function to create mock implementation +func newMockConsumerClientWrapperImplementation(cfg *Config) (consumerClientWrapper, error) { + var ccw consumerClientWrapper = &mockconsumerClientWrapper{} + return ccw, nil +} + +type mockDataConsumer struct { + logsUnmarshaler eventLogsUnmarshaler + nextLogsConsumer consumer.Logs + obsrecv *receiverhelper.ObsReport +} + +func (m *mockDataConsumer) setNextLogsConsumer(nextLogsConsumer consumer.Logs) { + m.nextLogsConsumer = nextLogsConsumer +} + +func (m *mockDataConsumer) setNextMetricsConsumer(_ consumer.Metrics) {} + +func (m *mockDataConsumer) consume(ctx context.Context, event *azeventhubs.ReceivedEventData) error { + logsContext := m.obsrecv.StartLogsOp(ctx) + + logs, err := m.logsUnmarshaler.UnmarshalLogs(event) + if err != nil { + return err + } + + err = m.nextLogsConsumer.ConsumeLogs(logsContext, logs) + m.obsrecv.EndLogsOp(logsContext, metadata.Type.String(), 1, err) + + return err +} + +// newMockEventhubHandler creates a mock handler for Azure Event Hub for use in unit tests. +func newMockEventhubHandler(config *Config, settings receiver.CreateSettings) *eventhubHandler { + // Mock implementation: No real operations are performed. + consumerClient, err := newMockConsumerClientWrapperImplementation(config) + if err != nil { + panic(err) + } + + eh := &eventhubHandler{ + processor: &azeventhubs.Processor{}, + consumerClient: consumerClient, + dataConsumer: &mockDataConsumer{}, + config: config, + settings: settings, + useProcessor: false, + } + return eh +} diff --git a/collector/components/azureeventhubreceiver/factory.go b/collector/components/azureeventhubreceiver/factory.go new file mode 100644 index 0000000..a9d0bfe --- /dev/null +++ b/collector/components/azureeventhubreceiver/factory.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "errors" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/receiver" + + "github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver/internal/metadata" +) + +var ( + // The receiver scope name + receiverScopeName = "otelcol/" + metadata.Type.String() + "receiver" +) + +var ( + errUnexpectedConfigurationType = errors.New("failed to cast configuration to azure event hub config") +) + +type eventhubReceiverFactory struct { + receivers *sharedcomponent.SharedComponents +} + +// NewFactory creates a factory for the Azure Event Hub receiver. +func NewFactory() receiver.Factory { + f := &eventhubReceiverFactory{ + receivers: sharedcomponent.NewSharedComponents(), + } + + return receiver.NewFactory( + metadata.Type, + createDefaultConfig, + receiver.WithLogs(f.createLogsReceiver, metadata.LogsStability), + receiver.WithMetrics(f.createMetricsReceiver, metadata.MetricsStability)) +} + +func createDefaultConfig() component.Config { + return &Config{} +} + +func (f *eventhubReceiverFactory) createLogsReceiver( + _ context.Context, + settings receiver.CreateSettings, + cfg component.Config, + nextConsumer consumer.Logs, +) (receiver.Logs, error) { + + receiver, err := f.getReceiver(component.DataTypeLogs, cfg, settings) + if err != nil { + return nil, err + } + + receiver.(dataConsumer).setNextLogsConsumer(nextConsumer) + + return receiver, nil +} + +func (f *eventhubReceiverFactory) createMetricsReceiver( + _ context.Context, + settings receiver.CreateSettings, + cfg component.Config, + nextConsumer consumer.Metrics, +) (receiver.Metrics, error) { + + receiver, err := f.getReceiver(component.DataTypeMetrics, cfg, settings) + if err != nil { + return nil, err + } + + receiver.(dataConsumer).setNextMetricsConsumer(nextConsumer) + + return receiver, nil +} + +func (f *eventhubReceiverFactory) getReceiver( + receiverType component.Type, + cfg component.Config, + settings receiver.CreateSettings, +) (component.Component, error) { + + var err error + r := f.receivers.GetOrAdd(cfg, func() component.Component { + receiverConfig, ok := cfg.(*Config) + if !ok { + err = errUnexpectedConfigurationType + return nil + } + + var logsUnmarshaler eventLogsUnmarshaler + var metricsUnmarshaler eventMetricsUnmarshaler + switch receiverType { + case component.DataTypeLogs: + if logFormat(receiverConfig.Format) == rawLogFormat { + logsUnmarshaler = newRawLogsUnmarshaler(settings.Logger) + } else { + logsUnmarshaler = newAzureResourceLogsUnmarshaler(settings.BuildInfo, settings.Logger) + } + case component.DataTypeMetrics: + if logFormat(receiverConfig.Format) == rawLogFormat { + metricsUnmarshaler = nil + err = errors.New("raw format not supported for Metrics") + } else { + metricsUnmarshaler = newAzureResourceMetricsUnmarshaler(settings.BuildInfo, settings.Logger) + } + case component.DataTypeTraces: + err = errors.New("unsupported traces data") + } + + if err != nil { + return nil + } + + eventHandler := newEventhubHandler(receiverConfig, settings) + + var receiver component.Component + receiver, err = newReceiver(receiverType, logsUnmarshaler, metricsUnmarshaler, eventHandler, settings) + return receiver + }) + + if err != nil { + return nil, err + } + + return r.Unwrap(), err +} diff --git a/collector/components/azureeventhubreceiver/factory_test.go b/collector/components/azureeventhubreceiver/factory_test.go new file mode 100644 index 0000000..a809213 --- /dev/null +++ b/collector/components/azureeventhubreceiver/factory_test.go @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com.open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver/receivertest" + + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver/internal/metadata" +) + +func Test_NewFactory(t *testing.T) { + f := NewFactory() + assert.Equal(t, metadata.Type, f.Type()) +} + +func Test_NewLogsReceiver(t *testing.T) { + f := NewFactory() + config := createDefaultConfig().(*Config) + config.Connection = "Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234;EntityPath=hubName" + + receiver, err := f.CreateLogsReceiver(context.Background(), receivertest.NewNopCreateSettings(), config, consumertest.NewNop()) + assert.NoError(t, err) + assert.NotNil(t, receiver) +} + +func Test_NewMetricsReceiver(t *testing.T) { + f := NewFactory() + config := createDefaultConfig().(*Config) + config.Connection = "Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234;EntityPath=hubName" + + receiver, err := f.CreateMetricsReceiver(context.Background(), receivertest.NewNopCreateSettings(), config, consumertest.NewNop()) + assert.NoError(t, err) + assert.NotNil(t, receiver) +} diff --git a/collector/components/azureeventhubreceiver/generated_component_test.go b/collector/components/azureeventhubreceiver/generated_component_test.go new file mode 100644 index 0000000..b0f794e --- /dev/null +++ b/collector/components/azureeventhubreceiver/generated_component_test.go @@ -0,0 +1,83 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package azureeventhubreceiver + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + + "go.opentelemetry.io/collector/consumer/consumertest" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receivertest" + + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +// assertNoErrorHost implements a component.Host that asserts that there were no errors. +type assertNoErrorHost struct { + component.Host + *testing.T +} + +var _ component.Host = (*assertNoErrorHost)(nil) + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + tests := []struct { + name string + createFn func(ctx context.Context, set receiver.CreateSettings, cfg component.Config) (component.Component, error) + }{ + + { + name: "logs", + createFn: func(ctx context.Context, set receiver.CreateSettings, cfg component.Config) (component.Component, error) { + return factory.CreateLogsReceiver(ctx, set, cfg, consumertest.NewNop()) + }, + }, + + { + name: "metrics", + createFn: func(ctx context.Context, set receiver.CreateSettings, cfg component.Config) (component.Component, error) { + return factory.CreateMetricsReceiver(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, component.UnmarshalConfig(sub, cfg)) + + for _, test := range tests { + t.Run(test.name+"-shutdown", func(t *testing.T) { + c, err := test.createFn(context.Background(), receivertest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + err = c.Shutdown(context.Background()) + require.NoError(t, err) + }) + + t.Run(test.name+"-lifecycle", func(t *testing.T) { + + // TODO support lifecycle + t.SkipNow() + + firstRcvr, err := test.createFn(context.Background(), receivertest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + host := componenttest.NewNopHost() + require.NoError(t, err) + require.NoError(t, firstRcvr.Start(context.Background(), host)) + require.NoError(t, firstRcvr.Shutdown(context.Background())) + secondRcvr, err := test.createFn(context.Background(), receivertest.NewNopCreateSettings(), cfg) + require.NoError(t, err) + require.NoError(t, secondRcvr.Start(context.Background(), host)) + require.NoError(t, secondRcvr.Shutdown(context.Background())) + }) + } +} diff --git a/collector/components/azureeventhubreceiver/go.mod b/collector/components/azureeventhubreceiver/go.mod new file mode 100644 index 0000000..67629d4 --- /dev/null +++ b/collector/components/azureeventhubreceiver/go.mod @@ -0,0 +1,125 @@ +module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver + +go 1.21 + +require ( + github.com/Azure/azure-amqp-common-go/v4 v4.2.0 + github.com/Azure/azure-event-hubs-go/v3 v3.6.2 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 + github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 + github.com/json-iterator/go v1.1.12 + github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.94.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure v0.94.0 + github.com/relvacode/iso8601 v1.4.0 + github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/collector/component v0.94.1 + go.opentelemetry.io/collector/confmap v0.94.1 + go.opentelemetry.io/collector/consumer v0.94.1 + go.opentelemetry.io/collector/extension v0.94.1 + go.opentelemetry.io/collector/otelcol v0.94.1 + go.opentelemetry.io/collector/pdata v1.1.0 + go.opentelemetry.io/collector/receiver v0.94.1 + go.opentelemetry.io/collector/semconv v0.94.1 + go.opentelemetry.io/otel/metric v1.23.1 + go.opentelemetry.io/otel/trace v1.23.1 + go.uber.org/zap v1.26.0 +) + +require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/go-amqp v1.0.5 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.0.2 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.46.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + github.com/shirou/gopsutil/v3 v3.24.1 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/collector v0.94.1 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.94.1 // indirect + go.opentelemetry.io/collector/connector v0.94.1 // indirect + go.opentelemetry.io/collector/exporter v0.94.1 // indirect + go.opentelemetry.io/collector/featuregate v1.1.0 // indirect + go.opentelemetry.io/collector/processor v0.94.1 // indirect + go.opentelemetry.io/collector/service v0.94.1 // indirect + go.opentelemetry.io/contrib/config v0.3.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.22.0 // indirect + go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/bridge/opencensus v0.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.45.1 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.45.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.0 // indirect + go.opentelemetry.io/otel/sdk v1.23.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.23.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + gonum.org/v1/gonum v0.14.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/grpc v1.61.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage => ../../extension/storage + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza => ../../pkg/stanza + +retract ( + v0.76.2 + v0.76.1 + v0.65.0 +) + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal => ../../internal/coreinternal + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../pkg/pdatatest + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../pkg/pdatautil + +replace github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent => ../../internal/sharedcomponent + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/azure => ../../pkg/translator/azure + +replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../pkg/golden diff --git a/collector/components/azureeventhubreceiver/go.sum b/collector/components/azureeventhubreceiver/go.sum new file mode 100644 index 0000000..cf28ad5 --- /dev/null +++ b/collector/components/azureeventhubreceiver/go.sum @@ -0,0 +1,359 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/azure-amqp-common-go/v4 v4.2.0 h1:q/jLx1KJ8xeI8XGfkOWMN9XrXzAfVTkyvCxPvHCjd2I= +github.com/Azure/azure-amqp-common-go/v4 v4.2.0/go.mod h1:GD3m/WPPma+621UaU6KNjKEo5Hl09z86viKwQjTpV0Q= +github.com/Azure/azure-event-hubs-go/v3 v3.6.2 h1:7rNj1/iqS/i3mUKokA2n2eMYO72TB7lO7OmpbKoakKY= +github.com/Azure/azure-event-hubs-go/v3 v3.6.2/go.mod h1:n+ocYr9j2JCLYqUqz9eI+lx/TEAtL/g6rZzyTFSuIpc= +github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.1.0 h1:vEe09cdSBy7evqoVUvuitnsjyozsSzI4TbGgwu01+TI= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs v1.1.0/go.mod h1:PgOlzIlvwIagKI8N6hCsfFDpAijHCmlHqOwA5GsSh9w= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0 h1:BWeAAEzkCnL0ABVJqs+4mYudNch7oFGPtTlSmIWL8ms= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/eventhub/armeventhub v1.0.0/go.mod h1:Y3gnVwfaz8h6L1YHar+NfWORtBoVUSB5h4GlGkdeF7Q= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E= +github.com/Azure/go-amqp v1.0.5 h1:po5+ljlcNSU8xtapHTe8gIc8yHxCzC03E8afH2g1ftU= +github.com/Azure/go-amqp v1.0.5/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.0.2 h1:sEZzPW2rVWSahcYILNq/syJdEyRafZIG0l9aWwL86HA= +github.com/knadh/koanf/v2 v2.0.2/go.mod h1:HN9uZ+qFAejH1e4G41gnoffIanINWQuONLXiV7kir6k= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs= +github.com/relvacode/iso8601 v1.4.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= +github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/collector v0.94.1 h1:bGHW5NKmh34oMflMEyNCHpes6vtiQNXpgea4GiscAOs= +go.opentelemetry.io/collector v0.94.1/go.mod h1:5ACZXRo6O23gBkRrHSxYs1sLaP4pZ8w+flZNE7pvoNg= +go.opentelemetry.io/collector/component v0.94.1 h1:j4peKsWb+QVBKPs2RJeIj5EoQW7yp2ZVGrd8Bu9HU9M= +go.opentelemetry.io/collector/component v0.94.1/go.mod h1:vg+kAH81C3YS0SPzUXkSFWLPC1WH7zx70dAtUWWIHcE= +go.opentelemetry.io/collector/config/confignet v0.94.1 h1:kaV1iwZKjv7ZJZ+PtLTQYfVNLVD3EAyxfYBNZf5GXmo= +go.opentelemetry.io/collector/config/confignet v0.94.1/go.mod h1:rraribsOoZsYZm51+3TXHavmXKJOC5a5/X20vfmNbdw= +go.opentelemetry.io/collector/config/configretry v0.94.1 h1:0rJXulYg7DouKfrfyhNgT2SyDtTx2+PSQATuHQK6kLU= +go.opentelemetry.io/collector/config/configretry v0.94.1/go.mod h1:gt1HRYyMxcMca9lbDLPbivQzsUCjVjkPAn/3S6fiD14= +go.opentelemetry.io/collector/config/configtelemetry v0.94.1 h1:ztYpBEBlvhcoxMiDKNmQ2SS+A41JZ4M19GfcxjCo8Zs= +go.opentelemetry.io/collector/config/configtelemetry v0.94.1/go.mod h1:2XLhyR/GVpWeZ2K044vCmrvH/d4Ewt0aD/y46avZyMU= +go.opentelemetry.io/collector/confmap v0.94.1 h1:O69bkeyR1YPAFz+jMd45aDZc1DtYnwb3Skgr2yALPqQ= +go.opentelemetry.io/collector/confmap v0.94.1/go.mod h1:pCT5UtcHaHVJ5BIILv1Z2VQyjZzmT9uTdBmC9+Z0AgA= +go.opentelemetry.io/collector/connector v0.94.1 h1:ZYGNubGypsxK5XN6rkCdMaS0PDnMW7yzOj4CHUNIAII= +go.opentelemetry.io/collector/connector v0.94.1/go.mod h1:iv4lgIGa15FDwz7UN/pBMtrihTJEsZUxbWfPbM7e2QM= +go.opentelemetry.io/collector/consumer v0.94.1 h1:l/9h5L71xr/d93snQ9fdxgz64C4UuB8mEDxpp456X8o= +go.opentelemetry.io/collector/consumer v0.94.1/go.mod h1:BIPWmw8wES6jlPTPC+acJxLvUzIdOm6uh/p/X85ALsY= +go.opentelemetry.io/collector/exporter v0.94.1 h1:tu9l/lZdgf0zLvWTZeRPV6wKLkQ8ymMFx7GBGjweQtw= +go.opentelemetry.io/collector/exporter v0.94.1/go.mod h1:XO3dwIIjrHTu0Z9Fs0pQASFTNZcT7uQiYd78f49gNsk= +go.opentelemetry.io/collector/extension v0.94.1 h1:f0yyW2lmLg+PI1FjNWJaGcKVQV6TRgLqqbMA/4S5dA4= +go.opentelemetry.io/collector/extension v0.94.1/go.mod h1:fxQXkLkFcea3uJ3hlImBs5kQ/pWjeDIC2OylnDYIA4g= +go.opentelemetry.io/collector/extension/zpagesextension v0.94.1 h1:s+cb8nh2vS9F2/UkWO+jhsvJE2SV5CF3BZ1KJ6q8rSk= +go.opentelemetry.io/collector/extension/zpagesextension v0.94.1/go.mod h1:NVoJq0mxe7AIw+EjDPyrwMZ21DGxoUqXZ1W5dTXrV38= +go.opentelemetry.io/collector/featuregate v1.1.0 h1:W+/FKvRxHMFC6MuTTEgrHINCf1vFBvLH7stSOEar6zU= +go.opentelemetry.io/collector/featuregate v1.1.0/go.mod h1:QQXjP4etmJQhkQ20j4P/rapWuItYxoFozg/iIwuKnYg= +go.opentelemetry.io/collector/otelcol v0.94.1 h1:iXCIjOxjAHiMtguDz8JK7lGMkvKRrretnJ+hbuimYd8= +go.opentelemetry.io/collector/otelcol v0.94.1/go.mod h1:/cYiy1apIC+04ij+miTGUjm2Qc23oq/6KUcBlCeeBEw= +go.opentelemetry.io/collector/pdata v1.1.0 h1:cE6Al1rQieUjMHro6p6cKwcu3sjHXGG59BZ3kRVUvsM= +go.opentelemetry.io/collector/pdata v1.1.0/go.mod h1:IDkDj+B4Fp4wWOclBELN97zcb98HugJ8Q2gA4ZFsN8Q= +go.opentelemetry.io/collector/processor v0.94.1 h1:cNlGox8fN85KhtUq6yuqgPM9KDCQ4O5aDQ864joc4JQ= +go.opentelemetry.io/collector/processor v0.94.1/go.mod h1:pMwIDr5UTSjBJ8ATLR8e84TWEnqO/9HTmDjj1NJ3K84= +go.opentelemetry.io/collector/receiver v0.94.1 h1:p9kIPmDeLSAlFZZuHdFELGGiP0JduFEfsT8Uz6Ut+8g= +go.opentelemetry.io/collector/receiver v0.94.1/go.mod h1:AYdIg3Bl4kwiqQy/k3tuYQnS918gb5i3HcInn6owudE= +go.opentelemetry.io/collector/semconv v0.94.1 h1:+FoBlzwFgwalgbdBhJHtHPvR7W0+aJDUAdQdsmfT/Ts= +go.opentelemetry.io/collector/semconv v0.94.1/go.mod h1:gZ0uzkXsN+J5NpiRcdp9xOhNGQDDui8Y62p15sKrlzo= +go.opentelemetry.io/collector/service v0.94.1 h1:O2n+j22ycTi5cikDehYlYKw2VslCbcwjX8Pgf5NeVoc= +go.opentelemetry.io/collector/service v0.94.1/go.mod h1:Lq55nShtnd7y2iZAXW1DIO+gmGYgSdbju+ESL+NnWZg= +go.opentelemetry.io/contrib/config v0.3.0 h1:nJxYSB7/8fckSya4EAFyFGxIytMvNlQInXSmhz/OKKg= +go.opentelemetry.io/contrib/config v0.3.0/go.mod h1:tQW0mY8be9/LGikwZNYno97PleUhF/lMal9xJ1TC2vo= +go.opentelemetry.io/contrib/propagators/b3 v1.22.0 h1:Okbgv0pWHMQq+mF7H2o1mucJ5PvxKFq2c8cyqoXfeaQ= +go.opentelemetry.io/contrib/propagators/b3 v1.22.0/go.mod h1:N3z0ycFRhsVZ+tG/uavMxHvOvFE95QM6gwW1zSqT9dQ= +go.opentelemetry.io/contrib/zpages v0.47.0 h1:ekpdNa2wqOvAfwZIGDIIV02zmR+z08aWPt21KrPJnaU= +go.opentelemetry.io/contrib/zpages v0.47.0/go.mod h1:rBeFA/UxnMjRlEGpmClIqzf1mCIKtl7ahjww3wsSdGs= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/bridge/opencensus v0.45.0 h1:kEOlv9Exuv3J8GCf1nLMHfrTPGnZOuIkN8YlRM14TtQ= +go.opentelemetry.io/otel/bridge/opencensus v0.45.0/go.mod h1:tkVMJeFOr43+zzwbxtIWsNcCCDT7rI5/c9rhMfMIENg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0 h1:+RbSCde0ERway5FwKvXR3aRJIFeDu9rtwC6E7BC6uoM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.45.0/go.mod h1:zcI8u2EJxbLPyoZ3SkVAAcQPgYb1TDRzW93xLFnsggU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.0 h1:D/cXD+03/UOphyyT87NX6h+DlU+BnplN6/P6KJwsgGc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.0/go.mod h1:L669qRGbPBwLcftXLFnTVFO6ES/GyMAvITLdvRjEAIM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.0 h1:VZrBiTXzP3FErizsdF1JQj0qf0yA8Ktt6LAcjUhZqbc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.0/go.mod h1:xkkwo777b9MEfsyD1yUZa4g+7MCqqWAP3r2tTSZePRc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.0 h1:cZXHUQvCx7YMdjGu0AlmoArUz7NZ7K6WWsT4cjSkzc0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.23.0/go.mod h1:OHlshrAeSV9uiVQs1n+c0FVCyo8L0NrYzVf5GuLllRo= +go.opentelemetry.io/otel/exporters/prometheus v0.45.1 h1:R/bW3afad6q6VGU+MFYpnEdo0stEARMCdhWu6+JI6aI= +go.opentelemetry.io/otel/exporters/prometheus v0.45.1/go.mod h1:wnHAfKRav5Dfp4iZhyWZ7SzQfT+rDZpEpYG7To+qJ1k= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.45.0 h1:NjN6zc7Mwy9torqa3mo+pMJ3mHoPI0uzVSYcqB2t72A= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.45.0/go.mod h1:U+T5v2bk4fCC8XdSEWZja3Pm/ZhvV/zE7JwX/ELJKts= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.0 h1:f4N/tfYchDXfM78Ng5KKO7OjrShVzww1g4oYxZ7tyMA= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.0/go.mod h1:v1gipIZLj3qtxR1L1F7jF/WaPFA5ptuHk52+eq9SSRg= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.0 h1:0KM9Zl2esnl+WSukEmlaAEjVY5HDZANOHferLq36BPc= +go.opentelemetry.io/otel/sdk v1.23.0/go.mod h1:wUscup7byToqyKJSilEtMf34FgdCAsFpFOjXnAwFfO0= +go.opentelemetry.io/otel/sdk/metric v1.23.0 h1:u81lMvmK6GMgN4Fty7K7S6cSKOZhMKJMK2TB+KaTs0I= +go.opentelemetry.io/otel/sdk/metric v1.23.0/go.mod h1:2LUOToN/FdX6wtfpHybOnCZjoZ6ViYajJYMiJ1LKDtQ= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= +google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= +nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= diff --git a/collector/components/azureeventhubreceiver/internal/metadata/generated_status.go b/collector/components/azureeventhubreceiver/internal/metadata/generated_status.go new file mode 100644 index 0000000..23cb579 --- /dev/null +++ b/collector/components/azureeventhubreceiver/internal/metadata/generated_status.go @@ -0,0 +1,26 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/trace" +) + +var ( + Type = component.MustNewType("azureeventhub") +) + +const ( + MetricsStability = component.StabilityLevelAlpha + LogsStability = component.StabilityLevelAlpha +) + +func Meter(settings component.TelemetrySettings) metric.Meter { + return settings.MeterProvider.Meter("otelcol/azureeventhubreceiver") +} + +func Tracer(settings component.TelemetrySettings) trace.Tracer { + return settings.TracerProvider.Tracer("otelcol/azureeventhubreceiver") +} diff --git a/collector/components/azureeventhubreceiver/metadata.yaml b/collector/components/azureeventhubreceiver/metadata.yaml new file mode 100644 index 0000000..b090eda --- /dev/null +++ b/collector/components/azureeventhubreceiver/metadata.yaml @@ -0,0 +1,13 @@ +type: azureeventhub + +status: + class: receiver + stability: + alpha: [metrics, logs] + distributions: [contrib, splunk, observiq, sumo] + codeowners: + active: [atoulme, djaglowski] + +tests: + config: + skip_lifecycle: true diff --git a/collector/components/azureeventhubreceiver/persister.go b/collector/components/azureeventhubreceiver/persister.go new file mode 100644 index 0000000..7d9be73 --- /dev/null +++ b/collector/components/azureeventhubreceiver/persister.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "fmt" + + "github.com/Azure/azure-event-hubs-go/v3/persist" + jsoniter "github.com/json-iterator/go" + "go.opentelemetry.io/collector/extension/experimental/storage" +) + +const ( + storageKeyFormat = "%s/%s/%s/%s" +) + +type storageCheckpointPersister struct { + storageClient storage.Client +} + +func (s *storageCheckpointPersister) Write(namespace, name, consumerGroup, partitionID string, checkpoint persist.Checkpoint) error { + b, err := jsoniter.Marshal(checkpoint) + if err != nil { + return err + } + return s.storageClient.Set(context.Background(), fmt.Sprintf(storageKeyFormat, namespace, name, consumerGroup, partitionID), b) +} + +func (s *storageCheckpointPersister) Read(namespace, name, consumerGroup, partitionID string) (persist.Checkpoint, error) { + var checkpoint persist.Checkpoint + bytes, err := s.storageClient.Get(context.Background(), fmt.Sprintf(storageKeyFormat, namespace, name, consumerGroup, partitionID)) + if err != nil { + return persist.NewCheckpointFromStartOfStream(), err + } + if len(bytes) == 0 { + return persist.NewCheckpointFromStartOfStream(), err + } + err = jsoniter.Unmarshal(bytes, &checkpoint) + return checkpoint, err +} diff --git a/collector/components/azureeventhubreceiver/persister_test.go b/collector/components/azureeventhubreceiver/persister_test.go new file mode 100644 index 0000000..123cae0 --- /dev/null +++ b/collector/components/azureeventhubreceiver/persister_test.go @@ -0,0 +1,102 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/Azure/azure-event-hubs-go/v3/persist" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/extension/experimental/storage" +) + +func TestStorageOffsetPersisterUnknownCheckpoint(t *testing.T) { + client := newMockClient() + s := storageCheckpointPersister{storageClient: client} + // check we have no match + checkpoint, err := s.Read("foo", "bar", "foobar", "foobarfoo") + assert.NoError(t, err) + assert.NotNil(t, checkpoint) + assert.Equal(t, "-1", checkpoint.Offset) +} + +func TestStorageOffsetPersisterWithKnownCheckpoint(t *testing.T) { + client := newMockClient() + s := storageCheckpointPersister{storageClient: client} + checkpoint := persist.Checkpoint{ + Offset: "foo", + SequenceNumber: 2, + EnqueueTime: time.Now(), + } + err := s.Write("foo", "bar", "foobar", "foobarfoo", checkpoint) + assert.NoError(t, err) + read, err := s.Read("foo", "bar", "foobar", "foobarfoo") + assert.NoError(t, err) + assert.Equal(t, checkpoint.Offset, read.Offset) + assert.Equal(t, checkpoint.SequenceNumber, read.SequenceNumber) + assert.True(t, checkpoint.EnqueueTime.Equal(read.EnqueueTime)) +} + +// copied from pkg/stanza/adapter/mocks_test.go +type mockClient struct { + cache map[string][]byte + cacheMux sync.Mutex +} + +func newMockClient() *mockClient { + return &mockClient{ + cache: make(map[string][]byte), + } +} + +func (p *mockClient) Get(_ context.Context, key string) ([]byte, error) { + p.cacheMux.Lock() + defer p.cacheMux.Unlock() + return p.cache[key], nil +} + +func (p *mockClient) Set(_ context.Context, key string, value []byte) error { + p.cacheMux.Lock() + defer p.cacheMux.Unlock() + p.cache[key] = value + return nil +} + +func (p *mockClient) Delete(_ context.Context, key string) error { + p.cacheMux.Lock() + defer p.cacheMux.Unlock() + delete(p.cache, key) + return nil +} + +func (p *mockClient) Batch(_ context.Context, ops ...storage.Operation) error { + p.cacheMux.Lock() + defer p.cacheMux.Unlock() + + for _, op := range ops { + switch op.Type { + case storage.Get: + op.Value = p.cache[op.Key] + case storage.Set: + p.cache[op.Key] = op.Value + case storage.Delete: + delete(p.cache, op.Key) + default: + return errors.New("wrong operation type") + } + } + + return nil +} + +func (p *mockClient) Close(_ context.Context) error { + p.cacheMux.Lock() + defer p.cacheMux.Unlock() + p.cache = nil + return nil +} diff --git a/collector/components/azureeventhubreceiver/rawlogs_unmarshaler.go b/collector/components/azureeventhubreceiver/rawlogs_unmarshaler.go new file mode 100644 index 0000000..f5019df --- /dev/null +++ b/collector/components/azureeventhubreceiver/rawlogs_unmarshaler.go @@ -0,0 +1,77 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" + "go.uber.org/zap" +) + +type rawLogsUnmarshaler struct { + logger *zap.Logger +} + +func newRawLogsUnmarshaler(logger *zap.Logger) eventLogsUnmarshaler { + return rawLogsUnmarshaler{ + logger: logger, + } +} + +func (r rawLogsUnmarshaler) UnmarshalLogs(event *azeventhubs.ReceivedEventData) (plog.Logs, error) { + l := plog.NewLogs() + lr := l.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty() + slice := lr.Body().SetEmptyBytes() + slice.Append(event.Body...) //event.EventData.([]byte)...) + if event.EnqueuedTime != nil { + lr.SetTimestamp(pcommon.NewTimestampFromTime(*event.EnqueuedTime)) + } + + if err := lr.Attributes().FromRaw(event.Properties); err != nil { + return l, err + } + + return l, nil +} + +// // Copyright The OpenTelemetry Authors +// // SPDX-License-Identifier: Apache-2.0 + +// package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +// import ( +// "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" +// "go.opentelemetry.io/collector/pdata/pcommon" +// "go.opentelemetry.io/collector/pdata/plog" +// "go.uber.org/zap" +// ) + +// type rawLogsUnmarshaler struct { +// logger *zap.Logger +// } + +// func newRawLogsUnmarshaler(logger *zap.Logger) eventLogsUnmarshaler { + +// return rawLogsUnmarshaler{ +// logger: logger, +// } +// } + +// func (r rawLogsUnmarshaler) UnmarshalLogs(event *[]azeventhubs.ReceivedEventData) (plog.Logs, error) { + +// l := plog.NewLogs() +// lr := l.ResourceLogs().AppendEmpty().ScopeLogs().AppendEmpty().LogRecords().AppendEmpty() +// slice := lr.Body().SetEmptyBytes() +// slice.Append(event.EventData...) +// if event.SystemProperties.EnqueuedTime != nil { +// lr.SetTimestamp(pcommon.NewTimestampFromTime(*event.SystemProperties.EnqueuedTime)) +// } + +// if err := lr.Attributes().FromRaw(event.Properties); err != nil { +// return l, err +// } + +// return l, nil +// } diff --git a/collector/components/azureeventhubreceiver/receiver.go b/collector/components/azureeventhubreceiver/receiver.go new file mode 100644 index 0000000..1aeb9a1 --- /dev/null +++ b/collector/components/azureeventhubreceiver/receiver.go @@ -0,0 +1,161 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package azureeventhubreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver" + +import ( + "context" + "errors" + "fmt" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/collector/receiver" + "go.opentelemetry.io/collector/receiver/receiverhelper" + "go.uber.org/zap" + + "github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" + "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/azureeventhubreceiver/internal/metadata" +) + +type dataConsumer interface { + consume(ctx context.Context, event *azeventhubs.ReceivedEventData) error + setNextLogsConsumer(nextLogsConsumer consumer.Logs) + setNextMetricsConsumer(nextLogsConsumer consumer.Metrics) +} + +type eventLogsUnmarshaler interface { + UnmarshalLogs(event *azeventhubs.ReceivedEventData) (plog.Logs, error) +} + +type eventMetricsUnmarshaler interface { + UnmarshalMetrics(event *azeventhubs.ReceivedEventData) (pmetric.Metrics, error) +} + +type eventhubReceiver struct { + eventHandler eventHandler + dataType component.Type + logger *zap.Logger + logsUnmarshaler eventLogsUnmarshaler + metricsUnmarshaler eventMetricsUnmarshaler + nextLogsConsumer consumer.Logs + nextMetricsConsumer consumer.Metrics + obsrecv *receiverhelper.ObsReport +} + +func (receiver *eventhubReceiver) Start(ctx context.Context, host component.Host) error { + + err := receiver.eventHandler.run(ctx, host) + return err +} + +func (receiver *eventhubReceiver) Shutdown(ctx context.Context) error { + + return receiver.eventHandler.close(ctx) +} + +func (receiver *eventhubReceiver) setNextLogsConsumer(nextLogsConsumer consumer.Logs) { + + receiver.nextLogsConsumer = nextLogsConsumer +} + +func (receiver *eventhubReceiver) setNextMetricsConsumer(nextMetricsConsumer consumer.Metrics) { + + receiver.nextMetricsConsumer = nextMetricsConsumer +} + +func (receiver *eventhubReceiver) consume(ctx context.Context, event *azeventhubs.ReceivedEventData) error { + + switch receiver.dataType { + case component.DataTypeLogs: + return receiver.consumeLogs(ctx, event) + case component.DataTypeMetrics: + return receiver.consumeMetrics(ctx, event) + case component.DataTypeTraces: + fallthrough + default: + return fmt.Errorf("invalid data type: %v", receiver.dataType) + } +} + +func (receiver *eventhubReceiver) consumeLogs(ctx context.Context, event *azeventhubs.ReceivedEventData) error { + + if receiver.nextLogsConsumer == nil { + return nil + } + + if receiver.logsUnmarshaler == nil { + return errors.New("unable to unmarshal logs with configured format") + } + + logsContext := receiver.obsrecv.StartLogsOp(ctx) + + logs, err := receiver.logsUnmarshaler.UnmarshalLogs(event) + if err != nil { + return fmt.Errorf("failed to unmarshal logs: %w", err) + } + + receiver.logger.Debug("Log Records", zap.Any("logs", logs)) + err = receiver.nextLogsConsumer.ConsumeLogs(logsContext, logs) + receiver.obsrecv.EndLogsOp(logsContext, metadata.Type.String(), 1, err) + + return err +} + +func (receiver *eventhubReceiver) consumeMetrics(ctx context.Context, event *azeventhubs.ReceivedEventData) error { + + if receiver.nextMetricsConsumer == nil { + return nil + } + + if receiver.metricsUnmarshaler == nil { + return errors.New("unable to unmarshal metrics with configured format") + } + + metricsContext := receiver.obsrecv.StartMetricsOp(ctx) + + metrics, err := receiver.metricsUnmarshaler.UnmarshalMetrics(event) + if err != nil { + return fmt.Errorf("failed to unmarshal metrics: %w", err) + } + + receiver.logger.Debug("Metric Records", zap.Any("metrics", metrics)) + err = receiver.nextMetricsConsumer.ConsumeMetrics(metricsContext, metrics) + + receiver.obsrecv.EndMetricsOp(metricsContext, metadata.Type.String(), 1, err) + + return err +} + +func newReceiver( + receiverType component.Type, + logsUnmarshaler eventLogsUnmarshaler, + metricsUnmarshaler eventMetricsUnmarshaler, + eventHandler eventHandler, + settings receiver.CreateSettings, +) (component.Component, error) { + + obsrecv, err := receiverhelper.NewObsReport(receiverhelper.ObsReportSettings{ + ReceiverID: settings.ID, + Transport: "event", + ReceiverCreateSettings: settings, + }) + if err != nil { + return nil, err + } + + eventhubReceiver := &eventhubReceiver{ + dataType: receiverType, + eventHandler: eventHandler, + logger: settings.Logger, + logsUnmarshaler: logsUnmarshaler, + metricsUnmarshaler: metricsUnmarshaler, + obsrecv: obsrecv, + } + + eventHandler.setDataConsumer(eventhubReceiver) + + return eventhubReceiver, nil +} diff --git a/collector/components/azureeventhubreceiver/testdata/config.yaml b/collector/components/azureeventhubreceiver/testdata/config.yaml new file mode 100644 index 0000000..4afddc4 --- /dev/null +++ b/collector/components/azureeventhubreceiver/testdata/config.yaml @@ -0,0 +1,22 @@ +receivers: + azureeventhub: + connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName + + azureeventhub/all: + connection: Endpoint=sb://namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=superSecret1234=;EntityPath=hubName + partition: foo + offset: "1234-5566" + format: "raw" + +processors: + nop: + +exporters: + nop: + +service: + pipelines: + logs: + receivers: [azureeventhub, azureeventhub/all] + processors: [nop] + exporters: [nop] diff --git a/collector/components/azureeventhubreceiver/testdata/log-maximum.json b/collector/components/azureeventhubreceiver/testdata/log-maximum.json new file mode 100644 index 0000000..caef4ec --- /dev/null +++ b/collector/components/azureeventhubreceiver/testdata/log-maximum.json @@ -0,0 +1,31 @@ +{ + "records": [ + { + "time": "2022-11-11T04:48:27.6767145Z", + "resourceId": "/RESOURCE_ID", + "tenantId": "/TENANT_ID", + "operationName": "SecretGet", + "operationVersion": "7.0", + "category": "AuditEvent", + "resultType": "Success", + "resultSignature": "Signature", + "resultDescription": "Description", + "durationMs": "1234", + "callerIpAddress": "127.0.0.1", + "correlationId": "607964b6-41a5-4e24-a5db-db7aab3b9b34", + "Level": "Warning", + "location": "ukso", + "identity": { + "claim": { + "oid": "607964b6-41a5-4e24-a5db-db7aab3b9b34" + } + }, + "properties": { + "string": "string", + "int": 429, + "float": 3.14, + "bool": false + } + } + ] +} diff --git a/collector/components/azureeventhubreceiver/testdata/log-minimum-2.json b/collector/components/azureeventhubreceiver/testdata/log-minimum-2.json new file mode 100644 index 0000000..6eac63f --- /dev/null +++ b/collector/components/azureeventhubreceiver/testdata/log-minimum-2.json @@ -0,0 +1,16 @@ +{ + "records": [ + { + "time": "2022-11-11T04:48:27.6767145Z", + "resourceId": "/RESOURCE_ID", + "operationName": "SecretGet", + "category": "AuditEvent" + }, + { + "time": "2022-11-11T04:48:27.6767145Z", + "resourceId": "/RESOURCE_ID", + "operationName": "SecretGet", + "category": "AuditEvent" + } + ] +} diff --git a/collector/components/azureeventhubreceiver/testdata/log-minimum.json b/collector/components/azureeventhubreceiver/testdata/log-minimum.json new file mode 100644 index 0000000..16d4f2e --- /dev/null +++ b/collector/components/azureeventhubreceiver/testdata/log-minimum.json @@ -0,0 +1,10 @@ +{ + "records": [ + { + "time": "2022-11-11T04:48:27.6767145Z", + "resourceId": "/RESOURCE_ID", + "operationName": "SecretGet", + "category": "AuditEvent" + } + ] +}