diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 41ae372e..8d5b8a2b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -77,7 +77,7 @@ jobs: - run: | mk python-release owner=vkottler \ - repo=runtimepy version=5.7.2 + repo=runtimepy version=5.7.3 if: | matrix.python-version == '3.12' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index ccca8ed0..9741a426 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.1.4 - hash=618546df79dd7f387faa6f84f9a58c79 + hash=ef917873929a5dc6a70367fde7f1447f ===================================== --> -# runtimepy ([5.7.2](https://pypi.org/project/runtimepy/)) +# runtimepy ([5.7.3](https://pypi.org/project/runtimepy/)) [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/) ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg) diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 26f41f9a..f5b58ba7 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 5 minor: 7 -patch: 2 +patch: 3 entry: runtimepy diff --git a/pyproject.toml b/pyproject.toml index a8593ebb..1218442f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "runtimepy" -version = "5.7.2" +version = "5.7.3" description = "A framework for implementing Python services." readme = "README.md" requires-python = ">=3.11" diff --git a/runtimepy/__init__.py b/runtimepy/__init__.py index 5757f2ab..81245040 100644 --- a/runtimepy/__init__.py +++ b/runtimepy/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.1.4 -# hash=0a0c607a8f78dc35cf48427539c9bafa +# hash=37a32f51e0bf04276100ce27658b7f8f # ===================================== """ @@ -10,7 +10,7 @@ DESCRIPTION = "A framework for implementing Python services." PKG_NAME = "runtimepy" -VERSION = "5.7.2" +VERSION = "5.7.3" # runtimepy-specific content. METRICS_NAME = "metrics" diff --git a/runtimepy/mixins/logging.py b/runtimepy/mixins/logging.py index 3179e800..d1d5003d 100644 --- a/runtimepy/mixins/logging.py +++ b/runtimepy/mixins/logging.py @@ -10,7 +10,7 @@ # third-party import aiofiles -from vcorelib.logging import LoggerMixin, LoggerType +from vcorelib.logging import ListLogger, LoggerMixin, LoggerType from vcorelib.paths import Pathlike, normalize # internal @@ -66,6 +66,25 @@ def setup_level_channel( LogPaths = Iterable[tuple[LogLevellike, Pathlike]] +EXT_LOG_EXTRA = {"external": True} + + +def handle_safe_log( + logger: LoggerType, level: int, data: str, safe_to_log: bool +) -> None: + """handle a log filtering scenario.""" + + if safe_to_log: + logger.log(level, data, extra=EXT_LOG_EXTRA) + else: + record = logging.LogRecord( + logger.name, level, __file__, -1, data, (), None + ) + record.external = True + + for handler in logger.handlers: # type: ignore + if isinstance(handler, ListLogger): + handler.emit(record) class LogCaptureMixin: @@ -76,7 +95,10 @@ class LogCaptureMixin: # Open aiofiles handles. streams: list[tuple[int, Any]] - ext_log_extra = {"external": True} + # Set false to only forward to ListLogger handlers. Required for when the + # system log / process-management logs are being forwarded (otherwise also + # logging would lead to infinite spam). + safe_to_log = True async def init_log_capture( self, stack: AsyncExitStack, log_paths: LogPaths @@ -98,7 +120,8 @@ async def init_log_capture( def log_line(self, level: int, data: str) -> None: """Log a line for output.""" - self.logger.log(level, data, extra=self.ext_log_extra) + + handle_safe_log(self.logger, level, data, self.safe_to_log) async def dispatch_log_capture(self) -> None: """Get the next line from this log stream.""" diff --git a/runtimepy/net/server/app/env/tab/html.py b/runtimepy/net/server/app/env/tab/html.py index 48b528a8..e4f5ce31 100644 --- a/runtimepy/net/server/app/env/tab/html.py +++ b/runtimepy/net/server/app/env/tab/html.py @@ -241,7 +241,9 @@ def compose(self, parent: Element) -> None: logs = div( tag="textarea", parent=div(parent=vert_container, class_str="form-floating"), - class_str=(f"form-control rounded-0 {TEXT} text-logs"), + class_str=( + f"form-control rounded-0 {TEXT} text-body-emphasis text-logs" + ), id=self.get_id("logs"), title=f"Text logs for {self.name}.", ) diff --git a/tasks/dev.yaml b/tasks/dev.yaml index 740b43af..8863442e 100644 --- a/tasks/dev.yaml +++ b/tasks/dev.yaml @@ -4,29 +4,6 @@ includes: - dev_no_wait.yaml - ../tests/data/valid/connection_arbiter/test_ssl.yaml -factories: - - {name: tasks.tlm.LogCapture} -tasks: - - name: root_log - factory: log_capture - period_s: 0.1 - markdown: | - *something isn't looking right...* - - **why's it looking like that...** - - ***why's it looking like THAT...*** - - `nice mono stuff there` - - *`nice slanted mono yeah`* - - **`nice mono bold type shit there`** - - ***`nice mono bold type slant shit there`*** - - ligature type shit \_\_\_|\_\_\_ ligature type shit - port_overrides: runtimepy_https_server: 8443 diff --git a/tasks/tlm.py b/tasks/tlm.py index fc62b01d..34cca957 100644 --- a/tasks/tlm.py +++ b/tasks/tlm.py @@ -4,14 +4,9 @@ # built-in import asyncio -import os -from pathlib import Path # internal -from runtimepy.mixins.logging import LogCaptureMixin from runtimepy.net.arbiter import AppInfo -from runtimepy.net.arbiter.task import ArbiterTask as _ArbiterTask -from runtimepy.net.arbiter.task import TaskFactory as _TaskFactory async def sample_app(app: AppInfo) -> int: @@ -24,36 +19,3 @@ async def sample_app(app: AppInfo) -> int: await asyncio.sleep(0.01) return 0 - - -class LogCaptureTask(_ArbiterTask, LogCaptureMixin): - """ - A task that captures all log messages emitted by this program instance. - """ - - auto_finalize = True - - async def init(self, app: AppInfo) -> None: - """Initialize this task with application information.""" - - await super().init(app) - - # See above comment, we can probably keep the mixin class but delete - # this one - unless we want a separate "Linux" task to run / handle - # this (we might want to increase the housekeeping task rate to reduce - # async command latency + connection processing?). - await self.init_log_capture( - app.stack, [("info", Path(os.sep, "var", "log", "syslog"))] - ) - - async def dispatch(self) -> bool: - """Dispatch an iteration of this task.""" - - await self.dispatch_log_capture() - return True - - -class LogCapture(_TaskFactory[LogCaptureTask]): - """A factory for the syslog capture task.""" - - kind = LogCaptureTask diff --git a/tests/mixins/test_logging.py b/tests/mixins/test_logging.py index 4e0e7aa3..f5eba326 100644 --- a/tests/mixins/test_logging.py +++ b/tests/mixins/test_logging.py @@ -4,20 +4,31 @@ # built-in from contextlib import AsyncExitStack +import logging # third-party from pytest import mark -from vcorelib.logging import LoggerMixin +from vcorelib.logging import ListLogger, LoggerMixin from vcorelib.paths.context import tempfile # module under test -from runtimepy.mixins.logging import LogCaptureMixin +from runtimepy.mixins.logging import LogCaptureMixin, handle_safe_log class SampleLogger(LoggerMixin, LogCaptureMixin): """A sample class.""" +def test_handle_safe_log_basic(): + """Test basic scenarios for the 'handle_safe_log' method.""" + + logger = logging.getLogger(__name__) + logger.addHandler(ListLogger.create()) + + handle_safe_log(logger, logging.INFO, "message", True) + handle_safe_log(logger, logging.INFO, "message", False) + + @mark.asyncio async def test_log_capture_mixin_basic(): """Test basic interactions with a log capture."""