syslog

package module
v0.1.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package syslog provides an RFC 5424 syslog audit.Output implementation supporting TCP, UDP, and TCP+TLS (including mTLS) transport.

Construction

New dials the syslog server immediately — the server must be reachable at construction time:

out, err := syslog.New(&syslog.Config{
    Network: "tcp+tls",
    Address: "syslog.example.com:6514",
    TLSCA:   "/etc/audit/ca.pem",
})

Valid [Config.Network] values: "tcp" (default), "udp", "tcp+tls". Use "tcp+tls" with [Config.TLSCert] and [Config.TLSKey] for mTLS.

Reconnection

TCP and TLS connections are re-established automatically on write failure, up to [Config.MaxRetries] attempts (default 10). UDP is connectionless and does not reconnect, but messages exceeding the UDP MTU are silently truncated by the network.

To observe reconnect events, wire an audit.OutputMetrics value via WithOutputMetrics at construction. If the value also implements ReconnectRecorder its RecordReconnect method is called on each reconnect attempt (structural typing — no explicit registration needed).

Recommended import alias:

import auditsyslog "github.com/axonops/audit/syslog"

Index

Examples

Constants

View Source
const (
	// DefaultAppName is the default application name in the
	// syslog header when [Config.AppName] is empty.
	DefaultAppName = "audit"

	// DefaultFacility is the default syslog facility when
	// [Config.Facility] is empty.
	DefaultFacility = "local0"

	// DefaultMaxRetries is the default number of reconnection
	// attempts before giving up.
	DefaultMaxRetries = 10

	// MaxMaxRetries is the upper bound for MaxRetries. Values above
	// this are rejected to prevent unbounded retry loops.
	MaxMaxRetries = 20

	// DefaultBufferSize is the default async buffer capacity for the
	// syslog output. Matches the default for all other async outputs.
	DefaultBufferSize = 10_000

	// MaxOutputBufferSize is the maximum allowed per-output async
	// buffer capacity.
	MaxOutputBufferSize = 100_000

	// DefaultBatchSize is the default number of events accumulated
	// before a flush is triggered. Matches [loki.DefaultBatchSize].
	DefaultBatchSize = 100

	// MaxBatchSize is the upper bound for BatchSize. Values above
	// this cause [New] to return an error wrapping
	// [audit.ErrConfigInvalid].
	MaxBatchSize = 10_000

	// DefaultFlushInterval is the default maximum time between
	// flushes. Matches [loki.DefaultFlushInterval].
	DefaultFlushInterval = 5 * time.Second

	// MinFlushInterval is the lower bound for FlushInterval. Values
	// below this cause [New] to return an error — a sub-millisecond
	// interval would busy-spin the writeLoop.
	MinFlushInterval = 1 * time.Millisecond

	// MaxFlushInterval is the upper bound for FlushInterval.
	MaxFlushInterval = 1 * time.Hour

	// DefaultMaxBatchBytes is the default maximum accumulated batch
	// size in bytes before a flush is triggered. 1 MiB matches
	// [loki.DefaultMaxBatchBytes]. Events exceeding this threshold
	// alone trigger an immediate flush (the event is sent in its own
	// batch; it is never dropped).
	DefaultMaxBatchBytes = 1 << 20 // 1 MiB

	// MinMaxBatchBytes is the lower bound for MaxBatchBytes.
	MinMaxBatchBytes = 1 << 10 // 1 KiB

	// MaxMaxBatchBytes is the upper bound for MaxBatchBytes. Capped
	// at 10 MiB to match [loki.MaxMaxBatchBytes]; real-world syslog
	// receivers (syslog-ng, rsyslog) typically reject single messages
	// over 64 KiB, but batches of many smaller messages can legitimately
	// reach several MiB.
	MaxMaxBatchBytes = 10 << 20 // 10 MiB

	// DefaultMaxEventBytes is the default per-event size cap at
	// [Output.Write] entry. Events with data longer than this are
	// rejected with [audit.ErrEventTooLarge] (#688) — a defence
	// against consumer-controlled memory pressure. 1 MiB matches
	// the cap used by loki and webhook.
	DefaultMaxEventBytes = 1 << 20 // 1 MiB

	// MinMaxEventBytes is the lower bound for MaxEventBytes.
	MinMaxEventBytes = 1 << 10 // 1 KiB

	// MaxMaxEventBytes is the upper bound for MaxEventBytes.
	MaxMaxEventBytes = 10 << 20 // 10 MiB

	// DefaultTLSHandshakeTimeout is the default total budget for the
	// TCP dial + TLS handshake on `network: tcp+tls` connections.
	// Matches [net/http.DefaultTransport.TLSHandshakeTimeout]. Without
	// a bound, a server that completes the TCP three-way handshake
	// but never sends ServerHello would wedge [New] indefinitely
	// (#746).
	DefaultTLSHandshakeTimeout = 10 * time.Second

	// MinTLSHandshakeTimeout is the lower bound for TLSHandshakeTimeout.
	// 100ms is the practical floor — typical TLS 1.3 handshakes on a
	// LAN complete in 5–20ms but cold-start handshakes (cert verify
	// with stapled OCSP, key derivation) can hit ~80ms on slow
	// hardware. Production deployments SHOULD use ≥1s.
	MinTLSHandshakeTimeout = 100 * time.Millisecond

	// MaxTLSHandshakeTimeout is the upper bound for TLSHandshakeTimeout.
	// 60s is the absolute ceiling; any single handshake taking longer
	// is almost certainly a wedged connection that benefits from
	// being failed and retried.
	MaxTLSHandshakeTimeout = 60 * time.Second
)

