diff --git a/docs/source/public_api.rst b/docs/source/public_api.rst index 3038f3fd..3dd5de00 100644 --- a/docs/source/public_api.rst +++ b/docs/source/public_api.rst @@ -405,6 +405,20 @@ This page summarises the parts of the LabThings API that should be most frequent :no-index: +.. py:function:: get_thing_logger(thing_name: str | None = None) -> logging.Logger + + Return the parent logger of all the `~lt.Thing.logger` instances. + + This logger is where invocation logs are collected, so if you are writing code that + is not part of a `~lt.Thing` but still wants to show up in the log associated with + a particular invocation, you should create a child of this logger. + + Full details are at `labthings_fastapi.logs.get_thing_logger`\ . + + :param thing_name: if supplied, a child logger is returned, using this name. + :return: the Thing logger, or a child of it. + + .. py:class:: ThingClient A client for a LabThings-FastAPI Thing, alias of `labthings_fastapi.client.ThingClient` diff --git a/src/labthings_fastapi/__init__.py b/src/labthings_fastapi/__init__.py index 860b1fba..3dc99adc 100644 --- a/src/labthings_fastapi/__init__.py +++ b/src/labthings_fastapi/__init__.py @@ -33,6 +33,7 @@ cancellable_sleep, raise_if_cancelled, ) +from labthings_fastapi.logs import get_thing_logger from labthings_fastapi.outputs import blob from labthings_fastapi.properties import DataProperty, DataSetting, property, setting from labthings_fastapi.server import ThingServer, cli @@ -64,6 +65,7 @@ "cancellable_sleep", "cli", "endpoint", + "get_thing_logger", "outputs", "property", "raise_if_cancelled", diff --git a/src/labthings_fastapi/logs.py b/src/labthings_fastapi/logs.py index 865247ac..51898379 100644 --- a/src/labthings_fastapi/logs.py +++ b/src/labthings_fastapi/logs.py @@ -107,6 +107,28 @@ def configure_thing_logger(level: int | None = None) -> None: THING_LOGGER.addHandler(DequeByInvocationIDHandler()) +def get_thing_logger(thing_name: str | None = None) -> logging.Logger: + r"""Return the Thing Logger, or a child logger. + + This function returns either the Thing logger, or a child of it. Any messages + logged to this logger will be picked up by invocation logs, if they are + logged from an invocation thread/context. + + ``thing.logger`` is equivalent to ``get_thing_logger(thing.name)`` for any + `~lt.Thing` instance. + + :param thing_name: the name of a `lt.Thing`\ . If supplied, we will get a child + logger (i.e. ``labthings_fastapi.things.{thing_name}``). By default, + the root Thing logger (``labthings_fastapi.things``) is returned. + :return: the Thing logger or a child of it. + """ + if thing_name: + logger = THING_LOGGER.getChild(thing_name) + else: + logger = THING_LOGGER + return logger + + def add_thing_log_destination( invocation_id: UUID, destination: MutableSequence ) -> None: diff --git a/src/labthings_fastapi/thing.py b/src/labthings_fastapi/thing.py index 4356f239..986b6d26 100644 --- a/src/labthings_fastapi/thing.py +++ b/src/labthings_fastapi/thing.py @@ -23,7 +23,7 @@ from labthings_fastapi.actions import ActionCollection from labthings_fastapi.base_descriptor import OptionallyBoundDescriptor from labthings_fastapi.invocation_contexts import get_invocation_id -from labthings_fastapi.logs import THING_LOGGER +from labthings_fastapi.logs import get_thing_logger from labthings_fastapi.properties import ( PropertyCollection, SettingCollection, @@ -148,7 +148,7 @@ def name(self) -> str: @property def logger(self) -> logging.Logger: """A logger, named after this Thing.""" - return THING_LOGGER.getChild(self.name) + return get_thing_logger(self.name) async def __aenter__(self) -> Self: """Context management is used to set up/close the thing. diff --git a/tests/test_logs.py b/tests/test_logs.py index a3e5ed53..3cd33ed3 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -302,3 +302,11 @@ def test_action_logs_over_http(): for log in logs: log_as_model = LogRecordModel(**log) assert log_as_model.message == "foobar" + + +def test_get_thing_logger(): + """Check the convenience function to get the thing logger.""" + assert lt.get_thing_logger() is logs.THING_LOGGER + child = lt.get_thing_logger("thing_name") + assert isinstance(child, logging.Logger) + assert child.name == logs.THING_LOGGER.name + ".thing_name"