2 _reader - Python module that reads systemd journal similar to journalctl
3 Copyright (C) 2012 Steven Hiscocks
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 This library 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 GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 #include <systemd/sd-journal.h>
22 #include <structmember.h>
28 PyObject *default_call;
31 static PyTypeObject JournalType;
34 Journal_dealloc(Journal* self)
36 sd_journal_close(self->j);
37 Py_XDECREF(self->default_call);
38 Py_XDECREF(self->call_dict);
39 Py_TYPE(self)->tp_free((PyObject*)self);
43 Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
47 self = (Journal *)type->tp_alloc(type, 0);
49 PyObject *globals, *temp;
51 globals = PyEval_GetBuiltins();
52 temp = PyImport_ImportModule("functools");
53 PyDict_SetItemString(globals, "functools", temp);
55 temp = PyImport_ImportModule("datetime");
56 PyDict_SetItemString(globals, "datetime", temp);
59 #if PY_MAJOR_VERSION >=3
60 self->default_call = PyRun_String("functools.partial(str, encoding='utf-8')", Py_eval_input, globals, NULL);
62 self->default_call = PyRun_String("functools.partial(unicode, encoding='utf-8')", Py_eval_input, globals, NULL);
65 self->call_dict = PyRun_String("{"
69 "'USERSPACE_USEC': int,"
75 "'SYSLOG_FACILITY': int,"
77 "'_AUDIT_SESSION': int,"
78 "'_AUDIT_LOGINUID': int,"
79 "'_SYSTEMD_SESSION': int,"
80 "'_SYSTEMD_OWNER_UID': int,"
84 "'_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
85 "'__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
86 "'_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),"
87 "'__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),"
88 #if PY_MAJOR_VERSION >=3
93 "'COREDUMP_PID': int,"
94 "'COREDUMP_UID': int,"
95 "'COREDUMP_GID': int,"
96 "'COREDUMP_SESSION': int,"
97 "'COREDUMP_SIGNAL': int,"
98 "'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
99 "}", Py_eval_input, globals, NULL);
102 return (PyObject *) self;
105 PyDoc_STRVAR(Journal__doc__,
106 "Journal([flags][, default_call][, call_dict][,path]) -> ...\n"
107 "Journal instance\n\n"
108 "Returns instance of Journal, which allows filtering and return\n"
109 "of journal entries.\n"
110 "Argument `flags` sets open flags of the journal, which can be one\n"
111 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
112 "journal on local machine only; RUNTIME_ONLY opens only\n"
113 "volatile journal files; and SYSTEM_ONLY opens only\n"
114 "journal files of system services and the kernel.\n"
115 "Argument `default_call` must be a callable that accepts one\n"
116 "argument which is string/bytes value of a field and returns\n"
118 "Argument `call_dict` is a dictionary where the key represents\n"
119 "a field name, and value is a callable as per `default_call`.\n"
120 "A set of sane defaults for `default_call` and `call_dict` are\n"
122 "Argument `path` is the directory of journal files. Note that\n"
123 "currently flags are ignored when `path` is present as they are\n"
126 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
128 int flags=SD_JOURNAL_LOCAL_ONLY;
130 PyObject *default_call=NULL, *call_dict=NULL;
132 static char *kwlist[] = {"flags", "default_call", "call_dict", "path", NULL};
133 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iOOs", kwlist,
134 &flags, &default_call, &call_dict, &path))
138 if (PyCallable_Check(default_call) || default_call == Py_None) {
139 Py_DECREF(self->default_call);
140 self->default_call = default_call;
141 Py_INCREF(self->default_call);
143 PyErr_SetString(PyExc_TypeError, "Default call not callable");
149 if (PyDict_Check(call_dict)) {
150 Py_DECREF(self->call_dict);
151 self->call_dict = call_dict;
152 Py_INCREF(self->call_dict);
153 }else if (call_dict == Py_None) {
154 Py_DECREF(self->call_dict);
155 self->call_dict = PyDict_New();
157 PyErr_SetString(PyExc_TypeError, "Call dictionary must be dict type");
164 r = sd_journal_open_directory(&self->j, path, 0);
166 Py_BEGIN_ALLOW_THREADS
167 r = sd_journal_open(&self->j, flags);
171 PyErr_SetString(PyExc_ValueError, "Invalid flags or path");
173 }else if (r == -ENOMEM) {
174 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
177 PyErr_SetString(PyExc_RuntimeError, "Error opening journal");
185 Journal___process_field(Journal *self, PyObject *key, const void *value, ssize_t value_len)
187 PyObject *callable=NULL, *return_value=NULL;
188 if (PyDict_Check(self->call_dict))
189 callable = PyDict_GetItem(self->call_dict, key);
191 if (PyCallable_Check(callable)) {
192 #if PY_MAJOR_VERSION >=3
193 return_value = PyObject_CallFunction(callable, "y#", value, value_len);
195 return_value = PyObject_CallFunction(callable, "s#", value, value_len);
200 if (!return_value && PyCallable_Check(self->default_call))
201 #if PY_MAJOR_VERSION >=3
202 return_value = PyObject_CallFunction(self->default_call, "y#", value, value_len);
204 return_value = PyObject_CallFunction(self->default_call, "s#", value, value_len);
208 #if PY_MAJOR_VERSION >=3
209 return_value = PyBytes_FromStringAndSize(value, value_len);
211 return_value = PyString_FromStringAndSize(value, value_len);
215 return_value = Py_None;
220 PyDoc_STRVAR(Journal_get_next__doc__,
221 "get_next([skip]) -> dict\n\n"
222 "Return dictionary of the next log entry. Optional skip value will\n"
223 "return the `skip`th log entry.");
225 Journal_get_next(Journal *self, PyObject *args)
228 if (! PyArg_ParseTuple(args, "|L", &skip))
233 Py_BEGIN_ALLOW_THREADS
234 r = sd_journal_next(self->j);
236 }else if (skip == -1LL) {
237 Py_BEGIN_ALLOW_THREADS
238 r = sd_journal_previous(self->j);
240 }else if (skip > 1LL) {
241 Py_BEGIN_ALLOW_THREADS
242 r = sd_journal_next_skip(self->j, skip);
244 }else if (skip < -1LL) {
245 Py_BEGIN_ALLOW_THREADS
246 r = sd_journal_previous_skip(self->j, -skip);
249 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
254 PyErr_SetString(PyExc_RuntimeError, "Error getting next message");
256 }else if ( r == 0) { //EOF
265 const char *delim_ptr;
266 PyObject *key, *value, *cur_value, *tmp_list;
268 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
269 delim_ptr = memchr(msg, '=', msg_len);
270 #if PY_MAJOR_VERSION >=3
271 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
273 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
275 value = Journal___process_field(self, key, delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
276 if (PyDict_Contains(dict, key)) {
277 cur_value = PyDict_GetItem(dict, key);
278 if (PyList_CheckExact(cur_value) && PyList_Size(cur_value) > 1) {
279 PyList_Append(cur_value, value);
281 tmp_list = PyList_New(0);
282 PyList_Append(tmp_list, cur_value);
283 PyList_Append(tmp_list, value);
284 PyDict_SetItem(dict, key, tmp_list);
288 PyDict_SetItem(dict, key, value);
295 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
296 char realtime_str[20];
297 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
299 #if PY_MAJOR_VERSION >=3
300 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
302 key = PyString_FromString("__REALTIME_TIMESTAMP");
304 value = Journal___process_field(self, key, realtime_str, strlen(realtime_str));
305 PyDict_SetItem(dict, key, value);
312 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
313 char monotonic_str[20];
314 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
315 #if PY_MAJOR_VERSION >=3
316 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
318 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
320 value = Journal___process_field(self, key, monotonic_str, strlen(monotonic_str));
322 PyDict_SetItem(dict, key, value);
328 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
329 #if PY_MAJOR_VERSION >=3
330 key = PyUnicode_FromString("__CURSOR");
332 key = PyString_FromString("__CURSOR");
334 value = Journal___process_field(self, key, cursor, strlen(cursor));
335 PyDict_SetItem(dict, key, value);
344 PyDoc_STRVAR(Journal_get_previous__doc__,
345 "get_previous([skip]) -> dict\n\n"
346 "Return dictionary of the previous log entry. Optional skip value\n"
347 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
349 Journal_get_previous(Journal *self, PyObject *args)
352 if (! PyArg_ParseTuple(args, "|L", &skip))
355 PyObject *dict, *arg;
356 arg = Py_BuildValue("(L)", -skip);
357 dict = Journal_get_next(self, arg);
362 PyDoc_STRVAR(Journal_add_match__doc__,
363 "add_match(match, ..., field=value, ...) -> None\n\n"
364 "Add a match to filter journal log entries. All matches of different\n"
365 "field are combined in logical AND, and matches of the same field\n"
366 "are automatically combined in logical OR.\n"
367 "Matches can be passed as strings \"field=value\", or keyword\n"
368 "arguments field=\"value\".");
370 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
372 Py_ssize_t arg_match_len;
375 for (i = 0; i < PySequence_Size(args); i++) {
376 #if PY_MAJOR_VERSION >=3
378 arg = PySequence_Fast_GET_ITEM(args, i);
379 if (PyUnicode_Check(arg)) {
380 #if PY_MINOR_VERSION >=3
381 arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
384 temp = PyUnicode_AsUTF8String(arg);
385 PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
388 }else if (PyBytes_Check(arg)) {
389 PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
391 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
394 PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
396 if (PyErr_Occurred())
398 r = sd_journal_add_match(self->j, arg_match, arg_match_len);
400 PyErr_SetString(PyExc_ValueError, "Invalid match");
402 }else if (r == -ENOMEM) {
403 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
406 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
414 PyObject *key, *value;
415 Py_ssize_t pos=0, match_key_len, match_value_len;
417 char *match_key, *match_value;
419 while (PyDict_Next(keywds, &pos, &key, &value)) {
420 #if PY_MAJOR_VERSION >=3
421 if (PyUnicode_Check(key)) {
422 #if PY_MINOR_VERSION >=3
423 match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
426 temp2 = PyUnicode_AsUTF8String(key);
427 PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
430 }else if (PyBytes_Check(key)) {
431 PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
433 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
435 if (PyUnicode_Check(value)) {
436 #if PY_MINOR_VERSION >=3
437 match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
440 temp3 = PyUnicode_AsUTF8String(value);
441 PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
444 }else if (PyBytes_Check(value)) {
445 PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
447 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
450 PyString_AsStringAndSize(key, &match_key, &match_key_len);
451 PyString_AsStringAndSize(value, &match_value, &match_value_len);
453 if (PyErr_Occurred())
456 match_len = match_key_len + 1 + match_value_len;
457 match = malloc(match_len);
458 memcpy(match, match_key, match_key_len);
459 memcpy(match + match_key_len, "=", 1);
460 memcpy(match + match_key_len + 1, match_value, match_value_len);
462 r = sd_journal_add_match(self->j, match, match_len);
465 PyErr_SetString(PyExc_ValueError, "Invalid match");
467 }else if (r == -ENOMEM) {
468 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
471 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
479 PyDoc_STRVAR(Journal_add_disjunction__doc__,
480 "add_disjunction() -> None\n\n"
481 "Once called, all matches before and after are combined in logical\n"
484 Journal_add_disjunction(Journal *self, PyObject *args)
487 r = sd_journal_add_disjunction(self->j);
489 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
492 PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction");
498 PyDoc_STRVAR(Journal_flush_matches__doc__,
499 "flush_matches() -> None\n\n"
500 "Clears all current match filters.");
502 Journal_flush_matches(Journal *self, PyObject *args)
504 sd_journal_flush_matches(self->j);
508 PyDoc_STRVAR(Journal_seek__doc__,
509 "seek(offset[, whence]) -> None\n\n"
510 "Seek through journal by `offset` number of entries. Argument\n"
511 "`whence` defines what the offset is relative to:\n"
512 "os.SEEK_SET (default) from first match in journal;\n"
513 "os.SEEK_CUR from current position in journal;\n"
514 "and os.SEEK_END is from last match in journal.");
516 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
520 static char *kwlist[] = {"offset", "whence", NULL};
522 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
527 if (whence == SEEK_SET){
529 Py_BEGIN_ALLOW_THREADS
530 r = sd_journal_seek_head(self->j);
533 PyErr_SetString(PyExc_RuntimeError, "Error seeking to head");
537 arg = Py_BuildValue("(L)", offset);
538 Py_DECREF(Journal_get_next(self, arg));
541 }else if (whence == SEEK_CUR){
542 arg = Py_BuildValue("(L)", offset);
543 Py_DECREF(Journal_get_next(self, arg));
545 }else if (whence == SEEK_END){
547 Py_BEGIN_ALLOW_THREADS
548 r = sd_journal_seek_tail(self->j);
551 PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail");
554 arg = Py_BuildValue("(L)", -1LL);
555 Py_DECREF(Journal_get_next(self, arg));
558 arg = Py_BuildValue("(L)", offset);
559 Py_DECREF(Journal_get_next(self, arg));
563 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
569 PyDoc_STRVAR(Journal_seek_realtime__doc__,
570 "seek_realtime(realtime) -> None\n\n"
571 "Seek to nearest matching journal entry to `realtime`. Argument\n"
572 "`realtime` can be an integer unix timestamp in usecs or a "
573 "datetime instance.");
575 Journal_seek_realtime(Journal *self, PyObject *args)
578 if (! PyArg_ParseTuple(args, "O", &arg))
581 uint64_t timestamp=-1LL;
582 if (PyDateTime_Check(arg)) {
585 temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
586 #if PY_MAJOR_VERSION >=3
588 temp2 = PyUnicode_AsUTF8String(temp);
589 timestamp_str = PyBytes_AsString(temp2);
592 timestamp_str = PyString_AsString(temp);
595 timestamp = strtoull(timestamp_str, NULL, 10);
596 }else if (PyLong_Check(arg)) {
597 timestamp = PyLong_AsUnsignedLongLong(arg);
598 #if PY_MAJOR_VERSION <3
599 }else if (PyInt_Check(arg)) {
600 timestamp = PyInt_AsUnsignedLongLongMask(arg);
603 if ((int64_t) timestamp < 0LL) {
604 PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
609 Py_BEGIN_ALLOW_THREADS
610 r = sd_journal_seek_realtime_usec(self->j, timestamp);
613 PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
619 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
620 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
621 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
622 "`monotonic` is an timestamp from boot in secs, or a\n"
623 "timedelta instance.\n"
624 "Argument `bootid` is a string representing which boot the\n"
625 "monotonic time is reference to. Defaults to current bootid.");
627 Journal_seek_monotonic(Journal *self, PyObject *args)
631 if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
634 uint64_t timestamp=-1LL;
635 if PyDelta_Check(arg) {
637 temp = PyObject_CallMethod(arg, "total_seconds", NULL);
638 timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
640 }else if (PyFloat_Check(arg)) {
641 timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
642 }else if (PyLong_Check(arg)) {
643 timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
644 #if PY_MAJOR_VERSION <3
645 }else if (PyInt_Check(arg)) {
646 timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
651 if ((int64_t) timestamp < 0LL) {
652 PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
659 r = sd_id128_from_string(bootid, &sd_id);
661 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
664 PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
668 r = sd_id128_get_boot(&sd_id);
670 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
673 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
678 Py_BEGIN_ALLOW_THREADS
679 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
682 PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
688 PyDoc_STRVAR(Journal_wait__doc__,
689 "wait([timeout]) -> Change state (integer)\n\n"
690 "Waits until there is a change in the journal. Argument `timeout`\n"
691 "is the maximum number of seconds to wait before returning\n"
692 "regardless if journal has changed. If `timeout` is not given or is\n"
693 "0, then it will block forever.\n"
694 "Will return constants: NOP if no change; APPEND if new\n"
695 "entries have been added to the end of the journal; and\n"
696 "INVALIDATE if journal files have been added or removed.");
698 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
701 if (! PyArg_ParseTuple(args, "|L", &timeout))
705 if ( timeout == 0LL) {
706 Py_BEGIN_ALLOW_THREADS
707 r = sd_journal_wait(self->j, (uint64_t) -1);
710 Py_BEGIN_ALLOW_THREADS
711 r = sd_journal_wait(self->j, timeout * 1E6);
714 #if PY_MAJOR_VERSION >=3
715 return PyLong_FromLong(r);
717 return PyInt_FromLong(r);
721 PyDoc_STRVAR(Journal_seek_cursor__doc__,
722 "seek_cursor(cursor) -> None\n\n"
723 "Seeks to journal entry by given unique reference `cursor`.");
725 Journal_seek_cursor(Journal *self, PyObject *args)
728 if (! PyArg_ParseTuple(args, "s", &cursor))
732 Py_BEGIN_ALLOW_THREADS
733 r = sd_journal_seek_cursor(self->j, cursor);
736 PyErr_SetString(PyExc_ValueError, "Invalid cursor");
738 }else if (r == -ENOMEM) {
739 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
742 PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
749 Journal_iter(PyObject *self)
756 Journal_iternext(PyObject *self)
758 Journal *iter = (Journal *)self;
759 PyObject *dict, *arg;
760 Py_ssize_t dict_size;
762 arg = Py_BuildValue("()");
763 dict = Journal_get_next(iter, arg);
765 dict_size = PyDict_Size(dict);
766 if ((int64_t) dict_size > 0LL) {
770 PyErr_SetNone(PyExc_StopIteration);
775 #ifdef SD_JOURNAL_FOREACH_UNIQUE
776 PyDoc_STRVAR(Journal_query_unique__doc__,
777 "query_unique(field) -> a set of values\n\n"
778 "Returns a set of unique values in journal for given `field`.\n"
779 "Note this does not respect any journal matches.");
781 Journal_query_unique(Journal *self, PyObject *args)
784 if (! PyArg_ParseTuple(args, "s", &query))
788 Py_BEGIN_ALLOW_THREADS
789 r = sd_journal_query_unique(self->j, query);
792 PyErr_SetString(PyExc_ValueError, "Invalid field name");
794 } else if (r == -ENOMEM) {
795 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
798 PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
804 const char *delim_ptr;
805 PyObject *value_set, *key, *value;
806 value_set = PySet_New(0);
808 #if PY_MAJOR_VERSION >=3
809 key = PyUnicode_FromString(query);
811 key = PyString_FromString(query);
814 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
815 delim_ptr = memchr(uniq, '=', uniq_len);
816 value = Journal___process_field(self, key, delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
817 PySet_Add(value_set, value);
823 #endif //def SD_JOURNAL_FOREACH_UNIQUE
825 PyDoc_STRVAR(Journal_log_level__doc__,
826 "log_level(level) -> None\n\n"
827 "Sets maximum log level by setting matches for PRIORITY.");
829 Journal_log_level(Journal *self, PyObject *args)
832 if (! PyArg_ParseTuple(args, "i", &level))
835 if (level < 0 || level > 7) {
836 PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7");
841 PyObject *arg, *keywds;
842 for(i = 0; i <= level; i++) {
843 sprintf(level_str, "%i", i);
844 arg = PyTuple_New(0);
845 keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str);
846 Journal_add_match(self, arg, keywds);
849 if (PyErr_Occurred())
855 PyDoc_STRVAR(Journal_this_boot__doc__,
856 "this_boot() -> None\n\n"
857 "Sets match filter for the current _BOOT_ID.");
859 Journal_this_boot(Journal *self, PyObject *args)
863 r = sd_id128_get_boot(&sd_id);
865 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
868 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
873 sd_id128_to_string(sd_id, bootid);
875 PyObject *arg, *keywds;
876 arg = PyTuple_New(0);
877 keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
878 Journal_add_match(self, arg, keywds);
881 if (PyErr_Occurred())
887 PyDoc_STRVAR(Journal_this_machine__doc__,
888 "this_machine() -> None\n\n"
889 "Sets match filter for the current _MACHINE_ID.");
891 Journal_this_machine(Journal *self, PyObject *args)
895 r = sd_id128_get_machine(&sd_id);
897 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
900 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
905 sd_id128_to_string(sd_id, machineid);
907 PyObject *arg, *keywds;
908 arg = PyTuple_New(0);
909 keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
910 Journal_add_match(self, arg, keywds);
913 if (PyErr_Occurred())
920 Journal_get_default_call(Journal *self, void *closure)
922 Py_INCREF(self->default_call);
923 return self->default_call;
927 Journal_set_default_call(Journal *self, PyObject *value, void *closure)
930 PyErr_SetString(PyExc_TypeError, "Cannot delete default_call");
933 if (! PyCallable_Check(value)) {
934 PyErr_SetString(PyExc_TypeError, "default_call must be callable");
937 Py_DECREF(self->default_call);
939 self->default_call = value;
945 Journal_get_call_dict(Journal *self, void *closure)
947 Py_INCREF(self->call_dict);
948 return self->call_dict;
952 Journal_set_call_dict(Journal *self, PyObject *value, void *closure)
955 PyErr_SetString(PyExc_TypeError, "Cannot delete call_dict");
958 if (! PyDict_Check(value)) {
959 PyErr_SetString(PyExc_TypeError, "call_dict must be dict type");
962 Py_DECREF(self->call_dict);
964 self->call_dict = value;
970 Journal_get_data_threshold(Journal *self, void *closure)
976 r = sd_journal_get_data_threshold(self->j, &cvalue);
978 PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
982 #if PY_MAJOR_VERSION >=3
983 value = PyLong_FromSize_t(cvalue);
985 value = PyInt_FromSize_t(cvalue);
991 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
994 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
997 #if PY_MAJOR_VERSION >=3
998 if (! PyLong_Check(value)){
1000 if (! PyInt_Check(value)){
1002 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
1006 #if PY_MAJOR_VERSION >=3
1007 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
1009 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
1012 PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
1018 static PyGetSetDef Journal_getseters[] = {
1020 (getter)Journal_get_data_threshold,
1021 (setter)Journal_set_data_threshold,
1025 (getter)Journal_get_call_dict,
1026 (setter)Journal_set_call_dict,
1027 "dictionary of calls for each field",
1030 (getter)Journal_get_default_call,
1031 (setter)Journal_set_default_call,
1032 "default call for values for fields",
1037 static PyMethodDef Journal_methods[] = {
1038 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
1039 Journal_get_next__doc__},
1040 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
1041 Journal_get_previous__doc__},
1042 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
1043 Journal_add_match__doc__},
1044 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
1045 Journal_add_disjunction__doc__},
1046 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
1047 Journal_flush_matches__doc__},
1048 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
1049 Journal_seek__doc__},
1050 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
1051 Journal_seek_realtime__doc__},
1052 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
1053 Journal_seek_monotonic__doc__},
1054 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
1055 Journal_wait__doc__},
1056 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
1057 Journal_seek_cursor__doc__},
1058 #ifdef SD_JOURNAL_FOREACH_UNIQUE
1059 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
1060 Journal_query_unique__doc__},
1062 {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS,
1063 Journal_log_level__doc__},
1064 {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
1065 Journal_this_boot__doc__},
1066 {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
1067 Journal_this_machine__doc__},
1068 {NULL} /* Sentinel */
1071 static PyTypeObject JournalType = {
1072 PyVarObject_HEAD_INIT(NULL, 0)
1073 "_reader.Journal", /*tp_name*/
1074 sizeof(Journal), /*tp_basicsize*/
1076 (destructor)Journal_dealloc, /*tp_dealloc*/
1083 0, /*tp_as_sequence*/
1084 0, /*tp_as_mapping*/
1091 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
1092 Journal__doc__, /* tp_doc */
1093 0, /* tp_traverse */
1095 0, /* tp_richcompare */
1096 0, /* tp_weaklistoffset */
1097 Journal_iter, /* tp_iter */
1098 Journal_iternext, /* tp_iternext */
1099 Journal_methods, /* tp_methods */
1101 Journal_getseters, /* tp_getset */
1104 0, /* tp_descr_get */
1105 0, /* tp_descr_set */
1106 0, /* tp_dictoffset */
1107 (initproc)Journal_init, /* tp_init */
1109 Journal_new, /* tp_new */
1112 #if PY_MAJOR_VERSION >= 3
1113 static PyModuleDef _reader_module = {
1114 PyModuleDef_HEAD_INIT,
1116 "Module that reads systemd journal similar to journalctl.",
1118 NULL, NULL, NULL, NULL, NULL
1123 #if PY_MAJOR_VERSION >= 3
1124 PyInit__reader(void)
1133 if (PyType_Ready(&JournalType) < 0)
1134 #if PY_MAJOR_VERSION >= 3
1140 #if PY_MAJOR_VERSION >= 3
1141 m = PyModule_Create(&_reader_module);
1145 m = Py_InitModule3("_reader", NULL,
1146 "Module that reads systemd journal similar to journalctl.");
1151 Py_INCREF(&JournalType);
1152 PyModule_AddObject(m, "Journal", (PyObject *)&JournalType);
1153 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
1154 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
1155 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
1156 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
1157 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
1158 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
1160 #if PY_MAJOR_VERSION >= 3