mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
runtime logger handler selector
This commit is contained in:
parent
6c3b2ef266
commit
91e548569d
@ -6,7 +6,7 @@ ENV AHRIMAN_DEBUG=""
|
||||
ENV AHRIMAN_FORCE_ROOT=""
|
||||
ENV AHRIMAN_HOST="0.0.0.0"
|
||||
ENV AHRIMAN_MULTILIB="yes"
|
||||
ENV AHRIMAN_OUTPUT="journald"
|
||||
ENV AHRIMAN_OUTPUT=""
|
||||
ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>"
|
||||
ENV AHRIMAN_PACMAN_MIRROR=""
|
||||
ENV AHRIMAN_PORT=""
|
||||
@ -28,9 +28,9 @@ RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
|
||||
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
|
||||
## install package dependencies
|
||||
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
|
||||
RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo python-systemd && \
|
||||
RUN pacman -Sy --noconfirm --asdeps devtools git pyalpm python-cerberus python-inflection python-passlib python-requests python-srcinfo && \
|
||||
pacman -Sy --noconfirm --asdeps python-build python-installer python-wheel && \
|
||||
pacman -Sy --noconfirm breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket rsync subversion && \
|
||||
pacman -Sy --noconfirm --asdeps breezy mercurial python-aiohttp python-aiohttp-cors python-boto3 python-cryptography python-jinja python-requests-unixsocket python-systemd rsync subversion && \
|
||||
runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-apispec-git python-aiohttp-jinja2 \
|
||||
python-aiohttp-debugtoolbar python-aiohttp-session python-aiohttp-security
|
||||
|
||||
|
@ -15,15 +15,10 @@ database = $AHRIMAN_REPOSITORY_ROOT/ahriman.db
|
||||
host = $AHRIMAN_HOST
|
||||
|
||||
EOF
|
||||
sed -i "s|handlers = journald_handler|handlers = ${AHRIMAN_OUTPUT}_handler|g" "/etc/ahriman.ini.d/logging.ini"
|
||||
|
||||
AHRIMAN_DEFAULT_ARGS=("--architecture" "$AHRIMAN_ARCHITECTURE")
|
||||
if [[ "$AHRIMAN_OUTPUT" == "syslog" ]]; then
|
||||
if [ ! -e "/dev/log" ]; then
|
||||
# by default ahriman uses syslog which is not available inside container
|
||||
# to make noise less we force quiet mode in case if /dev/log was not mounted
|
||||
AHRIMAN_DEFAULT_ARGS+=("--quiet")
|
||||
fi
|
||||
if [ -n "$AHRIMAN_OUTPUT" ]; then
|
||||
AHRIMAN_DEFAULT_ARGS+=("--log-handler" "$AHRIMAN_OUTPUT")
|
||||
fi
|
||||
|
||||
# create repository root inside the [[mounted]] directory and set correct ownership
|
||||
|
@ -1,9 +1,9 @@
|
||||
.TH AHRIMAN "1" "2023\-05\-25" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2023\-05\-28" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,user-add,user-list,user-remove,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [--report | --no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,user-add,user-list,user-remove,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH linux ReposItory MANager
|
||||
|
||||
@ -24,6 +24,10 @@ force run, remove file lock
|
||||
\fB\-l\fR \fI\,LOCK\/\fR, \fB\-\-lock\fR \fI\,LOCK\/\fR
|
||||
lock file
|
||||
|
||||
.TP
|
||||
\fB\-\-log\-handler\fR \fI\,{console,syslog,journald}\/\fR
|
||||
explicit log handler specification. If none set, the handler will be guessed from environment
|
||||
|
||||
.TP
|
||||
\fB\-\-report\fR, \fB\-\-no\-report\fR
|
||||
force enable or disable reporting to web service
|
||||
|
@ -20,6 +20,14 @@ ahriman.core.log.http\_log\_handler module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.log.journal\_handler module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.log.journal_handler
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.log.lazy\_logging module
|
||||
-------------------------------------
|
||||
|
||||
|
@ -60,6 +60,14 @@ ahriman.models.internal\_status module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.log\_handler module
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.log_handler
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.log\_record\_id module
|
||||
-------------------------------------
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--report' '--no-report' '-q' '--quiet' '--unsafe' '-V' '--version')
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '--report' '--no-report' '-q' '--quiet' '--unsafe' '-V' '--version')
|
||||
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
_shtab_ahriman_help_option_strings=('-h' '--help')
|
||||
@ -73,6 +73,7 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
|
||||
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
|
||||
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_package_add__s_choices=('auto' 'archive' 'aur' 'directory' 'local' 'remote' 'repository')
|
||||
|
@ -81,6 +81,7 @@ _shtab_ahriman_options=(
|
||||
{-c,--configuration}"[configuration path]:configuration:"
|
||||
"--force[force run, remove file lock]"
|
||||
{-l,--lock}"[lock file]:lock:"
|
||||
"--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment]:log_handler:(console syslog journald)"
|
||||
{--report,--no-report}"[force enable or disable reporting to web service]:report:"
|
||||
{-q,--quiet}"[force disable any logging]"
|
||||
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable]"
|
||||
|
20
docs/faq.rst
20
docs/faq.rst
@ -391,7 +391,7 @@ The following environment variables are supported:
|
||||
* ``AHRIMAN_FORCE_ROOT`` - force run ahriman as root instead of guessing by subcommand.
|
||||
* ``AHRIMAN_HOST`` - host for the web interface, default is ``0.0.0.0``.
|
||||
* ``AHRIMAN_MULTILIB`` - if set (default) multilib repository will be used, disabled otherwise.
|
||||
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` (the default) handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
|
||||
* ``AHRIMAN_OUTPUT`` - controls logging handler, e.g. ``syslog``, ``console``. The name must be found in logging configuration. Note that if ``syslog`` handler is used you will need to mount ``/dev/log`` inside container because it is not available there.
|
||||
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
|
||||
* ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set.
|
||||
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
|
||||
@ -663,7 +663,7 @@ How to report by email
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-jinja
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
@ -690,7 +690,7 @@ How to generate index page for S3
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-jinja
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
@ -714,7 +714,7 @@ How to post build report to telegram
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-jinja
|
||||
yay -S --asdeps python-jinja
|
||||
|
||||
#.
|
||||
Register bot in telegram. You can do it by talking with `@BotFather <https://t.me/botfather>`_. For more details please refer to `official documentation <https://core.telegram.org/bots>`_.
|
||||
@ -838,7 +838,7 @@ How to setup web service
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-aiohttp python-aiohttp-jinja2 python-aiohttp-apispec>=3.0.0 python-aiohttp-cors
|
||||
yay -S --asdeps python-aiohttp python-aiohttp-jinja2 python-aiohttp-apispec>=3.0.0 python-aiohttp-cors
|
||||
|
||||
#.
|
||||
Configure service:
|
||||
@ -859,7 +859,7 @@ How to enable basic authorization
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-aiohttp-security python-aiohttp-session python-cryptography
|
||||
yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography
|
||||
|
||||
#.
|
||||
Configure the service to enable authorization:
|
||||
@ -915,7 +915,7 @@ How to enable OAuth authorization
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
yay -S python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
|
||||
yay -S --asdeps python-aiohttp-security python-aiohttp-session python-cryptography python-aioauth-client
|
||||
|
||||
#.
|
||||
Configure the service:
|
||||
@ -1053,17 +1053,17 @@ It is automation tools for ``repoctl`` mentioned above. Except for using shell i
|
||||
How to check service logs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, the service writes logs to ``/dev/log`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run). In order to retrieve logs for the process you can use the following command:
|
||||
By default, the service writes logs to ``journald`` which can be accessed by using ``journalctl`` command (logs are written to the journal of the user under which command is run). In order to retrieve logs for the process you can use the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo journalctl SYSLOG_IDENTIFIER=ahriman
|
||||
|
||||
You can also edit configuration and forward logs to ``stderr``, just change ``handlers`` value, e.g.:
|
||||
You can also ask to forward logs to ``stderr``, just set ``--log-handler`` flag, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sed -i 's/handlers = journald_handler/handlers = console_handler/g' /etc/ahriman.ini.d/logging.ini
|
||||
ahriman --log-handler console ...
|
||||
|
||||
You can even configure logging as you wish, but kindly refer to python ``logging`` module `configuration <https://docs.python.org/3/library/logging.config.html>`_. The application uses java concept to log messages, e.g. class ``Application`` imported from ``ahriman.application.application`` package will have logger called ``ahriman.application.application.Application``. In order to e.g. change logger name for whole application package it is possible to change values for ``ahriman.application`` package; thus editing ``ahriman`` logger configuration will change logging for whole application (unless there are overrides for another logger).
|
||||
|
||||
|
@ -7,8 +7,7 @@ pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
url="https://github.com/arcan1s/ahriman"
|
||||
license=('GPL3')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests'
|
||||
'python-srcinfo' 'python-systemd')
|
||||
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-cerberus' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo')
|
||||
makedepends=('python-build' 'python-installer' 'python-wheel')
|
||||
optdepends=('breezy: -bzr packages support'
|
||||
'darcs: -darcs packages support'
|
||||
@ -25,6 +24,7 @@ optdepends=('breezy: -bzr packages support'
|
||||
'python-cryptography: web server with authorization'
|
||||
'python-requests-unixsocket: client report to web server by unix socket'
|
||||
'python-jinja: html report generation'
|
||||
'python-systemd: journal support'
|
||||
'rsync: sync by using rsync'
|
||||
'subversion: -svn packages support')
|
||||
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
|
||||
|
@ -1,2 +1 @@
|
||||
d /var/lib/ahriman 0755 ahriman ahriman
|
||||
d /var/log/ahriman 0755 ahriman ahriman
|
||||
d /var/lib/ahriman 0755 ahriman ahriman
|
@ -8,13 +8,13 @@ keys = console_handler,journald_handler,syslog_handler
|
||||
keys = generic_format,syslog_format
|
||||
|
||||
[handler_console_handler]
|
||||
class = StreamHandler
|
||||
class = logging.StreamHandler
|
||||
level = DEBUG
|
||||
formatter = generic_format
|
||||
args = (sys.stderr,)
|
||||
|
||||
[handler_journald_handler]
|
||||
class = systemd.journal.JournalHandler
|
||||
class = ahriman.core.log.journal_handler.JournalHandler
|
||||
level = DEBUG
|
||||
formatter = syslog_format
|
||||
kwargs = {"SYSLOG_IDENTIFIER": "ahriman"}
|
||||
@ -27,20 +27,16 @@ args = ("/dev/log",)
|
||||
|
||||
[formatter_generic_format]
|
||||
format = [%(levelname)s %(asctime)s] [%(name)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[formatter_syslog_format]
|
||||
format = [%(levelname)s] [%(name)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[logger_root]
|
||||
level = DEBUG
|
||||
handlers = journald_handler
|
||||
qualname = root
|
||||
|
||||
[logger_http]
|
||||
level = DEBUG
|
||||
handlers = journald_handler
|
||||
qualname = http
|
||||
propagate = 0
|
||||
|
||||
@ -51,24 +47,20 @@ qualname = stderr
|
||||
|
||||
[logger_boto3]
|
||||
level = INFO
|
||||
handlers = journald_handler
|
||||
qualname = boto3
|
||||
propagate = 0
|
||||
|
||||
[logger_botocore]
|
||||
level = INFO
|
||||
handlers = journald_handler
|
||||
qualname = botocore
|
||||
propagate = 0
|
||||
|
||||
[logger_nose]
|
||||
level = INFO
|
||||
handlers = journald_handler
|
||||
qualname = nose
|
||||
propagate = 0
|
||||
|
||||
[logger_s3transfer]
|
||||
level = INFO
|
||||
handlers = journald_handler
|
||||
qualname = s3transfer
|
||||
propagate = 0
|
||||
|
3
setup.py
3
setup.py
@ -121,6 +121,9 @@ setup(
|
||||
"sphinx-rtd-theme>=1.1.1", # https://stackoverflow.com/a/74355734
|
||||
"sphinxcontrib-napoleon",
|
||||
],
|
||||
"journald": [
|
||||
"systemd-python",
|
||||
],
|
||||
# FIXME technically this dependency is required, but in some cases we do not have access to
|
||||
# the libalpm which is required in order to install the package. Thus in case if we do not
|
||||
# really need to run the application we can move it to "optional" dependencies
|
||||
|
@ -30,6 +30,7 @@ from ahriman.application import handlers
|
||||
from ahriman.core.util import enum_values
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.sign_settings import SignSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
@ -58,6 +59,7 @@ def _formatter(prog: str) -> argparse.HelpFormatter:
|
||||
return argparse.ArgumentDefaultsHelpFormatter(prog, width=120)
|
||||
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
"""
|
||||
command line parser generator
|
||||
@ -75,6 +77,9 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
|
||||
parser.add_argument("-l", "--lock", help="lock file", type=Path,
|
||||
default=Path(tempfile.gettempdir()) / "ahriman.lock")
|
||||
parser.add_argument("--log-handler", help="explicit log handler specification. If none set, the handler will be "
|
||||
"guessed from environment",
|
||||
type=LogHandler, choices=enum_values(LogHandler))
|
||||
parser.add_argument("--report", help="force enable or disable reporting to web service",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||
|
@ -94,7 +94,8 @@ class Handler:
|
||||
"""
|
||||
try:
|
||||
configuration = Configuration.from_path(args.configuration, architecture)
|
||||
Log.load(configuration, quiet=args.quiet, report=args.report)
|
||||
log_handler = Log.handler(args.log_handler)
|
||||
Log.load(configuration, log_handler, quiet=args.quiet, report=args.report)
|
||||
with Lock(args, architecture, configuration):
|
||||
cls.run(args, architecture, configuration, report=args.report, unsafe=args.unsafe)
|
||||
return True
|
||||
|
47
src/ahriman/core/log/journal_handler.py
Normal file
47
src/ahriman/core/log/journal_handler.py
Normal file
@ -0,0 +1,47 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from logging import NullHandler
|
||||
from typing import Any
|
||||
|
||||
|
||||
__all__ = ["JournalHandler"]
|
||||
|
||||
|
||||
class _JournalHandler(NullHandler):
|
||||
"""
|
||||
wrapper for unexpected args and kwargs
|
||||
"""
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
*args(Any): positional arguments
|
||||
**kwargs(Any): keyword arguments
|
||||
"""
|
||||
NullHandler.__init__(self)
|
||||
del args, kwargs
|
||||
|
||||
|
||||
try:
|
||||
from systemd.journal import JournalHandler # type: ignore[import]
|
||||
except ImportError:
|
||||
JournalHandler = _JournalHandler
|
@ -20,9 +20,11 @@
|
||||
import logging
|
||||
|
||||
from logging.config import fileConfig
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log.http_log_handler import HttpLogHandler
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
|
||||
|
||||
class Log:
|
||||
@ -32,24 +34,65 @@ class Log:
|
||||
Attributes:
|
||||
DEFAULT_LOG_FORMAT(str): (class attribute) default log format (in case of fallback)
|
||||
DEFAULT_LOG_LEVEL(int): (class attribute) default log level (in case of fallback)
|
||||
DEFAULT_SYSLOG_DEVICE(Path): (class attribute) default path to syslog device
|
||||
"""
|
||||
|
||||
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
|
||||
DEFAULT_LOG_LEVEL = logging.DEBUG
|
||||
DEFAULT_SYSLOG_DEVICE = Path("/dev") / "log"
|
||||
|
||||
@staticmethod
|
||||
def load(configuration: Configuration, *, quiet: bool, report: bool) -> None:
|
||||
def handler(selected: LogHandler | None) -> LogHandler:
|
||||
"""
|
||||
try to guess default log handler. In case if ``selected`` is set, it will return specified value with appended
|
||||
_handler suffix. Otherwise, it will try to import journald handler and returns ``journald_handler`` if library
|
||||
is available. Otherwise, it will check if there is ``/dev/log`` device and returns ``syslog_handler`` in this
|
||||
case. And, finally, it will fall back to ``console_handler`` if none were found
|
||||
|
||||
Args:
|
||||
selected(LogHandler | None): user specified handler if any
|
||||
|
||||
Returns:
|
||||
LogHandler: selected log handler
|
||||
"""
|
||||
if selected is not None:
|
||||
return selected
|
||||
|
||||
try:
|
||||
from systemd.journal import JournalHandler # type: ignore[import]
|
||||
del JournalHandler
|
||||
return LogHandler.Journald # journald import was found
|
||||
except ImportError:
|
||||
if Log.DEFAULT_SYSLOG_DEVICE.exists():
|
||||
return LogHandler.Syslog
|
||||
return LogHandler.Console
|
||||
|
||||
@staticmethod
|
||||
def load(configuration: Configuration, handler: LogHandler, *, quiet: bool, report: bool) -> None:
|
||||
"""
|
||||
setup logging settings from configuration
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
handler(LogHandler): selected default log handler, which will be used if no handlers were set
|
||||
quiet(bool): force disable any log messages
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
default_handler = f"{handler.value}_handler"
|
||||
|
||||
try:
|
||||
path = configuration.logging_path
|
||||
fileConfig(path)
|
||||
log_configuration = Configuration()
|
||||
log_configuration.read(configuration.logging_path)
|
||||
|
||||
# set handlers if they are not set
|
||||
for section in filter(lambda s: s.startswith("logger_"), log_configuration.sections()):
|
||||
if "handlers" in log_configuration[section]:
|
||||
continue
|
||||
log_configuration.set_option(section, "handlers", default_handler)
|
||||
|
||||
# load logging configuration
|
||||
fileConfig(log_configuration, disable_existing_loggers=True)
|
||||
logging.debug("using %s logger", default_handler)
|
||||
except Exception:
|
||||
logging.basicConfig(filename=None, format=Log.DEFAULT_LOG_FORMAT,
|
||||
level=Log.DEFAULT_LOG_LEVEL)
|
||||
|
35
src/ahriman/models/log_handler.py
Normal file
35
src/ahriman/models/log_handler.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class LogHandler(str, Enum):
|
||||
"""
|
||||
log handler as described by default configuration
|
||||
|
||||
Attributes:
|
||||
Console(LogHandler): (class attribute) write logs to console
|
||||
Syslog(LogHandler): (class attribute) write logs to syslog device /dev/null
|
||||
Journald(LogHandler): (class attribute) write logs to journald directly
|
||||
"""
|
||||
|
||||
Console = "console"
|
||||
Syslog = "syslog"
|
||||
Journald = "journald"
|
@ -7,6 +7,7 @@ from pytest_mock import MockerFixture
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import ExitCode, MissingArchitectureError, MultipleArchitecturesError
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
|
||||
|
||||
def test_architectures_extract(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -56,17 +57,20 @@ def test_call(args: argparse.Namespace, configuration: Configuration, mocker: Mo
|
||||
must call inside lock
|
||||
"""
|
||||
args.configuration = Path("")
|
||||
args.log_handler = LogHandler.Console
|
||||
args.quiet = False
|
||||
args.report = False
|
||||
mocker.patch("ahriman.application.handlers.Handler.run")
|
||||
configuration_mock = mocker.patch("ahriman.core.configuration.Configuration.from_path", return_value=configuration)
|
||||
log_handler_mock = mocker.patch("ahriman.core.log.Log.handler", return_value=args.log_handler)
|
||||
log_load_mock = mocker.patch("ahriman.core.log.Log.load")
|
||||
enter_mock = mocker.patch("ahriman.application.lock.Lock.__enter__")
|
||||
exit_mock = mocker.patch("ahriman.application.lock.Lock.__exit__")
|
||||
|
||||
assert Handler.call(args, "x86_64")
|
||||
configuration_mock.assert_called_once_with(args.configuration, "x86_64")
|
||||
log_load_mock.assert_called_once_with(configuration, quiet=args.quiet, report=args.report)
|
||||
log_handler_mock.assert_called_once_with(args.log_handler)
|
||||
log_load_mock.assert_called_once_with(configuration, args.log_handler, quiet=args.quiet, report=args.report)
|
||||
enter_mock.assert_called_once_with()
|
||||
exit_mock.assert_called_once_with(None, None, None)
|
||||
|
||||
|
@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
from ahriman.models.sign_settings import SignSettings
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
@ -37,6 +38,14 @@ def test_parser_option_lock(parser: argparse.ArgumentParser) -> None:
|
||||
assert isinstance(args.lock, Path)
|
||||
|
||||
|
||||
def test_parser_option_log_handler(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
must convert log-handler option to LogHandler instance
|
||||
"""
|
||||
args = parser.parse_args(["--log-handler", "console", "service-config"])
|
||||
assert isinstance(args.log_handler, LogHandler)
|
||||
|
||||
|
||||
def test_multiple_architectures(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
must accept multiple architectures
|
||||
|
31
tests/ahriman/core/log/test_journal_handler.py
Normal file
31
tests/ahriman/core/log/test_journal_handler.py
Normal file
@ -0,0 +1,31 @@
|
||||
import sys
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
|
||||
# because of how imports work it must be first test
|
||||
def test_dummy_journal_handler(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must import dummy journal handler if upstream systemd was not found
|
||||
"""
|
||||
mocker.patch.dict(sys.modules, {"systemd.journal": None})
|
||||
from logging import NullHandler
|
||||
from ahriman.core.log.journal_handler import JournalHandler
|
||||
assert issubclass(JournalHandler, NullHandler)
|
||||
|
||||
|
||||
def test_init() -> None:
|
||||
"""
|
||||
must init dummy handler
|
||||
"""
|
||||
from ahriman.core.log.journal_handler import _JournalHandler
|
||||
assert _JournalHandler(42, answer=42)
|
||||
|
||||
|
||||
def test_journal_handler() -> None:
|
||||
"""
|
||||
must import journal handler
|
||||
"""
|
||||
from systemd.journal import JournalHandler as UpstreamJournalHandler
|
||||
from ahriman.core.log.journal_handler import JournalHandler
|
||||
assert JournalHandler is UpstreamJournalHandler
|
@ -1,21 +1,59 @@
|
||||
import logging
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from logging.config import fileConfig
|
||||
from pytest_mock import MockerFixture
|
||||
from systemd.journal import JournalHandler
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import Log
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
|
||||
|
||||
def test_handler() -> None:
|
||||
"""
|
||||
must extract journald handler if available
|
||||
"""
|
||||
assert Log.handler(None) == LogHandler.Journald
|
||||
|
||||
|
||||
def test_handler_selected() -> None:
|
||||
"""
|
||||
must return selected log handler
|
||||
"""
|
||||
assert Log.handler(LogHandler.Console) == LogHandler.Console
|
||||
|
||||
|
||||
def test_handler_syslog(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return syslog handler if no journal is available
|
||||
"""
|
||||
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||
mocker.patch.dict(sys.modules, {"systemd.journal": None})
|
||||
assert Log.handler(None) == LogHandler.Syslog
|
||||
|
||||
|
||||
def test_handler_console(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return console handler if no journal is available and no log device was found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.exists", return_value=False)
|
||||
mocker.patch.dict(sys.modules, {"systemd.journal": None})
|
||||
assert Log.handler(None) == LogHandler.Console
|
||||
|
||||
|
||||
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load logging
|
||||
"""
|
||||
logging_mock = mocker.patch("ahriman.core.log.log.fileConfig")
|
||||
logging_mock = mocker.patch("ahriman.core.log.log.fileConfig", side_effect=fileConfig)
|
||||
http_log_mock = mocker.patch("ahriman.core.log.http_log_handler.HttpLogHandler.load")
|
||||
|
||||
Log.load(configuration, quiet=False, report=False)
|
||||
logging_mock.assert_called_once_with(configuration.logging_path)
|
||||
Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
|
||||
logging_mock.assert_called_once_with(pytest.helpers.anyvar(int), disable_existing_loggers=True)
|
||||
http_log_mock.assert_called_once_with(configuration, report=False)
|
||||
assert all(isinstance(handler, JournalHandler) for handler in logging.getLogger().handlers)
|
||||
|
||||
|
||||
def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -23,7 +61,7 @@ def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> N
|
||||
must fall back to stderr without errors
|
||||
"""
|
||||
mocker.patch("ahriman.core.log.log.fileConfig", side_effect=PermissionError())
|
||||
Log.load(configuration, quiet=False, report=False)
|
||||
Log.load(configuration, LogHandler.Journald, quiet=False, report=False)
|
||||
|
||||
|
||||
def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -31,5 +69,5 @@ def test_load_quiet(configuration: Configuration, mocker: MockerFixture) -> None
|
||||
must disable logging in case if quiet flag set
|
||||
"""
|
||||
disable_mock = mocker.patch("logging.disable")
|
||||
Log.load(configuration, quiet=True, report=False)
|
||||
Log.load(configuration, LogHandler.Journald, quiet=True, report=False)
|
||||
disable_mock.assert_called_once_with(logging.WARNING)
|
||||
|
0
tests/ahriman/models/test_log_handler.py
Normal file
0
tests/ahriman/models/test_log_handler.py
Normal file
Loading…
Reference in New Issue
Block a user