-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Miguel Rodriguez
committed
Dec 14, 2023
1 parent
c931b99
commit e215cc1
Showing
16 changed files
with
1,216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Chronicle Forwarder Exporter | ||
|
||
The Chronicle Forwarder Exporter is designed for forwarding logs to a Chronicle endpoint using either Syslog or File-based methods. This exporter supports customization of data export types and various configuration options to tailor the connection and data handling to specific needs. | ||
|
||
## Minimum Agent Versions | ||
|
||
- Introduced: [v1.42.0](https://github.com/observIQ/bindplane-agent/releases/tag/v1.42.0) | ||
|
||
## Supported Pipelines | ||
|
||
- Logs | ||
|
||
## How It Works | ||
|
||
1. For Syslog, it establishes a network connection to the specified Chronicle forwarder endpoint. | ||
2. For File, it writes logs to a specified file path. | ||
|
||
## Configuration | ||
|
||
| Field | Type | Default Value | Required | Description | | ||
| -------------------- | ------------ | ------------- | -------- | ------------------------------------------------- | | ||
| export_type | string | `syslog` | `true` | Type of export, either `syslog` or `file`. | | ||
| syslog | SyslogConfig | | `true` | Configuration for Syslog connection. | | ||
| file | File | | `true` | Configuration for File connection. | | ||
| raw_log_field | string | | `false` | The field name to send raw logs to Chronicle. | | ||
| syslog.host | string | `127.0.0.1` | `false` | The Chronicle forwarder endpoint host. | | ||
| syslog.port | int | `10514` | `false` | The port to send logs to. | | ||
| syslog.network | string | `tcp` | `false` | The network protocol to use (e.g., `tcp`, `udp`). | | ||
| syslog.tls.key_file | string | | `false` | Configure the receiver to use TLS. | | ||
| syslog.tls.cert_file | string | | `false` | Configure the receiver to use TLS. | | ||
| file.path | string | | `false` | The path to the file for storing logs. | | ||
|
||
## Example Configurations | ||
|
||
### Syslog Configuration Example | ||
|
||
```yaml | ||
chronicleforwarderexporter: | ||
export_type: "syslog" | ||
syslog: | ||
host: "syslog.example.com" | ||
port: 10514 | ||
network: "tcp" | ||
``` | ||
### File Configuration Example | ||
```yaml | ||
chronicleforwarderexporter: | ||
export_type: "file" | ||
file: | ||
path: "/path/to/logfile" | ||
``` | ||
--- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright observIQ, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package chronicleforwarderexporter | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/observiq/bindplane-agent/expr" | ||
"go.opentelemetry.io/collector/component" | ||
"go.opentelemetry.io/collector/config/confighttp" | ||
"go.opentelemetry.io/collector/exporter/exporterhelper" | ||
"go.uber.org/zap" | ||
) | ||
|
||
const ( | ||
// ExportTypeSyslog is the syslog export type. | ||
ExportTypeSyslog = "syslog" | ||
|
||
// ExportTypeFile is the file export type. | ||
ExportTypeFile = "file" | ||
) | ||
|
||
// Config defines configuration for the Chronicle exporter. | ||
type Config struct { | ||
exporterhelper.TimeoutSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct. | ||
exporterhelper.QueueSettings `mapstructure:"sending_queue"` | ||
exporterhelper.RetrySettings `mapstructure:"retry_on_failure"` | ||
|
||
// ExportType is the type of export to use. | ||
ExportType string `mapstructure:"export_type"` | ||
|
||
// Syslog is the configuration for the connection to the Chronicle forwarder. | ||
Syslog SyslogConfig `mapstructure:"syslog"` | ||
|
||
// File is the configuration for the connection to the Chronicle forwarder. | ||
File File `mapstructure:"file"` | ||
|
||
// RawLogField is the field name that will be used to send raw logs to Chronicle. | ||
RawLogField string `mapstructure:"raw_log_field"` | ||
} | ||
|
||
// SyslogConfig defines configuration for the Chronicle forwarder connection. | ||
type SyslogConfig struct { | ||
// Host is the Chronicle forwarder endpoint to send logs to. | ||
Host string `mapstructure:"host"` | ||
|
||
// port is the port to send logs to. | ||
Port int `mapstructure:"port"` | ||
|
||
// Network is the network protocol to use. | ||
Network string `mapstructure:"network"` | ||
|
||
confighttp.HTTPServerSettings `mapstructure:",squash"` | ||
} | ||
|
||
// File defines configuration for sending to. | ||
type File struct { | ||
// Path is the path to the file to send to Chronicle. | ||
Path string `mapstructure:"path"` | ||
} | ||
|
||
func (cfg *Config) Validate() error { | ||
if cfg.ExportType != ExportTypeSyslog && cfg.ExportType != ExportTypeFile { | ||
return fmt.Errorf("export_type must be either 'syslog' or 'file'") | ||
} | ||
|
||
if cfg.ExportType == ExportTypeSyslog { | ||
if cfg.Syslog.Host == "" || cfg.Syslog.Port <= 0 || cfg.Syslog.Network == "" { | ||
return fmt.Errorf("incomplete syslog configuration: host, port, and network are required") | ||
} | ||
} | ||
|
||
if cfg.ExportType == ExportTypeFile { | ||
if cfg.File.Path == "" { | ||
return fmt.Errorf("file path is required for file export type") | ||
} | ||
} | ||
|
||
if cfg.RawLogField != "" { | ||
_, err := expr.NewOTTLLogRecordExpression(cfg.RawLogField, component.TelemetrySettings{ | ||
Logger: zap.NewNop(), | ||
}) | ||
if err != nil { | ||
return fmt.Errorf("raw_log_field is invalid: %s", err) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright observIQ, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package chronicleforwarderexporter | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConfig_Validate(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
cfg Config | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "Valid syslog config", | ||
cfg: Config{ | ||
ExportType: ExportTypeSyslog, | ||
Syslog: SyslogConfig{ | ||
Host: "localhost", | ||
Port: 514, | ||
Network: "tcp", | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Invalid syslog config - missing host", | ||
cfg: Config{ | ||
ExportType: ExportTypeSyslog, | ||
Syslog: SyslogConfig{ | ||
Port: 514, | ||
Network: "tcp", | ||
}, | ||
}, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "Valid file config", | ||
cfg: Config{ | ||
ExportType: ExportTypeFile, | ||
File: File{ | ||
Path: "/path/to/file", | ||
}, | ||
}, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "Invalid file config - missing path", | ||
cfg: Config{ | ||
ExportType: ExportTypeFile, | ||
File: File{}, | ||
}, | ||
wantErr: true, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
err := tt.cfg.Validate() | ||
if tt.wantErr { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright observIQ, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:generate mdatagen metadata.yaml | ||
|
||
// Package chronicleforwarderexporter exports OpenTelemetry data to Chronicle. | ||
package chronicleforwarderexporter // import "github.com/observiq/bindplane-agent/exporter/azureblobexporter" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright observIQ, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package chronicleforwarderexporter | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"fmt" | ||
"io" | ||
"net" | ||
"os" | ||
"strings" | ||
|
||
"go.opentelemetry.io/collector/consumer" | ||
"go.opentelemetry.io/collector/exporter" | ||
"go.opentelemetry.io/collector/pdata/plog" | ||
"go.uber.org/zap" | ||
) | ||
|
||
type chronicleForwarderExporter struct { | ||
cfg *Config | ||
logger *zap.Logger | ||
writer io.Writer | ||
marshaler logMarshaler | ||
endpoint string | ||
} | ||
|
||
func newExporter(cfg *Config, params exporter.CreateSettings) (*chronicleForwarderExporter, error) { | ||
exporter := &chronicleForwarderExporter{ | ||
cfg: cfg, | ||
logger: params.Logger, | ||
marshaler: newMarshaler(*cfg, params.TelemetrySettings), | ||
} | ||
|
||
switch cfg.ExportType { | ||
case ExportTypeSyslog: | ||
endpoint := buildEndpoint(cfg) | ||
|
||
var conn net.Conn | ||
var err error | ||
if cfg.Syslog.TLSSetting != nil { | ||
tlsConfig, err := cfg.Syslog.TLSSetting.LoadTLSConfig() | ||
if err != nil { | ||
return nil, fmt.Errorf("load TLS config: %w", err) | ||
} | ||
conn, err = tls.Dial(cfg.Syslog.Network, endpoint, tlsConfig) | ||
} else { | ||
conn, err = net.Dial(cfg.Syslog.Network, endpoint) | ||
} | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("dial: %w", err) | ||
} | ||
exporter.writer = conn | ||
|
||
case ExportTypeFile: | ||
var err error | ||
exporter.writer, err = os.OpenFile(cfg.File.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) | ||
if err != nil { | ||
return nil, fmt.Errorf("open file: %w", err) | ||
} | ||
} | ||
|
||
return exporter, nil | ||
} | ||
|
||
func buildEndpoint(cfg *Config) string { | ||
return fmt.Sprintf("%s:%d", cfg.Syslog.Host, cfg.Syslog.Port) | ||
} | ||
|
||
func (ce *chronicleForwarderExporter) Capabilities() consumer.Capabilities { | ||
return consumer.Capabilities{MutatesData: false} | ||
} | ||
|
||
func (ce *chronicleForwarderExporter) logsDataPusher(ctx context.Context, ld plog.Logs) error { | ||
payloads, err := ce.marshaler.MarshalRawLogs(ctx, ld) | ||
if err != nil { | ||
return fmt.Errorf("marshal logs: %w", err) | ||
} | ||
|
||
for _, payload := range payloads { | ||
if err := ce.send(payload); err != nil { | ||
return fmt.Errorf("upload to Chronicle forwarder: %w", err) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *chronicleForwarderExporter) send(msg string) error { | ||
if !strings.HasSuffix(msg, "\n") { | ||
msg = fmt.Sprintf("%s%s", msg, "\n") | ||
} | ||
_, err := fmt.Fprint(s.writer, msg) | ||
return err | ||
} | ||
|
||
func (s *chronicleForwarderExporter) Shutdown(ctx context.Context) error { | ||
Check warning on line 110 in exporter/chronicleforwarderexporter/exporter.go GitHub Actions / lint
|
||
if s.writer != nil { | ||
if err := s.writer.(io.Closer).Close(); err != nil { | ||
return fmt.Errorf("close writer: %w", err) | ||
} | ||
} | ||
return nil | ||
} |
Oops, something went wrong.