{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Running a Python script as a Systemd service\n", "This is very simple, first we need to create a systmed \"unit\", in `/etc/systemd/system/foo.service`\n", "```bash\n", "[Unit]\n", "Description=Abracadabra\n", "After=network.target\n", "\n", "[Service]\n", "Type=simple\n", "User=bob\n", "Restart=on-failure\n", "RestartSec=3\n", "ExecStart=/usr/bin/python3 /home/bob/test_daemon.py\n", "[Install]\n", "WantedBy=multi-user.target\n", "```\n", "\n", "Then, we can enable and start the service with\n", "```bash\n", "systemctl enable foo.service\n", "systemctl start foo\n", "```\n", "\n", "## What happens to the stdout and stderr?\n", "They can be checked by calling `journalctl -u foo`. Assuming `test_daemon.py` was:\n", "```python\n", "from datetime import datetime\n", "from time import sleep\n", "import sys\n", "\n", "print(\"Started abracadabra\")\n", "\n", "while True:\n", " curtime = datetime.now().strftime('%X')\n", " print(\"Hi! Current time is: \", curtime)\n", " sleep(2)\n", "```\n", "we should see\n", "```bash\n", "$ sudo journalctl -u foo\n", "Okt 09 21:07:18 etna python3[13499]: Started abracadabra\n", "Okt 09 21:07:18 etna python3[13499]: Hi! Current time is: 21:07:18\n", "Okt 09 21:07:20 etna python3[13499]: Hi! Current time is: 21:07:20\n", "Okt 09 21:07:22 etna python3[13499]: Hi! Current time is: 21:07:22\n", "Okt 09 21:07:24 etna python3[13499]: Hi! Current time is: 21:07:24\n", "Okt 09 21:07:26 etna python3[13499]: Hi! Current time is: 21:07:26\n", "Okt 09 21:07:28 etna python3[13499]: Hi! Current time is: 21:07:28\n", "Okt 09 21:07:30 etna python3[13499]: Hi! Current time is: 21:07:30\n", "```\n", "\n", "## Redirecting stdout/stderr to a file\n", "We can ask Systemd to do this by adding these lines in `/etc/systemd/system/foo.service`\n", "```bash\n", "[Service]\n", "---snip---\n", "ExecStart=/usr/bin/python3 /home/bob/test_daemon.py\n", "StandardOutput=file:/var/log/foo/stdout\n", "StandardError=file:/var/log/foo/stderr\n", "---snip---\n", "```\n", "## But nothing gets written to `/var/log/foo/stdout`!\n", "This is because python [buffers stdout](https://unix.stackexchange.com/questions/182537/write-python-stdout-to-file-immediately) if it is not a console. If we really want to see the stdout of our daemon as soon as they are printed, we need to disable this buffering. This can be achieved by starting python with `-u` flag. So, the systemd unit file changes to:\n", "\n", "```bash\n", "[Unit]\n", "Description=Abracadabra\n", "After=network.target\n", "\n", "[Service]\n", "Type=simple\n", "User=bob\n", "Restart=on-failure\n", "RestartSec=3\n", "ExecStart=/usr/bin/python3 /home/bob/test_daemon.py\n", "StandardOutput=file:/var/log/foo/stdout\n", "StandardError=file:/var/log/foo/stderr\n", "[Install]\n", "WantedBy=multi-user.target\n", "```\n", "\n", "That's it! Now `/var/log/foo/stdout` should be populated with whatever `test_daemon.py` prints. \n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" }, "latex_envs": { "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 0 }, "nav_menu": {}, "nikola": { "category": "", "date": "2019-10-09 20:57:06 UTC+02:00", "description": "", "link": "", "slug": "pythons-stdout-buffering-and-systemd", "tags": "", "title": "Python's stdout buffering and systemd", "type": "text" }, "toc": { "navigate_menu": true, "number_sections": true, "sideBar": true, "threshold": 6, "toc_cell": false, "toc_section_display": "block", "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 2 }