]> git.proxmox.com Git - proxmox-mini-journalreader.git/blob - src/mini-journalreader.c
f6ae87450edc638c19fa6aab1fa41c3a74b23740
[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+1];
35 static size_t offset = 0;
36
37 uint64_t convert_argument(const char *argument) {
38 errno = 0;
39 char * end;
40 uint64_t value = strtoull(argument, &end, 10);
41 if (errno != 0 || *end != '\0') {
42 fprintf(stderr, "%s is not a valid number\n", argument);
43 exit(1);
44 }
45
46 return value;
47 }
48
49 uint64_t get_timestamp(sd_journal *j) {
50 uint64_t timestamp;
51 int r = sd_journal_get_realtime_usec(j, &timestamp);
52 if (r < 0) {
53 fprintf(stderr, "Failed %s\n", strerror(-r));
54 return 0xFFFFFFFFFFFFFFFF;
55 }
56 return timestamp;
57 }
58
59 void print_to_buf(const char * string, uint32_t length) {
60 if (!length) {
61 return;
62 }
63 size_t string_offset = 0;
64 size_t remaining = length;
65 while (offset + remaining > BUFSIZE) {
66 strncpy(buf+offset, string+string_offset, BUFSIZE-offset);
67 string_offset += BUFSIZE-offset;
68 remaining = length - string_offset;
69 write (1, buf, BUFSIZE);
70 offset = 0;
71 }
72 strncpy(buf+offset, string+string_offset, remaining);
73 offset += remaining;
74 }
75
76 bool printed_first_cursor = false;
77
78 void print_cursor(sd_journal *j) {
79 int r;
80 char *cursor = NULL;
81 r = sd_journal_get_cursor(j, &cursor);
82 if (r < 0) {
83 fprintf(stderr, "Failed to get cursor: %s\n", strerror(-r));
84 exit(1);
85 }
86 print_to_buf(cursor, strlen(cursor));
87 print_to_buf("\n", 1);
88 free(cursor);
89 }
90
91 void print_first_cursor(sd_journal *j) {
92 if (!printed_first_cursor) {
93 print_cursor(j);
94 printed_first_cursor = true;
95 }
96 }
97
98 static uint64_t last_timestamp;
99 static char timestring[16];
100 static char bootid[32];
101
102 void print_reboot(sd_journal *j) {
103 const char *d;
104 size_t l;
105 int r = sd_journal_get_data(j, "_BOOT_ID", (const void **)&d, &l);
106 if (r < 0) {
107 fprintf(stderr, "Failed %s\n", strerror(-r));
108 return;
109 }
110
111 // remove '_BOOT_ID='
112 d += 9;
113 l -= 9;
114
115 if (bootid[0] != '\0') { // we have some bootid
116 if (strncmp(bootid, d, l)) { // a new bootid found
117 strncpy(bootid, d, l);
118 print_to_buf("-- Reboot --\n", 13);
119 }
120 } else {
121 strncpy(bootid, d, l);
122 }
123 }
124
125 void print_timestamp(sd_journal *j) {
126 uint64_t timestamp;
127 int r = sd_journal_get_realtime_usec(j, &timestamp);
128 if (r < 0) {
129 fprintf(stderr, "Failed %s\n", strerror(-r));
130 return;
131 }
132
133 if (timestamp >= (last_timestamp+(1000*1000))) {
134 timestamp = timestamp / (1000*1000); // usec to sec
135 struct tm time;
136 localtime_r((time_t *)&timestamp, &time);
137 strftime(timestring, 16, "%b %d %T", &time);
138 last_timestamp = timestamp;
139 }
140
141 print_to_buf(timestring, 15);
142 }
143
144 void print_pid(sd_journal *j) {
145 const char *d;
146 size_t l;
147 int r = sd_journal_get_data(j, "_PID", (const void **)&d, &l);
148 if (r < 0) {
149 // we sometimes have no pid
150 return;
151 }
152
153 // remove '_PID='
154 d += 5;
155 l -= 5;
156
157 print_to_buf("[", 1);
158 print_to_buf(d, l);
159 print_to_buf("]", 1);
160 }
161
162 bool print_field(sd_journal *j, const char *field) {
163 const char *d;
164 size_t l;
165 int r = sd_journal_get_data(j, field, (const void **)&d, &l);
166 if (r < 0) {
167 // some fields do not exists
168 return false;
169 }
170
171 int fieldlen = strlen(field)+1;
172 d += fieldlen;
173 l -= fieldlen;
174 print_to_buf(d, l);
175 return true;
176 }
177
178
179 void print_line(sd_journal *j) {
180 print_reboot(j);
181 print_timestamp(j);
182 print_to_buf(" ", 1);
183 print_field(j, "_HOSTNAME");
184 print_to_buf(" ", 1);
185 if (!print_field(j, "SYSLOG_IDENTIFIER") &&
186 !print_field(j, "_COMM")) {
187 print_to_buf("unknown", strlen("unknown") - 1);
188 }
189 print_pid(j);
190 print_to_buf(": ", 2);
191 print_field(j, "MESSAGE");
192 print_to_buf("\n", 1);
193 }
194
195 void usage(char *progname) {
196 fprintf(stderr, "usage: %s [OPTIONS]\n", progname);
197 fprintf(stderr, " -b begin\tbegin at this epoch\n");
198 fprintf(stderr, " -e end\tend at this epoch\n");
199 fprintf(stderr, " -d directory\tpath to journal directory\n");
200 fprintf(stderr, " -n number\tprint the last number entries\n");
201 fprintf(stderr, " -f from\tprint from this cursor\n");
202 fprintf(stderr, " -t to\tprint to this cursor\n");
203 fprintf(stderr, " -h \t\tthis help\n");
204 fprintf(stderr, "\n");
205 fprintf(stderr, "giving a range conflicts with -n\n");
206 fprintf(stderr, "-b and -f conflict\n");
207 fprintf(stderr, "-e and -t conflict\n");
208 }
209
210 int main(int argc, char *argv[]) {
211 uint64_t number = 0;
212 const char *directory = NULL;
213 const char *startcursor = NULL;
214 const char *endcursor = NULL;
215 uint64_t begin = 0;
216 uint64_t end = 0;
217 char c;
218
219 while ((c = getopt (argc, argv, "b:e:d:n:f:t:h")) != -1) {
220 switch (c) {
221 case 'b':
222 begin = convert_argument(optarg);
223 begin = begin*1000*1000;
224 break;
225 case 'e':
226 end = convert_argument(optarg);
227 end = end*1000*1000;
228 break;
229 case 'd':
230 directory = optarg;
231 break;
232 case 'n':
233 number = convert_argument(optarg);
234 break;
235 case 'f':
236 startcursor = optarg;
237 break;
238 case 't':
239 endcursor = optarg;
240 break;
241 case 'h':
242 usage(argv[0]);
243 exit(0);
244 break;
245 case '?':
246 default:
247 usage(argv[0]);
248 exit(1);
249 }
250 }
251
252 if (number && (begin || startcursor)) {
253 usage(argv[0]);
254 exit(1);
255 }
256
257 if (begin && startcursor) {
258 usage(argv[0]);
259 exit(1);
260 }
261
262 if (end && endcursor) {
263 usage(argv[0]);
264 exit(1);
265 }
266
267 if (argc > optind) {
268 usage(argv[0]);
269 exit(1);
270 }
271
272 // to prevent calling it everytime we generate a timestamp
273 tzset();
274
275 int r;
276 sd_journal *j;
277 if (directory == NULL) {
278 r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
279 } else {
280 r = sd_journal_open_directory(&j, directory, 0);
281 }
282
283 if (r < 0) {
284 fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
285 return 1;
286 }
287
288 // if we want to print the last x entries, seek to cursor or end,
289 // then x entries back, print the cursor and finally print the
290 // entries until end or cursor
291 if (number) {
292 if (end) {
293 r = sd_journal_seek_realtime_usec(j, end);
294 } else if (endcursor != NULL) {
295 r = sd_journal_seek_cursor(j, endcursor);
296 number++;
297 } else {
298 r = sd_journal_seek_tail(j);
299 }
300
301 if (r < 0) {
302 fprintf(stderr, "Failed to seek to end/cursor: %s\n", strerror(-r));
303 exit(1);
304 }
305
306 // seek back number entries and print cursor
307 r = sd_journal_previous_skip(j, number + 1);
308 if (r < 0) {
309 fprintf(stderr, "Failed to seek back: %s\n", strerror(-r));
310 exit(1);
311 }
312 } else {
313 if (begin) {
314 r = sd_journal_seek_realtime_usec(j, begin);
315 } else if (startcursor) {
316 r = sd_journal_seek_cursor(j, startcursor);
317 } else {
318 r = sd_journal_seek_head(j);
319 }
320
321 if (r < 0) {
322 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
323 exit(1);
324 }
325
326 // if we have a start cursor, we want to skip the first entry
327 if (startcursor) {
328 r = sd_journal_next(j);
329 if (r < 0) {
330 fprintf(stderr, "Failed to seek to begin/cursor: %s\n", strerror(-r));
331 exit(1);
332 }
333 print_first_cursor(j);
334 }
335 }
336
337
338 while ((r = sd_journal_next(j)) > 0 && (end == 0 || get_timestamp(j) < end)) {
339 print_first_cursor(j);
340 if (endcursor != NULL && sd_journal_test_cursor(j, endcursor)) {
341 break;
342 }
343 print_line(j);
344 }
345
346 // print optional reboot
347 print_reboot(j);
348
349 // print final cursor
350 print_cursor(j);
351
352 // print remaining buffer
353 write(1, buf, offset);
354 sd_journal_close(j);
355
356 return 0;
357 }
358