Variables

This section is empty.

Functions

func NewFactory

func NewFactory(factory audit.OutputMetricsFactory) audit.OutputFactory

NewFactory returns an audit.OutputFactory that creates syslog outputs from YAML configuration and wires per-output metrics via the supplied audit.OutputMetricsFactory. When factory is non-nil, the returned audit.Output receives its per-output audit.OutputMetrics via WithOutputMetrics at construction time; if the returned metrics also implement ReconnectRecorder, reconnection telemetry is wired in automatically. Pass nil to disable per-output metrics.

Signature is identical to the other output modules' `NewFactory` (file, webhook, loki) for consistency (#581).

Types

type Config

type Config struct {
	// Network is the transport protocol: "tcp", "udp", or "tcp+tls".
	// Empty defaults to "tcp". Note: UDP syslog may silently truncate
	// or drop messages larger than ~2048 bytes (RFC 5424 §6.1).
	// Use TCP or TCP+TLS for reliable delivery of large audit events.
	Network string

	// Address is the syslog server address in host:port format.
	// REQUIRED; an empty address causes [New] to return
	// an error.
	Address string

	// AppName is the application name in the syslog header.
	// Empty defaults to [DefaultAppName] ("audit").
	AppName string

	// Facility is the syslog facility name. Supported values:
	// kern, user, mail, daemon, auth, syslog, lpr, news, uucp,
	// cron, authpriv, ftp, local0 through local7.
	// Empty defaults to [DefaultFacility] ("local0").
	// Unknown values cause [New] to return an error.
	Facility string

	// TLSCert is the path to the client certificate for mTLS.
	// Both TLSCert and TLSKey must be set for client authentication.
	TLSCert string

	// TLSKey is the path to the client private key for mTLS.
	// Both TLSCert and TLSKey must be set for client authentication.
	TLSKey string

	// TLSCA is the path to the CA certificate for server verification.
	// When set, the server's certificate is verified against this CA.
	TLSCA string

	// TLSPolicy controls TLS version and cipher suite policy. When nil,
	// the default policy (TLS 1.3 only) is used. See [audit.TLSPolicy]
	// for details on enabling TLS 1.2 fallback.
	TLSPolicy *audit.TLSPolicy

	// Hostname overrides the hostname in the syslog RFC 5424 header.
	// When empty, [os.Hostname] is used at construction time. Set this
	// to match the auditor-wide host value from [audit.WithHost].
	Hostname string

	// TLSHandshakeTimeout bounds the total time spent on the TCP dial
	// plus the TLS handshake during [New] and on every reconnect.
	// Without this bound, a server that completes the TCP three-way
	// handshake but never sends ServerHello would wedge [New]
	// indefinitely (#746).
	//
	// Zero defaults to [DefaultTLSHandshakeTimeout] (10s — matches
	// [net/http.DefaultTransport.TLSHandshakeTimeout]). Values
	// outside [MinTLSHandshakeTimeout]–[MaxTLSHandshakeTimeout]
	// (100ms–60s) cause [New] to return an error wrapping
	// [audit.ErrConfigInvalid].
	//
	// On non-TLS networks (`tcp`, `udp`) this field is silently
	// ignored: it has no effect and is not validated.
	//
	// A handshake that exceeds this budget at runtime returns a
	// transient error containing the substring "tls handshake
	// timeout"; the existing reconnect path retries the connection.
	TLSHandshakeTimeout time.Duration

	// MaxRetries is the maximum number of consecutive reconnection
	// attempts before giving up. Zero defaults to
	// [DefaultMaxRetries] (10).
	MaxRetries int

	// BufferSize is the internal async buffer capacity. When full,
	// new events are dropped and [audit.OutputMetrics.RecordDrop] is
	// called. Zero defaults to [DefaultBufferSize] (10,000). Values
	// above [MaxOutputBufferSize] (100,000) cause [New] to return an
	// error wrapping [audit.ErrConfigInvalid].
	BufferSize int

	// BatchSize is the number of events accumulated in the write
	// loop before triggering a flush to the syslog server. Zero
	// defaults to [DefaultBatchSize] (100). Values above
	// [MaxBatchSize] (10,000) cause [New] to return an error
	// wrapping [audit.ErrConfigInvalid]. Set to 1 to disable
	// batching (every event flushes immediately — effectively
	// restoring pre-#599 per-event write behaviour).
	//
	// Matches the conventions established by [loki.Config.BatchSize]
	// and [github.com/axonops/audit/webhook.Config.BatchSize] so an
	// operator tuning multi-output deployments sees the same knob
	// across all batching outputs.
	BatchSize int

	// FlushInterval is the maximum time between flushes, regardless
	// of whether the batch has reached [Config.BatchSize]. Zero
	// defaults to [DefaultFlushInterval] (5 s). Values below
	// [MinFlushInterval] (1 ms) or above [MaxFlushInterval] (1 h)
	// cause [New] to return an error wrapping
	// [audit.ErrConfigInvalid].
	//
	// With the 5 s default, a single-event-per-5s audit trickle can
	// see up to 5 s of delivery latency. Consumers needing lower
	// latency should lower FlushInterval or set [Config.BatchSize]
	// to 1.
	FlushInterval time.Duration

	// MaxBatchBytes is the maximum accumulated payload size (sum of
	// event data lengths) before a flush is triggered, independent
	// of [Config.BatchSize]. Zero defaults to [DefaultMaxBatchBytes]
	// (1 MiB). Values below [MinMaxBatchBytes] (1 KiB) or above
	// [MaxMaxBatchBytes] (10 MiB) cause [New] to return an error
	// wrapping [audit.ErrConfigInvalid].
	//
	// A single event exceeding MaxBatchBytes is flushed alone — it is
	// never dropped. Events are never split across frames; RFC 5425
	// octet-counting framing is preserved per message.
	MaxBatchBytes int

	// MaxEventBytes is the maximum byte length accepted by
	// [Output.Write] for a single event. Events exceeding this cap
	// are rejected with [audit.ErrEventTooLarge] wrapping
	// [audit.ErrValidation] and [audit.OutputMetrics.RecordDrop] is
	// called. Zero defaults to [DefaultMaxEventBytes] (1 MiB).
	// Values below [MinMaxEventBytes] (1 KiB) or above
	// [MaxMaxEventBytes] (10 MiB) cause [New] to return an error
	// wrapping [audit.ErrConfigInvalid].
	//
	// Introduced by #688 as a defence against consumer-controlled
	// memory pressure: a single oversized event in a batching path
	// can be held in the async channel, the batch slice, and the
	// retry buffer simultaneously. A default 10 000-slot buffer
	// carrying 10 MiB events could pin ~100 GiB before backpressure
	// triggers.
	MaxEventBytes int
}

