]>
git.proxmox.com Git - proxmox-mini-journalreader.git/blob - src/mini-journalreader.c
2 Copyright (C) 2019 Proxmox Server Solutions GmbH
4 Copyright: mini-journal is under GNU GPL, the GNU General Public License.
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.
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.
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
20 Author: Dominik Csapak <d.csapak@proxmox.com>
28 #include <systemd/sd-journal.h>
34 static char BUF
[BUFSIZE
];
36 bool first_line
= true;
38 static uint64_t get_timestamp(sd_journal
*j
) {
40 int r
= sd_journal_get_realtime_usec(j
, ×tamp
);
42 fprintf(stderr
, "Failed %s\n", strerror(-r
));
48 static void print_to_buf(const char * string
, size_t length
) {
53 size_t r
= fwrite_unlocked(string
, 1, length
, stdout
);
55 fprintf(stderr
, "Failed to write\n");
60 static void print_cursor(sd_journal
*j
) {
63 r
= sd_journal_get_cursor(j
, &cursor
);
65 fprintf(stderr
, "Failed to get cursor: %s\n", strerror(-r
));
70 print_to_buf(",\"", 2);
72 print_to_buf("\"", 1);
75 print_to_buf(cursor
, strlen(cursor
));
77 print_to_buf("\"", 1);
79 print_to_buf("\n", 1);
84 static void print_first_cursor(sd_journal
*j
) {
85 static bool printed_first_cursor
= false;
86 if (!printed_first_cursor
) {
88 printed_first_cursor
= true;
92 static void print_reboot(sd_journal
*j
) {
95 int r
= sd_journal_get_data(j
, "_BOOT_ID", (const void **)&d
, &l
);
97 fprintf(stderr
, "Failed %s\n", strerror(-r
));
101 // remove '_BOOT_ID='
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
);
111 print_to_buf(",", 1);
113 print_to_buf("\"-- Reboot --\"\n", 15);
116 print_to_buf("-- Reboot --\n", 13);
120 memcpy(bootid
, d
, l
);
124 static void print_timestamp(sd_journal
*j
) {
125 uint64_t timestamp
= get_timestamp(j
);
126 if (timestamp
== (uint64_t) -1) {
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
135 localtime_r((time_t *)×tamp
, &time
);
136 strftime(timestring
, 16, "%b %d %T", &time
);
137 last_timestamp
= timestamp
;
140 print_to_buf(timestring
, 15);
143 static void print_pid(sd_journal
*j
) {
146 int r
= sd_journal_get_data(j
, "_PID", (const void **)&d
, &l
);
148 // we sometimes have no pid, e.g., kernel messages
156 print_to_buf("[", 1);
158 print_to_buf("]", 1);
161 static bool print_field(sd_journal
*j
, const char *field
) {
164 int r
= sd_journal_get_data(j
, field
, (const void **)&d
, &l
);
166 // some fields do not exists
170 size_t fieldlen
= strlen(field
)+1;
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);
181 print_to_buf(d
+i
, 1);
191 static void print_line(sd_journal
*j
) {
196 print_to_buf(",", 1);
198 print_to_buf("\"", 1);
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);
211 print_to_buf(": ", 2);
212 print_field(j
, "MESSAGE");
215 print_to_buf("\"", 1);
218 print_to_buf("\n", 1);
221 static char *progname
;
223 _Noreturn
static void usage(char *error
) {
225 fprintf(stderr
, "ERROR: %s\n", error
);
227 fprintf(stderr
, "usage: %s [OPTIONS]\n", progname
);
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"
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");
245 static uint64_t arg_to_uint64(const char *argument
) {
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
);
258 int main(int argc
, char *argv
[]) {
260 const char *directory
= NULL
;
261 const char *startcursor
= NULL
;
262 const char *endcursor
= NULL
;
269 while ((c
= (char)getopt (argc
, argv
, "b:e:d:n:f:t:jh")) != -1) {
272 begin
= arg_to_uint64(optarg
);
273 begin
= begin
* 1000 * 1000; // µs
276 end
= arg_to_uint64(optarg
);
277 end
= end
* 1000 * 1000; // µs
283 number
= arg_to_uint64(optarg
);
286 startcursor
= optarg
;
298 usage("invalid option or missing argument");
302 if (number
&& (begin
|| startcursor
)) {
303 usage("-n conflicts with -b and/or -f");
306 if (begin
&& startcursor
) {
307 usage("-b and -f conflict");
310 if (end
&& endcursor
) {
311 usage("-e and -t conflict");
315 usage("unkown, or to many arguments");
318 // setup stdout buffer
319 if (setvbuf(stdout
, BUF
, _IOFBF
, BUFSIZE
)) {
320 fprintf(stderr
, "Failed to set buffer for stdout: %s\n", strerror(errno
));
324 // to prevent calling it everytime we generate a timestamp
329 if (directory
== NULL
) {
330 r
= sd_journal_open(&j
, SD_JOURNAL_LOCAL_ONLY
);
332 r
= sd_journal_open_directory(&j
, directory
, 0);
336 fprintf(stderr
, "Failed to open journal: %s\n", strerror(-r
));
341 print_to_buf("{\"data\":[", 9);
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
349 r
= sd_journal_seek_realtime_usec(j
, end
);
350 } else if (endcursor
!= NULL
) {
351 r
= sd_journal_seek_cursor(j
, endcursor
);
354 r
= sd_journal_seek_tail(j
);
358 fprintf(stderr
, "Failed to seek to end/cursor: %s\n", strerror(-r
));
362 // seek back number entries and print cursor
363 r
= sd_journal_previous_skip(j
, number
+ 1);
365 fprintf(stderr
, "Failed to seek back: %s\n", strerror(-r
));
370 r
= sd_journal_seek_realtime_usec(j
, begin
);
371 } else if (startcursor
) {
372 r
= sd_journal_seek_cursor(j
, startcursor
);
374 r
= sd_journal_seek_head(j
);
378 fprintf(stderr
, "Failed to seek to begin/cursor: %s\n", strerror(-r
));
382 // if we have a start cursor, we want to skip the first entry
384 r
= sd_journal_next(j
);
386 fprintf(stderr
, "Failed to seek to begin/cursor: %s\n", strerror(-r
));
389 print_first_cursor(j
);
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
)) {
402 // print optional reboot
405 // print final cursor
410 print_to_buf("],\"success\":1}", 14);
413 // print remaining buffer
414 fflush_unlocked(stdout
);