or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

column-adapters.mddatabase-driver.mdindex.mdlogging-utilities.mdquery-system.mdschema-management.mdtransaction-management.md

logging-utilities.mddocs/

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

```