0
# Logging and Utilities
1
2
SQLDelight Runtime provides logging and debugging utilities to help monitor database operations and troubleshoot issues. These utilities include SQL statement logging and parameter inspection capabilities.
3
4
## Capabilities
5
6
### SQL Logging Driver
7
8
A decorator that wraps any SqlDriver to provide comprehensive logging of all database operations.
9
10
```kotlin { .api }
11
/**
12
* SqlDriver decorator that logs all database operations for debugging and monitoring
13
* @param sqlDriver The underlying SqlDriver to wrap
14
* @param logger Function to receive log messages
15
*/
16
class LogSqliteDriver(
17
private val sqlDriver: SqlDriver,
18
private val logger: (String) -> Unit
19
) : SqlDriver {
20
override fun <R> executeQuery(
21
identifier: Int?,
22
sql: String,
23
mapper: (SqlCursor) -> QueryResult<R>,
24
parameters: Int,
25
binders: (SqlPreparedStatement.() -> Unit)?
26
): QueryResult<R>
27
28
override fun execute(
29
identifier: Int?,
30
sql: String,
31
parameters: Int,
32
binders: (SqlPreparedStatement.() -> Unit)?
33
): QueryResult<Long>
34
35
override fun newTransaction(): QueryResult<Transacter.Transaction>
36
override fun currentTransaction(): Transacter.Transaction?
37
override fun addListener(vararg queryKeys: String, listener: Query.Listener)
38
override fun removeListener(vararg queryKeys: String, listener: Query.Listener)
39
override fun notifyListeners(vararg queryKeys: String)
40
override fun close()
41
}
42
```
43
44
**Usage Examples:**
45
46
```kotlin
47
import app.cash.sqldelight.logs.LogSqliteDriver
48
49
// Create a logging wrapper around existing driver
50
val loggingDriver = LogSqliteDriver(
51
sqlDriver = actualDriver,
52
logger = { message ->
53
println("[SQLDelight] $message")
54
}
55
)
56
57
// Use with custom logger (e.g., Android Log)
58
val androidLoggingDriver = LogSqliteDriver(
59
sqlDriver = actualDriver,
60
logger = { message ->
61
Log.d("Database", message)
62
}
63
)
64
65
// Use with structured logging
66
val structuredLoggingDriver = LogSqliteDriver(
67
sqlDriver = actualDriver,
68
logger = { message ->
69
logger.debug("database_operation") {
70
put("sql_operation", message)
71
put("timestamp", System.currentTimeMillis())
72
}
73
}
74
)
75
76
// Create database with logging
77
val database = Database(loggingDriver)
78
79
// All operations will now be logged:
80
// [SQLDelight] QUERY
81
// SELECT * FROM users WHERE active = ?
82
// [true]
83
val users = database.userQueries.selectActiveUsers(true).executeAsList()
84
85
// [SQLDelight] EXECUTE
86
// INSERT INTO users (name, email) VALUES (?, ?)
87
// [Alice, alice@example.com]
88
database.userQueries.insertUser("Alice", "alice@example.com")
89
90
// [SQLDelight] TRANSACTION BEGIN
91
// [SQLDelight] EXECUTE
92
// UPDATE users SET last_login = ? WHERE id = ?
93
// [1609459200000, 1]
94
// [SQLDelight] TRANSACTION COMMIT
95
database.transaction {
96
database.userQueries.updateLastLogin(1, System.currentTimeMillis())
97
}
98
```
99
100
### Statement Parameter Interceptor
101
102
Utility class for intercepting and inspecting prepared statement parameters during SQL execution.
103
104
```kotlin { .api }
105
/**
106
* SqlPreparedStatement implementation that intercepts and stores parameter values for logging
107
*/
108
class StatementParameterInterceptor : SqlPreparedStatement {
109
override fun bindBytes(index: Int, bytes: ByteArray?)
110
override fun bindLong(index: Int, long: Long?)
111
override fun bindDouble(index: Int, double: Double?)
112
override fun bindString(index: Int, string: String?)
113
override fun bindBoolean(index: Int, boolean: Boolean?)
114
115
/**
116
* Get all bound parameters and clear the internal parameter list
117
* @returns List of all parameter values in binding order
118
*/
119
fun getAndClearParameters(): List<Any?>
120
}
121
```
122
123
**Usage Examples:**
124
125
```kotlin
126
import app.cash.sqldelight.logs.StatementParameterInterceptor
127
128
// Manual parameter inspection
129
val interceptor = StatementParameterInterceptor()
130
131
// Simulate parameter binding
132
interceptor.bindString(1, "Alice")
133
interceptor.bindLong(2, 25L)
134
interceptor.bindBoolean(3, true)
135
136
// Retrieve bound parameters
137
val parameters = interceptor.getAndClearParameters()
138
println("Bound parameters: $parameters")
139
// Output: Bound parameters: [Alice, 25, true]
140
141
// Use in custom driver implementation
142
class CustomLoggingDriver(private val delegate: SqlDriver) : SqlDriver by delegate {
143
override fun execute(
144
identifier: Int?,
145
sql: String,
146
parameters: Int,
147
binders: (SqlPreparedStatement.() -> Unit)?
148
): QueryResult<Long> {
149
150
if (binders != null) {
151
val interceptor = StatementParameterInterceptor()
152
interceptor.binders()
153
val params = interceptor.getAndClearParameters()
154
println("Executing: $sql with parameters: $params")
155
} else {
156
println("Executing: $sql (no parameters)")
157
}
158
159
return delegate.execute(identifier, sql, parameters, binders)
160
}
161
}
162
```
163
164
### Transaction Lifecycle Logging
165
166
LogSqliteDriver automatically attaches logging hooks to transactions for comprehensive transaction lifecycle monitoring.
167
168
**Usage Examples:**
169
170
```kotlin
171
import app.cash.sqldelight.logs.LogSqliteDriver
172
173
val loggingDriver = LogSqliteDriver(actualDriver) { message ->
174
println("[${System.currentTimeMillis()}] $message")
175
}
176
177
val database = Database(loggingDriver)
178
179
// Transaction logging shows complete lifecycle
180
database.transaction {
181
// [1609459200000] TRANSACTION BEGIN
182
183
database.userQueries.insertUser("Alice", "alice@example.com")
184
// [1609459200001] EXECUTE
185
// INSERT INTO users (name, email) VALUES (?, ?)
186
// [Alice, alice@example.com]
187
188
database.userQueries.insertUser("Bob", "bob@example.com")
189
// [1609459200002] EXECUTE
190
// INSERT INTO users (name, email) VALUES (?, ?)
191
// [Bob, bob@example.com]
192
193
// [1609459200003] TRANSACTION COMMIT
194
}
195
196
// Failed transaction logging
197
try {
198
database.transaction {
199
// [1609459200100] TRANSACTION BEGIN
200
201
database.userQueries.insertUser("Charlie", "invalid-email-format")
202
// [1609459200101] EXECUTE
203
// INSERT INTO users (name, email) VALUES (?, ?)
204
// [Charlie, invalid-email-format]
205
206
throw RuntimeException("Simulated failure")
207
208
// [1609459200102] TRANSACTION ROLLBACK
209
}
210
} catch (e: Exception) {
211
println("Transaction failed as expected")
212
}
213
```
214
215
### Query Listener Logging
216
217
Monitor query listener registration and notification events.
218
219
**Usage Examples:**
220
221
```kotlin
222
import app.cash.sqldelight.Query
223
import app.cash.sqldelight.logs.LogSqliteDriver
224
225
val loggingDriver = LogSqliteDriver(actualDriver) { message ->
226
println("[Listener] $message")
227
}
228
229
val database = Database(loggingDriver)
230
val userQuery = database.userQueries.selectAll()
231
232
val listener = Query.Listener {
233
println("User data changed!")
234
}
235
236
// Register listener
237
userQuery.addListener(listener)
238
// [Listener] BEGIN Query.Listener@123456 LISTENING TO [users]
239
240
// Make changes that trigger notifications
241
database.userQueries.insertUser("New User", "new@example.com")
242
// [Listener] NOTIFYING LISTENERS OF [users]
243
// User data changed!
244
245
// Remove listener
246
userQuery.removeListener(listener)
247
// [Listener] END Query.Listener@123456 LISTENING TO [users]
248
```
249
250
### Custom Logging Integration
251
252
Integrate with various logging frameworks and monitoring systems.
253
254
**Usage Examples:**
255
256
```kotlin
257
import app.cash.sqldelight.logs.LogSqliteDriver
258
259
// SLF4J integration
260
import org.slf4j.LoggerFactory
261
262
val slf4jLogger = LoggerFactory.getLogger("SqlDelight")
263
val slf4jLoggingDriver = LogSqliteDriver(actualDriver) { message ->
264
slf4jLogger.debug(message)
265
}
266
267
// Logback with structured logging
268
val structuredDriver = LogSqliteDriver(actualDriver) { message ->
269
slf4jLogger.atDebug()
270
.addKeyValue("component", "sqldelight")
271
.addKeyValue("operation", extractOperation(message))
272
.log(message)
273
}
274
275
// Metrics collection
276
class MetricsLoggingDriver(
277
delegate: SqlDriver,
278
private val metrics: MetricsCollector
279
) : LogSqliteDriver(delegate, { message ->
280
when {
281
message.startsWith("QUERY") -> metrics.incrementCounter("sql.queries")
282
message.startsWith("EXECUTE") -> metrics.incrementCounter("sql.executions")
283
message.startsWith("TRANSACTION BEGIN") -> metrics.incrementCounter("sql.transactions.begin")
284
message.startsWith("TRANSACTION COMMIT") -> metrics.incrementCounter("sql.transactions.commit")
285
message.startsWith("TRANSACTION ROLLBACK") -> metrics.incrementCounter("sql.transactions.rollback")
286
}
287
288
// Also log to console for debugging
289
println(message)
290
})
291
292
// Conditional logging (e.g., only in debug builds)
293
val conditionalDriver = LogSqliteDriver(actualDriver) { message ->
294
if (BuildConfig.DEBUG) {
295
println("[SQL Debug] $message")
296
}
297
}
298
299
// File-based logging
300
val fileLoggingDriver = LogSqliteDriver(actualDriver) { message ->
301
File("sql_operations.log").appendText("${Date()}: $message\n")
302
}
303
304
// Network logging for remote monitoring
305
val networkLoggingDriver = LogSqliteDriver(actualDriver) { message ->
306
// Send to remote logging service
307
loggingService.send(LogEntry(
308
timestamp = System.currentTimeMillis(),
309
component = "sqldelight",
310
message = message,
311
deviceId = getDeviceId()
312
))
313
}
314
```
315
316
### Performance Monitoring
317
318
Use logging to monitor database performance and identify bottlenecks.
319
320
**Usage Examples:**
321
322
```kotlin
323
import app.cash.sqldelight.logs.LogSqliteDriver
324
325
class PerformanceLoggingDriver(
326
delegate: SqlDriver
327
) : LogSqliteDriver(delegate, { message -> /* no-op */ }) {
328
329
private val queryTimes = mutableMapOf<String, Long>()
330
331
override fun <R> executeQuery(
332
identifier: Int?,
333
sql: String,
334
mapper: (SqlCursor) -> QueryResult<R>,
335
parameters: Int,
336
binders: (SqlPreparedStatement.() -> Unit)?
337
): QueryResult<R> {
338
val startTime = System.nanoTime()
339
340
return try {
341
super.executeQuery(identifier, sql, mapper, parameters, binders)
342
} finally {
343
val duration = (System.nanoTime() - startTime) / 1_000_000 // Convert to milliseconds
344
345
if (duration > 100) { // Log slow queries (>100ms)
346
println("SLOW QUERY ($duration ms): $sql")
347
}
348
349
// Track average query times
350
val avgKey = sql.take(50) // Use first 50 chars as key
351
queryTimes[avgKey] = ((queryTimes[avgKey] ?: 0L) + duration) / 2
352
}
353
}
354
355
fun printPerformanceStats() {
356
println("Query Performance Statistics:")
357
queryTimes.entries.sortedByDescending { it.value }.forEach { (sql, avgTime) ->
358
println("$avgTime ms avg: $sql...")
359
}
360
}
361
}
362
363
// Usage
364
val perfDriver = PerformanceLoggingDriver(actualDriver)
365
val database = Database(perfDriver)
366
367
// Perform operations...
368
database.userQueries.selectAll().executeAsList()
369
database.userQueries.selectById(1).executeAsOne()
370
371
// Print performance report
372
perfDriver.printPerformanceStats()
373
```