File() reconfigures the root logger to ERROR, silencing the host application's logging (regression in 1.0.0)

#470 · closed · 1 comments

View on GitHub ↗

d-gski

### Summary `h5pyd.File.__init__` takes the **root** logger and forces it to `ERROR`: https://github.com/HDFGroup/h5pyd/blob/v1.0.0/h5pyd/_hl/files.py#L521-L523 ```python self.log = logging.getLogger() self.log.setLevel(logging.ERROR) ``` `logging.getLogger()` with no name returns the root logger, so this reconfigures logging for the entire process, not just h5pyd. Any application that opens an `h5pyd.File` loses all its own `DEBUG`/`INFO`/`WARNING` output from that moment on. This is new in 1.0.0 — `h5pyd/_hl/files.py` in v0.24.0 contains no `logging` references at all, and that version logged under a logger named `h5pyd` (see the output below). ### Reproduction ```python import logging import h5pyd logging.basicConfig(level=logging.INFO) log = logging.getLogger("myapp") log.info("before h5pyd.File()") h5pyd.File(DOMAIN, "r", bucket=BUCKET, endpoint=ENDPOINT) log.info("after h5pyd.File()") root = logging.getLogger() print(f"h5pyd {h5pyd.version.version}: root logger level is now " f"{root.level} ({logging.getLevelName(root.level)})") ``` **h5pyd 0.24.0** — application logging intact, h5pyd logs under its own name: ``` INFO:h5pyd:status: 200 INFO:h5pyd:got domain json: 101216 bytes INFO:myapp:after h5pyd.File() h5pyd 0.24.0: root logger level is now 20 (INFO) ``` **h5pyd 1.0.0** — note the `after h5pyd.File()` line is simply gone: ``` INFO:myapp:before h5pyd.File() h5pyd 1.0.0: root logger level is now 40 (ERROR) ``` The two statements are the first thing `__init__` does, before any connection is attempted, so this is independent of the server and happens even when the open subsequently fails. ### Why this matters A library setting a level on the root logger takes over logging policy for the whole process. The effects go beyond h5pyd's own output: - **The application's logging disappears.** Every logger that doesn't set an explicit level inherits from root, so after the first `File()` the host application — and every other library in the process — is silenced below `ERROR`. For a long-running service this means losing exactly the `INFO`/`WARNING` breadcrumbs needed to diagnose an incident. - **The cause is very hard to attribute.** The change is a side effect of constructing an unrelated object, so it takes effect somewhere in the middle of a run, far from any logging setup. Someone investigating "our logs stopped after the first data read" has little reason to suspect the HDF client. - **Applications can't reliably defend against it.** The assignment happens on *every* `File()` construction, so re-asserting the level after configuring logging doesn't hold — the next file open undoes it again. Short of monkeypatching, there's no way for a caller to opt out. - **It's order-dependent.** Whether logging works depends on when the first `File()` is created relative to the application's logging configuration, which makes the resulting behaviour differ between test runs, local development, and production startup paths. - **Inconsistent levels can break more than output.** Setups that combine the root logger with per-logger levels — handlers, filters, or formatter chains that inspect `isEnabledFor()` — can end up in states their authors never anticipated, since the library has changed one half of the configuration behind them. The standard convention for libraries (see the Python logging HOWTO) is to log to a named logger, attach at most a `NullHandler`, and leave levels, handlers and formatting entirely to the application. ### Suggested fix Use a module-scoped named logger and let the application own its levels: ```python logger = logging.getLogger(__name__) # or "h5pyd", matching 0.24.0's behaviour ... self.log = logger ``` and drop the `setLevel` call entirely. If the intent was to quiet h5pyd's own chatter by default, `logging.getLogger("h5pyd").addHandler(logging.NullHandler())` achieves that without touching anyone else's configuration — the usual library convention. `self.log` is used at [`files.py:231`](https://github.com/HDFGroup/h5pyd/blob/v1.0.0/h5pyd/_hl/files.py#L231) and passed as `app_logger` at [L396](https://github.com/HDFGroup/h5pyd/blob/v1.0.0/h5pyd/_hl/files.py#L396) / [L405](https://github.com/HDFGroup/h5pyd/blob/v1.0.0/h5pyd/_hl/files.py#L405), so a named logger substitutes cleanly.

Comments

d-gski

I'll move this to the h5pyd repo.