Table of Contents

Logging & validation

Saneject injection is designed to finish a full run and then report everything it found. Instead of stopping at the first failure, one run gives you a complete list of issues to fix: invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., missing bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., missing dependencies, unused bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., and summary totals.

Log examples by type

❌ Error examples

❌ Saneject: Invalid binding [Binding: BindAsset<PlayerController>() | Scope: GameplayScope] Asset binding type 'PlayerController' derives from Component.
❌ Saneject: Missing binding. Expected something like [Binding: BindComponent<IHealthService>() | Nearest scope: GameplayScope] [Field: Root/Player/PlayerHud/PlayerHud.healthService]
❌ Saneject: Method invocation exception (exception details in next log) [Method: Root/Player/PlayerBootstrap/PlayerBootstrap.Initialize]

⚠️ Warning examples

⚠️ Saneject: Unused binding [Binding: BindComponent<AudioSource>().FromDescendants() | Scope: GameplayScope]. If you don't plan to use this binding, you can safely remove it.
⚠️ 2 missing binding/dependency errors were suppressed due to [Inject(suppressMissingErrors: true)]. Remove the flag to view detailed logs.

ℹ️ Info examples

ℹ️ Saneject: Created proxy asset at 'Assets/SanejectGenerated/RuntimeProxies/EnemyServiceProxy.asset'.
ℹ️ Saneject: Injection complete | 3 scopes processed | 6 globals registered | 1 proxy swap target registered | 24 fields injected | 3 properties injected | 2 methods injected | 0 missing dependencies | 0 missing bindings | 0 invalid bindings | 1 unused binding | Completed in 18 ms

Where validation and logging happen in the pipeline

For each run, Saneject executes these stages in order:

  1. Build the injection graphInjection graphHierarchy model built from selected root hierarchies. It contains transform, component, scope, binding, and injection-member information used by the edit-time pipeline. and apply ContextWalkFilter.
  2. Build an InjectionContext (active transforms, components, scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy., and bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search.).
  3. Validate bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..
  4. Resolve globals, fields/properties, and methods.
  5. Inject resolved values.
  6. Collect proxy swap targetsProxy swap targetComponent that implements IRuntimeProxySwapTarget and is asked at runtime startup to replace proxy references with resolved real instances..
  7. Log errors, warnings, and summary.

Validation runs before resolution. Invalid bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. are reported and then excluded from resolution for that run.

Binding validation

Validation checks each active bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. and records all failures without aborting the run.

Saneject validates:

  • Duplicate bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. declarations inside the same scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy..
  • Duplicate global bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. for the same concrete type across scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy..
  • Runtime proxy bindingRuntime proxy bindingComponent binding configured with FromRuntimeProxy() that injects a proxy asset at editor time and swaps it for a real runtime instance during scope initialization. requirements (interface and concrete type required, and single-value only).
  • Component bindingComponent bindingBinding declared with BindComponent... or BindComponents... that resolves Component instances from transforms, hierarchies, scenes, or explicit instances. concrete type must derive from UnityEngine.Component.
  • Asset bindingAsset bindingBinding declared with BindAsset... or BindAssets... that resolves UnityEngine.Object assets from project content instead of scene or hierarchy components. concrete type must not derive from UnityEngine.Component.
  • InterfaceType must be an interface.
  • Concrete type must implement the interface when both are specified.
  • A locator strategyLocator strategyThe From... part of a binding that defines where candidates are searched or loaded from. must be specified (From...).

If a bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. fails validation:

  • It is logged as an Invalid binding error.
  • It is excluded from ValidBindingNodes.
  • The rest of injection still continues.

What gets logged in a single run

A single run can emit:

  • Error logs: invalid bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., missing bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search., missing global object, missing dependency, binding filterBinding filterPredicate declared with Where... methods that removes candidates after location and before injection. exception, method invocation exception.
  • Warning logs: unused bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..
  • Info logs: created runtime proxyRuntime proxyScriptableObject placeholder asset (RuntimeProxy<TComponent>) injected into interface members at editor time and swapped to the real instance during scope startup. assets and summary logs (if enabled).

Summary behavior:

  • Controlled by Saneject/Settings/User Settings/Log Injection Summary.
  • Severity is computed from unsuppressed counts:
    • Error if there are missing binding/dependency/invalid bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. errors.
    • Warning if there are no such errors but there are unused bindingsBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search..
    • Info otherwise.
  • If [Inject(suppressMissingErrors: true)] suppresses missing binding/dependency errors, the summary appends a warning line with the suppressed count.

No-scope behavior:

  • If zero scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy. are processed, Saneject logs that no scopesScopeMonoBehaviour that declares bindings for a part of your hierarchy. were found and nothing was injected.

What gets logged in batch runs

Batch injectionBatch injectionRunning injection across multiple selected scene and prefab assets in one operation. adds structure around normal per-asset logs:

  1. Batch header for scenes and/or prefabs.
  2. Per-asset start/end markers.
  3. Normal injection logs inside each asset section, including per-asset summary.
  4. Final batch summary section with aggregated scene and prefab summaries.

If the save-scenes prompt is cancelled before batch starts, Saneject logs Injection cancelled by user. and exits without injecting.

Exceptions and flow continuity

Exceptions from user code inside [Inject] methods are caught and logged as Method invocation exception, followed by the exception details in a separate log entry. This keeps the injection runInjection runOne execution of the injection pipeline for a chosen selection, context walk filter, and active set of targets. moving so later targets can still be processed.

Exceptions thrown by bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. dependency filters are also caught and logged as Binding filter exception, so one faulty filter does not terminate the run.

Filter logs from scopes and components

Saneject adds contextContextSerialization boundary Saneject uses during injection to decide scope traversal and candidate eligibility. menu actions that set the Unity Console search query:

  • On Scope: Saneject/Filter Logs By Scope Type
    • Sets search to Scope: <ScopeTypeName>.
  • On Scope and MonoBehaviour: Saneject/Filter Logs By Component Path
    • Sets search to the full component pathComponent pathFull hierarchy path plus component type name, for example Root/Player/HUD/HudController. Used by Saneject log filtering and context menu actions., for example Root/Player/Hud/PlayerHud.

These filters work well because Saneject log signatures include scopeScopeMonoBehaviour that declares bindings for a part of your hierarchy. names and full member/component paths.

Settings that affect logging

In Saneject/Settings/User Settings:

  • Clear Logs On Injection: clears the console before injection starts.
  • Log Injection Summary: enables/disables summary logging.
  • Log Unused Bindings: enables/disables unused bindingBindingInstruction declared in a Scope that tells Saneject what to resolve, how to inject it, and where to search. warnings.

In Saneject/Settings/Project Settings:

  • Use Context Isolation: when enabled, cross-context candidates are rejected during resolution. This can surface missing dependency errors that include rejected candidate type details.