Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] Add syslog format to ingest CLI #445

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion config_examples/syslog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ receivers:
- type: add
field: attributes.device.type
value: "ubuntu-syslog"
syslog/udp:
udp:
listen_address: "0.0.0.0:54528"
add_attributes: true
protocol: rfc5424

exporters:
otlphttp:
Expand All @@ -37,5 +42,5 @@ exporters:
service:
pipelines:
logs:
receivers: [syslog/f5, syslog/host]
receivers: [syslog/f5, syslog/host, syslog/udp]
exporters: [otlphttp]
7 changes: 7 additions & 0 deletions internal/data-ingest-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ The tool accepts the following input parameters:
- `--receiver-port`: The port of the OTLP receiver created to act as a sink for the collector.
- `--receiver-type`: The type of receiver created to act as a sink for the collector (options: `http`, `grpc`). Please not, that when using the `http` option with Collector's `otlphttp exporter`, you need to disable the compression on the exporter, as no decompression is supported.
- `--statsd-protocol`: Statsd protocol to send metrics (options: 'udp', 'udp4', 'udp6', 'tcp', 'tcp4', 'tcp6', 'unixgram').
- `--syslog-transport`: Syslog network transport (options: 'udp', 'tcp')

## Example Commands

Expand All @@ -50,3 +51,9 @@ The tool accepts the following input parameters:
```shell
./data-ingest --input-file=data.json --input-format=otlpjson --collector-url=http://collector.example.com:4317 --output-file=received.json --receiver-port=4319
```

2. Send Syslog data to a collector:

```shell
./data-ingest --input-format syslog --input-file $(pwd)/commands/syslog/testdata/rfc6587-non-transparent-framing --collector-url localhost:54526
```
76 changes: 76 additions & 0 deletions internal/data-ingest-cli/commands/syslog/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package syslog

import (
"context"
"fmt"
"os"

"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/receiver"
otlpreceiver "github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/receiver/otlp"
"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/receiver/otlphttp"
"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/sender/syslog"
)

type Config struct {
InputFile string
CollectorURL string
Transport string
OutputFile string
ReceiverPort int
ReceiverType string
}

type Cmd struct {
cfg Config
receiver receiver.Receiver
}

func New(cfg Config) (*Cmd, error) {
if cfg.Transport != "tcp" && cfg.Transport != "udp" {
return nil, fmt.Errorf("invalid transport: %q", cfg.Transport)
}

c := &Cmd{
cfg: cfg,
}

if cfg.ReceiverPort > 0 && cfg.OutputFile != "" {
switch cfg.ReceiverType {
case "grpc":
c.receiver = otlpreceiver.NewOTLPReceiver(otlpreceiver.Config{
Port: cfg.ReceiverPort,
OutputFile: cfg.OutputFile,
})
case "http":
c.receiver = otlphttp.NewOTLPHTTPReceiver(otlphttp.Config{
Port: cfg.ReceiverPort,
OutputFile: cfg.OutputFile,
})
default:
return nil, fmt.Errorf("invalid receiver type %s", cfg.ReceiverType)
}
}

return c, nil
}

func (c *Cmd) Do(ctx context.Context) error {
return c.sendLogs(ctx)
}

func (c *Cmd) sendLogs(ctx context.Context) error {
fileContent, err := os.ReadFile(c.cfg.InputFile)
if err != nil {
return err
}

sender, err := syslog.Connect(ctx, &syslog.Config{
Endpoint: c.cfg.CollectorURL,
Transport: c.cfg.Transport,
})
if err != nil {
return err
}

return sender.Write(ctx, string(fileContent))
}
10 changes: 10 additions & 0 deletions internal/data-ingest-cli/commands/syslog/testdata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Syslog test data

All files in this directory can be used as the basis for custom syslog payloads
to be sent to the syslog receiver. The payloads were taken from examples used to
test the parser used for the syslog receiver, so they should be sufficiently
comprehensive as a basis.

## Attribution

