-
Couldn't load subscription status.
- Fork 2
Invocation context variables #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: thing-connection
Are you sure you want to change the base?
Conversation
This new module uses context variables to provide cancellation and logging to action invocations. It will replace the various dependencies `InvocationID`, `InvocationLogger`, and `CancelHook`.
The actions module now uses the new cancellation/logging code. Said code is available via top-level imports. I'm currently still generating invocation IDs using the dependency. This will need to stay until we remove the dependency, as without it the other dependencies (`CancelHook` and `InvocationLogger`) will break.
I've not made a dedicated page (yet) but have added this to the conceptual docs on actions and concurrency. Test code achieves 100% coverage of the new module from `test_invocation_contexts`.
Code testing dependencies is now moved into a submodule, pending their removal. The tests for cancellation and logging are duplicated: the old copies are in the submodule, but I have also migrated them to the new API in the main tests folder.
|
After reading the logging docs I think our current approach of creating a new logger name for every invocation is explicitly discouraged (because loggers don't get garbage collected). I think a good solution would be to use a filter to add context information to the This would result in a property A side effect of this approach is that I no longer think it's reasonable to raise an exception if the logger is used without the invocation context variable being set: exceptions in logging code are usually unhelpful. If the logger is used from outside an action, the log message should still be logged, but without the invocation context data. In the future, it would be nice to think more about how to relay progress to a user/client. I think our current use of a logger for this has some advantages, but also some drawbacks. |
|
After discussing with @julianstirling and @bprobert97 I think logs should go via @julianstirling was also strongly in favour of adding a second function with a descriptive name, rather than |
In response to feedback, I've added `raise_if_cancelled()` to replace `cancellable_sleep(None)`. In response to the same feedback, I now handle the error if no invocation ID is available, so we simply perform a regular time.sleep. I've also deleted a couple of defunct print statements.
This commit adds a logger for every Thing instance, and a custom log handler and filter that inject the invocation ID into LogRecord objects. This means we can still filter out invocation logs as we did before, but we no longer need to make a new logger for each invocation. This more or less follows the example given in the Logging Cookbook for adding context.
For ease of migration, I've fixed the old InvocationLogger dependency. This uses a hard-coded Thing name, but will work well enough to enable a smooth migration to the new syntax.
The test for ThreadWithInvocationID occasionally failed, because the thread was cancelled before it started running, and the CancelEvent was destroyed (and reset). I now hold a reference to the CancelEvent for the lifetime of the Thread, which means this is no longer possible.
This adds full unit test coverage, in addition to the more functional testing in `test_action_logging`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand this a bit more than the last two.
I think the upshot of this reviewing is that it is very hard to keep 4 complex PRs that interact in my head. I think it would be good once we have merged the 3 later PRs into the base one, to do a final review of #183
| logger = logging.getLogger(f"labthings_fastapi.actions.{id}") | ||
| logger.setLevel(logging.INFO) | ||
| return logger | ||
| return THING_LOGGER.getChild("OLD_DEPENDENCY_LOGGER") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this would have meant that invocation loggers the old way wouldn't work? But this is superseded I think by #191
| from labthings_fastapi import invocation_contexts as ic | ||
| from labthings_fastapi import exceptions as exc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unhelpful when reading tests.
This pull request introduces two new top-level functions:
lt.cancellable_sleep(interval)is equivalent totime.sleepbut may raiseInvocationCancelledErrorto stop the action.lt.get_invocation_logger()supplies alogging.Loggerobject that captures logs specific to the current invocation.Together, these two functions remove the need for the
InvocationLoggerandCancelHookdependencies, completing the third phase of #182 .I have also added
lt.ThreadWithInvocationID(name might want to change) which is a subclass ofthreading.Threadthat supplies an invocation ID. This allows an action to be run in a background thread without errors because it's missing an invocation ID context. It also allows the thread to be cancelled.This is mostly implemented with one new module,
invocation_contexts. I've movedCancelEventfrom the invocation dependencies module into the new module, and the dependencies now useinvocation_contextsso they still work.The one place where dependencies are still used is in generating invocation IDs for actions that are invoked over HTTP. Because of the way FastAPI's dependency mechanism works, I can't get rid of this without breaking both of the dependencies mentioned above. I intend to leave them in for now to facilitate migration, but the whole module will be deleted before too long.
I have written new, bottom-up unit tests in
test_invocation_contexts(with 100% coverage of the new module) and have also migratedtest_action_cancellationandtest_action_loggingto use the new API. The old tests are preserved in a submodule, along with other dependency-related tests.I've deliberately not exposed all the functions at top-level in the module: I think the two functions and one class I've exposed are the API we want people to use. I should make
fake_action_contextvisible, most likely in a test module, so one could sayfrom labthings_fastapi.testing import fake_action_context. That would be a natural home forcreate_thing_without_serverand maybe a few other things.