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", "path", NULL};
58 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
63 Py_BEGIN_ALLOW_THREADS
65 r = sd_journal_open_directory(&self->j, path, 0);
67 r = sd_journal_open(&self->j, flags);
72 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
73 r == -ENOMEM ? PyExc_MemoryError :
75 PyErr_SetFromErrnoWithFilename(errtype, path);
82 PyDoc_STRVAR(Journal_get_next__doc__,
83 "get_next([skip]) -> dict\n\n"
84 "Return dictionary of the next log entry. Optional skip value will\n"
85 "return the `skip`th log entry.");
87 Journal_get_next(Journal *self, PyObject *args)
90 if (! PyArg_ParseTuple(args, "|L", &skip))
94 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
99 Py_BEGIN_ALLOW_THREADS
101 r = sd_journal_next(self->j);
102 }else if (skip == -1LL) {
103 r = sd_journal_previous(self->j);
104 }else if (skip > 1LL) {
105 r = sd_journal_next_skip(self->j, skip);
106 }else if (skip < -1LL) {
107 r = sd_journal_previous_skip(self->j, -skip);
113 PyErr_SetFromErrno(PyExc_OSError);
115 }else if ( r == 0) { //EOF
124 const char *delim_ptr;
125 PyObject *key, *value, *cur_value, *tmp_list;
127 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
128 delim_ptr = memchr(msg, '=', msg_len);
129 #if PY_MAJOR_VERSION >=3
130 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
132 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
134 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
135 if (PyDict_Contains(dict, key)) {
136 cur_value = PyDict_GetItem(dict, key);
137 if (PyList_CheckExact(cur_value)) {
138 PyList_Append(cur_value, value);
140 tmp_list = PyList_New(0);
141 PyList_Append(tmp_list, cur_value);
142 PyList_Append(tmp_list, value);
143 PyDict_SetItem(dict, key, tmp_list);
147 PyDict_SetItem(dict, key, value);
154 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
155 char realtime_str[20];
156 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
158 #if PY_MAJOR_VERSION >=3
159 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
161 key = PyString_FromString("__REALTIME_TIMESTAMP");
163 value = PyBytes_FromString(realtime_str);
164 PyDict_SetItem(dict, key, value);
171 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
172 char monotonic_str[20];
173 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
174 #if PY_MAJOR_VERSION >=3
175 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
177 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
179 value = PyBytes_FromString(monotonic_str);
181 PyDict_SetItem(dict, key, value);
187 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
188 #if PY_MAJOR_VERSION >=3
189 key = PyUnicode_FromString("__CURSOR");
191 key = PyString_FromString("__CURSOR");
193 value = PyBytes_FromString(cursor);
194 PyDict_SetItem(dict, key, value);
203 PyDoc_STRVAR(Journal_get_previous__doc__,
204 "get_previous([skip]) -> dict\n\n"
205 "Return dictionary of the previous log entry. Optional skip value\n"
206 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
208 Journal_get_previous(Journal *self, PyObject *args)
211 if (! PyArg_ParseTuple(args, "|L", &skip))
214 return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
217 PyDoc_STRVAR(Journal_add_match__doc__,
218 "add_match(match, ..., field=value, ...) -> None\n\n"
219 "Add a match to filter journal log entries. All matches of different\n"
220 "field are combined in logical AND, and matches of the same field\n"
221 "are automatically combined in logical OR.\n"
222 "Matches can be passed as strings \"field=value\", or keyword\n"
223 "arguments field=\"value\".");
225 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
227 Py_ssize_t arg_match_len;
230 for (i = 0; i < PySequence_Size(args); i++) {
231 #if PY_MAJOR_VERSION >=3
233 arg = PySequence_Fast_GET_ITEM(args, i);
234 if (PyUnicode_Check(arg)) {
235 #if PY_MINOR_VERSION >=3
236 arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
239 temp = PyUnicode_AsUTF8String(arg);
240 PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
243 }else if (PyBytes_Check(arg)) {
244 PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
246 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
249 PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
251 if (PyErr_Occurred())
253 r = sd_journal_add_match(self->j, arg_match, arg_match_len);
256 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
257 r == -ENOMEM ? PyExc_MemoryError :
259 PyErr_SetFromErrno(errtype);
267 PyObject *key, *value;
268 Py_ssize_t pos=0, match_key_len, match_value_len;
270 char *match_key, *match_value;
272 while (PyDict_Next(keywds, &pos, &key, &value)) {
273 #if PY_MAJOR_VERSION >=3
274 if (PyUnicode_Check(key)) {
275 #if PY_MINOR_VERSION >=3
276 match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
279 temp2 = PyUnicode_AsUTF8String(key);
280 PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
283 }else if (PyBytes_Check(key)) {
284 PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
286 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
288 if (PyUnicode_Check(value)) {
289 #if PY_MINOR_VERSION >=3
290 match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
293 temp3 = PyUnicode_AsUTF8String(value);
294 PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
297 }else if (PyBytes_Check(value)) {
298 PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
300 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
303 PyString_AsStringAndSize(key, &match_key, &match_key_len);
304 PyString_AsStringAndSize(value, &match_value, &match_value_len);
306 if (PyErr_Occurred())
309 match_len = match_key_len + 1 + match_value_len;
310 match = malloc(match_len);
311 memcpy(match, match_key, match_key_len);
312 memcpy(match + match_key_len, "=", 1);
313 memcpy(match + match_key_len + 1, match_value, match_value_len);
315 r = sd_journal_add_match(self->j, match, match_len);
318 PyErr_SetString(PyExc_ValueError, "Invalid match");
320 }else if (r == -ENOMEM) {
321 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
324 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
332 PyDoc_STRVAR(Journal_add_disjunction__doc__,
333 "add_disjunction() -> None\n\n"
334 "Once called, all matches before and after are combined in logical\n"
337 Journal_add_disjunction(Journal *self, PyObject *args)
340 r = sd_journal_add_disjunction(self->j);
343 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
345 PyErr_SetFromErrno(errtype);
351 PyDoc_STRVAR(Journal_flush_matches__doc__,
352 "flush_matches() -> None\n\n"
353 "Clears all current match filters.");
355 Journal_flush_matches(Journal *self, PyObject *args)
357 sd_journal_flush_matches(self->j);
361 PyDoc_STRVAR(Journal_seek__doc__,
362 "seek(offset[, whence]) -> None\n\n"
363 "Seek through journal by `offset` number of entries. Argument\n"
364 "`whence` defines what the offset is relative to:\n"
365 "os.SEEK_SET (default) from first match in journal;\n"
366 "os.SEEK_CUR from current position in journal;\n"
367 "and os.SEEK_END is from last match in journal.");
369 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
373 static char *kwlist[] = {"offset", "whence", NULL};
375 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
379 PyObject *result=NULL;
380 if (whence == SEEK_SET){
382 Py_BEGIN_ALLOW_THREADS
383 r = sd_journal_seek_head(self->j);
387 PyErr_SetFromErrno(PyExc_OSError);
391 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
393 }else if (whence == SEEK_CUR){
394 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
395 }else if (whence == SEEK_END){
397 Py_BEGIN_ALLOW_THREADS
398 r = sd_journal_seek_tail(self->j);
402 PyErr_SetFromErrno(PyExc_OSError);
406 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
408 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
411 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
416 if (PyErr_Occurred())
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, "K", ×tamp))
433 if ((int64_t) timestamp < 0LL) {
434 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
439 Py_BEGIN_ALLOW_THREADS
440 r = sd_journal_seek_realtime_usec(self->j, timestamp);
444 PyErr_SetFromErrno(PyExc_OSError);
450 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
451 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
452 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
453 "`monotonic` is an timestamp from boot in secs, or a\n"
454 "timedelta instance.\n"
455 "Argument `bootid` is a string representing which boot the\n"
456 "monotonic time is reference to. Defaults to current bootid.");
458 Journal_seek_monotonic(Journal *self, PyObject *args)
462 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
466 timestamp = (uint64_t) (timedouble * 1.0E6);
468 if ((int64_t) timestamp < 0LL) {
469 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
476 r = sd_id128_from_string(bootid, &sd_id);
478 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
482 PyErr_SetFromErrno(PyExc_OSError);
486 r = sd_id128_get_boot(&sd_id);
488 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
492 PyErr_SetFromErrno(PyExc_OSError);
497 Py_BEGIN_ALLOW_THREADS
498 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
502 PyErr_SetFromErrno(PyExc_OSError);
508 PyDoc_STRVAR(Journal_wait__doc__,
509 "wait([timeout]) -> Change state (integer)\n\n"
510 "Waits until there is a change in the journal. Argument `timeout`\n"
511 "is the maximum number of seconds to wait before returning\n"
512 "regardless if journal has changed. If `timeout` is not given or is\n"
513 "0, then it will block forever.\n"
514 "Will return constants: NOP if no change; APPEND if new\n"
515 "entries have been added to the end of the journal; and\n"
516 "INVALIDATE if journal files have been added or removed.");
518 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
521 if (! PyArg_ParseTuple(args, "|L", &timeout))
525 Py_BEGIN_ALLOW_THREADS
526 if ( timeout == 0LL) {
527 r = sd_journal_wait(self->j, (uint64_t) -1);
529 r = sd_journal_wait(self->j, timeout * 1E6);
534 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
536 PyErr_SetFromErrno(errtype);
539 #if PY_MAJOR_VERSION >=3
540 return PyLong_FromLong(r);
542 return PyInt_FromLong(r);
546 PyDoc_STRVAR(Journal_seek_cursor__doc__,
547 "seek_cursor(cursor) -> None\n\n"
548 "Seeks to journal entry by given unique reference `cursor`.");
550 Journal_seek_cursor(Journal *self, PyObject *args)
553 if (! PyArg_ParseTuple(args, "s", &cursor))
557 Py_BEGIN_ALLOW_THREADS
558 r = sd_journal_seek_cursor(self->j, cursor);
562 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
563 r == -ENOMEM ? PyExc_MemoryError :
565 PyErr_SetFromErrno(errtype);
572 Journal_iter(PyObject *self)
579 Journal_iternext(PyObject *self)
582 Py_ssize_t dict_size;
584 dict = PyObject_CallMethod(self, "get_next", "");
585 dict_size = PyDict_Size(dict);
586 if ((int64_t) dict_size > 0LL) {
590 PyErr_SetNone(PyExc_StopIteration);
595 #ifdef SD_JOURNAL_FOREACH_UNIQUE
596 PyDoc_STRVAR(Journal_query_unique__doc__,
597 "query_unique(field) -> a set of values\n\n"
598 "Returns a set of unique values in journal for given `field`.\n"
599 "Note this does not respect any journal matches.");
601 Journal_query_unique(Journal *self, PyObject *args)
604 if (! PyArg_ParseTuple(args, "s", &query))
608 Py_BEGIN_ALLOW_THREADS
609 r = sd_journal_query_unique(self->j, query);
613 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
614 r == -ENOMEM ? PyExc_MemoryError :
616 PyErr_SetFromErrno(errtype);
622 const char *delim_ptr;
623 PyObject *value_set, *key, *value;
624 value_set = PySet_New(0);
626 #if PY_MAJOR_VERSION >=3
627 key = PyUnicode_FromString(query);
629 key = PyString_FromString(query);
632 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
633 delim_ptr = memchr(uniq, '=', uniq_len);
634 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
635 PySet_Add(value_set, value);
641 #endif //def SD_JOURNAL_FOREACH_UNIQUE
644 Journal_get_data_threshold(Journal *self, void *closure)
650 r = sd_journal_get_data_threshold(self->j, &cvalue);
653 PyErr_SetFromErrno(PyExc_OSError);
657 #if PY_MAJOR_VERSION >=3
658 value = PyLong_FromSize_t(cvalue);
660 value = PyInt_FromSize_t(cvalue);
666 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
669 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
672 #if PY_MAJOR_VERSION >=3
673 if (! PyLong_Check(value)){
675 if (! PyInt_Check(value)){
677 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
681 #if PY_MAJOR_VERSION >=3
682 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
684 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
688 PyErr_SetFromErrno(PyExc_OSError);
694 static PyGetSetDef Journal_getseters[] = {
696 (getter)Journal_get_data_threshold,
697 (setter)Journal_set_data_threshold,
703 static PyMethodDef Journal_methods[] = {
704 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
705 Journal_get_next__doc__},
706 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
707 Journal_get_previous__doc__},
708 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
709 Journal_add_match__doc__},
710 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
711 Journal_add_disjunction__doc__},
712 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
713 Journal_flush_matches__doc__},
714 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
715 Journal_seek__doc__},
716 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
717 Journal_seek_realtime__doc__},
718 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
719 Journal_seek_monotonic__doc__},
720 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
721 Journal_wait__doc__},
722 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
723 Journal_seek_cursor__doc__},
724 #ifdef SD_JOURNAL_FOREACH_UNIQUE
725 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
726 Journal_query_unique__doc__},
728 {NULL} /* Sentinel */
731 static PyTypeObject JournalType = {
732 PyVarObject_HEAD_INIT(NULL, 0)
733 "_reader.Journal", /*tp_name*/
734 sizeof(Journal), /*tp_basicsize*/
736 (destructor)Journal_dealloc, /*tp_dealloc*/
743 0, /*tp_as_sequence*/
751 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
752 Journal__doc__, /* tp_doc */
755 0, /* tp_richcompare */
756 0, /* tp_weaklistoffset */
757 Journal_iter, /* tp_iter */
758 Journal_iternext, /* tp_iternext */
759 Journal_methods, /* tp_methods */
761 Journal_getseters, /* tp_getset */
764 0, /* tp_descr_get */
765 0, /* tp_descr_set */
766 0, /* tp_dictoffset */
767 (initproc)Journal_init, /* tp_init */
769 PyType_GenericNew, /* tp_new */
772 #if PY_MAJOR_VERSION >= 3
773 static PyModuleDef _reader_module = {
774 PyModuleDef_HEAD_INIT,
776 "Module that reads systemd journal similar to journalctl.",
778 NULL, NULL, NULL, NULL, NULL
783 #if PY_MAJOR_VERSION >= 3
793 if (PyType_Ready(&JournalType) < 0)
794 #if PY_MAJOR_VERSION >= 3
800 #if PY_MAJOR_VERSION >= 3
801 m = PyModule_Create(&_reader_module);
805 m = Py_InitModule3("_reader", NULL,
806 "Module that reads systemd journal similar to journalctl.");
811 Py_INCREF(&JournalType);
812 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
813 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
814 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
815 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
816 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
817 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
818 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
820 #if PY_MAJOR_VERSION >= 3