Config holds configuration for Output.

Example (Mtls)
package main

import (
	"fmt"

	"github.com/axonops/audit/syslog"
)

func main() {
	// mTLS syslog with client certificate authentication.
	cfg := &syslog.Config{
		Network: "tcp+tls",
		Address: "syslog.example.com:6514",
		TLSCert: "/etc/audit/client-cert.pem",
		TLSKey:  "/etc/audit/client-key.pem",
		TLSCA:   "/etc/audit/ca.pem",
	}
	fmt.Printf("network=%s address=%s cert=%s key=%s ca=%s\n",
		cfg.Network, cfg.Address, cfg.TLSCert, cfg.TLSKey, cfg.TLSCA)
}
Output:
network=tcp+tls address=syslog.example.com:6514 cert=/etc/audit/client-cert.pem key=/etc/audit/client-key.pem ca=/etc/audit/ca.pem
Example (Tcp)
package main

import (
	"fmt"

	"github.com/axonops/audit/syslog"
)

func main() {
	// Plain TCP syslog — the simplest configuration.
	cfg := &syslog.Config{
		Network:  "tcp",
		Address:  "syslog.example.com:514",
		Facility: "local0",
		AppName:  "myapp",
	}
	fmt.Printf("network=%s address=%s facility=%s app=%s\n",
		cfg.Network, cfg.Address, cfg.Facility, cfg.AppName)
}
Output:
network=tcp address=syslog.example.com:514 facility=local0 app=myapp
Example (Tls)
package main

