chiark / gitweb /
journalctl: implement --since= and --until for filtering by time
authorLennart Poettering <lennart@poettering.net>
Thu, 11 Oct 2012 14:42:46 +0000 (16:42 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 11 Oct 2012 14:43:37 +0000 (16:43 +0200)
.gitignore
Makefile.am
TODO
man/journalctl.xml
src/journal/journalctl.c
src/shared/util.c
src/shared/util.h
src/test/test-date.c [new file with mode: 0644]

index 13d2df4..71359ce 100644 (file)
@@ -1,3 +1,4 @@
+/test-date
 /install-tree
 /systemd-journal-gatewayd
 /test-mmap-cache
index c23afdf..e332183 100644 (file)
@@ -1157,14 +1157,16 @@ noinst_PROGRAMS += \
        test-watchdog \
        test-unit-name \
        test-log \
-       test-unit-file
+       test-unit-file \
+       test-date
 
 TESTS += \
        test-job-type \
        test-env-replace \
        test-strv \
        test-unit-name \
-       test-unit-file
+       test-unit-file \
+       test-data
 
 test_engine_SOURCES = \
        src/test/test-engine.c
@@ -1226,6 +1228,12 @@ test_log_SOURCES = \
 test_log_LDADD = \
        libsystemd-core.la
 
+test_date_SOURCES = \
+       src/test/test-date.c
+
+test_date_LDADD = \
+       libsystemd-core.la
+
 test_daemon_SOURCES = \
        src/test/test-daemon.c
 
diff --git a/TODO b/TODO
index 5943c9a..f3f66ff 100644 (file)
--- a/TODO
+++ b/TODO
@@ -19,6 +19,8 @@ F18:
 
 Features:
 
+* _SOURCE_MONOTONIC_TIMESTAMP entries from the kernel seem to be off by 1000000
+
 * document unit_name_mangle()
 
 * add new command to systemctl: "systemctl system-reexec" which reexecs as many daemons as virtually possible
index 62373d8..3786fdf 100644 (file)
                                 <term><option>--cursor=</option></term>
                                 <term><option>-c</option></term>
 
-                                <listitem><para>Jump to the location
-                                in the journal specified by the passed
+                                <listitem><para>Start showing entries
+                                from the location in the journal
+                                specified by the passed
                                 cursor.</para></listitem>
                         </varlistentry>
 
                         <varlistentry>
+                                <term><option>--since=</option></term>
+                                <term><option>--until=</option></term>
+
+                                <listitem><para>Start showing entries
+                                newer or of the specified date,
+                                resp. older or of the specified
+                                date. Date specifications should be of
+                                the format "2012-10-30 18:17:16". If
+                                the time part is omitted, 00:00:00 is
+                                assumed. If only the seconds component
+                                is omitted, :00 is assumed. If the
+                                date component is ommitted, the
+                                current day is assumed. Alternatively
+                                the strings
+                                <literal>yesterday</literal>,
+                                <literal>today</literal>,
+                                <literal>tomorrow</literal> are
+                                understood, which refer to 00:00:00 of
+                                the day before the current day, the
+                                current day, resp the day after the
+                                current day. <literal>now</literal>
+                                refers to the current time. Finally,
+                                relative times may be specified,
+                                prefixed with <literal>-</literal> or
+                                <literal>+</literal>, referring to
+                                times before resp. after the current
+                                time.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><option>--directory=</option></term>
                                 <term><option>-D</option></term>
 
index 04cebff..54ee6d8 100644 (file)
@@ -58,7 +58,7 @@ static OutputMode arg_output = OUTPUT_SHORT;
 static bool arg_follow = false;
 static bool arg_show_all = false;
 static bool arg_no_pager = false;
-static int arg_lines = -1;
+static unsigned arg_lines = 0;
 static bool arg_no_tail = false;
 static bool arg_quiet = false;
 static bool arg_merge = false;
@@ -70,6 +70,8 @@ static const char *arg_verify_key = NULL;
 #ifdef HAVE_GCRYPT
 static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC;
 #endif
+static usec_t arg_since, arg_until;
+static bool arg_since_set = false, arg_until_set = false;
 
 static enum {
         ACTION_SHOW,
@@ -88,7 +90,9 @@ static int help(void) {
                "     --version           Show package version\n"
                "     --no-pager          Do not pipe output into a pager\n"
                "  -a --all               Show all fields, including long and unprintable\n"
-               "  -c --cursor=CURSOR     Jump to the specified cursor\n"
+               "  -c --cursor=CURSOR     Start showing entries from specified cursor\n"
+               "     --since=DATE        Start showing entries newer or of the specified date\n"
+               "     --until=DATE        Stop showing entries older or of the specified date\n"
                "  -f --follow            Follow journal\n"
                "  -n --lines[=INTEGER]   Number of journal entries to show\n"
                "     --no-tail           Show all lines, even in follow mode\n"
@@ -126,7 +130,9 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_INTERVAL,
                 ARG_VERIFY,
                 ARG_VERIFY_KEY,
-                ARG_DISK_USAGE
+                ARG_DISK_USAGE,
+                ARG_SINCE,
+                ARG_UNTIL
         };
 
         static const struct option options[] = {
@@ -151,6 +157,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "verify-key",   required_argument, NULL, ARG_VERIFY_KEY   },
                 { "disk-usage",   no_argument,       NULL, ARG_DISK_USAGE   },
                 { "cursor",       required_argument, NULL, 'c'              },
+                { "since",        required_argument, NULL, ARG_SINCE        },
+                { "until",        required_argument, NULL, ARG_UNTIL        },
                 { NULL,           0,                 NULL, 0                }
         };
 
@@ -197,8 +205,8 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case 'n':
                         if (optarg) {
-                                r = safe_atoi(optarg, &arg_lines);
-                                if (r < 0 || arg_lines < 0) {
+                                r = safe_atou(optarg, &arg_lines);
+                                if (r < 0 || arg_lines <= 0) {
                                         log_error("Failed to parse lines '%s'", optarg);
                                         return -EINVAL;
                                 }
@@ -324,6 +332,24 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_SINCE:
+                        r = parse_timestamp(optarg, &arg_since);
+                        if (r < 0) {
+                                log_error("Failed to parse timestamp: %s", optarg);
+                                return -EINVAL;
+                        }
+                        arg_since_set = true;
+                        break;
+
+                case ARG_UNTIL:
+                        r = parse_timestamp(optarg, &arg_until);
+                        if (r < 0) {
+                                log_error("Failed to parse timestamp: %s", optarg);
+                                return -EINVAL;
+                        }
+                        arg_until_set = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -333,9 +359,19 @@ static int parse_argv(int argc, char *argv[]) {
                 }
         }
 
-        if (arg_follow && !arg_no_tail && arg_lines < 0)
+        if (arg_follow && !arg_no_tail && arg_lines <= 0)
                 arg_lines = 10;
 
+        if (arg_since_set && arg_until_set && arg_since_set > arg_until_set) {
+                log_error("--since= must be before --until=.");
+                return -EINVAL;
+        }
+
+        if (arg_cursor && arg_since_set) {
+                log_error("Please specify either --since= or --cursor=, not both.");
+                return -EINVAL;
+        }
+
         return 1;
 }
 
@@ -734,6 +770,7 @@ int main(int argc, char *argv[]) {
         sd_id128_t previous_boot_id;
         bool previous_boot_id_valid = false;
         bool have_pager;
+        unsigned n_shown = 0;
 
         log_parse_environment();
         log_open();
@@ -815,36 +852,24 @@ int main(int argc, char *argv[]) {
         if (r < 0)
                 goto finish;
 
-        if (!arg_quiet) {
-                usec_t start, end;
-                char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
-
-                r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
+        if (arg_cursor) {
+                r = sd_journal_seek_cursor(j, arg_cursor);
                 if (r < 0) {
-                        log_error("Failed to get cutoff: %s", strerror(-r));
+                        log_error("Failed to seek to cursor: %s", strerror(-r));
                         goto finish;
                 }
 
-                if (r > 0) {
-                        if (arg_follow)
-                                printf("Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
-                        else
-                                printf("Logs begin at %s, end at %s.\n",
-                                       format_timestamp(start_buf, sizeof(start_buf), start),
-                                       format_timestamp(end_buf, sizeof(end_buf), end));
-                }
-        }
+                r = sd_journal_next(j);
 
-        if (arg_cursor) {
-                r = sd_journal_seek_cursor(j, arg_cursor);
+        } else if (arg_since_set) {
+                r = sd_journal_seek_realtime_usec(j, arg_since);
                 if (r < 0) {
-                        log_error("Failed to seek to cursor: %s", strerror(-r));
+                        log_error("Failed to seek to date: %s", strerror(-r));
                         goto finish;
                 }
-
                 r = sd_journal_next(j);
 
-        } else if (arg_lines >= 0) {
+        } else if (arg_lines > 0) {
                 r = sd_journal_seek_tail(j);
                 if (r < 0) {
                         log_error("Failed to seek to tail: %s", strerror(-r));
@@ -871,12 +896,34 @@ int main(int argc, char *argv[]) {
         on_tty();
         have_pager = !arg_no_pager && !arg_follow && pager_open();
 
+        if (!arg_quiet) {
+                usec_t start, end;
+                char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX];
+
+                r = sd_journal_get_cutoff_realtime_usec(j, &start, &end);
+                if (r < 0) {
+                        log_error("Failed to get cutoff: %s", strerror(-r));
+                        goto finish;
+                }
+
+                if (r > 0) {
+                        if (arg_follow)
+                                printf("---- Logs begin at %s.\n", format_timestamp(start_buf, sizeof(start_buf), start));
+                        else
+                                printf("---- Logs begin at %s, end at %s.\n",
+                                       format_timestamp(start_buf, sizeof(start_buf), start),
+                                       format_timestamp(end_buf, sizeof(end_buf), end));
+                }
+        }
+
         for (;;) {
                 for (;;) {
-                        int flags =
-                                arg_show_all * OUTPUT_SHOW_ALL |
-                                have_pager * OUTPUT_FULL_WIDTH |
-                                on_tty() * OUTPUT_COLOR;
+                        int flags;
+
+                        if (arg_lines > 0 && n_shown >= arg_lines) {
+                                r = 0;
+                                goto finish;
+                        }
 
                         if (need_seek) {
                                 r = sd_journal_next(j);
@@ -889,6 +936,16 @@ int main(int argc, char *argv[]) {
                         if (r == 0)
                                 break;
 
+                        if (arg_until_set) {
+                                usec_t usec;
+
+                                r = sd_journal_get_realtime_usec(j, &usec);
+                                if (r < 0) {
+                                        log_error("Failed to determine timestamp: %s", strerror(-r));
+                                        goto finish;
+                                }
+                        }
+
                         if (!arg_merge) {
                                 sd_id128_t boot_id;
 
@@ -896,18 +953,24 @@ int main(int argc, char *argv[]) {
                                 if (r >= 0) {
                                         if (previous_boot_id_valid &&
                                             !sd_id128_equal(boot_id, previous_boot_id))
-                                                printf(ANSI_HIGHLIGHT_ON "----- Reboot -----" ANSI_HIGHLIGHT_OFF "\n");
+                                                printf(ANSI_HIGHLIGHT_ON "---- Reboot ----" ANSI_HIGHLIGHT_OFF "\n");
 
                                         previous_boot_id = boot_id;
                                         previous_boot_id_valid = true;
                                 }
                         }
 
+                        flags =
+                                arg_show_all * OUTPUT_SHOW_ALL |
+                                have_pager * OUTPUT_FULL_WIDTH |
+                                on_tty() * OUTPUT_COLOR;
+
                         r = output_journal(stdout, j, arg_output, 0, flags);
                         if (r < 0)
                                 goto finish;
 
                         need_seek = true;
+                        n_shown++;
                 }
 
                 if (!arg_follow)
index 6310aec..15481b6 100644 (file)
@@ -2733,16 +2733,31 @@ int parse_usec(const char *t, usec_t *usec) {
                 const char *suffix;
                 usec_t usec;
         } table[] = {
+                { "seconds", USEC_PER_SEC },
+                { "second", USEC_PER_SEC },
                 { "sec", USEC_PER_SEC },
                 { "s", USEC_PER_SEC },
+                { "minutes", USEC_PER_MINUTE },
+                { "minute", USEC_PER_MINUTE },
                 { "min", USEC_PER_MINUTE },
+                { "months", USEC_PER_MONTH },
+                { "month", USEC_PER_MONTH },
+                { "msec", USEC_PER_MSEC },
+                { "ms", USEC_PER_MSEC },
+                { "m", USEC_PER_MINUTE },
+                { "hours", USEC_PER_HOUR },
+                { "hour", USEC_PER_HOUR },
                 { "hr", USEC_PER_HOUR },
                 { "h", USEC_PER_HOUR },
+                { "days", USEC_PER_DAY },
+                { "day", USEC_PER_DAY },
                 { "d", USEC_PER_DAY },
+                { "weeks", USEC_PER_WEEK },
+                { "week", USEC_PER_WEEK },
                 { "w", USEC_PER_WEEK },
-                { "msec", USEC_PER_MSEC },
-                { "ms", USEC_PER_MSEC },
-                { "m", USEC_PER_MINUTE },
+                { "years", USEC_PER_YEAR },
+                { "year", USEC_PER_YEAR },
+                { "y", USEC_PER_YEAR },
                 { "usec", 1ULL },
                 { "us", 1ULL },
                 { "", USEC_PER_SEC }, /* default is sec */
@@ -2796,16 +2811,31 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                 const char *suffix;
                 nsec_t nsec;
         } table[] = {
+                { "seconds", NSEC_PER_SEC },
+                { "second", NSEC_PER_SEC },
                 { "sec", NSEC_PER_SEC },
                 { "s", NSEC_PER_SEC },
+                { "minutes", NSEC_PER_MINUTE },
+                { "minute", NSEC_PER_MINUTE },
                 { "min", NSEC_PER_MINUTE },
+                { "months", NSEC_PER_MONTH },
+                { "month", NSEC_PER_MONTH },
+                { "msec", NSEC_PER_MSEC },
+                { "ms", NSEC_PER_MSEC },
+                { "m", NSEC_PER_MINUTE },
+                { "hours", NSEC_PER_HOUR },
+                { "hour", NSEC_PER_HOUR },
                 { "hr", NSEC_PER_HOUR },
                 { "h", NSEC_PER_HOUR },
+                { "days", NSEC_PER_DAY },
+                { "day", NSEC_PER_DAY },
                 { "d", NSEC_PER_DAY },
+                { "weeks", NSEC_PER_WEEK },
+                { "week", NSEC_PER_WEEK },
                 { "w", NSEC_PER_WEEK },
-                { "msec", NSEC_PER_MSEC },
-                { "ms", NSEC_PER_MSEC },
-                { "m", NSEC_PER_MINUTE },
+                { "years", NSEC_PER_YEAR },
+                { "year", NSEC_PER_YEAR },
+                { "y", NSEC_PER_YEAR },
                 { "usec", NSEC_PER_USEC },
                 { "us", NSEC_PER_USEC },
                 { "nsec", 1ULL },
@@ -5888,3 +5918,136 @@ bool string_is_safe(const char *p) {
 
         return true;
 }
+
+int parse_timestamp(const char *t, usec_t *usec) {
+        const char *k;
+        struct tm tm, copy;
+        time_t x;
+        usec_t plus = 0, minus = 0, ret;
+        int r;
+
+        /*
+         * Allowed syntaxes:
+         *
+         *   2012-09-22 16:34:22
+         *   2012-09-22 16:34     (seconds will be set to 0)
+         *   2012-09-22           (time will be set to 00:00:00)
+         *   16:34:22             (date will be set to today)
+         *   16:34                (date will be set to today, seconds to 0)
+         *   now
+         *   yesterday            (time is set to 00:00:00)
+         *   today                (time is set to 00:00:00)
+         *   tomorrow             (time is set to 00:00:00)
+         *   +5min
+         *   -5days
+         *
+         */
+
+        assert(t);
+        assert(usec);
+
+        x = time(NULL);
+        assert_se(localtime_r(&x, &tm));
+
+        if (streq(t, "now"))
+                goto finish;
+
+        else if (streq(t, "today")) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto finish;
+
+        } else if (streq(t, "yesterday")) {
+                tm.tm_mday --;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto finish;
+
+        } else if (streq(t, "tomorrow")) {
+                tm.tm_mday ++;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto finish;
+
+        } else if (t[0] == '+') {
+
+                r = parse_usec(t+1, &plus);
+                if (r < 0)
+                        return r;
+
+                goto finish;
+        } else if (t[0] == '-') {
+
+                r = parse_usec(t+1, &minus);
+                if (r < 0)
+                        return r;
+
+                goto finish;
+        }
+
+        copy = tm;
+        k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
+        if (k && *k == 0)
+                goto finish;
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
+        if (k && *k == 0)
+                goto finish;
+
+        tm = copy;
+        k = strptime(t, "%y-%m-%d %H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto finish;
+        }
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d %H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto finish;
+        }
+
+        tm = copy;
+        k = strptime(t, "%y-%m-%d", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto finish;
+        }
+
+        tm = copy;
+        k = strptime(t, "%Y-%m-%d", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto finish;
+        }
+
+        tm = copy;
+        k = strptime(t, "%H:%M:%S", &tm);
+        if (k && *k == 0)
+                goto finish;
+
+        tm = copy;
+        k = strptime(t, "%H:%M", &tm);
+        if (k && *k == 0) {
+                tm.tm_sec = 0;
+                goto finish;
+        }
+
+        return -EINVAL;
+
+finish:
+        x = mktime(&tm);
+        if (x == (time_t) -1)
+                return -EINVAL;
+
+        ret = (usec_t) x * USEC_PER_SEC;
+
+        ret += plus;
+        if (ret > minus)
+                ret -= minus;
+        else
+                ret = 0;
+
+        *usec = ret;
+
+        return 0;
+}
index cbded08..50911eb 100644 (file)
@@ -561,3 +561,5 @@ _malloc_ static inline void *memdup_multiply(const void *p, size_t a, size_t b)
 
 bool filename_is_safe(const char *p);
 bool string_is_safe(const char *p);
+
+int parse_timestamp(const char *t, usec_t *usec);
diff --git a/src/test/test-date.c b/src/test/test-date.c
new file mode 100644 (file)
index 0000000..57f8b37
--- /dev/null
@@ -0,0 +1,69 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+
+int main(int argc, char *argv[]) {
+
+        usec_t t;
+        char buf[FORMAT_TIMESTAMP_MAX];
+
+        assert_se(parse_timestamp("17:41", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("18:42:44", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("12-10-02 12:13:14", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("12-10-2 12:13:14", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("12-10-03 12:13", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("2012-12-30 18:42", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("2012-10-02", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("now", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("yesterday", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("today", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("tomorrow", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("+2d", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        assert_se(parse_timestamp("+2y 4d", &t) >= 0);
+        log_info("%s", format_timestamp(buf, sizeof(buf), t));
+
+        return 0;
+}