]> git.proxmox.com Git - proxmox-mini-journalreader.git/blob - src/mini-journalreader.c
makefile: convert to use simple parenthesis
[proxmox-mini-journalreader.git] / src / mini-journalreader.c
1 /*
2 Copyright (C) 2019 Proxmox Server Solutions GmbH
3
4 Copyright: mini-journal is under GNU GPL, the GNU General Public License.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; version 2 dated June, 1991.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA.
19
20 Author: Dominik Csapak <d.csapak@proxmox.com>
21 */
22
23 #include <errno.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <systemd/sd-journal.h>
29 #include <time.h>
30 #include <unistd.h>
31
32 #define BUFSIZE 4096
33
34 static char BUF[BUFSIZE];
35 bool json = false;
36 bool first_line = true;
37
38 static uint64_t get_timestamp(sd_journal *j) {
39 uint64_t timestamp;
40 int r = sd_journal_get_realtime_usec(j, &timestamp);
41 if (r < 0) {
42 fprintf(stderr, "Failed %s\n", strerror(-r));
43 return (uint64_t) -1;
44 }
45 return timestamp;
46 }
47
48 static void print_to_buf(const char * string, size_t length) {
49 if (!length) {
50 return;
51 }
52
53 size_t r = fwrite_unlocked(string, 1, length, stdout);
54 if (r < length) {
55 fprintf(stderr, "Failed to write\n");
56 exit(1);
57 }
58 }
59
60 static void print_cursor(sd_journal *j) {
61 int r;
62 char *cursor = NULL;
63 r = sd_journal_get_cursor(j, &cursor);
64 if (r < 0) {
65 fprintf(stderr, "Failed to get cursor: %s\n", strerror(-r));
66 exit(1);
67 }
68 if (json) {
69 if (!first_line) {
70 print_to_buf(",\"", 2);
71 } else {
72 print_to_buf("\"", 1);
73 }
74 }
75 print_to_buf(cursor, strlen(cursor));
76 if (json) {
77 print_to_buf("\"", 1);
78 }
79 print_to_buf("\n", 1);
80 first_line = false;
81 free(cursor);
82 }
83
84 static void print_first_cursor(sd_journal *j) {
85 static bool printed_first_cursor = false;
86 if (!printed_first_cursor) {
87 print_cursor(j);
88 printed_first_cursor = true;
89 }
90 }
91
92 static void print_reboot(sd_journal *j) {
93 const char *d;
94 size_t l;
95 int r = sd_journal_get_data(j, "_BOOT_ID", (const void **)&d, &l);
96 if (r < 0) {
97 fprintf(stderr, "Failed %s\n", strerror(-r));
98 return;
99 }
100
101 // remove '_BOOT_ID='
102 d += 9;
103 l -= 9;
104
105 static char bootid[32];
106 if (bootid[0] != '\0') { // we have some bootid
107 if (memcmp(bootid, d, l)) { // a new bootid found
108 memcpy(bootid, d, l);
109 if (json) {
110 if (!first_line) {
111 print_to_buf(",", 1);
112 }
113 print_to_buf("\"-- Reboot --\"\n", 15);
114 first_line = false;
115 } else {
116 print_to_buf("-- Reboot --\n", 13);
117 }
118 }
119 } else {
120 memcpy(bootid, d, l);
121 }
122 }
123
124 static void print_timestamp(sd_journal *j) {
125 uint64_t timestamp = get_timestamp(j);
126 if (timestamp == (uint64_t) -1) {
127 return;
128 }
129
130 static uint64_t last_timestamp;
131 static char timestring[16];
132 if (timestamp >= (last_timestamp+(1000*1000))) {
133 timestamp = timestamp / (1000*1000); // usec to sec
134 struct tm time;
135 localtime_r((time_t *)&timestamp, &time);
136 strftime(timestring, 16, "%b %d %T", &time);
137 last_timestamp = timestamp;
138 }
139
140 print_to_buf(timestring, 15);
141 }
142
143 static void print_pid(sd_journal *j) {
144 const char *d;
145 size_t l;
146 int r = sd_journal_get_data(j, "_PID", (const void **)&d, &l);
147 if (r < 0) {
148 // we sometimes have no pid, e.g., kernel messages
149 return;
150 }
151
152 // remove '_PID='
153 d += 5;
154 l -= 5;
155
156 print_to_buf("[", 1);
157 print_to_buf(d, l);
158 print_to_buf("]", 1);
159 }
160
161 static bool print_field(sd_journal *j, const char *field) {
162 const char *d;
163 size_t l;
164 int r = sd_journal_get_data(j, field, (const void **)&d, &l);
165 if (r < 0) {
166 // some fields do not exists
167 return false;
168 }
169
170 size_t fieldlen = strlen(field)+1;
171 d += fieldlen;
172 l -= fieldlen;
173
174 if (json) {
175 char tmp[7];
176 for (size_t i = 0; i < l;i++) {
177 if (d[i] == '"' || d[i] == '\\' || (d[i] >= 0 && d[i] <= 0x1F)) {
178 sprintf(tmp, "\\u%04X", d[i]);
179 print_to_buf(tmp, 6);
180 } else {
181 print_to_buf(d+i, 1);
182 }
183 }
184 } else {
185 print_to_buf(d, l);
186 }
187 return true;
188 }
189
190
191 static void print_line(sd_journal *j) {
192 print_reboot(j);
193
194 if (json) {
195 if (!first_line) {
196 print_to_buf(",", 1);
197 }
198 print_to_buf("\"", 1);
199 first_line = false;
200 }
201
202 print_timestamp(j);
203 print_to_buf(" ", 1);
204 print_field(j, "_HOSTNAME");
205 print_to_buf(" ", 1);
206 if (!print_field(j, "SYSLOG_IDENTIFIER") &&
207 !print_field(j, "_COMM")) {
208 print_to_buf("unknown", strlen("unknown") - 1);
209 }
210 print_pid(j);
211 print_to_buf(": ", 2);
212 print_field(j, "MESSAGE");
213
214 if (json) {
215 print_to_buf("\"", 1);
216 }
217
218 print_to_buf("\n", 1);
219 }
220
221 static char *progname;
222
223 _Noreturn static void usage(char *error) {
224 if (error) {
225 fprintf(stderr, "ERROR: %s\n", error);
226 }
227 fprintf(stderr, "usage: %s [OPTIONS]\n", progname);
228 fprintf(stderr,
229 " -b <timestamp>\tbegin at this UNIX epoch based timestamp\n"
230 " -e <timestamp>\tend at this UNIX epoch based timestamp\n"
231 " -d <directory>\tpath to a journal directory\n"
232 " -n <integer>\t\tprint the last number entries logged\n"
233 " -f <cursor>\t\tprint from this cursor\n"
234 " -t <cursor>\t\tprint to this cursor\n"
235 " -j \t\t\tprint as json"
236 " -h \t\t\tthis help\n"
237 "\n"
238 "Passing no range option will dump all the available journal\n"
239 "Giving a range conflicts with -n\n"
240 "-b and -f conflict\n"
241 "-e and -t conflict\n");
242 exit(error ? 1 : 0);
243 }
244
245 static uint64_t arg_to_uint64(const char *argument) {
246 errno = 0;
247 char * end;
248 uint64_t value = strtoull(argument, &end, 10);
249 if (errno != 0 || *end != '\0') {
250 fprintf(stderr, "%s is not a valid integer number\n", argument);
251 exit(1);
252 }
253
254 return value;
255 }
256
257
258 int main(int argc, char *argv[]) {
259 uint64_t number = 0;
260 const char *directory = NULL;
261 const char *startcursor = NULL;
262 const char *endcursor = NULL;
263 uint64_t begin = 0;
264 uint64_t end = 0;
265 char c;
266
267 progname = argv[0];
268
269 while ((c = (char)getopt (argc, argv, "b:e:d:n:f:t:jh")) != -1) {
270 switch (c) {
271 case 'b':
272 begin = arg_to_uint64(optarg);
273 begin = begin * 1000 * 1000; // µs
274 break;
275 case 'e':
276 end = arg_to_uint64(optarg);
277 end = end * 1000 * 1000; // µs
278 break;
279 case 'd':
280 directory = optarg;
281 break;
282 case 'n':
283 number = arg_to_uint64(optarg);
284 break;
285 case 'f':
286 startcursor = optarg;
287 break;
288 case 't':
289 endcursor = optarg;
290 break;
291 case 'j':
292 json = true;
293 break;
294 case 'h':
295 usage(NULL);
296 case '?':
297 default:
298 usage("invalid option or missing argument");
299 }
300 }
301
302 if (number && (begin || startcursor)) {
303 usage("-n conflicts with -b and/or -f");
304 }
305
306 if (begin && startcursor) {
307 usage("-b and -f conflict");
308 }
309
310 if (end && endcursor) {
311 usage("-e and -t conflict");
312 }
313
314 if (argc > optind) {
315 usage("unkown, or to many arguments");
316 }
317
318 // setup stdout buffer
319 if (setvbuf(stdout, BUF, _IOFBF, BUFSIZE)) {
320 fprintf(stderr, "Failed to set buffer for stdout: %s\n", strerror(errno));
321 return 1;
322 }
323
324 // to prevent calling it everytime we generate a timestamp
325 tzset();
326
327 int r;
328 sd_journal *j;
329 if (directory == NULL) {
330 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
331 } else {
332 r = sd_journal_open_directory(&j, directory, 0);
333 }
334
335 if (r < 0) {
336 fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
337 return 1;
338 }
339
340 if (json) {
341 print_to_buf("{\"data\":[", 9);
342 }
343
344 // if we want to print the last x entries, seek to cursor or end,
345 // then x entries back, print the cursor and finally print the
346 // entries until end or cursor
347 if (number) {
348 if (end) {
349 r = sd_journal_seek_realtime_usec(j, end);
350 } else if (endcursor != NULL) {
351 r = sd_journal_seek_cursor(j, endcursor);
352 number++;
353 } else {
354 r = sd_journal_seek_tail(j);
355 }
356
357 if (r < 0) {
358 fprintf(stderr, "Failed to seek to end/cursor: %s\n", strerror(-r));
359 exit(1);
360 }
361
362 // seek back number entries and print cursor
363 r = sd_journal_previous_skip(j, number + 1);
364 if (r < 0) {
365 fprintf(stderr, "Failed to seek back: %s\n", strerror(-r));
366 exit(1);
367 }
368 } else {
369 if (begin) {
370 r = sd_journal_seek_realtime_usec(j, begin);
371 } else if (startcursor) {
372 r = sd_journal_seek_cursor(j, startcursor);
373 } else {
374 r = sd_journal_seek_head(j);
375 }
376
377 if (r < 0) {
378 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
379 exit(1);
380 }
381
382 // if we have a start cursor, we want to skip the first entry
383 if (startcursor) {
384 r = sd_journal_next(j);
385 if (r < 0) {
386 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
387 exit(1);
388 }
389 print_first_cursor(j);
390 }
391 }
392
393
394 while ((r = sd_journal_next(j)) > 0 && (end == 0 || get_timestamp(j) < end)) {
395 print_first_cursor(j);
396 if (endcursor != NULL && sd_journal_test_cursor(j, endcursor)) {
397 break;
398 }
399 print_line(j);
400 }
401
402 // print optional reboot
403 print_reboot(j);
404
405 // print final cursor
406 print_cursor(j);
407 sd_journal_close(j);
408
409 if (json) {
410 print_to_buf("],\"success\":1}", 14);
411 }
412
413 // print remaining buffer
414 fflush_unlocked(stdout);
415
416 return 0;
417 }
418