From: Lennart Poettering Date: Thu, 13 Sep 2012 02:01:18 +0000 (+0200) Subject: python: integrate David Strauss' python-systemd package X-Git-Tag: v190~116 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=8d7e170a5230753d8406276f8b5598e5bb6766e6;p=elogind.git python: integrate David Strauss' python-systemd package --- diff --git a/Makefile.am b/Makefile.am index 730db1d5a..be97193b4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3408,6 +3408,37 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ +if HAVE_PYTHON_DEVEL + +pkgpyexec_LTLIBRARIES = \ + _journal.la + +_journal_la_SOURCES = \ + src/python-systemd/_journal.c + +_journal_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=default \ + $(PYTHON_CFLAGS) + +_journal_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -shared \ + -module \ + -avoid-version + +_journal_la_LIBADD = \ + $(PYTHON_LIBS) \ + libsystemd-journal.la + +dist_pkgpyexec_PYTHON = \ + src/python-systemd/journal.py \ + src/python-systemd/__init__.py + +endif + +# ------------------------------------------------------------------------------ + SED_PROCESS = \ $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ $(SED) -e 's,@rootlibexecdir\@,$(rootlibexecdir),g' \ diff --git a/configure.ac b/configure.ac index b13249955..da550212e 100644 --- a/configure.ac +++ b/configure.ac @@ -80,16 +80,31 @@ m4_ifdef([GOBJECT_INTROSPECTION_CHECK], [ GOBJECT_INTROSPECTION_CHECK([1.31.1]) ], [AM_CONDITIONAL([HAVE_INTROSPECTION], [false])]) -AC_CHECK_TOOL(OBJCOPY, objcopy) -AC_CHECK_TOOL(STRINGS, strings) -AC_CHECK_TOOL(GPERF, gperf) +AC_PATH_TOOL(OBJCOPY, objcopy) +AC_PATH_TOOL(STRINGS, strings) +AC_PATH_TOOL(GPERF, gperf) if test -z "$GPERF" ; then AC_MSG_ERROR([*** gperf not found]) fi -# we use python only to build the man page index +# we use python to build the man page index, and for systemd-python +have_python=no +have_python_devel=no AM_PATH_PYTHON(,, [:]) -AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != :]) +if test "$PYTHON" != : ; then + have_python=yes + AC_PATH_PROG([PYTHON_CONFIG], python-config) + + if test -n "$PYTHON_CONFIG" ; then + have_python_devel=yes + PYTHON_CFLAGS="`$PYTHON_CONFIG --cflags`" + PYTHON_LIBS="`$PYTHON_CONFIG --libs`" + AC_SUBST(PYTHON_CFLAGS) + AC_SUBST(PYTHON_LIBS) + fi +fi +AM_CONDITIONAL([HAVE_PYTHON], [test "$have_python" = "yes"]) +AM_CONDITIONAL([HAVE_PYTHON_DEVEL], [test "$have_python_devel" = "yes"]) CC_CHECK_FLAGS_APPEND([with_cflags], [CFLAGS], [\ -pipe \ @@ -803,6 +818,8 @@ AC_MSG_RESULT([ gudev: ${enable_gudev} gintrospection: ${enable_introspection} keymap: ${enable_keymap} + Python: ${have_python} + Python Headers: ${have_python_devel} prefix: ${prefix} rootprefix: ${with_rootprefix} diff --git a/src/python-systemd/Makefile b/src/python-systemd/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/python-systemd/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/python-systemd/__init__.py b/src/python-systemd/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/python-systemd/_journal.c b/src/python-systemd/_journal.c new file mode 100644 index 000000000..3e1098170 --- /dev/null +++ b/src/python-systemd/_journal.c @@ -0,0 +1,137 @@ +#include + +#define SD_JOURNAL_SUPPRESS_LOCATION +#include + +#include "macro.h" + +PyDoc_STRVAR(journal_sendv__doc__, + "sendv('FIELD=value', 'FIELD=value', ...) -> None\n\n" + "Send an entry to the journal." + ); + +static PyObject * +journal_sendv(PyObject *self, PyObject *args) { + struct iovec *iov = NULL; + int argc = PyTuple_Size(args); + int i, r; + PyObject *ret = NULL; + + PyObject **encoded = calloc(argc, sizeof(PyObject*)); + if (!encoded) { + ret = PyErr_NoMemory(); + goto out1; + } + + // Allocate sufficient iovector space for the arguments. + iov = malloc(argc * sizeof(struct iovec)); + if (!iov) { + ret = PyErr_NoMemory(); + goto out; + } + + // Iterate through the Python arguments and fill the iovector. + for (i = 0; i < argc; ++i) { + PyObject *item = PyTuple_GetItem(args, i); + char *stritem; + Py_ssize_t length; + + if (PyUnicode_Check(item)) { + encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict"); + if (encoded[i] == NULL) + goto out; + item = encoded[i]; + } + if (PyBytes_AsStringAndSize(item, &stritem, &length)) + goto out; + + iov[i].iov_base = stritem; + iov[i].iov_len = length; + } + + // Clear errno, because sd_journal_sendv will not set it by + // itself, unless an error occurs in one of the system calls. + errno = 0; + + // Send the iovector to the journal. + r = sd_journal_sendv(iov, argc); + + if (r) { + if (errno) + PyErr_SetFromErrno(PyExc_IOError); + else + PyErr_SetString(PyExc_ValueError, "invalid message format"); + goto out; + } + + // End with success. + Py_INCREF(Py_None); + ret = Py_None; + +out: + for (i = 0; i < argc; ++i) + Py_XDECREF(encoded[i]); + + free(encoded); + +out1: + // Free the iovector. The actual strings + // are already managed by Python. + free(iov); + + return ret; +} + +PyDoc_STRVAR(journal_stream_fd__doc__, + "stream_fd(identifier, priority, level_prefix) -> fd\n\n" + "Open a stream to journal by calling sd_journal_stream_fd(3)." + ); + +static PyObject* +journal_stream_fd(PyObject *self, PyObject *args) { + const char* identifier; + int priority, level_prefix; + int fd; + if (!PyArg_ParseTuple(args, "sii:stream_fd", + &identifier, &priority, &level_prefix)) + return NULL; + + fd = sd_journal_stream_fd(identifier, priority, level_prefix); + if (fd < 0) + return PyErr_SetFromErrno(PyExc_IOError); + + return PyLong_FromLong(fd); +} + +static PyMethodDef methods[] = { + {"sendv", journal_sendv, METH_VARARGS, journal_sendv__doc__}, + {"stream_fd", journal_stream_fd, METH_VARARGS, + journal_stream_fd__doc__}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +#if PY_MAJOR_VERSION < 3 + +PyMODINIT_FUNC +init_journal(void) +{ + (void) Py_InitModule("_journal", methods); +} + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "_journal", /* name of module */ + NULL, /* module documentation, may be NULL */ + 0, /* size of per-interpreter state of the module */ + methods +}; + +PyMODINIT_FUNC +PyInit__journal(void) +{ + return PyModule_Create(&module); +} + +#endif diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py new file mode 100644 index 000000000..53e992bed --- /dev/null +++ b/src/python-systemd/journal.py @@ -0,0 +1,93 @@ +import traceback as _traceback +import os as _os +from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, + LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) +from ._journal import sendv, stream_fd + +def _make_line(field, value): + if isinstance(value, bytes): + return field.encode('utf-8') + b'=' + value + else: + return field + '=' + value + +def send(MESSAGE, MESSAGE_ID=None, + CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, + **kwargs): + r"""Send a message to journald. + + >>> journal.send('Hello world') + >>> journal.send('Hello, again, world', FIELD2='Greetings!') + >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') + + Value of the MESSAGE argument will be used for the MESSAGE= field. + + MESSAGE_ID can be given to uniquely identify the type of message. + + Other parts of the message can be specified as keyword arguments. + + Both MESSAGE and MESSAGE_ID, if present, must be strings, and will + be sent as UTF-8 to journal. Other arguments can be bytes, in + which case they will be sent as-is to journal. + + CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify + the caller. Unless at least on of the three is given, values are + extracted from the stack frame of the caller of send(). CODE_FILE + and CODE_FUNC must be strings, CODE_LINE must be an integer. + + Other useful fields include PRIORITY, SYSLOG_FACILITY, + SYSLOG_IDENTIFIER, SYSLOG_PID. + """ + + args = ['MESSAGE=' + MESSAGE] + + if MESSAGE_ID is not None: + args.append('MESSAGE_ID=' + MESSAGE_ID) + + if CODE_LINE == CODE_FILE == CODE_FUNC == None: + CODE_FILE, CODE_LINE, CODE_FUNC = \ + _traceback.extract_stack(limit=2)[0][:3] + if CODE_FILE is not None: + args.append('CODE_FILE=' + CODE_FILE) + if CODE_LINE is not None: + args.append('CODE_LINE={:d}'.format(CODE_LINE)) + if CODE_FUNC is not None: + args.append('CODE_FUNC=' + CODE_FUNC) + + args.extend(_make_line(key, val) for key, val in kwargs.items()) + return sendv(*args) + +def stream(identifier, priority=LOG_DEBUG, level_prefix=False): + r"""Return a file object wrapping a stream to journal. + + Log messages written to this file as simple newline sepearted + text strings are written to the journal. + + The file will be line buffered, so messages are actually sent + after a newline character is written. + + >>> stream = journal.stream('myapp') + >>> stream + ', mode 'w' at 0x...> + >>> stream.write('message...\n') + + will produce the following message in the journal: + + PRIORITY=7 + SYSLOG_IDENTIFIER=myapp + MESSAGE=message... + + Using the interface with print might be more convinient: + + >>> from __future__ import print_function + >>> print('message...', file=stream) + + priority is the syslog priority, one of LOG_EMERG, LOG_ALERT, + LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. + + level_prefix is a boolean. If true, kernel-style log priority + level prefixes (such as '<1>') are interpreted. See sd-daemon(3) + for more information. + """ + + fd = stream_fd(identifier, priority, level_prefix) + return _os.fdopen(fd, 'w', 1)