]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal/journalctl.c
journalctl: add --priority= switch for filtering by priority
[thirdparty/systemd.git] / src / journal / journalctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2011 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <stddef.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <unistd.h>
28 #include <stdlib.h>
29 #include <sys/poll.h>
30 #include <time.h>
31 #include <getopt.h>
32 #include <sys/stat.h>
33
34 #include <systemd/sd-journal.h>
35
36 #include "log.h"
37 #include "util.h"
38 #include "path-util.h"
39 #include "build.h"
40 #include "pager.h"
41 #include "logs-show.h"
42 #include "strv.h"
43 #include "journal-internal.h"
44
45 static OutputMode arg_output = OUTPUT_SHORT;
46 static bool arg_follow = false;
47 static bool arg_show_all = false;
48 static bool arg_no_pager = false;
49 static int arg_lines = -1;
50 static bool arg_no_tail = false;
51 static bool arg_new_id128 = false;
52 static bool arg_print_header = false;
53 static bool arg_quiet = false;
54 static bool arg_local = false;
55 static bool arg_this_boot = false;
56 static const char *arg_directory = NULL;
57 static int arg_priorities = 0xFF;
58
59 static int help(void) {
60
61 printf("%s [OPTIONS...] [MATCH]\n\n"
62 "Send control commands to or query the journal.\n\n"
63 " -h --help Show this help\n"
64 " --version Show package version\n"
65 " --no-pager Do not pipe output into a pager\n"
66 " -a --all Show all fields, including long and unprintable\n"
67 " -f --follow Follow journal\n"
68 " -n --lines=INTEGER Journal entries to show\n"
69 " --no-tail Show all lines, even in follow mode\n"
70 " -o --output=STRING Change journal output mode (short, short-monotonic,\n"
71 " verbose, export, json, cat)\n"
72 " -q --quiet Don't show privilege warning\n"
73 " -l --local Only local entries\n"
74 " -b --this-boot Show data only from current boot\n"
75 " -D --directory=PATH Show journal files from directory\n"
76 " -p --priority=RANGE Show only messages within the specified priority range\n"
77 " --header Show journal header information\n"
78 " --new-id128 Generate a new 128 Bit id\n",
79 program_invocation_short_name);
80
81 return 0;
82 }
83
84 static int parse_argv(int argc, char *argv[]) {
85
86 enum {
87 ARG_VERSION = 0x100,
88 ARG_NO_PAGER,
89 ARG_NO_TAIL,
90 ARG_NEW_ID128,
91 ARG_HEADER
92 };
93
94 static const struct option options[] = {
95 { "help", no_argument, NULL, 'h' },
96 { "version" , no_argument, NULL, ARG_VERSION },
97 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
98 { "follow", no_argument, NULL, 'f' },
99 { "output", required_argument, NULL, 'o' },
100 { "all", no_argument, NULL, 'a' },
101 { "lines", required_argument, NULL, 'n' },
102 { "no-tail", no_argument, NULL, ARG_NO_TAIL },
103 { "new-id128", no_argument, NULL, ARG_NEW_ID128 },
104 { "quiet", no_argument, NULL, 'q' },
105 { "local", no_argument, NULL, 'l' },
106 { "this-boot", no_argument, NULL, 'b' },
107 { "directory", required_argument, NULL, 'D' },
108 { "header", no_argument, NULL, ARG_HEADER },
109 { "priority", no_argument, NULL, 'p' },
110 { NULL, 0, NULL, 0 }
111 };
112
113 int c, r;
114
115 assert(argc >= 0);
116 assert(argv);
117
118 while ((c = getopt_long(argc, argv, "hfo:an:qlbD:p:", options, NULL)) >= 0) {
119
120 switch (c) {
121
122 case 'h':
123 help();
124 return 0;
125
126 case ARG_VERSION:
127 puts(PACKAGE_STRING);
128 puts(DISTRIBUTION);
129 puts(SYSTEMD_FEATURES);
130 return 0;
131
132 case ARG_NO_PAGER:
133 arg_no_pager = true;
134 break;
135
136 case 'f':
137 arg_follow = true;
138 break;
139
140 case 'o':
141 arg_output = output_mode_from_string(optarg);
142 if (arg_output < 0) {
143 log_error("Unknown output '%s'.", optarg);
144 return -EINVAL;
145 }
146
147 break;
148
149 case 'a':
150 arg_show_all = true;
151 break;
152
153 case 'n':
154 r = safe_atoi(optarg, &arg_lines);
155 if (r < 0 || arg_lines < 0) {
156 log_error("Failed to parse lines '%s'", optarg);
157 return -EINVAL;
158 }
159 break;
160
161 case ARG_NO_TAIL:
162 arg_no_tail = true;
163 break;
164
165 case ARG_NEW_ID128:
166 arg_new_id128 = true;
167 break;
168
169 case 'q':
170 arg_quiet = true;
171 break;
172
173 case 'l':
174 arg_local = true;
175 break;
176
177 case 'b':
178 arg_this_boot = true;
179 break;
180
181 case 'D':
182 arg_directory = optarg;
183 break;
184
185 case ARG_HEADER:
186 arg_print_header = true;
187 break;
188
189 case 'p': {
190 const char *dots;
191
192 dots = strstr(optarg, "..");
193 if (dots) {
194 char *a;
195 int from, to, i;
196
197 /* a range */
198 a = strndup(optarg, dots - optarg);
199 if (!a)
200 return log_oom();
201
202 from = log_level_from_string(a);
203 to = log_level_from_string(dots + 2);
204 free(a);
205
206 if (from < 0 || to < 0) {
207 log_error("Failed to parse log level range %s", optarg);
208 return -EINVAL;
209 }
210
211 arg_priorities = 0;
212
213 if (from < to) {
214 for (i = from; i <= to; i++)
215 arg_priorities |= 1 << i;
216 } else {
217 for (i = to; i <= from; i++)
218 arg_priorities |= 1 << i;
219 }
220
221 } else {
222 int p, i;
223
224 p = log_level_from_string(optarg);
225 if (p < 0) {
226 log_error("Unknown log level %s", optarg);
227 return -EINVAL;
228 }
229
230 arg_priorities = 0;
231
232 for (i = 0; i <= p; i++)
233 arg_priorities |= 1 << i;
234 }
235
236 break;
237 }
238
239 case '?':
240 return -EINVAL;
241
242 default:
243 log_error("Unknown option code %c", c);
244 return -EINVAL;
245 }
246 }
247
248 if (arg_follow && !arg_no_tail && arg_lines < 0)
249 arg_lines = 10;
250
251 return 1;
252 }
253
254 static bool on_tty(void) {
255 static int t = -1;
256
257 /* Note that this is invoked relatively early, before we start
258 * the pager. That means the value we return reflects whether
259 * we originally were started on a tty, not if we currently
260 * are. But this is intended, since we want colour and so on
261 * when run in our own pager. */
262
263 if (_unlikely_(t < 0))
264 t = isatty(STDOUT_FILENO) > 0;
265
266 return t;
267 }
268
269 static int generate_new_id128(void) {
270 sd_id128_t id;
271 int r;
272 unsigned i;
273
274 r = sd_id128_randomize(&id);
275 if (r < 0) {
276 log_error("Failed to generate ID: %s", strerror(-r));
277 return r;
278 }
279
280 printf("As string:\n"
281 SD_ID128_FORMAT_STR "\n\n"
282 "As UUID:\n"
283 "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
284 "As macro:\n"
285 "#define MESSAGE_XYZ SD_ID128_MAKE(",
286 SD_ID128_FORMAT_VAL(id),
287 SD_ID128_FORMAT_VAL(id));
288
289 for (i = 0; i < 16; i++)
290 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
291
292 fputs(")\n", stdout);
293
294 return 0;
295 }
296
297 static int add_matches(sd_journal *j, char **args) {
298 char **i;
299 int r;
300
301 assert(j);
302
303 STRV_FOREACH(i, args) {
304
305 if (streq(*i, "+"))
306 r = sd_journal_add_disjunction(j);
307 else if (path_is_absolute(*i)) {
308 char *p;
309 const char *path;
310 struct stat st;
311
312 p = canonicalize_file_name(*i);
313 path = p ? p : *i;
314
315 if (stat(path, &st) < 0) {
316 free(p);
317 log_error("Couldn't stat file: %m");
318 return -errno;
319 }
320
321 if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) {
322 char *t;
323
324 t = strappend("_EXE=", path);
325 if (!t) {
326 free(p);
327 return log_oom();
328 }
329
330 r = sd_journal_add_match(j, t, 0);
331 free(t);
332 } else {
333 free(p);
334 log_error("File is not a regular file or is not executable: %s", *i);
335 return -EINVAL;
336 }
337
338 free(p);
339 } else
340 r = sd_journal_add_match(j, *i, 0);
341
342 if (r < 0) {
343 log_error("Failed to add match '%s': %s", *i, strerror(-r));
344 return r;
345 }
346 }
347
348 return 0;
349 }
350
351 static int add_this_boot(sd_journal *j) {
352 char match[9+32+1] = "_BOOT_ID=";
353 sd_id128_t boot_id;
354 int r;
355
356 assert(j);
357
358 if (!arg_this_boot)
359 return 0;
360
361 r = sd_id128_get_boot(&boot_id);
362 if (r < 0) {
363 log_error("Failed to get boot id: %s", strerror(-r));
364 return r;
365 }
366
367 sd_id128_to_string(boot_id, match + 9);
368 r = sd_journal_add_match(j, match, strlen(match));
369 if (r < 0) {
370 log_error("Failed to add match: %s", strerror(-r));
371 return r;
372 }
373
374 return 0;
375 }
376
377 static int add_priorities(sd_journal *j) {
378 char match[] = "PRIORITY=0";
379 int i, r;
380
381 assert(j);
382
383 if (arg_priorities == 0xFF)
384 return 0;
385
386 for (i = LOG_EMERG; i <= LOG_DEBUG; i++)
387 if (arg_priorities & (1 << i)) {
388 match[sizeof(match)-2] = '0' + i;
389
390 log_info("adding match %s", match);
391
392 r = sd_journal_add_match(j, match, strlen(match));
393 if (r < 0) {
394 log_error("Failed to add match: %s", strerror(-r));
395 return r;
396 }
397 }
398
399 return 0;
400 }
401
402 int main(int argc, char *argv[]) {
403 int r;
404 sd_journal *j = NULL;
405 unsigned line = 0;
406 bool need_seek = false;
407 sd_id128_t previous_boot_id;
408 bool previous_boot_id_valid = false;
409 bool have_pager;
410
411 log_parse_environment();
412 log_open();
413
414 r = parse_argv(argc, argv);
415 if (r <= 0)
416 goto finish;
417
418 if (arg_new_id128) {
419 r = generate_new_id128();
420 goto finish;
421 }
422
423 #ifdef HAVE_ACL
424 if (!arg_quiet && geteuid() != 0 && in_group("adm") <= 0)
425 log_warning("Showing user generated messages only. Users in the group 'adm' can see all messages. Pass -q to turn this message off.");
426 #endif
427
428 if (arg_directory)
429 r = sd_journal_open_directory(&j, arg_directory, 0);
430 else
431 r = sd_journal_open(&j, arg_local ? SD_JOURNAL_LOCAL_ONLY : 0);
432
433 if (r < 0) {
434 log_error("Failed to open journal: %s", strerror(-r));
435 goto finish;
436 }
437
438 if (arg_print_header) {
439 journal_print_header(j);
440 r = 0;
441 goto finish;
442 }
443
444 r = add_this_boot(j);
445 if (r < 0)
446 goto finish;
447
448 r = add_matches(j, argv + optind);
449 if (r < 0)
450 goto finish;
451
452 r = add_priorities(j);
453 if (r < 0)
454 goto finish;
455
456 if (!arg_quiet) {
457 usec_t start, end;
458 char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
459
460 r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
461 if (r < 0) {
462 log_error("Failed to get cutoff: %s", strerror(-r));
463 goto finish;
464 }
465
466 if (r > 0) {
467 if (arg_follow)
468 printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
469 else
470 printf("Logs begin at %s, end at %s.\n",
471 format_timestamp(start_buf, sizeof(start_buf), start),
472 format_timestamp(end_buf, sizeof(end_buf), end));
473 }
474 }
475
476 if (arg_lines >= 0) {
477 r = sd_journal_seek_tail(j);
478 if (r < 0) {
479 log_error("Failed to seek to tail: %s", strerror(-r));
480 goto finish;
481 }
482
483 r = sd_journal_previous_skip(j, arg_lines);
484 } else {
485 r = sd_journal_seek_head(j);
486 if (r < 0) {
487 log_error("Failed to seek to head: %s", strerror(-r));
488 goto finish;
489 }
490
491 r = sd_journal_next(j);
492 }
493
494 if (r < 0) {
495 log_error("Failed to iterate through journal: %s", strerror(-r));
496 goto finish;
497 }
498
499 on_tty();
500 have_pager = !arg_no_pager && !arg_follow && pager_open();
501
502 if (arg_output == OUTPUT_JSON) {
503 fputc('[', stdout);
504 fflush(stdout);
505 }
506
507 for (;;) {
508 for (;;) {
509 sd_id128_t boot_id;
510 int flags =
511 arg_show_all * OUTPUT_SHOW_ALL |
512 have_pager * OUTPUT_FULL_WIDTH |
513 on_tty() * OUTPUT_COLOR;
514
515 if (need_seek) {
516 r = sd_journal_next(j);
517 if (r < 0) {
518 log_error("Failed to iterate through journal: %s", strerror(-r));
519 goto finish;
520 }
521 }
522
523 if (r == 0)
524 break;
525
526 r = sd_journal_get_monotonic_usec(j, NULL, &boot_id);
527 if (r >= 0) {
528 if (previous_boot_id_valid &&
529 !sd_id128_equal(boot_id, previous_boot_id))
530 printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
531
532 previous_boot_id = boot_id;
533 previous_boot_id_valid = true;
534 }
535
536 line ++;
537
538 r = output_journal(j, arg_output, line, 0, flags);
539 if (r < 0)
540 goto finish;
541
542 need_seek = true;
543 }
544
545 if (!arg_follow)
546 break;
547
548 r = sd_journal_wait(j, (uint64_t) -1);
549 if (r < 0) {
550 log_error("Couldn't wait for log event: %s", strerror(-r));
551 goto finish;
552 }
553 }
554
555 if (arg_output == OUTPUT_JSON)
556 fputs("\n]\n", stdout);
557
558 finish:
559 if (j)
560 sd_journal_close(j);
561
562 pager_close();
563
564 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
565 }