Diagnostics
grelmicro reports a handful of configuration problems: a variable that will not be applied, a backend that cannot keep its promise, a middleware that is missing, a leader election that never runs. Each one carries a stable code so you can grep it, look it up, and assert on it in a test without pinning the wording.
A code is a slug such as backend-scope. It carries no severity letter,
because the same problem is a warning in one deployment and an error in
another. An unmet backend scope is reported when no tier is declared and
refused in staging and production. Severity belongs to the report, not to
the identity of the problem.
Reading a report
The code trails the sentence, followed by the page that explains it:
Coordination('default') is bound to MemoryLockAdapter, which provides scope
'process', but requires scope 'cluster'. [backend-scope] https://grelmicro.grel.info/diagnostics/#backend-scope
The same code travels as a structured field on the log record, so a JSON log
stream can be filtered on diagnostic without parsing the message.
Silencing
There are two kinds of report and they are silenced differently. grelmicro does not pretend otherwise.
A warning has a category of its own. Filter on the category, never on the message text:
# pyproject.toml
[tool.pytest.ini_options]
filterwarnings = [
"error",
"ignore::grelmicro.BackendScopeWarning",
]
Every category derives from GrelmicroConfigWarning, so one filter silences
them all:
filterwarnings = ["ignore::grelmicro.GrelmicroConfigWarning"]
The same names work with -W and PYTHONWARNINGS, and -W error promotes any
of them to an exception.
A coming change of default or name warns with GrelmicroFutureWarning
instead. It is a FutureWarning, not a configuration diagnostic, so it carries
no code and the filters above leave it alone. See
About deprecation.
An error is raised and cannot be filtered. It is averted by fixing the configuration it names. Each entry below says which.
The codes
| Code | Warning category | Error | What it means |
|---|---|---|---|
env-load-off |
EnvLoadOffWarning |
none | A GREL_* variable is set but GREL_ENV_LOAD is off, so it was not applied. |
unknown-environment |
UnknownEnvironmentWarning |
none | GREL_ENVIRONMENT names no known tier, so the backend check runs as if undeclared. |
backend-scope |
BackendScopeWarning |
BackendScopeError |
A bound backend reaches less far than its component requires. |
ambient-binding |
AmbientBindingWarning |
AmbientBindingError |
Ambient components are registered but the binding middleware is missing. |
middleware-placement |
MiddlewarePlacementWarning |
MiddlewarePlacementError |
A grelmicro middleware wraps middleware the app declared itself, so it would answer before them, cannot see a path it serves, or cannot be added. |
provider-order |
none | LifecycleOrderError |
A Provider is listed after the Component that borrows it. |
sentinel-password |
SentinelPasswordWarning |
none | A Sentinel password is set but the URL scheme cannot apply it. |
leader-not-running |
LeaderNotRunningWarning |
none | A task is gated on a leader election that does not run, so it skips every fire. |
ungated-cron |
UngatedCronWarning |
none | A cron task with no gate= runs each fire on every worker, although a schedule backend is registered. |
gate-backend |
Grelmicro045Warning in staging and production |
none | A gated task resolves no backend to claim through, so every fire runs nothing. Reported by grelmicro check and micro.describe(). |
health-not-served |
HealthNotServedWarning |
none | A HealthChecks is registered but nothing serves its endpoints, so probes get 404. |
routes-not-declared |
RoutesNotDeclaredWarning |
none | AuthenticatedRequests is installed through an integration with no install_route_gate, so every route needs a credential, Anonymous() included. grelmicro check also reports an HTTP integration with no route_declarations when a component acting per endpoint is registered. |
env-load-off
GREL_ENV_LOAD is set to a falsy value such as 0, which turns
environment-driven configuration off. Unset it or set GREL_ENV_LOAD=1 to read
the variable, or pass the value directly in code. See
Configuration.
unknown-environment
Set GREL_ENVIRONMENT to development, test, staging, or production.
backend-scope
Wire a backend that reaches far enough, or pass requires= to declare the
reach you meant. A warning with no tier declared, a BackendScopeError in
staging and production. See the backend check.
A @cached(lock=...) fold is the exception. It warns in every tier that
reports it, staging and production included, and its first miss raises.
ambient-binding
Call micro.install(app), including on every mounted sub-application. Raises
AmbientBindingError under Grelmicro(strict=True) and when another
middleware wraps GrelmicroMiddleware. See Wiring.
middleware-placement
Litestar builds its middleware stack when the app is constructed, so
micro.install(app) can only wrap the whole thing. A middleware of ours that
answers a request itself, such as an idempotent replay, would then answer
before the app's own middleware runs, authentication included.
Pass it at construction instead, which puts it inside the stack:
app = Litestar(
route_handlers=[...],
middleware=[
DefineMiddleware(AuthMiddleware),
DefineMiddleware(IdempotencyMiddleware, idempotency=Idempotency("http")),
],
)
micro.install(app) # finds it already wired, and leaves it alone
The OpenAPI schema describes the registered component, not the middleware passed by hand. Register the component with the same settings you pass, or the schema leaves the middleware out.
FastAPI and Starlette never raise this: install puts grelmicro's middleware
behind everything the app added itself.
On Litestar, micro.install(app) also warns when an
AuthenticatedRequestsMiddleware passed to Litestar(middleware=[...])
publishes protected resource metadata, and a GET for the document every
challenge points at never reaches it. With no route at the metadata path,
register AuthenticatedRequests and call micro.install(app), which adds that
route. With a route of your own there for other methods only, remove it: the
middleware answers every request at that path.
micro.install(app) raises MiddlewarePlacementError when
AuthenticatedRequests is registered and the app's integration has no
install_middleware. Nothing would add AuthenticatedRequestsMiddleware, so
no request would be authenticated. The app is refused before anything is
wired. Ship install_middleware, install_route_gate and route_declarations
in the integration, as Plugins
shows. An app whose integration's health_endpoints(app) returns None, which
says the app serves no HTTP, is not refused.
provider-order
List providers before the components that borrow them. grelmicro reorders them
for you by default, and raises LifecycleOrderError under
Grelmicro(strict=True), which asks for the list you wrote to be the list that
runs.
sentinel-password
The password configures the Sentinel servers, which only a redis+sentinel://
URL connects to. Set it with sentinel_password= or the matching variable.
leader-not-running
A LeaderElection acquires leadership only while it runs. A task gated on one
that nothing runs skips every fire. The task reports it once, on the first fire
it skips a lease duration after it started. Register the election beside the
task with tasks.add_task(election), or start it yourself. An election started
later, by another Tasks or a lifespan, is fine as long as it runs within the
lease duration. See Leader Election.
python -m grelmicro check and micro.describe() report the same code, as a
warning, for a task gated on an election that no registered Tasks holds. An
election you start yourself still shows it, so read it as a reminder.
ungated-cron
A cron task runs each fire on every worker unless it passes gate=. With a
schedule backend registered, a task that leaves gate= out is reported each
time it starts. Pass gate="claim" to run each fire on one worker, or gate=None to
keep every worker. Quiet in development and test. python -m grelmicro
check shows the same check. See Run on one worker.
gate-backend
A task gated with "claim", a LeaderElection or a TaskLock claims each
fire through a backend: the schedule backend for a cron task, the lock backend
for an interval task. With none to resolve, every fire reports a
coordination_error and runs nothing. Register a
Coordination that holds that backend, or pass
backend= to the task or its TaskLock.
python -m grelmicro check and micro.describe() report it as a warning in
every tier, since such a task runs nothing anywhere. In staging and
production the report says it fails there from 0.45.0, and building the
report warns with Grelmicro045Warning.
health-not-served
micro.install(app) does not mount the health endpoints. Serve them with
health_router() on FastAPI, health_asgi() on another ASGI framework, or
OpsServer on a port of their own. A router or a mount built for a named
HealthChecks serves that one, and one built with no argument, like
OpsServer, serves the default. See Health.
An OpsServer switched off with enabled=False or GREL_OPS_ENABLED=false
serves nothing, and the message says it is switched off. Set
GREL_OPS_ENABLED=true to switch it on.
Reported once the app opens, only when it is installed on an app that serves
HTTP, and quiet in development and test. python -m grelmicro check shows
the same check. If you serve the checks some other way, filter
HealthNotServedWarning.
routes-not-declared
AuthenticatedRequests decides which route serves a caller with no
credential from the declarations an integration hands it, once the framework
routed the request. An integration without install_route_gate declares no
route, so every route answers 401 to a request without a credential, a
route declaring Anonymous() included. A token is verified as usual.
Reported by micro.install(app). Ship install_route_gate and
route_declarations in the integration, as
Declare the routes shows. Until
then, list the public paths in exclude=.
python -m grelmicro check and micro.describe(app) report the same code
when a component acting per endpoint, such as CachedResponses or
AccessLog, is registered and the integration serves HTTP without
route_declarations. The endpoint table then stays empty and no
include= pattern is checked until the integration ships it.