master
md 909 lines 37.8 KB
Rendered Raw
1 # log2journal
2
3 `log2journal` and `systemd-cat-native` can be used to convert a structured log file, such as the ones generated by web servers, into `systemd-journal` entries.
4
5 By combining these tools you can create advanced log processing pipelines sending any kind of structured text logs to systemd-journald. This is a simple, but powerful and efficient way to handle log processing.
6
7 The process involves the usual piping of shell commands, to get and process the log files in realtime.
8
9 The result is like this: nginx logs into systemd-journal:
10
11 ![image](https://github.com/netdata/netdata/assets/2662304/16b471ff-c5a1-4fcc-bcd5-83551e089f6c)
12
13 The overall process looks like this:
14
15 ```bash
16 tail -F /var/log/nginx/*.log |\ # outputs log lines
17 log2journal 'PATTERN' |\ # outputs Journal Export Format
18 systemd-cat-native # send to local/remote journald
19 ```
20
21 These are the steps:
22
23 1. `tail -F /var/log/nginx/*.log`<br/>this command will tail all `*.log` files in `/var/log/nginx/`. We use `-F` instead of `-f` to ensure that files will still be tailed after log rotation.
24 2. `log2journal` is a Netdata program. It reads log entries and extracts fields, according to the PCRE2 pattern it accepts. It can also apply some basic operations on the fields, like injecting new fields or duplicating existing ones or rewriting their values. The output of `log2journal` is in Systemd Journal Export Format, and it looks like this:
25
26 ```bash
27 KEY1=VALUE1 # << start of the first log line
28 KEY2=VALUE2
29 # << log lines separator
30 KEY1=VALUE1 # << start of the second log line
31 KEY2=VALUE2
32 ```
33
34 3. `systemd-cat-native` is a Netdata program. I can send the logs to a local `systemd-journald` (journal namespaces supported), or to a remote `systemd-journal-remote`.
35
36 ## Processing pipeline
37
38 The sequence of processing in Netdata's `log2journal` is designed to methodically transform and prepare log data for export in the systemd Journal Export Format. This transformation occurs through a pipeline of stages, each with a specific role in processing the log entries. Here's a description of each stage in the sequence:
39
40 1. **Input**<br/>
41 The tool reads one log line at a time from the input source. It supports different input formats such as JSON, logfmt, and free-form logs defined by PCRE2 patterns.
42
43 2. **Extract Fields and Values**<br/>
44 Based on the input format (JSON, logfmt, or custom pattern), it extracts fields and their values from each log line. In the case of JSON and logfmt, it automatically extracts all fields. For custom patterns, it uses PCRE2 regular expressions, and fields are extracted based on sub-expressions defined in the pattern.
45
46 3. **Transliteration**<br/>
47 Extracted fields are transliterated to the limited character set accepted by systemd-journal: capitals A-Z, digits 0-9, underscores.
48
49 4. **Apply Optional Prefix**<br/>
50 If a prefix is specified, it is added to all keys. This happens before any other processing so that all subsequent matches and manipulations take the prefix into account.
51
52 5. **Rename Fields**<br/>
53 Renames fields as specified in the configuration. This is used to change the names of the fields to match desired or required naming conventions.
54
55 6. **Inject New Fields**<br/>
56 New fields are injected into the log data. This can include constants or values derived from other fields, using variable substitution.
57
58 7. **Rewrite Field Values**<br/>
59 Applies rewriting rules to alter the values of the fields. This can involve complex transformations, including regular expressions and variable substitutions. The rewrite rules can also inject new fields into the data.
60
61 8. **Filter Fields**<br/>
62 Fields are filtered based on include and exclude patterns. This stage selects which fields are to be sent to the journal, allowing for selective logging.
63
64 9. **Output**<br/>
65 Finally, the processed log data is output in the Journal Export Format. This format is compatible with systemd's journaling system and can be sent to local or remote systemd journal systems, by piping the output of `log2journal` to `systemd-cat-native`.
66
67 This pipeline ensures a flexible and comprehensive approach to log processing, allowing for a wide range of modifications and customizations to fit various logging requirements. Each stage builds upon the previous one, enabling complex log transformations and enrichments before the data is exported to the systemd journal.
68
69 ## Real-life example
70
71 We have an nginx server logging in this standard combined log format:
72
73 ```bash
74 log_format combined '$remote_addr - $remote_user [$time_local] '
75 '"$request" $status $body_bytes_sent '
76 '"$http_referer" "$http_user_agent"';
77 ```
78
79 ### Extracting fields with a pattern
80
81 First, let's find the right pattern for `log2journal`. We ask ChatGPT:
82
83 ```text
84 My nginx log uses this log format:
85
86 log_format access '$remote_addr - $remote_user [$time_local] '
87 '"$request" $status $body_bytes_sent '
88 '"$http_referer" "$http_user_agent"';
89
90 I want to use `log2joural` to convert this log for systemd-journal.
91 `log2journal` accepts a PCRE2 regular expression, using the named groups
92 in the pattern as the journal fields to extract from the logs.
93
94 Please give me the PCRE2 pattern to extract all the fields from my nginx
95 log files.
96 ```
97
98 ChatGPT replies with this:
99
100 ```regexp
101 (?x) # Enable PCRE2 extended mode
102 ^
103 (?<remote_addr>[^ ]+) \s - \s
104 (?<remote_user>[^ ]+) \s
105 \[
106 (?<time_local>[^\]]+)
107 \]
108 \s+ "
109 (?<request>
110 (?<request_method>[A-Z]+) \s+
111 (?<request_uri>[^ ]+) \s+
112 (?<server_protocol>[^"]+)
113 )
114 " \s+
115 (?<status>\d+) \s+
116 (?<body_bytes_sent>\d+) \s+
117 "(?<http_referer>[^"]*)" \s+
118 "(?<http_user_agent>[^"]*)"
119 ```
120
121 Let's see what the above says:
122
123 1. `(?x)`: enable PCRE2 extended mode. In this mode spaces and newlines in the pattern are ignored. To match a space you have to use `\s`. This mode allows us to split the pattern is multiple lines and add comments to it.
124 2. `^`: match the beginning of the line
125 3. `(?<remote_addr[^ ]+)`: match anything up to the first space (`[^ ]+`), and name it `remote_addr`.
126 4. `\s`: match a space
127 5. `-`: match a hyphen
128 6. and so on...
129
130 We edit `nginx.yaml` and add it, like this:
131
132 ```yaml
133 pattern: |
134 (?x) # Enable PCRE2 extended mode
135 ^
136 (?<remote_addr>[^ ]+) \s - \s
137 (?<remote_user>[^ ]+) \s
138 \[
139 (?<time_local>[^\]]+)
140 \]
141 \s+ "
142 (?<request>
143 (?<request_method>[A-Z]+) \s+
144 (?<request_uri>[^ ]+) \s+
145 (?<server_protocol>[^"]+)
146 )
147 " \s+
148 (?<status>\d+) \s+
149 (?<body_bytes_sent>\d+) \s+
150 "(?<http_referer>[^"]*)" \s+
151 "(?<http_user_agent>[^"]*)"
152 ```
153
154 Let's test it with a sample line (instead of `tail`):
155
156 ```bash
157 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 104 0.001 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml
158 BODY_BYTES_SENT=4172
159 HTTP_REFERER=-
160 HTTP_USER_AGENT=Go-http-client/1.1
161 REMOTE_ADDR=1.2.3.4
162 REMOTE_USER=-
163 REQUEST=GET /index.html HTTP/1.1
164 REQUEST_METHOD=GET
165 REQUEST_URI=/index.html
166 SERVER_PROTOCOL=HTTP/1.1
167 STATUS=200
168 TIME_LOCAL=19/Nov/2023:00:24:43 +0000
169
170 ```
171
172 As you can see, it extracted all the fields and made them capitals, as systemd-journal expects them.
173
174 ### Prefixing field names
175
176 To make sure the fields are unique for nginx and do not interfere with other applications, we should prefix them with `NGINX_`:
177
178 ```yaml
179 pattern: |
180 (?x) # Enable PCRE2 extended mode
181 ^
182 (?<remote_addr>[^ ]+) \s - \s
183 (?<remote_user>[^ ]+) \s
184 \[
185 (?<time_local>[^\]]+)
186 \]
187 \s+ "
188 (?<request>
189 (?<request_method>[A-Z]+) \s+
190 (?<request_uri>[^ ]+) \s+
191 (?<server_protocol>[^"]+)
192 )
193 " \s+
194 (?<status>\d+) \s+
195 (?<body_bytes_sent>\d+) \s+
196 "(?<http_referer>[^"]*)" \s+
197 "(?<http_user_agent>[^"]*)"
198
199 prefix: 'NGINX_' # <<< we added this
200 ```
201
202 And let's try it:
203
204 ```bash
205 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml
206 NGINX_BODY_BYTES_SENT=4172
207 NGINX_HTTP_REFERER=-
208 NGINX_HTTP_USER_AGENT=Go-http-client/1.1
209 NGINX_REMOTE_ADDR=1.2.3.4
210 NGINX_REMOTE_USER=-
211 NGINX_REQUEST=GET /index.html HTTP/1.1
212 NGINX_REQUEST_METHOD=GET
213 NGINX_REQUEST_URI=/index.html
214 NGINX_SERVER_PROTOCOL=HTTP/1.1
215 NGINX_STATUS=200
216 NGINX_TIME_LOCAL=19/Nov/2023:00:24:43 +0000
217
218 ```
219
220 ### Renaming fields
221
222 Now, all fields start with `NGINX_` but we want `NGINX_REQUEST` to be the `MESSAGE` of the log line, as we will see it by default in `journalctl` and the Netdata dashboard. Let's rename it:
223
224 ```yaml
225 pattern: |
226 (?x) # Enable PCRE2 extended mode
227 ^
228 (?<remote_addr>[^ ]+) \s - \s
229 (?<remote_user>[^ ]+) \s
230 \[
231 (?<time_local>[^\]]+)
232 \]
233 \s+ "
234 (?<request>
235 (?<request_method>[A-Z]+) \s+
236 (?<request_uri>[^ ]+) \s+
237 (?<server_protocol>[^"]+)
238 )
239 " \s+
240 (?<status>\d+) \s+
241 (?<body_bytes_sent>\d+) \s+
242 "(?<http_referer>[^"]*)" \s+
243 "(?<http_user_agent>[^"]*)"
244
245 prefix: 'NGINX_'
246
247 rename: # <<< we added this
248 - new_key: MESSAGE # <<< we added this
249 old_key: NGINX_REQUEST # <<< we added this
250 ```
251
252 Let's test it:
253
254 ```bash
255 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml
256 MESSAGE=GET /index.html HTTP/1.1 # <<< renamed !
257 NGINX_BODY_BYTES_SENT=4172
258 NGINX_HTTP_REFERER=-
259 NGINX_HTTP_USER_AGENT=Go-http-client/1.1
260 NGINX_REMOTE_ADDR=1.2.3.4
261 NGINX_REMOTE_USER=-
262 NGINX_REQUEST_METHOD=GET
263 NGINX_REQUEST_URI=/index.html
264 NGINX_SERVER_PROTOCOL=HTTP/1.1
265 NGINX_STATUS=200
266 NGINX_TIME_LOCAL=19/Nov/2023:00:24:43 +0000
267
268 ```
269
270 ### Injecting new fields
271
272 To have a complete message in journals we need 3 fields: `MESSAGE`, `PRIORITY` and `SYSLOG_IDENTIFIER`. We have already added `MESSAGE` by renaming `NGINX_REQUEST`. We can also inject a `SYSLOG_IDENTIFIER` and `PRIORITY`.
273
274 Ideally, we would want the 5xx errors to be red in our `journalctl` output and the dashboard. To achieve that we need to set the `PRIORITY` field to the right log level. Log priorities are numeric and follow the `syslog` priorities. Checking `/usr/include/sys/syslog.h` we can see these:
275
276 ```c
277 #define LOG_EMERG 0 /* system is unusable */
278 #define LOG_ALERT 1 /* action must be taken immediately */
279 #define LOG_CRIT 2 /* critical conditions */
280 #define LOG_ERR 3 /* error conditions */
281 #define LOG_WARNING 4 /* warning conditions */
282 #define LOG_NOTICE 5 /* normal but significant condition */
283 #define LOG_INFO 6 /* informational */
284 #define LOG_DEBUG 7 /* debug-level messages */
285 ```
286
287 Avoid setting priority to 0 (`LOG_EMERG`), because these will be on your terminal (the journal uses `wall` to let you know of such events). A good priority for errors is 3 (red), or 4 (yellow).
288
289 To set the PRIORITY field in the output, we can use `NGINX_STATUS`. We will do this in 2 steps: a) inject the priority field as a copy is `NGINX_STATUS` and then b) use a pattern on its value to rewrite it to the priority level we want.
290
291 First, let's inject `SYSLOG_IDENTIFIER` and `PRIORITY`:
292
293 ```yaml
294 pattern: |
295 (?x) # Enable PCRE2 extended mode
296 ^
297 (?<remote_addr>[^ ]+) \s - \s
298 (?<remote_user>[^ ]+) \s
299 \[
300 (?<time_local>[^\]]+)
301 \]
302 \s+ "
303 (?<request>
304 (?<request_method>[A-Z]+) \s+
305 (?<request_uri>[^ ]+) \s+
306 (?<server_protocol>[^"]+)
307 )
308 " \s+
309 (?<status>\d+) \s+
310 (?<body_bytes_sent>\d+) \s+
311 "(?<http_referer>[^"]*)" \s+
312 "(?<http_user_agent>[^"]*)"
313
314 prefix: 'NGINX_'
315
316 rename:
317 - new_key: MESSAGE
318 old_key: NGINX_REQUEST
319
320 inject: # <<< we added this
321 - key: PRIORITY # <<< we added this
322 value: '${NGINX_STATUS}' # <<< we added this
323
324 - key: SYSLOG_IDENTIFIER # <<< we added this
325 value: 'nginx-log' # <<< we added this
326 ```
327
328 Let's see what this does:
329
330 ```bash
331 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml
332 MESSAGE=GET /index.html HTTP/1.1
333 NGINX_BODY_BYTES_SENT=4172
334 NGINX_HTTP_REFERER=-
335 NGINX_HTTP_USER_AGENT=Go-http-client/1.1
336 NGINX_REMOTE_ADDR=1.2.3.4
337 NGINX_REMOTE_USER=-
338 NGINX_REQUEST_METHOD=GET
339 NGINX_REQUEST_URI=/index.html
340 NGINX_SERVER_PROTOCOL=HTTP/1.1
341 NGINX_STATUS=200
342 NGINX_TIME_LOCAL=19/Nov/2023:00:24:43 +0000
343 PRIORITY=200 # <<< PRIORITY added
344 SYSLOG_IDENTIFIER=nginx-log # <<< SYSLOG_IDENTIFIER added
345
346 ```
347
348 ### Rewriting field values
349
350 Now we need to rewrite `PRIORITY` to the right syslog level based on its value (`NGINX_STATUS`). We will assign the priority 6 (info) when the status is 1xx, 2xx, 3xx, priority 5 (notice) when status is 4xx, priority 3 (error) when status is 5xx and anything else will go to priority 4 (warning). Let's do it:
351
352 ```yaml
353 pattern: |
354 (?x) # Enable PCRE2 extended mode
355 ^
356 (?<remote_addr>[^ ]+) \s - \s
357 (?<remote_user>[^ ]+) \s
358 \[
359 (?<time_local>[^\]]+)
360 \]
361 \s+ "
362 (?<request>
363 (?<request_method>[A-Z]+) \s+
364 (?<request_uri>[^ ]+) \s+
365 (?<server_protocol>[^"]+)
366 )
367 " \s+
368 (?<status>\d+) \s+
369 (?<body_bytes_sent>\d+) \s+
370 "(?<http_referer>[^"]*)" \s+
371 "(?<http_user_agent>[^"]*)"
372
373 prefix: 'NGINX_'
374
375 rename:
376 - new_key: MESSAGE
377 old_key: NGINX_REQUEST
378
379 inject:
380 - key: PRIORITY
381 value: '${NGINX_STATUS}'
382
383 rewrite: # <<< we added this
384 - key: PRIORITY # <<< we added this
385 match: '^[123]' # <<< we added this
386 value: 6 # <<< we added this
387
388 - key: PRIORITY # <<< we added this
389 match: '^4' # <<< we added this
390 value: 5 # <<< we added this
391
392 - key: PRIORITY # <<< we added this
393 match: '^5' # <<< we added this
394 value: 3 # <<< we added this
395
396 - key: PRIORITY # <<< we added this
397 match: '.*' # <<< we added this
398 value: 4 # <<< we added this
399 ```
400
401 Rewrite rules are processed in order and the first matching a field, stops by default processing for this field. This is why the last rule, that matches everything does not always change the priority to 4.
402
403 Let's test it:
404
405 ```bash
406 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml
407 MESSAGE=GET /index.html HTTP/1.1
408 NGINX_BODY_BYTES_SENT=4172
409 NGINX_HTTP_REFERER=-
410 NGINX_HTTP_USER_AGENT=Go-http-client/1.1
411 NGINX_REMOTE_ADDR=1.2.3.4
412 NGINX_REMOTE_USER=-
413 NGINX_REQUEST_METHOD=GET
414 NGINX_REQUEST_URI=/index.html
415 NGINX_SERVER_PROTOCOL=HTTP/1.1
416 NGINX_STATUS=200
417 NGINX_TIME_LOCAL=19/Nov/2023:00:24:43 +0000
418 PRIORITY=6 # <<< PRIORITY rewritten here
419 SYSLOG_IDENTIFIER=nginx-log
420
421 ```
422
423 Rewrite rules are powerful. You can have named groups in them, like in the main pattern, to extract sub-fields from them, which you can then use in variable substitution. You can use rewrite rules to anonymize the URLs, e.g to remove customer IDs or transaction details from them.
424
425 ### Sending logs to systemd-journal
426
427 Now the message is ready to be sent to a systemd-journal. For this we use `systemd-cat-native`. This command can send such messages to a journal running on the localhost, a local journal namespace, or a `systemd-journal-remote` running on another server. By just appending `| systemd-cat-native` to the command, the message will be sent to the local journal.
428
429 ```bash
430 # echo '1.2.3.4 - - [19/Nov/2023:00:24:43 +0000] "GET /index.html HTTP/1.1" 200 4172 "-" "Go-http-client/1.1"' | log2journal -f nginx.yaml | systemd-cat-native
431 # no output
432
433 # let's find the message
434 # journalctl -r -o verbose SYSLOG_IDENTIFIER=nginx-log
435 Wed 2023-12-06 13:23:07.083299 EET [s=5290f0133f25407aaa1e2c451c0e4756;i=57194;b=0dfa96ecc2094cecaa8ec0efcb93b865;m=b133308867;t=60bd59346a289;x=5c1bdacf2b9c4bbd]
436 PRIORITY=6
437 _UID=0
438 _GID=0
439 _CAP_EFFECTIVE=1ffffffffff
440 _SELINUX_CONTEXT=unconfined
441 _BOOT_ID=0dfa96ecc2094cecaa8ec0efcb93b865
442 _MACHINE_ID=355c8eca894d462bbe4c9422caf7a8bb
443 _HOSTNAME=lab-logtest-src
444 _RUNTIME_SCOPE=system
445 _TRANSPORT=journal
446 MESSAGE=GET /index.html HTTP/1.1
447 NGINX_BODY_BYTES_SENT=4172
448 NGINX_HTTP_REFERER=-
449 NGINX_HTTP_USER_AGENT=Go-http-client/1.1
450 NGINX_REMOTE_ADDR=1.2.3.4
451 NGINX_REMOTE_USER=-
452 NGINX_REQUEST_METHOD=GET
453 NGINX_REQUEST_URI=/index.html
454 NGINX_SERVER_PROTOCOL=HTTP/1.1
455 NGINX_STATUS=200
456 NGINX_TIME_LOCAL=19/Nov/2023:00:24:43 +0000
457 SYSLOG_IDENTIFIER=nginx-log
458 _PID=114343
459 _COMM=systemd-cat-nat
460 _AUDIT_SESSION=253
461 _AUDIT_LOGINUID=1000
462 _SYSTEMD_CGROUP=/user.slice/user-1000.slice/session-253.scope
463 _SYSTEMD_SESSION=253
464 _SYSTEMD_OWNER_UID=1000
465 _SYSTEMD_UNIT=session-253.scope
466 _SYSTEMD_SLICE=user-1000.slice
467 _SYSTEMD_USER_SLICE=-.slice
468 _SYSTEMD_INVOCATION_ID=c59e33ead8c24880b027e317b89f9f76
469 _SOURCE_REALTIME_TIMESTAMP=1701861787083299
470
471 ```
472
473 So, the log line, with all its fields parsed, ended up in systemd-journal. Now we can send all the nginx logs to systemd-journal like this:
474
475 ```bash
476 tail -F /var/log/nginx/access.log |\
477 log2journal -f nginx.yaml |\
478 systemd-cat-native
479 ```
480
481 ## Best practices
482
483 **Create a systemd service unit**: Add the above commands to a systemd unit file. When you run it in a systemd unit file you will be able to start/stop it and also see its status. Furthermore you can use the `LogNamespace=` directive of systemd service units to isolate your nginx logs from the logs of the rest of the system. Here is how to do it:
484
485 Create the file `/etc/systemd/system/nginx-logs.service` (change `/path/to/nginx.yaml` to the right path):
486
487 ```text
488 [Unit]
489 Description=NGINX Log to Systemd Journal
490 After=network.target
491
492 [Service]
493 ExecStart=/bin/sh -c 'tail -F /var/log/nginx/access.log | log2journal -f /path/to/nginx.yaml' | systemd-cat-native
494 LogNamespace=nginx-logs
495 Restart=always
496 RestartSec=3
497
498 [Install]
499 WantedBy=multi-user.target
500 ```
501
502 Reload systemd to grab this file:
503
504 ```bash
505 sudo systemctl daemon-reload
506 ```
507
508 Enable and start the service:
509
510 ```bash
511 sudo systemctl enable nginx-logs.service
512 sudo systemctl start nginx-logs.service
513 ```
514
515 To see the logs of the namespace, use:
516
517 ```bash
518 journalctl -f --namespace=nginx-logs
519 ```
520
521 Netdata will automatically pick the new namespace and present it at the list of sources of the dashboard.
522
523 You can also instruct `systemd-cat-native` to log to a remote system, sending the logs to a `systemd-journal-remote` instance running on another server. Check [the manual of systemd-cat-native](/src/libnetdata/log/systemd-cat-native.md).
524
525 ## Performance
526
527 `log2journal` and `systemd-cat-native` have been designed to process hundreds of thousands of log lines per second. They both utilize high performance indexing hashtables to speed up lookups, and queues that dynamically adapt to the number of log lines offered, offering a smooth and fast experience under all conditions.
528
529 In our tests, the combined CPU utilization of `log2journal` and `systemd-cat-native` versus `promtail` with similar configuration is 1 to 5. So, `log2journal` and `systemd-cat-native` combined, are 5 times faster than `promtail`.
530
531 ### PCRE2 patterns
532
533 The key characteristic that can influence the performance of a logs processing pipeline using these tools, is the quality of the PCRE2 patterns used. Poorly created PCRE2 patterns can make processing significantly slower, or CPU consuming.
534
535 Especially the pattern `.*` seems to have the biggest impact on CPU consumption, especially when multiple `.*` are on the same pattern.
536
537 Usually we use `.*` to indicate that we need to match everything up to a character, e.g. `.*` to match up to a space. By replacing it with `[^ ]+` (meaning: match at least a character up to a space), the regular expression engine can be a lot more efficient, reducing the overall CPU utilization significantly.
538
539 ### Performance of systemd journals
540
541 The ingestion pipeline of logs, from `tail` to `systemd-journald` or `systemd-journal-remote` is very efficient in all aspects. CPU utilization is better than any other system we tested and RAM usage is independent of the number of fields indexed, making systemd-journal one of the most efficient log management engines for ingesting high volumes of structured logs.
542
543 High fields cardinality does not have a noticeable impact on systemd-journal. The amount of fields indexed and the amount of unique values per field, have a linear and predictable result in the resource utilization of `systemd-journald` and `systemd-journal-remote`. This is unlike other logs management solutions, like Loki, that their RAM requirements grow exponentially as the cardinality increases, making it impractical for them to index the amount of information systemd journals can index.
544
545 However, the number of fields added to journals influences the overall disk footprint. Less fields means more log entries per journal file, smaller overall disk footprint and faster queries.
546
547 systemd-journal files are primarily designed for security and reliability. This comes at the cost of disk footprint. The internal structure of journal files is such that in case of corruption, minimum data loss will incur. To achieve such a unique characteristic, certain data within the files need to be aligned at predefined boundaries, so that in case there is a corruption, non-corrupted parts of the journal file can be recovered.
548
549 Despite the fact that systemd-journald employees several techniques to optimize disk footprint, like deduplication of log entries, shared indexes for fields and their values, compression of long log entries, etc. the disk footprint of journal files is generally 10x more compared to other monitoring solutions, like Loki.
550
551 This can be improved by storing journal files in a compressed filesystem. In our tests, a compressed filesystem can save up to 75% of the space required by journal files. The journal files will still be bigger than the overall disk footprint of other solutions, but the flexibility (index any number of fields), reliability (minimal potential data loss) and security (tampering protection and sealing) features of systemd-journal justify the difference.
552
553 When using versions of systemd prior to 254 and you are centralizing logs to a remote system, `systemd-journal-remote` creates very small files (32MB). This results in increased duplication of information across the files, increasing the overall disk footprint. systemd versions 254+, added options to `systemd-journal-remote` to control the max size per file. This can significantly reduce the duplication of information.
554
555 Another limitation of the `systemd-journald` ecosystem is the uncompressed transmission of logs across systems. `systemd-journal-remote` up to version 254 that we tested, accepts encrypted, but uncompressed data. This means that when centralizing logs to a logs server, the bandwidth required will be increased compared to other log management solution.
556
557 ## Security Considerations
558
559 `log2journal` and `systemd-cat-native` are used to convert log files to structured logs in the systemd-journald ecosystem.
560
561 Systemd-journal is a logs management solution designed primarily for security and reliability. When configured properly, it can reliably and securely store your logs, ensuring they will available and unchanged for as long as you need them.
562
563 When sending logs to a remote system, `systemd-cat-native` can be configured the same way `systemd-journal-upload` is configured, using HTTPS and private keys to encrypt and secure their transmission over the network.
564
565 When dealing with sensitive logs, organizations usually follow 2 strategies:
566
567 1. Anonymize the logs before storing them, so that the stored logs do not have any sensitive information.
568 2. Store the logs in full, including sensitive information, and carefully control who and how has access to them.
569
570 Netdata can help in both cases.
571
572 If you want to anonymize the logs before storing them, use rewriting rules at the `log2journal` phase to remove sensitive information from them. This process usually means matching the sensitive part and replacing with `XXX` or `CUSTOMER_ID`, or `CREDIT_CARD_NUMBER`, so that the resulting log entries stored in journal files will not include any such sensitive information.
573
574 If on other hand your organization prefers to maintain the full logs and control who and how has access on them, use Netdata Cloud to assign roles to your team members and control which roles can access the journal logs in your environment.
575
576 ## `log2journal` options
577
578 ````text
579
580 Netdata log2journal v1.43.0-341-gdac4df856
581
582 Convert logs to systemd Journal Export Format.
583
584 - JSON logs: extracts all JSON fields.
585 - logfmt logs: extracts all logfmt fields.
586 - free-form logs: uses PCRE2 patterns to extracts fields.
587
588 Usage: ./log2journal [OPTIONS] PATTERN|json
589
590 Options:
591
592 --file /path/to/file.yaml or -f /path/to/file.yaml
593 Read yaml configuration file for instructions.
594
595 --config CONFIG_NAME or -c CONFIG_NAME
596 Run with the internal YAML configuration named CONFIG_NAME.
597 Available internal YAML configs:
598
599 nginx-combined nginx-json default
600
601 --------------------------------------------------------------------------------
602 INPUT PROCESSING
603
604 PATTERN
605 PATTERN should be a valid PCRE2 regular expression.
606 RE2 regular expressions (like the ones usually used in Go applications),
607 are usually valid PCRE2 patterns too.
608 Sub-expressions without named groups are evaluated, but their matches are
609 not added to the output.
610
611 - JSON mode
612 JSON mode is enabled when the pattern is set to: json
613 Field names are extracted from the JSON logs and are converted to the
614 format expected by Journal Export Format (all caps, only _ is allowed).
615
616 - logfmt mode
617 logfmt mode is enabled when the pattern is set to: logfmt
618 Field names are extracted from the logfmt logs and are converted to the
619 format expected by Journal Export Format (all caps, only _ is allowed).
620
621 All keys extracted from the input, are transliterated to match Journal
622 semantics (capital A-Z, digits 0-9, underscore).
623
624 In a YAML file:
625 ```yaml
626 pattern: 'PCRE2 pattern | json | logfmt'
627 ```
628
629 --------------------------------------------------------------------------------
630 GLOBALS
631
632 --prefix PREFIX
633 Prefix all fields with PREFIX. The PREFIX is added before any other
634 processing, so that the extracted keys have to be matched with the PREFIX in
635 them. PREFIX is NOT transliterated and it is assumed to be systemd-journal
636 friendly.
637
638 In a YAML file:
639 ```yaml
640 prefix: 'PREFIX_' # prepend all keys with this prefix.
641 ```
642
643 --filename-key KEY
644 Add a field with KEY as the key and the current filename as value.
645 Automatically detects filenames when piped after 'tail -F',
646 and tail matches multiple filenames.
647 To inject the filename when tailing a single file, use --inject.
648
649 In a YAML file:
650 ```yaml
651 filename:
652 key: KEY
653 ```
654
655 --------------------------------------------------------------------------------
656 RENAMING OF KEYS
657
658 --rename NEW=OLD
659 Rename fields. OLD has been transliterated and PREFIX has been added.
660 NEW is assumed to be systemd journal friendly.
661
662 Up to 512 renaming rules are allowed.
663
664 In a YAML file:
665 ```yaml
666 rename:
667 - new_key: KEY1
668 old_key: KEY2 # transliterated with PREFIX added
669 - new_key: KEY3
670 old_key: KEY4 # transliterated with PREFIX added
671 # add as many as required
672 ```
673
674 --------------------------------------------------------------------------------
675 INJECTING NEW KEYS
676
677 --inject KEY=VALUE
678 Inject constant fields to the output (both matched and unmatched logs).
679 --inject entries are added to unmatched lines too, when their key is
680 not used in --inject-unmatched (--inject-unmatched override --inject).
681 VALUE can use variable like ${OTHER_KEY} to be replaced with the values
682 of other keys available.
683
684 Up to 512 fields can be injected.
685
686 In a YAML file:
687 ```yaml
688 inject:
689 - key: KEY1
690 value: 'VALUE1'
691 - key: KEY2
692 value: '${KEY3}${KEY4}' # gets the values of KEY3 and KEY4
693 # add as many as required
694 ```
695
696 --------------------------------------------------------------------------------
697 REWRITING KEY VALUES
698
699 --rewrite KEY=/MATCH/REPLACE[/OPTIONS]
700 Apply a rewrite rule to the values of a specific key.
701 The first character after KEY= is the separator, which should also
702 be used between the MATCH, REPLACE and OPTIONS.
703
704 OPTIONS can be a comma separated list of `non-empty`, `dont-stop` and
705 `inject`.
706
707 When `non-empty` is given, MATCH is expected to be a variable
708 substitution using `${KEY1}${KEY2}`. Once the substitution is completed
709 the rule is matching the KEY only if the result is not empty.
710 When `non-empty` is not set, the MATCH string is expected to be a PCRE2
711 regular expression to be checked against the KEY value. This PCRE2
712 pattern may include named groups to extract parts of the KEY's value.
713
714 REPLACE supports variable substitution like `${variable}` against MATCH
715 named groups (when MATCH is a PCRE2 pattern) and `${KEY}` against the
716 keys defined so far.
717
718 Example:
719 --rewrite DATE=/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/
720 ${day}/${month}/${year}
721 The above will rewrite dates in the format YYYY-MM-DD to DD/MM/YYYY.
722
723 Only one rewrite rule is applied per key; the sequence of rewrites for a
724 given key, stops once a rule matches it. This allows providing a sequence
725 of independent rewriting rules for the same key, matching the different
726 values the key may get, and also provide a catch-all rewrite rule at the
727 end, for setting the key value if no other rule matched it. The rewrite
728 rule can allow processing more rewrite rules when OPTIONS includes
729 the keyword 'dont-stop'.
730
731 Up to 512 rewriting rules are allowed.
732
733 In a YAML file:
734 ```yaml
735 rewrite:
736 # the order if these rules in important - processed top to bottom
737 - key: KEY1
738 match: 'PCRE2 PATTERN WITH NAMED GROUPS'
739 value: 'all match fields and input keys as ${VARIABLE}'
740 inject: BOOLEAN # yes = inject the field, don't just rewrite it
741 stop: BOOLEAN # no = continue processing, don't stop if matched
742 - key: KEY2
743 non_empty: '${KEY3}${KEY4}' # match only if this evaluates to non empty
744 value: 'all input keys as ${VARIABLE}'
745 inject: BOOLEAN # yes = inject the field, don't just rewrite it
746 stop: BOOLEAN # no = continue processing, don't stop if matched
747 # add as many rewrites as required
748 ```
749
750 By default rewrite rules are applied only on fields already defined.
751 This allows shipping YAML files that include more rewrites than are
752 required for a specific input file.
753 Rewrite rules however allow injecting new fields when OPTIONS include
754 the keyword `inject` or in YAML `inject: yes` is given.
755
756 MATCH on the command line can be empty to define an unconditional rule.
757 Similarly, `match` and `non_empty` can be omitted in the YAML file.
758 --------------------------------------------------------------------------------
759 UNMATCHED LINES
760
761 --unmatched-key KEY
762 Include unmatched log entries in the output with KEY as the field name.
763 Use this to include unmatched entries to the output stream.
764 Usually it should be set to --unmatched-key=MESSAGE so that the
765 unmatched entry will appear as the log message in the journals.
766 Use --inject-unmatched to inject additional fields to unmatched lines.
767
768 In a YAML file:
769 ```yaml
770 unmatched:
771 key: MESSAGE # inject the error log as MESSAGE
772 ```
773
774 --inject-unmatched LINE
775 Inject lines into the output for each unmatched log entry.
776 Usually, --inject-unmatched=PRIORITY=3 is needed to mark the unmatched
777 lines as errors, so that they can easily be spotted in the journals.
778
779 Up to 512 such lines can be injected.
780
781 In a YAML file:
782 ```yaml
783 unmatched:
784 key: MESSAGE # inject the error log as MESSAGE
785 inject::
786 - key: KEY1
787 value: 'VALUE1'
788 # add as many constants as required
789 ```
790
791 --------------------------------------------------------------------------------
792 FILTERING
793
794 --include PATTERN
795 Include only keys matching the PCRE2 PATTERN.
796 Useful when parsing JSON of logfmt logs, to include only the keys given.
797 The keys are matched after the PREFIX has been added to them.
798
799 --exclude PATTERN
800 Exclude the keys matching the PCRE2 PATTERN.
801 Useful when parsing JSON of logfmt logs, to exclude some of the keys given.
802 The keys are matched after the PREFIX has been added to them.
803
804 When both include and exclude patterns are set and both match a key,
805 exclude wins and the key will not be added, like a pipeline, we first
806 include it and then exclude it.
807
808 In a YAML file:
809 ```yaml
810 filter:
811 include: 'PCRE2 PATTERN MATCHING KEY NAMES TO INCLUDE'
812 exclude: 'PCRE2 PATTERN MATCHING KEY NAMES TO EXCLUDE'
813 ```
814
815 --------------------------------------------------------------------------------
816 OTHER
817
818 -h, or --help
819 Display this help and exit.
820
821 --show-config
822 Show the configuration in YAML format before starting the job.
823 This is also an easy way to convert command line parameters to yaml.
824
825 The program accepts all parameters as both --option=value and --option value.
826
827 The maximum log line length accepted is 1048576 characters.
828
829 PIPELINE AND SEQUENCE OF PROCESSING
830
831 This is a simple diagram of the pipeline taking place:
832
833 +---------------------------------------------------+
834 | INPUT |
835 | read one log line at a time |
836 +---------------------------------------------------+
837 v v v v v v
838 +---------------------------------------------------+
839 | EXTRACT FIELDS AND VALUES |
840 | JSON, logfmt, or pattern based |
841 | (apply optional PREFIX - all keys use capitals) |
842 +---------------------------------------------------+
843 v v v v v v
844 +---------------------------------------------------+
845 | RENAME FIELDS |
846 | change the names of the fields |
847 +---------------------------------------------------+
848 v v v v v v
849 +---------------------------------------------------+
850 | INJECT NEW FIELDS |
851 | constants, or other field values as variables |
852 +---------------------------------------------------+
853 v v v v v v
854 +---------------------------------------------------+
855 | REWRITE FIELD VALUES |
856 | pipeline multiple rewriting rules to alter |
857 | the values of the fields |
858 +---------------------------------------------------+
859 v v v v v v
860 +---------------------------------------------------+
861 | FILTER FIELDS |
862 | use include and exclude patterns on the field |
863 | names, to select which fields are sent to journal |
864 +---------------------------------------------------+
865 v v v v v v
866 +---------------------------------------------------+
867 | OUTPUT |
868 | generate Journal Export Format |
869 +---------------------------------------------------+
870
871 --------------------------------------------------------------------------------
872 JOURNAL FIELDS RULES (enforced by systemd-journald)
873
874 - field names can be up to 64 characters
875 - the only allowed field characters are A-Z, 0-9 and underscore
876 - the first character of fields cannot be a digit
877 - protected journal fields start with underscore:
878 * they are accepted by systemd-journal-remote
879 * they are NOT accepted by a local systemd-journald
880
881 For best results, always include these fields:
882
883 MESSAGE=TEXT
884 The MESSAGE is the body of the log entry.
885 This field is what we usually see in our logs.
886
887 PRIORITY=NUMBER
888 PRIORITY sets the severity of the log entry.
889 0=emerg, 1=alert, 2=crit, 3=err, 4=warn, 5=notice, 6=info, 7=debug
890 - Emergency events (0) are usually broadcast to all terminals.
891 - Emergency, alert, critical, and error (0-3) are usually colored red.
892 - Warning (4) entries are usually colored yellow.
893 - Notice (5) entries are usually bold or have a brighter white color.
894 - Info (6) entries are the default.
895 - Debug (7) entries are usually grayed or dimmed.
896
897 SYSLOG_IDENTIFIER=NAME
898 SYSLOG_IDENTIFIER sets the name of application.
899 Use something descriptive, like: SYSLOG_IDENTIFIER=nginx-logs
900
901 You can find the most common fields at 'man systemd.journal-fields'.
902
903 ````
904
905 `log2journal` supports YAML configuration files, like the ones found [in this directory](https://github.com/netdata/netdata/tree/master/src/collectors/log2journal/log2journal.d).
906
907 ## `systemd-cat-native` options
908
909 Read [the manual of systemd-cat-native](/src/libnetdata/log/systemd-cat-native.md).