Haraka 3.1.4 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +1 -1
- package/{Changes.md → CHANGELOG.md} +34 -0
- package/CONTRIBUTORS.md +26 -26
- package/README.md +68 -93
- package/SECURITY.md +178 -0
- package/bin/haraka +7 -14
- package/config/plugins +0 -3
- package/docs/Connection.md +126 -39
- package/docs/CoreConfig.md +92 -74
- package/docs/HAProxy.md +41 -25
- package/docs/Logging.md +68 -38
- package/docs/Outbound.md +124 -179
- package/docs/Plugins.md +38 -59
- package/docs/Transaction.md +78 -83
- package/docs/Tutorial.md +122 -209
- package/docs/plugins/aliases.md +1 -141
- package/docs/plugins/auth/auth_ldap.md +2 -39
- package/docs/plugins/max_unrecognized_commands.md +4 -18
- package/docs/plugins/process_title.md +3 -3
- package/docs/plugins/reseed_rng.md +11 -13
- package/docs/plugins/tls.md +7 -7
- package/docs/plugins/toobusy.md +10 -4
- package/docs/tutorials/SettingUpOutbound.md +40 -48
- package/endpoint.js +32 -2
- package/outbound/hmail.js +3 -2
- package/outbound/index.js +3 -0
- package/package.json +21 -34
- package/plugins/queue/smtp_forward.js +4 -4
- package/run_tests +3 -15
- package/server.js +17 -7
- package/smtp_client.js +8 -6
- package/test/connection.js +234 -0
- package/test/endpoint.js +32 -4
- package/test/host_pool.js +57 -31
- package/test/logger.js +75 -135
- package/test/outbound/bounce_net_errors.js +87 -131
- package/test/outbound/bounce_rfc3464.js +177 -254
- package/test/outbound/hmail.js +19 -0
- package/test/outbound/index.js +189 -0
- package/test/outbound/queue.js +92 -0
- package/test/plugins/auth/auth_base.js +39 -44
- package/test/plugins/auth/auth_vpopmaild.js +8 -9
- package/test/plugins/queue/smtp_forward.js +953 -183
- package/test/plugins/rcpt_to.host_list_base.js +58 -93
- package/test/plugins/rcpt_to.in_host_list.js +126 -175
- package/test/plugins/record_envelope_addresses.js +8 -8
- package/test/plugins/status.js +10 -10
- package/test/plugins/tls.js +9 -19
- package/test/plugins/xclient.js +75 -110
- package/test/plugins.js +10 -13
- package/test/rfc1869.js +50 -70
- package/test/server.js +438 -421
- package/test/smtp_client.js +1192 -218
- package/test/tls_socket.js +242 -0
- package/tls_socket.js +18 -22
package/docs/CoreConfig.md
CHANGED
|
@@ -1,76 +1,94 @@
|
|
|
1
1
|
# Core Configuration Files
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
If either of these files
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- plugins
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
3
|
+
Haraka reads the configuration files in this document directly. Plugins typically own their own files (named after the plugin); see the individual plugin docs.
|
|
4
|
+
|
|
5
|
+
See also [Logging](Logging.md), [HAProxy](HAProxy.md), and [Outbound](Outbound.md).
|
|
6
|
+
|
|
7
|
+
## smtp.yaml / smtp.json
|
|
8
|
+
|
|
9
|
+
If either of these files exists it is loaded first. It uses the YAML/JSON override mechanism from [haraka-config](https://github.com/haraka/haraka-config) and can provide the entire configuration as a single file.
|
|
10
|
+
|
|
11
|
+
## plugins
|
|
12
|
+
|
|
13
|
+
A newline-delimited list of plugins to load. Lines starting with `#` are ignored.
|
|
14
|
+
|
|
15
|
+
## smtp.ini
|
|
16
|
+
|
|
17
|
+
Controls the SMTP listener and the master process.
|
|
18
|
+
|
|
19
|
+
| Key | Default | Description |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `listen` | `[::0]:25` | Comma-separated `IP:port` pairs to listen on (e.g. `127.0.0.1:25,127.0.0.1:587`). |
|
|
22
|
+
| `listen_host` / `port` | — | Legacy. If set, a `<listen_host>:<port>` entry is prepended to `listen`. Prefer `listen`. |
|
|
23
|
+
| `smtps_port` | `465` | Port used by the optional implicit-TLS listener. |
|
|
24
|
+
| `public_ip` | none | The server's public IP. Helps NAT-aware plugins (SPF, GeoIP) when Haraka is behind NAT. If `stun` is on `$PATH` Haraka will try to discover it automatically. |
|
|
25
|
+
| `inactivity_timeout` | `300` | Idle seconds before a client socket is dropped. |
|
|
26
|
+
| `nodes` | `1` | Number of worker processes to fork. The string `cpus` forks one per CPU. |
|
|
27
|
+
| `user` / `group` | — | User and group to drop privileges to (name or numeric ID). |
|
|
28
|
+
| `ignore_bad_plugins` | `0` | If `1`, Haraka starts even if some plugins fail to compile. |
|
|
29
|
+
| `daemonize` | `false` | If `true`, fork into the background at start-up. |
|
|
30
|
+
| `daemon_log_file` | `/var/log/haraka.log` | Where to redirect stdout/stderr when daemonized. |
|
|
31
|
+
| `daemon_pid_file` | `/var/run/haraka.pid` | Where to write the PID file. |
|
|
32
|
+
| `graceful_shutdown` | `false` | If `true`, wait for in-flight sockets on shutdown. |
|
|
33
|
+
| `force_shutdown_timeout` | `30` | Seconds to wait before forcing shutdown. |
|
|
34
|
+
|
|
35
|
+
## me
|
|
36
|
+
|
|
37
|
+
A single-line file containing the server name used in `Received:` headers
|
|
38
|
+
and elsewhere. Defaults to `hostname(1)`.
|
|
39
|
+
|
|
40
|
+
## connection.ini
|
|
41
|
+
|
|
42
|
+
Per-connection limits and behaviours. See inline comments in the shipped
|
|
43
|
+
`config/connection.ini` for full details.
|
|
44
|
+
|
|
45
|
+
| Section / Key | Default | Description |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `main.spool_dir` | `/tmp` | Directory for temporary spool files. |
|
|
48
|
+
| `main.spool_after` | `-1` | Size (bytes) at which to spool the message to disk. `-1` never spools; `0` always spools. |
|
|
49
|
+
| `main.strict_rfc1869` | `false` | Reject `MAIL FROM` / `RCPT TO` that violates RFC 1869/821 (spurious spaces, missing brackets). |
|
|
50
|
+
| `main.smtputf8` | `true` | Advertise `SMTPUTF8` (RFC 6531). |
|
|
51
|
+
| `haproxy.hosts` | empty | Array of IPs/CIDRs allowed to send the PROXY protocol. See [HAProxy.md](HAProxy.md). |
|
|
52
|
+
| `headers.add_received` | `true` | Add a `Received:` header to incoming mail. |
|
|
53
|
+
| `headers.clean_auth_results` | `true` | Strip inbound `Authentication-Results:` headers before plugins run. |
|
|
54
|
+
| `headers.show_version` | `true` | Include the Haraka version in the SMTP banner and the `Received:` header. |
|
|
55
|
+
| `headers.max_lines` | `1000` | Maximum number of header lines accepted. |
|
|
56
|
+
| `headers.max_received` | `100` | Maximum number of `Received:` headers before mail is rejected as looping. |
|
|
57
|
+
| `max.bytes` | `26214400` | Maximum message size (advertised as the `SIZE` ESMTP extension). |
|
|
58
|
+
| `max.line_length` | `512` | SMTP command line length cap. Clients exceeding this are dropped with `521`. |
|
|
59
|
+
| `max.data_line_length` | `992` | Maximum line length in `DATA`. Longer lines are wrapped with `CRLF SPACE` (Sendmail behaviour) and `transaction.notes.data_line_length_exceeded` is set. |
|
|
60
|
+
| `max.mime_parts` | `1000` | Maximum MIME parts per message. |
|
|
61
|
+
| `message.greeting` | — | Array. Lines used as the SMTP greeting banner. |
|
|
62
|
+
| `message.helo` | `Haraka is at your service.` | Reply text for `HELO` / `EHLO`. |
|
|
63
|
+
| `message.close` | `closing connection. …` | Reply text on `QUIT`. |
|
|
64
|
+
| `uuid.banner_chars` | `6` | Number of UUID chars included in the SMTP banner (`0` to disable, `40` for the full UUID). |
|
|
65
|
+
| `uuid.deny_chars` | `0` | Number of UUID chars prepended (in brackets) to deny messages. |
|
|
66
|
+
|
|
67
|
+
## plugin_timeout
|
|
68
|
+
|
|
69
|
+
Single-integer file. Seconds to allow a plugin hook to run before Haraka
|
|
70
|
+
automatically advances to the next hook. Default: `30`.
|
|
71
|
+
|
|
72
|
+
A per-plugin override may be placed at `config/<plugin>.timeout`. A value
|
|
73
|
+
of `0` disables the timeout for that plugin — use with care. Plugins in
|
|
74
|
+
subdirectories need a matching path: `queue/smtp_forward` looks for
|
|
75
|
+
`config/queue/smtp_forward.timeout`.
|
|
76
|
+
|
|
77
|
+
## outbound.ini
|
|
78
|
+
|
|
79
|
+
Configures the outbound delivery engine. The most common keys are listed
|
|
80
|
+
below; see [Outbound.md](Outbound.md) for the full set.
|
|
81
|
+
|
|
82
|
+
| Key | Default | Description |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `disabled` | `false` | Disable outbound delivery entirely. |
|
|
85
|
+
| `concurrency_max` | `100` | Maximum simultaneous outbound deliveries. |
|
|
86
|
+
| `enable_tls` | `true` | Use STARTTLS opportunistically on outbound deliveries. |
|
|
87
|
+
| `maxTempFailures` | `13` | Number of temporary failures before a message bounces. |
|
|
88
|
+
| `always_split` | `false` | Create one queue file per recipient instead of one per destination domain. |
|
|
89
|
+
| `received_header` | `Haraka outbound` | Value used in the outbound `Received:` header. |
|
|
90
|
+
| `inet_prefer` | `default` | IP family preference for MX lookups: `v4`, `v6`, or `default` (OS preference). |
|
|
91
|
+
|
|
92
|
+
## outbound.bounce_message
|
|
93
|
+
|
|
94
|
+
Template used when an outbound message bounces. The default is usually fine. Available template variables are documented in the source of `outbound/hmail.js`.
|
package/docs/HAProxy.md
CHANGED
|
@@ -1,43 +1,59 @@
|
|
|
1
|
-
# HAProxy PROXY
|
|
1
|
+
# HAProxy PROXY Protocol Support
|
|
2
2
|
|
|
3
|
-
Haraka supports PROXY protocol
|
|
3
|
+
Haraka supports version 1 (text format) of the HAProxy [PROXY protocol][proxy-spec], which allows an upstream proxy to tell Haraka the real client IP and port. DNSBLs, allow-lists, and other IP-based plugins then see the original client rather than the proxy.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The PROXY v2 binary header is not currently supported.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Configuration
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
PROXY support is disabled by default. To enable it, list the IPs (or CIDRs) of trusted proxies in `connection.ini`:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```ini
|
|
12
|
+
[haproxy]
|
|
13
|
+
hosts[] = 192.0.2.4
|
|
14
|
+
hosts[] = 192.0.2.5/30
|
|
15
|
+
hosts[] = 2001:db8::1
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Connections from any other IP get a `DENYSOFTDISCONNECT` if they send a `PROXY` command. `DENYSOFT` is deliberate — it avoids permanently rejecting valid mail when a misconfiguration causes a legitimate proxy to fall outside the allow-list.
|
|
19
|
+
|
|
20
|
+
When a listed proxy connects, Haraka **does not** send the SMTP banner; it waits for the `PROXY` command. If none arrives within 30 seconds the connection is closed with `421 PROXY timed out`.
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
## What plugins see after PROXY is parsed
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
| Property | Value |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| `connection.remote.ip` / `.port` | the **real** client address from the PROXY header |
|
|
27
|
+
| `connection.local.ip` / `.port` | the destination IP/port from the PROXY header |
|
|
28
|
+
| `connection.proxy.allowed` | `true` |
|
|
29
|
+
| `connection.proxy.ip` | the proxy's address (i.e. the original socket peer) |
|
|
30
|
+
| `connection.proxy.type` | `'haproxy'` |
|
|
31
|
+
| `connection.notes.proxy` | full parsed record: `{ type, proto, src_ip, src_port, dst_ip, dst_port, proxy_ip }` |
|
|
16
32
|
|
|
17
|
-
HAProxy
|
|
33
|
+
## HAProxy configuration
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
You need HAProxy ≥ 1.5. The `send-proxy` option on each backend server tells HAProxy to emit the v1 header on every connection.
|
|
20
36
|
|
|
21
37
|
```
|
|
22
38
|
listen smtp :25
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
server smtp2 ip.of.haraka.server2:25 check-send-proxy check inter 10s send-proxy
|
|
29
|
-
server smtp3 ip.of.haraka.server3:25 check-send-proxy check inter 10s send-proxy
|
|
30
|
-
server smtp4 ip.of.haraka.server4:25 check-send-proxy check inter 10s send-proxy
|
|
31
|
-
server smtp5 ip.of.haraka.server5:25 check-send-proxy check inter 10s send-proxy
|
|
39
|
+
mode tcp
|
|
40
|
+
option tcplog
|
|
41
|
+
balance roundrobin
|
|
42
|
+
server smtp1 ip.of.haraka1:25 check-send-proxy check inter 10s send-proxy
|
|
43
|
+
server smtp2 ip.of.haraka2:25 check-send-proxy check inter 10s send-proxy
|
|
32
44
|
```
|
|
33
45
|
|
|
34
|
-
The
|
|
46
|
+
The `check-send-proxy` flag is required for HAProxy's health checks because Haraka does not respond with a banner before the PROXY header arrives.
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
### Health checks
|
|
49
|
+
|
|
50
|
+
`option smtpchk` drops the connection mid-handshake and shows up as `CONNRESET` in Haraka's logs. A cleaner check is to use `tcp-check` and politely close with `QUIT`:
|
|
37
51
|
|
|
38
52
|
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
option tcp-check
|
|
54
|
+
tcp-check expect rstring ^220
|
|
55
|
+
tcp-check send QUIT\r\n
|
|
56
|
+
tcp-check expect rstring ^221
|
|
43
57
|
```
|
|
58
|
+
|
|
59
|
+
[proxy-spec]: https://www.haproxy.org/download/2.8/doc/proxy-protocol.txt
|
package/docs/Logging.md
CHANGED
|
@@ -1,83 +1,109 @@
|
|
|
1
1
|
# Haraka Logging
|
|
2
2
|
|
|
3
|
-
Haraka has built-in
|
|
3
|
+
Haraka has a built-in logger (described below) and a plugin hook (`log`) that lets log plugins ship messages elsewhere — for example to syslog via [haraka-plugin-syslog](https://github.com/haraka/haraka-plugin-syslog).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Configuration
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### log.ini
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```ini
|
|
10
|
+
[main]
|
|
11
|
+
; data, protocol, debug, info, notice, warn, error, crit, alert, emerg
|
|
12
|
+
level=info
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
; prepend ISO-8601 timestamps to log entries (built-in logger only)
|
|
15
|
+
timestamps=false
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
; default, logfmt, json
|
|
18
|
+
format=default
|
|
19
|
+
```
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
### loglevel
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
A single-line file for quick CLI tweaks:
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
```sh
|
|
26
|
+
echo DEBUG > config/loglevel
|
|
27
|
+
```
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
When both `log.ini` and the `loglevel` file are present, whichever was edited most recently wins at runtime — `loglevel` is convenient for an interactive bump without touching `log.ini`.
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
### log_timestamps
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
to the log method.
|
|
33
|
+
A single-value file that toggles timestamp prepending. Equivalent to `main.timestamps` in `log.ini`. If either source enables timestamps, they are enabled.
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
## Log Levels
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
In ascending severity (and decreasing verbosity):
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
| Level | Numeric | Use |
|
|
40
|
+
| -------- | ------- | --- |
|
|
41
|
+
| DATA | 9 | message body bytes — extremely verbose |
|
|
42
|
+
| PROTOCOL | 8 | SMTP wire protocol |
|
|
43
|
+
| DEBUG | 7 | developer diagnostics |
|
|
44
|
+
| INFO | 6 | general informational |
|
|
45
|
+
| NOTICE | 5 | normal but significant events (connect/disconnect, summary lines) |
|
|
46
|
+
| WARN | 4 | recoverable problems |
|
|
47
|
+
| ERROR | 3 | non-fatal errors |
|
|
48
|
+
| CRIT | 2 | critical errors |
|
|
49
|
+
| ALERT | 1 | needs immediate attention |
|
|
50
|
+
| EMERG | 0 | unusable |
|
|
33
51
|
|
|
34
|
-
|
|
52
|
+
A message is emitted when its level ≤ the configured level.
|
|
35
53
|
|
|
36
|
-
|
|
54
|
+
## Logging API
|
|
37
55
|
|
|
38
|
-
|
|
56
|
+
Every log call ultimately produces:
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
[level] [uuid] [origin] message
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
`origin` is `core` or the plugin name; `uuid` is the connection UUID (with `.N` appended for the Nth transaction).
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
The simplest call is on the connection or plugin object — origin and uuid are filled in automatically:
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
```js
|
|
65
|
+
connection.logdebug('turtles all the way down')
|
|
66
|
+
plugin.loginfo('checking sender', connection)
|
|
67
|
+
```
|
|
47
68
|
|
|
48
|
-
|
|
69
|
+
Each of the level names has a matching method:
|
|
70
|
+
`logdata`, `logprotocol`, `logdebug`, `loginfo`, `lognotice`, `logwarn`,
|
|
71
|
+
`logerror`, `logcrit`, `logalert`, `logemerg`.
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
73
|
+
Calling the logger directly works too — pass the plugin and/or connection anywhere in the arguments and the logger sniffs them:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
logger.logdebug('i like turtles', plugin, connection)
|
|
77
|
+
// → [DEBUG] [7F1C820F-…] [dnsbl] i like turtles
|
|
54
78
|
```
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
Plain objects mixed into the arguments are merged into the log record (in `logfmt` / `json` formats) or stringified (`key=value` pairs in the default format).
|
|
81
|
+
|
|
82
|
+
## Log Formats
|
|
83
|
+
|
|
84
|
+
Set `main.format` in `log.ini` to one of `default`, `logfmt`, or `json`.
|
|
85
|
+
|
|
86
|
+
`logfmt`:
|
|
57
87
|
|
|
58
|
-
level=PROTOCOL uuid=9FF7F70E
|
|
88
|
+
level=PROTOCOL uuid=9FF7F70E-…1 source=core message="S: 354 go ahead, make my day"
|
|
59
89
|
|
|
60
|
-
|
|
90
|
+
`json`:
|
|
61
91
|
|
|
62
92
|
```json
|
|
63
93
|
{
|
|
64
94
|
"level": "PROTOCOL",
|
|
65
|
-
"uuid": "9FF7F70E
|
|
95
|
+
"uuid": "9FF7F70E-…1",
|
|
66
96
|
"source": "core",
|
|
67
97
|
"message": "S: 354 go ahead, make my day"
|
|
68
98
|
}
|
|
69
99
|
```
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
level=NOTICE uuid=9FF7F70E-5D57-435A-AAD9-EA069B6159D9.1 source=core message=disconnect ip=127.0.0.1 rdns=Unknown helo=3h2dnz8a0if relay=N early=N esmtp=N tls=N pipe=N errors=0 txns=1 rcpts=1/0/0 msgs=1/0/0 bytes=222 lr="" time=0.052
|
|
74
|
-
|
|
75
|
-
And using JSON:
|
|
101
|
+
A typical structured disconnect line looks like:
|
|
76
102
|
|
|
77
103
|
```json
|
|
78
104
|
{
|
|
79
105
|
"level": "NOTICE",
|
|
80
|
-
"uuid": "9FF7F70E
|
|
106
|
+
"uuid": "9FF7F70E-…1",
|
|
81
107
|
"source": "core",
|
|
82
108
|
"message": "disconnect",
|
|
83
109
|
"ip": "127.0.0.1",
|
|
@@ -97,3 +123,7 @@ And using JSON:
|
|
|
97
123
|
"time": 0.052
|
|
98
124
|
}
|
|
99
125
|
```
|
|
126
|
+
|
|
127
|
+
## The `log` hook
|
|
128
|
+
|
|
129
|
+
Each log message becomes a `log` hook invocation. The built-in handler writes to stdout (with ANSI colour when stdout is a TTY); log plugins can return `OK` or `STOP` to suppress the built-in output and ship the message elsewhere. Messages emitted before plugins finish loading are buffered and replayed once the plugin chain is ready.
|