diff --git a/internal/huawei/Makefile b/internal/huawei/Makefile
new file mode 100644
index 000000000000..bdd863a203be
--- /dev/null
+++ b/internal/huawei/Makefile
@@ -0,0 +1 @@
+include ../../../Makefile.Common
diff --git a/internal/huawei/backoff.go b/internal/huawei/backoff.go
new file mode 100644
index 000000000000..efdeef9058b3
--- /dev/null
+++ b/internal/huawei/backoff.go
@@ -0,0 +1,88 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huawei // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/huawei"
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/cenkalti/backoff/v4"
+ "go.uber.org/zap"
+)
+
+// Generic function to make an API call with exponential backoff and context cancellation handling.
+func MakeAPICallWithRetry[T any](
+ ctx context.Context,
+ shutdownChan chan struct{},
+ logger *zap.Logger,
+ apiCall func() (*T, error),
+ isThrottlingError func(error) bool,
+ backOffConfig *backoff.ExponentialBackOff,
+) (*T, error) {
+ // Immediately check for context cancellation or server shutdown.
+ select {
+ case <-ctx.Done():
+ return nil, fmt.Errorf("request was cancelled or timed out")
+ case <-shutdownChan:
+ return nil, fmt.Errorf("request is cancelled due to server shutdown")
+ case <-time.After(50 * time.Millisecond):
+ }
+
+ // Make the initial API call.
+ resp, err := apiCall()
+ if err == nil {
+ return resp, nil
+ }
+
+ // If the error is not due to request throttling, return the error.
+ if !isThrottlingError(err) {
+ return nil, err
+ }
+
+ // Initialize the backoff mechanism for retrying the API call.
+ expBackoff := &backoff.ExponentialBackOff{
+ InitialInterval: backOffConfig.InitialInterval,
+ RandomizationFactor: backOffConfig.RandomizationFactor,
+ Multiplier: backOffConfig.Multiplier,
+ MaxInterval: backOffConfig.MaxInterval,
+ MaxElapsedTime: backOffConfig.MaxElapsedTime,
+ Stop: backoff.Stop,
+ Clock: backoff.SystemClock,
+ }
+ expBackoff.Reset()
+ attempts := 0
+
+ // Retry loop for handling throttling errors.
+ for {
+ attempts++
+ delay := expBackoff.NextBackOff()
+ if delay == backoff.Stop {
+ return resp, err
+ }
+ logger.Warn("server busy, retrying request",
+ zap.Int("attempts", attempts),
+ zap.Duration("delay", delay))
+
+ // Handle context cancellation or shutdown before retrying.
+ select {
+ case <-ctx.Done():
+ return nil, fmt.Errorf("request was cancelled or timed out")
+ case <-shutdownChan:
+ return nil, fmt.Errorf("request is cancelled due to server shutdown")
+ case <-time.After(delay):
+ }
+
+ // Retry the API call.
+ resp, err = apiCall()
+ if err == nil {
+ return resp, nil
+ }
+ if !isThrottlingError(err) {
+ break
+ }
+ }
+
+ return nil, err
+}
diff --git a/internal/huawei/backoff_test.go b/internal/huawei/backoff_test.go
new file mode 100644
index 000000000000..f8448223e046
--- /dev/null
+++ b/internal/huawei/backoff_test.go
@@ -0,0 +1,129 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huawei
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/cenkalti/backoff/v4"
+ "github.com/stretchr/testify/assert"
+ "go.uber.org/zap/zaptest"
+)
+
+func TestMakeAPICallWithRetrySuccess(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ apiCall := func() (*string, error) {
+ result := "success"
+ return &result, nil
+ }
+ isThrottlingError := func(_ error) bool {
+ return false
+ }
+
+ resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff())
+
+ assert.NoError(t, err)
+ assert.Equal(t, "success", *resp)
+}
+
+func TestMakeAPICallWithRetryImmediateFailure(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ apiCall := func() (*string, error) {
+ return nil, errors.New("some error")
+ }
+ isThrottlingError := func(_ error) bool {
+ return false
+ }
+
+ resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff())
+
+ assert.Error(t, err)
+ assert.Nil(t, resp)
+ assert.Equal(t, "some error", err.Error())
+}
+
+func TestMakeAPICallWithRetryThrottlingWithSuccess(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ callCount := 0
+ apiCall := func() (*string, error) {
+ callCount++
+ if callCount == 3 {
+ result := "success"
+ return &result, nil
+ }
+ return nil, errors.New("throttling error")
+ }
+ isThrottlingError := func(err error) bool {
+ return err.Error() == "throttling error"
+ }
+
+ backOffConfig := backoff.NewExponentialBackOff()
+ backOffConfig.InitialInterval = 10 * time.Millisecond
+
+ resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backOffConfig)
+
+ assert.NoError(t, err)
+ assert.Equal(t, "success", *resp)
+ assert.Equal(t, 3, callCount)
+}
+
+func TestMakeAPICallWithRetryThrottlingMaxRetries(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ apiCall := func() (*string, error) {
+ return nil, errors.New("throttling error")
+ }
+ isThrottlingError := func(err error) bool {
+ return err.Error() == "throttling error"
+ }
+
+ backOffConfig := backoff.NewExponentialBackOff()
+ backOffConfig.MaxElapsedTime = 50 * time.Millisecond
+
+ resp, err := MakeAPICallWithRetry(context.TODO(), make(chan struct{}), logger, apiCall, isThrottlingError, backOffConfig)
+
+ assert.Error(t, err)
+ assert.Nil(t, resp)
+ assert.Equal(t, "throttling error", err.Error())
+}
+
+func TestMakeAPICallWithRetryContextCancellation(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ ctx, cancel := context.WithCancel(context.TODO())
+ time.AfterFunc(time.Second, cancel)
+
+ apiCall := func() (*string, error) {
+ return nil, errors.New("throttling error")
+ }
+ isThrottlingError := func(err error) bool {
+ return err.Error() == "throttling error"
+ }
+
+ resp, err := MakeAPICallWithRetry(ctx, make(chan struct{}), logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff())
+
+ assert.Error(t, err)
+ assert.Nil(t, resp)
+ assert.Equal(t, "request was cancelled or timed out", err.Error())
+}
+
+func TestMakeAPICallWithRetryServerShutdown(t *testing.T) {
+ logger := zaptest.NewLogger(t)
+ shutdownChan := make(chan struct{})
+ time.AfterFunc(time.Second, func() { close(shutdownChan) })
+
+ apiCall := func() (*string, error) {
+ return nil, errors.New("throttling error")
+ }
+ isThrottlingError := func(err error) bool {
+ return err.Error() == "throttling error"
+ }
+
+ resp, err := MakeAPICallWithRetry(context.TODO(), shutdownChan, logger, apiCall, isThrottlingError, backoff.NewExponentialBackOff())
+
+ assert.Error(t, err)
+ assert.Nil(t, resp)
+ assert.Equal(t, "request is cancelled due to server shutdown", err.Error())
+}
diff --git a/internal/huawei/go.mod b/internal/huawei/go.mod
new file mode 100644
index 000000000000..071f0c52f1d4
--- /dev/null
+++ b/internal/huawei/go.mod
@@ -0,0 +1,16 @@
+module github.com/open-telemetry/opentelemetry-collector-contrib/internal/huawei
+
+go 1.22.3
+
+require (
+ github.com/cenkalti/backoff/v4 v4.3.0
+ github.com/stretchr/testify v1.9.0
+ go.uber.org/zap v1.27.0
+)
+
+require (
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/internal/huawei/go.sum b/internal/huawei/go.sum
new file mode 100644
index 000000000000..2002d92a7041
--- /dev/null
+++ b/internal/huawei/go.sum
@@ -0,0 +1,18 @@
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/receiver/huaweicloudlogsreceiver/Makefile b/receiver/huaweicloudlogsreceiver/Makefile
new file mode 100644
index 000000000000..c1496226e590
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/Makefile
@@ -0,0 +1 @@
+include ../../Makefile.Common
\ No newline at end of file
diff --git a/receiver/huaweicloudlogsreceiver/README.md b/receiver/huaweicloudlogsreceiver/README.md
new file mode 100644
index 000000000000..9594918a7459
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/README.md
@@ -0,0 +1,205 @@
+# Huawei Cloud CES Receiver
+
+
+| Status | |
+| ------------- |-----------|
+| Stability | [development]: logs |
+| Distributions | [] |
+| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Areceiver%2Fhuaweicloudlogs%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Areceiver%2Fhuaweicloudlogs) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Areceiver%2Fhuaweicloudlogs%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Areceiver%2Fhuaweicloudlogs) |
+| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@heitorganzeli](https://www.github.com/heitorganzeli), [@narcis96](https://www.github.com/narcis96) |
+
+[development]: https://github.com/open-telemetry/opentelemetry-collector#development
+
+
+This receiver contains the implementation of the Huawei Cloud [Log Tank Service](https://www.huaweicloud.com/intl/en-us/product/lts.html) (LTS) receiver for the OpenTelemetry Collector. The receiver collects logs from Huawei Cloud's LTS service and sends them to the OpenTelemetry Collector for processing and exporting.
+
+## Configuration
+
+The following settings are required:
+
+- `project_id`: The ID of the project in Huawei Cloud. This is used to identify which project's logs are to be collected. See [Obtaining a Project ID](https://support.huaweicloud.com/intl/en-us/devg-apisign/api-sign-provide-proid.html).
+
+- `region_id`: The ID of the Huawei Cloud region from which logs are collected. For example, `eu-west-101`. The full list of the available regions can be found [here](https://pkg.go.dev/github.com/huaweicloud/huaweicloud-sdk-go-v3@v0.1.110/services/lts/v2/region).
+
+- `log_group_id`: A string indicating the ID of the log group.
+
+- `log_stream_id`: A string indicating the ID of the log stream. See [Obtaining Log Group and Log Stream IDs](https://support.huaweicloud.com/intl/en-us/api-lts/lts_api_0006.html#section1).
+
+- `no_verify_ssl`: A boolean flag indicating whether SSL verification should be disabled. Set to True to disable SSL verification.
+
+- `access_key`: The access key needed for LTS authentification. Check `Huawei Cloud SDK Authentication Setup` section for more details.
+
+- `secret_key`: The secret key needed for LTS authentification. Check `Huawei Cloud SDK Authentication Setup` section for more details.
+
+The following settings are optional:
+
+- `initial_delay`: The delay before the first collection of logs begins. This is a duration field, such as 5s for 5 seconds.
+
+- `collection_interval` (default = `60s`): This is the interval at which this receiver collects logs. This value must be a string readable by Golang's [time.ParseDuration](https://pkg.go.dev/time#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. We recommend a polling interval of at least one minute.
+
+- `retry_on_failure`: The following configurations can be used to control the retry policy of the LTS client. The default values are suitable for most deployment scenarios.
+ - `enabled` (default true)
+ - `initial_interval` (default 100ms)
+ - `max_interval` (default 1s)
+ - `max_elapsed_time` (default 15s)
+ - `randomization_factor` (default 0.5)
+ - `multiplier` (default 1.5)
+
+### Example Configuration
+
+```yaml
+receivers:
+ huaweicloudlogsreceiver:
+ collection_interval: 3h
+ initial_delay: 5s
+ access_key: ${env:HUAWEICLOUD_SDK_AK}
+ secret_key: ${env:HUAWEICLOUD_SDK_SK}
+ project_id: project_1
+ region_id: eu-west-101
+ log_group_id: test-group-id
+ log_stream_id: test-stream-id
+ no_verify_ssl: True
+```
+
+The full list of settings exposed for this receiver are documented [here](./config.go).
+
+### Huawei Cloud SDK Authentication Setup
+
+
+To ensure secure authentication, the Access Key (AK) and Secret Key (SK) used by the Huawei Cloud SDK must be stored in environment variables. See [Obtaining an AK/SK](https://support.huaweicloud.com/intl/en-us/devg-apisign/api-sign-provide-aksk.html).
+
+Before running the application, you need to set the environment variables `HUAWEICLOUD_SDK_AK` and `HUAWEICLOUD_SDK_SK` in your local environment. Here’s how you can do it:
+
+1. Open your terminal.
+2. Set the environment variables by executing the following commands:
+
+ ```sh
+ export HUAWEICLOUD_SDK_AK=your-access-key
+ export HUAWEICLOUD_SDK_SK=your-secret-key
+ ```
+
+3. Verify that the variables are set correctly:
+
+ ```sh
+ echo $HUAWEICLOUD_SDK_AK
+ echo $HUAWEICLOUD_SDK_SK
+ ```
+
+## Error handling
+If you encounter any LTS errors, please refer to the [Huawei Cloud Error Codes](https://support.huaweicloud.com/intl/en-us/ae-ad-1-api-lts/lts_api_0021.html).
+
+## Converting LTS Log Representation to OpenTelemetry Log Representation
+
+| Source Field | Target Field | Description |
+|---------------------------|----------------------------------------------------|---------------------------------------------------------------------------------------------------|
+| **Log Content (timestamp)** | `logRecord.observedTimestamp` | The timestamp extracted from the log content. It follows the layout `"2006-01-02/15:04:05"`. Converted to Unix time in nanoseconds. |
+| **Log Content (body)** | `logRecord.body` | The main content of the log. Stored as a string in the OTLP log body. |
+| **Labels (key)** | `logRecord.attributes` | Each label from the LTS log is converted to an attribute in the OTLP log, where the label's key is used as the attribute's key. |
+| **Labels (value)** | `logRecord.attributes` | The corresponding value of each LTS log label key is stored as the attribute's value. |
+| **Line Number** | `logRecord.attributes.lineNum` | The line number from the LTS log, if available. Stored as an attribute with the key `lineNum`. |
+| **Project ID** | `resource.attributes.project.id` | The project ID used in the configuration file of the receiver.. |
+| **Region ID** | `resource.attributes.region.id` | The region id used in the configuration file of the receiver. |
+| **Group ID** | `resource.attributes.group.id` | The log group id used in the configuration file of the receiver. |
+| **Stream ID** | `resource.attributes.stream.id` | The log stream id used in the configuration file of the receiver. |
+| *N/A* | `resource.attributes.cloud.provider` | Set to `"huawei_cloud"` as the cloud provider. |
+| *N/A* | `scopeLogs.scope.name` | Set to `"huawei_cloud_lts"` as the scope name. |
+| *N/A* | `scopeLogs.scope.version` | Set to `"v2"` as the scope version. |
+
+This mapping ensures that logs from Huawei Cloud's LTS can be seamlessly integrated into systems using the OpenTelemetry Protocol for observability.
+
+
+### Notes
+
+- The `timestamp` field in the source is converted from milliseconds to nanoseconds in the target field.
+- Some fields are added in the target format with constant values to provide additional context and metadata.
+
+### Example:
+
+```json
+[
+ {
+ "content": "2020-07-25/14:40:00 this logis Error NO 2",
+ "line_num": "123",
+ "labels": {
+ "hostIP": "192.168.0.156",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace":"CONFIG_FILE",
+ "category": "LTS",
+ }
+ },
+ {
+ "content": "2020-07-25/14:50:00 this logis Error NO 3",
+ "line_num": "456",
+ "labels": {
+ "hostIP": "192.168.0.156",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace":"CONFIG_FILE",
+ "category": "LTS",
+ }
+ },
+]
+```
+
+converts to
+
+```json
+{
+ "resourceLogs": [
+ {
+ "resource": {
+ "attributes": {
+ "cloud.provider": "huawei_cloud",
+ "project.id": "project1",
+ "region.id": "region1",
+ "group.id": "group1",
+ "stream.id":"stream1"
+ },
+ "scopeLogs": [
+ {
+ "scope": {
+ "name": "huawei_cloud_lts",
+ "version": "v2"
+ },
+ "logRecords": [
+ {
+ "observedTimeUnixNano": "1595688000000000000",
+ "body": {
+ "stringValue": "2020-07-25/14:40:00 this log is Error NO 2"
+ },
+ "attributes": {
+ "hostIP": "192.168.0.156",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace":"CONFIG_FILE",
+ "category": "LTS",
+ "lineNum": "123"
+ },
+ },
+ {
+ "observedTimeUnixNano": "1595688600000000000",
+ "body": {
+ "stringValue": "2020-07-25/14:50:00 this log is Error NO 3"
+ },
+ "attributes": {
+ "hostIP": "192.168.0.156",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace":"CONFIG_FILE",
+ "category": "LTS",
+ "lineNum": "456"
+ },
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+}
+```
\ No newline at end of file
diff --git a/receiver/huaweicloudlogsreceiver/config.go b/receiver/huaweicloudlogsreceiver/config.go
new file mode 100644
index 000000000000..dcd4db3149b4
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/config.go
@@ -0,0 +1,122 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver"
+
+import (
+ "errors"
+ "net/url"
+ "strconv"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/config"
+ "go.opentelemetry.io/collector/component"
+ "go.opentelemetry.io/collector/config/confighttp"
+ "go.opentelemetry.io/collector/config/configopaque"
+ "go.opentelemetry.io/collector/config/configretry"
+ "go.opentelemetry.io/collector/receiver/scraperhelper"
+ "go.uber.org/multierr"
+)
+
+var (
+ // Predefined error responses for configuration validation failures
+ errMissingProjectID = errors.New(`"project_id" is not specified in config`)
+ errMissingRegionID = errors.New(`"region_id" is not specified in config`)
+ errMissingGroupID = errors.New(`"log_group_id" is not specified in config`)
+ errMissingStreamID = errors.New(`"log_stream_id" is not specified in config`)
+
+ errInvalidProxy = errors.New(`"proxy_address" must be specified if "proxy_user" or "proxy_password" is set"`)
+)
+
+// Config represent a configuration for the CloudWatch logs exporter.
+type Config struct {
+ scraperhelper.ControllerConfig `mapstructure:",squash"`
+ confighttp.ClientConfig `mapstructure:",squash"`
+ // Set of attributes used to configure huawei's CES SDK connection
+ HuaweiSessionConfig `mapstructure:",squash"`
+
+ // ProjectID is a string to reference project where logs should be associated with.
+ // If ProjectID is not filled in, the SDK will automatically call the IAM service to query the project id corresponding to the region.
+ ProjectID string `mapstructure:"project_id"`
+
+ // RegionID is the ID of the LTS region.
+ RegionID string `mapstructure:"region_id"`
+
+ // GroupID is the ID of the LTS log group.
+ GroupID string `mapstructure:"log_group_id"`
+
+ // Stream is the ID of the LTS log stream.
+ StreamID string `mapstructure:"log_stream_id"`
+
+ BackOffConfig configretry.BackOffConfig `mapstructure:"retry_on_failure"`
+}
+
+type HuaweiSessionConfig struct {
+ AccessKey configopaque.String `mapstructure:"access_key"`
+
+ SecretKey configopaque.String `mapstructure:"secret_key"`
+ // Number of seconds before timing out a request.
+ NoVerifySSL bool `mapstructure:"no_verify_ssl"`
+ // Upload segments to AWS X-Ray through a proxy.
+ ProxyAddress string `mapstructure:"proxy_address"`
+ ProxyUser string `mapstructure:"proxy_user"`
+ ProxyPassword string `mapstructure:"proxy_password"`
+}
+
+var _ component.Config = (*Config)(nil)
+
+// Validate config
+func (config *Config) Validate() error {
+ var err error
+ if config.ProjectID == "" {
+ err = multierr.Append(err, errMissingProjectID)
+ }
+ if config.RegionID == "" {
+ err = multierr.Append(err, errMissingRegionID)
+ }
+ if config.GroupID == "" {
+ err = multierr.Append(err, errMissingGroupID)
+ }
+ if config.StreamID == "" {
+ err = multierr.Append(err, errMissingStreamID)
+ }
+
+ // Validate that ProxyAddress is provided if ProxyUser or ProxyPassword is set
+ if (config.ProxyUser != "" || config.ProxyPassword != "") && config.ProxyAddress == "" {
+ err = multierr.Append(err, errInvalidProxy)
+ }
+
+ return err
+}
+
+func createHTTPConfig(cfg HuaweiSessionConfig) (*config.HttpConfig, error) {
+ if cfg.ProxyAddress == "" {
+ return config.DefaultHttpConfig().WithIgnoreSSLVerification(cfg.NoVerifySSL), nil
+ }
+ proxy, err := configureHTTPProxy(cfg)
+ if err != nil {
+ return nil, err
+ }
+ return config.DefaultHttpConfig().WithProxy(proxy), nil
+}
+
+func configureHTTPProxy(cfg HuaweiSessionConfig) (*config.Proxy, error) {
+ proxyURL, err := url.Parse(cfg.ProxyAddress)
+ if err != nil {
+ return nil, err
+ }
+
+ proxy := config.NewProxy().
+ WithSchema(proxyURL.Scheme).
+ WithHost(proxyURL.Hostname())
+ if len(proxyURL.Port()) > 0 {
+ if i, err := strconv.Atoi(proxyURL.Port()); err == nil {
+ proxy = proxy.WithPort(i)
+ }
+ }
+
+ // Configure the username and password if the proxy requires authentication
+ if len(cfg.ProxyUser) > 0 {
+ proxy = proxy.WithUsername(cfg.ProxyUser).WithPassword(cfg.ProxyPassword)
+ }
+ return proxy, nil
+}
diff --git a/receiver/huaweicloudlogsreceiver/config_test.go b/receiver/huaweicloudlogsreceiver/config_test.go
new file mode 100644
index 000000000000..750dec39403a
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/config_test.go
@@ -0,0 +1,115 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "go.opentelemetry.io/collector/receiver/scraperhelper"
+)
+
+func TestConfig_Validate(t *testing.T) {
+ tests := []struct {
+ name string
+ config Config
+ expectedError string
+ }{
+ {
+ name: "Valid config",
+ config: Config{
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ RegionID: "cn-north-1",
+ ProjectID: "my_project",
+ GroupID: "group-1",
+ StreamID: "stream-1",
+ },
+ expectedError: "",
+ },
+ {
+ name: "Missing region name",
+ config: Config{
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ ProjectID: "my_project",
+ },
+ expectedError: errMissingRegionID.Error(),
+ },
+ {
+ name: "Missing project id",
+ config: Config{
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ RegionID: "cn-north-1",
+ },
+ expectedError: errMissingProjectID.Error(),
+ },
+ {
+ name: "Proxy user without proxy address",
+ config: Config{
+ HuaweiSessionConfig: HuaweiSessionConfig{
+ ProxyUser: "user",
+ },
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ RegionID: "cn-north-1",
+ ProjectID: "my_project",
+ GroupID: "group-1",
+ StreamID: "stream-1",
+ },
+ expectedError: errInvalidProxy.Error(),
+ },
+ {
+ name: "Proxy password without proxy address",
+ config: Config{
+ HuaweiSessionConfig: HuaweiSessionConfig{
+ ProxyPassword: "password",
+ },
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ RegionID: "cn-north-1",
+ ProjectID: "my_project",
+ GroupID: "group-1",
+ StreamID: "stream-1",
+ },
+ expectedError: errInvalidProxy.Error(),
+ },
+ {
+ name: "Proxy address with proxy user and password",
+ config: Config{
+ HuaweiSessionConfig: HuaweiSessionConfig{
+ ProxyAddress: "http://proxy.example.com",
+ ProxyUser: "user",
+ ProxyPassword: "password",
+ },
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: time.Hour,
+ },
+ RegionID: "cn-north-1",
+ ProjectID: "my_project",
+ GroupID: "group-1",
+ StreamID: "stream-1",
+ },
+ expectedError: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := tt.config.Validate()
+ if tt.expectedError == "" {
+ assert.NoError(t, err)
+ } else {
+ assert.ErrorContains(t, err, tt.expectedError)
+ }
+ })
+ }
+}
diff --git a/receiver/huaweicloudlogsreceiver/factory.go b/receiver/huaweicloudlogsreceiver/factory.go
new file mode 100644
index 000000000000..91662fa72231
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/factory.go
@@ -0,0 +1,54 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver"
+
+import (
+ "context"
+ "time"
+
+ "github.com/cenkalti/backoff/v4"
+ "go.opentelemetry.io/collector/component"
+ "go.opentelemetry.io/collector/config/configretry"
+ "go.opentelemetry.io/collector/consumer"
+ "go.opentelemetry.io/collector/receiver"
+
+ "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal/metadata"
+)
+
+func NewFactory() receiver.Factory {
+ return receiver.NewFactory(
+ metadata.Type,
+ createDefaultConfig,
+ receiver.WithLogs(createLogsReceiver, metadata.LogsStability))
+}
+
+func createDefaultConfig() component.Config {
+ return &Config{
+ BackOffConfig: configretry.BackOffConfig{
+ Enabled: true,
+ InitialInterval: 100 * time.Millisecond,
+ MaxInterval: time.Second,
+ MaxElapsedTime: 15 * time.Second,
+ RandomizationFactor: backoff.DefaultRandomizationFactor,
+ Multiplier: backoff.DefaultMultiplier,
+ },
+ HuaweiSessionConfig: HuaweiSessionConfig{
+ NoVerifySSL: false,
+ },
+ }
+}
+
+func createLogsReceiver(
+ _ context.Context,
+ params receiver.Settings,
+ cfg component.Config,
+ next consumer.Logs) (receiver.Logs, error) {
+
+ cesCfg := cfg.(*Config)
+
+ cesReceiver := newHuaweiCloudLogsReceiver(params, cesCfg, next)
+
+ return cesReceiver, nil
+
+}
diff --git a/receiver/huaweicloudlogsreceiver/factory_test.go b/receiver/huaweicloudlogsreceiver/factory_test.go
new file mode 100644
index 000000000000..970a666768af
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/factory_test.go
@@ -0,0 +1,43 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "go.opentelemetry.io/collector/component"
+ "go.opentelemetry.io/collector/component/componenttest"
+ "go.opentelemetry.io/collector/consumer/consumertest"
+ "go.opentelemetry.io/collector/receiver/receivertest"
+)
+
+func TestNewFactory(t *testing.T) {
+ factory := NewFactory()
+ assert.NotNil(t, factory)
+ assert.Equal(t, component.MustNewType("huaweicloudlogsreceiver"), factory.Type())
+}
+
+func TestCreateDefaultConfig(t *testing.T) {
+ factory := NewFactory()
+ config := factory.CreateDefaultConfig()
+ assert.NotNil(t, config)
+ assert.NoError(t, componenttest.CheckConfigStruct(config))
+}
+
+func TestCreateLogsReceiver(t *testing.T) {
+ factory := NewFactory()
+ config := factory.CreateDefaultConfig()
+
+ rConfig := config.(*Config)
+ rConfig.CollectionInterval = 60 * time.Second
+ rConfig.InitialDelay = time.Second
+
+ nextConsumer := new(consumertest.LogsSink)
+ receiver, err := factory.CreateLogsReceiver(context.Background(), receivertest.NewNopSettings(), config, nextConsumer)
+ assert.NoError(t, err)
+ assert.NotNil(t, receiver)
+}
diff --git a/receiver/huaweicloudlogsreceiver/generated_component_test.go b/receiver/huaweicloudlogsreceiver/generated_component_test.go
new file mode 100644
index 000000000000..c6ce35029500
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/generated_component_test.go
@@ -0,0 +1,69 @@
+// Code generated by mdatagen. DO NOT EDIT.
+
+package huaweicloudlogsreceiver
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/collector/component"
+ "go.opentelemetry.io/collector/component/componenttest"
+ "go.opentelemetry.io/collector/confmap/confmaptest"
+ "go.opentelemetry.io/collector/consumer/consumertest"
+ "go.opentelemetry.io/collector/receiver"
+ "go.opentelemetry.io/collector/receiver/receivertest"
+)
+
+func TestComponentFactoryType(t *testing.T) {
+ require.Equal(t, "huaweicloudlogsreceiver", NewFactory().Type().String())
+}
+
+func TestComponentConfigStruct(t *testing.T) {
+ require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig()))
+}
+
+func TestComponentLifecycle(t *testing.T) {
+ factory := NewFactory()
+
+ tests := []struct {
+ name string
+ createFn func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error)
+ }{
+
+ {
+ name: "logs",
+ createFn: func(ctx context.Context, set receiver.Settings, cfg component.Config) (component.Component, error) {
+ return factory.CreateLogsReceiver(ctx, set, cfg, consumertest.NewNop())
+ },
+ },
+ }
+
+ cm, err := confmaptest.LoadConf("metadata.yaml")
+ require.NoError(t, err)
+ cfg := factory.CreateDefaultConfig()
+ sub, err := cm.Sub("tests::config")
+ require.NoError(t, err)
+ require.NoError(t, sub.Unmarshal(&cfg))
+
+ for _, test := range tests {
+ t.Run(test.name+"-shutdown", func(t *testing.T) {
+ c, err := test.createFn(context.Background(), receivertest.NewNopSettings(), cfg)
+ require.NoError(t, err)
+ err = c.Shutdown(context.Background())
+ require.NoError(t, err)
+ })
+ t.Run(test.name+"-lifecycle", func(t *testing.T) {
+ firstRcvr, err := test.createFn(context.Background(), receivertest.NewNopSettings(), 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.NewNopSettings(), cfg)
+ require.NoError(t, err)
+ require.NoError(t, secondRcvr.Start(context.Background(), host))
+ require.NoError(t, secondRcvr.Shutdown(context.Background()))
+ })
+ }
+}
diff --git a/receiver/huaweicloudlogsreceiver/generated_package_test.go b/receiver/huaweicloudlogsreceiver/generated_package_test.go
new file mode 100644
index 000000000000..3b6ca7e307c7
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/generated_package_test.go
@@ -0,0 +1,12 @@
+// Code generated by mdatagen. DO NOT EDIT.
+
+package huaweicloudlogsreceiver
+
+import (
+ "go.uber.org/goleak"
+ "testing"
+)
+
+func TestMain(m *testing.M) {
+ goleak.VerifyTestMain(m)
+}
diff --git a/receiver/huaweicloudlogsreceiver/go.mod b/receiver/huaweicloudlogsreceiver/go.mod
new file mode 100644
index 000000000000..b68e0f441b64
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/go.mod
@@ -0,0 +1,95 @@
+module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver
+
+go 1.22.0
+
+toolchain go1.22.3
+
+require (
+ github.com/cenkalti/backoff/v4 v4.3.0
+ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.110
+ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.108.0
+ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.107.0
+ github.com/stretchr/testify v1.9.0
+ go.opentelemetry.io/collector/component v0.107.0
+ go.opentelemetry.io/collector/config/confighttp v0.107.0
+ go.opentelemetry.io/collector/config/configopaque v1.13.0
+ go.opentelemetry.io/collector/config/configretry v1.13.0
+ go.opentelemetry.io/collector/confmap v0.107.0
+ go.opentelemetry.io/collector/consumer v0.107.0
+ go.opentelemetry.io/collector/consumer/consumertest v0.107.0
+ go.opentelemetry.io/collector/pdata v1.14.1
+ go.opentelemetry.io/collector/receiver v0.106.1
+ go.uber.org/goleak v1.3.0
+ go.uber.org/multierr v1.11.0
+ go.uber.org/zap v1.27.0
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/felixge/httpsnoop v1.0.4 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/go-version v1.7.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/compress v1.17.9 // 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.1.1 // indirect
+ github.com/mitchellh/copystructure v1.2.0 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.108.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_golang v1.19.1 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.55.0 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ github.com/rs/cors v1.11.0 // indirect
+ github.com/stretchr/objx v0.5.2 // indirect
+ github.com/tjfoc/gmsm v1.4.1 // indirect
+ go.mongodb.org/mongo-driver v1.15.0 // indirect
+ go.opentelemetry.io/collector v0.107.0 // indirect
+ go.opentelemetry.io/collector/client v1.13.0 // indirect
+ go.opentelemetry.io/collector/config/configauth v0.107.0 // indirect
+ go.opentelemetry.io/collector/config/configcompression v1.13.0 // indirect
+ go.opentelemetry.io/collector/config/configtelemetry v0.107.0 // indirect
+ go.opentelemetry.io/collector/config/configtls v1.13.0 // indirect
+ go.opentelemetry.io/collector/config/internal v0.107.0 // indirect
+ go.opentelemetry.io/collector/consumer/consumerprofiles v0.107.0 // indirect
+ go.opentelemetry.io/collector/extension v0.107.0 // indirect
+ go.opentelemetry.io/collector/extension/auth v0.107.0 // indirect
+ go.opentelemetry.io/collector/featuregate v1.13.0 // indirect
+ go.opentelemetry.io/collector/internal/globalgates v0.107.0 // indirect
+ go.opentelemetry.io/collector/pdata/pprofile v0.107.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
+ go.opentelemetry.io/otel v1.28.0 // indirect
+ go.opentelemetry.io/otel/exporters/prometheus v0.50.0 // indirect
+ go.opentelemetry.io/otel/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.28.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect
+ go.opentelemetry.io/otel/trace v1.28.0 // indirect
+ golang.org/x/crypto v0.26.0 // indirect
+ golang.org/x/net v0.28.0 // indirect
+ golang.org/x/sys v0.23.0 // indirect
+ golang.org/x/text v0.17.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
+ google.golang.org/grpc v1.65.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+)
+
+replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../pkg/pdatautil
+
+replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../pkg/pdatatest
+
+replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../pkg/golden
diff --git a/receiver/huaweicloudlogsreceiver/go.sum b/receiver/huaweicloudlogsreceiver/go.sum
new file mode 100644
index 000000000000..c9dc3a760183
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/go.sum
@@ -0,0 +1,310 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+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.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/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.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.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/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/envoyproxy/go-control-plane v0.9.0/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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/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-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
+github.com/go-viper/mapstructure/v2 v2.0.0/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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+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.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.110 h1:XaWasqaF3tzRGw9cFmhXM1IiPAom8Nhg760F9LYHPKg=
+github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.110/go.mod h1:JWz2ujO9X3oU5wb6kXp+DpR2UuDj2SldDbX8T0FSuhI=
+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/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+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.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
+github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
+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/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/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/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
+github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
+github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
+github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
+github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
+go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
+go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+go.opentelemetry.io/collector v0.107.0 h1:C1Mng03iE73flGhEg795IFVlr3qhDLef5GESjIVtx5g=
+go.opentelemetry.io/collector v0.107.0/go.mod h1:7xDYvzBb3Ez6qFQl0IArBbmNNazIxZMVoRkbgJYRjyg=
+go.opentelemetry.io/collector/client v1.13.0 h1:I0GzZPcOG+F6BY46SgsdcpCN+L0xPfjzTRN2Y4Etdrw=
+go.opentelemetry.io/collector/client v1.13.0/go.mod h1:GOE/UvRdklwPCqYLRUOSiU/syl1BGQWa48ex7OGOq9Y=
+go.opentelemetry.io/collector/component v0.107.0 h1:3ReaEAtKwrPj7HrlKjEGBDKbBaxdRMPC2mfZ9b6zjXE=
+go.opentelemetry.io/collector/component v0.107.0/go.mod h1:1xMIYKvpnP7laipjgEw7kq1ozG7ySLkA0Evhr2Bp8M4=
+go.opentelemetry.io/collector/config/configauth v0.107.0 h1:2rAoMJjSfdP+ao5i7Ku68QYW5p0jjZ9y6a0nj3cIFp0=
+go.opentelemetry.io/collector/config/configauth v0.107.0/go.mod h1:IQx5BBZRoyPX9Qr3W4Ajy/4AnyJZb6jylI4rITrZMHQ=
+go.opentelemetry.io/collector/config/configcompression v1.13.0 h1:2LApl3qDZgjsVblY1Qu0gJaw9ZyYnZ6ZDUvid9rCZVg=
+go.opentelemetry.io/collector/config/configcompression v1.13.0/go.mod h1:6+m0GKCv7JKzaumn7u80A2dLNCuYf5wdR87HWreoBO0=
+go.opentelemetry.io/collector/config/confighttp v0.107.0 h1:HnfFg/n3xu+XH7onWrFZl8jBrVVUCe+updh3ANUB3w8=
+go.opentelemetry.io/collector/config/confighttp v0.107.0/go.mod h1:/slm41hcfOwAxv8ZcGCKHC22jnQZ71z42OSWChKuIgU=
+go.opentelemetry.io/collector/config/configopaque v1.13.0 h1:EDB9JIifmBth1z9IsEduoE1bT1Q8jV0sR005EMW7q1w=
+go.opentelemetry.io/collector/config/configopaque v1.13.0/go.mod h1:0xURn2sOy5j4fbaocpEYfM97HPGsiffkkVudSPyTJlM=
+go.opentelemetry.io/collector/config/configretry v1.13.0 h1:gcjWB6FOG1u1e5ecs3nOtOysXWtxJxeL+cNiFLI+nCo=
+go.opentelemetry.io/collector/config/configretry v1.13.0/go.mod h1:P+RA0IA+QoxnDn4072uyeAk1RIoYiCbxYsjpKX5eFC4=
+go.opentelemetry.io/collector/config/configtelemetry v0.107.0 h1:pSGd4FWQ/Up/Af+XZTR8JNneH/wmQ/TAU4Z16JHQeUc=
+go.opentelemetry.io/collector/config/configtelemetry v0.107.0/go.mod h1:WxWKNVAQJg/Io1nA3xLgn/DWLE/W1QOB2+/Js3ACi40=
+go.opentelemetry.io/collector/config/configtls v1.13.0 h1:N57vOibMIPX9YZq4ZLFjj5ZjUHMYW7bpkPkygU3vt8w=
+go.opentelemetry.io/collector/config/configtls v1.13.0/go.mod h1:3CBJYFQYTCYIPJMRvzn3NVtasv8out21ZNXkSCPepuY=
+go.opentelemetry.io/collector/config/internal v0.107.0 h1:aENT1bVin8HCHZuKXc8U1sTYalzl6+RpOMDWpt+VoCQ=
+go.opentelemetry.io/collector/config/internal v0.107.0/go.mod h1:2LQPVQPDeIyXN6AXazlls1M0zmlhIM7q80V4K7mQ6PM=
+go.opentelemetry.io/collector/confmap v0.107.0 h1:M2o7jvQM9bnMU3pE2N6BK4KHYtSnvsSZkegUD89y8BU=
+go.opentelemetry.io/collector/confmap v0.107.0/go.mod h1:9Fs/ZEIeiMa38VqkqIpn+JKQkcPf/lhAKA9fHu6c9GY=
+go.opentelemetry.io/collector/consumer v0.107.0 h1:fF/+xyv9BfXQUvuJqkljrpzKyBQExDQt6zB5rzGyuHs=
+go.opentelemetry.io/collector/consumer v0.107.0/go.mod h1:wgWpFes9sbnZ11XeJPSeutU8GJx6dT/gzSUqHpaZZQA=
+go.opentelemetry.io/collector/consumer/consumerprofiles v0.107.0 h1:SEP5rLm4KgBaELciRQO4m9U2q3xn16KGjpIw8zQn6Ik=
+go.opentelemetry.io/collector/consumer/consumerprofiles v0.107.0/go.mod h1:Vi/aqlZjCBdGgGu+iOEfUyHvq2TJBar0WfsQSOMhR6Y=
+go.opentelemetry.io/collector/consumer/consumertest v0.107.0 h1:BfjFHHAqbTmCN32akYvMhWKYC+ayHTX935/fRChwohM=
+go.opentelemetry.io/collector/consumer/consumertest v0.107.0/go.mod h1:qNMedscdVyuxbV+wWUt4yGKQM3c0YEgQJTFeAtGZjRY=
+go.opentelemetry.io/collector/extension v0.107.0 h1:wstZXb24RwdcchZu3juTH9M0xryKG4sYmb1/w6J3tqQ=
+go.opentelemetry.io/collector/extension v0.107.0/go.mod h1:w/+HXzRO7jPutUIMyBlzX6deUPl205QCEwteB0YgWOg=
+go.opentelemetry.io/collector/extension/auth v0.107.0 h1:xv+MI9ce8RG1UP4XihTaPR3pCKVvKOl2iIyFqYa0bvE=
+go.opentelemetry.io/collector/extension/auth v0.107.0/go.mod h1:tFMzaKaUAx3eWpp3lOLZ8OcLbryydyof9+GCvCWmKmU=
+go.opentelemetry.io/collector/featuregate v1.13.0 h1:rc84eCf5hesXQ8/bP6Zc15wqthbomfLBHmox5tT7AwM=
+go.opentelemetry.io/collector/featuregate v1.13.0/go.mod h1:PsOINaGgTiFc+Tzu2K/X2jP+Ngmlp7YKGV1XrnBkH7U=
+go.opentelemetry.io/collector/internal/globalgates v0.107.0 h1:PaD6WgQg80YTVxg8OF+YEqgI7WRd13wMu/R6GIG7uNU=
+go.opentelemetry.io/collector/internal/globalgates v0.107.0/go.mod h1:hca7Tpzu6JmBrAOgmlyp/ZM6kxprPRMKqSYoq/Tdzjw=
+go.opentelemetry.io/collector/pdata v1.14.1 h1:wXZjtQA7Vy5HFqco+yA95ENyMQU5heBB1IxMHQf6mUk=
+go.opentelemetry.io/collector/pdata v1.14.1/go.mod h1:z1dTjwwtcoXxZx2/nkHysjxMeaxe9pEmYTEr4SMNIx8=
+go.opentelemetry.io/collector/pdata/pprofile v0.107.0 h1:F25VZrEkSaneIBNcNI9LEBWf9nRC/WHKluSBTP0gKAA=
+go.opentelemetry.io/collector/pdata/pprofile v0.107.0/go.mod h1:1GrwsKtgogRCt8aG/0lfJ037yDdFtYqF+OtJr+snxRQ=
+go.opentelemetry.io/collector/pdata/testdata v0.107.0 h1:02CqvJrYjkrBlWDD+6yrByN1AhG2zT61OScLPhyyMwU=
+go.opentelemetry.io/collector/pdata/testdata v0.107.0/go.mod h1:bqaeiDH1Lc5DFJXvjVHwO50x00TXj+oFre+EbOVeZXs=
+go.opentelemetry.io/collector/receiver v0.106.1 h1:9kDLDJmInnz+AzAV9oV/UGMoc1+oI1pwMMs7+uMiJq4=
+go.opentelemetry.io/collector/receiver v0.106.1/go.mod h1:3j9asWz7mqsgE77rPaNhlNQhRwgFhRynf0UEPs/4rkM=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng=
+go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08=
+go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+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.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+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.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+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-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
+golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+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=
+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/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+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.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
+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.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
+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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+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=
diff --git a/receiver/huaweicloudlogsreceiver/integration_test.go b/receiver/huaweicloudlogsreceiver/integration_test.go
new file mode 100644
index 000000000000..22494f271d30
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/integration_test.go
@@ -0,0 +1,99 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudcesreceiver"
+
+import (
+ "context"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/collector/component/componenttest"
+ "go.opentelemetry.io/collector/consumer/consumertest"
+ "go.opentelemetry.io/collector/receiver/receivertest"
+
+ "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden"
+ "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest/plogtest"
+ "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal/mocks"
+)
+
+func TestHuaweiCloudLogsReceiverIntegration(t *testing.T) {
+ mc := mocks.NewLtsClient(t)
+
+ mc.On("ListLogs", mock.Anything).Return(&model.ListLogsResponse{
+ Logs: &[]model.LogContents{
+ {
+ Content: stringPtr("2020-07-25/14:40:00 this log is Error NO 2"),
+ LineNum: stringPtr("123"),
+ Labels: map[string]string{
+ "hostName": "ecs-kwxtest",
+ "hostIP": "192.168.0.156",
+ "appName": "default_appname",
+ "containerName": "CONFIG_FILE",
+ "clusterName": "CONFIG_FILE",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f55",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace": "CONFIG_FILE",
+ "category": "LTS",
+ },
+ },
+ {
+ Content: stringPtr("2020-07-25/14:50:00 this log is Error NO 3"),
+ LineNum: stringPtr("456"),
+ Labels: map[string]string{
+ "hostName": "ecs-kwxtest",
+ "hostIP": "192.168.0.156",
+ "appName": "default_appname",
+ "containerName": "CONFIG_FILE",
+ "clusterName": "CONFIG_FILE",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f55",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace": "CONFIG_FILE",
+ "category": "LTS",
+ },
+ },
+ },
+ }, nil)
+
+ sink := &consumertest.LogsSink{}
+ cfg := createDefaultConfig().(*Config)
+ cfg.RegionID = "us-east-2"
+ cfg.CollectionInterval = time.Second
+ cfg.ProjectID = "my-project"
+ cfg.GroupID = "group-1"
+ cfg.StreamID = "stream-1"
+
+ recv, err := NewFactory().CreateLogsReceiver(
+ context.Background(),
+ receivertest.NewNopSettings(),
+ cfg,
+ sink,
+ )
+ require.NoError(t, err)
+
+ rcvr, ok := recv.(*logsReceiver)
+ require.True(t, ok)
+ rcvr.client = mc
+
+ err = recv.Start(context.Background(), componenttest.NewNopHost())
+ require.NoError(t, err)
+
+ require.Eventually(t, func() bool {
+ return sink.LogRecordCount() > 0
+ }, 5*time.Second, 10*time.Millisecond)
+
+ err = recv.Shutdown(context.Background())
+ require.NoError(t, err)
+
+ logs := sink.AllLogs()[0]
+
+ expectedLogs, err := golden.ReadLogs(filepath.Join("testdata", "golden", "logs_golden.json"))
+ require.NoError(t, err)
+ require.NoError(t, plogtest.CompareLogs(expectedLogs, logs))
+}
diff --git a/receiver/huaweicloudcesreceiver/internal/backoff.go b/receiver/huaweicloudlogsreceiver/internal/backoff.go
similarity index 100%
rename from receiver/huaweicloudcesreceiver/internal/backoff.go
rename to receiver/huaweicloudlogsreceiver/internal/backoff.go
diff --git a/receiver/huaweicloudcesreceiver/internal/backoff_test.go b/receiver/huaweicloudlogsreceiver/internal/backoff_test.go
similarity index 100%
rename from receiver/huaweicloudcesreceiver/internal/backoff_test.go
rename to receiver/huaweicloudlogsreceiver/internal/backoff_test.go
diff --git a/receiver/huaweicloudlogsreceiver/internal/lts_client.go b/receiver/huaweicloudlogsreceiver/internal/lts_client.go
new file mode 100644
index 000000000000..8755afaf5e9d
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/internal/lts_client.go
@@ -0,0 +1,16 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal"
+
+import (
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+)
+
+// This interface should have all the function defined inside https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.110/services/lts/v2/lts_client.go
+// Check https://github.com/vektra/mockery on how to install it on your machine.
+//
+//go:generate mockery --name LtsClient --case=underscore --output=./mocks
+type LtsClient interface {
+ ListLogs(request *model.ListLogsRequest) (*model.ListLogsResponse, error)
+}
diff --git a/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp.go b/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp.go
new file mode 100644
index 000000000000..d954ae4696ab
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp.go
@@ -0,0 +1,73 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package internal // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal"
+
+import (
+ "fmt"
+ "regexp"
+ "time"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ "go.opentelemetry.io/collector/pdata/pcommon"
+ "go.opentelemetry.io/collector/pdata/plog"
+)
+
+const layout = "2006-01-02/15:04:05"
+
+// example of log content : "2020-07-25/14:44:43 this log is Error NO 2"
+func extractTimestamp(log string) (time.Time, error) {
+ // Define the regex pattern to match the timestamp
+ pattern := `(\d{4}-\d{2}-\d{2}/\d{2}:\d{2}:\d{2})`
+ re := regexp.MustCompile(pattern)
+
+ matches := re.FindStringSubmatch(log)
+ if len(matches) == 0 {
+ return time.Time{}, fmt.Errorf("timestamp not found")
+ }
+
+ timestamp, err := time.Parse(layout, matches[0])
+ if err != nil {
+ return time.Time{}, err
+ }
+ return timestamp, nil
+}
+
+func ConvertLTSLogsToOTLP(projectID, regionID, groupID, streamD string, ltsLogs []model.LogContents) plog.Logs {
+ logs := plog.NewLogs()
+ resourceMetric := logs.ResourceLogs().AppendEmpty()
+
+ resource := resourceMetric.Resource()
+ resource.Attributes().PutStr("cloud.provider", "huawei_cloud")
+ resource.Attributes().PutStr("project.id", projectID)
+ resource.Attributes().PutStr("region.id", regionID)
+ resource.Attributes().PutStr("group.id", groupID)
+ resource.Attributes().PutStr("stream.id", streamD)
+
+ if len(ltsLogs) == 0 {
+ return logs
+ }
+ scopedLog := resourceMetric.ScopeLogs().AppendEmpty()
+ scopedLog.Scope().SetName("huawei_cloud_lts")
+ scopedLog.Scope().SetVersion("v2")
+
+ for _, ltsLog := range ltsLogs {
+
+ logRecord := scopedLog.LogRecords().AppendEmpty()
+ if ltsLog.Content != nil {
+ content := *ltsLog.Content
+ if ts, err := extractTimestamp(content); err == nil {
+ logRecord.SetObservedTimestamp(pcommon.Timestamp(ts.UnixNano()))
+ }
+ logRecord.Body().SetStr(content)
+ }
+ for key, value := range ltsLog.Labels {
+ logRecord.Attributes().PutStr(key, value)
+ }
+ if ltsLog.LineNum != nil {
+ logRecord.Attributes().PutStr("lineNum", *ltsLog.LineNum)
+ }
+ }
+
+ return logs
+}
diff --git a/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp_test.go b/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp_test.go
new file mode 100644
index 000000000000..684c6442588c
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/internal/lts_to_otlp_test.go
@@ -0,0 +1,235 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/collector/pdata/pcommon"
+ "go.opentelemetry.io/collector/pdata/plog"
+)
+
+func mustParseTime(t *testing.T, value string) time.Time {
+ ts, err := time.Parse(layout, value)
+ if err != nil {
+ require.NoError(t, err)
+ }
+ return ts
+}
+
+func TestExtractTimestamp(t *testing.T) {
+ tests := []struct {
+ name string
+ log string
+ expectedTime time.Time
+ expectedError bool
+ }{
+ {
+ name: "Valid Timestamp",
+ log: "2020-07-25/14:44:43 this log is Error NO 2",
+ expectedTime: time.Date(2020, 7, 25, 14, 44, 43, 0, time.UTC),
+ },
+ {
+ name: "Invalid Timestamp Format",
+ log: "2020/07/25 14:44:43 this log is Error NO 2",
+ expectedError: true,
+ },
+ {
+ name: "No Timestamp",
+ log: "this log is Error NO 2",
+ expectedError: true,
+ },
+ {
+ name: "Empty Log",
+ log: "",
+ expectedError: true,
+ },
+ {
+ name: "Partial Timestamp",
+ log: "2020-07-25/14:44 this log is Error NO 2",
+ expectedError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := extractTimestamp(tt.log)
+
+ if (err != nil) != tt.expectedError {
+ t.Errorf("extractTimestamp() error = %v, expectedError %v", err, tt.expectedError)
+ return
+ }
+ if !tt.expectedError && !got.Equal(tt.expectedTime) {
+ t.Errorf("extractTimestamp() = %v, expected %v", got, tt.expectedTime)
+ }
+ })
+ }
+}
+
+func TestConvertLTSLogsToOTLP(t *testing.T) {
+ tests := []struct {
+ name string
+ projectID string
+ regionID string
+ groupID string
+ streamID string
+ ltsLogs []model.LogContents
+ expectLogs int
+ expectedTime time.Time
+ }{
+ {
+ name: "Valid log content with timestamp",
+ projectID: "project1",
+ regionID: "region1",
+ groupID: "group1",
+ streamID: "stream1",
+ ltsLogs: []model.LogContents{
+ {
+ Content: stringPointer("2020-07-25/14:40:00 this log is Error NO 2"),
+ LineNum: stringPointer("123"),
+ Labels: map[string]string{
+ "hostName": "ecs-kwxtest",
+ "hostIP": "192.168.0.156",
+ "appName": "default_appname",
+ "containerName": "CONFIG_FILE",
+ "clusterName": "CONFIG_FILE",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace": "CONFIG_FILE",
+ "category": "LTS",
+ },
+ },
+ {
+ Content: stringPointer("2020-07-25/14:50:00 this log is Error NO 2"),
+ LineNum: stringPointer("456"),
+ Labels: map[string]string{
+ "hostName": "ecs-kwxtest",
+ "hostIP": "192.168.0.156",
+ "appName": "default_appname",
+ "containerName": "CONFIG_FILE",
+ "clusterName": "CONFIG_FILE",
+ "hostId": "9787ef31-f171-4eff-ba71-72d580f11f60",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace": "CONFIG_FILE",
+ "category": "LTS",
+ },
+ },
+ },
+ expectLogs: 2,
+ expectedTime: mustParseTime(t, "2020-07-25/14:44:43"),
+ },
+ {
+ name: "Log content without timestamp",
+ projectID: "project2",
+ regionID: "region2",
+ groupID: "group2",
+ streamID: "stream2",
+ ltsLogs: []model.LogContents{
+ {
+ Content: stringPointer("this log has no timestamp"),
+ Labels: map[string]string{"level": "info"},
+ LineNum: stringPointer("456"),
+ },
+ },
+ expectLogs: 1,
+ expectedTime: time.Time{},
+ },
+ {
+ name: "Empty log content",
+ projectID: "project3",
+ regionID: "region3",
+ groupID: "group3",
+ streamID: "stream3",
+ ltsLogs: []model.LogContents{
+ {
+ Content: stringPointer(""),
+ Labels: map[string]string{"level": "debug"},
+ LineNum: stringPointer("789"),
+ },
+ },
+ expectLogs: 1,
+ expectedTime: time.Time{},
+ },
+ {
+ name: "Multiple logs",
+ projectID: "project4",
+ regionID: "region4",
+ groupID: "group4",
+ streamID: "stream4",
+ ltsLogs: []model.LogContents{
+ {
+ Content: stringPointer("2020-07-25/14:44:43 log1"),
+ Labels: map[string]string{"level": "error"},
+ LineNum: stringPointer("1"),
+ },
+ {
+ Content: stringPointer("2020-07-25/14:45:43 log2"),
+ Labels: map[string]string{"level": "info"},
+ LineNum: stringPointer("2"),
+ },
+ },
+ expectLogs: 2,
+ expectedTime: time.Time{},
+ },
+ {
+ name: "Invalid timestamp format",
+ projectID: "project5",
+ regionID: "region5",
+ groupID: "group5",
+ streamID: "stream5",
+ ltsLogs: []model.LogContents{
+ {
+ Content: stringPointer("25/07/2020 14:44:43 this log has an invalid timestamp"),
+ Labels: map[string]string{"level": "error"},
+ LineNum: stringPointer("123"),
+ },
+ },
+ expectLogs: 1,
+ expectedTime: time.Time{},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ logs := ConvertLTSLogsToOTLP(tt.projectID, tt.regionID, tt.groupID, tt.streamID, tt.ltsLogs)
+ m := &plog.JSONMarshaler{}
+
+ jsonLogs, err := m.MarshalLogs(logs)
+ require.NoError(t, err)
+ fmt.Println(tt.name, string(jsonLogs))
+ require.Equal(t, 1, logs.ResourceLogs().Len())
+ require.Equal(t, 1, logs.ResourceLogs().At(0).ScopeLogs().Len())
+
+ logRecords := logs.ResourceLogs().At(0).ScopeLogs().At(0).LogRecords()
+ assert.Equal(t, tt.expectLogs, logRecords.Len(), "unexpected number of logs")
+
+ for i := 0; i < tt.expectLogs; i++ {
+ logRecord := logRecords.At(i)
+ if tt.ltsLogs[i].Content != nil {
+ content := *tt.ltsLogs[i].Content
+ if ts, err := extractTimestamp(content); err == nil {
+ assert.Equal(t, pcommon.Timestamp(ts.UnixNano()), logRecord.ObservedTimestamp(), "unexpected timestamp in log record")
+ }
+ }
+
+ for key, value := range tt.ltsLogs[i].Labels {
+ attr, found := logRecord.Attributes().Get(key)
+ assert.True(t, found, "expected attribute %s not found", key)
+ assert.Equal(t, value, attr.Str(), "unexpected value for attribute %s", key)
+ }
+ }
+ })
+ }
+}
+
+func stringPointer(s string) *string {
+ return &s
+}
diff --git a/receiver/huaweicloudlogsreceiver/internal/metadata/generated_status.go b/receiver/huaweicloudlogsreceiver/internal/metadata/generated_status.go
new file mode 100644
index 000000000000..6ec59b8b2775
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/internal/metadata/generated_status.go
@@ -0,0 +1,16 @@
+// Code generated by mdatagen. DO NOT EDIT.
+
+package metadata
+
+import (
+ "go.opentelemetry.io/collector/component"
+)
+
+var (
+ Type = component.MustNewType("huaweicloudlogsreceiver")
+ ScopeName = "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver"
+)
+
+const (
+ LogsStability = component.StabilityLevelDevelopment
+)
diff --git a/receiver/huaweicloudlogsreceiver/internal/mocks/lts_client.go b/receiver/huaweicloudlogsreceiver/internal/mocks/lts_client.go
new file mode 100644
index 000000000000..32865bd87791
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/internal/mocks/lts_client.go
@@ -0,0 +1,57 @@
+// Code generated by mockery v2.44.1. DO NOT EDIT.
+
+package mocks
+
+import (
+ model "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// LtsClient is an autogenerated mock type for the LtsClient type
+type LtsClient struct {
+ mock.Mock
+}
+
+// ListLogs provides a mock function with given fields: request
+func (_m *LtsClient) ListLogs(request *model.ListLogsRequest) (*model.ListLogsResponse, error) {
+ ret := _m.Called(request)
+
+ if len(ret) == 0 {
+ panic("no return value specified for ListLogs")
+ }
+
+ var r0 *model.ListLogsResponse
+ var r1 error
+ if rf, ok := ret.Get(0).(func(*model.ListLogsRequest) (*model.ListLogsResponse, error)); ok {
+ return rf(request)
+ }
+ if rf, ok := ret.Get(0).(func(*model.ListLogsRequest) *model.ListLogsResponse); ok {
+ r0 = rf(request)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*model.ListLogsResponse)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(*model.ListLogsRequest) error); ok {
+ r1 = rf(request)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// NewLtsClient creates a new instance of LtsClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewLtsClient(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *LtsClient {
+ mock := &LtsClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/receiver/huaweicloudlogsreceiver/metadata.yaml b/receiver/huaweicloudlogsreceiver/metadata.yaml
new file mode 100644
index 000000000000..b3f516ad2f16
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/metadata.yaml
@@ -0,0 +1,9 @@
+type: huaweicloudlogsreceiver
+
+status:
+ class: receiver
+ stability:
+ development: [logs]
+ distributions: []
+ codeowners:
+ active: [heitorganzeli, narcis96]
diff --git a/receiver/huaweicloudlogsreceiver/receiver.go b/receiver/huaweicloudlogsreceiver/receiver.go
new file mode 100644
index 000000000000..0263f6ed50df
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/receiver.go
@@ -0,0 +1,200 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver"
+
+import (
+ "context"
+ "errors"
+ "strings"
+ "time"
+
+ "github.com/cenkalti/backoff/v4"
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
+ lts "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2"
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/region"
+ "go.opentelemetry.io/collector/component"
+ "go.opentelemetry.io/collector/consumer"
+ "go.opentelemetry.io/collector/receiver"
+ "go.uber.org/zap"
+
+ "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal"
+)
+
+const (
+ // See https://support.huaweicloud.com/intl/en-us/devg-apisign/api-sign-errorcode.html
+ requestThrottledErrMsg = "APIGW.0308"
+)
+
+type logsReceiver struct {
+ logger *zap.Logger
+ client internal.LtsClient
+ cancel context.CancelFunc
+
+ host component.Host
+ nextConsumer consumer.Logs
+ lastTs time.Time
+ config *Config
+ shutdownChan chan struct{}
+}
+
+func newHuaweiCloudLogsReceiver(settings receiver.Settings, cfg *Config, next consumer.Logs) *logsReceiver {
+ rcvr := &logsReceiver{
+ logger: settings.Logger,
+ config: cfg,
+ nextConsumer: next,
+ shutdownChan: make(chan struct{}, 1),
+ }
+ return rcvr
+}
+
+func (rcvr *logsReceiver) Start(ctx context.Context, host component.Host) error {
+ rcvr.host = host
+ ctx, rcvr.cancel = context.WithCancel(ctx)
+
+ if rcvr.client == nil {
+ client, err := rcvr.createClient()
+ if err != nil {
+ rcvr.logger.Error(err.Error())
+ return nil
+ }
+ rcvr.client = client
+ }
+
+ go rcvr.startReadingLogs(ctx)
+ return nil
+}
+
+func (rcvr *logsReceiver) startReadingLogs(ctx context.Context) {
+ if rcvr.config.InitialDelay > 0 {
+ <-time.After(rcvr.config.InitialDelay)
+ }
+ if err := rcvr.pollLogsAndConsume(ctx); err != nil {
+ rcvr.logger.Error(err.Error())
+ }
+ ticker := time.NewTicker(rcvr.config.CollectionInterval)
+
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ // TODO: Improve error handling for client-server interactions
+ // The current implementation lacks robust error handling, especially for
+ // scenarios such as service unavailability, timeouts, and request errors.
+ // - Investigate how to handle service unavailability or timeouts gracefully.
+ // - Implement appropriate actions or retries for different types of request errors.
+ // - Refer to the Huawei SDK documentation to identify
+ // all possible client/request errors and determine how to manage them.
+ // - Consider implementing custom error messages or fallback mechanisms for critical failures.
+
+ if err := rcvr.pollLogsAndConsume(ctx); err != nil {
+ rcvr.logger.Error(err.Error())
+ }
+ case <-ctx.Done():
+ return
+ }
+ }
+}
+
+func (rcvr *logsReceiver) createClient() (*lts.LtsClient, error) {
+ auth, err := basic.NewCredentialsBuilder().
+ WithAk(string(rcvr.config.AccessKey)).
+ WithSk(string(rcvr.config.SecretKey)).
+ WithProjectId(rcvr.config.ProjectID).
+ SafeBuild()
+
+ if err != nil {
+ return nil, err
+ }
+
+ httpConfig, err := createHTTPConfig(rcvr.config.HuaweiSessionConfig)
+ if err != nil {
+ return nil, err
+ }
+ r, err := region.SafeValueOf(rcvr.config.RegionID)
+ if err != nil {
+ return nil, err
+ }
+
+ hcHTTPConfig, err := lts.LtsClientBuilder().
+ WithRegion(r).
+ WithCredential(auth).
+ WithHttpConfig(httpConfig).
+ SafeBuild()
+
+ if err != nil {
+ return nil, err
+ }
+
+ return lts.NewLtsClient(hcHTTPConfig), nil
+}
+
+func (rcvr *logsReceiver) pollLogsAndConsume(ctx context.Context) error {
+ if rcvr.client == nil {
+ return errors.New("invalid client")
+ }
+ to := time.Now()
+ from := rcvr.lastTs
+ if from.IsZero() {
+ from = to.Add(-1 * rcvr.config.CollectionInterval)
+ }
+ logs, err := rcvr.listLogs(ctx, from, to)
+ if err != nil {
+ return err
+ }
+ config := rcvr.config
+ otpLogs := internal.ConvertLTSLogsToOTLP(config.ProjectID, config.RegionID, config.GroupID, config.StreamID, logs)
+ if err := rcvr.nextConsumer.ConsumeLogs(ctx, otpLogs); err != nil {
+ return err
+ }
+ rcvr.lastTs = to
+ return nil
+}
+
+func (rcvr *logsReceiver) listLogs(ctx context.Context, from, to time.Time) ([]model.LogContents, error) {
+ backOffConfig := rcvr.config.BackOffConfig
+ // TODO: Add pagination logic. Check IsQueryComplete field
+ response, err := internal.MakeAPICallWithRetry(
+ ctx,
+ rcvr.shutdownChan,
+ rcvr.logger,
+ func() (*model.ListLogsResponse, error) {
+ return rcvr.client.ListLogs(&model.ListLogsRequest{
+ LogGroupId: "test",
+ LogStreamId: "test",
+ Body: &model.QueryLtsLogParams{
+ StartTime: from.UTC().String(),
+ EndTime: to.UTC().String(),
+ },
+ })
+ },
+ func(err error) bool { return strings.Contains(err.Error(), requestThrottledErrMsg) },
+ &backoff.ExponentialBackOff{
+ InitialInterval: backOffConfig.InitialInterval,
+ RandomizationFactor: backOffConfig.RandomizationFactor,
+ Multiplier: backOffConfig.Multiplier,
+ MaxInterval: backOffConfig.MaxInterval,
+ MaxElapsedTime: backOffConfig.MaxElapsedTime,
+ Stop: backoff.Stop,
+ Clock: backoff.SystemClock,
+ },
+ )
+ if err != nil {
+ return []model.LogContents{}, err
+ }
+ if response == nil || response.Logs == nil || len((*response.Logs)) == 0 {
+ return []model.LogContents{}, errors.New("unexpected empty list of logs")
+ }
+
+ return *response.Logs, nil
+}
+
+func (rcvr *logsReceiver) Shutdown(_ context.Context) error {
+ if rcvr.cancel != nil {
+ rcvr.cancel()
+ }
+ rcvr.shutdownChan <- struct{}{}
+ close(rcvr.shutdownChan)
+ return nil
+}
diff --git a/receiver/huaweicloudlogsreceiver/receiver_test.go b/receiver/huaweicloudlogsreceiver/receiver_test.go
new file mode 100644
index 000000000000..d269d718a87f
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/receiver_test.go
@@ -0,0 +1,136 @@
+// Copyright The OpenTelemetry Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package huaweicloudlogsreceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver"
+
+import (
+ "context"
+ "errors"
+ "testing"
+ "time"
+
+ "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/lts/v2/model"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/collector/consumer/consumertest"
+ "go.opentelemetry.io/collector/receiver/receivertest"
+ "go.opentelemetry.io/collector/receiver/scraperhelper"
+
+ "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/huaweicloudlogsreceiver/internal/mocks"
+)
+
+func stringPtr(s string) *string {
+ return &s
+}
+
+func TestNewReceiver(t *testing.T) {
+ cfg := &Config{
+ ControllerConfig: scraperhelper.ControllerConfig{
+ CollectionInterval: 1 * time.Second,
+ },
+ }
+ mr := newHuaweiCloudLogsReceiver(receivertest.NewNopSettings(), cfg, new(consumertest.LogsSink))
+ assert.NotNil(t, mr)
+}
+
+func TestListLogsSuccess(t *testing.T) {
+ mc := mocks.NewLtsClient(t)
+
+ mc.On("ListLogs", mock.Anything).Return(&model.ListLogsResponse{
+ Logs: &[]model.LogContents{
+ {
+ Content: stringPtr("2020-07-25/14:40:43 this log is Error NO 2"),
+ LineNum: stringPtr("10"),
+ Labels: map[string]string{
+ "hostName": "ecs-kwxtest",
+ "hostIP": "192.168.0.156",
+ "appName": "default_appname",
+ "containerName": "CONFIG_FILE",
+ "clusterName": "CONFIG_FILE",
+ "hostId": "9787ef31-fd7b-4eff-ba71-72d580f11f55",
+ "podName": "default_procname",
+ "clusterId": "CONFIG_FILE",
+ "nameSpace": "CONFIG_FILE",
+ "category": "LTS",
+ },
+ },
+ {
+ Content: stringPtr("2020-07-26/15:00:43 this log is Error NO 3"),
+ },
+ },
+ }, nil)
+
+ receiver := &logsReceiver{
+ client: mc,
+ config: createDefaultConfig().(*Config),
+ }
+
+ logs, err := receiver.listLogs(context.Background(), time.Now(), time.Now())
+
+ assert.NoError(t, err)
+ assert.Len(t, logs, 2)
+ mc.AssertExpectations(t)
+}
+
+func TestListLogsFailure(t *testing.T) {
+ mc := mocks.NewLtsClient(t)
+
+ mc.On("ListLogs", mock.Anything).Return(nil, errors.New("failed to list logs"))
+ receiver := &logsReceiver{
+ client: mc,
+ config: createDefaultConfig().(*Config),
+ }
+
+ logs, err := receiver.listLogs(context.Background(), time.Now(), time.Now())
+
+ assert.Error(t, err)
+ assert.Len(t, logs, 0)
+ assert.Equal(t, "failed to list logs", err.Error())
+ mc.AssertExpectations(t)
+}
+
+func TestPollLogsAndConsumeSuccess(t *testing.T) {
+ mc := mocks.NewLtsClient(t)
+ next := new(consumertest.LogsSink)
+ receiver := newHuaweiCloudLogsReceiver(receivertest.NewNopSettings(), &Config{}, next)
+ receiver.client = mc
+
+ mc.On("ListLogs", mock.Anything).Return(&model.ListLogsResponse{
+ Logs: &[]model.LogContents{
+ {
+ Content: stringPtr("2020-07-25/14:40:43 this log is Error NO 2"),
+ },
+ {
+ Content: stringPtr("2020-07-26/15:00:43 this log is Error NO 3"),
+ },
+ },
+ }, nil)
+
+ err := receiver.pollLogsAndConsume(context.Background())
+
+ require.NoError(t, err)
+ assert.Equal(t, 2, next.LogRecordCount())
+}
+
+func TestCreateHTTPConfigNoVerifySSL(t *testing.T) {
+ cfg, err := createHTTPConfig(HuaweiSessionConfig{NoVerifySSL: true})
+ require.NoError(t, err)
+ assert.Equal(t, cfg.IgnoreSSLVerification, true)
+}
+
+func TestCreateHTTPConfigWithProxy(t *testing.T) {
+ cfg, err := createHTTPConfig(HuaweiSessionConfig{
+ ProxyAddress: "https://127.0.0.1:8888",
+ ProxyUser: "admin",
+ ProxyPassword: "pass",
+ AccessKey: "123",
+ SecretKey: "secret",
+ })
+ require.NoError(t, err)
+ assert.Equal(t, cfg.HttpProxy.Schema, "https")
+ assert.Equal(t, cfg.HttpProxy.Host, "127.0.0.1")
+ assert.Equal(t, cfg.HttpProxy.Port, 8888)
+ assert.Equal(t, cfg.IgnoreSSLVerification, false)
+
+}
diff --git a/receiver/huaweicloudlogsreceiver/testdata/golden/logs_golden.yaml b/receiver/huaweicloudlogsreceiver/testdata/golden/logs_golden.yaml
new file mode 100644
index 000000000000..b1fc9fee3056
--- /dev/null
+++ b/receiver/huaweicloudlogsreceiver/testdata/golden/logs_golden.yaml
@@ -0,0 +1,29 @@
+resourceLogs:
+ - resource:
+ attributes:
+ - key: cloud.provider
+ value:
+ stringValue: huawei_cloud
+ - key: project.id
+ value:
+ stringValue: my-project
+ - key: region.id
+ value:
+ stringValue: us-east-2
+ - key: group.id
+ value:
+ stringValue: group-1
+ - key: stream.id
+ value:
+ stringValue: stream-1
+ scopeLogs:
+ - scope:
+ name: huawei_cloud_lts
+ version: v2
+ - logRecords:
+ - body: 2020-07-25/14:40:00 this log is Error NO 2
+ attrivutes:
+ - key: hostName
+ value:
+ stringValue: ecs-kwxtest
+ observedTimestamp: 2020-07-25/14:40:00