Dependency Tracking
How It Works
Section titled “How It Works”do-auditlog does not access samber/do’s internal DAG. Instead, it uses a lightweight invocation stack to infer dependencies at runtime:
HookBeforeInvocationfires for service A. A is pushed onto a stack.- A’s provider calls
do.MustInvoke[B](i).HookBeforeInvocationfires for B while A is still on the stack. - The plugin records: A depends on B.
HookAfterInvocationfires. The service is popped from the stack.
This correctly reconstructs the dependency graph even for:
- Cached services — subsequent invocations of a lazy service are near-instant but still tracked
- Cross-scope resolution — services inherited from parent scopes
- Provider errors — failed invocations are still recorded with error details
Forward and Reverse Graphs
Section titled “Forward and Reverse Graphs”Every service has both:
report := plugin.Report()svc := report.ServiceByName("*main.UserService")
// Services that UserService depends onfor _, dep := range svc.Dependencies { fmt.Printf("depends on: %s\n", dep.ServiceName)}
// Services that depend on UserServicefor _, dependent := range svc.Dependents { fmt.Printf("depended on by: %s\n", dependent.ServiceName)}The reverse graph (Dependents) is computed at report time from the forward dependencies.
Scope Tree
Section titled “Scope Tree”do-auditlog tracks the full scope hierarchy:
report := plugin.Report()
// Get all services in a specific scopeservices := report.ServicesByScope("drivers")
// The scope tree is part of the report// [root]// ├── *main.Database// └── drivers (child scope)// └── *main.DriverServiceStack-Based Inference
Section titled “Stack-Based Inference”The invocation stack is LIFO (last-in, first-out) with a fast path:
- O(1) common case: The most recently pushed service is the first checked.
- Backward search: Falls back only for unusual invocation orderings.
This means the hot path — a linear chain of do.MustInvoke calls — is a single pointer comparison.
Cross-Scope Dependencies
Section titled “Cross-Scope Dependencies”When a child scope invokes a service from a parent scope, the dependency is recorded with the correct scope information:
// In scope "drivers":db := do.MustInvoke[*Database](i)// Records: DriverService (scope: drivers) depends on Database (scope: [root])What Is NOT Tracked
Section titled “What Is NOT Tracked”- Lazy providers that are never invoked — if a service is registered but never resolved, it has no dependencies recorded. This is correct: no provider ran, so no
MustInvokecalls were intercepted. - Health check capabilities —
IsHealthcheckerandIsShutdownerare populated viado.ExplainInjectorat report time, not from the stack. Capabilities are only visible for invoked services.