1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Steven Hiscocks, Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <systemd/sd-journal.h>
24 #include <structmember.h>
31 static PyTypeObject JournalType;
34 Journal_dealloc(Journal* self)
36 sd_journal_close(self->j);
37 Py_TYPE(self)->tp_free((PyObject*)self);
40 PyDoc_STRVAR(Journal__doc__,
41 "Journal([flags][,path]) -> ...\n"
42 "Journal instance\n\n"
43 "Returns instance of Journal, which allows filtering and return\n"
44 "of journal entries.\n"
45 "Argument `flags` sets open flags of the journal, which can be one\n"
46 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
47 "journal on local machine only; RUNTIME_ONLY opens only\n"
48 "volatile journal files; and SYSTEM_ONLY opens only\n"
49 "journal files of system services and the kernel.\n"
50 "Argument `path` is the directory of journal files. Note that\n"
51 "currently flags are ignored when `path` is present as they are\n"
54 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
56 int flags=SD_JOURNAL_LOCAL_ONLY;
59 static char *kwlist[] = {"flags", "path", NULL};
60 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
65 Py_BEGIN_ALLOW_THREADS
67 r = sd_journal_open_directory(&self->j, path, 0);
69 r = sd_journal_open(&self->j, flags);
74 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
75 r == -ENOMEM ? PyExc_MemoryError :
77 PyErr_SetFromErrnoWithFilename(errtype, path);
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))
96 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
101 Py_BEGIN_ALLOW_THREADS
103 r = sd_journal_next(self->j);
104 }else if (skip == -1LL) {
105 r = sd_journal_previous(self->j);
106 }else if (skip > 1LL) {
107 r = sd_journal_next_skip(self->j, skip);
108 }else if (skip < -1LL) {
109 r = sd_journal_previous_skip(self->j, -skip);
115 PyErr_SetFromErrno(PyExc_OSError);
117 }else if ( r == 0) { //EOF
126 const char *delim_ptr;
127 PyObject *key, *value, *cur_value, *tmp_list;
129 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
130 delim_ptr = memchr(msg, '=', msg_len);
131 #if PY_MAJOR_VERSION >=3
132 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
134 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
136 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
137 if (PyDict_Contains(dict, key)) {
138 cur_value = PyDict_GetItem(dict, key);
139 if (PyList_CheckExact(cur_value)) {
140 PyList_Append(cur_value, value);
142 tmp_list = PyList_New(0);
143 PyList_Append(tmp_list, cur_value);
144 PyList_Append(tmp_list, value);
145 PyDict_SetItem(dict, key, tmp_list);
149 PyDict_SetItem(dict, key, value);
156 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
157 char realtime_str[20];
158 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
160 #if PY_MAJOR_VERSION >=3
161 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
163 key = PyString_FromString("__REALTIME_TIMESTAMP");
165 value = PyBytes_FromString(realtime_str);
166 PyDict_SetItem(dict, key, value);
173 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
174 char monotonic_str[20];
175 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
176 #if PY_MAJOR_VERSION >=3
177 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
179 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
181 value = PyBytes_FromString(monotonic_str);
183 PyDict_SetItem(dict, key, value);
189 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
190 #if PY_MAJOR_VERSION >=3
191 key = PyUnicode_FromString("__CURSOR");
193 key = PyString_FromString("__CURSOR");
195 value = PyBytes_FromString(cursor);
196 PyDict_SetItem(dict, key, value);
205 PyDoc_STRVAR(Journal_get_previous__doc__,
206 "get_previous([skip]) -> dict\n\n"
207 "Return dictionary of the previous log entry. Optional skip value\n"
208 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
210 Journal_get_previous(Journal *self, PyObject *args)
213 if (! PyArg_ParseTuple(args, "|L", &skip))
216 return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
219 PyDoc_STRVAR(Journal_add_match__doc__,
220 "add_match(match) -> None\n\n"
221 "Add a match to filter journal log entries. All matches of different\n"
222 "fields are combined in logical AND, and matches of the same field\n"
223 "are automatically combined in logical OR.\n"
224 "Match is string of form \"field=value\".");
226 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
230 if (! PyArg_ParseTuple(args, "s#", &match, &match_len))
234 r = sd_journal_add_match(self->j, match, match_len);
237 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
238 r == -ENOMEM ? PyExc_MemoryError :
240 PyErr_SetFromErrno(errtype);
247 PyDoc_STRVAR(Journal_add_disjunction__doc__,
248 "add_disjunction() -> None\n\n"
249 "Once called, all matches before and after are combined in logical\n"
252 Journal_add_disjunction(Journal *self, PyObject *args)
255 r = sd_journal_add_disjunction(self->j);
258 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
260 PyErr_SetFromErrno(errtype);
266 PyDoc_STRVAR(Journal_flush_matches__doc__,
267 "flush_matches() -> None\n\n"
268 "Clears all current match filters.");
270 Journal_flush_matches(Journal *self, PyObject *args)
272 sd_journal_flush_matches(self->j);
276 PyDoc_STRVAR(Journal_seek__doc__,
277 "seek(offset[, whence]) -> None\n\n"
278 "Seek through journal by `offset` number of entries. Argument\n"
279 "`whence` defines what the offset is relative to:\n"
280 "os.SEEK_SET (default) from first match in journal;\n"
281 "os.SEEK_CUR from current position in journal;\n"
282 "and os.SEEK_END is from last match in journal.");
284 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
288 static char *kwlist[] = {"offset", "whence", NULL};
290 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
294 PyObject *result=NULL;
295 if (whence == SEEK_SET){
297 Py_BEGIN_ALLOW_THREADS
298 r = sd_journal_seek_head(self->j);
302 PyErr_SetFromErrno(PyExc_OSError);
306 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
308 }else if (whence == SEEK_CUR){
309 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
310 }else if (whence == SEEK_END){
312 Py_BEGIN_ALLOW_THREADS
313 r = sd_journal_seek_tail(self->j);
317 PyErr_SetFromErrno(PyExc_OSError);
321 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
323 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
326 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
331 if (PyErr_Occurred())
336 PyDoc_STRVAR(Journal_seek_realtime__doc__,
337 "seek_realtime(realtime) -> None\n\n"
338 "Seek to nearest matching journal entry to `realtime`. Argument\n"
339 "`realtime` can must be an integer unix timestamp in usecs.");
341 Journal_seek_realtime(Journal *self, PyObject *args)
344 if (! PyArg_ParseTuple(args, "K", ×tamp))
347 if ((int64_t) timestamp < 0LL) {
348 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
353 Py_BEGIN_ALLOW_THREADS
354 r = sd_journal_seek_realtime_usec(self->j, timestamp);
358 PyErr_SetFromErrno(PyExc_OSError);
364 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
365 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
366 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
367 "`monotonic` is an timestamp from boot in seconds.\n"
368 "Argument `bootid` is a string representing which boot the\n"
369 "monotonic time is reference to. Defaults to current bootid.");
371 Journal_seek_monotonic(Journal *self, PyObject *args)
375 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
379 timestamp = (uint64_t) (timedouble * 1.0E6);
381 if ((int64_t) timestamp < 0LL) {
382 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
389 r = sd_id128_from_string(bootid, &sd_id);
391 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
395 PyErr_SetFromErrno(PyExc_OSError);
399 r = sd_id128_get_boot(&sd_id);
401 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
405 PyErr_SetFromErrno(PyExc_OSError);
410 Py_BEGIN_ALLOW_THREADS
411 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
415 PyErr_SetFromErrno(PyExc_OSError);
421 PyDoc_STRVAR(Journal_wait__doc__,
422 "wait([timeout]) -> Change state (integer)\n\n"
423 "Waits until there is a change in the journal. Argument `timeout`\n"
424 "is the maximum number of seconds to wait before returning\n"
425 "regardless if journal has changed. If `timeout` is not given or is\n"
426 "0, then it will block forever.\n"
427 "Will return constants: NOP if no change; APPEND if new\n"
428 "entries have been added to the end of the journal; and\n"
429 "INVALIDATE if journal files have been added or removed.");
431 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
434 if (! PyArg_ParseTuple(args, "|L", &timeout))
438 Py_BEGIN_ALLOW_THREADS
439 if ( timeout == 0LL) {
440 r = sd_journal_wait(self->j, (uint64_t) -1);
442 r = sd_journal_wait(self->j, timeout * 1E6);
447 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
449 PyErr_SetFromErrno(errtype);
452 #if PY_MAJOR_VERSION >=3
453 return PyLong_FromLong(r);
455 return PyInt_FromLong(r);
459 PyDoc_STRVAR(Journal_seek_cursor__doc__,
460 "seek_cursor(cursor) -> None\n\n"
461 "Seeks to journal entry by given unique reference `cursor`.");
463 Journal_seek_cursor(Journal *self, PyObject *args)
466 if (! PyArg_ParseTuple(args, "s", &cursor))
470 Py_BEGIN_ALLOW_THREADS
471 r = sd_journal_seek_cursor(self->j, cursor);
475 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
476 r == -ENOMEM ? PyExc_MemoryError :
478 PyErr_SetFromErrno(errtype);
485 Journal_iter(PyObject *self)
492 Journal_iternext(PyObject *self)
495 Py_ssize_t dict_size;
497 dict = PyObject_CallMethod(self, "get_next", "");
498 dict_size = PyDict_Size(dict);
499 if ((int64_t) dict_size > 0LL) {
503 PyErr_SetNone(PyExc_StopIteration);
508 #ifdef SD_JOURNAL_FOREACH_UNIQUE
509 PyDoc_STRVAR(Journal_query_unique__doc__,
510 "query_unique(field) -> a set of values\n\n"
511 "Returns a set of unique values in journal for given `field`.\n"
512 "Note this does not respect any journal matches.");
514 Journal_query_unique(Journal *self, PyObject *args)
517 if (! PyArg_ParseTuple(args, "s", &query))
521 Py_BEGIN_ALLOW_THREADS
522 r = sd_journal_query_unique(self->j, query);
526 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
527 r == -ENOMEM ? PyExc_MemoryError :
529 PyErr_SetFromErrno(errtype);
535 const char *delim_ptr;
536 PyObject *value_set, *key, *value;
537 value_set = PySet_New(0);
539 #if PY_MAJOR_VERSION >=3
540 key = PyUnicode_FromString(query);
542 key = PyString_FromString(query);
545 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
546 delim_ptr = memchr(uniq, '=', uniq_len);
547 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
548 PySet_Add(value_set, value);
554 #endif //def SD_JOURNAL_FOREACH_UNIQUE
557 Journal_get_data_threshold(Journal *self, void *closure)
563 r = sd_journal_get_data_threshold(self->j, &cvalue);
566 PyErr_SetFromErrno(PyExc_OSError);
570 #if PY_MAJOR_VERSION >=3
571 value = PyLong_FromSize_t(cvalue);
573 value = PyInt_FromSize_t(cvalue);
579 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
582 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
585 #if PY_MAJOR_VERSION >=3
586 if (! PyLong_Check(value)){
588 if (! PyInt_Check(value)){
590 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
594 #if PY_MAJOR_VERSION >=3
595 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
597 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
601 PyErr_SetFromErrno(PyExc_OSError);
607 static PyGetSetDef Journal_getseters[] = {
609 (getter)Journal_get_data_threshold,
610 (setter)Journal_set_data_threshold,
616 static PyMethodDef Journal_methods[] = {
617 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
618 Journal_get_next__doc__},
619 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
620 Journal_get_previous__doc__},
621 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
622 Journal_add_match__doc__},
623 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
624 Journal_add_disjunction__doc__},
625 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
626 Journal_flush_matches__doc__},
627 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
628 Journal_seek__doc__},
629 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
630 Journal_seek_realtime__doc__},
631 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
632 Journal_seek_monotonic__doc__},
633 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
634 Journal_wait__doc__},
635 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
636 Journal_seek_cursor__doc__},
637 #ifdef SD_JOURNAL_FOREACH_UNIQUE
638 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
639 Journal_query_unique__doc__},
641 {NULL} /* Sentinel */
644 static PyTypeObject JournalType = {
645 PyVarObject_HEAD_INIT(NULL, 0)
646 "_reader.Journal", /*tp_name*/
647 sizeof(Journal), /*tp_basicsize*/
649 (destructor)Journal_dealloc, /*tp_dealloc*/
656 0, /*tp_as_sequence*/
664 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
665 Journal__doc__, /* tp_doc */
668 0, /* tp_richcompare */
669 0, /* tp_weaklistoffset */
670 Journal_iter, /* tp_iter */
671 Journal_iternext, /* tp_iternext */
672 Journal_methods, /* tp_methods */
674 Journal_getseters, /* tp_getset */
677 0, /* tp_descr_get */
678 0, /* tp_descr_set */
679 0, /* tp_dictoffset */
680 (initproc)Journal_init, /* tp_init */
682 PyType_GenericNew, /* tp_new */
685 #if PY_MAJOR_VERSION >= 3
686 static PyModuleDef _reader_module = {
687 PyModuleDef_HEAD_INIT,
689 "Module that reads systemd journal similar to journalctl.",
691 NULL, NULL, NULL, NULL, NULL
696 #if PY_MAJOR_VERSION >= 3
706 if (PyType_Ready(&JournalType) < 0)
707 #if PY_MAJOR_VERSION >= 3
713 #if PY_MAJOR_VERSION >= 3
714 m = PyModule_Create(&_reader_module);
718 m = Py_InitModule3("_reader", NULL,
719 "Module that reads systemd journal similar to journalctl.");
724 Py_INCREF(&JournalType);
725 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
726 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
727 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
728 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
729 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
730 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
731 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
733 #if PY_MAJOR_VERSION >= 3