import (
	"fmt"

	"github.com/axonops/audit/syslog"
)

func main() {
	// TLS syslog with CA verification.
	cfg := &syslog.Config{
		Network: "tcp+tls",
		Address: "syslog.example.com:6514",
		TLSCA:   "/etc/audit/ca.pem",
	}
	fmt.Printf("network=%s address=%s ca=%s\n", cfg.Network, cfg.Address, cfg.TLSCA)
}
Output:
network=tcp+tls address=syslog.example.com:6514 ca=/etc/audit/ca.pem

func (Config) Format added in v0.1.2

func (c Config) Format(f fmt.State, _ rune)

Format writes the redacted representation to the formatter. This prevents TLS key path leakage via %+v and all other format verbs.

func (Config) GoString added in v0.1.2

func (c Config) GoString() string

GoString returns the same redacted representation as Config.String. This prevents TLS key path leakage when configs are formatted via %#v.

func (Config) String

func (c Config) String() string

String returns a human-readable representation of the config with TLS key paths redacted. This prevents credential path leakage when configs are accidentally logged via %v or %+v.

type Option added in v0.1.11

type Option func(*options)

Option configures a syslog Output at construction time. Options are passed as variadic arguments to New and applied in order before any configuration validation, TLS setup, or warning emission.

func WithDiagnosticLogger added in v0.1.11

func WithDiagnosticLogger(l *slog.Logger) Option

WithDiagnosticLogger routes construction-time and runtime warnings (TLS policy, reconnection backoff, buffer-full drops) to the given logger. When nil or not supplied, warnings go to slog.Default.

Consumers normally do not call this directly when using github.com/axonops/audit/outputconfig.Load — outputconfig plumbs the auditor's diagnostic logger into every output it constructs. Use this option when constructing a syslog output programmatically and you want its warnings to match your application's log handler.

The option mirrors github.com/axonops/audit.WithDiagnosticLogger at the auditor level; the same logger may be passed to both for consistent routing.

func WithOutputMetrics added in v0.1.12

func WithOutputMetrics(m audit.OutputMetrics) Option

WithOutputMetrics sets the audit.OutputMetrics sink for this output. When omitted or nil, metrics calls become no-ops via audit.NoOpOutputMetrics. Mirrors WithDiagnosticLogger in usage and zero-value semantics.

If the supplied value also implements ReconnectRecorder, reconnection telemetry is wired in automatically (structural typing — see net/http.Flusher precedent).

Consumers normally do not call this directly when using github.com/axonops/audit/outputconfig.Load — outputconfig wires per-output metrics through the audit.OutputMetricsFactory supplied via outputconfig.WithOutputMetricsFactory.

