https://github.com/zone-eu/haraka-plugin-gelf
https://github.com/zone-eu/haraka-plugin-gelf
Last synced: 29 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/zone-eu/haraka-plugin-gelf
- Owner: zone-eu
- License: eupl-1.1
- Created: 2026-04-27T08:52:36.000Z (3 months ago)
- Default Branch: master
- Last Pushed: 2026-06-16T09:28:12.000Z (about 2 months ago)
- Last Synced: 2026-06-16T11:18:24.093Z (about 2 months ago)
- Language: JavaScript
- Size: 86.9 KB
- Stars: 0
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# haraka-plugin-gelf
A [Haraka](https://haraka.github.io/) plugin that forwards log messages to a [Graylog](https://graylog.org/) server via GELF UDP.
## Features
- Forwards all Haraka log messages to Graylog in GELF format
- UDP transport with automatic IPv4/IPv6 detection
- Chunked UDP support for large messages (GELF chunking spec)
- Optional gzip compression
- Per-plugin configuration with independent URL, fields, and routing
- Configurable custom GELF fields with variable interpolation
- Exposes a `loggelf` API on `server.notes` for other plugins to send structured GELF messages directly
## AI Usage Disclaimer
This project makes limited use of generative AI tools during development:
- Generative AI was used for brainstorming, test generation, documentation and code review assistance
- All production code was written, reviewed, and validated by a human
- Final design decisions and implementations are human-driven
- Any defects or limitations in the code are the responsibility of the human authors
## Installation
```
npm install haraka-plugin-gelf
```
Add to `config/plugins`:
```
gelf
```
## Configuration
Create `config/gelf.yaml`. A minimal configuration:
```yaml
url: 'udp://graylog.example.com:12201'
```
Full configuration with all options:
```yaml
# Enable/disable GELF logging
enabled: true
# Enable/disable Haraka log hook
# true - all Haraka log messages are forwarded to Graylog
# false - only messages sent via the server.notes.loggelf API are forwarded
log_hook_enabled: false
# Minimum level of log messages to forward to graylog from log hook
# 0 - emerg, 1 - alert, 2 - crit, 3 - error, 4 - warn, 5 - notice, 6 - info, 7 - debug
log_hook_level: 4
# GELF UDP endpoint. Supports udp://, udp4://, udp6:// schemes
url: 'udp://graylog.example.com:12201'
# Compress messages with gzip
compress: true
# Maximum UDP packet/chunk size in bytes (64–65475)
# 1420 suits standard Ethernet MTU; 8192 suits jumbo frames
max_chunk_size: 1420
# Override the hostname reported in GELF messages (defaults to os.hostname())
# hostname: mail.example.com
# If true, no further log plugins will be called after this one
last: false
# Custom fields added to every GELF message (see Custom Fields below)
fields:
logger: '${logger}'
connection: '${connection_uuid}'
transaction: '${transaction_uuid}'
# Per-plugin overrides (see Per-plugin Configuration below)
# plugins:
# rspamd:
# url: 'udp4://rspamd-graylog.example.com:12201'
# fields:
# component: 'rspamd'
```
## URL scheme
The `url` setting controls both the transport endpoint and the preferred address family:
| Scheme | Behaviour |
|-----------|----------------------------------------------------------------------|
| `udp://` | Dual-stack; uses system address family preference (IPv4 or IPv6) |
| `udp4://` | Forces IPv4 |
| `udp6://` | Forces IPv6 only |
When a hostname resolves to multiple addresses, DNS round-robin provides basic load distribution.
## Custom Fields
The `fields` section lets you add fixed or dynamic fields to every GELF message. Field values are strings and support variable interpolation using `${variable}` syntax.
The following variables are available:
| Variable | Value |
|--------------------|------------------------------------------------|
| `${logger}` | Name of the plugin that emitted the log entry |
| `${connection_uuid}` | Haraka connection UUID |
| `${transaction_uuid}` | Haraka transaction UUID (includes `.N` suffix) |
If a variable is not available in context (e.g. `${transaction_uuid}` outside of a transaction), the field is omitted from the message entirely.
Example:
```yaml
fields:
logger: '${logger}'
connection: '${connection_uuid}'
transaction: '${transaction_uuid}'
environment: 'production'
facility: 'smtp'
```
To explicitly suppress a field that would be inherited from the main config in a per-plugin override, set it to `null`:
```yaml
plugins:
rspamd:
fields:
transaction: null # omit transaction field for rspamd messages
component: 'rspamd'
```
## Per-plugin Configuration
Any top-level setting can be overridden per Haraka plugin under the `plugins` key. Plugin names match `plugin.name` in Haraka (e.g. `karma`, `dkim`, `rcpt_to.in_host_list`).
Per-plugin `fields` are merged with (not replaced by) the top-level `fields`.
```yaml
plugins:
karma:
url: 'udp://karma-graylog.example.com:12201'
last: true
fields:
component: 'karma'
rspamd:
enabled: false
wildduck:
log_hook_enabled: true
url: 'udp4://wildduck-graylog.example.com:12201'
fields:
component: 'mx'
transaction: null
queue_id: '${transaction_uuid}'
```
## API for other plugins
When loaded, the plugin exposes `server.notes.loggelf` for structured GELF logging from other plugins.
### `message(callerPlugin, msg)`
Send a raw GELF message object. Returns `cfg.last` (boolean) — when `true`, no further log plugins will be called.
```javascript
exports.hook_queue = function (next, connection) {
connection.server.notes.loggelf?.message(this, {
short_message: 'Mail queued',
level: 6, // INFO
_recipient: connection.transaction.rcpt_to.toString(),
_sender: connection.transaction.mail_from.toString(),
});
next(OK);
};
```
### `log(callerPlugin, connection, level, shortMessage, additionalFields)`
Send a structured log message. Automatically formats `short_message` as `[transaction_uuid] [plugin_name] message` to match Haraka's log format, and populates `connection_uuid` and `transaction_uuid` template variables from the connection object.
```javascript
gelf?.log(this, connection, 6, 'Mail queued', { _queue: 'outbound' });
```
### Convenience log methods
All accept `(callerPlugin, connection, shortMessage, additionalFields)`. `connection` may be `null`.
```javascript
const gelf = connection.server.notes.loggelf;
gelf?.emergency(this, connection, 'System failure');
gelf?.alert(this, connection, 'Disk almost full');
gelf?.critical(this, connection, 'Database unreachable');
gelf?.error(this, connection, 'Delivery failed', { _recipient: 'user@example.com' });
gelf?.warning(this, connection, 'Rate limit approached');
gelf?.notice(this, connection, 'New connection');
gelf?.info(this, connection, 'Message accepted');
gelf?.debug(this, connection, 'Processing step', { _detail: 'some value' });
```
### Additional fields in `msg`
Fields in `msg` beyond the standard GELF fields are included as additional fields, automatically prefixed with `_` if not already. Reserved GELF field names (`host`, `version`, `short_message`, `full_message`, `timestamp`, `level`, `facility`, `file`, `line`, `id`) are never duplicated as additional fields.
| Type | Behaviour |
|-----------|------------------------------|
| `string` | Passed as-is |
| `number` | Passed as-is |
| `boolean` | Converted to `"true"`/`"false"` |
| `Date` | Converted to ISO 8601 string |
| Other | JSON round-tripped |
## GELF message format
Messages are sent as GELF 1.1 JSON over UDP. Supported standard fields:
| Field | Source |
|-----------------|------------------------------------------------|
| `version` | Always `"1.1"` |
| `host` | `msg.host`, `hostname` config, or `os.hostname()` |
| `short_message` | Required |
| `full_message` | Optional |
| `timestamp` | `msg.timestamp` (Date or number) or `Date.now()` |
| `level` | Syslog severity (0–7), defaults to INFO (6) |
| `facility` | Optional |
| `file` | Optional |
| `line` | Optional integer |
## Log levels
| Level | Value |
|-----------|-------|
| emergency | 0 |
| alert | 1 |
| critical | 2 |
| error | 3 |
| warning | 4 |
| notice | 5 |
| info | 6 |
| debug | 7 |
Haraka log levels `DATA` and `PROTOCOL` are mapped to `DEBUG` (7).
## License
EUPL-1.1+