Skip to content

Commit 6f36264

Browse files
committed
Merge pull request #2 from keszybz/master
Python 3 and UTF-8
2 parents bc56ace + 6f19065 commit 6f36264

File tree

7 files changed

+208
-64
lines changed

7 files changed

+208
-64
lines changed

.dir-locals.el

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
; Sets emacs variables based on mode.
2+
; A list of (major-mode . ((var1 . value1) (var2 . value2)))
3+
; Mode can be nil, which gives default values.
4+
5+
((nil . ((indent-tabs-mode . nil)
6+
(c-basic-offset . 4)))
7+
)

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
__pycache__/
12
*.py[co]
3+
/journald/*.so
24

35
# Packages
46
*.egg

README.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
journald-python
22
===============
33

4-
Python module for native access to the journald facilities in recent versions of systemd. In particular, this capability includes passing key/value pairs as fields that journald can use for filtering.
4+
Python module for native access to the journald facilities in recent
5+
versions of systemd. In particular, this capability includes passing
6+
key/value pairs as fields that journald can use for filtering.
57

68
Installation
79
============
@@ -17,16 +19,35 @@ Usage
1719
Quick example:
1820

1921
import journald
20-
journald.send('MESSAGE=Hello world.')
21-
journald.send('MESSAGE=Hello, again, world.', 'FIELD2=Greetings!', 'FIELD3=Guten tag.')
22-
journald.send('ARBITRARY=anything', 'FIELD3=Greetings!')
22+
journald.send('Hello world')
23+
journald.send('Hello, again, world', FIELD2='Greetings!', FIELD3='Guten tag')
24+
journald.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
25+
26+
There is one required argument -- the message, and additional fields
27+
can be specified as keyword arguments. Following the journald API, all
28+
names are uppercase.
29+
30+
The journald sendv call can also be accessed directly:
31+
32+
import journald
33+
journald.sendv('MESSAGE=Hello world')
34+
journald.sendv('MESSAGE=Hello, again, world', 'FIELD2=Greetings!',
35+
'FIELD3=Guten tag')
36+
journald.sendv('MESSAGE=Binary message', b'BINARY=\xde\xad\xbe\xef')
37+
38+
The two examples should give the same results in the log.
2339

2440
Notes:
2541

26-
* Each argument must be in the form of a KEY=value pair, environmental variable style.
27-
* Unlike the native C version of journald's sd_journal_send(), printf-style substitution is not supported. Perform any substitution using Python's % operator or .format() capabilities first.
28-
* The base message is usually sent in the form MESSAGE=hello. The MESSAGE field is, however, not required.
29-
* Invalid or zero arguments results in nothing recorded in journald.
42+
* Unlike the native C version of journald's sd_journal_send(),
43+
printf-style substitution is not supported. Perform any
44+
substitution using Python's % operator or .format() capabilities
45+
first.
46+
* The base message is usually sent in the form MESSAGE=hello. The
47+
MESSAGE field is, however, not required.
48+
* A ValueError is thrown is thrown if sd_journald_sendv() results in
49+
an error. This might happen if there are no arguments or one of them
50+
is invalid.
3051

3152
Viewing Output
3253
==============

journald.c

Lines changed: 0 additions & 52 deletions
This file was deleted.

journald/__init__.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from ._journald import sendv
2+
import traceback as _traceback
3+
4+
def _make_line(field, value):
5+
if isinstance(value, bytes):
6+
return field.encode('utf-8') + b'=' + value
7+
else:
8+
return field + '=' + value
9+
10+
def send(MESSAGE, MESSAGE_ID=None,
11+
CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
12+
**kwargs):
13+
r"""Send a message to journald.
14+
15+
>>> journald.send('Hello world')
16+
>>> journald.send('Hello, again, world', FIELD2='Greetings!')
17+
>>> journald.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
18+
19+
Value of the MESSAGE argument will be used for the MESSAGE= field.
20+
21+
MESSAGE_ID can be given to uniquely identify the type of message.
22+
23+
Other parts of the message can be specified as keyword arguments.
24+
25+
Both MESSAGE and MESSAGE_ID, if present, must be strings, and will
26+
be sent as UTF-8 to journald. Other arguments can be bytes, in
27+
which case they will be sent as-is to journald.
28+
29+
CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify
30+
the caller. Unless at least on of the three is given, values are
31+
extracted from the stack frame of the caller of send(). CODE_FILE
32+
and CODE_FUNC must be strings, CODE_LINE must be an integer.
33+
34+
Other useful fields include PRIORITY, SYSLOG_FACILITY,
35+
SYSLOG_IDENTIFIER, SYSLOG_PID.
36+
"""
37+
38+
args = ['MESSAGE=' + MESSAGE]
39+
40+
if MESSAGE_ID is not None:
41+
args.append('MESSAGE_ID=' + MESSAGE_ID)
42+
43+
if CODE_LINE == CODE_FILE == CODE_FUNC == None:
44+
CODE_FILE, CODE_LINE, CODE_FUNC = \
45+
_traceback.extract_stack(limit=2)[0][:3]
46+
if CODE_FILE is not None:
47+
args.append('CODE_FILE=' + CODE_FILE)
48+
if CODE_LINE is not None:
49+
args.append('CODE_LINE={:d}'.format(CODE_LINE))
50+
if CODE_FUNC is not None:
51+
args.append('CODE_FUNC=' + CODE_FUNC)
52+
53+
args.extend(_make_line(key, val) for key, val in kwargs.items())
54+
return sendv(*args)

journald/_journald.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#include <Python.h>
2+
#define SD_JOURNAL_SUPPRESS_LOCATION
3+
#include <systemd/sd-journal.h>
4+
5+
PyDoc_STRVAR(journald_sendv__doc__,
6+
"sendv('FIELD=value', 'FIELD=value', ...) -> None\n\n"
7+
"Send an entry to journald."
8+
);
9+
10+
static PyObject *
11+
journald_sendv(PyObject *self, PyObject *args) {
12+
struct iovec *iov = NULL;
13+
int argc = PyTuple_Size(args);
14+
int i, r;
15+
PyObject *ret = NULL;
16+
17+
PyObject **encoded = calloc(argc, sizeof(PyObject*));
18+
if (!encoded) {
19+
ret = PyErr_NoMemory();
20+
goto out1;
21+
}
22+
23+
// Allocate sufficient iovector space for the arguments.
24+
iov = malloc(argc * sizeof(struct iovec));
25+
if (!iov) {
26+
ret = PyErr_NoMemory();
27+
goto out;
28+
}
29+
30+
// Iterate through the Python arguments and fill the iovector.
31+
for (i = 0; i < argc; ++i) {
32+
PyObject *item = PyTuple_GetItem(args, i);
33+
char *stritem;
34+
Py_ssize_t length;
35+
36+
if (PyUnicode_Check(item)) {
37+
encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict");
38+
if (encoded[i] == NULL)
39+
goto out;
40+
item = encoded[i];
41+
}
42+
if (PyBytes_AsStringAndSize(item, &stritem, &length))
43+
goto out;
44+
45+
iov[i].iov_base = stritem;
46+
iov[i].iov_len = length;
47+
}
48+
49+
// Clear errno, because sd_journal_sendv will not set it by
50+
// itself, unless an error occurs in one of the system calls.
51+
errno = 0;
52+
53+
// Send the iovector to journald.
54+
r = sd_journal_sendv(iov, argc);
55+
56+
if (r) {
57+
if (errno)
58+
PyErr_SetFromErrno(PyExc_IOError);
59+
else
60+
PyErr_SetString(PyExc_ValueError, "invalid message format");
61+
goto out;
62+
}
63+
64+
// End with success.
65+
Py_INCREF(Py_None);
66+
ret = Py_None;
67+
68+
out:
69+
for (i = 0; i < argc; ++i)
70+
Py_XDECREF(encoded[i]);
71+
72+
free(encoded);
73+
74+
out1:
75+
// Free the iovector. The actual strings
76+
// are already managed by Python.
77+
free(iov);
78+
79+
return ret;
80+
}
81+
82+
static PyMethodDef methods[] = {
83+
{"sendv", journald_sendv, METH_VARARGS, journald_sendv__doc__},
84+
{NULL, NULL, 0, NULL} /* Sentinel */
85+
};
86+
87+
#if PY_MAJOR_VERSION < 3
88+
89+
PyMODINIT_FUNC
90+
init_journald(void)
91+
{
92+
(void) Py_InitModule("_journald", methods);
93+
}
94+
95+
#else
96+
97+
static struct PyModuleDef module = {
98+
PyModuleDef_HEAD_INIT,
99+
"_journald", /* name of module */
100+
NULL, /* module documentation, may be NULL */
101+
0, /* size of per-interpreter state of the module */
102+
methods
103+
};
104+
105+
PyMODINIT_FUNC
106+
PyInit__journald(void)
107+
{
108+
return PyModule_Create(&module);
109+
}
110+
111+
#endif

setup.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from distutils.core import setup, Extension
22

3-
journald = Extension('journald',
4-
libraries = ['systemd-journal'],
5-
sources = ['journald.c'])
3+
cjournald = Extension('journald/_journald',
4+
libraries = ['systemd-journal'],
5+
sources = ['journald/_journald.c'])
66

77
setup (name = 'journald',
88
version = '0.1',
99
description = 'Native interface to the journald facilities of systemd',
1010
author_email = 'david@davidstrauss.net',
1111
url = 'https://github.com/davidstrauss/journald-python',
12-
ext_modules = [journald])
12+
py_modules = ['journald'],
13+
ext_modules = [cjournald])

0 commit comments

Comments
 (0)