All payloads in this directory were derived from test cases provided in the `syslogtest` package [here](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/pkg/stanza/operator/parser/syslog/syslogtest/data.go#L46).
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<34>Feb 4 11:14:27 1.2.3.4 apache_server: test message
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<86>1 2015-08-05T21:58:59.693Z 192.168.2.132 SecureAuth0 23108 ID52020 [SecureAuth@27389 UserHostAddress="192.168.2.132" Realm="SecureAuth0" UserID="Tester2" PEN="27389"] Found the user for retrieving user's profile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Feb 4 11:14:27 1.2.3.4 apache_server: test message
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<123>1 2015-08-05T21:58:59.693Z 192.168.2.132 SecureAuth0 23108 ID52020 [SecureAuth@27389 UserHostAddress="192.168.2.132" Realm="SecureAuth0" UserID="Tester2" PEN="27389"] Found the user for retrieving user's profile
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
215 <86>1 2015-08-05T21:58:59.693Z 192.168.2.132 SecureAuth0 23108 ID52020 [SecureAuth@27389 UserHostAddress="192.168.2.132" Realm="SecureAuth0" UserID="Tester2" PEN="27389"] Found the user for retrieving user's profile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
77 <86>1 2015-08-05T21:58:59.693Z 192.168.2.132 inactive - - - partition is p2
10 changes: 8 additions & 2 deletions internal/data-ingest-cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ module github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli
go 1.23.5

require (
go.opentelemetry.io/collector/pdata v1.24.0
go.opentelemetry.io/collector/config/confignet v1.25.0
go.opentelemetry.io/collector/config/configtls v1.25.0
go.opentelemetry.io/collector/pdata v1.25.0
google.golang.org/grpc v1.70.0
)

require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
go.opentelemetry.io/collector/config/configopaque v1.25.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d // indirect
google.golang.org/protobuf v1.36.2 // indirect
google.golang.org/protobuf v1.36.4 // indirect
)
38 changes: 24 additions & 14 deletions internal/data-ingest-cli/go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
Expand Down Expand Up @@ -31,18 +33,26 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/collector/pdata v1.24.0 h1:D6j92eAzmAbQgivNBUnt8r9juOl8ugb+ihYynoFZIEg=
go.opentelemetry.io/collector/pdata v1.24.0/go.mod h1:cf3/W9E/uIvPS4MR26SnMFJhraUCattzzM6qusuONuc=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/collector/config/confignet v1.25.0 h1:DteOIhwvXkmGhqggKU/s0qfjpVW21TGku1vM2S0xArY=
go.opentelemetry.io/collector/config/confignet v1.25.0/go.mod h1:ZppUH1hgUJOubawEsxsQ9MzEYFytqo2GnVSS7d4CVxc=
go.opentelemetry.io/collector/config/configopaque v1.25.0 h1:raFi+CC8Sn4KzKCPhtnnrnkDQ0eFzJCN8xJpQh9d1sU=
go.opentelemetry.io/collector/config/configopaque v1.25.0/go.mod h1:sW0t0iI/VfRL9VYX7Ik6XzVgPcR+Y5kejTLsYcMyDWs=
go.opentelemetry.io/collector/config/configtls v1.25.0 h1:x915Us8mhYWGB025LBMH8LT9ZPdvg2WKAyCQ7IDUSfw=
go.opentelemetry.io/collector/config/configtls v1.25.0/go.mod h1:jE4WbJE12AltJ3BZU1R0GnYI8D14bTqbTq4yuaTHdms=
go.opentelemetry.io/collector/pdata v1.25.0 h1:AmgBklQfbfy0lT8qsoJtRuYMZ7ZV3VZvkvhjSDentrg=
go.opentelemetry.io/collector/pdata v1.25.0/go.mod h1:Zs7D4RXOGS7E2faGc/jfWdbmhoiHBxA7QbpuJOioxq8=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
Expand Down Expand Up @@ -82,7 +92,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU=
google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 changes: 18 additions & 5 deletions internal/data-ingest-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/commands/otlpjson"
"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/commands/statsd"
"github.com/Dynatrace/dynatrace-otel-collector/internal/data-ingest-cli/commands/syslog"
)

