fix(db): bridge go-sql-driver/mysql logger to slog (bookshelf-xrfax) #1238

Merged
zombor merged 1 commit from bd-bookshelf-xrfax into main 2026-07-25 01:27:00 +00:00
Owner

Summary

Plain-text [mysql] ... driver lines bypassed the app's structured JSON logging. This PR installs a DriverLogger adapter so driver self-healing messages (e.g. "closing bad idle connection: EOF") become structured Warn-level slog entries with \"component\":\"mysql_driver\".

  • internal/db/driver_logger.go: exported DriverLogger + NewDriverLogger adapting *slog.Logger to the go-sql-driver/mysql Logger interface ({ Print(v ...any) }); unexported installDriverLogger calls mysqldriver.SetLogger once. The driver import is aliased as mysqldriver to avoid collision with the existing golang-migrate/database/mysql import in db.go.
  • internal/db/db.go: Open calls installDriverLogger(logger) on entry.
  • internal/db/driver_logger_test.go: black-box test (package db_test) asserting message, component="mysql_driver" attr, and WARN level in JSON output. installDriverLogger is covered via the existing db.Open integration tests.

Test plan

  • make build passes
  • make lint on ./internal/db/ — 0 issues
  • make test (unit) — all pass
  • CI integration job runs internal/db tests including DriverLogger specs

Closes bead bookshelf-xrfax on merge.

## Summary Plain-text `[mysql] ...` driver lines bypassed the app's structured JSON logging. This PR installs a `DriverLogger` adapter so driver self-healing messages (e.g. \"closing bad idle connection: EOF\") become structured Warn-level slog entries with `\"component\":\"mysql_driver\"`. - `internal/db/driver_logger.go`: exported `DriverLogger` + `NewDriverLogger` adapting `*slog.Logger` to the `go-sql-driver/mysql` `Logger` interface (`{ Print(v ...any) }`); unexported `installDriverLogger` calls `mysqldriver.SetLogger` once. The driver import is aliased as `mysqldriver` to avoid collision with the existing `golang-migrate/database/mysql` import in `db.go`. - `internal/db/db.go`: `Open` calls `installDriverLogger(logger)` on entry. - `internal/db/driver_logger_test.go`: black-box test (`package db_test`) asserting message, `component="mysql_driver"` attr, and `WARN` level in JSON output. `installDriverLogger` is covered via the existing `db.Open` integration tests. ## Test plan - [ ] `make build` passes - [ ] `make lint` on `./internal/db/` — 0 issues - [ ] `make test` (unit) — all pass - [ ] CI integration job runs `internal/db` tests including `DriverLogger` specs Closes bead bookshelf-xrfax on merge.
fix(db): bridge go-sql-driver/mysql logger to slog (bookshelf-xrfax)
All checks were successful
/ JS Unit Tests (pull_request) Successful in 2m17s
/ Test Race (pull_request) Successful in 3m55s
/ E2E API (pull_request) Successful in 3m28s
/ Coverage (pull_request) Successful in 4m37s
/ Integration (pull_request) Successful in 6m5s
/ Lint (pull_request) Successful in 6m24s
/ E2E Browser (pull_request) Successful in 6m26s
fcf1583d60
Plain-text [mysql] lines (e.g. "closing bad idle connection: EOF") bypassed
the app's structured JSON logging. Install a DriverLogger adapter via
mysqldriver.SetLogger so driver self-healing messages become structured
Warn-level slog entries with component="mysql_driver".

- New internal/db/driver_logger.go: exported DriverLogger + NewDriverLogger
  adapting *slog.Logger to the driver's { Print(v ...any) } interface;
  unexported installDriverLogger calls mysqldriver.SetLogger once.
- Aliases the driver import as mysqldriver to avoid collision with the
  existing golang-migrate/database/mysql import in db.go.
- db.Open calls installDriverLogger(logger) on entry (last-write-wins global
  side-effect is fine across the 4 Open callers in a single process).
- Black-box test (package db_test) asserts message text, component attr,
  and Warn level in the JSON output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Security Review — PR #1238 (driver log → slog)

Adversarial focus: credential leakage via the mysql driver logger, log injection/forging, global SetLogger abuse, new boundaries.

(1) Secret leakage — CLEAR. Audited every call site that reaches the driver's Logger.Print in go-sql-driver/mysql v1.9.3 (connection.go:64 mc.log, connector.go:119/148, packets.go:58/68/86/100/155, auth.go:341, connection.go:189/214/706). All log error strings, auth-plugin names, or sequence numbers. cfg.Passwd is referenced only in auth.go (scramble) and dsn.go:262 (FormatDSN) — neither reaches the logger. The DSN itself is never passed to Print. So this path cannot surface credentials and does not bypass maskDSN (the DSN never enters it).

(2) Log injection / forging — CLEAR. The driver message is passed to logger.Warn(msg). slog handlers escape the msg field (JSONHandler JSON-escapes newlines/quotes; TextHandler quotes on special chars), so no newline-forging of fake log records is possible. Driver messages are driver-internal, not app-user input; the only externally-influenced content (a server error string) requires a trusted-DB compromise and is escaped regardless.

