321 lines
8.8 KiB
Plaintext
321 lines
8.8 KiB
Plaintext
Metadata-Version: 2.1
|
|
Name: cysystemd
|
|
Version: 1.6.2
|
|
Summary: systemd wrapper in Cython
|
|
Home-page: http://github.com/mosquito/cysystemd
|
|
Author: Dmitry Orlov <me@mosquito.su>
|
|
Author-email: me@mosquito.su
|
|
License: Apache
|
|
Keywords: systemd,python,daemon,sd_notify,cython
|
|
Platform: POSIX
|
|
Classifier: Development Status :: 4 - Beta
|
|
Classifier: Environment :: Console
|
|
Classifier: Intended Audience :: Developers
|
|
Classifier: Intended Audience :: Education
|
|
Classifier: Intended Audience :: End Users/Desktop
|
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
Classifier: Natural Language :: English
|
|
Classifier: Natural Language :: Russian
|
|
Classifier: Operating System :: POSIX :: Linux
|
|
Classifier: Programming Language :: Cython
|
|
Classifier: Programming Language :: Python
|
|
Classifier: Programming Language :: Python :: 3
|
|
Classifier: Programming Language :: Python :: 3.8
|
|
Classifier: Programming Language :: Python :: 3.9
|
|
Classifier: Programming Language :: Python :: 3.10
|
|
Classifier: Programming Language :: Python :: 3.11
|
|
Classifier: Programming Language :: Python :: 3.12
|
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
Classifier: Topic :: Software Development :: Libraries
|
|
Classifier: Topic :: System
|
|
Classifier: Topic :: System :: Operating System
|
|
Provides: systemd
|
|
Requires-Python: >3.6, <4
|
|
Description-Content-Type: text/markdown
|
|
License-File: LICENSE
|
|
|
|
![pypi version](https://img.shields.io/pypi/v/cysystemd.svg) ![](https://img.shields.io/pypi/pyversions/cysystemd.svg) ![License](https://img.shields.io/pypi/l/cysystemd.svg)
|
|
|
|
# systemd wrapper in Cython
|
|
|
|
Python systemd wrapper using Cython.
|
|
|
|
|
|
## Installation
|
|
|
|
All packages available on `github releases <https://github.com/mosquito/cysystemd/releases>`_.
|
|
|
|
### Installation from binary wheels
|
|
|
|
* wheels is now available for Python 3.8, 3.9, 3.10, 3.11, 3.12
|
|
for `x86_64` and `arm64`
|
|
|
|
```shell
|
|
python3.10 -m pip install \
|
|
https://github.com/mosquito/cysystemd/releases/download/1.6.2/cysystemd-1.6.2-cp310-cp310-linux_x86_64.whl
|
|
```
|
|
|
|
### Installation from sources
|
|
|
|
You **must** install **systemd headers**
|
|
|
|
For Debian/Ubuntu users:
|
|
|
|
```shell
|
|
apt install build-essential libsystemd-dev
|
|
```
|
|
|
|
On older versions of Debian/Ubuntu, you might also need to install:
|
|
|
|
```shell
|
|
apt install libsystemd-daemon-dev libsystemd-journal-dev
|
|
```
|
|
|
|
For CentOS/RHEL
|
|
|
|
```shell
|
|
yum install gcc systemd-devel
|
|
```
|
|
|
|
And install it from pypi
|
|
|
|
```shell
|
|
pip install cysystemd
|
|
```
|
|
|
|
## Usage examples
|
|
|
|
### Writing to journald
|
|
|
|
#### Logging handler for python logger
|
|
|
|
```python
|
|
from cysystemd import journal
|
|
import logging
|
|
import uuid
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
logger = logging.getLogger()
|
|
logger.addHandler(journal.JournaldLogHandler())
|
|
|
|
try:
|
|
logger.info("Trying to do something")
|
|
raise Exception('foo')
|
|
except:
|
|
logger.exception("Test Exception %s", 1)
|
|
```
|
|
|
|
#### systemd daemon notification
|
|
|
|
|
|
```python
|
|
from cysystemd.daemon import notify, Notification
|
|
|
|
# Send READY=1
|
|
notify(Notification.READY)
|
|
|
|
# Send status
|
|
notify(Notification.STATUS, "I'm fine.")
|
|
|
|
# Send stopping
|
|
notify(Notification.STOPPING)
|
|
```
|
|
|
|
Write message into systemd journal:
|
|
|
|
```python
|
|
from cysystemd import journal
|
|
|
|
|
|
journal.write("Hello Lennart")
|
|
|
|
# Or send structured data
|
|
journal.send(
|
|
message="Hello Lennart",
|
|
priority=journal.Priority.INFO,
|
|
some_field='some value',
|
|
)
|
|
```
|
|
|
|
### Reading journald
|
|
|
|
#### Reading all systemd records
|
|
|
|
```python
|
|
|
|
from cysystemd.reader import JournalReader, JournalOpenMode
|
|
|
|
journal_reader = JournalReader()
|
|
journal_reader.open(JournalOpenMode.SYSTEM)
|
|
journal_reader.seek_head()
|
|
|
|
for record in journal_reader:
|
|
print(record.data['MESSAGE'])
|
|
```
|
|
|
|
#### Read only cron logs
|
|
|
|
```python
|
|
from cysystemd.reader import JournalReader, JournalOpenMode, Rule
|
|
|
|
|
|
rules = (
|
|
Rule("SYSLOG_IDENTIFIER", "CRON") &
|
|
Rule("_SYSTEMD_UNIT", "crond.service") |
|
|
Rule("_SYSTEMD_UNIT", "cron.service")
|
|
)
|
|
|
|
cron_reader = JournalReader()
|
|
cron_reader.open(JournalOpenMode.SYSTEM)
|
|
cron_reader.seek_head()
|
|
cron_reader.add_filter(rules)
|
|
|
|
for record in cron_reader:
|
|
print(record.data['MESSAGE'])
|
|
```
|
|
|
|
#### Polling records
|
|
|
|
```python
|
|
from cysystemd.reader import JournalReader, JournalOpenMode
|
|
|
|
|
|
reader = JournalReader()
|
|
reader.open(JournalOpenMode.SYSTEM)
|
|
reader.seek_tail()
|
|
|
|
poll_timeout = 255
|
|
|
|
while True:
|
|
reader.wait(poll_timeout)
|
|
|
|
for record in reader:
|
|
print(record.data['MESSAGE'])
|
|
```
|
|
|
|
#### journald open modes
|
|
|
|
* `CURRENT_USER`
|
|
* `LOCAL_ONLY`
|
|
* `RUNTIME_ONLY`
|
|
* `SYSTEM`
|
|
* `SYSTEM_ONLY` - deprecated alias of `SYSTEM`
|
|
|
|
|
|
```python
|
|
from cysystemd.reader import JournalReader, JournalOpenMode
|
|
|
|
|
|
reader = JournalReader()
|
|
reader.open(JournalOpenMode.CURRENT_USER)
|
|
```
|
|
|
|
#### journald entry
|
|
|
|
JournalEntry class has some special properties and methods:
|
|
|
|
* `data` - journal entry content (`dict`)
|
|
* `date` - entry timestamp (`datetime` instance)
|
|
* `cursor` - systemd identification bytes for this entry
|
|
* `boot_id()` - returns bootid
|
|
* `get_realtime_sec()` - entry epoch (`float`)
|
|
* `get_realtime_usec()` - entry epoch (`int` microseconds)
|
|
* `get_monotonic_sec()` - entry monotonic time (`float`)
|
|
* `get_monotonic_usec()` - entry monotonic time (`int` microseconds)
|
|
* `__getitem__(key)` - shoutcut for `entry.data[key]`
|
|
|
|
|
|
#### journald reader
|
|
|
|
JournalReader class has some special properties and methods:
|
|
|
|
* `open(flags=JournalOpenMode.CURRENT_USER)` - opening journald
|
|
with selected mode
|
|
* `open_directory(path)` - opening journald from path
|
|
* `open_files(*filename)` - opening journald from files
|
|
* `data_threshold` - may be used to get or set the data field size threshold
|
|
for data returned by fething entry data.
|
|
* `closed` - returns True when journal reader closed
|
|
* `locked` - returns True when journal reader locked
|
|
* `idle` - returns True when journal reader opened
|
|
* `seek_head` - move reader pointer to the first entry
|
|
* `seek_tail` - move reader pointer to the last entry
|
|
* `seek_monotonic_usec` - seeks to the entry with the specified monotonic
|
|
timestamp, i.e. CLOCK_MONOTONIC. Since monotonic time restarts on every
|
|
reboot a boot ID needs to be specified as well.
|
|
* `seek_realtime_usec` - seeks to the entry with the specified realtime
|
|
(wallclock) timestamp, i.e. CLOCK_REALTIME. Note that the realtime clock
|
|
is not necessarily monotonic. If a realtime timestamp is ambiguous, it is
|
|
not defined which position is sought to.
|
|
* `seek_cursor` - seeks to the entry located at the specified cursor
|
|
(see `JournalEntry.cursor`).
|
|
* `wait(timeout)` - It will synchronously wait until the journal gets
|
|
changed. The maximum time this call sleeps may be controlled with the
|
|
timeout_usec parameter.
|
|
* `__iter__` - returns JournalReader object
|
|
* `__next__` - calls `next()` or raise `StopIteration`
|
|
* `next(skip=0)` - returns the next `JournalEntry`. The `skip`
|
|
parameter skips some entries.
|
|
* `previous(skip=0)` - returns the previous `JournalEntry`.
|
|
The `skip` parameter skips some entries.
|
|
* `skip_next(skip)` - skips next entries.
|
|
* `skip_previous(skip)` - skips next entries.
|
|
* `add_filter(rule)` - adding filter rule.
|
|
See `read-only-cron-logs`_ as example.
|
|
* `clear_filter` - reset all filters
|
|
* `fd` - returns a special file descriptor
|
|
* `events` - returns `EPOLL` events
|
|
* `timeout` - returns internal timeout
|
|
* `process_events()` - After each poll() wake-up process_events() needs
|
|
to be called to process events. This call will also indicate what kind of
|
|
change has been detected.
|
|
* `get_catalog()` - retrieves a message catalog entry for the current
|
|
journal entry. This will look up an entry in the message catalog by using
|
|
the "MESSAGE_ID=" field of the current journal entry. Before returning
|
|
the entry all journal field names in the catalog entry text enclosed in
|
|
"@" will be replaced by the respective field values of the current entry.
|
|
If a field name referenced in the message catalog entry does not exist,
|
|
in the current journal entry, the "@" will be removed, but the field name
|
|
otherwise left untouched.
|
|
* `get_catalog_for_message_id(message_id: UUID)` - works similar to
|
|
`get_catalog()` but the entry is looked up by the specified
|
|
message ID (no open journal context is necessary for this),
|
|
and no field substitution is performed.
|
|
|
|
|
|
### Asyncio support
|
|
|
|
Initial `asyncio` support for reading journal asynchronously.
|
|
|
|
#### AsyncJournalReader
|
|
|
|
Blocking methods were wrapped by threads.
|
|
Method `wait()` use epoll on journald file descriptor.
|
|
|
|
```python
|
|
import asyncio
|
|
import json
|
|
|
|
from cysystemd.reader import JournalOpenMode
|
|
from cysystemd.async_reader import AsyncJournalReader
|
|
|
|
|
|
async def main():
|
|
reader = AsyncJournalReader()
|
|
await reader.open(JournalOpenMode.SYSTEM)
|
|
await reader.seek_tail()
|
|
|
|
while await reader.wait():
|
|
async for record in reader:
|
|
print(
|
|
json.dumps(
|
|
record.data,
|
|
indent=1,
|
|
sort_keys=True
|
|
)
|
|
)
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main())
|
|
```
|