Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/source/public_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions src/labthings_fastapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -64,6 +65,7 @@
"cancellable_sleep",
"cli",
"endpoint",
"get_thing_logger",
"outputs",
"property",
"raise_if_cancelled",
Expand Down
22 changes: 22 additions & 0 deletions src/labthings_fastapi/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions src/labthings_fastapi/thing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -148,7 +148,7 @@
@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.
Expand Down Expand Up @@ -294,7 +294,7 @@
# Load the key from the JSON file using the setting's model
setting.set(setting.validate(value))
except ValidationError:
self.logger.warning(

Check warning on line 297 in src/labthings_fastapi/thing.py

View workflow job for this annotation

GitHub Actions / coverage

297 line is not covered with tests
f"Could not load setting {name} from settings file "
f"because of a validation error.",
exc_info=True,
Expand Down Expand Up @@ -366,9 +366,9 @@
Some measure of caching here is a nice aim for the future, but not yet
implemented.
"""
if self._labthings_thing_state is None:
self._labthings_thing_state = {}
return self._labthings_thing_state

Check warning on line 371 in src/labthings_fastapi/thing.py

View workflow job for this annotation

GitHub Actions / coverage

369-371 lines are not covered with tests

def validate_thing_description(self) -> None:
"""Raise an exception if the thing description is not valid."""
Expand Down Expand Up @@ -459,7 +459,7 @@
inv_id = get_invocation_id()
server = self._thing_server_interface._server()
if server is None:
raise RuntimeError("Could not get server from thing_server_interface")

Check warning on line 462 in src/labthings_fastapi/thing.py

View workflow job for this annotation

GitHub Actions / coverage

462 line is not covered with tests
action_manager = server.action_manager
this_invocation = action_manager.get_invocation(inv_id)
return this_invocation.log
8 changes: 8 additions & 0 deletions tests/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading