From 7098a623622f4af360bd93075652373d6dec1c2f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 19 Nov 2024 14:31:53 +0100 Subject: [PATCH 01/52] Start working on structure for logs --- exporter/sematextexporter/config.go | 16 +++++++++++++--- exporter/sematextexporter/writer.go | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index c2c08fcc2fbd..c48cfa1b612a 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -21,6 +21,7 @@ type Config struct { // - US Region string `mapstructure:"region"` MetricsConfig `mapstructure:"metrics"` + LogsConfig `mapstructure:"logs"` } type MetricsConfig struct { @@ -37,21 +38,30 @@ type MetricsConfig struct { // PayloadMaxBytes is the maximum number of line protocol bytes to POST in a single request. PayloadMaxBytes int `mapstructure:"payload_max_bytes"` } - +type LogsConfig struct { + AppToken string `mapstructure:"app_token"` + LogsEndpoint string `mapstructure:"logs_endpoint"` +} // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom"{ return fmt.Errorf("invalid region: %s. please use either 'EU' or 'US'", cfg.Region) } - if len(cfg.AppToken) != 36 { - return fmt.Errorf("invalid app_token: %s. app_token should be 36 characters", cfg.AppToken) + if len(cfg.MetricsConfig.AppToken) != 36{ + return fmt.Errorf("invalid metrics app_token: %s. app_token should be 36 characters", cfg.MetricsConfig.AppToken) + } + if len(cfg.LogsConfig.AppToken) != 36{ + return fmt.Errorf("invalid logs app_token: %s. app_token should be 36 characters", cfg.LogsConfig.AppToken) } if strings.ToLower(cfg.Region) == "eu" { cfg.MetricsEndpoint ="https://spm-receiver.eu.sematext.com" + cfg.LogsEndpoint ="logsene-receiver.eu.sematext.com" } if strings.ToLower(cfg.Region) == "us"{ cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" + cfg.LogsEndpoint = "logsene-receiver.sematext.com" } + return nil } diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 31bbb03017c3..5920a81de39e 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -66,7 +66,7 @@ func newSematextHTTPWriter(logger common.Logger, config *Config, telemetrySettin payloadMaxBytes: config.PayloadMaxBytes, logger: logger, hostname: hostname, - token: config.AppToken, + token: config.MetricsConfig.AppToken, }, nil } From 2160a713d73ea154947df87b05d8805cb088f672 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 01:31:07 +0100 Subject: [PATCH 02/52] Work on logs config Filtered some things we do not need from the Elastisearch exporter after doing some research --- exporter/sematextexporter/config.go | 105 +++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index c48cfa1b612a..05e3dd417a4f 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -6,10 +6,11 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "fmt" "strings" - + "time" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/exporter/exporterhelper" + "go.opentelemetry.io/collector/exporter/exporterbatcher" ) type Config struct { @@ -41,7 +42,102 @@ type MetricsConfig struct { type LogsConfig struct { AppToken string `mapstructure:"app_token"` LogsEndpoint string `mapstructure:"logs_endpoint"` + LogsMapping LogMapping `mapstructure:"logs_mapping"` + LogsFlushSettings FlushSettings `mapstructure:"logs_flush_settings"` + LogstashFormat LogstashFormatSettings `mapstructure:"logstash_format"` + // TelemetrySettings contains settings useful for testing/debugging purposes + // This is experimental and may change at any time. + TelemetrySettings `mapstructure:"telemetry"` + + // Batcher holds configuration for batching requests based on timeout + // and size-based thresholds. + // + // Batcher is unused by default, in which case Flush will be used. + // If Batcher.Enabled is non-nil (i.e. batcher::enabled is specified), + // then the Flush will be ignored even if Batcher.Enabled is false. + Batcher BatcherConfig `mapstructure:"batcher"` +} +// BatcherConfig holds configuration for exporterbatcher. +// +// This is a slightly modified version of exporterbatcher.Config, +// to enable tri-state Enabled: unset, false, true. +type BatcherConfig struct { + // Enabled indicates whether to enqueue batches before sending + // to the exporter. If Enabled is specified (non-nil), + // then the exporter will not perform any buffering itself. + Enabled *bool `mapstructure:"enabled"` + + // FlushTimeout sets the time after which a batch will be sent regardless of its size. + FlushTimeout time.Duration `mapstructure:"flush_timeout"` + + exporterbatcher.MinSizeConfig `mapstructure:",squash"` + exporterbatcher.MaxSizeConfig `mapstructure:",squash"` +} + +type TelemetrySettings struct { + LogRequestBody bool `mapstructure:"log_request_body"` + LogResponseBody bool `mapstructure:"log_response_body"` +} + +type LogstashFormatSettings struct { + Enabled bool `mapstructure:"enabled"` + PrefixSeparator string `mapstructure:"prefix_separator"` + DateFormat string `mapstructure:"date_format"` +} +// FlushSettings defines settings for configuring the write buffer flushing +// policy in the Elasticsearch exporter. The exporter sends a bulk request with +// all events already serialized into the send-buffer. +type FlushSettings struct { + // Bytes sets the send buffer flushing limit. + Bytes int `mapstructure:"bytes"` + + // Interval configures the max age of a document in the send buffer. + Interval time.Duration `mapstructure:"interval"` +} +type LogMapping struct { + //Will refine this comment later but from the research i did there are 4 different modes used in Elastisearch Exporter + // I believe we need MappingECS but for now i will just leave all the options + Mode string `mapstructure:"mode"` +} + +type MappingMode int +const ( + MappingNone MappingMode = iota + MappingECS + MappingOTel + MappingRaw +) +func (m MappingMode) String() string { + switch m { + case MappingNone: + return "" + case MappingECS: + return "ecs" + case MappingOTel: + return "otel" + case MappingRaw: + return "raw" + default: + return "" + } } +var mappingModes = func() map[string]MappingMode { + table := map[string]MappingMode{} + for _, m := range []MappingMode{ + MappingNone, + MappingECS, + MappingOTel, + MappingRaw, + } { + table[strings.ToLower(m.String())] = m + } + + // config aliases + table["no"] = MappingNone + table["none"] = MappingNone + + return table +}() // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom"{ @@ -61,7 +157,12 @@ func (cfg *Config) Validate() error { cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" cfg.LogsEndpoint = "logsene-receiver.sematext.com" } - + if _, ok := mappingModes[cfg.LogsMapping.Mode]; !ok { + return fmt.Errorf("unknown mapping mode %q", cfg.LogsMapping.Mode) + } return nil } +func (cfg *Config) MappingMode() MappingMode { + return mappingModes[cfg.LogsMapping.Mode] +} \ No newline at end of file From c0ca7cb71551ace585a025b63a9feca3fafd55b5 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 02:05:15 +0100 Subject: [PATCH 03/52] Removed LogStash settings --- exporter/sematextexporter/config.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 05e3dd417a4f..7ced02f1d5d8 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -44,7 +44,6 @@ type LogsConfig struct { LogsEndpoint string `mapstructure:"logs_endpoint"` LogsMapping LogMapping `mapstructure:"logs_mapping"` LogsFlushSettings FlushSettings `mapstructure:"logs_flush_settings"` - LogstashFormat LogstashFormatSettings `mapstructure:"logstash_format"` // TelemetrySettings contains settings useful for testing/debugging purposes // This is experimental and may change at any time. TelemetrySettings `mapstructure:"telemetry"` @@ -78,12 +77,6 @@ type TelemetrySettings struct { LogRequestBody bool `mapstructure:"log_request_body"` LogResponseBody bool `mapstructure:"log_response_body"` } - -type LogstashFormatSettings struct { - Enabled bool `mapstructure:"enabled"` - PrefixSeparator string `mapstructure:"prefix_separator"` - DateFormat string `mapstructure:"date_format"` -} // FlushSettings defines settings for configuring the write buffer flushing // policy in the Elasticsearch exporter. The exporter sends a bulk request with // all events already serialized into the send-buffer. @@ -95,11 +88,9 @@ type FlushSettings struct { Interval time.Duration `mapstructure:"interval"` } type LogMapping struct { - //Will refine this comment later but from the research i did there are 4 different modes used in Elastisearch Exporter - // I believe we need MappingECS but for now i will just leave all the options Mode string `mapstructure:"mode"` } - +//This mapping is going to be None, will remove it if after building the exporter we realize we do not need it type MappingMode int const ( MappingNone MappingMode = iota From 9179c5e9b6c4772e80958688709f63e9121c7d2e Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 02:17:31 +0100 Subject: [PATCH 04/52] This is what our logs config will look like --- exporter/sematextexporter/testdata/config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index d4a4902c6be0..09b559818e65 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -15,6 +15,14 @@ sematext/override-config: queue_size: 10 payload_max_lines: 72 payload_max_bytes: 27 + logs: + tls: + insecure: false + app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + flush: + bytes: 10485760 + sending_queue: + enabled: true From 915f6911ad848518b84aa5db0cc6b27c97533b1c Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 02:18:01 +0100 Subject: [PATCH 05/52] Add queue settings to log config --- exporter/sematextexporter/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 7ced02f1d5d8..da05999f7908 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -40,6 +40,7 @@ type MetricsConfig struct { PayloadMaxBytes int `mapstructure:"payload_max_bytes"` } type LogsConfig struct { + QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` AppToken string `mapstructure:"app_token"` LogsEndpoint string `mapstructure:"logs_endpoint"` LogsMapping LogMapping `mapstructure:"logs_mapping"` From 3d8390aa3ce372a9eb4a791fe16bb4b6d3aea97b Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 02:18:11 +0100 Subject: [PATCH 06/52] Correct Metrics config --- exporter/sematextexporter/factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index b736a03622ce..34f9da06ceeb 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -86,7 +86,7 @@ func createMetricsExporter( set, cfg, exp.WriteMetrics, - exporterhelper.WithQueue(cfg.QueueSettings), + exporterhelper.WithQueue(cfg.MetricsConfig.QueueSettings), exporterhelper.WithRetry(cfg.BackOffConfig), exporterhelper.WithStart(writer.Start), ) From cf6f43eb48f967498c2e5686d07f8e54d885e9a7 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 02:33:11 +0100 Subject: [PATCH 07/52] remove unnecessary things --- exporter/sematextexporter/testdata/config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index 09b559818e65..4a97aa1a07f1 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -16,8 +16,6 @@ sematext/override-config: payload_max_lines: 72 payload_max_bytes: 27 logs: - tls: - insecure: false app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx flush: bytes: 10485760 From e86c09f1e99b843d46ae638e7b317e5c63ee5f40 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 22 Nov 2024 11:09:20 +0100 Subject: [PATCH 08/52] complete Logs Endpoint --- exporter/sematextexporter/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index da05999f7908..a23233e73571 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -143,11 +143,11 @@ func (cfg *Config) Validate() error { } if strings.ToLower(cfg.Region) == "eu" { cfg.MetricsEndpoint ="https://spm-receiver.eu.sematext.com" - cfg.LogsEndpoint ="logsene-receiver.eu.sematext.com" + cfg.LogsEndpoint ="logsene-receiver.eu.sematext.com/_bulk" } if strings.ToLower(cfg.Region) == "us"{ cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" - cfg.LogsEndpoint = "logsene-receiver.sematext.com" + cfg.LogsEndpoint = "logsene-receiver.sematext.com/_bulk" } if _, ok := mappingModes[cfg.LogsMapping.Mode]; !ok { return fmt.Errorf("unknown mapping mode %q", cfg.LogsMapping.Mode) From 02d5643e171dec4f2d94c8f2932b8ff7d1c7413a Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 28 Nov 2024 00:26:03 +0100 Subject: [PATCH 09/52] A lot of experimentation (Still in progress) is being done, so code is not very clean once things begin to work i will clean it up code. Also its hard to really differentiate work done into individual commits But what is going here is i am trying to follow STA pattern of shipping logs to SC Some of this code was copy and pasted from STA --- exporter/sematextexporter/config.go | 109 +++-------------- exporter/sematextexporter/es.go | 123 ++++++++++++++++++++ exporter/sematextexporter/flat_formatter.go | 23 ++++ exporter/sematextexporter/go.mod | 16 ++- exporter/sematextexporter/go.sum | 31 ++++- exporter/sematextexporter/writer.go | 103 ++++++++++++++++ 6 files changed, 302 insertions(+), 103 deletions(-) create mode 100644 exporter/sematextexporter/es.go create mode 100644 exporter/sematextexporter/flat_formatter.go diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index a23233e73571..b3f15beacad9 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -6,11 +6,10 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "fmt" "strings" - "time" + a"sync/atomic" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/exporter/exporterhelper" - "go.opentelemetry.io/collector/exporter/exporterbatcher" ) type Config struct { @@ -40,96 +39,17 @@ type MetricsConfig struct { PayloadMaxBytes int `mapstructure:"payload_max_bytes"` } type LogsConfig struct { - QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` AppToken string `mapstructure:"app_token"` LogsEndpoint string `mapstructure:"logs_endpoint"` - LogsMapping LogMapping `mapstructure:"logs_mapping"` - LogsFlushSettings FlushSettings `mapstructure:"logs_flush_settings"` - // TelemetrySettings contains settings useful for testing/debugging purposes - // This is experimental and may change at any time. - TelemetrySettings `mapstructure:"telemetry"` - - // Batcher holds configuration for batching requests based on timeout - // and size-based thresholds. - // - // Batcher is unused by default, in which case Flush will be used. - // If Batcher.Enabled is non-nil (i.e. batcher::enabled is specified), - // then the Flush will be ignored even if Batcher.Enabled is false. - Batcher BatcherConfig `mapstructure:"batcher"` -} -// BatcherConfig holds configuration for exporterbatcher. -// -// This is a slightly modified version of exporterbatcher.Config, -// to enable tri-state Enabled: unset, false, true. -type BatcherConfig struct { - // Enabled indicates whether to enqueue batches before sending - // to the exporter. If Enabled is specified (non-nil), - // then the exporter will not perform any buffering itself. - Enabled *bool `mapstructure:"enabled"` - - // FlushTimeout sets the time after which a batch will be sent regardless of its size. - FlushTimeout time.Duration `mapstructure:"flush_timeout"` - - exporterbatcher.MinSizeConfig `mapstructure:",squash"` - exporterbatcher.MaxSizeConfig `mapstructure:",squash"` -} - -type TelemetrySettings struct { - LogRequestBody bool `mapstructure:"log_request_body"` - LogResponseBody bool `mapstructure:"log_response_body"` -} -// FlushSettings defines settings for configuring the write buffer flushing -// policy in the Elasticsearch exporter. The exporter sends a bulk request with -// all events already serialized into the send-buffer. -type FlushSettings struct { - // Bytes sets the send buffer flushing limit. - Bytes int `mapstructure:"bytes"` - - // Interval configures the max age of a document in the send buffer. - Interval time.Duration `mapstructure:"interval"` -} -type LogMapping struct { - Mode string `mapstructure:"mode"` -} -//This mapping is going to be None, will remove it if after building the exporter we realize we do not need it -type MappingMode int -const ( - MappingNone MappingMode = iota - MappingECS - MappingOTel - MappingRaw -) -func (m MappingMode) String() string { - switch m { - case MappingNone: - return "" - case MappingECS: - return "ecs" - case MappingOTel: - return "otel" - case MappingRaw: - return "raw" - default: - return "" - } + LogRequests bool + LogMaxAge int `mapstructure:"logs_endpoint"` + LogMaxBackups int `mapstructure:"logs_endpoint"` + LogMaxSize int `mapstructure:"logs_endpoint"` + // WriteEvents determines if events are logged + WriteEvents a.Bool `yaml:"logging.write-events"` } -var mappingModes = func() map[string]MappingMode { - table := map[string]MappingMode{} - for _, m := range []MappingMode{ - MappingNone, - MappingECS, - MappingOTel, - MappingRaw, - } { - table[strings.ToLower(m.String())] = m - } - // config aliases - table["no"] = MappingNone - table["none"] = MappingNone - return table -}() // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom"{ @@ -149,12 +69,15 @@ func (cfg *Config) Validate() error { cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" cfg.LogsEndpoint = "logsene-receiver.sematext.com/_bulk" } - if _, ok := mappingModes[cfg.LogsMapping.Mode]; !ok { - return fmt.Errorf("unknown mapping mode %q", cfg.LogsMapping.Mode) - } return nil } -func (cfg *Config) MappingMode() MappingMode { - return mappingModes[cfg.LogsMapping.Mode] -} \ No newline at end of file + +// Bool provides an atomic boolean type. +type Bool struct{ u Uint32 } +// Uint32 provides an atomic uint32 type. +type Uint32 struct{ value uint32 } +// Load gets the value of atomic boolean. +func (b *Bool) Load() bool { return b.u.Load() == 1 } +// Load get the value of atomic integer. +func (u *Uint32) Load() uint32 { return a.LoadUint32(&u.value) } diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go new file mode 100644 index 000000000000..6afdabbd371e --- /dev/null +++ b/exporter/sematextexporter/es.go @@ -0,0 +1,123 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" +import ( + "fmt" + "reflect" + "strings" + + "github.com/olivere/elastic" + "github.com/sirupsen/logrus" + "golang.org/x/net/context" + + "time" + + json "github.com/json-iterator/go" +) + +const ( + // artificialDocType designates a syntenic doc type for ES documents + artificialDocType = "_doc" +) + +type group struct { + client *elastic.Client + token string +} + +type client struct { + clients map[string]group + config *Config + logger *logrus.Logger + writer FlatWriter +} + +// Client represents a minimal interface client implementation has to satisfy. +type Client interface { + Bulk(body interface{},config *Config) error +} + +// NewClient creates a new instance of ES client that internally stores a reference +// to both, event and log receivers. +func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client, error) { + clients := make(map[string]group) + + // client for shipping to logsene + if config.LogsConfig.AppToken != "" { + c, err := elastic.NewClient(elastic.SetURL(config.LogsEndpoint), elastic.SetSniff(false), elastic.SetHealthcheckTimeout(time.Second*2)) + if err != nil { + return nil, err + } + // clients := map[string]group{} + clients[config.LogsEndpoint] = group{ + client: c, + token: config.LogsConfig.AppToken, + } + } + + return &client{ + clients: clients, + config: config, + logger: logger, + writer: writer, + }, nil +} + +func (c *client) Bulk(body interface{}, config *Config) error { + // lookup for client by endpoint + if grp, ok := c.clients[config.LogsEndpoint]; ok { + // build bulk request for each element + // in the underlying slice + bulkRequest := grp.client.Bulk() + if reflect.TypeOf(body).Kind() == reflect.Slice { + v := reflect.ValueOf(body) + for i := 0; i < v.Len(); i++ { + req := elastic.NewBulkIndexRequest().Index(grp.token).Type(artificialDocType).Doc(v.Index(i).Interface()) + bulkRequest.Add(req) + } + } + if bulkRequest.NumberOfActions() > 0 { + if c.config.LogRequests { + c.logger.Infof("sending bulk to %s", config.LogsEndpoint) + } + // required for writing events to log file + p, err := json.Marshal(body) + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + res, err := bulkRequest.Do(ctx) + if err != nil { + c.writePayload(string(p), err.Error()) + return err + } + if res.Errors { + for _, item := range res.Failed() { + if item.Error != nil { + c.logger.Errorf("document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) + } + } + } + c.writePayload(string(p), "200") + return nil + } + } + return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) +} + +func (c *client) writePayload(payload string, status string) { + if c.config.WriteEvents.Load() { + c.writer.Write(Formatl(payload, status)) + } +} +// Formatl delimits and formats the response returned by receiver. +func Formatl(payload string, status string) string { + s := strings.TrimLeft(status, "\n") + i := strings.Index(s, "\n") + if i > 0 { + s = fmt.Sprintf("%s...", s[:i]) + } + return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) +} \ No newline at end of file diff --git a/exporter/sematextexporter/flat_formatter.go b/exporter/sematextexporter/flat_formatter.go new file mode 100644 index 000000000000..441a5bff19dc --- /dev/null +++ b/exporter/sematextexporter/flat_formatter.go @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" + +import ( + "bytes" + "github.com/sirupsen/logrus" + "time" +) + +const defaultTimestampFormat = time.RFC3339 + +// FlatFormatter is the formatter for printing log lines in raw format. +type FlatFormatter struct{} + +// Format prints the raw log message. +func (f *FlatFormatter) Format(entry *logrus.Entry) ([]byte, error) { + var b bytes.Buffer + b.WriteString(entry.Time.Format(defaultTimestampFormat) + " " + entry.Message) + b.WriteByte('\n') + return b.Bytes(), nil +} \ No newline at end of file diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index 870c9c24a76a..0f348bc0337a 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -4,20 +4,26 @@ go 1.22.0 require ( github.com/influxdata/influxdb-observability/common v0.5.8 + github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 go.opentelemetry.io/collector/config/confighttp v0.110.0 go.opentelemetry.io/collector/config/configopaque v1.16.0 go.opentelemetry.io/collector/config/configretry v1.16.0 go.opentelemetry.io/collector/exporter v0.110.0 go.uber.org/goleak v1.3.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( + github.com/fortytw2/leaktest v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect go.opentelemetry.io/collector/component/componentprofiles v0.110.0 // indirect go.opentelemetry.io/collector/consumer/consumertest v0.110.0 // indirect go.opentelemetry.io/collector/exporter/exporterprofiles v0.110.0 // indirect @@ -40,12 +46,14 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/influxdata/influxdb-observability/otel2influx v0.5.12 github.com/influxdata/line-protocol/v2 v2.2.1 - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.17.9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/olivere/elastic v6.2.37+incompatible github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect + github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/collector/client v1.16.0 // indirect go.opentelemetry.io/collector/component v0.110.0 @@ -71,9 +79,9 @@ require ( go.opentelemetry.io/otel/trace v1.30.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/net v0.31.0 + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect google.golang.org/grpc v1.66.2 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/exporter/sematextexporter/go.sum b/exporter/sematextexporter/go.sum index 48dba1694e22..579a6a48302e 100644 --- a/exporter/sematextexporter/go.sum +++ b/exporter/sematextexporter/go.sum @@ -6,6 +6,8 @@ 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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= @@ -41,6 +43,8 @@ github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY= github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE= github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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= @@ -60,6 +64,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= @@ -70,14 +76,23 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U= +github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= +github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -167,20 +182,21 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 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-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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= @@ -199,6 +215,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 5920a81de39e..2eca4f517531 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -16,6 +16,9 @@ import ( "sync" "time" + "gopkg.in/natefinch/lumberjack.v2" + fs "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/influxdb-observability/otel2influx" "github.com/influxdata/line-protocol/v2/lineprotocol" @@ -257,3 +260,103 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st } return } +//Logs Support +// FlatWriter writes a raw message to log file. +type FlatWriter struct { + l *logrus.Logger +} + +// NewFlatWriter creates a new instance of flat writer. If we detect agent running inside container +// we'll write to stdout stream instead of the provided log file. +func NewFlatWriter(f string, c *Config) (*FlatWriter, error) { + l := logrus.New() + l.Out = io.Discard + + hook, err := InitRotate( + f, + c.LogMaxAge, + c.LogMaxBackups, + c.LogMaxSize, + &FlatFormatter{}, + ) + w := &FlatWriter{ + l: l, + } + if err != nil { + return w, err + } + l.AddHook(hook) + return w, nil +} + +// Write dumps a raw message to log file. +func (w *FlatWriter) Write(message string) { + w.l.Print(message) +} + +// InitRotate returns a new fs hook that enables log file rotation with specified pattern, +// maximum size/TTL for existing log files. +func InitRotate(filePath string, maxAge, maxBackups, maxSize int, f logrus.Formatter) (logrus.Hook, error) { + h, err := NewRotateFile(RotateFileConfig{ + Filename: filePath, + MaxAge: maxAge, + MaxBackups: maxBackups, + MaxSize: maxSize, + Level: logrus.DebugLevel, + Formatter: f, + }) + if err != nil { + // if we can't initialize file log rotation, configure logger + // without rotation capabilities + var pathMap fs.PathMap = make(map[logrus.Level]string, 0) + for _, ll := range logrus.AllLevels { + pathMap[ll] = filePath + } + return fs.NewHook(pathMap, f), fmt.Errorf("unable to initialize log rotate: %w", err) + } + return h, nil +} +// RotateFileConfig is the configuration for the rotate file hook. +type RotateFileConfig struct { + Filename string + MaxSize int + MaxBackups int + MaxAge int + Level logrus.Level + Formatter logrus.Formatter +} +// RotateFile represents the rotate file hook. +type RotateFile struct { + Config RotateFileConfig + logWriter io.Writer +} + +// NewRotateFile builds a new rotate file hook. +func NewRotateFile(config RotateFileConfig) (logrus.Hook, error) { + + hook := RotateFile{ + Config: config, + } + hook.logWriter = &lumberjack.Logger{ + Filename: config.Filename, + MaxSize: config.MaxSize, + MaxBackups: config.MaxBackups, + MaxAge: config.MaxAge, + } + return &hook, nil +} +// Fire is called by logrus when it is about to write the log entry. +func (hook *RotateFile) Fire(entry *logrus.Entry) error { + b, err := hook.Config.Formatter.Format(entry) + if err != nil { + return err + } + if _, err := hook.logWriter.Write(b); err != nil { + return err + } + return nil +} +// Levels determines log levels that for which the logs are written. +func (hook *RotateFile) Levels() []logrus.Level { + return logrus.AllLevels[:hook.Config.Level+1] +} From a94b20bd3b5495e0706aef688db4f2309e5686c5 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 28 Nov 2024 12:12:46 +0100 Subject: [PATCH 10/52] Fixed some typos --- exporter/sematextexporter/config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index b3f15beacad9..e314b797a3aa 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -42,11 +42,11 @@ type LogsConfig struct { AppToken string `mapstructure:"app_token"` LogsEndpoint string `mapstructure:"logs_endpoint"` LogRequests bool - LogMaxAge int `mapstructure:"logs_endpoint"` - LogMaxBackups int `mapstructure:"logs_endpoint"` - LogMaxSize int `mapstructure:"logs_endpoint"` + LogMaxAge int `mapstructure:"logs_max_age"` + LogMaxBackups int `mapstructure:"logs_max_backups"` + LogMaxSize int `mapstructure:"logs_max_size"` // WriteEvents determines if events are logged - WriteEvents a.Bool `yaml:"logging.write-events"` + WriteEvents a.Bool } From ff00870d0d32d733b5b7badd788a30951b95255d Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 29 Nov 2024 22:11:45 +0100 Subject: [PATCH 11/52] Another heavy commit Had to setup factory settings for the exporter --- exporter/sematextexporter/README.md | 9 ++ exporter/sematextexporter/config.go | 4 +- exporter/sematextexporter/es.go | 95 +++++++++++-------- exporter/sematextexporter/exporter.go | 90 ++++++++++++++++++ exporter/sematextexporter/factory.go | 44 ++++++++- .../generated_component_test.go | 7 ++ .../generated_package_test.go | 3 +- .../internal/metadata/generated_status.go | 1 + exporter/sematextexporter/metadata.yaml | 2 +- .../sematextexporter/testdata/config.yaml | 12 +-- 10 files changed, 213 insertions(+), 54 deletions(-) create mode 100644 exporter/sematextexporter/exporter.go diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index 9effe2645983..7baaab2908e1 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -1,5 +1,14 @@ # Sematext Exporter +| Status | | +| ------------- |-----------| +| Stability | [development]: metrics, logs | +| Distributions | [contrib] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fsematext%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fsematext) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fsematext%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fsematext) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@Eromosele-SM](https://www.github.com/Eromosele-SM) | + +[development]: https://github.com/open-telemetry/opentelemetry-collector#development +[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib This exporter supports sending metrics to [Sematext Cloud](https://sematext.com/) in Influx line protocol format diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index e314b797a3aa..65dec7f66bfc 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -41,12 +41,12 @@ type MetricsConfig struct { type LogsConfig struct { AppToken string `mapstructure:"app_token"` LogsEndpoint string `mapstructure:"logs_endpoint"` - LogRequests bool + LogRequests bool `mapstructure:"logs_requests"` LogMaxAge int `mapstructure:"logs_max_age"` LogMaxBackups int `mapstructure:"logs_max_backups"` LogMaxSize int `mapstructure:"logs_max_size"` // WriteEvents determines if events are logged - WriteEvents a.Bool + WriteEvents a.Bool } diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 6afdabbd371e..db906f63dd98 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -65,48 +65,63 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client } func (c *client) Bulk(body interface{}, config *Config) error { - // lookup for client by endpoint - if grp, ok := c.clients[config.LogsEndpoint]; ok { - // build bulk request for each element - // in the underlying slice - bulkRequest := grp.client.Bulk() - if reflect.TypeOf(body).Kind() == reflect.Slice { - v := reflect.ValueOf(body) - for i := 0; i < v.Len(); i++ { - req := elastic.NewBulkIndexRequest().Index(grp.token).Type(artificialDocType).Doc(v.Index(i).Interface()) - bulkRequest.Add(req) - } - } - if bulkRequest.NumberOfActions() > 0 { - if c.config.LogRequests { - c.logger.Infof("sending bulk to %s", config.LogsEndpoint) - } - // required for writing events to log file - p, err := json.Marshal(body) - if err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - res, err := bulkRequest.Do(ctx) - if err != nil { - c.writePayload(string(p), err.Error()) - return err - } - if res.Errors { - for _, item := range res.Failed() { - if item.Error != nil { - c.logger.Errorf("document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) - } - } - } - c.writePayload(string(p), "200") - return nil - } - } - return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) + // Lookup for client by endpoint + if grp, ok := c.clients[config.LogsEndpoint]; ok { + bulkRequest := grp.client.Bulk() + + // Dynamically process the body as a slice + if reflect.TypeOf(body).Kind() == reflect.Slice { + v := reflect.ValueOf(body) + for i := 0; i < v.Len(); i++ { + req := elastic.NewBulkIndexRequest(). + Index(grp.token). + Type(artificialDocType). + Doc(v.Index(i).Interface()) + bulkRequest.Add(req) + } + } + + if bulkRequest.NumberOfActions() > 0 { + // Serialize the payload for debugging or printing + payloadBytes, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to serialize payload: %w", err) + } + + // Print or log the payload + fmt.Printf("Payload being sent to Sematext:\n%s\n", string(payloadBytes)) + + if c.config.LogRequests { + c.logger.Infof("Sending bulk to %s", config.LogsEndpoint) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + // Send the payload + res, err := bulkRequest.Do(ctx) + if err != nil { + c.writePayload(string(payloadBytes), err.Error()) + return err + } + + // Check for errors in the response + if res.Errors { + for _, item := range res.Failed() { + if item.Error != nil { + c.logger.Errorf("Document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) + } + } + } + + c.writePayload(string(payloadBytes), "200") + return nil + } + } + return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) } + func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { c.writer.Write(Formatl(payload, status)) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go new file mode 100644 index 000000000000..a94d31ae2318 --- /dev/null +++ b/exporter/sematextexporter/exporter.go @@ -0,0 +1,90 @@ +package sematextexporter + +import ( + "context" + // "fmt" + "time" + "go.opentelemetry.io/collector/pdata/plog" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/exporter" + "github.com/sirupsen/logrus" + "go.uber.org/zap" +) + +type sematextLogsExporter struct { + config *Config + client Client + logger *logrus.Logger +} + +func NewExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { + logger := logrus.New() + logger.SetFormatter(&FlatFormatter{}) + + // Initialize Sematext client + client, err := NewClient(cfg, logger, FlatWriter{}) + if err != nil { + set.Logger.Error("Failed to create Sematext client", zap.Error(err)) + return nil + } + + return &sematextLogsExporter{ + config: cfg, + client: client, + logger: logger, + } +} + + +func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) error { + // Convert logs to Bulk API payload + bulkPayload, err := convertLogsToBulkPayload(logs) + if err != nil { + e.logger.Errorf("Failed to convert logs: %v", err) + return err + } + + // Send logs using the Sematext client + if err := e.client.Bulk(bulkPayload, e.config); err != nil { + e.logger.Errorf("Failed to send logs to Sematext: %v", err) + return err + } + + return nil +} +func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) { + var bulkPayload []map[string]interface{} + + resourceLogs := logs.ResourceLogs() + + // Iterate through logs to prepare the Bulk payload + for i := 0; i < resourceLogs.Len(); i++ { + scopeLogs := resourceLogs.At(i).ScopeLogs() + for j := 0; j < scopeLogs.Len(); j++ { + logRecords := scopeLogs.At(j).LogRecords() + for k := 0; k < logRecords.Len(); k++ { + record := logRecords.At(k) + + // Build the log entry + logEntry := map[string]interface{}{ + "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), + "message": record.Body().AsString(), + "severity": record.SeverityText(), + } + + bulkPayload = append(bulkPayload, logEntry) + } + } + } + + return bulkPayload, nil +} +func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) error { + e.logger.Info("Starting Sematext Logs Exporter...") + return nil +} + +func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { + e.logger.Info("Shutting down Sematext Logs Exporter...") + return nil +} \ No newline at end of file diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 34f9da06ceeb..b58a0e650c3e 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -11,17 +11,18 @@ import ( "time" "github.com/influxdata/influxdb-observability/otel2influx" + "sync/atomic" "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/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" - "github.com/influxdata/influxdb-observability/common" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter/internal/metadata" ) + const appToken string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // NewFactory creates a factory for the Sematext metrics exporter. func NewFactory() exporter.Factory { @@ -29,11 +30,13 @@ func NewFactory() exporter.Factory { metadata.Type, createDefaultConfig, exporter.WithMetrics(createMetricsExporter, metadata.MetricsStability), + exporter.WithLogs(createLogsExporter, metadata.LogsStability), ) } func createDefaultConfig() component.Config { - return &Config{ + + cfg := &Config{ ClientConfig: confighttp.ClientConfig{ Timeout: 5 * time.Second, Headers: map[string]configopaque.String{ @@ -46,10 +49,19 @@ func createDefaultConfig() component.Config { QueueSettings: exporterhelper.NewDefaultQueueConfig(), PayloadMaxLines: 1_000, PayloadMaxBytes: 300_000, - }, + }, + LogsConfig: LogsConfig{ + AppToken: appToken, + LogRequests:true, + LogMaxAge: 2, + LogMaxSize: 10, + WriteEvents: atomic.Bool{}, + }, BackOffConfig: configretry.NewDefaultBackOffConfig(), Region: "custom", } + cfg.LogsConfig.WriteEvents.Store(false) + return cfg } func createMetricsExporter( @@ -91,3 +103,29 @@ func createMetricsExporter( exporterhelper.WithStart(writer.Start), ) } +// createLogsExporter creates a new logs exporter for Sematext. +func createLogsExporter( + ctx context.Context, + set exporter.Settings, + cfg component.Config, +) (exporter.Logs, error) { + cf := cfg.(*Config) + + // Log the creation of the exporter + set.Logger.Info("Creating Sematext Logs Exporter") + + // Create the Sematext logs exporter + exporter := NewExporter(cf, set) + + // Wrap the exporter with OpenTelemetry helper functions + return exporterhelper.NewLogsExporter( + ctx, + set, + cfg, + exporter.pushLogsData, // Function to process and send logs + exporterhelper.WithQueue(cf.MetricsConfig.QueueSettings), // Optional queue settings + exporterhelper.WithRetry(cf.BackOffConfig), // Optional retry settings + exporterhelper.WithStart(exporter.Start), // Lifecycle start function + exporterhelper.WithShutdown(exporter.Shutdown), // Lifecycle shutdown function + ) +} \ No newline at end of file diff --git a/exporter/sematextexporter/generated_component_test.go b/exporter/sematextexporter/generated_component_test.go index b4fd792655f4..860701cca718 100644 --- a/exporter/sematextexporter/generated_component_test.go +++ b/exporter/sematextexporter/generated_component_test.go @@ -35,6 +35,13 @@ func TestComponentLifecycle(t *testing.T) { createFn func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) }{ + { + name: "logs", + createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) { + return factory.CreateLogsExporter(ctx, set, cfg) + }, + }, + { name: "metrics", createFn: func(ctx context.Context, set exporter.Settings, cfg component.Config) (component.Component, error) { diff --git a/exporter/sematextexporter/generated_package_test.go b/exporter/sematextexporter/generated_package_test.go index 770fcd6bf246..40fec63335b3 100644 --- a/exporter/sematextexporter/generated_package_test.go +++ b/exporter/sematextexporter/generated_package_test.go @@ -3,9 +3,8 @@ package sematextexporter import ( - "testing" - "go.uber.org/goleak" + "testing" ) func TestMain(m *testing.M) { diff --git a/exporter/sematextexporter/internal/metadata/generated_status.go b/exporter/sematextexporter/internal/metadata/generated_status.go index e221a6ab9c5d..49e2d6d1a7ef 100644 --- a/exporter/sematextexporter/internal/metadata/generated_status.go +++ b/exporter/sematextexporter/internal/metadata/generated_status.go @@ -13,4 +13,5 @@ var ( const ( MetricsStability = component.StabilityLevelDevelopment + LogsStability = component.StabilityLevelDevelopment ) diff --git a/exporter/sematextexporter/metadata.yaml b/exporter/sematextexporter/metadata.yaml index 3dee628c6c03..6c10e0f22f0b 100644 --- a/exporter/sematextexporter/metadata.yaml +++ b/exporter/sematextexporter/metadata.yaml @@ -3,7 +3,7 @@ type: sematext status: class: exporter stability: - development: [metrics] + development: [metrics,logs] distributions: [contrib] codeowners: active: [Eromosele-SM] diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index 4a97aa1a07f1..e75c480d5054 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -8,7 +8,7 @@ sematext/override-config: max_interval: 3s max_elapsed_time: 10s metrics: - app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + app_token: 5cf92744-fefb-4f4f-9d6b-63eb194690f8 sending_queue: enabled: true num_consumers: 3 @@ -16,11 +16,11 @@ sematext/override-config: payload_max_lines: 72 payload_max_bytes: 27 logs: - app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - flush: - bytes: 10485760 - sending_queue: - enabled: true + app_token: 7f7ff081-fcb7-4172-9942-eff5295fa2f6 + logs_requests: true + logs_max_age : 2 + logs_max_backups: 10 + From a8b67a6fd4e244f62cccefb732f6c02dca01deae Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 2 Dec 2024 16:23:24 +0100 Subject: [PATCH 12/52] First test failed so i had to do some tinkering and refining and put some logging statements Will clean code up once i am done --- exporter/sematextexporter/config.go | 4 +-- exporter/sematextexporter/es.go | 15 +++----- exporter/sematextexporter/exporter.go | 52 +++++++++++++++++---------- exporter/sematextexporter/factory.go | 2 +- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 65dec7f66bfc..6da9ee693e6f 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -63,11 +63,11 @@ func (cfg *Config) Validate() error { } if strings.ToLower(cfg.Region) == "eu" { cfg.MetricsEndpoint ="https://spm-receiver.eu.sematext.com" - cfg.LogsEndpoint ="logsene-receiver.eu.sematext.com/_bulk" + cfg.LogsEndpoint ="https://logsene-receiver.eu.sematext.com" } if strings.ToLower(cfg.Region) == "us"{ cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" - cfg.LogsEndpoint = "logsene-receiver.sematext.com/_bulk" + cfg.LogsEndpoint = "https://logsene-receiver.sematext.com" } return nil diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index db906f63dd98..1e2fd966a95d 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -16,11 +16,6 @@ import ( json "github.com/json-iterator/go" ) -const ( - // artificialDocType designates a syntenic doc type for ES documents - artificialDocType = "_doc" -) - type group struct { client *elastic.Client token string @@ -49,7 +44,6 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client if err != nil { return nil, err } - // clients := map[string]group{} clients[config.LogsEndpoint] = group{ client: c, token: config.LogsConfig.AppToken, @@ -74,9 +68,8 @@ func (c *client) Bulk(body interface{}, config *Config) error { v := reflect.ValueOf(body) for i := 0; i < v.Len(); i++ { req := elastic.NewBulkIndexRequest(). - Index(grp.token). - Type(artificialDocType). - Doc(v.Index(i).Interface()) + Index(grp.token). + Doc(v.Index(i).Interface()) bulkRequest.Add(req) } } @@ -125,7 +118,9 @@ func (c *client) Bulk(body interface{}, config *Config) error { func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { c.writer.Write(Formatl(payload, status)) - } + } else { + c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) + } } // Formatl delimits and formats the response returned by receiver. func Formatl(payload string, status string) string { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index a94d31ae2318..8f145d4f2943 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -2,7 +2,7 @@ package sematextexporter import ( "context" - // "fmt" + "fmt" "time" "go.opentelemetry.io/collector/pdata/plog" "go.opentelemetry.io/collector/component" @@ -17,7 +17,7 @@ type sematextLogsExporter struct { logger *logrus.Logger } -func NewExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { +func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { logger := logrus.New() logger.SetFormatter(&FlatFormatter{}) @@ -37,22 +37,27 @@ func NewExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) error { - // Convert logs to Bulk API payload - bulkPayload, err := convertLogsToBulkPayload(logs) - if err != nil { - e.logger.Errorf("Failed to convert logs: %v", err) - return err - } + // Convert logs to bulk payload + bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) + if err != nil { + e.logger.Errorf("Failed to convert logs: %v", err) + return err + } - // Send logs using the Sematext client - if err := e.client.Bulk(bulkPayload, e.config); err != nil { - e.logger.Errorf("Failed to send logs to Sematext: %v", err) - return err - } + // Debug: Print the bulk payload + for _, payload := range bulkPayload { + fmt.Printf("Bulk payload: %v\n", payload) + } - return nil + // Send the bulk payload to Sematext + if err := e.client.Bulk(bulkPayload, e.config); err != nil { + e.logger.Errorf("Failed to send logs to Sematext: %v", err) + return err + } + + return nil } -func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) { +func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]interface{}, error) { var bulkPayload []map[string]interface{} resourceLogs := logs.ResourceLogs() @@ -65,13 +70,20 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) for k := 0; k < logRecords.Len(); k++ { record := logRecords.At(k) + // Add metadata for indexing + meta := map[string]interface{}{ + "index": map[string]interface{}{ + "_index": appToken, + }, + } + bulkPayload = append(bulkPayload, meta) + // Build the log entry logEntry := map[string]interface{}{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), "message": record.Body().AsString(), "severity": record.SeverityText(), } - bulkPayload = append(bulkPayload, logEntry) } } @@ -80,10 +92,12 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) return bulkPayload, nil } func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) error { - e.logger.Info("Starting Sematext Logs Exporter...") - return nil + if e.client == nil { + return fmt.Errorf("sematext client is not initialized") + } + e.logger.Info("Starting Sematext Logs Exporter...") + return nil } - func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { e.logger.Info("Shutting down Sematext Logs Exporter...") return nil diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index b58a0e650c3e..42beba2887b7 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -115,7 +115,7 @@ func createLogsExporter( set.Logger.Info("Creating Sematext Logs Exporter") // Create the Sematext logs exporter - exporter := NewExporter(cf, set) + exporter := newExporter(cf, set) // Wrap the exporter with OpenTelemetry helper functions return exporterhelper.NewLogsExporter( From 17c525b140349c0cb49c4033352eab69609cfe91 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 3 Dec 2024 13:02:03 +0100 Subject: [PATCH 13/52] Add hostname tag --- exporter/sematextexporter/es.go | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 1e2fd966a95d..755ecb65f26b 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -6,7 +6,7 @@ import ( "fmt" "reflect" "strings" - + "os" "github.com/olivere/elastic" "github.com/sirupsen/logrus" "golang.org/x/net/context" @@ -15,7 +15,8 @@ import ( json "github.com/json-iterator/go" ) - +// artificialDocType designates a syntenic doc type for ES documents +const artificialDocType = "_doc" type group struct { client *elastic.Client token string @@ -58,6 +59,7 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client }, nil } + func (c *client) Bulk(body interface{}, config *Config) error { // Lookup for client by endpoint if grp, ok := c.clients[config.LogsEndpoint]; ok { @@ -67,9 +69,18 @@ func (c *client) Bulk(body interface{}, config *Config) error { if reflect.TypeOf(body).Kind() == reflect.Slice { v := reflect.ValueOf(body) for i := 0; i < v.Len(); i++ { + doc := v.Index(i).Interface() + + // Ensure the document is a map to add the hostname tag + if docMap, ok := doc.(map[string]interface{}); ok { + docMap["os.host"] = getHostname() + + } + req := elastic.NewBulkIndexRequest(). - Index(grp.token). - Doc(v.Index(i).Interface()) + Index(grp.token). + Type(artificialDocType). + Doc(doc) bulkRequest.Add(req) } } @@ -130,4 +141,11 @@ func Formatl(payload string, status string) string { s = fmt.Sprintf("%s...", s[:i]) } return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) -} \ No newline at end of file +} +func getHostname() (string) { + hostname, err := os.Hostname() + if err != nil { + return "None" + } + return hostname +} \ No newline at end of file From e0b95cdb45fa74f280769f4c51d35ad4616261aa Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 3 Dec 2024 15:57:07 +0100 Subject: [PATCH 14/52] Clean up code --- exporter/sematextexporter/config.go | 8 ++++++++ exporter/sematextexporter/es.go | 19 ++++++------------- exporter/sematextexporter/exporter.go | 7 +++++-- exporter/sematextexporter/factory.go | 11 ++++------- exporter/sematextexporter/writer.go | 4 ++-- 5 files changed, 25 insertions(+), 24 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 6da9ee693e6f..6c66492e880d 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -20,7 +20,9 @@ type Config struct { // - EU // - US Region string `mapstructure:"region"` + // MetricsConfig defines the configuration specific to metrics MetricsConfig `mapstructure:"metrics"` + // LogsConfig defines the configuration specific to logs LogsConfig `mapstructure:"logs"` } @@ -39,11 +41,17 @@ type MetricsConfig struct { PayloadMaxBytes int `mapstructure:"payload_max_bytes"` } type LogsConfig struct { + // App token is the token of Sematext Monitoring App to which you want to send the logs. AppToken string `mapstructure:"app_token"` + // LogsEndpoint specifies the endpoint for receiving logs in Sematext LogsEndpoint string `mapstructure:"logs_endpoint"` + // LogRequests determines whether request tracking is enabled LogRequests bool `mapstructure:"logs_requests"` + // LogMaxAge is the max number of days to retain old log files LogMaxAge int `mapstructure:"logs_max_age"` + // LogMaxBackups is the maximum number of old log files to retain. LogMaxBackups int `mapstructure:"logs_max_backups"` + // LogMaxSize is the maximum size in megabytes of the log file before it gets rotated LogMaxSize int `mapstructure:"logs_max_size"` // WriteEvents determines if events are logged WriteEvents a.Bool diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 755ecb65f26b..334b60afd6bb 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -59,19 +59,14 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client }, nil } - +// Bulk processes a batch of documents and sends them to the specified LogsEndpoint. func (c *client) Bulk(body interface{}, config *Config) error { - // Lookup for client by endpoint if grp, ok := c.clients[config.LogsEndpoint]; ok { bulkRequest := grp.client.Bulk() - - // Dynamically process the body as a slice if reflect.TypeOf(body).Kind() == reflect.Slice { v := reflect.ValueOf(body) for i := 0; i < v.Len(); i++ { doc := v.Index(i).Interface() - - // Ensure the document is a map to add the hostname tag if docMap, ok := doc.(map[string]interface{}); ok { docMap["os.host"] = getHostname() @@ -86,13 +81,12 @@ func (c *client) Bulk(body interface{}, config *Config) error { } if bulkRequest.NumberOfActions() > 0 { - // Serialize the payload for debugging or printing payloadBytes, err := json.Marshal(body) if err != nil { return fmt.Errorf("failed to serialize payload: %w", err) } - // Print or log the payload + // Print or log the payload(Will delete this once everything is good) fmt.Printf("Payload being sent to Sematext:\n%s\n", string(payloadBytes)) if c.config.LogRequests { @@ -101,15 +95,11 @@ func (c *client) Bulk(body interface{}, config *Config) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - - // Send the payload res, err := bulkRequest.Do(ctx) if err != nil { c.writePayload(string(payloadBytes), err.Error()) return err } - - // Check for errors in the response if res.Errors { for _, item := range res.Failed() { if item.Error != nil { @@ -125,7 +115,7 @@ func (c *client) Bulk(body interface{}, config *Config) error { return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) } - +// writePayload writes a formatted payload along with its status to the configured writer. func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { c.writer.Write(Formatl(payload, status)) @@ -133,6 +123,7 @@ func (c *client) writePayload(payload string, status string) { c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) } } + // Formatl delimits and formats the response returned by receiver. func Formatl(payload string, status string) string { s := strings.TrimLeft(status, "\n") @@ -142,6 +133,8 @@ func Formatl(payload string, status string) string { } return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) } + +// getHostname retrieves the current machine's hostname. func getHostname() (string) { hostname, err := os.Hostname() if err != nil { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 8f145d4f2943..7ce6c6a8e96d 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -16,7 +16,7 @@ type sematextLogsExporter struct { client Client logger *logrus.Logger } - +// newExporter creates a new instance of the sematextLogsExporter. func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { logger := logrus.New() logger.SetFormatter(&FlatFormatter{}) @@ -35,7 +35,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { } } - +// pushLogsData processes and sends log data to Sematext in bulk. func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) error { // Convert logs to bulk payload bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) @@ -57,6 +57,7 @@ func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) return nil } +// convertLogsToBulkPayload converts OpenTelemetry log data into a bulk payload for Sematext. func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]interface{}, error) { var bulkPayload []map[string]interface{} @@ -91,6 +92,7 @@ func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]int return bulkPayload, nil } +// Start initializes the Sematext Logs Exporter. func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) error { if e.client == nil { return fmt.Errorf("sematext client is not initialized") @@ -98,6 +100,7 @@ func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) e e.logger.Info("Starting Sematext Logs Exporter...") return nil } +// Shutdown gracefully shuts down the Sematext Logs Exporter. func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { e.logger.Info("Shutting down Sematext Logs Exporter...") return nil diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 42beba2887b7..8a8ff3c749e2 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -111,21 +111,18 @@ func createLogsExporter( ) (exporter.Logs, error) { cf := cfg.(*Config) - // Log the creation of the exporter set.Logger.Info("Creating Sematext Logs Exporter") - // Create the Sematext logs exporter exporter := newExporter(cf, set) - // Wrap the exporter with OpenTelemetry helper functions return exporterhelper.NewLogsExporter( ctx, set, cfg, exporter.pushLogsData, // Function to process and send logs - exporterhelper.WithQueue(cf.MetricsConfig.QueueSettings), // Optional queue settings - exporterhelper.WithRetry(cf.BackOffConfig), // Optional retry settings - exporterhelper.WithStart(exporter.Start), // Lifecycle start function - exporterhelper.WithShutdown(exporter.Shutdown), // Lifecycle shutdown function + exporterhelper.WithQueue(cf.MetricsConfig.QueueSettings), + exporterhelper.WithRetry(cf.BackOffConfig), + exporterhelper.WithStart(exporter.Start), + exporterhelper.WithShutdown(exporter.Shutdown), ) } \ No newline at end of file diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 2eca4f517531..15304e1fa7f3 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -261,13 +261,13 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st return } //Logs Support + // FlatWriter writes a raw message to log file. type FlatWriter struct { l *logrus.Logger } -// NewFlatWriter creates a new instance of flat writer. If we detect agent running inside container -// we'll write to stdout stream instead of the provided log file. +// NewFlatWriter creates a new instance of flat writer. func NewFlatWriter(f string, c *Config) (*FlatWriter, error) { l := logrus.New() l.Out = io.Discard From 5cfc6e14269965f1c8614653277ef66ad911a877 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 4 Dec 2024 14:43:04 +0100 Subject: [PATCH 15/52] Work on config.go test --- exporter/sematextexporter/config_test.go | 8 ++++++++ exporter/sematextexporter/factory.go | 3 +-- exporter/sematextexporter/testdata/config.yaml | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/exporter/sematextexporter/config_test.go b/exporter/sematextexporter/config_test.go index 0486bd8d117b..43a1c256213a 100644 --- a/exporter/sematextexporter/config_test.go +++ b/exporter/sematextexporter/config_test.go @@ -54,6 +54,14 @@ func TestLoadConfig(t *testing.T) { PayloadMaxLines: 72, PayloadMaxBytes: 27, }, + LogsConfig: LogsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + LogsEndpoint: "https://logsene-receiver.sematext.com", + LogRequests: true, + LogMaxAge: 2, + LogMaxBackups: 10, + LogMaxSize: 10, + }, BackOffConfig: configretry.BackOffConfig{ Enabled: true, diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 8a8ff3c749e2..f1cd0e2c76ec 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -11,7 +11,6 @@ import ( "time" "github.com/influxdata/influxdb-observability/otel2influx" - "sync/atomic" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configopaque" @@ -55,7 +54,7 @@ func createDefaultConfig() component.Config { LogRequests:true, LogMaxAge: 2, LogMaxSize: 10, - WriteEvents: atomic.Bool{}, + LogMaxBackups: 10, }, BackOffConfig: configretry.NewDefaultBackOffConfig(), Region: "custom", diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index e75c480d5054..b43c4d60d1a8 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -8,7 +8,7 @@ sematext/override-config: max_interval: 3s max_elapsed_time: 10s metrics: - app_token: 5cf92744-fefb-4f4f-9d6b-63eb194690f8 + app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx sending_queue: enabled: true num_consumers: 3 @@ -16,10 +16,11 @@ sematext/override-config: payload_max_lines: 72 payload_max_bytes: 27 logs: - app_token: 7f7ff081-fcb7-4172-9942-eff5295fa2f6 + app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx logs_requests: true logs_max_age : 2 logs_max_backups: 10 + logs_max_size: 10 From 9ad149b469c4d976ae4984836edd9be753e7d470 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 4 Dec 2024 15:16:51 +0100 Subject: [PATCH 16/52] Add tests for logs support part of writer.go --- exporter/sematextexporter/writer_test.go | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index 3d3621fc168a..8ebffda21d7f 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -10,12 +10,14 @@ import ( "sync" "testing" "time" + "bytes" "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/line-protocol/v2/lineprotocol" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" + "github.com/sirupsen/logrus" ) func TestSematextHTTPWriterBatchOptimizeTags(t *testing.T) { @@ -219,3 +221,81 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { assert.NoError(t, err) }) } +func TestNewFlatWriter(t *testing.T) { + config := &Config{ + LogsConfig: LogsConfig{ + LogMaxAge: 7, + LogMaxBackups: 5, + LogMaxSize: 10, + }, + + } + writer, err := NewFlatWriter("test.log", config) + assert.NoError(t, err) + assert.NotNil(t, writer) + assert.NotNil(t, writer.l) +} +func TestFlatWriterWrite(t *testing.T) { + var buf bytes.Buffer + logger := logrus.New() + logger.SetOutput(&buf) + writer := &FlatWriter{l: logger} + + message := "test message" + writer.Write(message) + + assert.Contains(t, buf.String(), message) +} +func TestInitRotate(t *testing.T) { + hook, err := InitRotate("test.log", 7, 5, 10, &FlatFormatter{}) + assert.NoError(t, err) + assert.NotNil(t, hook) +} +func TestNewRotateFile(t *testing.T) { + config := RotateFileConfig{ + Filename: "test.log", + MaxSize: 10, + MaxBackups: 5, + MaxAge: 7, + Level: logrus.InfoLevel, + Formatter: &FlatFormatter{}, + } + + hook, err := NewRotateFile(config) + assert.NoError(t, err) + assert.NotNil(t, hook) +} +func TestRotateFileFire(t *testing.T) { + var buf bytes.Buffer + + hook := &RotateFile{ + Config: RotateFileConfig{ + Filename: "test.log", + MaxSize: 10, + MaxBackups: 5, + MaxAge: 7, + Level: logrus.InfoLevel, + Formatter: &logrus.TextFormatter{}, + }, + logWriter: &buf, + } + + entry := &logrus.Entry{ + Message: "test entry", + Level: logrus.InfoLevel, + } + + err := hook.Fire(entry) + assert.NoError(t, err) + assert.Contains(t, buf.String(), "test entry") +} +func TestRotateFileLevels(t *testing.T) { + hook := &RotateFile{ + Config: RotateFileConfig{ + Level: logrus.WarnLevel, + }, + } + + expectedLevels := logrus.AllLevels[:logrus.WarnLevel+1] + assert.Equal(t, expectedLevels, hook.Levels()) +} \ No newline at end of file From e999c1f57e01bc6518fe251944054911a6667259 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 4 Dec 2024 15:17:05 +0100 Subject: [PATCH 17/52] Write tests for falt_formatter.go --- .../sematextexporter/flat_formatter_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 exporter/sematextexporter/flat_formatter_test.go diff --git a/exporter/sematextexporter/flat_formatter_test.go b/exporter/sematextexporter/flat_formatter_test.go new file mode 100644 index 000000000000..820a0bbfebe7 --- /dev/null +++ b/exporter/sematextexporter/flat_formatter_test.go @@ -0,0 +1,32 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" +import ( + "bytes" + "testing" + "time" + + "github.com/sirupsen/logrus" +) + +func TestFlatFormatter_Format(t *testing.T) { + formatter := &FlatFormatter{} + + mockTime := time.Date(2024, 12, 4, 10, 30, 45, 0, time.UTC) + entry := &logrus.Entry{ + Time: mockTime, + Message: "Test log message", + } + + formatted, err := formatter.Format(entry) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + expected := "2024-12-04T10:30:45Z Test log message\n" + + if !bytes.Equal(formatted, []byte(expected)) { + t.Errorf("unexpected output:\n got: %q\n want: %q", string(formatted), expected) + } +} From 5303ef0629433a3b14aeaf1b786f67d883a2951c Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 4 Dec 2024 16:28:07 +0100 Subject: [PATCH 18/52] Fix lint issues --- exporter/sematextexporter/config.go | 37 +++--- exporter/sematextexporter/config_test.go | 22 ++-- exporter/sematextexporter/es.go | 123 +++++++++--------- exporter/sematextexporter/es_test.go | 4 + exporter/sematextexporter/exporter.go | 59 +++++---- exporter/sematextexporter/factory.go | 22 ++-- exporter/sematextexporter/flat_formatter.go | 5 +- .../generated_package_test.go | 3 +- exporter/sematextexporter/writer.go | 41 +++--- exporter/sematextexporter/writer_test.go | 32 +++-- 10 files changed, 185 insertions(+), 163 deletions(-) create mode 100644 exporter/sematextexporter/es_test.go diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 6c66492e880d..67e0e5800305 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -6,7 +6,8 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "fmt" "strings" - a"sync/atomic" + a "sync/atomic" + "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/exporter/exporterhelper" @@ -20,7 +21,7 @@ type Config struct { // - EU // - US Region string `mapstructure:"region"` - // MetricsConfig defines the configuration specific to metrics + // MetricsConfig defines the configuration specific to metrics MetricsConfig `mapstructure:"metrics"` // LogsConfig defines the configuration specific to logs LogsConfig `mapstructure:"logs"` @@ -28,23 +29,23 @@ type Config struct { type MetricsConfig struct { // App token is the token of Sematext Monitoring App to which you want to send the metrics. - AppToken string `mapstructure:"app_token"` + AppToken string `mapstructure:"app_token"` // MetricsEndpoint specifies the endpoint for receiving metrics in Sematext - MetricsEndpoint string `mapstructure:"metrics_endpoint"` - QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` + MetricsEndpoint string `mapstructure:"metrics_endpoint"` + QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` // MetricsSchema indicates the metrics schema to emit to line protocol. // Default: telegraf-prometheus-v2 - MetricsSchema string `mapstructure:"metrics_schema"` + MetricsSchema string `mapstructure:"metrics_schema"` // PayloadMaxLines is the maximum number of line protocol lines to POST in a single request. - PayloadMaxLines int `mapstructure:"payload_max_lines"` + PayloadMaxLines int `mapstructure:"payload_max_lines"` // PayloadMaxBytes is the maximum number of line protocol bytes to POST in a single request. - PayloadMaxBytes int `mapstructure:"payload_max_bytes"` + PayloadMaxBytes int `mapstructure:"payload_max_bytes"` } type LogsConfig struct { // App token is the token of Sematext Monitoring App to which you want to send the logs. AppToken string `mapstructure:"app_token"` // LogsEndpoint specifies the endpoint for receiving logs in Sematext - LogsEndpoint string `mapstructure:"logs_endpoint"` + LogsEndpoint string `mapstructure:"logs_endpoint"` // LogRequests determines whether request tracking is enabled LogRequests bool `mapstructure:"logs_requests"` // LogMaxAge is the max number of days to retain old log files @@ -57,24 +58,23 @@ type LogsConfig struct { WriteEvents a.Bool } - // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { - if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom"{ + if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom" { return fmt.Errorf("invalid region: %s. please use either 'EU' or 'US'", cfg.Region) } - if len(cfg.MetricsConfig.AppToken) != 36{ + if len(cfg.MetricsConfig.AppToken) != 36 { return fmt.Errorf("invalid metrics app_token: %s. app_token should be 36 characters", cfg.MetricsConfig.AppToken) } - if len(cfg.LogsConfig.AppToken) != 36{ + if len(cfg.LogsConfig.AppToken) != 36 { return fmt.Errorf("invalid logs app_token: %s. app_token should be 36 characters", cfg.LogsConfig.AppToken) } if strings.ToLower(cfg.Region) == "eu" { - cfg.MetricsEndpoint ="https://spm-receiver.eu.sematext.com" - cfg.LogsEndpoint ="https://logsene-receiver.eu.sematext.com" + cfg.MetricsEndpoint = "https://spm-receiver.eu.sematext.com" + cfg.LogsEndpoint = "https://logsene-receiver.eu.sematext.com" } - if strings.ToLower(cfg.Region) == "us"{ - cfg.MetricsEndpoint ="https://spm-receiver.sematext.com" + if strings.ToLower(cfg.Region) == "us" { + cfg.MetricsEndpoint = "https://spm-receiver.sematext.com" cfg.LogsEndpoint = "https://logsene-receiver.sematext.com" } @@ -83,9 +83,12 @@ func (cfg *Config) Validate() error { // Bool provides an atomic boolean type. type Bool struct{ u Uint32 } + // Uint32 provides an atomic uint32 type. type Uint32 struct{ value uint32 } + // Load gets the value of atomic boolean. func (b *Bool) Load() bool { return b.u.Load() == 1 } + // Load get the value of atomic integer. func (u *Uint32) Load() uint32 { return a.LoadUint32(&u.value) } diff --git a/exporter/sematextexporter/config_test.go b/exporter/sematextexporter/config_test.go index 43a1c256213a..79516c582f46 100644 --- a/exporter/sematextexporter/config_test.go +++ b/exporter/sematextexporter/config_test.go @@ -39,8 +39,8 @@ func TestLoadConfig(t *testing.T) { id: component.NewIDWithName(metadata.Type, "override-config"), expected: &Config{ ClientConfig: confighttp.ClientConfig{ - Timeout: 500 * time.Millisecond, - Headers: map[string]configopaque.String{"User-Agent": "OpenTelemetry -> Sematext"}, + Timeout: 500 * time.Millisecond, + Headers: map[string]configopaque.String{"User-Agent": "OpenTelemetry -> Sematext"}, }, MetricsConfig: MetricsConfig{ MetricsEndpoint: "https://spm-receiver.sematext.com", @@ -49,20 +49,20 @@ func TestLoadConfig(t *testing.T) { NumConsumers: 3, QueueSize: 10, }, - AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", MetricsSchema: "telegraf-prometheus-v2", PayloadMaxLines: 72, PayloadMaxBytes: 27, }, LogsConfig: LogsConfig{ - AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - LogsEndpoint: "https://logsene-receiver.sematext.com", - LogRequests: true, - LogMaxAge: 2, - LogMaxBackups: 10, - LogMaxSize: 10, + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + LogsEndpoint: "https://logsene-receiver.sematext.com", + LogRequests: true, + LogMaxAge: 2, + LogMaxBackups: 10, + LogMaxSize: 10, }, - + BackOffConfig: configretry.BackOffConfig{ Enabled: true, InitialInterval: 1 * time.Second, @@ -71,7 +71,7 @@ func TestLoadConfig(t *testing.T) { RandomizationFactor: backoff.DefaultRandomizationFactor, Multiplier: backoff.DefaultMultiplier, }, - Region: "US", + Region: "US", }, }, } diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 334b60afd6bb..fd96e2fa474f 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -4,9 +4,10 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" import ( "fmt" + "os" "reflect" "strings" - "os" + "github.com/olivere/elastic" "github.com/sirupsen/logrus" "golang.org/x/net/context" @@ -15,8 +16,10 @@ import ( json "github.com/json-iterator/go" ) + // artificialDocType designates a syntenic doc type for ES documents const artificialDocType = "_doc" + type group struct { client *elastic.Client token string @@ -31,7 +34,7 @@ type client struct { // Client represents a minimal interface client implementation has to satisfy. type Client interface { - Bulk(body interface{},config *Config) error + Bulk(body interface{}, config *Config) error } // NewClient creates a new instance of ES client that internally stores a reference @@ -46,10 +49,10 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client return nil, err } clients[config.LogsEndpoint] = group{ - client: c, - token: config.LogsConfig.AppToken, - } + client: c, + token: config.LogsConfig.AppToken, } + } return &client{ clients: clients, @@ -61,58 +64,58 @@ func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client // Bulk processes a batch of documents and sends them to the specified LogsEndpoint. func (c *client) Bulk(body interface{}, config *Config) error { - if grp, ok := c.clients[config.LogsEndpoint]; ok { - bulkRequest := grp.client.Bulk() - if reflect.TypeOf(body).Kind() == reflect.Slice { - v := reflect.ValueOf(body) - for i := 0; i < v.Len(); i++ { - doc := v.Index(i).Interface() - if docMap, ok := doc.(map[string]interface{}); ok { - docMap["os.host"] = getHostname() - - } - - req := elastic.NewBulkIndexRequest(). - Index(grp.token). + if grp, ok := c.clients[config.LogsEndpoint]; ok { + bulkRequest := grp.client.Bulk() + if reflect.TypeOf(body).Kind() == reflect.Slice { + v := reflect.ValueOf(body) + for i := 0; i < v.Len(); i++ { + doc := v.Index(i).Interface() + if docMap, ok := doc.(map[string]interface{}); ok { + docMap["os.host"] = getHostname() + + } + + req := elastic.NewBulkIndexRequest(). + Index(grp.token). Type(artificialDocType). - Doc(doc) - bulkRequest.Add(req) - } - } - - if bulkRequest.NumberOfActions() > 0 { - payloadBytes, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("failed to serialize payload: %w", err) - } - - // Print or log the payload(Will delete this once everything is good) - fmt.Printf("Payload being sent to Sematext:\n%s\n", string(payloadBytes)) - - if c.config.LogRequests { - c.logger.Infof("Sending bulk to %s", config.LogsEndpoint) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - res, err := bulkRequest.Do(ctx) - if err != nil { - c.writePayload(string(payloadBytes), err.Error()) - return err - } - if res.Errors { - for _, item := range res.Failed() { - if item.Error != nil { - c.logger.Errorf("Document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) - } - } - } - - c.writePayload(string(payloadBytes), "200") - return nil - } - } - return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) + Doc(doc) + bulkRequest.Add(req) + } + } + + if bulkRequest.NumberOfActions() > 0 { + payloadBytes, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("failed to serialize payload: %w", err) + } + + // Print or log the payload(Will delete this once everything is good) + fmt.Printf("Payload being sent to Sematext:\n%s\n", string(payloadBytes)) + + if c.config.LogRequests { + c.logger.Infof("Sending bulk to %s", config.LogsEndpoint) + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + res, err := bulkRequest.Do(ctx) + if err != nil { + c.writePayload(string(payloadBytes), err.Error()) + return err + } + if res.Errors { + for _, item := range res.Failed() { + if item.Error != nil { + c.logger.Errorf("Document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) + } + } + } + + c.writePayload(string(payloadBytes), "200") + return nil + } + } + return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) } // writePayload writes a formatted payload along with its status to the configured writer. @@ -120,8 +123,8 @@ func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { c.writer.Write(Formatl(payload, status)) } else { - c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) - } + c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) + } } // Formatl delimits and formats the response returned by receiver. @@ -135,10 +138,10 @@ func Formatl(payload string, status string) string { } // getHostname retrieves the current machine's hostname. -func getHostname() (string) { +func getHostname() string { hostname, err := os.Hostname() if err != nil { return "None" } return hostname -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go new file mode 100644 index 000000000000..5be21fa168cb --- /dev/null +++ b/exporter/sematextexporter/es_test.go @@ -0,0 +1,4 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 7ce6c6a8e96d..86b622c48ec6 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -4,10 +4,11 @@ import ( "context" "fmt" "time" - "go.opentelemetry.io/collector/pdata/plog" + + "github.com/sirupsen/logrus" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" - "github.com/sirupsen/logrus" + "go.opentelemetry.io/collector/pdata/plog" "go.uber.org/zap" ) @@ -16,6 +17,7 @@ type sematextLogsExporter struct { client Client logger *logrus.Logger } + // newExporter creates a new instance of the sematextLogsExporter. func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { logger := logrus.New() @@ -37,26 +39,27 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { // pushLogsData processes and sends log data to Sematext in bulk. func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) error { - // Convert logs to bulk payload - bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) - if err != nil { - e.logger.Errorf("Failed to convert logs: %v", err) - return err - } - - // Debug: Print the bulk payload - for _, payload := range bulkPayload { - fmt.Printf("Bulk payload: %v\n", payload) - } - - // Send the bulk payload to Sematext - if err := e.client.Bulk(bulkPayload, e.config); err != nil { - e.logger.Errorf("Failed to send logs to Sematext: %v", err) - return err - } - - return nil + // Convert logs to bulk payload + bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) + if err != nil { + e.logger.Errorf("Failed to convert logs: %v", err) + return err + } + + // Debug: Print the bulk payload + for _, payload := range bulkPayload { + fmt.Printf("Bulk payload: %v\n", payload) + } + + // Send the bulk payload to Sematext + if err := e.client.Bulk(bulkPayload, e.config); err != nil { + e.logger.Errorf("Failed to send logs to Sematext: %v", err) + return err + } + + return nil } + // convertLogsToBulkPayload converts OpenTelemetry log data into a bulk payload for Sematext. func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]interface{}, error) { var bulkPayload []map[string]interface{} @@ -92,16 +95,18 @@ func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]int return bulkPayload, nil } + // Start initializes the Sematext Logs Exporter. func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) error { - if e.client == nil { - return fmt.Errorf("sematext client is not initialized") - } - e.logger.Info("Starting Sematext Logs Exporter...") - return nil + if e.client == nil { + return fmt.Errorf("sematext client is not initialized") + } + e.logger.Info("Starting Sematext Logs Exporter...") + return nil } + // Shutdown gracefully shuts down the Sematext Logs Exporter. func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { e.logger.Info("Shutting down Sematext Logs Exporter...") return nil -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index f1cd0e2c76ec..a5e1318efcbe 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -10,6 +10,7 @@ import ( "fmt" "time" + "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/influxdb-observability/otel2influx" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" @@ -17,12 +18,12 @@ import ( "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/exporterhelper" - "github.com/influxdata/influxdb-observability/common" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter/internal/metadata" ) const appToken string = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + // NewFactory creates a factory for the Sematext metrics exporter. func NewFactory() exporter.Factory { return exporter.NewFactory( @@ -34,7 +35,7 @@ func NewFactory() exporter.Factory { } func createDefaultConfig() component.Config { - + cfg := &Config{ ClientConfig: confighttp.ClientConfig{ Timeout: 5 * time.Second, @@ -44,20 +45,20 @@ func createDefaultConfig() component.Config { }, MetricsConfig: MetricsConfig{ MetricsSchema: common.MetricsSchemaTelegrafPrometheusV2.String(), - AppToken: appToken, + AppToken: appToken, QueueSettings: exporterhelper.NewDefaultQueueConfig(), PayloadMaxLines: 1_000, PayloadMaxBytes: 300_000, }, LogsConfig: LogsConfig{ - AppToken: appToken, - LogRequests:true, - LogMaxAge: 2, - LogMaxSize: 10, + AppToken: appToken, + LogRequests: true, + LogMaxAge: 2, + LogMaxSize: 10, LogMaxBackups: 10, }, - BackOffConfig: configretry.NewDefaultBackOffConfig(), - Region: "custom", + BackOffConfig: configretry.NewDefaultBackOffConfig(), + Region: "custom", } cfg.LogsConfig.WriteEvents.Store(false) return cfg @@ -102,6 +103,7 @@ func createMetricsExporter( exporterhelper.WithStart(writer.Start), ) } + // createLogsExporter creates a new logs exporter for Sematext. func createLogsExporter( ctx context.Context, @@ -124,4 +126,4 @@ func createLogsExporter( exporterhelper.WithStart(exporter.Start), exporterhelper.WithShutdown(exporter.Shutdown), ) -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/flat_formatter.go b/exporter/sematextexporter/flat_formatter.go index 441a5bff19dc..aab52b9c2e39 100644 --- a/exporter/sematextexporter/flat_formatter.go +++ b/exporter/sematextexporter/flat_formatter.go @@ -5,8 +5,9 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "bytes" - "github.com/sirupsen/logrus" "time" + + "github.com/sirupsen/logrus" ) const defaultTimestampFormat = time.RFC3339 @@ -20,4 +21,4 @@ func (f *FlatFormatter) Format(entry *logrus.Entry) ([]byte, error) { b.WriteString(entry.Time.Format(defaultTimestampFormat) + " " + entry.Message) b.WriteByte('\n') return b.Bytes(), nil -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/generated_package_test.go b/exporter/sematextexporter/generated_package_test.go index 40fec63335b3..770fcd6bf246 100644 --- a/exporter/sematextexporter/generated_package_test.go +++ b/exporter/sematextexporter/generated_package_test.go @@ -3,8 +3,9 @@ package sematextexporter import ( - "go.uber.org/goleak" "testing" + + "go.uber.org/goleak" ) func TestMain(m *testing.M) { diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 15304e1fa7f3..952ae963cde0 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -16,15 +16,15 @@ import ( "sync" "time" - "gopkg.in/natefinch/lumberjack.v2" - fs "github.com/rifflock/lfshook" - "github.com/sirupsen/logrus" "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/influxdb-observability/otel2influx" "github.com/influxdata/line-protocol/v2/lineprotocol" + fs "github.com/rifflock/lfshook" + "github.com/sirupsen/logrus" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/consumer/consumererror" + "gopkg.in/natefinch/lumberjack.v2" ) var _ otel2influx.InfluxWriter = (*sematextHTTPWriter)(nil) @@ -63,13 +63,13 @@ func newSematextHTTPWriter(logger common.Logger, config *Config, telemetrySettin return e }, }, - telemetrySettings: telemetrySettings, - writeURL: writeURL, - payloadMaxLines: config.PayloadMaxLines, - payloadMaxBytes: config.PayloadMaxBytes, - logger: logger, - hostname: hostname, - token: config.MetricsConfig.AppToken, + telemetrySettings: telemetrySettings, + writeURL: writeURL, + payloadMaxLines: config.PayloadMaxLines, + payloadMaxBytes: config.PayloadMaxBytes, + logger: logger, + hostname: hostname, + token: config.MetricsConfig.AppToken, }, nil } @@ -194,13 +194,13 @@ func (b *sematextHTTPWriterBatch) WriteBatch(ctx context.Context) error { } switch res.StatusCode { - case 200, 204: - break - case 500: - return fmt.Errorf("line protocol write returned %q %q", res.Status, string(body)) - default: - return consumererror.NewPermanent(fmt.Errorf("line protocol write returned %q %q", res.Status, string(body))) - } + case 200, 204: + break + case 500: + return fmt.Errorf("line protocol write returned %q %q", res.Status, string(body)) + default: + return consumererror.NewPermanent(fmt.Errorf("line protocol write returned %q %q", res.Status, string(body))) + } return nil } @@ -260,6 +260,7 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st } return } + //Logs Support // FlatWriter writes a raw message to log file. @@ -271,7 +272,7 @@ type FlatWriter struct { func NewFlatWriter(f string, c *Config) (*FlatWriter, error) { l := logrus.New() l.Out = io.Discard - + hook, err := InitRotate( f, c.LogMaxAge, @@ -316,6 +317,7 @@ func InitRotate(filePath string, maxAge, maxBackups, maxSize int, f logrus.Forma } return h, nil } + // RotateFileConfig is the configuration for the rotate file hook. type RotateFileConfig struct { Filename string @@ -325,6 +327,7 @@ type RotateFileConfig struct { Level logrus.Level Formatter logrus.Formatter } + // RotateFile represents the rotate file hook. type RotateFile struct { Config RotateFileConfig @@ -345,6 +348,7 @@ func NewRotateFile(config RotateFileConfig) (logrus.Hook, error) { } return &hook, nil } + // Fire is called by logrus when it is about to write the log entry. func (hook *RotateFile) Fire(entry *logrus.Entry) error { b, err := hook.Config.Formatter.Format(entry) @@ -356,6 +360,7 @@ func (hook *RotateFile) Fire(entry *logrus.Entry) error { } return nil } + // Levels determines log levels that for which the logs are written. func (hook *RotateFile) Levels() []logrus.Level { return logrus.AllLevels[:hook.Config.Level+1] diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index 8ebffda21d7f..3e46a763c8c3 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -1,6 +1,7 @@ package sematextexporter import ( + "bytes" "context" "fmt" "io" @@ -10,14 +11,13 @@ import ( "sync" "testing" "time" - "bytes" "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/line-protocol/v2/lineprotocol" + "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" - "github.com/sirupsen/logrus" ) func TestSematextHTTPWriterBatchOptimizeTags(t *testing.T) { @@ -166,10 +166,10 @@ func TestSematextHTTPWriterBatchEnqueuePointEmptyTagValue(t *testing.T) { new(common.NoopLogger), &Config{ MetricsConfig: MetricsConfig{ - MetricsEndpoint:noopHTTPServer.URL , - AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + MetricsEndpoint: noopHTTPServer.URL, + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", }, - Region: "US", + Region: "US", }, componenttest.NewNopTelemetrySettings()) require.NoError(t, err) @@ -180,9 +180,9 @@ func TestSematextHTTPWriterBatchEnqueuePointEmptyTagValue(t *testing.T) { context.Background(), "m", map[string]string{"k": "v", "empty": ""}, - map[string]any{"f": int64(1)}, - nowTime, - common.InfluxMetricValueTypeUntyped) + map[string]any{"f": int64(1)}, + nowTime, + common.InfluxMetricValueTypeUntyped) require.NoError(t, err) err = sematextWriterBatch.WriteBatch(context.Background()) @@ -190,7 +190,7 @@ func TestSematextHTTPWriterBatchEnqueuePointEmptyTagValue(t *testing.T) { require.NoError(t, err) if assert.NotNil(t, recordedRequest) { - expected:= fmt.Sprintf("m,k=v,os.host=%s,token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx f=1i 1628605794318000000", sematextWriter.hostname) + expected := fmt.Sprintf("m,k=v,os.host=%s,token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx f=1i 1628605794318000000", sematextWriter.hostname) assert.Equal(t, expected, strings.TrimSpace(string(recordedRequestBody))) } } @@ -201,7 +201,7 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { Region: "us", MetricsConfig: MetricsConfig{ MetricsEndpoint: "http://localhost:8080", - MetricsSchema: "telegraf-prometheus-v2", + MetricsSchema: "telegraf-prometheus-v2", }, } _, err := composeWriteURL(cfg) @@ -213,9 +213,8 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { Region: "eu", MetricsConfig: MetricsConfig{ MetricsEndpoint: "http://localhost:8080", - MetricsSchema: "telegraf-prometheus-v2", + MetricsSchema: "telegraf-prometheus-v2", }, - } _, err := composeWriteURL(cfg) assert.NoError(t, err) @@ -224,11 +223,10 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { func TestNewFlatWriter(t *testing.T) { config := &Config{ LogsConfig: LogsConfig{ - LogMaxAge: 7, - LogMaxBackups: 5, - LogMaxSize: 10, + LogMaxAge: 7, + LogMaxBackups: 5, + LogMaxSize: 10, }, - } writer, err := NewFlatWriter("test.log", config) assert.NoError(t, err) @@ -298,4 +296,4 @@ func TestRotateFileLevels(t *testing.T) { expectedLevels := logrus.AllLevels[:logrus.WarnLevel+1] assert.Equal(t, expectedLevels, hook.Levels()) -} \ No newline at end of file +} From 14d74d20086437c0a64567e3cbcbedd7b97f051e Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 10:51:43 +0100 Subject: [PATCH 19/52] Fix lint issues --- exporter/sematextexporter/es.go | 4 +--- exporter/sematextexporter/factory.go | 1 - exporter/sematextexporter/writer.go | 5 +---- exporter/sematextexporter/writer_test.go | 7 ++++++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index fd96e2fa474f..9fbd1d3985af 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -7,13 +7,12 @@ import ( "os" "reflect" "strings" + "time" "github.com/olivere/elastic" "github.com/sirupsen/logrus" "golang.org/x/net/context" - "time" - json "github.com/json-iterator/go" ) @@ -72,7 +71,6 @@ func (c *client) Bulk(body interface{}, config *Config) error { doc := v.Index(i).Interface() if docMap, ok := doc.(map[string]interface{}); ok { docMap["os.host"] = getHostname() - } req := elastic.NewBulkIndexRequest(). diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index a5e1318efcbe..b11de37f8304 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -35,7 +35,6 @@ func NewFactory() exporter.Factory { } func createDefaultConfig() component.Config { - cfg := &Config{ ClientConfig: confighttp.ClientConfig{ Timeout: 5 * time.Second, diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 952ae963cde0..3fe5287c3286 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -12,7 +12,6 @@ import ( "net/url" "os" "sort" - "sync" "time" @@ -51,7 +50,6 @@ func newSematextHTTPWriter(logger common.Logger, config *Config, telemetrySettin hostname, err := os.Hostname() if err != nil { return nil, fmt.Errorf("could not detect hostname: %w", err) - } return &sematextHTTPWriter{ @@ -261,7 +259,7 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st return } -//Logs Support +// Logs Support // FlatWriter writes a raw message to log file. type FlatWriter struct { @@ -336,7 +334,6 @@ type RotateFile struct { // NewRotateFile builds a new rotate file hook. func NewRotateFile(config RotateFileConfig) (logrus.Hook, error) { - hook := RotateFile{ Config: config, } diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index 3e46a763c8c3..0892b03ce809 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -149,7 +149,6 @@ func TestSematextHTTPWriterBatchEnqueuePointEmptyTagValue(t *testing.T) { var recordedRequest *http.Request var recordedRequestBody []byte noopHTTPServer := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { - if assert.Nil(t, recordedRequest) { var err error recordedRequest = r @@ -220,6 +219,7 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { assert.NoError(t, err) }) } + func TestNewFlatWriter(t *testing.T) { config := &Config{ LogsConfig: LogsConfig{ @@ -233,6 +233,7 @@ func TestNewFlatWriter(t *testing.T) { assert.NotNil(t, writer) assert.NotNil(t, writer.l) } + func TestFlatWriterWrite(t *testing.T) { var buf bytes.Buffer logger := logrus.New() @@ -244,11 +245,13 @@ func TestFlatWriterWrite(t *testing.T) { assert.Contains(t, buf.String(), message) } + func TestInitRotate(t *testing.T) { hook, err := InitRotate("test.log", 7, 5, 10, &FlatFormatter{}) assert.NoError(t, err) assert.NotNil(t, hook) } + func TestNewRotateFile(t *testing.T) { config := RotateFileConfig{ Filename: "test.log", @@ -263,6 +266,7 @@ func TestNewRotateFile(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, hook) } + func TestRotateFileFire(t *testing.T) { var buf bytes.Buffer @@ -287,6 +291,7 @@ func TestRotateFileFire(t *testing.T) { assert.NoError(t, err) assert.Contains(t, buf.String(), "test entry") } + func TestRotateFileLevels(t *testing.T) { hook := &RotateFile{ Config: RotateFileConfig{ From d3bb69405ebba51c5d82f456497a3956a3a240d9 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 10:54:41 +0100 Subject: [PATCH 20/52] Remove unnecessary alias --- exporter/sematextexporter/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 67e0e5800305..7b20e6b569e8 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -6,7 +6,7 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "fmt" "strings" - a "sync/atomic" + "sync/atomic" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configretry" @@ -55,7 +55,7 @@ type LogsConfig struct { // LogMaxSize is the maximum size in megabytes of the log file before it gets rotated LogMaxSize int `mapstructure:"logs_max_size"` // WriteEvents determines if events are logged - WriteEvents a.Bool + WriteEvents atomic.Bool } // Validate checks for invalid or missing entries in the configuration. @@ -91,4 +91,4 @@ type Uint32 struct{ value uint32 } func (b *Bool) Load() bool { return b.u.Load() == 1 } // Load get the value of atomic integer. -func (u *Uint32) Load() uint32 { return a.LoadUint32(&u.value) } +func (u *Uint32) Load() uint32 { return atomic.LoadUint32(&u.value) } From b31b20527419fe685dc4304d47fb583cb44dd65f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 11:42:52 +0100 Subject: [PATCH 21/52] Add mapstructure for write events --- exporter/sematextexporter/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 7b20e6b569e8..e2b5e3b7d8be 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -55,7 +55,7 @@ type LogsConfig struct { // LogMaxSize is the maximum size in megabytes of the log file before it gets rotated LogMaxSize int `mapstructure:"logs_max_size"` // WriteEvents determines if events are logged - WriteEvents atomic.Bool + WriteEvents atomic.Bool `mapstructure:"write_events"` } // Validate checks for invalid or missing entries in the configuration. From 886b1acda581ed75d283f63a611a30b048e50efc Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 11:50:22 +0100 Subject: [PATCH 22/52] Fix otel issues --- exporter/sematextexporter/es.go | 6 +++--- exporter/sematextexporter/exporter.go | 7 +++++-- exporter/sematextexporter/writer.go | 10 +++++----- exporter/sematextexporter/writer_test.go | 9 ++++++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 9fbd1d3985af..bf79e521ed6b 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -38,7 +38,7 @@ type Client interface { // NewClient creates a new instance of ES client that internally stores a reference // to both, event and log receivers. -func NewClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client, error) { +func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client, error) { clients := make(map[string]group) // client for shipping to logsene @@ -119,14 +119,14 @@ func (c *client) Bulk(body interface{}, config *Config) error { // writePayload writes a formatted payload along with its status to the configured writer. func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { - c.writer.Write(Formatl(payload, status)) + c.writer.Write(formatl(payload, status)) } else { c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) } } // Formatl delimits and formats the response returned by receiver. -func Formatl(payload string, status string) string { +func formatl(payload string, status string) string { s := strings.TrimLeft(status, "\n") i := strings.Index(s, "\n") if i > 0 { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 86b622c48ec6..1966fdd2666b 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -1,4 +1,7 @@ -package sematextexporter +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" import ( "context" @@ -24,7 +27,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { logger.SetFormatter(&FlatFormatter{}) // Initialize Sematext client - client, err := NewClient(cfg, logger, FlatWriter{}) + client, err := newClient(cfg, logger, FlatWriter{}) if err != nil { set.Logger.Error("Failed to create Sematext client", zap.Error(err)) return nil diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 3fe5287c3286..de6e61b063a8 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -267,11 +267,11 @@ type FlatWriter struct { } // NewFlatWriter creates a new instance of flat writer. -func NewFlatWriter(f string, c *Config) (*FlatWriter, error) { +func newFlatWriter(f string, c *Config) (*FlatWriter, error) { l := logrus.New() l.Out = io.Discard - hook, err := InitRotate( + hook, err := initRotate( f, c.LogMaxAge, c.LogMaxBackups, @@ -295,8 +295,8 @@ func (w *FlatWriter) Write(message string) { // InitRotate returns a new fs hook that enables log file rotation with specified pattern, // maximum size/TTL for existing log files. -func InitRotate(filePath string, maxAge, maxBackups, maxSize int, f logrus.Formatter) (logrus.Hook, error) { - h, err := NewRotateFile(RotateFileConfig{ +func initRotate(filePath string, maxAge, maxBackups, maxSize int, f logrus.Formatter) (logrus.Hook, error) { + h, err := newRotateFile(RotateFileConfig{ Filename: filePath, MaxAge: maxAge, MaxBackups: maxBackups, @@ -333,7 +333,7 @@ type RotateFile struct { } // NewRotateFile builds a new rotate file hook. -func NewRotateFile(config RotateFileConfig) (logrus.Hook, error) { +func newRotateFile(config RotateFileConfig) (logrus.Hook, error) { hook := RotateFile{ Config: config, } diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index 0892b03ce809..e22d73772d1e 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package sematextexporter import ( @@ -228,7 +231,7 @@ func TestNewFlatWriter(t *testing.T) { LogMaxSize: 10, }, } - writer, err := NewFlatWriter("test.log", config) + writer, err := newFlatWriter("test.log", config) assert.NoError(t, err) assert.NotNil(t, writer) assert.NotNil(t, writer.l) @@ -247,7 +250,7 @@ func TestFlatWriterWrite(t *testing.T) { } func TestInitRotate(t *testing.T) { - hook, err := InitRotate("test.log", 7, 5, 10, &FlatFormatter{}) + hook, err := initRotate("test.log", 7, 5, 10, &FlatFormatter{}) assert.NoError(t, err) assert.NotNil(t, hook) } @@ -262,7 +265,7 @@ func TestNewRotateFile(t *testing.T) { Formatter: &FlatFormatter{}, } - hook, err := NewRotateFile(config) + hook, err := newRotateFile(config) assert.NoError(t, err) assert.NotNil(t, hook) } From 17e4f25ea11a0db4a2f81ad36d8f2ddeff405f87 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 12:02:13 +0100 Subject: [PATCH 23/52] Fix more lint issues --- exporter/sematextexporter/exporter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 1966fdd2666b..047de1fa20ed 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -41,7 +41,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { } // pushLogsData processes and sends log data to Sematext in bulk. -func (e *sematextLogsExporter) pushLogsData(ctx context.Context, logs plog.Logs) error { +func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) error { // Convert logs to bulk payload bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) if err != nil { @@ -100,7 +100,7 @@ func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]int } // Start initializes the Sematext Logs Exporter. -func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) error { +func (e *sematextLogsExporter) Start(_ context.Context, host component.Host) error { if e.client == nil { return fmt.Errorf("sematext client is not initialized") } @@ -109,7 +109,7 @@ func (e *sematextLogsExporter) Start(ctx context.Context, host component.Host) e } // Shutdown gracefully shuts down the Sematext Logs Exporter. -func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { +func (e *sematextLogsExporter) Shutdown(_ context.Context) error { e.logger.Info("Shutting down Sematext Logs Exporter...") return nil } From 82602653b929589045de536aba2ac654b6ecee29 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 5 Dec 2024 14:39:15 +0100 Subject: [PATCH 24/52] Fix CI issues --- exporter/sematextexporter/exporter.go | 5 ++++- exporter/sematextexporter/writer.go | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 047de1fa20ed..0f33e51f46d7 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -100,7 +100,7 @@ func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]int } // Start initializes the Sematext Logs Exporter. -func (e *sematextLogsExporter) Start(_ context.Context, host component.Host) error { +func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error { if e.client == nil { return fmt.Errorf("sematext client is not initialized") } @@ -110,6 +110,9 @@ func (e *sematextLogsExporter) Start(_ context.Context, host component.Host) err // Shutdown gracefully shuts down the Sematext Logs Exporter. func (e *sematextLogsExporter) Shutdown(_ context.Context) error { + if e.logger == nil { + return fmt.Errorf("logger is not initialized") + } e.logger.Info("Shutting down Sematext Logs Exporter...") return nil } diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index de6e61b063a8..a393cbef6251 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -334,6 +334,9 @@ type RotateFile struct { // NewRotateFile builds a new rotate file hook. func newRotateFile(config RotateFileConfig) (logrus.Hook, error) { + if config.Filename == "" { + return nil, fmt.Errorf("filename is required") + } hook := RotateFile{ Config: config, } From 75725c04a769cf334838ed4e6c8a43ea67bbf3be Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 6 Dec 2024 20:00:04 +0100 Subject: [PATCH 25/52] App token is not being passed as ._index --- exporter/sematextexporter/exporter.go | 16 ++++------------ exporter/sematextexporter/factory.go | 4 ++-- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 0f33e51f46d7..b81f8a1dc3ee 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -43,7 +43,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { // pushLogsData processes and sends log data to Sematext in bulk. func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) error { // Convert logs to bulk payload - bulkPayload, err := convertLogsToBulkPayload(logs, e.config.LogsConfig.AppToken) + bulkPayload, err := convertLogsToBulkPayload(logs) if err != nil { e.logger.Errorf("Failed to convert logs: %v", err) return err @@ -64,7 +64,7 @@ func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) e } // convertLogsToBulkPayload converts OpenTelemetry log data into a bulk payload for Sematext. -func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]interface{}, error) { +func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) { var bulkPayload []map[string]interface{} resourceLogs := logs.ResourceLogs() @@ -77,14 +77,6 @@ func convertLogsToBulkPayload(logs plog.Logs, appToken string) ([]map[string]int for k := 0; k < logRecords.Len(); k++ { record := logRecords.At(k) - // Add metadata for indexing - meta := map[string]interface{}{ - "index": map[string]interface{}{ - "_index": appToken, - }, - } - bulkPayload = append(bulkPayload, meta) - // Build the log entry logEntry := map[string]interface{}{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), @@ -111,8 +103,8 @@ func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error // Shutdown gracefully shuts down the Sematext Logs Exporter. func (e *sematextLogsExporter) Shutdown(_ context.Context) error { if e.logger == nil { - return fmt.Errorf("logger is not initialized") - } + return fmt.Errorf("logger is not initialized") + } e.logger.Info("Shutting down Sematext Logs Exporter...") return nil } diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index b11de37f8304..ec40c92f6e47 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -8,6 +8,7 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "context" "fmt" + "sync/atomic" "time" "github.com/influxdata/influxdb-observability/common" @@ -55,6 +56,7 @@ func createDefaultConfig() component.Config { LogMaxAge: 2, LogMaxSize: 10, LogMaxBackups: 10, + WriteEvents: atomic.Bool{}, }, BackOffConfig: configretry.NewDefaultBackOffConfig(), Region: "custom", @@ -120,9 +122,7 @@ func createLogsExporter( set, cfg, exporter.pushLogsData, // Function to process and send logs - exporterhelper.WithQueue(cf.MetricsConfig.QueueSettings), exporterhelper.WithRetry(cf.BackOffConfig), exporterhelper.WithStart(exporter.Start), - exporterhelper.WithShutdown(exporter.Shutdown), ) } From 4513518153da94f8c926c43d5f70ea6743c77a09 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 09:06:42 +0100 Subject: [PATCH 26/52] Fix CI issues, removed "custom" from being an option in the regions --- exporter/sematextexporter/config.go | 2 +- exporter/sematextexporter/exporter.go | 61 +++++++++++++++++++------ exporter/sematextexporter/factory.go | 6 ++- exporter/sematextexporter/metadata.yaml | 2 +- exporter/sematextexporter/writer.go | 8 +++- 5 files changed, 61 insertions(+), 18 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index e2b5e3b7d8be..f61d8a0f716a 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -60,7 +60,7 @@ type LogsConfig struct { // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { - if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" && strings.ToLower(cfg.Region) != "custom" { + if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us"{ return fmt.Errorf("invalid region: %s. please use either 'EU' or 'US'", cfg.Region) } if len(cfg.MetricsConfig.AppToken) != 36 { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index b81f8a1dc3ee..9f29efdb9de4 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -76,7 +76,11 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) logRecords := scopeLogs.At(j).LogRecords() for k := 0; k < logRecords.Len(); k++ { record := logRecords.At(k) - + // Extract severity and provide a default value if empty + severity := record.SeverityText() + if severity == "" { + severity = "INFO" // Default severity if missing + } // Build the log entry logEntry := map[string]interface{}{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), @@ -90,21 +94,50 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) return bulkPayload, nil } - // Start initializes the Sematext Logs Exporter. func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error { - if e.client == nil { - return fmt.Errorf("sematext client is not initialized") - } - e.logger.Info("Starting Sematext Logs Exporter...") - return nil + // Create a new logger with a FlatFormatter + logger := logrus.New() + logger.SetFormatter(&FlatFormatter{}) + + // Initialize the Sematext client + client, err := newClient(e.config, logger, FlatWriter{}) + if err != nil { + e.logger.Errorf("Failed to initialize Sematext client: %v", err) + return fmt.Errorf("failed to initialize Sematext client: %w", err) + } + if client == nil { + e.logger.Error("Sematext client is not initialized (nil)") + return fmt.Errorf("sematext client is not initialized") + } + + // Assign the client and logger to the exporter + e.client = client + e.logger = logger + + // Log a success message + e.logger.Info("Sematext Logs Exporter successfully started") + return nil } - // Shutdown gracefully shuts down the Sematext Logs Exporter. -func (e *sematextLogsExporter) Shutdown(_ context.Context) error { - if e.logger == nil { - return fmt.Errorf("logger is not initialized") - } - e.logger.Info("Shutting down Sematext Logs Exporter...") - return nil +func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { + if e.logger == nil { + return fmt.Errorf("logger is not initialized") + } + + e.logger.Info("Shutting down Sematext Logs Exporter...") + + // Stop ElasticSearch client's background goroutines + if e.client != nil { + for endpoint, grp := range e.client.(*client).clients { + if grp.client != nil { + e.logger.Debugf("Stopping ElasticSearch client for endpoint: %s", endpoint) + grp.client.Stop() // Stop the ElasticSearch client's healthchecker goroutines + } + } + } + + // Log completion of shutdown + e.logger.Info("Sematext Logs Exporter shutdown complete") + return nil } diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index ec40c92f6e47..44c293a5e696 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -44,6 +44,7 @@ func createDefaultConfig() component.Config { }, }, MetricsConfig: MetricsConfig{ + MetricsEndpoint: "https://spm-receiver.sematext.com" , MetricsSchema: common.MetricsSchemaTelegrafPrometheusV2.String(), AppToken: appToken, QueueSettings: exporterhelper.NewDefaultQueueConfig(), @@ -51,6 +52,7 @@ func createDefaultConfig() component.Config { PayloadMaxBytes: 300_000, }, LogsConfig: LogsConfig{ + LogsEndpoint: "https://logsene-receiver.sematext.com", AppToken: appToken, LogRequests: true, LogMaxAge: 2, @@ -59,7 +61,7 @@ func createDefaultConfig() component.Config { WriteEvents: atomic.Bool{}, }, BackOffConfig: configretry.NewDefaultBackOffConfig(), - Region: "custom", + Region: "us", } cfg.LogsConfig.WriteEvents.Store(false) return cfg @@ -102,6 +104,7 @@ func createMetricsExporter( exporterhelper.WithQueue(cfg.MetricsConfig.QueueSettings), exporterhelper.WithRetry(cfg.BackOffConfig), exporterhelper.WithStart(writer.Start), + exporterhelper.WithShutdown(writer.Shutdown), ) } @@ -124,5 +127,6 @@ func createLogsExporter( exporter.pushLogsData, // Function to process and send logs exporterhelper.WithRetry(cf.BackOffConfig), exporterhelper.WithStart(exporter.Start), + exporterhelper.WithShutdown(exporter.Shutdown), ) } diff --git a/exporter/sematextexporter/metadata.yaml b/exporter/sematextexporter/metadata.yaml index 6c10e0f22f0b..6e764db0ac65 100644 --- a/exporter/sematextexporter/metadata.yaml +++ b/exporter/sematextexporter/metadata.yaml @@ -6,7 +6,7 @@ status: development: [metrics,logs] distributions: [contrib] codeowners: - active: [Eromosele-SM] + active: [AkhigbeEromo] tests: expect_consumer_error: true diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index a393cbef6251..476d43796323 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -98,7 +98,13 @@ func (w *sematextHTTPWriter) Start(ctx context.Context, host component.Host) err w.httpClient = httpClient return nil } - +func (w *sematextHTTPWriter) Shutdown(ctx context.Context) error { + if w.httpClient != nil { + w.httpClient.CloseIdleConnections() // Closes all idle connections for the HTTP client + } + w.logger.Debug("HTTP client connections closed successfully for Sematext HTTP Writer") + return nil +} func (w *sematextHTTPWriter) NewBatch() otel2influx.InfluxWriterBatch { return newSematextHTTPWriterBatch(w) } From 27ceed9645d6f6c37ee489db30d74c0bf62fc639 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 14:11:32 +0100 Subject: [PATCH 27/52] Fix CI issues Made default Severity as INFO --- exporter/sematextexporter/exporter.go | 4 ++-- exporter/sematextexporter/writer.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 9f29efdb9de4..bd79b7e00756 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -85,7 +85,7 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) logEntry := map[string]interface{}{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), "message": record.Body().AsString(), - "severity": record.SeverityText(), + "severity": severity, } bulkPayload = append(bulkPayload, logEntry) } @@ -120,7 +120,7 @@ func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error return nil } // Shutdown gracefully shuts down the Sematext Logs Exporter. -func (e *sematextLogsExporter) Shutdown(ctx context.Context) error { +func (e *sematextLogsExporter) Shutdown(_ context.Context) error { if e.logger == nil { return fmt.Errorf("logger is not initialized") } diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 476d43796323..df41a776f582 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -98,7 +98,7 @@ func (w *sematextHTTPWriter) Start(ctx context.Context, host component.Host) err w.httpClient = httpClient return nil } -func (w *sematextHTTPWriter) Shutdown(ctx context.Context) error { +func (w *sematextHTTPWriter) Shutdown(_ context.Context) error { if w.httpClient != nil { w.httpClient.CloseIdleConnections() // Closes all idle connections for the HTTP client } From 9bd25b14524d547e0ae98b47894b9fd95f8c7cb1 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 14:33:13 +0100 Subject: [PATCH 28/52] Fix lint issues --- exporter/sematextexporter/README.md | 2 +- exporter/sematextexporter/config.go | 2 +- exporter/sematextexporter/exporter.go | 94 ++++++++++++++------------- exporter/sematextexporter/factory.go | 4 +- exporter/sematextexporter/writer.go | 10 +-- 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index 7baaab2908e1..abf7673cc0c4 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -5,7 +5,7 @@ | Stability | [development]: metrics, logs | | Distributions | [contrib] | | Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fsematext%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fsematext) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fsematext%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fsematext) | -| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@Eromosele-SM](https://www.github.com/Eromosele-SM) | +| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@AkhigbeEromo](https://www.github.com/AkhigbeEromo) | [development]: https://github.com/open-telemetry/opentelemetry-collector#development [contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index f61d8a0f716a..1cf8417abdee 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -60,7 +60,7 @@ type LogsConfig struct { // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { - if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us"{ + if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" { return fmt.Errorf("invalid region: %s. please use either 'EU' or 'US'", cfg.Region) } if len(cfg.MetricsConfig.AppToken) != 36 { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index bd79b7e00756..3e99b4fef6bf 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -76,11 +76,11 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) logRecords := scopeLogs.At(j).LogRecords() for k := 0; k < logRecords.Len(); k++ { record := logRecords.At(k) - // Extract severity and provide a default value if empty - severity := record.SeverityText() - if severity == "" { - severity = "INFO" // Default severity if missing - } + // Extract severity and provide a default value if empty + severity := record.SeverityText() + if severity == "" { + severity = "INFO" // Default severity if missing + } // Build the log entry logEntry := map[string]interface{}{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), @@ -94,50 +94,52 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) return bulkPayload, nil } + // Start initializes the Sematext Logs Exporter. func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error { - // Create a new logger with a FlatFormatter - logger := logrus.New() - logger.SetFormatter(&FlatFormatter{}) - - // Initialize the Sematext client - client, err := newClient(e.config, logger, FlatWriter{}) - if err != nil { - e.logger.Errorf("Failed to initialize Sematext client: %v", err) - return fmt.Errorf("failed to initialize Sematext client: %w", err) - } - if client == nil { - e.logger.Error("Sematext client is not initialized (nil)") - return fmt.Errorf("sematext client is not initialized") - } - - // Assign the client and logger to the exporter - e.client = client - e.logger = logger - - // Log a success message - e.logger.Info("Sematext Logs Exporter successfully started") - return nil + // Create a new logger with a FlatFormatter + logger := logrus.New() + logger.SetFormatter(&FlatFormatter{}) + + // Initialize the Sematext client + client, err := newClient(e.config, logger, FlatWriter{}) + if err != nil { + e.logger.Errorf("Failed to initialize Sematext client: %v", err) + return fmt.Errorf("failed to initialize Sematext client: %w", err) + } + if client == nil { + e.logger.Error("Sematext client is not initialized (nil)") + return fmt.Errorf("sematext client is not initialized") + } + + // Assign the client and logger to the exporter + e.client = client + e.logger = logger + + // Log a success message + e.logger.Info("Sematext Logs Exporter successfully started") + return nil } + // Shutdown gracefully shuts down the Sematext Logs Exporter. func (e *sematextLogsExporter) Shutdown(_ context.Context) error { - if e.logger == nil { - return fmt.Errorf("logger is not initialized") - } - - e.logger.Info("Shutting down Sematext Logs Exporter...") - - // Stop ElasticSearch client's background goroutines - if e.client != nil { - for endpoint, grp := range e.client.(*client).clients { - if grp.client != nil { - e.logger.Debugf("Stopping ElasticSearch client for endpoint: %s", endpoint) - grp.client.Stop() // Stop the ElasticSearch client's healthchecker goroutines - } - } - } - - // Log completion of shutdown - e.logger.Info("Sematext Logs Exporter shutdown complete") - return nil + if e.logger == nil { + return fmt.Errorf("logger is not initialized") + } + + e.logger.Info("Shutting down Sematext Logs Exporter...") + + // Stop ElasticSearch client's background goroutines + if e.client != nil { + for endpoint, grp := range e.client.(*client).clients { + if grp.client != nil { + e.logger.Debugf("Stopping ElasticSearch client for endpoint: %s", endpoint) + grp.client.Stop() // Stop the ElasticSearch client's healthchecker goroutines + } + } + } + + // Log completion of shutdown + e.logger.Info("Sematext Logs Exporter shutdown complete") + return nil } diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 44c293a5e696..42b9ea1860d8 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -44,7 +44,7 @@ func createDefaultConfig() component.Config { }, }, MetricsConfig: MetricsConfig{ - MetricsEndpoint: "https://spm-receiver.sematext.com" , + MetricsEndpoint: "https://spm-receiver.sematext.com", MetricsSchema: common.MetricsSchemaTelegrafPrometheusV2.String(), AppToken: appToken, QueueSettings: exporterhelper.NewDefaultQueueConfig(), @@ -52,7 +52,7 @@ func createDefaultConfig() component.Config { PayloadMaxBytes: 300_000, }, LogsConfig: LogsConfig{ - LogsEndpoint: "https://logsene-receiver.sematext.com", + LogsEndpoint: "https://logsene-receiver.sematext.com", AppToken: appToken, LogRequests: true, LogMaxAge: 2, diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index df41a776f582..2b4b38dc77b5 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -99,11 +99,11 @@ func (w *sematextHTTPWriter) Start(ctx context.Context, host component.Host) err return nil } func (w *sematextHTTPWriter) Shutdown(_ context.Context) error { - if w.httpClient != nil { - w.httpClient.CloseIdleConnections() // Closes all idle connections for the HTTP client - } - w.logger.Debug("HTTP client connections closed successfully for Sematext HTTP Writer") - return nil + if w.httpClient != nil { + w.httpClient.CloseIdleConnections() // Closes all idle connections for the HTTP client + } + w.logger.Debug("HTTP client connections closed successfully for Sematext HTTP Writer") + return nil } func (w *sematextHTTPWriter) NewBatch() otel2influx.InfluxWriterBatch { return newSematextHTTPWriterBatch(w) From 50357632d2aba9de15e81dd63807325544ce2cad Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 15:07:37 +0100 Subject: [PATCH 29/52] Fix CI issues --- exporter/sematextexporter/es.go | 9 ++++----- exporter/sematextexporter/exporter.go | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index bf79e521ed6b..fdeb6f1f411a 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -9,11 +9,10 @@ import ( "strings" "time" + json "github.com/json-iterator/go" "github.com/olivere/elastic" "github.com/sirupsen/logrus" "golang.org/x/net/context" - - json "github.com/json-iterator/go" ) // artificialDocType designates a syntenic doc type for ES documents @@ -33,7 +32,7 @@ type client struct { // Client represents a minimal interface client implementation has to satisfy. type Client interface { - Bulk(body interface{}, config *Config) error + Bulk(body any, config *Config) error } // NewClient creates a new instance of ES client that internally stores a reference @@ -62,14 +61,14 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client } // Bulk processes a batch of documents and sends them to the specified LogsEndpoint. -func (c *client) Bulk(body interface{}, config *Config) error { +func (c *client) Bulk(body any, config *Config) error { if grp, ok := c.clients[config.LogsEndpoint]; ok { bulkRequest := grp.client.Bulk() if reflect.TypeOf(body).Kind() == reflect.Slice { v := reflect.ValueOf(body) for i := 0; i < v.Len(); i++ { doc := v.Index(i).Interface() - if docMap, ok := doc.(map[string]interface{}); ok { + if docMap, ok := doc.(map[string]any); ok { docMap["os.host"] = getHostname() } diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 3e99b4fef6bf..28bdcaaa5598 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -64,8 +64,8 @@ func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) e } // convertLogsToBulkPayload converts OpenTelemetry log data into a bulk payload for Sematext. -func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) { - var bulkPayload []map[string]interface{} +func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]any, error) { + var bulkPayload []map[string]any resourceLogs := logs.ResourceLogs() @@ -82,7 +82,7 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]interface{}, error) severity = "INFO" // Default severity if missing } // Build the log entry - logEntry := map[string]interface{}{ + logEntry := map[string]any{ "@timestamp": record.Timestamp().AsTime().Format(time.RFC3339), "message": record.Body().AsString(), "severity": severity, From ef179364c842f20a1be623deeacb9bc89fded810 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 15:14:27 +0100 Subject: [PATCH 30/52] Update gendistributions --- reports/distributions/contrib.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/reports/distributions/contrib.yaml b/reports/distributions/contrib.yaml index a915a45f43fa..ed2fb9b212d1 100644 --- a/reports/distributions/contrib.yaml +++ b/reports/distributions/contrib.yaml @@ -48,6 +48,7 @@ components: - pulsar - rabbitmq - sapm + - sematext - sentry - signalfx - splunk_hec From 8bdc05125ffe4e1f439de1dcd6880a6ff1100f5f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 15:45:09 +0100 Subject: [PATCH 31/52] Remove unnecessary error return value --- exporter/sematextexporter/exporter.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 28bdcaaa5598..3dd532b127df 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -43,11 +43,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { // pushLogsData processes and sends log data to Sematext in bulk. func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) error { // Convert logs to bulk payload - bulkPayload, err := convertLogsToBulkPayload(logs) - if err != nil { - e.logger.Errorf("Failed to convert logs: %v", err) - return err - } + bulkPayload := convertLogsToBulkPayload(logs) // Debug: Print the bulk payload for _, payload := range bulkPayload { @@ -64,7 +60,7 @@ func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) e } // convertLogsToBulkPayload converts OpenTelemetry log data into a bulk payload for Sematext. -func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]any, error) { +func convertLogsToBulkPayload(logs plog.Logs) []map[string]any { var bulkPayload []map[string]any resourceLogs := logs.ResourceLogs() @@ -92,7 +88,7 @@ func convertLogsToBulkPayload(logs plog.Logs) ([]map[string]any, error) { } } - return bulkPayload, nil + return bulkPayload } // Start initializes the Sematext Logs Exporter. From 11ba415543080258c5f7aa83841745590ffc2c8f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 15:57:55 +0100 Subject: [PATCH 32/52] Make anchor links lowercase --- exporter/sematextexporter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index abf7673cc0c4..5f72645fa986 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -24,7 +24,7 @@ The following configuration options are supported: * `payload_max_lines` (default = 1_000) Maximum number of lines allowed per HTTP POST request * `payload_max_bytes` (default = 300_000) Maximum number of bytes allowed per HTTP POST request * `metrics_schema` (default = telegraf-prometheus-v2) The chosen metrics schema to write -* `sending_queue` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) +* `sending_queue` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/readme.md#configuration) * `enabled` (default = true) * `num_consumers` (default = 10) The number of consumers from the queue * `queue_size` (default = 1000) Maximum number of batches allowed in queue at a given time From 91c749420aba482aa79c9093472348e561e0972f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 18:54:05 +0100 Subject: [PATCH 33/52] Fix anchor links --- exporter/sematextexporter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index 5f72645fa986..786da92d9d40 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -28,7 +28,7 @@ The following configuration options are supported: * `enabled` (default = true) * `num_consumers` (default = 10) The number of consumers from the queue * `queue_size` (default = 1000) Maximum number of batches allowed in queue at a given time -* `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) +* `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/readme.md#configuration) * `enabled` (default = true) * `initial_interval` (default = 5s) Time to wait after the first failure before retrying * `max_interval` (default = 30s) Upper bound on backoff interval From 8be0231b5e63ba1cd1f0c8fe1a75050367cce297 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 22:53:07 +0100 Subject: [PATCH 34/52] Fix anchor link Hope this works --- exporter/sematextexporter/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index 786da92d9d40..abf7673cc0c4 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -24,11 +24,11 @@ The following configuration options are supported: * `payload_max_lines` (default = 1_000) Maximum number of lines allowed per HTTP POST request * `payload_max_bytes` (default = 300_000) Maximum number of bytes allowed per HTTP POST request * `metrics_schema` (default = telegraf-prometheus-v2) The chosen metrics schema to write -* `sending_queue` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/readme.md#configuration) +* `sending_queue` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) * `enabled` (default = true) * `num_consumers` (default = 10) The number of consumers from the queue * `queue_size` (default = 1000) Maximum number of batches allowed in queue at a given time -* `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/readme.md#configuration) +* `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) * `enabled` (default = true) * `initial_interval` (default = 5s) Time to wait after the first failure before retrying * `max_interval` (default = 30s) Upper bound on backoff interval From cde2d8f029404c220b48c0647681cfd613d2b740 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 9 Dec 2024 23:10:05 +0100 Subject: [PATCH 35/52] remove links --- exporter/sematextexporter/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/README.md b/exporter/sematextexporter/README.md index abf7673cc0c4..bac2b25231c8 100644 --- a/exporter/sematextexporter/README.md +++ b/exporter/sematextexporter/README.md @@ -24,11 +24,11 @@ The following configuration options are supported: * `payload_max_lines` (default = 1_000) Maximum number of lines allowed per HTTP POST request * `payload_max_bytes` (default = 300_000) Maximum number of bytes allowed per HTTP POST request * `metrics_schema` (default = telegraf-prometheus-v2) The chosen metrics schema to write -* `sending_queue` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) +* `sending_queue` * `enabled` (default = true) * `num_consumers` (default = 10) The number of consumers from the queue * `queue_size` (default = 1000) Maximum number of batches allowed in queue at a given time -* `retry_on_failure` [details here](https://github.com/open-telemetry/opentelemetry-collector/blob/v0.25.0/exporter/exporterhelper/README.md#configuration) +* `retry_on_failure` * `enabled` (default = true) * `initial_interval` (default = 5s) Time to wait after the first failure before retrying * `max_interval` (default = 30s) Upper bound on backoff interval From ddd7911db971a932ee83b15e8fb97d778e71da3f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 10 Dec 2024 03:00:15 +0100 Subject: [PATCH 36/52] Write more unit tests --- exporter/sematextexporter/es_test.go | 126 +++++++++++++++++++++ exporter/sematextexporter/exporter_test.go | 75 ++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 exporter/sematextexporter/exporter_test.go diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 5be21fa168cb..1d1ce7e46033 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -2,3 +2,129 @@ // SPDX-License-Identifier: Apache-2.0 package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" +import ( + "io" + "testing" + + "github.com/olivere/elastic" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestNewClient(t *testing.T) { + mockConfig := &Config{ + Region: "US", + } + mockLogger := logrus.New() + mockLogger.SetOutput(io.Discard) + + writer := FlatWriter{} + client, err := newClient(mockConfig, mockLogger, writer) + + assert.NoError(t, err, "Expected no error while creating new client") + assert.NotNil(t, client, "Expected client to be non-nil") +} +func TestBulkWithMockClient(t *testing.T) { + mockConfig := &Config{ + Region: "US", + LogsConfig: LogsConfig{ + LogsEndpoint: "https://logsene-receiver.sematext.com", + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + } + + // Create a mock client + mockClient := newMockClient(mockConfig) + + // Create mock payload + mockPayload := []map[string]any{ + {"field1": "value1", "field2": "value2"}, + } + + // Call the Bulk method on the mock client + err := mockClient.Bulk(mockPayload, mockConfig) + assert.NoError(t, err, "Expected no error while sending bulk request") + for _, group := range mockClient.clients { + assert.True(t, group.client.BulkCalled, "Expected Bulk to be called on the mock client") + } +} + +func TestWritePayload(t *testing.T) { + mockConfig := &Config{ + Region: "US", + LogsConfig: LogsConfig{ + LogsEndpoint: "https://logsene-receiver.sematext.com", + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + } + mockLogger := logrus.New() + mockLogger.SetOutput(io.Discard) + + mockWriter := FlatWriter{} + client := &client{ + config: mockConfig, + logger: mockLogger, + writer: mockWriter, + } + + payload := "mockPayload" + status := "200" + client.writePayload(payload, status) + + // Validate that the payload and status are written correctly + expectedOutput := formatl(payload, status) + assert.Equal(t, expectedOutput, formatl(payload, status), "Payload should be formatted and written correctly") +} +func TestFormatl(t *testing.T) { + payload := "mockPayload" + status := "200 OK" + + formatted := formatl(payload, status) + expected := "mockPayload 200 OK" + + assert.Equal(t, expected, formatted, "Formatted payload should match the expected output") +} + +func TestGetHostname(t *testing.T) { + hostname := getHostname() + assert.NotEmpty(t, hostname, "Hostname should not be empty") +} + +type mockElasticClient struct { + BulkCalled bool +} + +func (m *mockElasticClient) Bulk() *elastic.BulkService { + m.BulkCalled = true + return nil +} + +type mockGroup struct { + client *mockElasticClient + token string +} + +type MockClient struct { + clients map[string]mockGroup + Error error +} + +func (m *MockClient) Bulk(body any, config *Config) error { + for _, group := range m.clients { + group.client.Bulk() + } + return m.Error +} + +func newMockClient(config *Config) *MockClient { + mockElastic := &mockElasticClient{} + return &MockClient{ + clients: map[string]mockGroup{ + config.LogsEndpoint: { + client: mockElastic, + token: config.LogsConfig.AppToken, + }, + }, + } +} + diff --git a/exporter/sematextexporter/exporter_test.go b/exporter/sematextexporter/exporter_test.go new file mode 100644 index 000000000000..c3d8a507857f --- /dev/null +++ b/exporter/sematextexporter/exporter_test.go @@ -0,0 +1,75 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/plog" + "go.uber.org/zap" +) + +func TestConvertLogsToBulkPayload(t *testing.T) { + // Create a plog.Logs object + logs := plog.NewLogs() + resourceLogs := logs.ResourceLogs().AppendEmpty() + scopeLogs := resourceLogs.ScopeLogs().AppendEmpty() + logRecord := scopeLogs.LogRecords().AppendEmpty() + + // Set log record fields + timestamp := time.Now().UTC() // Ensure timestamp is in UTC + logRecord.SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) + logRecord.Body().SetStr("This is a test log message") + logRecord.SetSeverityText("ERROR") + + // Call the function + result := convertLogsToBulkPayload(logs) + + // Prepare the expected result + expected := []map[string]any{ + { + "@timestamp": timestamp.Format(time.RFC3339), // Ensure expected timestamp is in RFC3339 format + "message": "This is a test log message", + "severity": "ERROR", + }, + } + + // Assert that the result matches the expected value + assert.Equal(t, expected, result) +} +func TestNewExporter(t *testing.T) { + // Mock configuration + mockConfig := &Config{ + // Add any necessary fields for testing + Region: "US", + } + logger := zap.NewNop() + // Mock exporter settings with a zap.Logger + mockSettings := exporter.Settings{ + TelemetrySettings: component.TelemetrySettings{ + Logger: logger, + }, + // Use a no-op logger for testing + } + + // Call the function + exporter := newExporter(mockConfig, mockSettings) + + // Ensure the exporter is not nil + assert.NotNil(t, exporter) + + // Validate the exporter fields + assert.Equal(t, mockConfig, exporter.config, "Exporter config does not match") + assert.NotNil(t, exporter.client, "Exporter client should not be nil") + assert.NotNil(t, exporter.logger, "Exporter logger should not be nil") + + // Ensure the logger has the correct formatter + flatFormatter, ok := exporter.logger.Formatter.(*FlatFormatter) + assert.True(t, ok, "Exporter logger should use FlatFormatter") + assert.NotNil(t, flatFormatter, "FlatFormatter should be properly initialized") +} From 7b0668830a1f4ff5b2d9ba8dfc3084b5b0d3c270 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 10 Dec 2024 14:00:11 +0100 Subject: [PATCH 37/52] Remove unused prameter --- exporter/sematextexporter/es_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 1d1ce7e46033..635b1c15d2bc 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -109,7 +109,7 @@ type MockClient struct { Error error } -func (m *MockClient) Bulk(body any, config *Config) error { +func (m *MockClient) Bulk(_ any, config *Config) error { for _, group := range m.clients { group.client.Bulk() } From 5978836f809a95293ee1514f85ca996534cd04a3 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 12 Dec 2024 08:51:21 +0100 Subject: [PATCH 38/52] Fix lint issues --- exporter/sematextexporter/es_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 635b1c15d2bc..0120283a8711 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -109,7 +109,7 @@ type MockClient struct { Error error } -func (m *MockClient) Bulk(_ any, config *Config) error { +func (m *MockClient) Bulk(_ any, _ *Config) error { for _, group := range m.clients { group.client.Bulk() } @@ -127,4 +127,3 @@ func newMockClient(config *Config) *MockClient { }, } } - From acfa9962b9232b7ae9a06d2f4be3f2e4755a7a78 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 18 Dec 2024 12:36:14 +0100 Subject: [PATCH 39/52] Implement fix that Akshat suggested --- exporter/sematextexporter/config.go | 35 ++++++++++------------ exporter/sematextexporter/es.go | 24 ++++++++------- exporter/sematextexporter/es_test.go | 15 ++++++++-- exporter/sematextexporter/exporter_test.go | 13 +------- exporter/sematextexporter/go.mod | 3 +- exporter/sematextexporter/go.sum | 4 +-- exporter/sematextexporter/writer.go | 4 +-- exporter/sematextexporter/writer_test.go | 2 ++ 8 files changed, 51 insertions(+), 49 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 1cf8417abdee..cd9ce48c4d99 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -13,6 +13,15 @@ import ( "go.opentelemetry.io/collector/exporter/exporterhelper" ) +const ( + euRegion = "eu" + usRegion = "us" + euMetricsEndpoint = "https://spm-receiver.eu.sematext.com" + euLogsEndpoint = "https://logsene-receiver.eu.sematext.com" + usMetricsEndpoint = "https://spm-receiver.eu.sematext.com" + usLogsEndpoint = "https://logsene-receiver.eu.sematext.com" +) + type Config struct { confighttp.ClientConfig `mapstructure:",squash"` configretry.BackOffConfig `mapstructure:"retry_on_failure"` @@ -60,7 +69,7 @@ type LogsConfig struct { // Validate checks for invalid or missing entries in the configuration. func (cfg *Config) Validate() error { - if strings.ToLower(cfg.Region) != "eu" && strings.ToLower(cfg.Region) != "us" { + if strings.ToLower(cfg.Region) != euRegion && strings.ToLower(cfg.Region) != usRegion { return fmt.Errorf("invalid region: %s. please use either 'EU' or 'US'", cfg.Region) } if len(cfg.MetricsConfig.AppToken) != 36 { @@ -69,26 +78,14 @@ func (cfg *Config) Validate() error { if len(cfg.LogsConfig.AppToken) != 36 { return fmt.Errorf("invalid logs app_token: %s. app_token should be 36 characters", cfg.LogsConfig.AppToken) } - if strings.ToLower(cfg.Region) == "eu" { - cfg.MetricsEndpoint = "https://spm-receiver.eu.sematext.com" - cfg.LogsEndpoint = "https://logsene-receiver.eu.sematext.com" + if strings.ToLower(cfg.Region) == euRegion { + cfg.MetricsEndpoint = euMetricsEndpoint + cfg.LogsEndpoint = euLogsEndpoint } - if strings.ToLower(cfg.Region) == "us" { - cfg.MetricsEndpoint = "https://spm-receiver.sematext.com" - cfg.LogsEndpoint = "https://logsene-receiver.sematext.com" + if strings.ToLower(cfg.Region) == usRegion { + cfg.MetricsEndpoint = usMetricsEndpoint + cfg.LogsEndpoint = usLogsEndpoint } return nil } - -// Bool provides an atomic boolean type. -type Bool struct{ u Uint32 } - -// Uint32 provides an atomic uint32 type. -type Uint32 struct{ value uint32 } - -// Load gets the value of atomic boolean. -func (b *Bool) Load() bool { return b.u.Load() == 1 } - -// Load get the value of atomic integer. -func (u *Uint32) Load() uint32 { return atomic.LoadUint32(&u.value) } diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index fdeb6f1f411a..ba9df165b658 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -10,7 +10,7 @@ import ( "time" json "github.com/json-iterator/go" - "github.com/olivere/elastic" + "github.com/olivere/elastic/v7" "github.com/sirupsen/logrus" "golang.org/x/net/context" ) @@ -24,10 +24,11 @@ type group struct { } type client struct { - clients map[string]group - config *Config - logger *logrus.Logger - writer FlatWriter + clients map[string]group + config *Config + logger *logrus.Logger + writer FlatWriter + hostname string } // Client represents a minimal interface client implementation has to satisfy. @@ -46,17 +47,20 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client if err != nil { return nil, err } + defer c.Stop() clients[config.LogsEndpoint] = group{ client: c, token: config.LogsConfig.AppToken, } } + hostname := getHostname() return &client{ - clients: clients, - config: config, - logger: logger, - writer: writer, + clients: clients, + config: config, + logger: logger, + writer: writer, + hostname: hostname, }, nil } @@ -69,7 +73,7 @@ func (c *client) Bulk(body any, config *Config) error { for i := 0; i < v.Len(); i++ { doc := v.Index(i).Interface() if docMap, ok := doc.(map[string]any); ok { - docMap["os.host"] = getHostname() + docMap["os.host"] = c.hostname } req := elastic.NewBulkIndexRequest(). diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 0120283a8711..dbdc057c4360 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -6,7 +6,7 @@ import ( "io" "testing" - "github.com/olivere/elastic" + "github.com/olivere/elastic/v7" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -35,6 +35,11 @@ func TestBulkWithMockClient(t *testing.T) { // Create a mock client mockClient := newMockClient(mockConfig) + defer func() { + for _, group := range mockClient.clients { + group.client.Stop() + } + }() // Create mock payload mockPayload := []map[string]any{ @@ -92,8 +97,12 @@ func TestGetHostname(t *testing.T) { type mockElasticClient struct { BulkCalled bool + done chan struct{} } +func (m *mockElasticClient) Stop() { + close(m.done) +} func (m *mockElasticClient) Bulk() *elastic.BulkService { m.BulkCalled = true return nil @@ -117,7 +126,9 @@ func (m *MockClient) Bulk(_ any, _ *Config) error { } func newMockClient(config *Config) *MockClient { - mockElastic := &mockElasticClient{} + mockElastic := &mockElasticClient{ + done: make(chan struct{}), + } return &MockClient{ clients: map[string]mockGroup{ config.LogsEndpoint: { diff --git a/exporter/sematextexporter/exporter_test.go b/exporter/sematextexporter/exporter_test.go index c3d8a507857f..cff27e85082d 100644 --- a/exporter/sematextexporter/exporter_test.go +++ b/exporter/sematextexporter/exporter_test.go @@ -22,15 +22,13 @@ func TestConvertLogsToBulkPayload(t *testing.T) { logRecord := scopeLogs.LogRecords().AppendEmpty() // Set log record fields - timestamp := time.Now().UTC() // Ensure timestamp is in UTC + timestamp := time.Now().UTC() logRecord.SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) logRecord.Body().SetStr("This is a test log message") logRecord.SetSeverityText("ERROR") - // Call the function result := convertLogsToBulkPayload(logs) - // Prepare the expected result expected := []map[string]any{ { "@timestamp": timestamp.Format(time.RFC3339), // Ensure expected timestamp is in RFC3339 format @@ -39,36 +37,27 @@ func TestConvertLogsToBulkPayload(t *testing.T) { }, } - // Assert that the result matches the expected value assert.Equal(t, expected, result) } func TestNewExporter(t *testing.T) { - // Mock configuration mockConfig := &Config{ - // Add any necessary fields for testing Region: "US", } logger := zap.NewNop() - // Mock exporter settings with a zap.Logger mockSettings := exporter.Settings{ TelemetrySettings: component.TelemetrySettings{ Logger: logger, }, - // Use a no-op logger for testing } - // Call the function exporter := newExporter(mockConfig, mockSettings) - // Ensure the exporter is not nil assert.NotNil(t, exporter) - // Validate the exporter fields assert.Equal(t, mockConfig, exporter.config, "Exporter config does not match") assert.NotNil(t, exporter.client, "Exporter client should not be nil") assert.NotNil(t, exporter.logger, "Exporter logger should not be nil") - // Ensure the logger has the correct formatter flatFormatter, ok := exporter.logger.Formatter.(*FlatFormatter) assert.True(t, ok, "Exporter logger should use FlatFormatter") assert.NotNil(t, flatFormatter, "FlatFormatter should be properly initialized") diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index 0f348bc0337a..acb02d50d29c 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -4,6 +4,7 @@ go 1.22.0 require ( github.com/influxdata/influxdb-observability/common v0.5.8 + github.com/olivere/elastic/v7 v7.0.32 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 go.opentelemetry.io/collector/config/confighttp v0.110.0 go.opentelemetry.io/collector/config/configopaque v1.16.0 @@ -14,7 +15,6 @@ require ( ) require ( - github.com/fortytw2/leaktest v1.3.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect @@ -50,7 +50,6 @@ require ( github.com/klauspost/compress v1.17.9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/olivere/elastic v6.2.37+incompatible github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect github.com/sirupsen/logrus v1.9.3 diff --git a/exporter/sematextexporter/go.sum b/exporter/sematextexporter/go.sum index 579a6a48302e..2a0df4de4244 100644 --- a/exporter/sematextexporter/go.sum +++ b/exporter/sematextexporter/go.sum @@ -76,8 +76,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/olivere/elastic v6.2.37+incompatible h1:UfSGJem5czY+x/LqxgeCBgjDn6St+z8OnsCuxwD3L0U= -github.com/olivere/elastic v6.2.37+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= +github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= +github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 2b4b38dc77b5..dcafccb37d81 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -198,9 +198,9 @@ func (b *sematextHTTPWriterBatch) WriteBatch(ctx context.Context) error { } switch res.StatusCode { - case 200, 204: + case http.StatusOK, http.StatusNoContent: break - case 500: + case http.StatusInternalServerError: return fmt.Errorf("line protocol write returned %q %q", res.Status, string(body)) default: return consumererror.NewPermanent(fmt.Errorf("line protocol write returned %q %q", res.Status, string(body))) diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index e22d73772d1e..f41bf09b0108 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -131,6 +131,7 @@ func TestSematextHTTPWriterBatchMaxPayload(t *testing.T) { logger: common.NoopLogger{}, }, } + defer batch.sematextHTTPWriter.httpClient.CloseIdleConnections() err := batch.EnqueuePoint(context.Background(), "m", map[string]string{"k": "v"}, map[string]any{"f": int64(1)}, time.Unix(1, 0), 0) require.NoError(t, err) @@ -177,6 +178,7 @@ func TestSematextHTTPWriterBatchEnqueuePointEmptyTagValue(t *testing.T) { require.NoError(t, err) sematextWriter.httpClient = noopHTTPServer.Client() sematextWriterBatch := sematextWriter.NewBatch() + defer sematextWriter.httpClient.CloseIdleConnections() err = sematextWriterBatch.EnqueuePoint( context.Background(), From 7bc9caf84cf55166ed07d53aedccb79dfafab5bc Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 18 Dec 2024 16:21:37 +0100 Subject: [PATCH 40/52] Correct endpoints --- exporter/sematextexporter/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index cd9ce48c4d99..26b26a6671b5 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -18,8 +18,8 @@ const ( usRegion = "us" euMetricsEndpoint = "https://spm-receiver.eu.sematext.com" euLogsEndpoint = "https://logsene-receiver.eu.sematext.com" - usMetricsEndpoint = "https://spm-receiver.eu.sematext.com" - usLogsEndpoint = "https://logsene-receiver.eu.sematext.com" + usMetricsEndpoint = "https://spm-receiver.sematext.com" + usLogsEndpoint = "https://logsene-receiver.sematext.com" ) type Config struct { From d4a6453b4927253b34a48651764f616dd908e4a9 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Wed, 18 Dec 2024 17:17:01 +0100 Subject: [PATCH 41/52] Fix lint issues --- exporter/sematextexporter/exporter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/sematextexporter/exporter_test.go b/exporter/sematextexporter/exporter_test.go index cff27e85082d..1b45563ee04f 100644 --- a/exporter/sematextexporter/exporter_test.go +++ b/exporter/sematextexporter/exporter_test.go @@ -22,7 +22,7 @@ func TestConvertLogsToBulkPayload(t *testing.T) { logRecord := scopeLogs.LogRecords().AppendEmpty() // Set log record fields - timestamp := time.Now().UTC() + timestamp := time.Now().UTC() logRecord.SetTimestamp(pcommon.NewTimestampFromTime(timestamp)) logRecord.Body().SetStr("This is a test log message") logRecord.SetSeverityText("ERROR") From 1fbb2bcf1b914cea696be7dbe0ef4cc34ebeb81f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Thu, 19 Dec 2024 09:44:05 +0100 Subject: [PATCH 42/52] Remove getHostName --- exporter/sematextexporter/es.go | 16 +++++----------- exporter/sematextexporter/es_test.go | 5 ----- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index ba9df165b658..6ac8d254449e 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -53,7 +53,10 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client token: config.LogsConfig.AppToken, } } - hostname := getHostname() + hostname, err := os.Hostname() + if err != nil{ + fmt.Printf("Could not retrieve hostname: %v\n", err) + } return &client{ clients: clients, @@ -136,13 +139,4 @@ func formatl(payload string, status string) string { s = fmt.Sprintf("%s...", s[:i]) } return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) -} - -// getHostname retrieves the current machine's hostname. -func getHostname() string { - hostname, err := os.Hostname() - if err != nil { - return "None" - } - return hostname -} +} \ No newline at end of file diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index dbdc057c4360..dccbe00fd0e3 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -90,11 +90,6 @@ func TestFormatl(t *testing.T) { assert.Equal(t, expected, formatted, "Formatted payload should match the expected output") } -func TestGetHostname(t *testing.T) { - hostname := getHostname() - assert.NotEmpty(t, hostname, "Hostname should not be empty") -} - type mockElasticClient struct { BulkCalled bool done chan struct{} From b0c1208835fdb9e749b1d3321cd70fcdc99ed23c Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 23 Dec 2024 10:45:45 +0100 Subject: [PATCH 43/52] Remove deprecated library --- exporter/sematextexporter/es.go | 98 ++++++++++++++------------- exporter/sematextexporter/es_test.go | 4 +- exporter/sematextexporter/exporter.go | 28 +------- exporter/sematextexporter/factory.go | 1 - exporter/sematextexporter/go.mod | 7 +- exporter/sematextexporter/go.sum | 16 ++--- 6 files changed, 63 insertions(+), 91 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 6ac8d254449e..ad22bf81b739 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -8,18 +8,20 @@ import ( "reflect" "strings" "time" + "bytes" + "log" json "github.com/json-iterator/go" - "github.com/olivere/elastic/v7" + "github.com/elastic/go-elasticsearch" + "github.com/elastic/go-elasticsearch/v8/esapi" "github.com/sirupsen/logrus" "golang.org/x/net/context" ) // artificialDocType designates a syntenic doc type for ES documents -const artificialDocType = "_doc" type group struct { - client *elastic.Client + client *elasticsearch.Client token string } @@ -43,11 +45,12 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client // client for shipping to logsene if config.LogsConfig.AppToken != "" { - c, err := elastic.NewClient(elastic.SetURL(config.LogsEndpoint), elastic.SetSniff(false), elastic.SetHealthcheckTimeout(time.Second*2)) + c, err := elasticsearch.NewClient(elasticsearch.Config{ + Addresses: []string{config.LogsEndpoint}, + }) if err != nil { return nil, err } - defer c.Stop() clients[config.LogsEndpoint] = group{ client: c, token: config.LogsConfig.AppToken, @@ -69,57 +72,58 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client // Bulk processes a batch of documents and sends them to the specified LogsEndpoint. func (c *client) Bulk(body any, config *Config) error { - if grp, ok := c.clients[config.LogsEndpoint]; ok { - bulkRequest := grp.client.Bulk() - if reflect.TypeOf(body).Kind() == reflect.Slice { - v := reflect.ValueOf(body) - for i := 0; i < v.Len(); i++ { - doc := v.Index(i).Interface() - if docMap, ok := doc.(map[string]any); ok { - docMap["os.host"] = c.hostname - } - - req := elastic.NewBulkIndexRequest(). - Index(grp.token). - Type(artificialDocType). - Doc(doc) - bulkRequest.Add(req) + grp, ok := c.clients[config.LogsEndpoint] + if !ok { + return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) + } + + var bulkBuffer bytes.Buffer + + if reflect.TypeOf(body).Kind() == reflect.Slice { + v := reflect.ValueOf(body) + for i := 0; i < v.Len(); i++ { + doc := v.Index(i).Interface() + if docMap, ok := doc.(map[string]any); ok { + docMap["os.host"] = c.hostname } - } - if bulkRequest.NumberOfActions() > 0 { - payloadBytes, err := json.Marshal(body) - if err != nil { - return fmt.Errorf("failed to serialize payload: %w", err) + meta := map[string]map[string]string{ + "index": {"_index": grp.token}, } + metaBytes, _ := json.Marshal(meta) + docBytes, _ := json.Marshal(doc) - // Print or log the payload(Will delete this once everything is good) - fmt.Printf("Payload being sent to Sematext:\n%s\n", string(payloadBytes)) + bulkBuffer.Write(metaBytes) + bulkBuffer.WriteByte('\n') + bulkBuffer.Write(docBytes) + bulkBuffer.WriteByte('\n') + } + } - if c.config.LogRequests { - c.logger.Infof("Sending bulk to %s", config.LogsEndpoint) - } + if bulkBuffer.Len() > 0 { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) - defer cancel() - res, err := bulkRequest.Do(ctx) - if err != nil { - c.writePayload(string(payloadBytes), err.Error()) - return err - } - if res.Errors { - for _, item := range res.Failed() { - if item.Error != nil { - c.logger.Errorf("Document %s failed to index: %s - %s", item.Id, item.Error.Type, item.Error.Reason) - } - } - } + req := esapi.BulkRequest{ + Body: bytes.NewReader(bulkBuffer.Bytes()), + } - c.writePayload(string(payloadBytes), "200") - return nil + res, err := req.Do(ctx, grp.client) + if err != nil { + log.Printf("Bulk request failed: %v", err) + return err } + defer res.Body.Close() + + if res.IsError() { + log.Printf("Bulk request returned error: %s", res.String()) + return fmt.Errorf("bulk request error: %s", res.String()) + } + + log.Printf("Bulk request successful: %s", res.String()) } - return fmt.Errorf("no client known for %s endpoint", config.LogsEndpoint) + + return nil } // writePayload writes a formatted payload along with its status to the configured writer. diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index dccbe00fd0e3..c5fd1a5e5220 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -6,7 +6,7 @@ import ( "io" "testing" - "github.com/olivere/elastic/v7" + "github.com/elastic/go-elasticsearch/v8/esapi" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -98,7 +98,7 @@ type mockElasticClient struct { func (m *mockElasticClient) Stop() { close(m.done) } -func (m *mockElasticClient) Bulk() *elastic.BulkService { +func (m *mockElasticClient) Bulk() *esapi.Bulk { m.BulkCalled = true return nil } diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 3dd532b127df..17cb4ffff300 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -107,35 +107,9 @@ func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error e.logger.Error("Sematext client is not initialized (nil)") return fmt.Errorf("sematext client is not initialized") } - - // Assign the client and logger to the exporter e.client = client e.logger = logger - // Log a success message e.logger.Info("Sematext Logs Exporter successfully started") return nil -} - -// Shutdown gracefully shuts down the Sematext Logs Exporter. -func (e *sematextLogsExporter) Shutdown(_ context.Context) error { - if e.logger == nil { - return fmt.Errorf("logger is not initialized") - } - - e.logger.Info("Shutting down Sematext Logs Exporter...") - - // Stop ElasticSearch client's background goroutines - if e.client != nil { - for endpoint, grp := range e.client.(*client).clients { - if grp.client != nil { - e.logger.Debugf("Stopping ElasticSearch client for endpoint: %s", endpoint) - grp.client.Stop() // Stop the ElasticSearch client's healthchecker goroutines - } - } - } - - // Log completion of shutdown - e.logger.Info("Sematext Logs Exporter shutdown complete") - return nil -} +} \ No newline at end of file diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 42b9ea1860d8..94d294386387 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -127,6 +127,5 @@ func createLogsExporter( exporter.pushLogsData, // Function to process and send logs exporterhelper.WithRetry(cf.BackOffConfig), exporterhelper.WithStart(exporter.Start), - exporterhelper.WithShutdown(exporter.Shutdown), ) } diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index acb02d50d29c..43db5294187f 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -3,8 +3,9 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/exporter/semate go 1.22.0 require ( + github.com/elastic/go-elasticsearch v0.0.0 + github.com/elastic/go-elasticsearch/v8 v8.17.0 github.com/influxdata/influxdb-observability/common v0.5.8 - github.com/olivere/elastic/v7 v7.0.32 github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 go.opentelemetry.io/collector/config/confighttp v0.110.0 go.opentelemetry.io/collector/config/configopaque v1.16.0 @@ -15,15 +16,13 @@ require ( ) require ( + github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect github.com/knadh/koanf/v2 v2.1.1 // indirect - github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/pkg/errors v0.9.1 // indirect go.opentelemetry.io/collector/component/componentprofiles v0.110.0 // indirect go.opentelemetry.io/collector/consumer/consumertest v0.110.0 // indirect go.opentelemetry.io/collector/exporter/exporterprofiles v0.110.0 // indirect diff --git a/exporter/sematextexporter/go.sum b/exporter/sematextexporter/go.sum index 2a0df4de4244..f070ac4802ef 100644 --- a/exporter/sematextexporter/go.sum +++ b/exporter/sematextexporter/go.sum @@ -4,10 +4,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= +github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= +github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA= +github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg= +github.com/elastic/go-elasticsearch/v8 v8.17.0 h1:e9cWksE/Fr7urDRmGPGp47Nsp4/mvNOrU8As1l2HQQ0= +github.com/elastic/go-elasticsearch/v8 v8.17.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= @@ -43,8 +47,6 @@ github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY= github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE= github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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= @@ -64,8 +66,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= @@ -76,10 +76,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E= -github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= From f087bd5f8836a31b590bb8b81648abda4ab1a16e Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 23 Dec 2024 13:13:41 +0100 Subject: [PATCH 44/52] Replace logrus with zap --- exporter/sematextexporter/es.go | 41 ++++++++++++---------- exporter/sematextexporter/es_test.go | 9 ++--- exporter/sematextexporter/exporter.go | 13 +++---- exporter/sematextexporter/exporter_test.go | 4 --- exporter/sematextexporter/go.mod | 4 +-- 5 files changed, 32 insertions(+), 39 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index ad22bf81b739..308604c7e7f7 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -2,23 +2,23 @@ // SPDX-License-Identifier: Apache-2.0 package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" + import ( + "bytes" + "context" + "encoding/json" "fmt" "os" "reflect" "strings" "time" - "bytes" - "log" - json "github.com/json-iterator/go" "github.com/elastic/go-elasticsearch" "github.com/elastic/go-elasticsearch/v8/esapi" - "github.com/sirupsen/logrus" - "golang.org/x/net/context" + "go.uber.org/zap" ) -// artificialDocType designates a syntenic doc type for ES documents +// artificialDocType designates a synthetic doc type for ES documents type group struct { client *elasticsearch.Client @@ -28,7 +28,7 @@ type group struct { type client struct { clients map[string]group config *Config - logger *logrus.Logger + logger *zap.Logger writer FlatWriter hostname string } @@ -39,16 +39,17 @@ type Client interface { } // NewClient creates a new instance of ES client that internally stores a reference -// to both, event and log receivers. -func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client, error) { +// to both event and log receivers. +func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, error) { clients := make(map[string]group) - // client for shipping to logsene + // Client for shipping to logsene if config.LogsConfig.AppToken != "" { c, err := elasticsearch.NewClient(elasticsearch.Config{ Addresses: []string{config.LogsEndpoint}, }) if err != nil { + logger.Error("Failed to create Elasticsearch client", zap.Error(err)) return nil, err } clients[config.LogsEndpoint] = group{ @@ -56,10 +57,12 @@ func newClient(config *Config, logger *logrus.Logger, writer FlatWriter) (Client token: config.LogsConfig.AppToken, } } + hostname, err := os.Hostname() - if err != nil{ - fmt.Printf("Could not retrieve hostname: %v\n", err) - } + if err != nil { + logger.Warn("Could not retrieve hostname", zap.Error(err)) + hostname = "unknown" + } return &client{ clients: clients, @@ -110,17 +113,17 @@ func (c *client) Bulk(body any, config *Config) error { res, err := req.Do(ctx, grp.client) if err != nil { - log.Printf("Bulk request failed: %v", err) + c.logger.Error("Bulk request failed", zap.Error(err)) return err } defer res.Body.Close() if res.IsError() { - log.Printf("Bulk request returned error: %s", res.String()) + c.logger.Error("Bulk request returned an error", zap.String("response", res.String())) return fmt.Errorf("bulk request error: %s", res.String()) } - log.Printf("Bulk request successful: %s", res.String()) + c.logger.Info("Bulk request successful", zap.String("response", res.String())) } return nil @@ -131,11 +134,11 @@ func (c *client) writePayload(payload string, status string) { if c.config.WriteEvents.Load() { c.writer.Write(formatl(payload, status)) } else { - c.logger.Debugf("WriteEvents disabled. Payload: %s, Status: %s", payload, status) + c.logger.Debug("WriteEvents disabled", zap.String("payload", payload), zap.String("status", status)) } } -// Formatl delimits and formats the response returned by receiver. +// Formatl delimits and formats the response returned by the receiver. func formatl(payload string, status string) string { s := strings.TrimLeft(status, "\n") i := strings.Index(s, "\n") @@ -143,4 +146,4 @@ func formatl(payload string, status string) string { s = fmt.Sprintf("%s...", s[:i]) } return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index c5fd1a5e5220..0506ced08b49 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -3,11 +3,10 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" import ( - "io" "testing" "github.com/elastic/go-elasticsearch/v8/esapi" - "github.com/sirupsen/logrus" + "go.uber.org/zap" "github.com/stretchr/testify/assert" ) @@ -15,8 +14,7 @@ func TestNewClient(t *testing.T) { mockConfig := &Config{ Region: "US", } - mockLogger := logrus.New() - mockLogger.SetOutput(io.Discard) + mockLogger := zap.NewNop() writer := FlatWriter{} client, err := newClient(mockConfig, mockLogger, writer) @@ -62,8 +60,7 @@ func TestWritePayload(t *testing.T) { AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", }, } - mockLogger := logrus.New() - mockLogger.SetOutput(io.Discard) + mockLogger := zap.NewNop() mockWriter := FlatWriter{} client := &client{ diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 17cb4ffff300..321e30122fdf 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - "github.com/sirupsen/logrus" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/pdata/plog" @@ -18,13 +17,12 @@ import ( type sematextLogsExporter struct { config *Config client Client - logger *logrus.Logger + logger *zap.Logger } // newExporter creates a new instance of the sematextLogsExporter. func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { - logger := logrus.New() - logger.SetFormatter(&FlatFormatter{}) + logger := zap.NewNop() // Initialize Sematext client client, err := newClient(cfg, logger, FlatWriter{}) @@ -52,7 +50,7 @@ func (e *sematextLogsExporter) pushLogsData(_ context.Context, logs plog.Logs) e // Send the bulk payload to Sematext if err := e.client.Bulk(bulkPayload, e.config); err != nil { - e.logger.Errorf("Failed to send logs to Sematext: %v", err) + e.logger.Error("Failed to send logs to Sematext", zap.Error(err)) return err } @@ -94,13 +92,12 @@ func convertLogsToBulkPayload(logs plog.Logs) []map[string]any { // Start initializes the Sematext Logs Exporter. func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error { // Create a new logger with a FlatFormatter - logger := logrus.New() - logger.SetFormatter(&FlatFormatter{}) + logger := zap.NewNop() // Initialize the Sematext client client, err := newClient(e.config, logger, FlatWriter{}) if err != nil { - e.logger.Errorf("Failed to initialize Sematext client: %v", err) + e.logger.Error("Failed to initialize Sematext client", zap.Error(err)) return fmt.Errorf("failed to initialize Sematext client: %w", err) } if client == nil { diff --git a/exporter/sematextexporter/exporter_test.go b/exporter/sematextexporter/exporter_test.go index 1b45563ee04f..05f9032be2e2 100644 --- a/exporter/sematextexporter/exporter_test.go +++ b/exporter/sematextexporter/exporter_test.go @@ -57,8 +57,4 @@ func TestNewExporter(t *testing.T) { assert.Equal(t, mockConfig, exporter.config, "Exporter config does not match") assert.NotNil(t, exporter.client, "Exporter client should not be nil") assert.NotNil(t, exporter.logger, "Exporter logger should not be nil") - - flatFormatter, ok := exporter.logger.Formatter.(*FlatFormatter) - assert.True(t, ok, "Exporter logger should use FlatFormatter") - assert.NotNil(t, flatFormatter, "FlatFormatter should be properly initialized") } diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index 43db5294187f..c1b1e194a5eb 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -45,7 +45,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/influxdata/influxdb-observability/otel2influx v0.5.12 github.com/influxdata/line-protocol/v2 v2.2.1 - github.com/json-iterator/go v1.1.12 + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -77,7 +77,7 @@ require ( go.opentelemetry.io/otel/trace v1.30.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.31.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect From 748519a1d740a53759f0bc5269203f0c40dd94f3 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Mon, 23 Dec 2024 13:28:37 +0100 Subject: [PATCH 45/52] Fix lint issues --- exporter/sematextexporter/es_test.go | 2 +- exporter/sematextexporter/exporter.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 0506ced08b49..7c84494e0a58 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/elastic/go-elasticsearch/v8/esapi" - "go.uber.org/zap" "github.com/stretchr/testify/assert" + "go.uber.org/zap" ) func TestNewClient(t *testing.T) { diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 321e30122fdf..9145bbccb004 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -109,4 +109,4 @@ func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error e.logger.Info("Sematext Logs Exporter successfully started") return nil -} \ No newline at end of file +} From 8c698d32c3483341eac9bb07153ae0f4ed23c5d4 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Tue, 24 Dec 2024 09:38:12 +0100 Subject: [PATCH 46/52] Add more tests --- exporter/sematextexporter/config_test.go | 86 ++++++++++++++++++- exporter/sematextexporter/es.go | 1 - exporter/sematextexporter/es_test.go | 25 ++++++ .../sematextexporter/testdata/config.yaml | 2 +- 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/exporter/sematextexporter/config_test.go b/exporter/sematextexporter/config_test.go index 79516c582f46..5b8e05c05156 100644 --- a/exporter/sematextexporter/config_test.go +++ b/exporter/sematextexporter/config_test.go @@ -71,7 +71,7 @@ func TestLoadConfig(t *testing.T) { RandomizationFactor: backoff.DefaultRandomizationFactor, Multiplier: backoff.DefaultMultiplier, }, - Region: "US", + Region: "us", }, }, } @@ -90,3 +90,87 @@ func TestLoadConfig(t *testing.T) { }) } } +func TestConfigValidation(t *testing.T) { + tests := []struct { + name string + config *Config + expectError bool + }{ + { + name: "Valid configuration 1", + config: &Config{ + Region: "US", + MetricsConfig: MetricsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + LogsConfig: LogsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + }, + expectError: false, + }, + { + name: "Valid configuration 2", + config: &Config{ + Region: "EU", + MetricsConfig: MetricsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + LogsConfig: LogsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + }, + expectError: false, + }, + { + name: "Invalid region", + config: &Config{ + Region: "ASIA", + MetricsConfig: MetricsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + LogsConfig: LogsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + }, + expectError: true, + }, + { + name: "Invalid metrics AppToken length", + config: &Config{ + Region: "US", + MetricsConfig: MetricsConfig{ + AppToken: "short-token", + }, + LogsConfig: LogsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + }, + expectError: true, + }, + { + name: "Invalid logs AppToken length", + config: &Config{ + Region: "EU", + MetricsConfig: MetricsConfig{ + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + LogsConfig: LogsConfig{ + AppToken: "short-token", + }, + }, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.Validate() + if tt.expectError { + assert.Error(t, err, "Expected an error for invalid configuration") + } else { + assert.NoError(t, err, "Expected no error for valid configuration") + } + }) + } +} \ No newline at end of file diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 308604c7e7f7..90853de21bff 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -61,7 +61,6 @@ func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, e hostname, err := os.Hostname() if err != nil { logger.Warn("Could not retrieve hostname", zap.Error(err)) - hostname = "unknown" } return &client{ diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 7c84494e0a58..8aff283aeab0 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -4,6 +4,8 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" import ( "testing" + "os" + "fmt" "github.com/elastic/go-elasticsearch/v8/esapi" "github.com/stretchr/testify/assert" @@ -51,6 +53,29 @@ func TestBulkWithMockClient(t *testing.T) { assert.True(t, group.client.BulkCalled, "Expected Bulk to be called on the mock client") } } +func TestNewClient_ErrorRetrievingHostname(t *testing.T) { + var osHostname = os.Hostname + mockConfig := &Config{ + Region: "US", + LogsConfig: LogsConfig{ + LogsEndpoint: "https://logsene-receiver.sematext.com", + AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + }, + } + mockLogger := zap.NewNop() + writer := FlatWriter{} + + originalHostname := osHostname + osHostname = func() (string, error) { + return "", fmt.Errorf("mock error retrieving hostname") + } + defer func() { osHostname = originalHostname }() + + client, err := newClient(mockConfig, mockLogger, writer) + + assert.NotNil(t, client, "Expected client to be created even with hostname error") + assert.NoError(t, err, "Expected no error even with hostname error") +} func TestWritePayload(t *testing.T) { mockConfig := &Config{ diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index b43c4d60d1a8..ff3942bf6dfe 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -1,7 +1,7 @@ sematext/default-config: sematext/override-config: timeout: 500ms - region: US + region: us retry_on_failure: enabled: true initial_interval: 1s From de4f3312ee5887a5bcde442141b76f4bced7a933 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 09:49:51 +0100 Subject: [PATCH 47/52] Remove logrus --- exporter/sematextexporter/config_test.go | 6 +- exporter/sematextexporter/es.go | 39 ++++---- exporter/sematextexporter/es_test.go | 39 +------- exporter/sematextexporter/go.mod | 1 - exporter/sematextexporter/go.sum | 2 - exporter/sematextexporter/writer.go | 121 +++++------------------ exporter/sematextexporter/writer_test.go | 99 ++++++------------- 7 files changed, 76 insertions(+), 231 deletions(-) diff --git a/exporter/sematextexporter/config_test.go b/exporter/sematextexporter/config_test.go index 5b8e05c05156..c879a1ab7b7e 100644 --- a/exporter/sematextexporter/config_test.go +++ b/exporter/sematextexporter/config_test.go @@ -92,8 +92,8 @@ func TestLoadConfig(t *testing.T) { } func TestConfigValidation(t *testing.T) { tests := []struct { - name string - config *Config + name string + config *Config expectError bool }{ { @@ -173,4 +173,4 @@ func TestConfigValidation(t *testing.T) { } }) } -} \ No newline at end of file +} diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 90853de21bff..4615f68d7be1 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -10,7 +10,6 @@ import ( "fmt" "os" "reflect" - "strings" "time" "github.com/elastic/go-elasticsearch" @@ -18,8 +17,6 @@ import ( "go.uber.org/zap" ) -// artificialDocType designates a synthetic doc type for ES documents - type group struct { client *elasticsearch.Client token string @@ -128,21 +125,21 @@ func (c *client) Bulk(body any, config *Config) error { return nil } -// writePayload writes a formatted payload along with its status to the configured writer. -func (c *client) writePayload(payload string, status string) { - if c.config.WriteEvents.Load() { - c.writer.Write(formatl(payload, status)) - } else { - c.logger.Debug("WriteEvents disabled", zap.String("payload", payload), zap.String("status", status)) - } -} - -// Formatl delimits and formats the response returned by the receiver. -func formatl(payload string, status string) string { - s := strings.TrimLeft(status, "\n") - i := strings.Index(s, "\n") - if i > 0 { - s = fmt.Sprintf("%s...", s[:i]) - } - return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) -} +// // writePayload writes a formatted payload along with its status to the configured writer. +// func (c *client) writePayload(payload string, status string) { +// if c.config.WriteEvents.Load() { +// c.writer.Write(formatl(payload, status)) +// } else { +// c.logger.Debug("WriteEvents disabled", zap.String("payload", payload), zap.String("status", status)) +// } +// } + +// // Formatl delimits and formats the response returned by the receiver. +// func formatl(payload string, status string) string { +// s := strings.TrimLeft(status, "\n") +// i := strings.Index(s, "\n") +// if i > 0 { +// s = fmt.Sprintf("%s...", s[:i]) +// } +// return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) +// } diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index 8aff283aeab0..f09e857164a8 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -3,9 +3,9 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/sematextexporter" import ( - "testing" - "os" "fmt" + "os" + "testing" "github.com/elastic/go-elasticsearch/v8/esapi" "github.com/stretchr/testify/assert" @@ -77,41 +77,6 @@ func TestNewClient_ErrorRetrievingHostname(t *testing.T) { assert.NoError(t, err, "Expected no error even with hostname error") } -func TestWritePayload(t *testing.T) { - mockConfig := &Config{ - Region: "US", - LogsConfig: LogsConfig{ - LogsEndpoint: "https://logsene-receiver.sematext.com", - AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - }, - } - mockLogger := zap.NewNop() - - mockWriter := FlatWriter{} - client := &client{ - config: mockConfig, - logger: mockLogger, - writer: mockWriter, - } - - payload := "mockPayload" - status := "200" - client.writePayload(payload, status) - - // Validate that the payload and status are written correctly - expectedOutput := formatl(payload, status) - assert.Equal(t, expectedOutput, formatl(payload, status), "Payload should be formatted and written correctly") -} -func TestFormatl(t *testing.T) { - payload := "mockPayload" - status := "200 OK" - - formatted := formatl(payload, status) - expected := "mockPayload 200 OK" - - assert.Equal(t, expected, formatted, "Formatted payload should match the expected output") -} - type mockElasticClient struct { BulkCalled bool done chan struct{} diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index c1b1e194a5eb..34def3f8bb6d 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -6,7 +6,6 @@ require ( github.com/elastic/go-elasticsearch v0.0.0 github.com/elastic/go-elasticsearch/v8 v8.17.0 github.com/influxdata/influxdb-observability/common v0.5.8 - github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 go.opentelemetry.io/collector/config/confighttp v0.110.0 go.opentelemetry.io/collector/config/configopaque v1.16.0 go.opentelemetry.io/collector/config/configretry v1.16.0 diff --git a/exporter/sematextexporter/go.sum b/exporter/sematextexporter/go.sum index f070ac4802ef..b28001220750 100644 --- a/exporter/sematextexporter/go.sum +++ b/exporter/sematextexporter/go.sum @@ -78,8 +78,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo= -github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index dcafccb37d81..fbf3ce4d16ae 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -18,11 +18,11 @@ import ( "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/influxdb-observability/otel2influx" "github.com/influxdata/line-protocol/v2/lineprotocol" - fs "github.com/rifflock/lfshook" - "github.com/sirupsen/logrus" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/consumer/consumererror" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) @@ -267,107 +267,32 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st // Logs Support -// FlatWriter writes a raw message to log file. +// FlatWriter writes a raw message to a log file. type FlatWriter struct { - l *logrus.Logger + logger *zap.Logger } -// NewFlatWriter creates a new instance of flat writer. -func newFlatWriter(f string, c *Config) (*FlatWriter, error) { - l := logrus.New() - l.Out = io.Discard - - hook, err := initRotate( - f, - c.LogMaxAge, - c.LogMaxBackups, - c.LogMaxSize, - &FlatFormatter{}, - ) - w := &FlatWriter{ - l: l, - } - if err != nil { - return w, err - } - l.AddHook(hook) - return w, nil -} - -// Write dumps a raw message to log file. -func (w *FlatWriter) Write(message string) { - w.l.Print(message) -} - -// InitRotate returns a new fs hook that enables log file rotation with specified pattern, -// maximum size/TTL for existing log files. -func initRotate(filePath string, maxAge, maxBackups, maxSize int, f logrus.Formatter) (logrus.Hook, error) { - h, err := newRotateFile(RotateFileConfig{ +// NewFlatWriter creates a new instance of FlatWriter. +func newFlatWriter(filePath string, c *Config) (*FlatWriter, error) { + lumberjackLogger := &lumberjack.Logger{ Filename: filePath, - MaxAge: maxAge, - MaxBackups: maxBackups, - MaxSize: maxSize, - Level: logrus.DebugLevel, - Formatter: f, - }) - if err != nil { - // if we can't initialize file log rotation, configure logger - // without rotation capabilities - var pathMap fs.PathMap = make(map[logrus.Level]string, 0) - for _, ll := range logrus.AllLevels { - pathMap[ll] = filePath - } - return fs.NewHook(pathMap, f), fmt.Errorf("unable to initialize log rotate: %w", err) - } - return h, nil -} - -// RotateFileConfig is the configuration for the rotate file hook. -type RotateFileConfig struct { - Filename string - MaxSize int - MaxBackups int - MaxAge int - Level logrus.Level - Formatter logrus.Formatter -} - -// RotateFile represents the rotate file hook. -type RotateFile struct { - Config RotateFileConfig - logWriter io.Writer -} - -// NewRotateFile builds a new rotate file hook. -func newRotateFile(config RotateFileConfig) (logrus.Hook, error) { - if config.Filename == "" { - return nil, fmt.Errorf("filename is required") - } - hook := RotateFile{ - Config: config, + MaxSize: c.LogMaxSize, + MaxBackups: c.LogMaxBackups, + MaxAge: c.LogMaxAge, + Compress: true, } - hook.logWriter = &lumberjack.Logger{ - Filename: config.Filename, - MaxSize: config.MaxSize, - MaxBackups: config.MaxBackups, - MaxAge: config.MaxAge, - } - return &hook, nil -} - -// Fire is called by logrus when it is about to write the log entry. -func (hook *RotateFile) Fire(entry *logrus.Entry) error { - b, err := hook.Config.Formatter.Format(entry) - if err != nil { - return err - } - if _, err := hook.logWriter.Write(b); err != nil { - return err - } - return nil + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), + zapcore.AddSync(lumberjackLogger), + zap.DebugLevel, + )) + + return &FlatWriter{ + logger: logger, + }, nil } -// Levels determines log levels that for which the logs are written. -func (hook *RotateFile) Levels() []logrus.Level { - return logrus.AllLevels[:hook.Config.Level+1] +// Write logs a raw message to a log file. +func (w *FlatWriter) Write(message string) { + w.logger.Info(message) } diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index f41bf09b0108..aab9f07d6679 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -10,6 +10,7 @@ import ( "io" "net/http" "net/http/httptest" + "os" "strings" "sync" "testing" @@ -17,10 +18,11 @@ import ( "github.com/influxdata/influxdb-observability/common" "github.com/influxdata/line-protocol/v2/lineprotocol" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) func TestSematextHTTPWriterBatchOptimizeTags(t *testing.T) { @@ -226,84 +228,43 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { } func TestNewFlatWriter(t *testing.T) { + // Temporary file for testing + filePath := "test_log_file.log" + defer os.Remove(filePath) // Clean up after test + config := &Config{ LogsConfig: LogsConfig{ - LogMaxAge: 7, - LogMaxBackups: 5, - LogMaxSize: 10, + LogMaxSize: 5, // Max size in MB + LogMaxBackups: 3, + LogMaxAge: 7, // Max age in days }, } - writer, err := newFlatWriter("test.log", config) - assert.NoError(t, err) - assert.NotNil(t, writer) - assert.NotNil(t, writer.l) -} -func TestFlatWriterWrite(t *testing.T) { - var buf bytes.Buffer - logger := logrus.New() - logger.SetOutput(&buf) - writer := &FlatWriter{l: logger} + flatWriter, err := newFlatWriter(filePath, config) + assert.NoError(t, err, "Expected no error creating FlatWriter") + assert.NotNil(t, flatWriter, "Expected FlatWriter to be created successfully") - message := "test message" - writer.Write(message) - - assert.Contains(t, buf.String(), message) + // Check if the logger is initialized + assert.NotNil(t, flatWriter.logger, "Expected logger to be initialized") } -func TestInitRotate(t *testing.T) { - hook, err := initRotate("test.log", 7, 5, 10, &FlatFormatter{}) - assert.NoError(t, err) - assert.NotNil(t, hook) +func newTestFlatWriter(writer io.Writer) *FlatWriter { + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), + zapcore.AddSync(writer), + zap.DebugLevel, + )) + return &FlatWriter{logger: logger} } -func TestNewRotateFile(t *testing.T) { - config := RotateFileConfig{ - Filename: "test.log", - MaxSize: 10, - MaxBackups: 5, - MaxAge: 7, - Level: logrus.InfoLevel, - Formatter: &FlatFormatter{}, - } - - hook, err := newRotateFile(config) - assert.NoError(t, err) - assert.NotNil(t, hook) -} - -func TestRotateFileFire(t *testing.T) { - var buf bytes.Buffer - - hook := &RotateFile{ - Config: RotateFileConfig{ - Filename: "test.log", - MaxSize: 10, - MaxBackups: 5, - MaxAge: 7, - Level: logrus.InfoLevel, - Formatter: &logrus.TextFormatter{}, - }, - logWriter: &buf, - } - - entry := &logrus.Entry{ - Message: "test entry", - Level: logrus.InfoLevel, - } - - err := hook.Fire(entry) - assert.NoError(t, err) - assert.Contains(t, buf.String(), "test entry") -} +func TestFlatWriterWrite(t *testing.T) { + var buffer bytes.Buffer + flatWriter := newTestFlatWriter(&buffer) -func TestRotateFileLevels(t *testing.T) { - hook := &RotateFile{ - Config: RotateFileConfig{ - Level: logrus.WarnLevel, - }, - } + // Write a test message + testMessage := "This is a test log message." + flatWriter.Write(testMessage) - expectedLevels := logrus.AllLevels[:logrus.WarnLevel+1] - assert.Equal(t, expectedLevels, hook.Levels()) + // Verify the message exists in the buffer + assert.Contains(t, buffer.String(), testMessage, "Expected message to be logged") } From 63406b62c929ebc10f03218caa88ad42151d4e54 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 10:21:37 +0100 Subject: [PATCH 48/52] Handle errors --- exporter/sematextexporter/es.go | 20 +------------------- exporter/sematextexporter/writer.go | 3 +++ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 4615f68d7be1..b8149a70a914 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -47,7 +47,7 @@ func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, e }) if err != nil { logger.Error("Failed to create Elasticsearch client", zap.Error(err)) - return nil, err + return nil, fmt.Errorf("elasticsearch client creation failed: %w", err) } clients[config.LogsEndpoint] = group{ client: c, @@ -125,21 +125,3 @@ func (c *client) Bulk(body any, config *Config) error { return nil } -// // writePayload writes a formatted payload along with its status to the configured writer. -// func (c *client) writePayload(payload string, status string) { -// if c.config.WriteEvents.Load() { -// c.writer.Write(formatl(payload, status)) -// } else { -// c.logger.Debug("WriteEvents disabled", zap.String("payload", payload), zap.String("status", status)) -// } -// } - -// // Formatl delimits and formats the response returned by the receiver. -// func formatl(payload string, status string) string { -// s := strings.TrimLeft(status, "\n") -// i := strings.Index(s, "\n") -// if i > 0 { -// s = fmt.Sprintf("%s...", s[:i]) -// } -// return fmt.Sprintf("%s %s", strings.TrimSpace(payload), s) -// } diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index fbf3ce4d16ae..7a72d8bcc687 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -274,6 +274,9 @@ type FlatWriter struct { // NewFlatWriter creates a new instance of FlatWriter. func newFlatWriter(filePath string, c *Config) (*FlatWriter, error) { + if filePath == "" { + return nil, fmt.Errorf("filePath cannot be empty") + } lumberjackLogger := &lumberjack.Logger{ Filename: filePath, MaxSize: c.LogMaxSize, From 814b00632fbfd156354e019bd7b603f89be72d30 Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 10:39:27 +0100 Subject: [PATCH 49/52] Fix issue with writer --- exporter/sematextexporter/es.go | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index b8149a70a914..4e682c9b850c 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -46,6 +46,7 @@ func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, e Addresses: []string{config.LogsEndpoint}, }) if err != nil { + writer.Write("Failed to create Elasticsearch client: " + err.Error()) logger.Error("Failed to create Elasticsearch client", zap.Error(err)) return nil, fmt.Errorf("elasticsearch client creation failed: %w", err) } From 032d36e4d3a1771e511d66d784a467335b06dd9f Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 16:36:22 +0100 Subject: [PATCH 50/52] Remove flatwriter --- exporter/sematextexporter/es.go | 5 +-- exporter/sematextexporter/es_test.go | 6 ++-- exporter/sematextexporter/exporter.go | 4 +-- exporter/sematextexporter/go.mod | 1 - exporter/sematextexporter/go.sum | 2 -- exporter/sematextexporter/writer.go | 35 ------------------ exporter/sematextexporter/writer_test.go | 45 ------------------------ 7 files changed, 5 insertions(+), 93 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 4e682c9b850c..6e7e3fee45f9 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -26,7 +26,6 @@ type client struct { clients map[string]group config *Config logger *zap.Logger - writer FlatWriter hostname string } @@ -37,7 +36,7 @@ type Client interface { // NewClient creates a new instance of ES client that internally stores a reference // to both event and log receivers. -func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, error) { +func newClient(config *Config, logger *zap.Logger) (Client, error) { clients := make(map[string]group) // Client for shipping to logsene @@ -46,7 +45,6 @@ func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, e Addresses: []string{config.LogsEndpoint}, }) if err != nil { - writer.Write("Failed to create Elasticsearch client: " + err.Error()) logger.Error("Failed to create Elasticsearch client", zap.Error(err)) return nil, fmt.Errorf("elasticsearch client creation failed: %w", err) } @@ -65,7 +63,6 @@ func newClient(config *Config, logger *zap.Logger, writer FlatWriter) (Client, e clients: clients, config: config, logger: logger, - writer: writer, hostname: hostname, }, nil } diff --git a/exporter/sematextexporter/es_test.go b/exporter/sematextexporter/es_test.go index f09e857164a8..eda01a7f55e6 100644 --- a/exporter/sematextexporter/es_test.go +++ b/exporter/sematextexporter/es_test.go @@ -18,8 +18,7 @@ func TestNewClient(t *testing.T) { } mockLogger := zap.NewNop() - writer := FlatWriter{} - client, err := newClient(mockConfig, mockLogger, writer) + client, err := newClient(mockConfig, mockLogger) assert.NoError(t, err, "Expected no error while creating new client") assert.NotNil(t, client, "Expected client to be non-nil") @@ -63,7 +62,6 @@ func TestNewClient_ErrorRetrievingHostname(t *testing.T) { }, } mockLogger := zap.NewNop() - writer := FlatWriter{} originalHostname := osHostname osHostname = func() (string, error) { @@ -71,7 +69,7 @@ func TestNewClient_ErrorRetrievingHostname(t *testing.T) { } defer func() { osHostname = originalHostname }() - client, err := newClient(mockConfig, mockLogger, writer) + client, err := newClient(mockConfig, mockLogger) assert.NotNil(t, client, "Expected client to be created even with hostname error") assert.NoError(t, err, "Expected no error even with hostname error") diff --git a/exporter/sematextexporter/exporter.go b/exporter/sematextexporter/exporter.go index 9145bbccb004..65d7a0f33cc1 100644 --- a/exporter/sematextexporter/exporter.go +++ b/exporter/sematextexporter/exporter.go @@ -25,7 +25,7 @@ func newExporter(cfg *Config, set exporter.Settings) *sematextLogsExporter { logger := zap.NewNop() // Initialize Sematext client - client, err := newClient(cfg, logger, FlatWriter{}) + client, err := newClient(cfg, logger) if err != nil { set.Logger.Error("Failed to create Sematext client", zap.Error(err)) return nil @@ -95,7 +95,7 @@ func (e *sematextLogsExporter) Start(_ context.Context, _ component.Host) error logger := zap.NewNop() // Initialize the Sematext client - client, err := newClient(e.config, logger, FlatWriter{}) + client, err := newClient(e.config, logger) if err != nil { e.logger.Error("Failed to initialize Sematext client", zap.Error(err)) return fmt.Errorf("failed to initialize Sematext client: %w", err) diff --git a/exporter/sematextexporter/go.mod b/exporter/sematextexporter/go.mod index 34def3f8bb6d..f16df72456a5 100644 --- a/exporter/sematextexporter/go.mod +++ b/exporter/sematextexporter/go.mod @@ -11,7 +11,6 @@ require ( go.opentelemetry.io/collector/config/configretry v1.16.0 go.opentelemetry.io/collector/exporter v0.110.0 go.uber.org/goleak v1.3.0 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( diff --git a/exporter/sematextexporter/go.sum b/exporter/sematextexporter/go.sum index b28001220750..eb25ad5ca1fb 100644 --- a/exporter/sematextexporter/go.sum +++ b/exporter/sematextexporter/go.sum @@ -209,8 +209,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index 7a72d8bcc687..b8c8ea79400c 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -21,9 +21,6 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/consumer/consumererror" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "gopkg.in/natefinch/lumberjack.v2" ) var _ otel2influx.InfluxWriter = (*sematextHTTPWriter)(nil) @@ -267,35 +264,3 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st // Logs Support -// FlatWriter writes a raw message to a log file. -type FlatWriter struct { - logger *zap.Logger -} - -// NewFlatWriter creates a new instance of FlatWriter. -func newFlatWriter(filePath string, c *Config) (*FlatWriter, error) { - if filePath == "" { - return nil, fmt.Errorf("filePath cannot be empty") - } - lumberjackLogger := &lumberjack.Logger{ - Filename: filePath, - MaxSize: c.LogMaxSize, - MaxBackups: c.LogMaxBackups, - MaxAge: c.LogMaxAge, - Compress: true, - } - logger := zap.New(zapcore.NewCore( - zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), - zapcore.AddSync(lumberjackLogger), - zap.DebugLevel, - )) - - return &FlatWriter{ - logger: logger, - }, nil -} - -// Write logs a raw message to a log file. -func (w *FlatWriter) Write(message string) { - w.logger.Info(message) -} diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index aab9f07d6679..b7d449518087 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -4,13 +4,11 @@ package sematextexporter import ( - "bytes" "context" "fmt" "io" "net/http" "net/http/httptest" - "os" "strings" "sync" "testing" @@ -21,8 +19,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/component/componenttest" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) func TestSematextHTTPWriterBatchOptimizeTags(t *testing.T) { @@ -227,44 +223,3 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { }) } -func TestNewFlatWriter(t *testing.T) { - // Temporary file for testing - filePath := "test_log_file.log" - defer os.Remove(filePath) // Clean up after test - - config := &Config{ - LogsConfig: LogsConfig{ - LogMaxSize: 5, // Max size in MB - LogMaxBackups: 3, - LogMaxAge: 7, // Max age in days - }, - } - - flatWriter, err := newFlatWriter(filePath, config) - assert.NoError(t, err, "Expected no error creating FlatWriter") - assert.NotNil(t, flatWriter, "Expected FlatWriter to be created successfully") - - // Check if the logger is initialized - assert.NotNil(t, flatWriter.logger, "Expected logger to be initialized") -} - -func newTestFlatWriter(writer io.Writer) *FlatWriter { - logger := zap.New(zapcore.NewCore( - zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), - zapcore.AddSync(writer), - zap.DebugLevel, - )) - return &FlatWriter{logger: logger} -} - -func TestFlatWriterWrite(t *testing.T) { - var buffer bytes.Buffer - flatWriter := newTestFlatWriter(&buffer) - - // Write a test message - testMessage := "This is a test log message." - flatWriter.Write(testMessage) - - // Verify the message exists in the buffer - assert.Contains(t, buffer.String(), testMessage, "Expected message to be logged") -} From 3f84e20df4fc015f9c73d8f2f42c5702ce20b00a Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 22:51:24 +0100 Subject: [PATCH 51/52] Fix lint issues --- exporter/sematextexporter/es.go | 1 - exporter/sematextexporter/writer.go | 3 --- exporter/sematextexporter/writer_test.go | 1 - 3 files changed, 5 deletions(-) diff --git a/exporter/sematextexporter/es.go b/exporter/sematextexporter/es.go index 6e7e3fee45f9..72305b14c893 100644 --- a/exporter/sematextexporter/es.go +++ b/exporter/sematextexporter/es.go @@ -122,4 +122,3 @@ func (c *client) Bulk(body any, config *Config) error { return nil } - diff --git a/exporter/sematextexporter/writer.go b/exporter/sematextexporter/writer.go index b8c8ea79400c..3eaf1c49157d 100644 --- a/exporter/sematextexporter/writer.go +++ b/exporter/sematextexporter/writer.go @@ -261,6 +261,3 @@ func (b *sematextHTTPWriterBatch) convertFields(m map[string]any) (fields map[st } return } - -// Logs Support - diff --git a/exporter/sematextexporter/writer_test.go b/exporter/sematextexporter/writer_test.go index b7d449518087..14e26a7d9560 100644 --- a/exporter/sematextexporter/writer_test.go +++ b/exporter/sematextexporter/writer_test.go @@ -222,4 +222,3 @@ func TestComposeWriteURLDoesNotPanic(t *testing.T) { assert.NoError(t, err) }) } - From 5e33287d8125f5ba4edfa3e5e263495b2d03915e Mon Sep 17 00:00:00 2001 From: Akhigbe Eromosele Date: Fri, 27 Dec 2024 23:15:44 +0100 Subject: [PATCH 52/52] Remove unnecessary things from exporter --- exporter/sematextexporter/config.go | 11 +---------- exporter/sematextexporter/config_test.go | 4 ---- exporter/sematextexporter/factory.go | 7 ------- exporter/sematextexporter/testdata/config.yaml | 4 ---- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/exporter/sematextexporter/config.go b/exporter/sematextexporter/config.go index 26b26a6671b5..49722979ccf4 100644 --- a/exporter/sematextexporter/config.go +++ b/exporter/sematextexporter/config.go @@ -6,7 +6,6 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "fmt" "strings" - "sync/atomic" "go.opentelemetry.io/collector/config/confighttp" "go.opentelemetry.io/collector/config/configretry" @@ -56,15 +55,7 @@ type LogsConfig struct { // LogsEndpoint specifies the endpoint for receiving logs in Sematext LogsEndpoint string `mapstructure:"logs_endpoint"` // LogRequests determines whether request tracking is enabled - LogRequests bool `mapstructure:"logs_requests"` - // LogMaxAge is the max number of days to retain old log files - LogMaxAge int `mapstructure:"logs_max_age"` - // LogMaxBackups is the maximum number of old log files to retain. - LogMaxBackups int `mapstructure:"logs_max_backups"` - // LogMaxSize is the maximum size in megabytes of the log file before it gets rotated - LogMaxSize int `mapstructure:"logs_max_size"` - // WriteEvents determines if events are logged - WriteEvents atomic.Bool `mapstructure:"write_events"` + // LogRequests bool `mapstructure:"logs_requests"` } // Validate checks for invalid or missing entries in the configuration. diff --git a/exporter/sematextexporter/config_test.go b/exporter/sematextexporter/config_test.go index c879a1ab7b7e..832f826a86b3 100644 --- a/exporter/sematextexporter/config_test.go +++ b/exporter/sematextexporter/config_test.go @@ -57,10 +57,6 @@ func TestLoadConfig(t *testing.T) { LogsConfig: LogsConfig{ AppToken: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", LogsEndpoint: "https://logsene-receiver.sematext.com", - LogRequests: true, - LogMaxAge: 2, - LogMaxBackups: 10, - LogMaxSize: 10, }, BackOffConfig: configretry.BackOffConfig{ diff --git a/exporter/sematextexporter/factory.go b/exporter/sematextexporter/factory.go index 94d294386387..0f122dcd5670 100644 --- a/exporter/sematextexporter/factory.go +++ b/exporter/sematextexporter/factory.go @@ -8,7 +8,6 @@ package sematextexporter // import "github.com/open-telemetry/opentelemetry-coll import ( "context" "fmt" - "sync/atomic" "time" "github.com/influxdata/influxdb-observability/common" @@ -54,16 +53,10 @@ func createDefaultConfig() component.Config { LogsConfig: LogsConfig{ LogsEndpoint: "https://logsene-receiver.sematext.com", AppToken: appToken, - LogRequests: true, - LogMaxAge: 2, - LogMaxSize: 10, - LogMaxBackups: 10, - WriteEvents: atomic.Bool{}, }, BackOffConfig: configretry.NewDefaultBackOffConfig(), Region: "us", } - cfg.LogsConfig.WriteEvents.Store(false) return cfg } diff --git a/exporter/sematextexporter/testdata/config.yaml b/exporter/sematextexporter/testdata/config.yaml index ff3942bf6dfe..debf95358d46 100644 --- a/exporter/sematextexporter/testdata/config.yaml +++ b/exporter/sematextexporter/testdata/config.yaml @@ -17,10 +17,6 @@ sematext/override-config: payload_max_bytes: 27 logs: app_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - logs_requests: true - logs_max_age : 2 - logs_max_backups: 10 - logs_max_size: 10