func main() {
Expand All @@ -16,8 +17,8 @@ func main() {
collectorURL := flag.String("collector-url", "localhost:4317", "URL of the OpenTelemetry collector")
outputFile := flag.String("output-file", "", "Path to the file where received OTLP data will be stored")
inputFormat := flag.String("input-format", "otlp-json", "Input format (options: 'otlp-json', 'syslog', 'statsd')")
statsdProtocol := flag.String("statsd-protocol", "udp4", "Statsd protocol to send metrics (options: 'udp', 'udp4', 'udp6', 'tcp', 'tcp4', 'tcp6', 'unixgram')")
otlpSignalType := flag.String("otlp-signal-type", "", "OTLP signal type (options: 'logs', 'traces', 'metrics')")
syslogTransport := flag.String("syslog-transport", "tcp", "Syslog network transport (options: 'udp', 'tcp')")
receiverPort := flag.Int("receiver-port", 0, "OTLP Receiver port. If set, the tool will open a grpc server on the specified port to receive data and store it in an output file")
receiverType := flag.String("receiver-type", "http", "The type of receiver created to act as a sink for the collector (options: `http`, `grpc`)")

Expand All @@ -34,7 +35,7 @@ func main() {
fmt.Println("Output File:", *outputFile)
fmt.Println("Input Format:", *inputFormat)
fmt.Println("OTLP Signal Type:", *otlpSignalType)
fmt.Println("Statsd protocol:", *statsdProtocol)
fmt.Println("Syslog transport:", *syslogTransport)
fmt.Println("Receiver type:", *receiverType)

switch *inputFormat {
Expand All @@ -54,8 +55,21 @@ func main() {
log.Fatalf("could not execute command: %s", err.Error())
}
case "syslog":
// Handle reading from syslog and sending to collector
fmt.Println("Reading from syslog and sending to collector...")
fmt.Println("Reading syslog data and sending it to collector...")
cmd, err := syslog.New(syslog.Config{
InputFile: *inputFile,
CollectorURL: *collectorURL,
Transport: *syslogTransport,
OutputFile: *outputFile,
ReceiverPort: *receiverPort,
ReceiverType: *receiverType,
})
if err != nil {
log.Fatalf("could not execute command: %s", err.Error())
}
if err := cmd.Do(context.Background()); err != nil {
log.Fatalf("could not execute command: %s", err.Error())
}
case "statsd":
log.Println("Reading from statsd and sending to collector...")
cmd, err := statsd.New(statsd.Config{
Expand All @@ -64,7 +78,6 @@ func main() {
SignalType: *otlpSignalType,
OutputFile: *outputFile,
ReceiverPort: *receiverPort,
Protocol: *statsdProtocol,
ReceiverType: *receiverType,
})
if err != nil {
Expand Down
98 changes: 98 additions & 0 deletions internal/data-ingest-cli/sender/syslog/sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright The OpenTelemetry Authors
// Copyright Dynatrace LLC
// SPDX-License-Identifier: Apache-2.0

package syslog

import (
"context"
"crypto/tls"
"fmt"
"net"
"strings"
"sync"

"go.opentelemetry.io/collector/config/confignet"
"go.opentelemetry.io/collector/config/configtls"
)

// Config defines configuration for Syslog exporter.
type Config struct {
// Syslog server address
Endpoint string `mapstructure:"endpoint"`
// Syslog server port
Port int `mapstructure:"port"`
// Transport for syslog communication
// options: tcp, udp
Transport string `mapstructure:"network"`

// TLSSetting struct exposes TLS client configuration.
TLSSetting configtls.ClientConfig `mapstructure:"tls"`
}

type sender struct {
transport string
addr string

// Currently unset, but can be updated to test Syslog receivers with TLS enabled
tlsConfig *tls.Config
mu sync.Mutex
conn net.Conn
}

func Connect(ctx context.Context, cfg *Config) (*sender, error) {
s := &sender{
transport: cfg.Transport,
addr: cfg.Endpoint,
}

s.mu.Lock()
defer s.mu.Unlock()

err := s.dial(ctx)
if err != nil {
return nil, err
}
return s, err
}

func (s *sender) dial(ctx context.Context) error {
if s.conn != nil {
s.conn.Close()
s.conn = nil
}
var err error
if s.tlsConfig != nil && s.transport == string(confignet.TransportTypeTCP) {
dialer := tls.Dialer{Config: s.tlsConfig}
s.conn, err = dialer.DialContext(ctx, s.transport, s.addr)
} else {
dialer := new(net.Dialer)
s.conn, err = dialer.DialContext(ctx, s.transport, s.addr)
}
return err
}

func (s *sender) Write(ctx context.Context, msgStr string) error {
s.mu.Lock()
defer s.mu.Unlock()

if s.conn != nil {
if err := s.write(msgStr); err == nil {
return nil
}
}
if err := s.dial(ctx); err != nil {
return err
}

return s.write(msgStr)
}

func (s *sender) write(msg string) error {
// check if logs contains new line character at the end, if not add it
if !strings.HasSuffix(msg, "\n") {
msg = fmt.Sprintf("%s%s", msg, "\n")
}
_, err := fmt.Fprint(s.conn, msg)
return err
}