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>
29 static PyTypeObject JournalType;
32 Journal_dealloc(Journal* self)
34 sd_journal_close(self->j);
35 Py_TYPE(self)->tp_free((PyObject*)self);
38 PyDoc_STRVAR(Journal__doc__,
39 "Journal([flags][,path]) -> ...\n"
40 "Journal instance\n\n"
41 "Returns instance of Journal, which allows filtering and return\n"
42 "of journal entries.\n"
43 "Argument `flags` sets open flags of the journal, which can be one\n"
44 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
45 "journal on local machine only; RUNTIME_ONLY opens only\n"
46 "volatile journal files; and SYSTEM_ONLY opens only\n"
47 "journal files of system services and the kernel.\n"
48 "Argument `path` is the directory of journal files. Note that\n"
49 "currently flags are ignored when `path` is present as they are\n"
52 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
54 int flags=SD_JOURNAL_LOCAL_ONLY;
57 static char *kwlist[] = {"flags", NULL};
58 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
64 r = sd_journal_open_directory(&self->j, path, 0);
66 Py_BEGIN_ALLOW_THREADS
67 r = sd_journal_open(&self->j, flags);
71 PyErr_SetString(PyExc_ValueError, "Invalid flags or path");
73 }else if (r == -ENOMEM) {
74 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
77 PyErr_SetString(PyExc_RuntimeError, "Error opening journal");
84 PyDoc_STRVAR(Journal_get_next__doc__,
85 "get_next([skip]) -> dict\n\n"
86 "Return dictionary of the next log entry. Optional skip value will\n"
87 "return the `skip`th log entry.");
89 Journal_get_next(Journal *self, PyObject *args)
92 if (! PyArg_ParseTuple(args, "|L", &skip))
97 Py_BEGIN_ALLOW_THREADS
98 r = sd_journal_next(self->j);
100 }else if (skip == -1LL) {
101 Py_BEGIN_ALLOW_THREADS
102 r = sd_journal_previous(self->j);
104 }else if (skip > 1LL) {
105 Py_BEGIN_ALLOW_THREADS
106 r = sd_journal_next_skip(self->j, skip);
108 }else if (skip < -1LL) {
109 Py_BEGIN_ALLOW_THREADS
110 r = sd_journal_previous_skip(self->j, -skip);
113 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
118 PyErr_SetString(PyExc_RuntimeError, "Error getting next message");
120 }else if ( r == 0) { //EOF
129 const char *delim_ptr;
130 PyObject *key, *value, *cur_value, *tmp_list;
132 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
133 delim_ptr = memchr(msg, '=', msg_len);
134 #if PY_MAJOR_VERSION >=3
135 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
137 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
139 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
140 if (PyDict_Contains(dict, key)) {
141 cur_value = PyDict_GetItem(dict, key);
142 if (PyList_CheckExact(cur_value)) {
143 PyList_Append(cur_value, value);
145 tmp_list = PyList_New(0);
146 PyList_Append(tmp_list, cur_value);
147 PyList_Append(tmp_list, value);
148 PyDict_SetItem(dict, key, tmp_list);
152 PyDict_SetItem(dict, key, value);
159 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
160 char realtime_str[20];
161 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
163 #if PY_MAJOR_VERSION >=3
164 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
166 key = PyString_FromString("__REALTIME_TIMESTAMP");
168 value = PyBytes_FromString(realtime_str);
169 PyDict_SetItem(dict, key, value);
176 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
177 char monotonic_str[20];
178 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
179 #if PY_MAJOR_VERSION >=3
180 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
182 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
184 value = PyBytes_FromString(monotonic_str);
186 PyDict_SetItem(dict, key, value);
192 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
193 #if PY_MAJOR_VERSION >=3
194 key = PyUnicode_FromString("__CURSOR");
196 key = PyString_FromString("__CURSOR");
198 value = PyBytes_FromString(cursor);
199 PyDict_SetItem(dict, key, value);
208 PyDoc_STRVAR(Journal_get_previous__doc__,
209 "get_previous([skip]) -> dict\n\n"
210 "Return dictionary of the previous log entry. Optional skip value\n"
211 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
213 Journal_get_previous(Journal *self, PyObject *args)
216 if (! PyArg_ParseTuple(args, "|L", &skip))
219 return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
222 PyDoc_STRVAR(Journal_add_match__doc__,
223 "add_match(match, ..., field=value, ...) -> None\n\n"
224 "Add a match to filter journal log entries. All matches of different\n"
225 "field are combined in logical AND, and matches of the same field\n"
226 "are automatically combined in logical OR.\n"
227 "Matches can be passed as strings \"field=value\", or keyword\n"
228 "arguments field=\"value\".");
230 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
232 Py_ssize_t arg_match_len;
235 for (i = 0; i < PySequence_Size(args); i++) {
236 #if PY_MAJOR_VERSION >=3
238 arg = PySequence_Fast_GET_ITEM(args, i);
239 if (PyUnicode_Check(arg)) {
240 #if PY_MINOR_VERSION >=3
241 arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
244 temp = PyUnicode_AsUTF8String(arg);
245 PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
248 }else if (PyBytes_Check(arg)) {
249 PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
251 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
254 PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
256 if (PyErr_Occurred())
258 r = sd_journal_add_match(self->j, arg_match, arg_match_len);
260 PyErr_SetString(PyExc_ValueError, "Invalid match");
262 }else if (r == -ENOMEM) {
263 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
266 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
274 PyObject *key, *value;
275 Py_ssize_t pos=0, match_key_len, match_value_len;
277 char *match_key, *match_value;
279 while (PyDict_Next(keywds, &pos, &key, &value)) {
280 #if PY_MAJOR_VERSION >=3
281 if (PyUnicode_Check(key)) {
282 #if PY_MINOR_VERSION >=3
283 match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
286 temp2 = PyUnicode_AsUTF8String(key);
287 PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
290 }else if (PyBytes_Check(key)) {
291 PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
293 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
295 if (PyUnicode_Check(value)) {
296 #if PY_MINOR_VERSION >=3
297 match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
300 temp3 = PyUnicode_AsUTF8String(value);
301 PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
304 }else if (PyBytes_Check(value)) {
305 PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
307 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
310 PyString_AsStringAndSize(key, &match_key, &match_key_len);
311 PyString_AsStringAndSize(value, &match_value, &match_value_len);
313 if (PyErr_Occurred())
316 match_len = match_key_len + 1 + match_value_len;
317 match = malloc(match_len);
318 memcpy(match, match_key, match_key_len);
319 memcpy(match + match_key_len, "=", 1);
320 memcpy(match + match_key_len + 1, match_value, match_value_len);
322 r = sd_journal_add_match(self->j, match, match_len);
325 PyErr_SetString(PyExc_ValueError, "Invalid match");
327 }else if (r == -ENOMEM) {
328 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
331 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
339 PyDoc_STRVAR(Journal_add_disjunction__doc__,
340 "add_disjunction() -> None\n\n"
341 "Once called, all matches before and after are combined in logical\n"
344 Journal_add_disjunction(Journal *self, PyObject *args)
347 r = sd_journal_add_disjunction(self->j);
349 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
352 PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction");
358 PyDoc_STRVAR(Journal_flush_matches__doc__,
359 "flush_matches() -> None\n\n"
360 "Clears all current match filters.");
362 Journal_flush_matches(Journal *self, PyObject *args)
364 sd_journal_flush_matches(self->j);
368 PyDoc_STRVAR(Journal_seek__doc__,
369 "seek(offset[, whence]) -> None\n\n"
370 "Seek through journal by `offset` number of entries. Argument\n"
371 "`whence` defines what the offset is relative to:\n"
372 "os.SEEK_SET (default) from first match in journal;\n"
373 "os.SEEK_CUR from current position in journal;\n"
374 "and os.SEEK_END is from last match in journal.");
376 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
380 static char *kwlist[] = {"offset", "whence", NULL};
382 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
387 if (whence == SEEK_SET){
389 Py_BEGIN_ALLOW_THREADS
390 r = sd_journal_seek_head(self->j);
393 PyErr_SetString(PyExc_RuntimeError, "Error seeking to head");
397 Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset));
399 }else if (whence == SEEK_CUR){
400 Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset));
401 }else if (whence == SEEK_END){
403 Py_BEGIN_ALLOW_THREADS
404 r = sd_journal_seek_tail(self->j);
407 PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail");
410 Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL));
412 Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset));
415 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
421 PyDoc_STRVAR(Journal_seek_realtime__doc__,
422 "seek_realtime(realtime) -> None\n\n"
423 "Seek to nearest matching journal entry to `realtime`. Argument\n"
424 "`realtime` can be an integer unix timestamp in usecs or a "
425 "datetime instance.");
427 Journal_seek_realtime(Journal *self, PyObject *args)
430 if (! PyArg_ParseTuple(args, "O", &arg))
433 uint64_t timestamp=-1LL;
434 if (PyDateTime_Check(arg)) {
437 temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
438 #if PY_MAJOR_VERSION >=3
440 temp2 = PyUnicode_AsUTF8String(temp);
441 timestamp_str = PyBytes_AsString(temp2);
444 timestamp_str = PyString_AsString(temp);
447 timestamp = strtoull(timestamp_str, NULL, 10);
448 }else if (PyLong_Check(arg)) {
449 timestamp = PyLong_AsUnsignedLongLong(arg);
450 #if PY_MAJOR_VERSION <3
451 }else if (PyInt_Check(arg)) {
452 timestamp = PyInt_AsUnsignedLongLongMask(arg);
455 if ((int64_t) timestamp < 0LL) {
456 PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
461 Py_BEGIN_ALLOW_THREADS
462 r = sd_journal_seek_realtime_usec(self->j, timestamp);
465 PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
471 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
472 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
473 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
474 "`monotonic` is an timestamp from boot in secs, or a\n"
475 "timedelta instance.\n"
476 "Argument `bootid` is a string representing which boot the\n"
477 "monotonic time is reference to. Defaults to current bootid.");
479 Journal_seek_monotonic(Journal *self, PyObject *args)
483 if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
486 uint64_t timestamp=-1LL;
487 if PyDelta_Check(arg) {
489 temp = PyObject_CallMethod(arg, "total_seconds", NULL);
490 timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
492 }else if (PyFloat_Check(arg)) {
493 timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
494 }else if (PyLong_Check(arg)) {
495 timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
496 #if PY_MAJOR_VERSION <3
497 }else if (PyInt_Check(arg)) {
498 timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
503 if ((int64_t) timestamp < 0LL) {
504 PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
511 r = sd_id128_from_string(bootid, &sd_id);
513 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
516 PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
520 r = sd_id128_get_boot(&sd_id);
522 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
525 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
530 Py_BEGIN_ALLOW_THREADS
531 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
534 PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
540 PyDoc_STRVAR(Journal_wait__doc__,
541 "wait([timeout]) -> Change state (integer)\n\n"
542 "Waits until there is a change in the journal. Argument `timeout`\n"
543 "is the maximum number of seconds to wait before returning\n"
544 "regardless if journal has changed. If `timeout` is not given or is\n"
545 "0, then it will block forever.\n"
546 "Will return constants: NOP if no change; APPEND if new\n"
547 "entries have been added to the end of the journal; and\n"
548 "INVALIDATE if journal files have been added or removed.");
550 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
553 if (! PyArg_ParseTuple(args, "|L", &timeout))
557 if ( timeout == 0LL) {
558 Py_BEGIN_ALLOW_THREADS
559 r = sd_journal_wait(self->j, (uint64_t) -1);
562 Py_BEGIN_ALLOW_THREADS
563 r = sd_journal_wait(self->j, timeout * 1E6);
566 #if PY_MAJOR_VERSION >=3
567 return PyLong_FromLong(r);
569 return PyInt_FromLong(r);
573 PyDoc_STRVAR(Journal_seek_cursor__doc__,
574 "seek_cursor(cursor) -> None\n\n"
575 "Seeks to journal entry by given unique reference `cursor`.");
577 Journal_seek_cursor(Journal *self, PyObject *args)
580 if (! PyArg_ParseTuple(args, "s", &cursor))
584 Py_BEGIN_ALLOW_THREADS
585 r = sd_journal_seek_cursor(self->j, cursor);
588 PyErr_SetString(PyExc_ValueError, "Invalid cursor");
590 }else if (r == -ENOMEM) {
591 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
594 PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
601 Journal_iter(PyObject *self)
608 Journal_iternext(PyObject *self)
610 PyObject *dict, *arg;
611 Py_ssize_t dict_size;
613 dict = PyObject_CallMethod(self, "get_next", "");
614 dict_size = PyDict_Size(dict);
615 if ((int64_t) dict_size > 0LL) {
619 PyErr_SetNone(PyExc_StopIteration);
624 #ifdef SD_JOURNAL_FOREACH_UNIQUE
625 PyDoc_STRVAR(Journal_query_unique__doc__,
626 "query_unique(field) -> a set of values\n\n"
627 "Returns a set of unique values in journal for given `field`.\n"
628 "Note this does not respect any journal matches.");
630 Journal_query_unique(Journal *self, PyObject *args)
633 if (! PyArg_ParseTuple(args, "s", &query))
637 Py_BEGIN_ALLOW_THREADS
638 r = sd_journal_query_unique(self->j, query);
641 PyErr_SetString(PyExc_ValueError, "Invalid field name");
643 } else if (r == -ENOMEM) {
644 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
647 PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
653 const char *delim_ptr;
654 PyObject *value_set, *key, *value;
655 value_set = PySet_New(0);
657 #if PY_MAJOR_VERSION >=3
658 key = PyUnicode_FromString(query);
660 key = PyString_FromString(query);
663 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
664 delim_ptr = memchr(uniq, '=', uniq_len);
665 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
666 PySet_Add(value_set, value);
672 #endif //def SD_JOURNAL_FOREACH_UNIQUE
674 PyDoc_STRVAR(Journal_log_level__doc__,
675 "log_level(level) -> None\n\n"
676 "Sets maximum log level by setting matches for PRIORITY.");
678 Journal_log_level(Journal *self, PyObject *args)
681 if (! PyArg_ParseTuple(args, "i", &level))
684 if (level < 0 || level > 7) {
685 PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7");
690 PyObject *arg, *keywds;
691 for(i = 0; i <= level; i++) {
692 sprintf(level_str, "%i", i);
693 arg = PyTuple_New(0);
694 keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str);
695 Journal_add_match(self, arg, keywds);
698 if (PyErr_Occurred())
704 PyDoc_STRVAR(Journal_this_boot__doc__,
705 "this_boot() -> None\n\n"
706 "Sets match filter for the current _BOOT_ID.");
708 Journal_this_boot(Journal *self, PyObject *args)
712 r = sd_id128_get_boot(&sd_id);
714 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
717 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
722 sd_id128_to_string(sd_id, bootid);
724 PyObject *arg, *keywds;
725 arg = PyTuple_New(0);
726 keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
727 Journal_add_match(self, arg, keywds);
730 if (PyErr_Occurred())
736 PyDoc_STRVAR(Journal_this_machine__doc__,
737 "this_machine() -> None\n\n"
738 "Sets match filter for the current _MACHINE_ID.");
740 Journal_this_machine(Journal *self, PyObject *args)
744 r = sd_id128_get_machine(&sd_id);
746 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
749 PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
754 sd_id128_to_string(sd_id, machineid);
756 PyObject *arg, *keywds;
757 arg = PyTuple_New(0);
758 keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
759 Journal_add_match(self, arg, keywds);
762 if (PyErr_Occurred())
769 Journal_get_data_threshold(Journal *self, void *closure)
775 r = sd_journal_get_data_threshold(self->j, &cvalue);
777 PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
781 #if PY_MAJOR_VERSION >=3
782 value = PyLong_FromSize_t(cvalue);
784 value = PyInt_FromSize_t(cvalue);
790 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
793 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
796 #if PY_MAJOR_VERSION >=3
797 if (! PyLong_Check(value)){
799 if (! PyInt_Check(value)){
801 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
805 #if PY_MAJOR_VERSION >=3
806 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
808 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
811 PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
817 static PyGetSetDef Journal_getseters[] = {
819 (getter)Journal_get_data_threshold,
820 (setter)Journal_set_data_threshold,
826 static PyMethodDef Journal_methods[] = {
827 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
828 Journal_get_next__doc__},
829 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
830 Journal_get_previous__doc__},
831 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
832 Journal_add_match__doc__},
833 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
834 Journal_add_disjunction__doc__},
835 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
836 Journal_flush_matches__doc__},
837 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
838 Journal_seek__doc__},
839 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
840 Journal_seek_realtime__doc__},
841 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
842 Journal_seek_monotonic__doc__},
843 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
844 Journal_wait__doc__},
845 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
846 Journal_seek_cursor__doc__},
847 #ifdef SD_JOURNAL_FOREACH_UNIQUE
848 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
849 Journal_query_unique__doc__},
851 {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS,
852 Journal_log_level__doc__},
853 {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
854 Journal_this_boot__doc__},
855 {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
856 Journal_this_machine__doc__},
857 {NULL} /* Sentinel */
860 static PyTypeObject JournalType = {
861 PyVarObject_HEAD_INIT(NULL, 0)
862 "_reader.Journal", /*tp_name*/
863 sizeof(Journal), /*tp_basicsize*/
865 (destructor)Journal_dealloc, /*tp_dealloc*/
872 0, /*tp_as_sequence*/
880 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
881 Journal__doc__, /* tp_doc */
884 0, /* tp_richcompare */
885 0, /* tp_weaklistoffset */
886 Journal_iter, /* tp_iter */
887 Journal_iternext, /* tp_iternext */
888 Journal_methods, /* tp_methods */
890 Journal_getseters, /* tp_getset */
893 0, /* tp_descr_get */
894 0, /* tp_descr_set */
895 0, /* tp_dictoffset */
896 (initproc)Journal_init, /* tp_init */
898 PyType_GenericNew, /* tp_new */
901 #if PY_MAJOR_VERSION >= 3
902 static PyModuleDef _reader_module = {
903 PyModuleDef_HEAD_INIT,
905 "Module that reads systemd journal similar to journalctl.",
907 NULL, NULL, NULL, NULL, NULL
912 #if PY_MAJOR_VERSION >= 3
922 if (PyType_Ready(&JournalType) < 0)
923 #if PY_MAJOR_VERSION >= 3
929 #if PY_MAJOR_VERSION >= 3
930 m = PyModule_Create(&_reader_module);
934 m = Py_InitModule3("_reader", NULL,
935 "Module that reads systemd journal similar to journalctl.");
940 Py_INCREF(&JournalType);
941 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
942 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
943 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
944 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
945 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
946 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
947 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
949 #if PY_MAJOR_VERSION >= 3