(3) Global SetLogger side-effect — CLEAR. installDriverLogger sets it to the app's own *slog.Logger (last-caller-wins is documented). No external input controls the installed logger; not abusable.

(4) New inputs/boundaries — none. No new request surface, no user-supplied data, no multi-user scoping surface.

No findings.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — PR #1238 (driver log → slog) Adversarial focus: credential leakage via the mysql driver logger, log injection/forging, global `SetLogger` abuse, new boundaries. **(1) Secret leakage — CLEAR.** Audited every call site that reaches the driver's `Logger.Print` in go-sql-driver/mysql v1.9.3 (`connection.go:64` `mc.log`, `connector.go:119/148`, `packets.go:58/68/86/100/155`, `auth.go:341`, `connection.go:189/214/706`). All log error strings, auth-plugin names, or sequence numbers. `cfg.Passwd` is referenced only in `auth.go` (scramble) and `dsn.go:262` (`FormatDSN`) — neither reaches the logger. The DSN itself is never passed to `Print`. So this path cannot surface credentials and does not bypass `maskDSN` (the DSN never enters it). **(2) Log injection / forging — CLEAR.** The driver message is passed to `logger.Warn(msg)`. slog handlers escape the `msg` field (JSONHandler JSON-escapes newlines/quotes; TextHandler quotes on special chars), so no newline-forging of fake log records is possible. Driver messages are driver-internal, not app-user input; the only externally-influenced content (a server error string) requires a trusted-DB compromise and is escaped regardless. **(3) Global `SetLogger` side-effect — CLEAR.** `installDriverLogger` sets it to the app's own `*slog.Logger` (last-caller-wins is documented). No external input controls the installed logger; not abusable. **(4) New inputs/boundaries — none.** No new request surface, no user-supplied data, no multi-user scoping surface. No findings. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Code Review: bookshelf-xrfax

Review of the mysql driver logger bridge to slog (new internal/db/driver_logger.go, integration into db.Open).

Phase 1: Spec Compliance

  • Bridges go-sql-driver/mysql logger to slog ✓
  • Emits structured JSON with component=mysql_driver ✓
  • Appropriate Warn level for benign driver events ✓

Phase 2: Code Quality

No findings. All checks pass:

  1. Import aliasing: mysqldriver "github.com/go-sql-driver/mysql" (line 7 of driver_logger.go) correctly aliases the import to avoid confusion with golang-migrate's mysql package. Zero collision risk.

  2. Print() formatting: Line 28 uses strings.TrimSpace(fmt.Sprintln(v...)) — correct. Sprintln adds a newline; TrimSpace removes it, preventing double-newlines in slog JSON output. No allocation concern (driver logs are rare, not hot-path).

  3. Warn level: Appropriate for self-healing driver events (e.g., "closing bad idle connection: EOF"). Not Error (alert noise), not Info (log spam). Good judgment in docstring (line 26).

  4. Global side-effect in db.Open: Intentional and idempotent. SetLogger last-write-wins; all Open calls in a process share the same driver logger (by design). Error from SetLogger correctly ignored (only fails on nil logger, which never happens). Behavior well-documented in installDriverLogger docstring (lines 33–35).

  5. Black-box test: package db_test (not db), exercises only public interface (NewDriverLogger, Print). Three Its, one Expect each (project convention). Asserts message content, component attribute ("mysql_driver"), and Warn level — all three behavioral aspects of the integration covered.

  6. Exports justified: DriverLogger and NewDriverLogger exported per project-conventions rule of wrapping third-party interfaces at the boundary. Comment (lines 12–14) explicitly cites the justification. Correct pattern.

  7. Coverage: installDriverLogger called from db.Open (line 76), which has extensive integration test coverage (books_integration_test, comic_integration_test, db_test, slow_query_test, etc.). Should hit 100%.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Code Review: bookshelf-xrfax Review of the mysql driver logger bridge to slog (new `internal/db/driver_logger.go`, integration into `db.Open`). **Phase 1: Spec Compliance** - Bridges go-sql-driver/mysql logger to slog ✓ - Emits structured JSON with component=mysql_driver ✓ - Appropriate Warn level for benign driver events ✓ **Phase 2: Code Quality** No findings. All checks pass: 1. **Import aliasing:** `mysqldriver "github.com/go-sql-driver/mysql"` (line 7 of driver_logger.go) correctly aliases the import to avoid confusion with golang-migrate's mysql package. Zero collision risk. 2. **Print() formatting:** Line 28 uses `strings.TrimSpace(fmt.Sprintln(v...))` — correct. Sprintln adds a newline; TrimSpace removes it, preventing double-newlines in slog JSON output. No allocation concern (driver logs are rare, not hot-path). 3. **Warn level:** Appropriate for self-healing driver events (e.g., "closing bad idle connection: EOF"). Not Error (alert noise), not Info (log spam). Good judgment in docstring (line 26). 4. **Global side-effect in db.Open:** Intentional and idempotent. SetLogger last-write-wins; all Open calls in a process share the same driver logger (by design). Error from SetLogger correctly ignored (only fails on nil logger, which never happens). Behavior well-documented in installDriverLogger docstring (lines 33–35). 5. **Black-box test:** `package db_test` (not `db`), exercises only public interface (NewDriverLogger, Print). Three Its, one Expect each (project convention). Asserts message content, component attribute ("mysql_driver"), and Warn level — all three behavioral aspects of the integration covered. 6. **Exports justified:** DriverLogger and NewDriverLogger exported per project-conventions rule of wrapping third-party interfaces at the boundary. Comment (lines 12–14) explicitly cites the justification. Correct pattern. 7. **Coverage:** installDriverLogger called from db.Open (line 76), which has extensive integration test coverage (books_integration_test, comic_integration_test, db_test, slow_query_test, etc.). Should hit 100%. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Author
Owner

Security Review — PR #1238 (mysql driver log → slog)

Adversarial focus: credential leakage via the driver logger, log injection/forging, global SetLogger abuse, new boundaries.

(1) Secret leakage — CLEAR. Audited every call site that reaches the driver's Logger.Print in go-sql-driver/mysql v1.9.3: connection.go:64 (mc.log), connector.go:119/148, packets.go:58/68/86/100/155, auth.go:341, connection.go:189/214/706. All emit error strings, auth-plugin names, or sequence numbers. cfg.Passwd is referenced only in auth.go (scramble) and dsn.go:262 (FormatDSN) — neither reaches the logger, and the DSN itself is never passed to Print. So this path cannot surface credentials and does not bypass maskDSN (the DSN never enters it).

(2) Log injection / forging — CLEAR. The driver message is passed to logger.Warn(msg). slog handlers escape the msg field (JSONHandler JSON-escapes newlines/quotes; TextHandler quotes on special chars), so an embedded newline cannot forge a fake log record. Driver messages are driver-internal, not app-user input; the only externally-influenced content (a server-returned error string) presupposes a trusted-DB compromise and is escaped regardless.

(3) Global SetLogger side-effect — CLEAR. installDriverLogger sets the driver's package logger to the app's own *slog.Logger (last-caller-wins, documented). No external input controls the installed logger; not abusable.

(4) New inputs/boundaries — none. No new request surface, no user-supplied data, no multi-user-scoping surface.

No findings.

REVIEW VERDICT: 0 blocker, 0 major, 0 minor

## Security Review — PR #1238 (mysql driver log → slog) Adversarial focus: credential leakage via the driver logger, log injection/forging, global `SetLogger` abuse, new boundaries. **(1) Secret leakage — CLEAR.** Audited every call site that reaches the driver's `Logger.Print` in go-sql-driver/mysql v1.9.3: `connection.go:64` (`mc.log`), `connector.go:119`/`148`, `packets.go:58/68/86/100/155`, `auth.go:341`, `connection.go:189/214/706`. All emit error strings, auth-plugin names, or sequence numbers. `cfg.Passwd` is referenced only in `auth.go` (scramble) and `dsn.go:262` (`FormatDSN`) — neither reaches the logger, and the DSN itself is never passed to `Print`. So this path cannot surface credentials and does not bypass `maskDSN` (the DSN never enters it). **(2) Log injection / forging — CLEAR.** The driver message is passed to `logger.Warn(msg)`. slog handlers escape the `msg` field (JSONHandler JSON-escapes newlines/quotes; TextHandler quotes on special chars), so an embedded newline cannot forge a fake log record. Driver messages are driver-internal, not app-user input; the only externally-influenced content (a server-returned error string) presupposes a trusted-DB compromise and is escaped regardless. **(3) Global `SetLogger` side-effect — CLEAR.** `installDriverLogger` sets the driver's package logger to the app's own `*slog.Logger` (last-caller-wins, documented). No external input controls the installed logger; not abusable. **(4) New inputs/boundaries — none.** No new request surface, no user-supplied data, no multi-user-scoping surface. No findings. REVIEW VERDICT: 0 blocker, 0 major, 0 minor
zombor force-pushed bd-bookshelf-xrfax from fcf1583d60
All checks were successful
/ JS Unit Tests (pull_request) Successful in 2m17s
/ Test Race (pull_request) Successful in 3m55s
/ E2E API (pull_request) Successful in 3m28s
/ Coverage (pull_request) Successful in 4m37s
/ Integration (pull_request) Successful in 6m5s
/ Lint (pull_request) Successful in 6m24s
/ E2E Browser (pull_request) Successful in 6m26s
to 7f557b39c8
All checks were successful
/ Test Race (pull_request) Successful in 3m27s
/ E2E API (pull_request) Successful in 2m52s
/ JS Unit Tests (pull_request) Successful in 1m46s
/ Coverage (pull_request) Successful in 4m30s
/ Lint (pull_request) Successful in 5m29s
/ Integration (pull_request) Successful in 6m30s
/ E2E Browser (pull_request) Successful in 5m52s
2026-07-25 01:14:58 +00:00
Compare
zombor merged commit 5e789d0932 into main 2026-07-25 01:27:00 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!1238
No description provided.