Logging
The IDK provides a structured logging system that is scope-aware, asynchronous, and multiplatform. Loggers are tied to the DI scope hierarchy (app, user context, and session), so every log message carries the context of where it was produced. Multiple log providers (console, mobile, custom) can run simultaneously, and a policy system controls what gets logged at runtime without code changes.
Key Concepts
Scope-aware: Each scope level has its own LogManager and set of LogService instances. A session-scoped logger automatically includes the session ID and context, while an app-scoped logger only has application-level context. In session-scoped code, prefer the session logger; it knows which tenant, principal, and session produced the log.
Async by default: All logging is non-blocking. Log calls dispatch to a coroutine scope and return immediately, so logging never slows down your application's critical path.
Lazy evaluation: Every log method has a lambda variant. The lambda is only evaluated when the log level is enabled, avoiding expensive string formatting for disabled levels.
Multi-provider: The IDK routes log messages to all registered providers (console, mobile buffer, custom backends) simultaneously. Providers are contributed to the DI graph per scope.
Config-driven: Log levels can be controlled entirely from config files, with overrides per module, service, or individual command. You can also set different log levels per tenant. No code changes needed to adjust logging in production. See Configuration for details.
Log Managers
Each scope has a dedicated LogManager:
| Scope | Manager | Graph accessor |
|---|---|---|
| Application | AppLogManager | appGraph.appLogManager |
| User Context | UserContextLogManager | userContext.graph.logManager |
| Session | SessionLogManager | session.graph.logManager |
The session graph also exposes a SessionLogService through SessionExecution, which is the most common way to log from session-scoped code.
Basic Usage
- Android/kotlin
- iOS/Swift
// Get a logger from the session graph
val logger = session.graph.logManager.withTag("MyFeature")
// Simple log calls
logger.info("Processing credential request")
logger.debug("Request payload size: ${payload.size}")
logger.warn("Retry attempt 2 of 3")
logger.error("Failed to verify signature", exception = ex)
// Lazy evaluation - the lambda is only called if DEBUG is enabled
logger.debug { "Full request dump: ${request.toDetailedString()}" }
// Get a logger from the session graph
let logger = session.graph.logManager.withTag(tag: "MyFeature")
// Simple log calls
logger.info(message: "Processing credential request")
logger.debug(message: "Request payload size: \(payload.count)")
logger.warn(message: "Retry attempt 2 of 3")
logger.error(message: "Failed to verify signature", exception: ex)
Log Levels
| Level | Value | Use for |
|---|---|---|
TRACE | 0 | Fine-grained diagnostic detail |
DEBUG | 10 | Development and troubleshooting information |
INFO | 20 | Normal operational events |
WARN | 30 | Unexpected but recoverable situations |
ERROR | 40 | Failures that need attention |
OFF | 100 | Disables logging entirely |
Log Messages
Every log call produces a LogMessage:
data class LogMessage(
val level: LogLevel,
val message: String,
val tag: String? = null,
val exception: Throwable? = null,
val errorResult: IdkResult<Nothing, *>? = null,
val metadata: Map<String, String>? = null,
val timestamp: Long
)
The metadata map lets you attach structured key-value pairs to any log entry. The errorResult field integrates with the IDK's IdkResult error handling, so you can log a failed result directly without extracting the error message yourself.
Why Session Loggers Matter
When code runs inside a session scope, the session logger knows the tenant, principal, and session ID. This context is automatically included in log output:
// App logger - no session context
val appLogger = appGraph.appLogManager.withTag("Startup")
appLogger.info("Application started") // Logs: [INFO] [Startup] Application started
// Session logger - includes session context
val sessionLogger = session.graph.logManager.withTag("Verification")
sessionLogger.info("Signature verified")
// Logs: [INFO] [Verification] [session:abc-123] [tenant:acme] Signature verified
If you use an app-scoped logger from within session code, you lose this context. Always prefer the scope-appropriate logger.
Next Steps
- Scoped Loggers: Detailed guide to app, user, and session loggers
- Configuration: Log levels, policies, output formats, and providers