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, ..., field=value, ...) -> None\n\n"
221 "Add a match to filter journal log entries. All matches of different\n"
222 "field are combined in logical AND, and matches of the same field\n"
223 "are automatically combined in logical OR.\n"
224 "Matches can be passed as strings \"field=value\", or keyword\n"
225 "arguments field=\"value\".");
227 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
231 if (! PyArg_ParseTuple(args, "s#", &match, &match_len))
235 r = sd_journal_add_match(self->j, match, match_len);
238 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
239 r == -ENOMEM ? PyExc_MemoryError :
241 PyErr_SetFromErrno(errtype);
248 PyDoc_STRVAR(Journal_add_disjunction__doc__,
249 "add_disjunction() -> None\n\n"
250 "Once called, all matches before and after are combined in logical\n"
253 Journal_add_disjunction(Journal *self, PyObject *args)
256 r = sd_journal_add_disjunction(self->j);
259 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
261 PyErr_SetFromErrno(errtype);
267 PyDoc_STRVAR(Journal_flush_matches__doc__,
268 "flush_matches() -> None\n\n"
269 "Clears all current match filters.");
271 Journal_flush_matches(Journal *self, PyObject *args)
273 sd_journal_flush_matches(self->j);
277 PyDoc_STRVAR(Journal_seek__doc__,
278 "seek(offset[, whence]) -> None\n\n"
279 "Seek through journal by `offset` number of entries. Argument\n"
280 "`whence` defines what the offset is relative to:\n"
281 "os.SEEK_SET (default) from first match in journal;\n"
282 "os.SEEK_CUR from current position in journal;\n"
283 "and os.SEEK_END is from last match in journal.");
285 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
289 static char *kwlist[] = {"offset", "whence", NULL};
291 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
295 PyObject *result=NULL;
296 if (whence == SEEK_SET){
298 Py_BEGIN_ALLOW_THREADS
299 r = sd_journal_seek_head(self->j);
303 PyErr_SetFromErrno(PyExc_OSError);
307 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
309 }else if (whence == SEEK_CUR){
310 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
311 }else if (whence == SEEK_END){
313 Py_BEGIN_ALLOW_THREADS
314 r = sd_journal_seek_tail(self->j);
318 PyErr_SetFromErrno(PyExc_OSError);
322 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
324 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
327 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
332 if (PyErr_Occurred())
337 PyDoc_STRVAR(Journal_seek_realtime__doc__,
338 "seek_realtime(realtime) -> None\n\n"
339 "Seek to nearest matching journal entry to `realtime`. Argument\n"
340 "`realtime` can be an integer unix timestamp in usecs or a "
341 "datetime instance.");
343 Journal_seek_realtime(Journal *self, PyObject *args)
346 if (! PyArg_ParseTuple(args, "K", ×tamp))
349 if ((int64_t) timestamp < 0LL) {
350 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
355 Py_BEGIN_ALLOW_THREADS
356 r = sd_journal_seek_realtime_usec(self->j, timestamp);
360 PyErr_SetFromErrno(PyExc_OSError);
366 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
367 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
368 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
369 "`monotonic` is an timestamp from boot in secs, or a\n"
370 "timedelta instance.\n"
371 "Argument `bootid` is a string representing which boot the\n"
372 "monotonic time is reference to. Defaults to current bootid.");
374 Journal_seek_monotonic(Journal *self, PyObject *args)
378 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
382 timestamp = (uint64_t) (timedouble * 1.0E6);
384 if ((int64_t) timestamp < 0LL) {
385 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
392 r = sd_id128_from_string(bootid, &sd_id);
394 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
398 PyErr_SetFromErrno(PyExc_OSError);
402 r = sd_id128_get_boot(&sd_id);
404 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
408 PyErr_SetFromErrno(PyExc_OSError);
413 Py_BEGIN_ALLOW_THREADS
414 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
418 PyErr_SetFromErrno(PyExc_OSError);
424 PyDoc_STRVAR(Journal_wait__doc__,
425 "wait([timeout]) -> Change state (integer)\n\n"
426 "Waits until there is a change in the journal. Argument `timeout`\n"
427 "is the maximum number of seconds to wait before returning\n"
428 "regardless if journal has changed. If `timeout` is not given or is\n"
429 "0, then it will block forever.\n"
430 "Will return constants: NOP if no change; APPEND if new\n"
431 "entries have been added to the end of the journal; and\n"
432 "INVALIDATE if journal files have been added or removed.");
434 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
437 if (! PyArg_ParseTuple(args, "|L", &timeout))
441 Py_BEGIN_ALLOW_THREADS
442 if ( timeout == 0LL) {
443 r = sd_journal_wait(self->j, (uint64_t) -1);
445 r = sd_journal_wait(self->j, timeout * 1E6);
450 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
452 PyErr_SetFromErrno(errtype);
455 #if PY_MAJOR_VERSION >=3
456 return PyLong_FromLong(r);
458 return PyInt_FromLong(r);
462 PyDoc_STRVAR(Journal_seek_cursor__doc__,
463 "seek_cursor(cursor) -> None\n\n"
464 "Seeks to journal entry by given unique reference `cursor`.");
466 Journal_seek_cursor(Journal *self, PyObject *args)
469 if (! PyArg_ParseTuple(args, "s", &cursor))
473 Py_BEGIN_ALLOW_THREADS
474 r = sd_journal_seek_cursor(self->j, cursor);
478 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
479 r == -ENOMEM ? PyExc_MemoryError :
481 PyErr_SetFromErrno(errtype);
488 Journal_iter(PyObject *self)
495 Journal_iternext(PyObject *self)
498 Py_ssize_t dict_size;
500 dict = PyObject_CallMethod(self, "get_next", "");
501 dict_size = PyDict_Size(dict);
502 if ((int64_t) dict_size > 0LL) {
506 PyErr_SetNone(PyExc_StopIteration);
511 #ifdef SD_JOURNAL_FOREACH_UNIQUE
512 PyDoc_STRVAR(Journal_query_unique__doc__,
513 "query_unique(field) -> a set of values\n\n"
514 "Returns a set of unique values in journal for given `field`.\n"
515 "Note this does not respect any journal matches.");
517 Journal_query_unique(Journal *self, PyObject *args)
520 if (! PyArg_ParseTuple(args, "s", &query))
524 Py_BEGIN_ALLOW_THREADS
525 r = sd_journal_query_unique(self->j, query);
529 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
530 r == -ENOMEM ? PyExc_MemoryError :
532 PyErr_SetFromErrno(errtype);
538 const char *delim_ptr;
539 PyObject *value_set, *key, *value;
540 value_set = PySet_New(0);
542 #if PY_MAJOR_VERSION >=3
543 key = PyUnicode_FromString(query);
545 key = PyString_FromString(query);
548 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
549 delim_ptr = memchr(uniq, '=', uniq_len);
550 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
551 PySet_Add(value_set, value);
557 #endif //def SD_JOURNAL_FOREACH_UNIQUE
560 Journal_get_data_threshold(Journal *self, void *closure)
566 r = sd_journal_get_data_threshold(self->j, &cvalue);
569 PyErr_SetFromErrno(PyExc_OSError);
573 #if PY_MAJOR_VERSION >=3
574 value = PyLong_FromSize_t(cvalue);
576 value = PyInt_FromSize_t(cvalue);
582 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
585 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
588 #if PY_MAJOR_VERSION >=3
589 if (! PyLong_Check(value)){
591 if (! PyInt_Check(value)){
593 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
597 #if PY_MAJOR_VERSION >=3
598 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
600 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
604 PyErr_SetFromErrno(PyExc_OSError);
610 static PyGetSetDef Journal_getseters[] = {
612 (getter)Journal_get_data_threshold,
613 (setter)Journal_set_data_threshold,
619 static PyMethodDef Journal_methods[] = {
620 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
621 Journal_get_next__doc__},
622 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
623 Journal_get_previous__doc__},
624 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
625 Journal_add_match__doc__},
626 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
627 Journal_add_disjunction__doc__},
628 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
629 Journal_flush_matches__doc__},
630 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
631 Journal_seek__doc__},
632 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
633 Journal_seek_realtime__doc__},
634 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
635 Journal_seek_monotonic__doc__},
636 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
637 Journal_wait__doc__},
638 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
639 Journal_seek_cursor__doc__},
640 #ifdef SD_JOURNAL_FOREACH_UNIQUE
641 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
642 Journal_query_unique__doc__},
644 {NULL} /* Sentinel */
647 static PyTypeObject JournalType = {
648 PyVarObject_HEAD_INIT(NULL, 0)
649 "_reader.Journal", /*tp_name*/
650 sizeof(Journal), /*tp_basicsize*/
652 (destructor)Journal_dealloc, /*tp_dealloc*/
659 0, /*tp_as_sequence*/
667 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
668 Journal__doc__, /* tp_doc */
671 0, /* tp_richcompare */
672 0, /* tp_weaklistoffset */
673 Journal_iter, /* tp_iter */
674 Journal_iternext, /* tp_iternext */
675 Journal_methods, /* tp_methods */
677 Journal_getseters, /* tp_getset */
680 0, /* tp_descr_get */
681 0, /* tp_descr_set */
682 0, /* tp_dictoffset */
683 (initproc)Journal_init, /* tp_init */
685 PyType_GenericNew, /* tp_new */
688 #if PY_MAJOR_VERSION >= 3
689 static PyModuleDef _reader_module = {
690 PyModuleDef_HEAD_INIT,
692 "Module that reads systemd journal similar to journalctl.",
694 NULL, NULL, NULL, NULL, NULL
699 #if PY_MAJOR_VERSION >= 3
709 if (PyType_Ready(&JournalType) < 0)
710 #if PY_MAJOR_VERSION >= 3
716 #if PY_MAJOR_VERSION >= 3
717 m = PyModule_Create(&_reader_module);
721 m = Py_InitModule3("_reader", NULL,
722 "Module that reads systemd journal similar to journalctl.");
727 Py_INCREF(&JournalType);
728 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
729 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
730 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
731 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
732 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
733 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
734 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
736 #if PY_MAJOR_VERSION >= 3