type Output

type Output struct {
	// contains filtered or unexported fields
}

Output writes serialised audit events to a syslog server over TCP, UDP, or TCP+TLS (including mTLS). Events are formatted as RFC 5424 structured syslog messages with the pre-serialised audit payload (JSON or CEF) as the message body.

Write enqueues events into an internal buffered channel and returns immediately. A background goroutine reads from the channel and performs the actual syslog write with reconnection handling.

Reconnection

On connection failure, the background goroutine attempts bounded exponential backoff reconnection (100ms to 30s with jitter, up to [Config.MaxRetries] attempts). If all retries are exhausted, the event is dropped and an error metric is recorded. The goroutine continues processing subsequent events.

UDP limitations

UDP syslog is fire-and-forget. Write over UDP rarely returns an error even if no server is listening. RFC 5424 recommends receivers support messages up to 2048 bytes on UDP; larger payloads may be silently truncated or dropped by the OS. Consumers with large audit events SHOULD use TCP or TCP+TLS.

TLS certificates

TLS certificate files are loaded once at construction time and are NOT hot-reloaded. If a certificate expires and is rotated on disk, the output continues using the old certificate until the process is restarted.

Output is safe for concurrent use.

func New

func New(cfg *Config, opts ...Option) (*Output, error)

New creates a new Output from the given config. It validates the config, establishes the initial connection, and starts the background writeLoop goroutine.

Per-output metrics may be supplied at construction via WithOutputMetrics. When omitted, telemetry calls become no-ops.

Optional Option arguments tune construction-time behaviour. Pass WithDiagnosticLogger to route TLS-policy warnings to a custom logger.

func (*Output) Close

func (s *Output) Close() error

Close signals the background goroutine to drain and flush, then waits for completion and closes the syslog connection. Close is idempotent and safe for concurrent use.

func (*Output) DestinationKey

func (s *Output) DestinationKey() string

DestinationKey returns the syslog server address, enabling duplicate destination detection via audit.DestinationKeyer.

func (*Output) LastDeliveryNanos added in v0.1.12

func (s *Output) LastDeliveryNanos() int64

LastDeliveryNanos returns the wall-clock UnixNano of the most recent successful syslog write, or 0 if no write has yet succeeded. Implements audit.LastDeliveryReporter (#753).

func (*Output) Name

func (s *Output) Name() string

Name returns the human-readable identifier for this output.

func (*Output) ReportsDelivery added in v0.1.11

func (s *Output) ReportsDelivery() bool

ReportsDelivery returns true, indicating that Output reports its own delivery metrics from the background writeLoop after actual syslog delivery, not from the Write enqueue path.

func (*Output) Write

func (s *Output) Write(data []byte) error

Write enqueues a serialised audit event for async delivery to the syslog server with the default severity (LOG_INFO). The data is copied before enqueuing. If the internal buffer is full, the event is dropped. Write never blocks the caller.

func (*Output) WriteWithMetadata

func (s *Output) WriteWithMetadata(data []byte, meta audit.EventMetadata) error

WriteWithMetadata enqueues a serialised audit event for async delivery with the syslog severity derived from the audit event's severity field.

type ReconnectRecorder added in v0.1.12

type ReconnectRecorder interface {
	// RecordReconnect records a syslog reconnection attempt. success
	// indicates whether the reconnection succeeded. address is the
	// configured host:port. Implementations SHOULD NOT use address
	// as an unbounded metric label.
	RecordReconnect(address string, success bool)
}

ReconnectRecorder is an OPTIONAL extension interface for syslog reconnection telemetry. A consumer's audit.OutputMetrics implementation MAY also implement ReconnectRecorder. When the syslog output receives per-output metrics via WithOutputMetrics (or factory wiring via outputconfig.WithOutputMetricsFactory), it type-asserts for ReconnectRecorder and invokes RecordReconnect on every connect attempt. Precedent: net/http.Flusher as an optional extension on [http.ResponseWriter].

Consumers who do not care about reconnection telemetry need not implement this interface — the base audit.OutputMetrics contract is sufficient.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL