feat: add logging levels and slog based logging
Summary
This PR introduces slog based logging (Issue #1698)
- 4 logging levels -> LogLevel:
INFO|DEBUG|WARN|ERROR - Deprecates
DebugandDebugf.
Both replaced byLogLevelfor unified level-based logging.
Creates a ClickHouse client using given address/auth credentials and enables debug-level logging.
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"127.0.0.1:9000"},
Auth: clickhouse.Auth{
Database: "nothing",
Username: "nothing",
Password: "nothing",
},
LogLevel: slog.LevelDebug,
})
Note: By default logging is set to slog.LevelInfo
Lastly, this is an initial iteration; I want maintainer's direct feedback on any required changes so I can apply them.
Sample Logs for refrence
time=2025-11-18T18:30:02.813+05:30 level=DEBUG msg="handshake: send" driver=clickhouse addr=127.0.0.1:9000 id=1 handshake=" 0.0.0"
time=2025-11-18T18:30:02.814+05:30 level=DEBUG msg="handshake: receive" driver=clickhouse addr=127.0.0.1:9000 id=1 server="ClickHouse (fe0ac18aec40) server version 25.10.2 revision 54482 (timezone UTC)"
time=2025-11-18T18:30:02.814+05:30 level=DEBUG msg="acquired new" driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.814+05:30 level=DEBUG msg=ping driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.814+05:30 level=DEBUG msg="ping request" driver=clickhouse addr=127.0.0.1:9000 id=1 payload=ping
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg="ping response" driver=clickhouse addr=127.0.0.1:9000 id=1 result=pong
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg=released driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg="acquired from pool" driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg="query row" driver=clickhouse addr=127.0.0.1:9000 id=1 query="SELECT 1"
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg="send query" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none body="SELECT 1"
time=2025-11-18T18:30:02.815+05:30 level=DEBUG msg="send data" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="read data" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none block=1 rows=0
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="read data" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none block=1 rows=1
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="profile info" driver=clickhouse addr=127.0.0.1:9000 id=1 info="rows=1, bytes=4104, blocks=1, rows before limit=0, applied limit=false, calculated rows before limit=true"
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg=progress driver=clickhouse addr=127.0.0.1:9000 id=1 progress="rows=1, bytes=1, total rows=1, wrote rows=0 wrote bytes=0 elapsed=2.094917ms"
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg=progress driver=clickhouse addr=127.0.0.1:9000 id=1 rows=1 bytes=1 total_rows=1 wrote_rows=0 wrote_bytes=0 elapsed=2.094917ms
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="read data" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none block=6 rows=28
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="profile events" driver=clickhouse addr=127.0.0.1:9000 id=1 rows=28
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="profile events: skipping scan" driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="read data" driver=clickhouse addr=127.0.0.1:9000 id=1 compression=none block=0 rows=0
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg=progress driver=clickhouse addr=127.0.0.1:9000 id=1 progress="rows=0, bytes=0, total rows=0, wrote rows=0 wrote bytes=0 elapsed=106.666µs"
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg=progress driver=clickhouse addr=127.0.0.1:9000 id=1 rows=0 bytes=0 total_rows=0 wrote_rows=0 wrote_bytes=0 elapsed=106.666µs
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg="end of stream" driver=clickhouse addr=127.0.0.1:9000 id=1
time=2025-11-18T18:30:02.817+05:30 level=DEBUG msg=released driver=clickhouse addr=127.0.0.1:9000 id=1
value: 1
@kavirajk
First of all my bad, I didn't run the test locally. After checking it on my branch (feat/logging) and on a clean clone of main, it consistently fails in both places. It’s also the only test that fails across all CI runs.
can you guide me a little bit ? how to resolve this.
goroutine 1068 [select, 3 minutes]:
github.com/ClickHouse/clickhouse-go/v2.(*clickhouse).startAutoCloseIdleConnections(0xc0010fade0)
/Users/p/oss/sp/clickhouse-go/clickhouse.go:342 +0x128
created by github.com/ClickHouse/clickhouse-go/v2.Open in goroutine 954
/Users/p/oss/sp/clickhouse-go/clickhouse.go:75 +0x2a0
FAIL github.com/ClickHouse/clickhouse-go/v2/tests/issues 244.567s
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/209 [no test files]
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/360 [no test files]
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/470 [no test files]
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/476 [no test files]
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/484 [no test files]
? github.com/ClickHouse/clickhouse-go/v2/tests/issues/485 [no test files]
using random seed 1763527752002031000 for std tests
2025/11/19 10:19:12 github.com/testcontainers/testcontainers-go - Connected to docker:
@kavirajk Thanks for your feedback I have reworked the implementation and updated a few things
Initial approach
LogLevel slog.Level
But this would cause ambiguity in the special case you highlighted: when both legacy debug options and the new LogLevel are set, as even if user does not set LogLevel it defaults to zero value and it is basically equal to slog.LevelInfo
This ambiguity disables us from distinguishing between these case:
- User sets
Debug = trueandLogLevel=slog.LevelInfo(This should give error) - User sets
Debug = trueand does not setLogLevel(This should not give error)
This happens because a bare slog.Level uses 0 for Info, so an unset value and an explicitly chosen Info level both appear as 0.
Other Feasible Approaches
1. Use a Pointer Level to Distinguish Unset from Set (Implemented)
LogLevel *slog.Level
level := slog.LevelDebug
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"127.0.0.1:9000"},
Auth: clickhouse.Auth{
Database: "default",
Username: "default",
Password: "pass",
},
LogLevel: &level,
})
| Debug | LogLevel | Note |
|---|---|---|
| true | nil | Uses legacy deprecated debug (backward-compatible). |
| true | not nil | Error: clickhouse: Debug is deprecated; use LogLevel instead (cannot set both). |
| false | nil | setDefaults() assigns slog.LevelInfo as the default. |
| false | valid (Debug/Info/Warn/Error) | Accepted; user explicitly sets the level. |
| false | invalid (wrong type, e.g. *int) | Compile-time error: cannot use incompatible pointer as *slog.Level. |
2. String-enum approach
Set up a public surface
type LogLevel string
const (
LevelDebug LogLevel = "debug"
LevelInfo LogLevel = "info"
LevelWarn LogLevel = "warn"
LevelError LogLevel = "error"
)
Client setup
conn, err := clickhouse.Open(&clickhouse.Options{
Addr: []string{"127.0.0.1:9000"},
Auth: ...,
LogLevel: clickhouse.LevelDebug,
})
Will require String-to-slog.Level mapping with strict validation and error.
Please let me know what you think. Thank you!
@kavirajk I have added the required methods in the new test.
@pkalsi97 I'm taking a look at it. Adding few changes on top of yours to avoid lots of duplicate logging.
@kavirajk thank for looking into this. If you could just point out what changes are needed. I'll implement.