Skip to content

Commit b47e3b5

Browse files
feat(kernel): surface kernel logs through Python logging on use_kernel
When the kernel backend loads, auto-initialize the kernel's tracing -> Python logging bridge so `use_kernel=True` users see kernel logs with no extra setup. Kernel logs land under the `databricks.sql.kernel` logger (a child of the connector's `databricks.sql.*` namespace), so an existing `logging.getLogger("databricks.sql").setLevel(...)` cascades to them. - `_errors.py` calls `databricks_sql_kernel.init_logging()` once at extension load (it's the canonical kernel-import site). The call is `getattr`-guarded so an older kernel wheel without the function still works — just without kernel logs. - e2e tests assert kernel records reach the `databricks.sql.kernel` logger (and the pyo3 boundary under `databricks.sql.kernel.pyo3`) and that the level set on the logger is respected. Creds-gated per the existing kernel e2e convention. Requires the companion kernel/pyo3 change (databricks-sql-kernel#120) that exposes `init_logging()`. Co-authored-by: Isaac Signed-off-by: Vikrant Puppala <vikrant.puppala@databricks.com>
1 parent 614dd91 commit b47e3b5

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

src/databricks/sql/backend/kernel/_errors.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@
5757
"(into the same venv as databricks-sql-connector)."
5858
) from exc
5959

60+
# Route the kernel's Rust-side logs into Python's ``logging`` as soon as
61+
# the extension loads. The kernel emits under the ``databricks.sql.kernel``
62+
# logger (a child of the connector's ``databricks.sql`` namespace), so a
63+
# customer who configures ``databricks.sql`` logging gets kernel logs for
64+
# free with no extra setup. ``init_logging`` is idempotent on the Rust
65+
# side; ``getattr`` guards against an older kernel wheel that predates the
66+
# function so ``use_kernel=True`` still works (just without kernel logs).
67+
_kernel_init_logging = getattr(_kernel, "init_logging", None)
68+
if _kernel_init_logging is not None:
69+
_kernel_init_logging()
70+
6071

6172
# Map a kernel `code` slug to the PEP 249 exception class that best
6273
# captures it. The match isn't a perfect 1:1 — PEP 249 has a

tests/e2e/test_kernel_backend.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,73 @@ def test_fetchall_arrow(conn):
127127
assert table.column_names == ["a", "b"]
128128

129129

130+
# ─── Logging (Rust kernel -> Python logging bridge) ──────────────────────────
131+
#
132+
# Layer 3 of the logger-name drift guard (see also the Rust tests
133+
# `klog::tests::klog_emits_contract_target` and
134+
# `logging::tests::kernel_target_matches_contract` in the kernel repo).
135+
# Asserts the *customer-facing* contract end-to-end: kernel logs reach
136+
# Python `logging` under the `databricks.sql.kernel` logger, respect the
137+
# level set on it, and the pyo3 boundary surfaces under
138+
# `databricks.sql.kernel.pyo3`. If the kernel's tracing target or the
139+
# pyo3-log wiring ever drifts, these fail.
140+
141+
import logging
142+
143+
144+
def test_kernel_logs_reach_python_logging(kernel_conn_params, caplog):
145+
"""A query at DEBUG produces records on the `databricks.sql.kernel`
146+
logger — proving the tracing -> log -> pyo3-log -> logging chain."""
147+
with caplog.at_level(logging.DEBUG, logger="databricks.sql.kernel"):
148+
c = sql.connect(**kernel_conn_params)
149+
try:
150+
with c.cursor() as cur:
151+
cur.execute("SELECT 1 AS a")
152+
cur.fetchall()
153+
finally:
154+
c.close()
155+
156+
kernel_records = [
157+
r for r in caplog.records if r.name.startswith("databricks.sql.kernel")
158+
]
159+
assert kernel_records, (
160+
"expected log records under the 'databricks.sql.kernel' logger; "
161+
"the kernel tracing -> Python logging bridge did not deliver any"
162+
)
163+
# The core kernel logger (not just the .pyo3 child) must be present:
164+
assert any(
165+
r.name == "databricks.sql.kernel" for r in kernel_records
166+
), "expected core kernel records on the exact 'databricks.sql.kernel' logger"
167+
# The pyo3 boundary breadcrumb must surface under the .pyo3 child:
168+
assert any(
169+
r.name == "databricks.sql.kernel.pyo3" for r in kernel_records
170+
), "expected pyo3-boundary records on 'databricks.sql.kernel.pyo3'"
171+
172+
173+
def test_kernel_log_level_is_respected(kernel_conn_params, caplog):
174+
"""At WARNING, the chatty DEBUG kernel records are filtered out
175+
before reaching Python — proving level control works (and that
176+
filtering happens, not that everything is forwarded unconditionally)."""
177+
with caplog.at_level(logging.WARNING, logger="databricks.sql.kernel"):
178+
c = sql.connect(**kernel_conn_params)
179+
try:
180+
with c.cursor() as cur:
181+
cur.execute("SELECT 1 AS a")
182+
cur.fetchall()
183+
finally:
184+
c.close()
185+
186+
debug_records = [
187+
r
188+
for r in caplog.records
189+
if r.name.startswith("databricks.sql.kernel") and r.levelno < logging.WARNING
190+
]
191+
assert not debug_records, (
192+
"DEBUG/INFO kernel records leaked through at WARNING level: "
193+
f"{[(r.name, r.levelname, r.getMessage()) for r in debug_records]}"
194+
)
195+
196+
130197
# ── Metadata ──────────────────────────────────────────────────────
131198

132199

0 commit comments

Comments
 (0)