Debugging / Introspection

Every Kontainer is fully introspectable. Query the complete service graph at runtime — what is registered, how it's wired, what overrides what, and whether instances have been created.

Dumping the container

For a quick overview, dumpKontainer() returns a human-readable table of all services, their types, and current instances:

val kontainer = blueprint.create()

println(kontainer.tools.dumpKontainer())
// Kontainer dump:
//
// Service ID       | Type      | Instances
// Counter          | Singleton |
// MyService        | Singleton |
// RequestContext   | Dynamic   | 2024-01-15T10:30:00Z: RequestContext

Structured debug info

For programmatic access, getDebugInfo() returns the full service graph as structured data. Each service entry includes its type, what it creates, what it injects, and whether it overrides another definition:

val debugInfo = kontainer.tools.getDebugInfo()

debugInfo.services.forEach { service ->
    println("${service.cls.fqn} [type=${service.type}]")

    // What does this service inject?
    service.definition.injects.forEach { param ->
        val lazy = if (param.provisionType == DebugInfo.ParamInfo.ProvisionType.Lazy) " (lazy)" else ""
        println("  -> ${param.name}: ${param.classes.map { it.fqn }}${lazy}")
    }

    // Does this service override another definition?
    service.definition.overwrites?.let { overridden ->
        println("  overrides: ${overridden.creates.fqn}")
    }
}

Service types

Each service in the debug info carries a type that tells you exactly how the container manages it:

Type Meaning
Singleton Shared across all container instances created from the same blueprint
Dynamic Lives only within a single container instance
SemiDynamic Defined as singleton but injects a dynamic service, so promoted automatically
Prototype New instance created for every injection
DynamicOverride A dynamic service overridden at container creation time

Querying the provider factory

For lower-level access, the tools.factory exposes the raw service providers:

val factory = kontainer.tools.factory

// All registered service providers
val allProviders = factory.getAllProviders()

allProviders.forEach { (cls, provider) ->
    println("${cls.simpleName}: ${provider.type}")
    println("  instances created: ${provider.instances.size}")
    println("  dependencies: ${provider.definition.producer.params.map { it.name }}")
}

// Check if a specific service has been instantiated
val isCreated = factory.isProviderCreated(MyService::class)

Tracking overrides

When a module or blueprint extension overrides a service, the override chain is preserved. Walk the overwrites chain to see the full history:

val debugInfo = kontainer.tools.getDebugInfo()

debugInfo.services.forEach { service ->
    var def = service.definition
    var depth = 0

    while (def.overwrites != null) {
        depth++
        def = def.overwrites!!
        println("${service.cls.fqn} override #${depth}: was ${def.creates.fqn}")
    }
}

Error handling

Kontainer throws specific exceptions for different problems:

Exception When
KontainerInconsistent Dependency graph has problems (missing deps, configuration errors)
ServiceNotFound Requested service is not registered
ServiceAmbiguous Multiple services match the requested type
InvalidClassProvided Tried to register an interface or abstract class directly
InvalidServiceFactory Factory method is invalid