diff --git a/slzebriumexporter/Makefile b/slzebriumexporter/Makefile new file mode 100644 index 0000000..39734bf --- /dev/null +++ b/slzebriumexporter/Makefile @@ -0,0 +1 @@ +include ../Makefile.Common diff --git a/slzebriumexporter/README.md b/slzebriumexporter/README.md new file mode 100644 index 0000000..a7edf0a --- /dev/null +++ b/slzebriumexporter/README.md @@ -0,0 +1,92 @@ +# ScienceLogic Zebrium Exporter + +| Status | | +| ------------------------ |-----------| +| Stability | [beta] | +| Supported pipeline types | logs | +| Distributions | [contrib] | + +Exports data via HTTP to Zebrium + +## Getting Started + +The following settings are required: + +- `endpoint` (no default): The target URL to send Zebrium log streams to (e.g.: `https://cloud.zebrium.com`). +- `ze_token` (no default): Authorization token for the Zebrium deployment + +Optional settings: + +- `verbosity` (default 'normal'): Use 'detailed' to log all incoming log records + +The ScienceLogic Zebrium Exporter relies on the [ScienceLogic Log Format Processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/processor/sllogformatprocessor/README.md) +to populate the following attributes: + +- `sl_metadata`: Resource attribute with encoded metadata that identifies logs from a single application instance +- `sl_msg`: Log record attribute with the body of the log message formated for ScienceLogic +- `sl_format`: Format option from the matching profile, e.g. event + +The following example shows how to configure the format processor and exporter in a pipeline: + +```yaml +receivers: + windowseventlog: + channel: application + start_at: end + +processors: + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # windows event log + exp: + source: lit:default + rename: ze_deployment_name + host: + exp: + source: body:computer + rename: host + logbasename: + exp: + op: lc + exps: + - op: alphanum + exps: + - op: rmprefix + exps: + - source: body:provider.name + - source: lit:Microsoft-Windows- + rename: logbasename + message: + exp: + op: or + exps: + - source: body:message + - source: body:event_data + - source: body:keywords + format: event + +exporters: + slzebrium: + verbosity: detailed + endpoint: https://cloud.zebrium.com + ze_token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + +service: + pipelines: + logs: + receivers: [windowseventlog] + processors: [sllogformat] + exporters: [slzebrium] +``` + +## Advanced Configuration + +Several helper files are leveraged to provide additional capabilities automatically: + +- [HTTP settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/config/confighttp/README.md) +- [Queuing and retry settings](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md) + +[beta]:https://github.com/open-telemetry/opentelemetry-collector#beta +[contrib]:https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib diff --git a/slzebriumexporter/config.go b/slzebriumexporter/config.go new file mode 100644 index 0000000..776185f --- /dev/null +++ b/slzebriumexporter/config.go @@ -0,0 +1,104 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import ( + "encoding/hex" + "errors" + "fmt" + "net/url" + + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +var ( + // supportedLevels in this exporter's configuration. + // configtelemetry.LevelNone and other future values are not supported. + supportedLevels map[configtelemetry.Level]struct{} = map[configtelemetry.Level]struct{}{ + configtelemetry.LevelBasic: {}, + configtelemetry.LevelNormal: {}, + configtelemetry.LevelDetailed: {}, + } +) + +// Config defines configuration for slzebrium exporter. +type Config struct { + confighttp.HTTPClientSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. + exporterhelper.QueueSettings `mapstructure:"sending_queue"` + exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` + + // Verbosity defines the zebrium exporter verbosity. + Verbosity configtelemetry.Level `mapstructure:"verbosity"` + + // ZeUrl Zebrium ZAPI authentication token + ZeToken string `mapstructure:"ze_token"` +} + +var _ component.Config = (*Config)(nil) +var _ confmap.Unmarshaler = (*Config)(nil) + +func mapLevel(level zapcore.Level) (configtelemetry.Level, error) { + switch level { + case zapcore.DebugLevel: + return configtelemetry.LevelDetailed, nil + case zapcore.InfoLevel: + return configtelemetry.LevelNormal, nil + case zapcore.WarnLevel, zapcore.ErrorLevel, + zapcore.DPanicLevel, zapcore.PanicLevel, zapcore.FatalLevel: + // Anything above info is mapped to 'basic' level. + return configtelemetry.LevelBasic, nil + default: + return configtelemetry.LevelNone, fmt.Errorf("log level %q is not supported", level) + } +} + +func (cfg *Config) Unmarshal(conf *confmap.Conf) error { + if err := conf.Unmarshal(cfg, confmap.WithErrorUnused()); err != nil { + return err + } + return nil +} + +func validateZeToken(token string) error { + if len(token) != 40 { + return errors.New("must be 40 hex characters") + } + dst := make([]byte, hex.DecodedLen(len(token))) + _, err := hex.Decode(dst, []byte(token)) + return err +} + +// Validate checks if the exporter configuration is valid +func (cfg *Config) Validate() error { + if err := cfg.QueueSettings.Validate(); err != nil { + return fmt.Errorf("queue settings has invalid configuration: %w", err) + } + if _, err := url.ParseRequestURI(cfg.Endpoint); cfg.Endpoint == "" || err != nil { + return fmt.Errorf("\"endpoint\" must be a valid URL") + } + if _, ok := supportedLevels[cfg.Verbosity]; !ok { + return fmt.Errorf("verbosity level %q is not supported", cfg.Verbosity) + } + if err := validateZeToken(cfg.ZeToken); err != nil { + return fmt.Errorf("ze_token invalid: %s", err.Error()) + } + return nil +} diff --git a/slzebriumexporter/config_test.go b/slzebriumexporter/config_test.go new file mode 100644 index 0000000..c1c595c --- /dev/null +++ b/slzebriumexporter/config_test.go @@ -0,0 +1,235 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configopaque" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +func TestUnmarshalDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NoError(t, component.UnmarshalConfig(confmap.New(), cfg)) + assert.Equal(t, factory.CreateDefaultConfig(), cfg) +} + +func TestUnmarshalConfig(t *testing.T) { + tests := []struct { + filename string + cfg *Config + expectedErr string + }{ + { + filename: "config_verbosity.yaml", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "0000000000000000000000000000000000000000", + }, + }, + { + filename: "invalid_verbosity_loglevel.yaml", + expectedErr: "1 error(s) decoding:\n\n* '' has invalid keys: loglevel", + }, + } + + for _, tt := range tests { + t.Run(tt.filename, func(t *testing.T) { + cm, err := confmaptest.LoadConf(filepath.Join("testdata", tt.filename)) + require.NoError(t, err) + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + err = component.UnmarshalConfig(cm, cfg) + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.cfg, cfg) + } + }) + } +} + +func TestValidate(t *testing.T) { + tests := []struct { + name string + cfg *Config + expectedErr string + }{ + { + name: "invalid queue settings", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 0, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "0000000000000000000000000000000000000000", + }, + expectedErr: "queue settings has invalid configuration: queue size must be positive", + }, + { + name: "invalid endpoint", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "bad//url?", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "0000000000000000000000000000000000000000", + }, + expectedErr: "\"endpoint\" must be a valid URL", + }, + { + name: "invalid verbosity", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelNone, + ZeToken: "0000000000000000000000000000000000000000", + }, + expectedErr: "verbosity level \"None\" is not supported", + }, + { + name: "invalid ze_token", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "z000000000000000000000000000000000000000", + }, + expectedErr: "ze_token invalid: encoding/hex: invalid byte: U+007A 'z'", + }, + { + name: "config valid", + cfg: &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "0000000000000000000000000000000000000000", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/slzebriumexporter/doc.go b/slzebriumexporter/doc.go new file mode 100644 index 0000000..15d3b75 --- /dev/null +++ b/slzebriumexporter/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package slzebriumexporter exports data to console as logs. +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" diff --git a/slzebriumexporter/examples/otelcol_azure.yaml b/slzebriumexporter/examples/otelcol_azure.yaml new file mode 100644 index 0000000..6578a97 --- /dev/null +++ b/slzebriumexporter/examples/otelcol_azure.yaml @@ -0,0 +1,47 @@ +receivers: + azureeventhub: + connection: Endpoint=sb://alannamespace.servicebus.windows.net/;SharedAccessKeyName=Listener;SharedAccessKey=OxiXco3aMe9aJ7T9i6wfYPqqbX3HWno3Z+AEhIyiv5A=;EntityPath=alaneventhub + format: azure + +processors: + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # azure audit log + exp: + source: lit:default + rename: ze_deployment_name + host: + exp: + source: attr:azure.tenant.id + rename: host + logbasename: + exp: + op: lc + exps: + - source: attr:azure.category + rename: logbasename + message: + exp: + op: and + exps: + - op: replace + exps: + - source: attr:azure.operation.name + - source: "lit:/" + - source: "lit: " + - source: attr:azure.result.type + - source: attr:azure.properties.entity + format: event + +exporters: + logging: + verbosity: detailed + +service: + pipelines: + logs: + receivers: [azureeventhub] + processors: [sllogformat] + exporters: [logging] diff --git a/slzebriumexporter/examples/otelcol_cloudwatch.yaml b/slzebriumexporter/examples/otelcol_cloudwatch.yaml new file mode 100644 index 0000000..cc11224 --- /dev/null +++ b/slzebriumexporter/examples/otelcol_cloudwatch.yaml @@ -0,0 +1,53 @@ +receivers: + awscloudwatch: + region: us-west-2 + logs: + poll_interval: 10s + +processors: + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # cloudwatch + exp: + source: rattr:cloudwatch.log.group.name + rename: ze_deployment_name + host: + exp: + source: rattr:aws.region + rename: host + logbasename: + exp: + op: lc + exps: + - op: alphanum + exps: + - op: rmprefix + exps: + - op: rmtail + exps: + - source: rattr:cloudwatch.log.stream + - source: lit:- + - source: lit:kube-apiserver- + rename: logbasename + severity: + exp: + source: body:responseStatus.code + message: + exp: + source: body + format: event + +exporters: + slzebrium: + verbosity: detailed + endpoint: https://cloud.zebrium.com + ze_token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + +service: + pipelines: + logs: + receivers: [awscloudwatch] + processors: [sllogformat] + exporters: [slzebrium] diff --git a/slzebriumexporter/examples/otelcol_docker.yaml b/slzebriumexporter/examples/otelcol_docker.yaml new file mode 100644 index 0000000..90e43da --- /dev/null +++ b/slzebriumexporter/examples/otelcol_docker.yaml @@ -0,0 +1,79 @@ +receivers: + filelog/containers: + include: [ "/var/lib/docker/containers/*/*.log" ] + start_at: end + include_file_path: true + include_file_name: false + operators: + - type: json_parser + id: parser-docker + output: extract_metadata_from_filepath + timestamp: + parse_from: attributes.time + layout: '%Y-%m-%dT%H:%M:%S.%LZ' + # Extract metadata from file path + - type: regex_parser + id: extract_metadata_from_filepath + regex: '^.*containers/(?P[^_]+)/.*log$' + parse_from: attributes["log.file.path"] + output: parse_body + - type: move + id: parse_body + from: attributes.log + to: body + output: add_source + - type: add + id: add_source + field: resource["source"] + value: "docker" + - type: remove + id: time + field: attributes.time + +processors: + resourcedetection/system: + detectors: ["system"] + system: + hostname_sources: ["os"] + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # docker logs + exp: + source: lit:default + rename: ze_deployment_name + host: + exp: + source: rattr:host.name + rename: host + logbasename: + exp: + source: attr:container_id + rename: logbasename + labels: + - exp: + source: rattr:os.type + - exp: + source: attr:log.file.path + rename: zid_path + message: + exp: + op: and + exps: + - source: body:timestamp + - source: body:log + format: container + +exporters: + slzebrium: + verbosity: detailed + endpoint: https://cloud.zebrium.com + ze_token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + +service: + pipelines: + logs: + receivers: [filelog/containers] + processors: [resourcedetection/system,sllogformat] + exporters: [slzebrium] diff --git a/slzebriumexporter/examples/otelcol_k8s_configmap.yaml b/slzebriumexporter/examples/otelcol_k8s_configmap.yaml new file mode 100644 index 0000000..dc324cc --- /dev/null +++ b/slzebriumexporter/examples/otelcol_k8s_configmap.yaml @@ -0,0 +1,239 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: otel-collector +rules: +- apiGroups: [""] + resources: ["pods", "namespaces"] + verbs: ["get", "watch", "list"] +- apiGroups: ["events.k8s.io"] + resources: ["events"] + verbs: ["watch"] +- apiGroups: ["apps"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] +- apiGroups: ["extensions"] + resources: ["replicasets"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: otel-collector +subjects: +- kind: ServiceAccount + name: default + namespace: default +roleRef: + kind: ClusterRole + name: otel-collector + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: otel-collector-config +data: + config.yaml: |- + receivers: + k8sobjects: + objects: + - name: events + mode: watch + group: events.k8s.io + filelog: + include: + - /var/log/pods/*/*/*.log + exclude: + # Exclude logs from all containers named otel-collector + - /var/log/pods/*/otel-collector/*.log + start_at: beginning + include_file_path: true + include_file_name: false + operators: + # Parse Docker format + - type: json_parser + id: parser-docker + output: extract_metadata_from_filepath + timestamp: + parse_from: attributes.time + layout: '%Y-%m-%dT%H:%M:%S.%LZ' + - type: move + from: attributes.log + to: body + # Extract metadata from file path + - type: regex_parser + id: extract_metadata_from_filepath + regex: '^.*\/(?P[^_]+)_(?P[^_]+)_(?P[a-f0-9\-]+)\/(?P[^\._]+)\/(?P\d+)\.log$' + parse_from: attributes["log.file.path"] + cache: + size: 128 # default maximum amount of Pods per Node is 110 + # Rename attributes + - type: move + from: attributes.stream + to: attributes["log.iostream"] + - type: move + from: attributes.container_name + to: resource["k8s.container.name"] + - type: move + from: attributes.namespace + to: resource["k8s.namespace.name"] + - type: move + from: attributes.pod_name + to: resource["k8s.pod.name"] + - type: move + from: attributes.restart_count + to: resource["k8s.container.restart_count"] + - type: move + from: attributes.uid + to: resource["k8s.pod.uid"] + + processors: + # k8sattributes processor to get the metadata from K8s + k8sattributes: + auth_type: "serviceAccount" + passthrough: false + extract: + metadata: + - k8s.pod.name + - k8s.pod.uid + - k8s.deployment.name + - k8s.namespace.name + - k8s.node.name + - k8s.pod.start_time + # Pod association using resource attributes and connection + pod_association: + - sources: + - from: resource_attribute + name: k8s.pod.uid + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # kubernetes log + exp: + source: rattr:k8s.namespace.name + rename: ze_deployment_name + host: + exp: + source: rattr:k8s.node.name:host + rename: host + logbasename: + exp: + source: rattr:k8s.container.name + rename: logbasename + message: + exp: + op: and + exps: + - source: body:timestamp + - source: body:log + format: container + - service_group: # kubernetes events + exp: + source: body:object.metadata.namespace + rename: ze_deployment_name + host: + exp: + op: or + exps: + - source: body:deprecatedSource.host + - source: lit:minikube + source: + rename: host + logbasename: + exp: + op: lc + exps: + - op: alphanum + exps: + - source: body:object.deprecatedSource.component + rename: logbasename + severity: + exp: + source: body:object.type + message: + exp: + source: body:object + format: event + + exporters: + slzebrium: + ## Replace with creditials for your Zebrium deployment + endpoint: https://cloud.zebrium.com + ze_token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + verbosity: detailed + + service: + pipelines: + logs: + receivers: [filelog,k8sobjects] + processors: [k8sattributes,sllogformat] + exporters: [slzebrium] + +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: otel-collector + labels: + app: opentelemetry + component: otel-collector +spec: + selector: + matchLabels: + app: opentelemetry + component: otel-collector + template: + metadata: + labels: + app: opentelemetry + component: otel-collector + spec: + containers: + - name: otel-collector + image: 872295030327.dkr.ecr.us-west-2.amazonaws.com/zebrium-otel-collector:0.0.0-8c2598a + resources: + limits: + cpu: 100m + memory: 200Mi + requests: + cpu: 100m + memory: 200Mi + volumeMounts: + - mountPath: /var/log + name: varlog + readOnly: true + - mountPath: /var/lib/docker/containers + name: varlibdockercontainers + readOnly: true + - mountPath: /etc/otel/config.yaml + name: data + subPath: config.yaml + readOnly: true + terminationGracePeriodSeconds: 30 + volumes: + - name: varlog + hostPath: + path: /var/log + - name: varlibdockercontainers + hostPath: + path: /var/lib/docker/containers + - name: data + configMap: + name: otel-collector-config + +--- +apiVersion: v1 +kind: Service +metadata: + name: otel-collector + labels: + app: opentelemetry + component: otel-collector +spec: + ports: + - name: metrics # Default endpoint for querying metrics. + port: 8888 + selector: + component: otel-collector diff --git a/slzebriumexporter/examples/otelcol_windows.yaml b/slzebriumexporter/examples/otelcol_windows.yaml new file mode 100644 index 0000000..9b5eb9b --- /dev/null +++ b/slzebriumexporter/examples/otelcol_windows.yaml @@ -0,0 +1,57 @@ +receivers: + windowseventlog: + channel: application + start_at: end + +processors: + sllogformat: + send_batch_size: 10000 + timeout: 10s + profiles: + - service_group: # windows event log + exp: + source: lit:default + rename: ze_deployment_name + host: + exp: + source: body:computer + rename: host + logbasename: + exp: + op: lc + exps: + - op: alphanum + exps: + - op: rmprefix + exps: + - source: body:provider.name + - source: lit:Microsoft-Windows- + rename: logbasename + labels: + - exp: + source: body:channel + rename: win_channel + - exp: + source: body:keywords + rename: win_keywords + message: + exp: + op: or + exps: + - source: body:message + - source: body:event_data + - source: body:keywords + format: event + +exporters: + slzebrium: + verbosity: detailed + endpoint: https://cloud.zebrium.com + ze_token: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + +service: + pipelines: + logs: + receivers: [windowseventlog] + processors: [sllogformat] + exporters: [slzebrium] diff --git a/slzebriumexporter/factory.go b/slzebriumexporter/factory.go new file mode 100644 index 0000000..b21f58c --- /dev/null +++ b/slzebriumexporter/factory.go @@ -0,0 +1,89 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import ( + "context" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configopaque" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/consumer" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/exporter/exporterhelper" +) + +const ( + // The value of "type" key in configuration. + typeStr = "slzebrium" + defaultZeUrl = "https://cloud.zebrium.com" +) + +// NewFactory creates a factory for Logging exporter +func NewFactory() exporter.Factory { + return exporter.NewFactory( + typeStr, + createDefaultConfig, + exporter.WithLogs(createZebriumExporter, component.StabilityLevelDevelopment), + ) +} + +func createDefaultConfig() component.Config { + return &Config{ + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: defaultZeUrl, + Timeout: 30 * time.Second, + Headers: map[string]configopaque.String{}, + // We almost read 0 bytes, so no need to tune ReadBufferSize. + WriteBufferSize: 512 * 1024, + }, + RetrySettings: exporterhelper.NewDefaultRetrySettings(), + QueueSettings: exporterhelper.NewDefaultQueueSettings(), + Verbosity: configtelemetry.LevelNormal, + } +} + +func createZebriumExporter(ctx context.Context, set exporter.CreateSettings, config component.Config) (exporter.Logs, error) { + cfg := config.(*Config) + exporterLogger := createLogger(cfg, set.TelemetrySettings.Logger) + s := newLoggingExporter(exporterLogger, cfg) + return exporterhelper.NewLogsExporter(ctx, set, cfg, + s.pushLogs, + exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}), + // Disable Timeout/RetryOnFailure and SendingQueue + exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}), + exporterhelper.WithRetry(cfg.RetrySettings), + exporterhelper.WithQueue(cfg.QueueSettings), + exporterhelper.WithStart(s.start), + exporterhelper.WithShutdown(loggerSync(exporterLogger)), + ) +} + +func createLogger(cfg *Config, logger *zap.Logger) *zap.Logger { + + core := zapcore.NewSamplerWithOptions( + logger.Core(), + 1*time.Second, + 2, + 500, + ) + + return zap.New(core) +} diff --git a/slzebriumexporter/factory_test.go b/slzebriumexporter/factory_test.go new file mode 100644 index 0000000..29a6803 --- /dev/null +++ b/slzebriumexporter/factory_test.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/exporter/exportertest" +) + +func TestCreateDefaultConfig(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + assert.NotNil(t, cfg, "failed to create default config") + assert.NoError(t, componenttest.CheckConfigStruct(cfg)) +} + +func TestCreateZebriumExporter(t *testing.T) { + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + + te, err := factory.CreateLogsExporter(context.Background(), exportertest.NewNopCreateSettings(), cfg) + assert.NoError(t, err) + assert.NotNil(t, te) +} diff --git a/slzebriumexporter/go.mod b/slzebriumexporter/go.mod new file mode 100644 index 0000000..6825a24 --- /dev/null +++ b/slzebriumexporter/go.mod @@ -0,0 +1,63 @@ +module github.sciencelogic.com/product-engineering/ze-otel/slzebriumexporter + +go 1.21 + +require ( + github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/collector/component v0.91.0 + go.opentelemetry.io/collector/config/confighttp v0.91.0 + go.opentelemetry.io/collector/config/configopaque v0.91.0 + go.opentelemetry.io/collector/config/configtelemetry v0.91.0 + go.opentelemetry.io/collector/confmap v0.91.0 + go.opentelemetry.io/collector/consumer v0.91.0 + go.opentelemetry.io/collector/exporter v0.91.0 + go.opentelemetry.io/collector/pdata v1.0.0 + go.uber.org/zap v1.26.0 + golang.org/x/sys v0.15.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.2.1 // 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.3.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.10.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/collector v0.91.0 // indirect + go.opentelemetry.io/collector/config/configauth v0.91.0 // indirect + go.opentelemetry.io/collector/config/configcompression v0.91.0 // indirect + go.opentelemetry.io/collector/config/configtls v0.91.0 // indirect + go.opentelemetry.io/collector/config/internal v0.91.0 // indirect + go.opentelemetry.io/collector/extension v0.91.0 // indirect + go.opentelemetry.io/collector/extension/auth v0.91.0 // indirect + go.opentelemetry.io/collector/featuregate v1.0.0 // indirect + go.opentelemetry.io/collector/receiver v0.91.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect + go.opentelemetry.io/otel v1.21.0 // indirect + go.opentelemetry.io/otel/metric v1.21.0 // indirect + go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/slzebriumexporter/go.sum b/slzebriumexporter/go.sum new file mode 100644 index 0000000..b6c1fac --- /dev/null +++ b/slzebriumexporter/go.sum @@ -0,0 +1,265 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= +contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/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-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/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/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/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/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.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +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.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.0.1 h1:1dYGITt1I23x8cfx8ZnldtezdyaZtfAuRtIFOiRzK7g= +github.com/knadh/koanf/v2 v2.0.1/go.mod h1:ZeiIlIDXTE7w1lMT6UVcNiRAS2/rCeLn/GdLNvY1Dus= +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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY= +github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/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.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= +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.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/collector v0.91.0 h1:C7sGUJDJ5nwm+CkWpAaVP3lNsuYpwSRbkmLncFjkmO8= +go.opentelemetry.io/collector v0.91.0/go.mod h1:YhQpIDZsn+bICAAqgBwXk9wqK8GKZDv+aogfG52zUuE= +go.opentelemetry.io/collector/component v0.91.0 h1:aBT1i2zGyfh9PalYJLfXVvQp+osHyalwyDFselI1CtA= +go.opentelemetry.io/collector/component v0.91.0/go.mod h1:2KBHvjNFdU7oOjsObQeC4Ta2Ef607OISU5obznW00fw= +go.opentelemetry.io/collector/config/configauth v0.91.0 h1:SjWKimuqlpfS3sIlFpfzdkSY/AmMMCEmn9+KRcjEU+s= +go.opentelemetry.io/collector/config/configauth v0.91.0/go.mod h1:wmmMYqv6PxwY+/h7qqvd/LP0XN/wzXoECDu6PYz2Of0= +go.opentelemetry.io/collector/config/configcompression v0.91.0 h1:v+jEpFhLgfJDCUCPsSF03gjoFEvm77PofTCqHKKgXTs= +go.opentelemetry.io/collector/config/configcompression v0.91.0/go.mod h1:LaavoxZsro5lL7qh1g9DMifG0qixWPEecW18Qr8bpag= +go.opentelemetry.io/collector/config/confighttp v0.91.0 h1:YAOyXcDaLDnF3UqPHH4kYU8lx8BqXJ7hS3Ou8GcmqpQ= +go.opentelemetry.io/collector/config/confighttp v0.91.0/go.mod h1:R6y8KSJzqDe6CE6JsYwt4CTZ2B4AlqRA+V74OJPX3vE= +go.opentelemetry.io/collector/config/configopaque v0.91.0 h1:bQgJPyARbuXAsU2p6h2YbEm1kHb1stS6hg42ekyMZmI= +go.opentelemetry.io/collector/config/configopaque v0.91.0/go.mod h1:TPCHaU+QXiEV+JXbgyr6mSErTI9chwQyasDVMdJr3eY= +go.opentelemetry.io/collector/config/configtelemetry v0.91.0 h1:mEwvqrYfwUJ7LwYfpcF9M8z7LHFoYaKhEPhnERD/88E= +go.opentelemetry.io/collector/config/configtelemetry v0.91.0/go.mod h1:+LAXM5WFMW/UbTlAuSs6L/W72WC+q8TBJt/6z39FPOU= +go.opentelemetry.io/collector/config/configtls v0.91.0 h1:lZromNeOslPwyVlTPMOzF2q++SY+VONvfH3cDqA0kKk= +go.opentelemetry.io/collector/config/configtls v0.91.0/go.mod h1:E+CW5gZoH8V3z5aSlZxwiof7GAcayzn1HRM+uRILLEI= +go.opentelemetry.io/collector/config/internal v0.91.0 h1:Yx17oFdXOPnY83Jfe1oiXhvfYW7RX/xh3/kpV/iYibM= +go.opentelemetry.io/collector/config/internal v0.91.0/go.mod h1:42VsQ/1kP2qnvzjNi+dfNP+KyCFRADejyrJ8m2GVL3M= +go.opentelemetry.io/collector/confmap v0.91.0 h1:7U2MT+u74oEzq/WWrpXSLKB7nX5jPNC4drwtQdYfwKk= +go.opentelemetry.io/collector/confmap v0.91.0/go.mod h1:uxV+fZ85kG31oovL6Cl3fAMQ3RRPwUvfAbbA9WT1Yhk= +go.opentelemetry.io/collector/consumer v0.91.0 h1:0nU1lUe2S0b8iOmF3w3R/9Dt24n413thRTbXz/nJgrM= +go.opentelemetry.io/collector/consumer v0.91.0/go.mod h1:phTUQmr7hpYfwXyDXo4mFHVjYrlSbZE+nZYlKlbVxGs= +go.opentelemetry.io/collector/exporter v0.91.0 h1:guWcGflFjaenp3BMxAmAKjb8RQG80jQQKjuUFouS+z8= +go.opentelemetry.io/collector/exporter v0.91.0/go.mod h1:hkOBunNNWu6CaTtkRsCJ/OJ509REJZg+DDElevFIQCQ= +go.opentelemetry.io/collector/extension v0.91.0 h1:bkoSLgnWm4g6n+RLmyKG6Up7dr8KmJy68quonoLZnr0= +go.opentelemetry.io/collector/extension v0.91.0/go.mod h1:F3r0fVTTh4sYR0GVv51Qez8lk8v77kTDPdyMOp6A2kg= +go.opentelemetry.io/collector/extension/auth v0.91.0 h1:28Hv5W0GZgv2jR5IiFdJzutTs91KmXFh8DUfVTjwwmI= +go.opentelemetry.io/collector/extension/auth v0.91.0/go.mod h1:diY6Sw7cOAn2qivKipZk4niBFzCCFBj7swAXiG2h9ro= +go.opentelemetry.io/collector/featuregate v1.0.0 h1:5MGqe2v5zxaoo73BUOvUTunftX5J8RGrbFsC2Ha7N3g= +go.opentelemetry.io/collector/featuregate v1.0.0/go.mod h1:xGbRuw+GbutRtVVSEy3YR2yuOlEyiUMhN2M9DJljgqY= +go.opentelemetry.io/collector/pdata v1.0.0 h1:ECP2jnLztewsHmL1opL8BeMtWVc7/oSlKNhfY9jP8ec= +go.opentelemetry.io/collector/pdata v1.0.0/go.mod h1:TsDFgs4JLNG7t6x9D8kGswXUz4mme+MyNChHx8zSF6k= +go.opentelemetry.io/collector/receiver v0.91.0 h1:0TZF/0OXoJtxgm+mvOinRRXo9LgVyOsOgCQfWkNGXJA= +go.opentelemetry.io/collector/receiver v0.91.0/go.mod h1:d5qo2mpovqKoi47hrMxj5BLdLzOXM0mUHL5CKrjfWNM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2 h1:TnhkxGJ5qPHAMIMI4r+HPT/BbpoHxqn4xONJrok054o= +go.opentelemetry.io/otel/exporters/prometheus v0.44.1-0.20231201153405-6027c1ae76f2/go.mod h1:ERL2uIeBtg4TxZdojHUwzZfIFlUIjZtxubT5p4h1Gjg= +go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= +go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= +go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/slzebriumexporter/internal/testdata/common.go b/slzebriumexporter/internal/testdata/common.go new file mode 100644 index 0000000..b62cf24 --- /dev/null +++ b/slzebriumexporter/internal/testdata/common.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testdata + +import ( + "go.opentelemetry.io/collector/pdata/pcommon" +) + +func initMetricExemplarAttributes(dest pcommon.Map) { + dest.PutStr("exemplar-attachment", "exemplar-attachment-value") +} + +func initMetricAttributes1(dest pcommon.Map) { + dest.PutStr("label-1", "label-value-1") +} + +func initMetricAttributes2(dest pcommon.Map) { + dest.PutStr("label-2", "label-value-2") +} + +func initMetricAttributes12(dest pcommon.Map) { + initMetricAttributes1(dest) + initMetricAttributes2(dest) +} + +func initMetricAttributes13(dest pcommon.Map) { + initMetricAttributes1(dest) + dest.PutStr("label-3", "label-value-3") +} diff --git a/slzebriumexporter/internal/testdata/log.go b/slzebriumexporter/internal/testdata/log.go new file mode 100644 index 0000000..5d2a0a1 --- /dev/null +++ b/slzebriumexporter/internal/testdata/log.go @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testdata + +import ( + "time" + + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" +) + +var ( + logTimestamp = pcommon.NewTimestampFromTime(time.Date(2020, 2, 11, 20, 26, 13, 789, time.UTC)) +) + +func GenerateLogs(count int) plog.Logs { + ld := plog.NewLogs() + initResource(ld.ResourceLogs().AppendEmpty().Resource()) + logs := ld.ResourceLogs().At(0).ScopeLogs().AppendEmpty().LogRecords() + logs.EnsureCapacity(count) + for i := 0; i < count; i++ { + switch i % 2 { + case 0: + fillLogOne(logs.AppendEmpty()) + case 1: + fillLogTwo(logs.AppendEmpty()) + } + } + return ld +} + +func fillLogOne(log plog.LogRecord) { + log.SetTimestamp(logTimestamp) + log.SetDroppedAttributesCount(1) + log.SetSeverityNumber(plog.SeverityNumberInfo) + log.SetSeverityText("Info") + log.SetSpanID([8]byte{0x01, 0x02, 0x04, 0x08}) + log.SetTraceID([16]byte{0x08, 0x04, 0x02, 0x01}) + + attrs := log.Attributes() + attrs.PutStr("app", "server") + attrs.PutInt("instance_num", 1) + + log.Body().SetStr("This is a log message") +} + +func fillLogTwo(log plog.LogRecord) { + log.SetTimestamp(logTimestamp) + log.SetDroppedAttributesCount(1) + log.SetSeverityNumber(plog.SeverityNumberInfo) + log.SetSeverityText("Info") + + attrs := log.Attributes() + attrs.PutStr("customer", "acme") + attrs.PutStr("env", "dev") + + log.Body().SetStr("something happened") +} diff --git a/slzebriumexporter/internal/testdata/resource.go b/slzebriumexporter/internal/testdata/resource.go new file mode 100644 index 0000000..cac7a40 --- /dev/null +++ b/slzebriumexporter/internal/testdata/resource.go @@ -0,0 +1,10 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package testdata + +import "go.opentelemetry.io/collector/pdata/pcommon" + +func initResource(r pcommon.Resource) { + r.Attributes().PutStr("resource-attr", "resource-attr-val-1") +} diff --git a/slzebriumexporter/known_sync_error.go b/slzebriumexporter/known_sync_error.go new file mode 100644 index 0000000..fdb6cf0 --- /dev/null +++ b/slzebriumexporter/known_sync_error.go @@ -0,0 +1,46 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !windows +// +build !windows + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import ( + "errors" + "syscall" +) + +var knownSyncErrors = []error{ + // sync /dev/stdout: invalid argument + syscall.EINVAL, + // sync /dev/stdout: not supported + syscall.ENOTSUP, + // sync /dev/stdout: inappropriate ioctl for device + syscall.ENOTTY, + // sync /dev/stdout: bad file descriptor + syscall.EBADF, +} + +// knownSyncError returns true if the given error is one of the known +// non-actionable errors returned by Sync on Linux and macOS. +func knownSyncError(err error) bool { + for _, syncError := range knownSyncErrors { + if errors.Is(err, syncError) { + return true + } + } + + return false +} diff --git a/slzebriumexporter/known_sync_error_windows.go b/slzebriumexporter/known_sync_error_windows.go new file mode 100644 index 0000000..b90d886 --- /dev/null +++ b/slzebriumexporter/known_sync_error_windows.go @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build windows +// +build windows + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import "golang.org/x/sys/windows" + +// knownSyncError returns true if the given error is one of the known +// non-actionable errors returned by Sync on Windows: +// +// - sync /dev/stderr: The handle is invalid. +func knownSyncError(err error) bool { + return err == windows.ERROR_INVALID_HANDLE +} diff --git a/slzebriumexporter/logging_exporter.go b/slzebriumexporter/logging_exporter.go new file mode 100644 index 0000000..8316f10 --- /dev/null +++ b/slzebriumexporter/logging_exporter.go @@ -0,0 +1,145 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "strings" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/pdata/plog" +) + +type loggingExporter struct { + cfg *Config + log *zap.Logger + client *http.Client + streamTokenMap map[string]string +} + +const ( + CfgFormatMessage string = "message" + CfgFormatContainer string = "container" + CfgFormatEvent string = "event" +) + +var cfgFormatMap map[string]struct{} = map[string]struct{}{ + CfgFormatMessage: {}, + CfgFormatContainer: {}, + CfgFormatEvent: {}, +} + +func keysForMap(mymap map[string]struct{}) []string { + keys := make([]string, len(mymap)) + i := 0 + for k := range mymap { + keys[i] = k + i++ + } + return keys +} + +func validateResourceElem(idx int, name, str string, cfgMap map[string]struct{}) error { + arr := strings.Split(str, ":") + if len(arr) < 1 || len(arr[0]) < 1 { + return fmt.Errorf("resource %d missing %s", idx, name) + } + _, ok := cfgMap[arr[0]] + if !ok { + return fmt.Errorf("resource %d invalid value %s for %s, supported values %v", idx, arr[0], name, keysForMap(cfgMap)) + } + return nil +} + +func (s *loggingExporter) pushLogs(_ context.Context, ld plog.Logs) error { + s.log.Info("SLZebriumExporter", zap.Int("#logs", ld.LogRecordCount())) + + rls := ld.ResourceLogs() + for i := 0; i < rls.Len(); i++ { + rl := rls.At(i) + request, err := s.getStreamTokenRequest(rl) + if err != nil { + s.log.Error("Failed to get metadata", zap.String("err", err.Error())) + return err + } + + buffer, err := s.marshalLogs(rl) + if err != nil { + s.log.Error("Failed to marshal log messages", zap.String("err", err.Error())) + return err + } + + if s.cfg.Verbosity == configtelemetry.LevelDetailed { + s.log.Info(request) + s.log.Info(string(buffer)) + } + + var format string + val, ok := rl.Resource().Attributes().Get("sl_format") + if ok { + format = val.AsString() + } + if err := validateResourceElem(i, "sl_format", format, cfgFormatMap); err != nil { + return err + } + err = s.sendLogs(request, format, buffer) + if err != nil { + s.log.Error("Failed to send logs", zap.String("err", err.Error())) + return err + } + } + + return nil +} + +func (s *loggingExporter) start(ctx context.Context, host component.Host) error { + client, err := s.cfg.HTTPClientSettings.ToClient(host, component.TelemetrySettings{Logger: s.log}) + if err != nil { + return err + } + s.client = client + return nil +} + +func newLoggingExporter(logger *zap.Logger, cfg *Config) *loggingExporter { + return &loggingExporter{ + cfg: cfg, + log: logger, + streamTokenMap: make(map[string]string), + } +} + +func loggerSync(logger *zap.Logger) func(context.Context) error { + return func(context.Context) error { + // Currently Sync() return a different error depending on the OS. + // Since these are not actionable ignore them. + err := logger.Sync() + osErr := &os.PathError{} + if errors.As(err, &osErr) { + wrappedErr := osErr.Unwrap() + if knownSyncError(wrappedErr) { + err = nil + } + } + return err + } +} diff --git a/slzebriumexporter/logging_exporter_test.go b/slzebriumexporter/logging_exporter_test.go new file mode 100644 index 0000000..b0149ea --- /dev/null +++ b/slzebriumexporter/logging_exporter_test.go @@ -0,0 +1,73 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package slzebriumexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest" + + "github.sciencelogic.com/product-engineering/ze-otel/slzebriumexporter/internal/testdata" + + "go.opentelemetry.io/collector/config/confighttp" + "go.opentelemetry.io/collector/config/configopaque" + "go.opentelemetry.io/collector/config/configtelemetry" + "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exportertest" + "go.opentelemetry.io/collector/pdata/plog" +) + +func TestLoggingZebriumExporterNoErrors(t *testing.T) { + f := NewFactory() + lle, err := f.CreateLogsExporter(context.Background(), exportertest.NewNopCreateSettings(), f.CreateDefaultConfig()) + require.NotNil(t, lle) + assert.NoError(t, err) + + assert.NoError(t, lle.ConsumeLogs(context.Background(), plog.NewLogs())) + assert.NoError(t, lle.ConsumeLogs(context.Background(), testdata.GenerateLogs(10))) + + assert.NoError(t, lle.Shutdown(context.Background())) +} + +func TestLoggingExporterErrors(t *testing.T) { + cfg := &Config{ + RetrySettings: exporterhelper.RetrySettings{ + Enabled: true, + InitialInterval: 5000000000, + MaxInterval: 30000000000, + MaxElapsedTime: 300000000000, + }, + QueueSettings: exporterhelper.QueueSettings{ + Enabled: true, + NumConsumers: 10, + QueueSize: 5000, + }, + HTTPClientSettings: confighttp.HTTPClientSettings{ + Endpoint: "https://cloud.zebrium.com", + WriteBufferSize: 524288, + Timeout: 30000000000, + Headers: map[string]configopaque.String{}, + }, + Verbosity: configtelemetry.LevelDetailed, + ZeToken: "0000000000000000000000000000000000000000", + } + le := newLoggingExporter(zaptest.NewLogger(t), cfg) + require.NotNil(t, le) + + err := le.pushLogs(context.Background(), plog.NewLogs()) + assert.NoError(t, err) +} diff --git a/slzebriumexporter/testdata/config_verbosity.yaml b/slzebriumexporter/testdata/config_verbosity.yaml new file mode 100644 index 0000000..dc16461 --- /dev/null +++ b/slzebriumexporter/testdata/config_verbosity.yaml @@ -0,0 +1,8 @@ +verbosity: detailed +endpoint: https://cloud.zebrium.com +ze_token: "0000000000000000000000000000000000000000" +sending_queue: + queue_size: 5000 +retry_on_failure: + multiplier: 0 + randomization_factor: 0 diff --git a/slzebriumexporter/testdata/invalid_verbosity_loglevel.yaml b/slzebriumexporter/testdata/invalid_verbosity_loglevel.yaml new file mode 100644 index 0000000..37f8f16 --- /dev/null +++ b/slzebriumexporter/testdata/invalid_verbosity_loglevel.yaml @@ -0,0 +1,2 @@ +loglevel: info +verbosity: detailed diff --git a/slzebriumexporter/zebrium_exporter.go b/slzebriumexporter/zebrium_exporter.go new file mode 100644 index 0000000..921b366 --- /dev/null +++ b/slzebriumexporter/zebrium_exporter.go @@ -0,0 +1,154 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package slzebriumexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/slzebriumexporter" + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "strings" + + "go.uber.org/zap" + + "go.opentelemetry.io/collector/pdata/plog" +) + +func (s *loggingExporter) getStreamTokenRequest(rl plog.ResourceLogs) (string, error) { + var request string + val, ok := rl.Resource().Attributes().Get("sl_metadata") + if ok { + request = val.AsString() + } + if request == "" { + return "", errors.New("Missing sl_metadata resource attribute, configure log format processor") + } + return request, nil +} + +func (s *loggingExporter) getStreamToken(request string) (string, error) { + url := s.cfg.Endpoint + "/api/v2/token" + req, err := http.NewRequest("POST", url, strings.NewReader(request)) + if err != nil { + s.log.Error("Unable to get HTTP request for stream token", zap.String("err", err.Error())) + return "", err + } + req.Header.Set("Authorization", "Token "+s.cfg.ZeToken) + req.Header.Set("Content-Type", "application/json") + req.Close = true + + resp, err := s.client.Do(req) + if err != nil { + s.log.Error("HTTP error getting stream token", zap.String("err", err.Error())) + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + s.log.Error("Error getting HTTP response", zap.String("err", err.Error())) + return "", err + } + type RespToken struct { + Token string `json:"token"` + } + var respToken RespToken + err = json.Unmarshal(body, &respToken) + if err != nil { + s.log.Error("Unable to unmarshal token response", zap.String("err", err.Error()), + zap.String("body", string(body))) + return "", err + } + token := respToken.Token + if token == "" { + s.log.Error("Got empty stream token", zap.String("body", string(body))) + return "", errors.New("Got empty stream token") + } + return token, nil +} + +func (s *loggingExporter) sendLogs(request, format string, buffer []byte) error { + alreadyRetried := false + token, _ := s.streamTokenMap[request] +retry: + if token == "" { + var err error + token, err = s.getStreamToken(request) + if err != nil { + return err + } + } + url := s.cfg.Endpoint + switch format { + case CfgFormatEvent: + url += "/api/v2/tmpost" + default: + url += "/api/v2/post" + } + req, err := http.NewRequest("POST", url, bytes.NewReader(buffer)) + if err != nil { + s.log.Info("Unable to get HTTP request for stream token", zap.String("err", err.Error())) + return err + } + req.Header.Set("Authorization", "Token "+token) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Transfer-Encoding", "chunked") + req.Close = true + resp, err := s.client.Do(req) + if err != nil { + s.log.Info("HTTP error sending data", zap.String("err", err.Error())) + return err + } + if resp == nil { + err = errors.New("Response is empty") + s.log.Info("HTTP error sending data", zap.String("err", err.Error())) + } + defer resp.Body.Close() + if resp.StatusCode == 401 { + s.log.Error("Authorization error posting to Zapi", zap.String("status", resp.Status)) + if !alreadyRetried { + s.log.Info("Refresh token ...") + token = "" + alreadyRetried = true + goto retry + } + return errors.New("Bad Authorization") + } + _, err = ioutil.ReadAll(resp.Body) + if err != nil { + s.log.Info("Error getting HTTP response", zap.String("err", err.Error())) + return err + } + return nil +} + +func (s *loggingExporter) marshalLogs(rl plog.ResourceLogs) ([]byte, error) { + out := []byte{} + ills := rl.ScopeLogs() + for j := 0; j < ills.Len(); j++ { + ils := ills.At(j) + logs := ils.LogRecords() + for k := 0; k < logs.Len(); k++ { + lr := logs.At(k) + val, ok := lr.Attributes().Get("sl_msg") + msg := val.AsString() + if !ok || msg == "" { + return []byte{}, errors.New("Missing sl_msg log record attribute, configure log format processor") + } + out = append(out, (msg + "\n")...) + } + } + return out, nil +}