Logging¶
water-ogcapi writes its log records to the water_ogcapi logger. That logger has its
own handler, which prints WARNING and above to stderr, and it does not propagate to the
root logger. A package that configures the root logger therefore neither receives these
records nor changes how they print, and a notebook shows each record once.
D-20 records why.
What gets logged¶
| Level | Record |
|---|---|
| WARNING | A 429 response that the transport waits out and retries |
| INFO | A retry after a connection failure, a timeout, or a 5xx response |
| INFO | A wait the quota governor adds for a low rate-limit count or a 429 |
| DEBUG | Each completed request, with its final URL, status, and attempt count |
A request that fails, on its first attempt or after its last retry, raises
ServiceError or RateLimitError without a log record, so log the exception where your
code handles it.
No record carries request headers. The API key travels only in the X-Api-Key header,
so it never reaches a log. Over HTTP/2, the hpack library logs every header value at
DEBUG, the key included, so the package raises the hpack logger to INFO at import. A
caller who sets hpack to DEBUG gets the key in those records
(D-20).
Scripts and notebooks¶
configure_logger() sets where records go:
from water_ogcapi import configure_logger
configure_logger(level="INFO") # adds retries to the console
configure_logger(verbose=True) # adds every request
configure_logger(file="logs/run.log") # console at WARNING, file at DEBUG
configure_logger(file="logs/run.log", file_only=True, file_mode="w")
Each call replaces the previous configuration, and an argument left out returns to its
default. Calling configure_logger(level="DEBUG") after
configure_logger(file="run.log") turns file logging off, so pass file= again to keep
it. configure_logger() with no arguments restores the default. Log files are written
as UTF-8 on every platform.
Configure logging before starting queries. A record logged while a call replaces the handlers can still land in the previous log file.
Applications with their own logging setup¶
AWS Lambda, web frameworks, and JSON log pipelines attach their handlers to the root
logger, and water_ogcapi does not propagate there. In Lambda, the default stderr
output still reaches CloudWatch, as plain lines that lack the request ID and the
formatting Lambda's own handler adds.
To send records through the application's handlers, remove the package's stderr handler,
turn propagation on, and set a level on the water_ogcapi logger itself:
import logging
log = logging.getLogger("water_ogcapi")
log.handlers.clear() # without this, every record also prints to stderr
log.propagate = True
log.setLevel(logging.INFO)
Set the level on water_ogcapi because the root logger's level does not filter records
that propagate up from a child logger. Without setLevel, the root handlers receive
every DEBUG record. In Lambda, run this at module level, outside the handler function,
so it runs once per execution environment. Do not call configure_logger() afterwards,
since it adds the stderr handler back.
To keep the stderr output and also send records elsewhere, add your handler to the
water_ogcapi logger. configure_logger() leaves handlers it did not create in place.
Tests¶
pytest's caplog captures through the root logger, so it sees no water_ogcapi records
until its handler is attached to that logger:
import logging
def test_rate_limit_is_logged(caplog):
log = logging.getLogger("water_ogcapi")
log.addHandler(caplog.handler)
try:
... # run the query
finally:
log.removeHandler(caplog.handler)
assert any(record.levelname == "WARNING" for record in caplog.records)
The httpx2 logger¶
The HTTP library logs to its own httpx2 logger, which propagates normally. At INFO it
writes one line per request:
These lines show up wherever the root logger is at INFO or below. The URL never holds the key (D-18). To silence them, raise that logger's level: