mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-04 07:43:42 +00:00 
			
		
		
		
	refine package logging
This commit is contained in:
		@ -7,6 +7,8 @@ logging = ahriman.ini.d/logging.ini
 | 
				
			|||||||
;apply_migrations = yes
 | 
					;apply_migrations = yes
 | 
				
			||||||
; Path to the application SQLite database.
 | 
					; Path to the application SQLite database.
 | 
				
			||||||
database = ${repository:root}/ahriman.db
 | 
					database = ${repository:root}/ahriman.db
 | 
				
			||||||
 | 
					; Keep last build logs for each package
 | 
				
			||||||
 | 
					keep_last_logs = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[alpm]
 | 
					[alpm]
 | 
				
			||||||
; Path to pacman system database cache.
 | 
					; Path to pacman system database cache.
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
 | 
				
			|||||||
                "path_exists": True,
 | 
					                "path_exists": True,
 | 
				
			||||||
                "path_type": "dir",
 | 
					                "path_type": "dir",
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
 | 
					            "keep_last_logs": {
 | 
				
			||||||
 | 
					                "type": "integer",
 | 
				
			||||||
 | 
					                "coerce": "integer",
 | 
				
			||||||
 | 
					                "min": 0,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
            "logging": {
 | 
					            "logging": {
 | 
				
			||||||
                "type": "path",
 | 
					                "type": "path",
 | 
				
			||||||
                "coerce": "absolute_path",
 | 
					                "coerce": "absolute_path",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										27
									
								
								src/ahriman/core/database/migrations/m015_logs_process_id.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/ahriman/core/database/migrations/m015_logs_process_id.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2021-2025 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/>.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					__all__ = ["steps"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					steps = [
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    alter table logs add column process_id text not null default ''
 | 
				
			||||||
 | 
					    """,
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
@ -30,7 +30,7 @@ class LogsOperations(Operations):
 | 
				
			|||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
 | 
					    def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
 | 
				
			||||||
                 repository_id: RepositoryId | None = None) -> list[tuple[float, str]]:
 | 
					                 repository_id: RepositoryId | None = None) -> list[tuple[LogRecordId, float, str]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        extract logs for specified package base
 | 
					        extract logs for specified package base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,16 +41,16 @@ class LogsOperations(Operations):
 | 
				
			|||||||
            repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
 | 
					            repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Return:
 | 
					        Return:
 | 
				
			||||||
            list[tuple[float, str]]: sorted package log records and their timestamps
 | 
					            list[tuple[LogRecordId, float, str]]: sorted package log records and their timestamps
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        repository_id = repository_id or self._repository_id
 | 
					        repository_id = repository_id or self._repository_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def run(connection: Connection) -> list[tuple[float, str]]:
 | 
					        def run(connection: Connection) -> list[tuple[LogRecordId, float, str]]:
 | 
				
			||||||
            return [
 | 
					            return [
 | 
				
			||||||
                (row["created"], row["record"])
 | 
					                (LogRecordId(package_base, row["version"], row["process_id"]), row["created"], row["record"])
 | 
				
			||||||
                for row in connection.execute(
 | 
					                for row in connection.execute(
 | 
				
			||||||
                    """
 | 
					                    """
 | 
				
			||||||
                    select created, record from (
 | 
					                    select created, record, version, process_id from (
 | 
				
			||||||
                        select * from logs
 | 
					                        select * from logs
 | 
				
			||||||
                        where package_base = :package_base and repository = :repository
 | 
					                        where package_base = :package_base and repository = :repository
 | 
				
			||||||
                        order by created desc limit :limit offset :offset
 | 
					                        order by created desc limit :limit offset :offset
 | 
				
			||||||
@ -83,9 +83,9 @@ class LogsOperations(Operations):
 | 
				
			|||||||
            connection.execute(
 | 
					            connection.execute(
 | 
				
			||||||
                """
 | 
					                """
 | 
				
			||||||
                insert into logs
 | 
					                insert into logs
 | 
				
			||||||
                (package_base, version, created, record, repository)
 | 
					                (package_base, version, created, record, repository, process_id)
 | 
				
			||||||
                values
 | 
					                values
 | 
				
			||||||
                (:package_base, :version, :created, :record, :repository)
 | 
					                (:package_base, :version, :created, :record, :repository, :process_id)
 | 
				
			||||||
                """,
 | 
					                """,
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    "package_base": log_record_id.package_base,
 | 
					                    "package_base": log_record_id.package_base,
 | 
				
			||||||
@ -93,6 +93,7 @@ class LogsOperations(Operations):
 | 
				
			|||||||
                    "created": created,
 | 
					                    "created": created,
 | 
				
			||||||
                    "record": record,
 | 
					                    "record": record,
 | 
				
			||||||
                    "repository": repository_id.id,
 | 
					                    "repository": repository_id.id,
 | 
				
			||||||
 | 
					                    "process_id": log_record_id.process_id,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,3 +126,54 @@ class LogsOperations(Operations):
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.with_connection(run, commit=True)
 | 
					        return self.with_connection(run, commit=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def logs_rotate(self, keep_last_records: int, repository_id: RepositoryId | None = None) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        rotate logs in storage. This method will remove old logs, keeping only the last N records for each package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            keep_last_records(int): number of last records to keep
 | 
				
			||||||
 | 
					            repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        repository_id = repository_id or self._repository_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def remove_duplicates(connection: Connection) -> None:
 | 
				
			||||||
 | 
					            connection.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                delete from logs
 | 
				
			||||||
 | 
					                where (package_base, version, repository, process_id) not in (
 | 
				
			||||||
 | 
					                  select package_base, version, repository, process_id from logs
 | 
				
			||||||
 | 
					                  where (package_base, version, repository, created) in (
 | 
				
			||||||
 | 
					                    select package_base, version, repository, max(created) from logs
 | 
				
			||||||
 | 
					                    where repository = :repository
 | 
				
			||||||
 | 
					                    group by package_base, version, repository
 | 
				
			||||||
 | 
					                  )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "repository": repository_id.id,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def remove_older(connection: Connection) -> None:
 | 
				
			||||||
 | 
					            connection.execute(
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					                delete from logs
 | 
				
			||||||
 | 
					                where (package_base, repository, process_id) in (
 | 
				
			||||||
 | 
					                  select package_base, repository, process_id from logs
 | 
				
			||||||
 | 
					                  where repository = :repository
 | 
				
			||||||
 | 
					                  group by package_base, repository, process_id
 | 
				
			||||||
 | 
					                  order by min(created) desc limit -1 offset :offset
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                """,
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    "offset": keep_last_records,
 | 
				
			||||||
 | 
					                    "repository": repository_id.id,
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def run(connection: Connection) -> None:
 | 
				
			||||||
 | 
					            remove_duplicates(connection)
 | 
				
			||||||
 | 
					            remove_older(connection)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return self.with_connection(run, commit=True)
 | 
				
			||||||
 | 
				
			|||||||
@ -17,6 +17,7 @@
 | 
				
			|||||||
# You should have received a copy of the GNU General Public License
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					import atexit
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from typing import Self
 | 
					from typing import Self
 | 
				
			||||||
@ -33,6 +34,7 @@ class HttpLogHandler(logging.Handler):
 | 
				
			|||||||
    method
 | 
					    method
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Attributes:
 | 
					    Attributes:
 | 
				
			||||||
 | 
					        keep_last_records(int): number of last records to keep
 | 
				
			||||||
        reporter(Client): build status reporter instance
 | 
					        reporter(Client): build status reporter instance
 | 
				
			||||||
        suppress_errors(bool): suppress logging errors (e.g. if no web server available)
 | 
					        suppress_errors(bool): suppress logging errors (e.g. if no web server available)
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
@ -51,6 +53,7 @@ class HttpLogHandler(logging.Handler):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.reporter = Client.load(repository_id, configuration, report=report)
 | 
					        self.reporter = Client.load(repository_id, configuration, report=report)
 | 
				
			||||||
        self.suppress_errors = suppress_errors
 | 
					        self.suppress_errors = suppress_errors
 | 
				
			||||||
 | 
					        self.keep_last_records = configuration.getint("settings", "keep_last_logs", fallback=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
 | 
					    def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
 | 
				
			||||||
@ -76,6 +79,8 @@ class HttpLogHandler(logging.Handler):
 | 
				
			|||||||
        handler = cls(repository_id, configuration, report=report, suppress_errors=suppress_errors)
 | 
					        handler = cls(repository_id, configuration, report=report, suppress_errors=suppress_errors)
 | 
				
			||||||
        root.addHandler(handler)
 | 
					        root.addHandler(handler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        atexit.register(handler.rotate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return handler
 | 
					        return handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def emit(self, record: logging.LogRecord) -> None:
 | 
					    def emit(self, record: logging.LogRecord) -> None:
 | 
				
			||||||
@ -95,3 +100,9 @@ class HttpLogHandler(logging.Handler):
 | 
				
			|||||||
            if self.suppress_errors:
 | 
					            if self.suppress_errors:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
            self.handleError(record)
 | 
					            self.handleError(record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def rotate(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        rotate log records, removing older ones
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.reporter.logs_rotate(self.keep_last_records)
 | 
				
			||||||
 | 
				
			|||||||
@ -99,24 +99,3 @@ class LazyLogging:
 | 
				
			|||||||
            yield
 | 
					            yield
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
            self._package_logger_reset()
 | 
					            self._package_logger_reset()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    @contextlib.contextmanager
 | 
					 | 
				
			||||||
    def suppress_logging(self, log_level: int = logging.WARNING) -> Generator[None, None, None]:
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        silence log messages in context
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Args:
 | 
					 | 
				
			||||||
            log_level(int, optional): the highest log level to keep (Default value = logging.WARNING)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Examples:
 | 
					 | 
				
			||||||
             This function is designed to be used to suppress all log messages in context, e.g.:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                 >>> with self.suppress_logging():
 | 
					 | 
				
			||||||
                 >>>     do_some_noisy_actions()
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        current_level = self.logger.manager.disable
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            logging.disable(log_level)
 | 
					 | 
				
			||||||
            yield
 | 
					 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            logging.disable(current_level)
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -75,7 +75,7 @@ class Executor(PackageInfo, Cleaner):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        result = Result()
 | 
					        result = Result()
 | 
				
			||||||
        for single in updates:
 | 
					        for single in updates:
 | 
				
			||||||
            with self.in_package_context(single.base, local_versions.get(single.base)), \
 | 
					            with self.in_package_context(single.base, single.version), \
 | 
				
			||||||
                    TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
 | 
					                    TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    with self.in_event(single.base, EventType.PackageUpdated, failure=EventType.PackageUpdateFailed):
 | 
					                    with self.in_event(single.base, EventType.PackageUpdated, failure=EventType.PackageUpdateFailed):
 | 
				
			||||||
@ -194,7 +194,6 @@ class Executor(PackageInfo, Cleaner):
 | 
				
			|||||||
            self.repo.add(package_path)
 | 
					            self.repo.add(package_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        current_packages = {package.base: package for package in self.packages()}
 | 
					        current_packages = {package.base: package for package in self.packages()}
 | 
				
			||||||
        local_versions = {package_base: package.version for package_base, package in current_packages.items()}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        removed_packages: list[str] = []  # list of packages which have been removed from the base
 | 
					        removed_packages: list[str] = []  # list of packages which have been removed from the base
 | 
				
			||||||
        updates = self.load_archives(packages)
 | 
					        updates = self.load_archives(packages)
 | 
				
			||||||
@ -202,7 +201,7 @@ class Executor(PackageInfo, Cleaner):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        result = Result()
 | 
					        result = Result()
 | 
				
			||||||
        for local in updates:
 | 
					        for local in updates:
 | 
				
			||||||
            with self.in_package_context(local.base, local_versions.get(local.base)):
 | 
					            with self.in_package_context(local.base, local.version):
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    packager = self.packager(packagers, local.base)
 | 
					                    packager = self.packager(packagers, local.base)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -144,8 +144,7 @@ class UpdateHandler(PackageInfo, Cleaner):
 | 
				
			|||||||
                        branch="master",
 | 
					                        branch="master",
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    with self.suppress_logging():
 | 
					                    Sources.fetch(cache_dir, source)
 | 
				
			||||||
                        Sources.fetch(cache_dir, source)
 | 
					 | 
				
			||||||
                    remote = Package.from_build(cache_dir, self.architecture, None)
 | 
					                    remote = Package.from_build(cache_dir, self.architecture, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    local = packages.get(remote.base)
 | 
					                    local = packages.get(remote.base)
 | 
				
			||||||
 | 
				
			|||||||
@ -115,6 +115,14 @@ class Client:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        raise NotImplementedError
 | 
					        raise NotImplementedError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def logs_rotate(self, keep_last_records: int) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        remove older logs from storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            keep_last_records(int): number of last records to keep
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_changes_get(self, package_base: str) -> Changes:
 | 
					    def package_changes_get(self, package_base: str) -> Changes:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package changes
 | 
					        get package changes
 | 
				
			||||||
@ -197,7 +205,8 @@ class Client:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        # this method does not raise NotImplementedError because it is actively used as dummy client for http log
 | 
					        # this method does not raise NotImplementedError because it is actively used as dummy client for http log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
 | 
					    def package_logs_get(self, package_base: str, limit: int = -1,
 | 
				
			||||||
 | 
					                         offset: int = 0) -> list[tuple[LogRecordId, float, str]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package logs
 | 
					        get package logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -207,7 +216,7 @@ class Client:
 | 
				
			|||||||
            offset(int, optional): records offset (Default value = 0)
 | 
					            offset(int, optional): records offset (Default value = 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            list[tuple[float, str]]: package logs
 | 
					            list[tuple[LogRecordId, float, str]]: package logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Raises:
 | 
					        Raises:
 | 
				
			||||||
            NotImplementedError: not implemented method
 | 
					            NotImplementedError: not implemented method
 | 
				
			||||||
 | 
				
			|||||||
@ -75,6 +75,15 @@ class LocalClient(Client):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.database.event_get(event, object_id, from_date, to_date, limit, offset, self.repository_id)
 | 
					        return self.database.event_get(event, object_id, from_date, to_date, limit, offset, self.repository_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def logs_rotate(self, keep_last_records: int) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        remove older logs from storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            keep_last_records(int): number of last records to keep
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.database.logs_rotate(keep_last_records, self.repository_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_changes_get(self, package_base: str) -> Changes:
 | 
					    def package_changes_get(self, package_base: str) -> Changes:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package changes
 | 
					        get package changes
 | 
				
			||||||
@ -145,7 +154,8 @@ class LocalClient(Client):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        self.database.logs_insert(log_record_id, created, message, self.repository_id)
 | 
					        self.database.logs_insert(log_record_id, created, message, self.repository_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
 | 
					    def package_logs_get(self, package_base: str, limit: int = -1,
 | 
				
			||||||
 | 
					                         offset: int = 0) -> list[tuple[LogRecordId, float, str]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package logs
 | 
					        get package logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,7 +165,7 @@ class LocalClient(Client):
 | 
				
			|||||||
            offset(int, optional): records offset (Default value = 0)
 | 
					            offset(int, optional): records offset (Default value = 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            list[tuple[float, str]]: package logs
 | 
					            list[tuple[LogRecordId, float, str]]: package logs
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self.database.logs_get(package_base, limit, offset, self.repository_id)
 | 
					        return self.database.logs_get(package_base, limit, offset, self.repository_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -53,9 +53,6 @@ class Watcher(LazyLogging):
 | 
				
			|||||||
        self._known: dict[str, tuple[Package, BuildStatus]] = {}
 | 
					        self._known: dict[str, tuple[Package, BuildStatus]] = {}
 | 
				
			||||||
        self.status = BuildStatus()
 | 
					        self.status = BuildStatus()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # special variables for updating logs
 | 
					 | 
				
			||||||
        self._last_log_record_id = LogRecordId("", "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def packages(self) -> list[tuple[Package, BuildStatus]]:
 | 
					    def packages(self) -> list[tuple[Package, BuildStatus]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -81,6 +78,8 @@ class Watcher(LazyLogging):
 | 
				
			|||||||
                for package, status in self.client.package_get(None)
 | 
					                for package, status in self.client.package_get(None)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logs_rotate: Callable[[int], None]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    package_changes_get: Callable[[str], Changes]
 | 
					    package_changes_get: Callable[[str], Changes]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    package_changes_update: Callable[[str, Changes], None]
 | 
					    package_changes_update: Callable[[str, Changes], None]
 | 
				
			||||||
@ -108,22 +107,9 @@ class Watcher(LazyLogging):
 | 
				
			|||||||
        except KeyError:
 | 
					        except KeyError:
 | 
				
			||||||
            raise UnknownPackageError(package_base) from None
 | 
					            raise UnknownPackageError(package_base) from None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None:
 | 
					    package_logs_add: Callable[[LogRecordId, float, str], None]
 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        make new log record into database
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Args:
 | 
					    package_logs_get: Callable[[str, int, int], list[tuple[LogRecordId, float, str]]]
 | 
				
			||||||
            log_record_id(LogRecordId): log record id
 | 
					 | 
				
			||||||
            created(float): log created timestamp
 | 
					 | 
				
			||||||
            message(str): log message
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        if self._last_log_record_id != log_record_id:
 | 
					 | 
				
			||||||
            # there is new log record, so we remove old ones
 | 
					 | 
				
			||||||
            self.package_logs_remove(log_record_id.package_base, log_record_id.version)
 | 
					 | 
				
			||||||
        self._last_log_record_id = log_record_id
 | 
					 | 
				
			||||||
        self.client.package_logs_add(log_record_id, created, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    package_logs_get: Callable[[str, int, int], list[tuple[float, str]]]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    package_logs_remove: Callable[[str, str | None], None]
 | 
					    package_logs_remove: Callable[[str, str | None], None]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -210,6 +210,18 @@ class WebClient(Client, SyncAhrimanClient):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def logs_rotate(self, keep_last_records: int) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        remove older logs from storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Args:
 | 
				
			||||||
 | 
					            keep_last_records(int): number of last records to keep
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        query = self.repository_id.query() + [("keep_last_records", str(keep_last_records))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with contextlib.suppress(Exception):
 | 
				
			||||||
 | 
					            self.make_request("DELETE", f"{self.address}/api/v1/service/logs", params=query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_changes_get(self, package_base: str) -> Changes:
 | 
					    def package_changes_get(self, package_base: str) -> Changes:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package changes
 | 
					        get package changes
 | 
				
			||||||
@ -306,6 +318,7 @@ class WebClient(Client, SyncAhrimanClient):
 | 
				
			|||||||
        payload = {
 | 
					        payload = {
 | 
				
			||||||
            "created": created,
 | 
					            "created": created,
 | 
				
			||||||
            "message": message,
 | 
					            "message": message,
 | 
				
			||||||
 | 
					            "process_id": log_record_id.process_id,
 | 
				
			||||||
            "version": log_record_id.version,
 | 
					            "version": log_record_id.version,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -315,7 +328,8 @@ class WebClient(Client, SyncAhrimanClient):
 | 
				
			|||||||
        self.make_request("POST", self._logs_url(log_record_id.package_base),
 | 
					        self.make_request("POST", self._logs_url(log_record_id.package_base),
 | 
				
			||||||
                          params=self.repository_id.query(), json=payload, suppress_errors=True)
 | 
					                          params=self.repository_id.query(), json=payload, suppress_errors=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
 | 
					    def package_logs_get(self, package_base: str, limit: int = -1,
 | 
				
			||||||
 | 
					                         offset: int = 0) -> list[tuple[LogRecordId, float, str]]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        get package logs
 | 
					        get package logs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -325,7 +339,7 @@ class WebClient(Client, SyncAhrimanClient):
 | 
				
			|||||||
            offset(int, optional): records offset (Default value = 0)
 | 
					            offset(int, optional): records offset (Default value = 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Returns:
 | 
					        Returns:
 | 
				
			||||||
            list[tuple[float, str]]: package logs
 | 
					            list[tuple[LogRecordId, float, str]]: package logs
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
 | 
					        query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -333,7 +347,13 @@ class WebClient(Client, SyncAhrimanClient):
 | 
				
			|||||||
            response = self.make_request("GET", self._logs_url(package_base), params=query)
 | 
					            response = self.make_request("GET", self._logs_url(package_base), params=query)
 | 
				
			||||||
            response_json = response.json()
 | 
					            response_json = response.json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return [(record["created"], record["message"]) for record in response_json]
 | 
					            return [
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    LogRecordId(package_base, record["version"], record["process_id"]),
 | 
				
			||||||
 | 
					                    record["created"],
 | 
				
			||||||
 | 
					                    record["message"]
 | 
				
			||||||
 | 
					                ) for record in response_json
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,9 @@
 | 
				
			|||||||
# You should have received a copy of the GNU General Public License
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
from dataclasses import dataclass
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from dataclasses import dataclass, field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass(frozen=True)
 | 
					@dataclass(frozen=True)
 | 
				
			||||||
@ -28,7 +30,12 @@ class LogRecordId:
 | 
				
			|||||||
    Attributes:
 | 
					    Attributes:
 | 
				
			||||||
        package_base(str): package base for which log record belongs
 | 
					        package_base(str): package base for which log record belongs
 | 
				
			||||||
        version(str): package version for which log record belongs
 | 
					        version(str): package version for which log record belongs
 | 
				
			||||||
 | 
					        process_id(str, optional): unique process identifier
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    package_base: str
 | 
					    package_base: str
 | 
				
			||||||
    version: str
 | 
					    version: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # this is not mistake, this value is kind of global identifier, which is generated
 | 
				
			||||||
 | 
					    # upon the process start
 | 
				
			||||||
 | 
					    process_id: str = field(default=str(uuid.uuid4()))
 | 
				
			||||||
 | 
				
			|||||||
@ -429,12 +429,11 @@ class Package(LazyLogging):
 | 
				
			|||||||
        task = Task(self, configuration, repository_id.architecture, paths)
 | 
					        task = Task(self, configuration, repository_id.architecture, paths)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            with self.suppress_logging():
 | 
					            # create fresh chroot environment, fetch sources and - automagically - update PKGBUILD
 | 
				
			||||||
                # create fresh chroot environment, fetch sources and - automagically - update PKGBUILD
 | 
					            task.init(paths.cache_for(self.base), [], None)
 | 
				
			||||||
                task.init(paths.cache_for(self.base), [], None)
 | 
					            pkgbuild = Pkgbuild.from_file(paths.cache_for(self.base) / "PKGBUILD")
 | 
				
			||||||
                pkgbuild = Pkgbuild.from_file(paths.cache_for(self.base) / "PKGBUILD")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return full_version(pkgbuild.get("epoch"), pkgbuild["pkgver"], pkgbuild["pkgrel"])
 | 
					            return full_version(pkgbuild.get("epoch"), pkgbuild["pkgver"], pkgbuild["pkgrel"])
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            self.logger.exception("cannot determine version of VCS package")
 | 
					            self.logger.exception("cannot determine version of VCS package")
 | 
				
			||||||
        finally:
 | 
					        finally:
 | 
				
			||||||
 | 
				
			|||||||
@ -18,7 +18,8 @@
 | 
				
			|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
from aiohttp.web import HTTPException
 | 
					from aiohttp.web import HTTPException
 | 
				
			||||||
from typing import Any, Callable
 | 
					from collections.abc import Callable
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ahriman.models.user_access import UserAccess
 | 
					from ahriman.models.user_access import UserAccess
 | 
				
			||||||
from ahriman.web.apispec import Schema, aiohttp_apispec
 | 
					from ahriman.web.apispec import Schema, aiohttp_apispec
 | 
				
			||||||
 | 
				
			|||||||
@ -31,6 +31,7 @@ from ahriman.web.schemas.info_schema import InfoSchema
 | 
				
			|||||||
from ahriman.web.schemas.internal_status_schema import InternalStatusSchema
 | 
					from ahriman.web.schemas.internal_status_schema import InternalStatusSchema
 | 
				
			||||||
from ahriman.web.schemas.log_schema import LogSchema
 | 
					from ahriman.web.schemas.log_schema import LogSchema
 | 
				
			||||||
from ahriman.web.schemas.login_schema import LoginSchema
 | 
					from ahriman.web.schemas.login_schema import LoginSchema
 | 
				
			||||||
 | 
					from ahriman.web.schemas.logs_rotate_schema import LogsRotateSchema
 | 
				
			||||||
from ahriman.web.schemas.logs_schema import LogsSchema
 | 
					from ahriman.web.schemas.logs_schema import LogsSchema
 | 
				
			||||||
from ahriman.web.schemas.oauth2_schema import OAuth2Schema
 | 
					from ahriman.web.schemas.oauth2_schema import OAuth2Schema
 | 
				
			||||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
 | 
					from ahriman.web.schemas.package_name_schema import PackageNameSchema
 | 
				
			||||||
@ -53,5 +54,4 @@ from ahriman.web.schemas.repository_stats_schema import RepositoryStatsSchema
 | 
				
			|||||||
from ahriman.web.schemas.search_schema import SearchSchema
 | 
					from ahriman.web.schemas.search_schema import SearchSchema
 | 
				
			||||||
from ahriman.web.schemas.status_schema import StatusSchema
 | 
					from ahriman.web.schemas.status_schema import StatusSchema
 | 
				
			||||||
from ahriman.web.schemas.update_flags_schema import UpdateFlagsSchema
 | 
					from ahriman.web.schemas.update_flags_schema import UpdateFlagsSchema
 | 
				
			||||||
from ahriman.web.schemas.versioned_log_schema import VersionedLogSchema
 | 
					 | 
				
			||||||
from ahriman.web.schemas.worker_schema import WorkerSchema
 | 
					from ahriman.web.schemas.worker_schema import WorkerSchema
 | 
				
			||||||
 | 
				
			|||||||
@ -17,12 +17,13 @@
 | 
				
			|||||||
# You should have received a copy of the GNU General Public License
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
 | 
					from ahriman import __version__
 | 
				
			||||||
from ahriman.web.apispec import Schema, fields
 | 
					from ahriman.web.apispec import Schema, fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LogSchema(Schema):
 | 
					class LogSchema(Schema):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    request package log schema
 | 
					    request and response package log schema
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    created = fields.Float(required=True, metadata={
 | 
					    created = fields.Float(required=True, metadata={
 | 
				
			||||||
@ -32,3 +33,10 @@ class LogSchema(Schema):
 | 
				
			|||||||
    message = fields.String(required=True, metadata={
 | 
					    message = fields.String(required=True, metadata={
 | 
				
			||||||
        "description": "Log message",
 | 
					        "description": "Log message",
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    version = fields.String(required=True, metadata={
 | 
				
			||||||
 | 
					        "description": "Package version to tag",
 | 
				
			||||||
 | 
					        "example": __version__,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    process_id = fields.String(metadata={
 | 
				
			||||||
 | 
					        "description": "Process unique identifier",
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
				
			|||||||
@ -17,18 +17,14 @@
 | 
				
			|||||||
# You should have received a copy of the GNU General Public License
 | 
					# You should have received a copy of the GNU General Public License
 | 
				
			||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
					# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
from ahriman import __version__
 | 
					from ahriman.web.apispec import Schema, fields
 | 
				
			||||||
from ahriman.web.apispec import fields
 | 
					 | 
				
			||||||
from ahriman.web.schemas.log_schema import LogSchema
 | 
					 | 
				
			||||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VersionedLogSchema(LogSchema, RepositoryIdSchema):
 | 
					class LogsRotateSchema(Schema):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    request package log schema
 | 
					    request logs rotate schema
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    version = fields.Integer(required=True, metadata={
 | 
					    keep_last_records = fields.Integer(metadata={
 | 
				
			||||||
        "description": "Package version to tag",
 | 
					        "description": "Keep the specified amount of records",
 | 
				
			||||||
        "example": __version__,
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
@ -24,8 +24,7 @@ from ahriman.core.utils import pretty_datetime
 | 
				
			|||||||
from ahriman.models.log_record_id import LogRecordId
 | 
					from ahriman.models.log_record_id import LogRecordId
 | 
				
			||||||
from ahriman.models.user_access import UserAccess
 | 
					from ahriman.models.user_access import UserAccess
 | 
				
			||||||
from ahriman.web.apispec.decorators import apidocs
 | 
					from ahriman.web.apispec.decorators import apidocs
 | 
				
			||||||
from ahriman.web.schemas import LogsSchema, PackageNameSchema, PackageVersionSchema, RepositoryIdSchema, \
 | 
					from ahriman.web.schemas import LogSchema, LogsSchema, PackageNameSchema, PackageVersionSchema, RepositoryIdSchema
 | 
				
			||||||
    VersionedLogSchema
 | 
					 | 
				
			||||||
from ahriman.web.views.base import BaseView
 | 
					from ahriman.web.views.base import BaseView
 | 
				
			||||||
from ahriman.web.views.status_view_guard import StatusViewGuard
 | 
					from ahriman.web.views.status_view_guard import StatusViewGuard
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,7 +96,7 @@ class LogsView(StatusViewGuard, BaseView):
 | 
				
			|||||||
        response = {
 | 
					        response = {
 | 
				
			||||||
            "package_base": package_base,
 | 
					            "package_base": package_base,
 | 
				
			||||||
            "status": status.view(),
 | 
					            "status": status.view(),
 | 
				
			||||||
            "logs": "\n".join(f"[{pretty_datetime(created)}] {message}" for created, message in logs)
 | 
					            "logs": "\n".join(f"[{pretty_datetime(created)}] {message}" for _, created, message in logs)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return json_response(response)
 | 
					        return json_response(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -109,7 +108,7 @@ class LogsView(StatusViewGuard, BaseView):
 | 
				
			|||||||
        error_400_enabled=True,
 | 
					        error_400_enabled=True,
 | 
				
			||||||
        error_404_description="Repository is unknown",
 | 
					        error_404_description="Repository is unknown",
 | 
				
			||||||
        match_schema=PackageNameSchema,
 | 
					        match_schema=PackageNameSchema,
 | 
				
			||||||
        body_schema=VersionedLogSchema,
 | 
					        body_schema=LogSchema,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    async def post(self) -> None:
 | 
					    async def post(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@ -129,6 +128,8 @@ class LogsView(StatusViewGuard, BaseView):
 | 
				
			|||||||
        except Exception as ex:
 | 
					        except Exception as ex:
 | 
				
			||||||
            raise HTTPBadRequest(reason=str(ex))
 | 
					            raise HTTPBadRequest(reason=str(ex))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.service().package_logs_add(LogRecordId(package_base, version), created, record)
 | 
					        # either read from process identifier from payload or assign to the current process identifier
 | 
				
			||||||
 | 
					        process_id = data.get("process_id", LogRecordId("", "").process_id)
 | 
				
			||||||
 | 
					        self.service().package_logs_add(LogRecordId(package_base, version, process_id), created, record)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        raise HTTPNoContent
 | 
					        raise HTTPNoContent
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										63
									
								
								src/ahriman/web/views/v1/service/logs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/ahriman/web/views/v1/service/logs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (c) 2021-2025 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 aiohttp.web import HTTPBadRequest, HTTPNoContent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ahriman.models.user_access import UserAccess
 | 
				
			||||||
 | 
					from ahriman.web.apispec.decorators import apidocs
 | 
				
			||||||
 | 
					from ahriman.web.schemas import LogsRotateSchema
 | 
				
			||||||
 | 
					from ahriman.web.views.base import BaseView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogsView(BaseView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    logs management web view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Attributes:
 | 
				
			||||||
 | 
					        DELETE_PERMISSION(UserAccess): (class attribute) delete permissions of self
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DELETE_PERMISSION = UserAccess.Full
 | 
				
			||||||
 | 
					    ROUTES = ["/api/v1/service/logs"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @apidocs(
 | 
				
			||||||
 | 
					        tags=["Actions"],
 | 
				
			||||||
 | 
					        summary="Rotate logs",
 | 
				
			||||||
 | 
					        description="Remove older logs from system",
 | 
				
			||||||
 | 
					        permission=DELETE_PERMISSION,
 | 
				
			||||||
 | 
					        error_400_enabled=True,
 | 
				
			||||||
 | 
					        error_404_description="Repository is unknown",
 | 
				
			||||||
 | 
					        query_schema=LogsRotateSchema,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    async def delete(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        rotate logs from system
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Raises:
 | 
				
			||||||
 | 
					            HTTPBadRequest: if bad data is supplied
 | 
				
			||||||
 | 
					            HTTPNoContent: on success response
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            keep_last_records = int(self.request.query.get("keep_last_records", 0))
 | 
				
			||||||
 | 
					        except Exception as ex:
 | 
				
			||||||
 | 
					            raise HTTPBadRequest(reason=str(ex))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.service().logs_rotate(keep_last_records)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raise HTTPNoContent
 | 
				
			||||||
@ -67,6 +67,8 @@ class LogsView(StatusViewGuard, BaseView):
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                "created": created,
 | 
					                "created": created,
 | 
				
			||||||
                "message": message,
 | 
					                "message": message,
 | 
				
			||||||
            } for created, message in logs
 | 
					                "version": log_record_id.version,
 | 
				
			||||||
 | 
					                "process_id": log_record_id.process_id,
 | 
				
			||||||
 | 
					            } for log_record_id, created, message in logs
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        return json_response(response)
 | 
					        return json_response(response)
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user