auditlog

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 34 Imported by: 0

README

🔍 do-auditlog

Audit-log plugin for samber/do v2

Track every service registration, invocation, and shutdown. Get timestamps, build durations, dependency graphs, and scope trees. Export as JSON, NDJSON, or a self-contained HTML visualization.

CI Go Reference Go Report Card


[!CAUTION]

🚧 ALPHA — WORK IN PROGRESS 🚧

This project is in early development. The API may change at any time without notice.

No guarantees are made regarding:

  • Backward compatibility between versions
  • Stability of exported types and functions
  • Correctness in all edge cases

Use at your own risk. Pin your dependency to a specific commit hash if you depend on this in production.

Feedback, bug reports, and breaking-change requests are very welcome in Issues.


Why?

samber/do v2 has lifecycle hooks but no built-in observability. You get hooks, but no recorder, no export, no visualization.

do-auditlog wires into those hooks in one line and gives you:

  • What services exist, when they were created, and how long they took to build
  • Which services depend on which — forward and reverse
  • The scope tree with per-scope service lists
  • A complete chronological event stream
  • A self-contained HTML page you can open in any browser to explore your DI container

Features

Feature Description
Drop-in setup do.NewWithOpts(plugin.Opts()) — one line, zero config
Dependency graph Infers which service resolved which, without accessing do's internal DAG
Reverse dependencies Every service knows who depends on it
Scope tree Full hierarchy with per-scope service lists
Service types Auto-detects lazy/eager/transient/alias via do.ExplainNamedService
Timing First build duration, shutdown duration, invocation count & order
Health checks Wraps injector.HealthCheck() with per-service audit events
9 export formats JSON · NDJSON · CSV · TSV · HTML · Mermaid · PlantUML · DOT · D2
Filtered reports Functional options to slice by name, type, scope, event type, time range
~1.7μs overhead In-memory capture, no I/O during container operation
Toggle on/off Enabled: false → zero hooks, zero cost
Minimal deps Only samber/do/v2 + a-h/templ (HTML visualization)

Install

go get github.com/larsartmann/samber-do-auditlog

Requires Go 1.26+ and samber/do v2.

Quick Start

package main

import (
    "os"

    "github.com/larsartmann/samber-do-auditlog"
    "github.com/samber/do/v2"
)

func main() {
    // 1. Create the plugin (validates config, returns error on invalid input)
    plugin, err := auditlog.New(auditlog.Config{
        Enabled:     true,           // flip to false in production
        ContainerID: "my-app",
    })
    if err != nil {
        panic(err)
    }

    // 2. Pass options to the DI container
    injector := do.NewWithOpts(plugin.Opts())

    // 3. Register and use services as usual
    do.Provide(injector, func(i do.Injector) (*MyService, error) {
        return &MyService{}, nil
    })
    svc := do.MustInvoke[*MyService](injector)
    _ = svc

    // 4. Export when you're done
    plugin.ExportToFile("audit.json")              // full report
    plugin.ExportEventsToNDJSON("events.ndjson")   // streaming format
    plugin.ExportToHTML("audit.html")              // open in browser

    // 5. Filtered export — only lazy services in a specific scope
    plugin.ExportFilteredToFile("lazy.json",
        auditlog.WithServicesByType(auditlog.ProviderTypeLazy),
        auditlog.WithScope("drivers"),
    )

    // 6. Mermaid dependency graph
    report := plugin.Report()
    report.WriteMermaid(os.Stdout)                 // paste into GitHub README
}

Export Formats

JSON Report

Full snapshot: event timeline, service summaries, scope tree.

{
  "version": "0.2.0",
  "container_id": "my-app",
  "exported_at": "2026-06-09T22:18:00Z",
  "service_count": 3,
  "event_count": 20,
  "services": [
    {
      "service_name": "*main.UserService",
      "invocation_count": 1,
      "invocation_order": 2,
      "first_build_duration_ms": 9.079,
      "dependencies": [
        { "scope_id": "...", "scope_name": "[root]", "service_name": "*main.Database" },
        { "scope_id": "...", "scope_name": "[root]", "service_name": "*main.Cache" }
      ],
      "dependents": [
        { "scope_id": "...", "scope_name": "[root]", "service_name": "*main.HTTPServer" }
      ]
    }
  ],
  "scope_tree": {
    "name": "[root]",
    "services": ["*main.Config", "*main.Database", "*main.Cache"],
    "children": []
  }
}
NDJSON Event Stream

One JSON object per line. Feed it into log aggregators, stream processors, or custom tooling.

{"sequence":1,"timestamp":"...","event_type":"registration","phase":"before","container_id":"my-app","scope_id":"...","scope_name":"[root]","service_name":"*main.Config"}
{"sequence":2,"timestamp":"...","event_type":"registration","phase":"after","container_id":"my-app","scope_id":"...","scope_name":"[root]","service_name":"*main.Config"}
{"sequence":3,"timestamp":"...","event_type":"invocation","phase":"before","container_id":"my-app","scope_id":"...","scope_name":"[root]","service_name":"*main.Database"}
{"sequence":4,"timestamp":"...","event_type":"invocation","phase":"after","container_id":"my-app","scope_id":"...","scope_name":"[root]","duration_ms":5.196,"service_name":"*main.Database"}
HTML Visualization

A single, self-contained dark-themed HTML page. No external JS/CSS. Works offline.

What you get:

  • Stats cards — services, events, scopes, dependency count, health check status
  • Services table — name, type badge, scope, invocation order, count, build time, deps, status, health
  • Scopes tab — collapsible scope tree with type emoji chips
  • Dependency graph — Sugiyama layered DAG layout with type-colored nodes, pan/zoom, click-to-highlight
  • Timeline — dual build+shutdown horizontal bars with type icons
  • Events table — full chronological log with type filter chips and keyboard navigation

Open the file in any browser. No server needed.

Mermaid Flowchart

Writes a flowchart TD representing the dependency graph. Paste it into any Markdown file that renders Mermaid (GitHub, GitLab, Notion).

report := plugin.Report()
report.WriteMermaid(os.Stdout)
flowchart TD
    root_*main.HTTPServer --> root_*main.UserService
    root_*main.HTTPServer --> root_*main.Database
    root_*main.UserService --> root_*main.Database
PlantUML Component Diagram

Writes a PlantUML component diagram. Paste it into any PlantUML renderer (GitHub with plugin, GitLab, IntelliJ, online editors).

report := plugin.Report()
report.WritePlantUML(os.Stdout)
@startuml
    component "*main.HTTPServer 🔄" as root__main_HTTPServer
    component "*main.UserService 😴" as root__main_UserService
    component "*main.Database 😴" as root__main_Database
    root__main_HTTPServer --> root__main_UserService
    root__main_HTTPServer --> root__main_Database
    root__main_UserService --> root__main_Database
@enduml
DOT Digraph

Writes a Graphviz DOT digraph. Render with dot -Tsvg graph.dot -o graph.svg or any Graphviz viewer. Each node is styled with the warm-amber fill; note that the dark canvas background is not emitted (go-output has no graph-level bgcolor setter — see Diagram theming below).

report := plugin.Report()
report.WriteDOT(os.Stdout)
D2 Diagram

Writes a D2 diagram — the most modern of the four formats, with native Markdown labels and a polished default renderer. Render with d2 graph.d2 graph.svg or the D2 playground. The diagram title is set to the container ID for self-documenting output.

report := plugin.Report()
report.WriteD2(os.Stdout)
direction: right
title: do-auditlog
"root/*main.HTTPServer 🔄": { style.fill: "#e8a838"; ... }
"root/*main.HTTPServer 🔄" -> "root/*main.UserService 😴"
"root/*main.HTTPServer 🔄" -> "root/*main.Database 😴"
"root/*main.UserService 😴" -> "root/*main.Database 😴"
Diagram theming

All four diagram formats share the warm-amber per-node style (fill:#e8a838, stroke:#4a4030, font:#14110d) via go-output's GraphStyle. Two visual elements are not emitted because go-output's renderers lack graph-level attribute setters: the DOT dark canvas background (bgcolor) and edge line-colors. Restoring these requires an upstream contribution to go-output.

Filtered Reports

Functional options let you slice the report before exporting. Filters compose — pass multiple options to intersect them.

// Only invocation events in the last 5 minutes
report := plugin.ReportFiltered(
    auditlog.WithEventsByType(auditlog.EventTypeInvocation),
    auditlog.WithTimeRange(time.Now().Add(-5*time.Minute), time.Now()),
)

// Only eager services
report = plugin.ReportFiltered(
    auditlog.WithServicesByType(auditlog.ProviderTypeEager),
)

// Only services named "*main.Database" in scope "drivers"
report = plugin.ReportFiltered(
    auditlog.WithServicesByName("*main.Database"),
    auditlog.WithScope("drivers"),
)

// Export the filtered report
plugin.ExportFilteredToFile("filtered.json",
    auditlog.WithServicesByType(auditlog.ProviderTypeLazy),
)

Available filters:

Option Filters by
WithServicesByName(names...) Service name(s)
WithServicesByType(type) Provider type (lazy, eager, transient, alias)
WithEventsByType(type) Event type (registration, invocation, shutdown, health_check)
WithTimeRange(from, to) Event timestamp range
WithScope(scopeID) Scope ID

Health Checks

samber/do v2 does not expose health-check hooks. do-auditlog wraps injector.HealthCheckWithContext() to record EventTypeHealthCheck events per service.

err := plugin.RecordHealthCheckWithContext(ctx, injector)
report := plugin.Report()
if !report.HealthCheckSucceeded {
    for _, svc := range report.UnhealthyServices() {
        log.Printf("unhealthy: %s — %s", svc.ServiceName, *svc.HealthCheckError)
    }
}

Health check events are PhaseAfter only (there is no before-hook). Per-service timing is unavailable from the bulk API, so DurationMs is nil for health check events.

Real-Time Event Streaming

Provide an OnEvent callback to react to events as they happen — no polling required.

plugin, err := auditlog.New(auditlog.Config{
    Enabled: true,
    OnEvent: func(ev auditlog.Event) {
        // Stream to Prometheus, OTel, a live dashboard, or custom tooling
        log.Printf("event %d: %s %s", ev.Sequence, ev.EventType, ev.ServiceName)
    },
})
if err != nil {
    log.Fatalf("create auditlog plugin: %v", err)
}

The callback is called outside the mutex on every event. Keep it fast — do not block the hot path.

API Reference

Plugin
Method Description
New(config Config) (*Plugin, error) Create plugin. Validates config, returns error if invalid.
Opts() *do.InjectorOpts Hooks for do.NewWithOpts. No-ops when Enabled: false.
Report() Report In-memory snapshot. No I/O.
ReportFiltered(opts...) Report Filtered snapshot with functional options.
Events() []Event Defensive copy of raw event slice.
EventsCount() int Event count without copying.
RecordHealthCheck(injector) Wrap injector.HealthCheck() with audit events.
RecordHealthCheckWithContext(ctx, injector) Same with context.
WriteReportJSON(w) error Indented JSON to any io.Writer.
WriteEventsNDJSON(w) error NDJSON event stream to any io.Writer.
WriteHTML(w) error Self-contained HTML visualization to any io.Writer.
WriteReportCSV(w) error CSV of all services to io.Writer.
WriteReportTSV(w) error TSV of all services to io.Writer.
WriteMermaid(w) error Mermaid flowchart to io.Writer.
WritePlantUML(w) error PlantUML component diagram to io.Writer.
WriteDOT(w) error Graphviz DOT digraph to io.Writer.
WriteD2(w) error D2 diagram to io.Writer.
ExportToFile(path) error JSON report to file.
ExportEventsToNDJSON(path) error NDJSON events to file.
ExportToHTML(path) error HTML visualization to file.
ExportToCSV(path) error CSV of all services to file.
ExportToTSV(path) error TSV of all services to file.
ExportToMermaid(path) error Mermaid flowchart to file.
ExportToPlantUML(path) error PlantUML component diagram to file.
ExportToDOT(path) error Graphviz DOT digraph to file.
ExportToD2(path) error D2 diagram to file.
ExportFilteredToFile(path, opts...) error Filtered JSON report to file.

Package-level

Function Description
MigrateReport(data []byte) (Report, error) Normalize/repair a JSON report to the current schema (upgrades v0.1.0 and re-derives all denormalized fields for any input version).
Report
Method Description
Filtered(opts...) Report New report with filters applied, counts recomputed.
Validate() error Check denormalized counts match actual data.
Index() ReportIndex Build O(1) lookup index for multiple queries.
ServiceByName(name) *ServiceInfo Lookup by service name.
ServiceByRef(scopeID, name) *ServiceInfo Lookup by scope + name.
ServicesByScope(scopeID) []ServiceInfo All services in a scope.
EventsByService(name) []Event All events for a service.
EventsByRef(scopeID, name) []Event All events for a scoped service.
EventsByType(type) []Event All events of a given type.
FailedServices() []ServiceInfo Services with invocation or shutdown errors.
UnhealthyServices() []ServiceInfo Services with health check errors.
WriteJSON(w) error Indented JSON report to io.Writer.
WriteNDJSON(w) error NDJSON event stream to io.Writer.
WriteHTML(w) error Self-contained HTML visualization to io.Writer.
WriteCSV(w) error Comma-separated values (all services).
WriteTSV(w) error Tab-separated values (all services).
WriteMermaid(w) error Mermaid flowchart to io.Writer.
WritePlantUML(w) error PlantUML component diagram to io.Writer.
WriteDOT(w) error Graphviz DOT digraph to io.Writer.
WriteD2(w) error D2 diagram to io.Writer.
Diff(other Report) DiffResult Structural comparison between two reports.
Package Functions
Function Purpose
New(...) (*Plugin, error) Construct a plugin from Config (validated).
NewReport(...) (Report, error) Construct a validated Report from core data.
LoadReport(path, opts...) (Report, Format, error) Auto-detect JSON/NDJSON and load.
ReadEvents(reader) ([]Event, error) Read NDJSON event stream.
ReplayEvents(events) (Report, error) Reconstruct a Report from events.
MigrateReport(data) (Report, error) Migrate/repair old or stale JSON.
JSONSchema() string Canonical JSON Schema for the report format.

How Dependency Tracking Works

do-auditlog does not access samber/do's internal DAG. Instead, it uses a lightweight invocation stack:

  1. HookBeforeInvocation fires for service A → A is pushed onto a stack
  2. A's provider calls do.MustInvoke[B](i)HookBeforeInvocation fires for B while A is still on the stack
  3. The plugin records: A depends on B
  4. HookAfterInvocation fires → 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

The reverse graph (Dependents) is computed at report time from the forward dependencies.

Performance

Benchmarks from a real run (AMD Ryzen AI MAX+ 395):

BenchmarkHookOverhead_Invocation    ~1,658 ns/op    6 allocs    (enabled)
BenchmarkHookOverhead_Disabled       ~113 ns/op    4 allocs    (disabled)
BenchmarkHookOverhead_Registration  ~21,982 ns/op   54 allocs    (full container)

Overhead: ~1.7μs per cached invocation when enabled. Zero cost when disabled.

No file I/O happens during container operation. Export is a single json.Marshal or line iteration — you pay the cost only when you need the data.

Data Model

Report
├── version                    string (schema version, e.g. "0.2.0")
├── container_id               string
├── exported_at                time
├── service_count              int
├── event_count                int
├── scope_count                int
├── total_build_duration_ms    float64
├── total_shutdown_duration_ms float64
├── shutdown_succeeded         bool
├── health_check_succeeded     bool
├── health_checked_count       int
├── services[]                 ServiceInfo
│   ├── service_name           string
│   ├── scope_id               string
│   ├── scope_name             string
│   ├── service_type           string (lazy, eager, transient, alias)
│   ├── status                 string (registered, active, invocation_error, shutdown, shutdown_error)
│   ├── registered_at          time
│   ├── invocation_count       int
│   ├── invocation_order       int
│   ├── first_build_duration_ms float64
│   ├── shutdown_duration_ms   float64
│   ├── invocation_error       string (on failure)
│   ├── shutdown_error         string (on failure)
│   ├── dependencies[]         {scope_id, scope_name, service_name}
│   ├── dependents[]           {scope_id, scope_name, service_name}
│   ├── health_check_count     int
│   ├── health_check_error     string (on failure)
│   ├── is_healthchecker       bool
│   └── is_shutdowner          bool
├── events[]                   Event
│   ├── sequence               int (monotonic)
│   ├── timestamp              time
│   ├── container_id           string
│   ├── scope_id               string
│   ├── scope_name             string
│   ├── service_name           string
│   ├── service_type           string
│   ├── event_type             registration | invocation | shutdown | health_check
│   ├── phase                  before | after
│   ├── duration_ms            float64 (after-invocation/shutdown only)
│   └── error                  string (on failure only)
└── scope_tree                 ScopeNode
    ├── id                     string
    ├── name                   string
    ├── services[]             string
    └── children[]             ScopeNode (recursive)

Schema migration: Reports exported with v0.1.0 can be upgraded to the current schema with auditlog.MigrateReport(oldJSONBytes). MigrateReport also repairs current-schema reports — it re-derives every denormalized field, so stale or hand-edited reports that would fail Validate() are normalized to a valid report.

Security

This project enforces security through:

  • gosec — security scanner integrated into golangci-lint (108 linters, 0 issues)
  • CSP — HTML output includes a strict Content-Security-Policy meta tag
  • XSS hardening — all user-controlled strings escaped via templ's context-aware auto-escaping
  • Fuzz testing — 3 fuzz targets verify XSS resilience against malicious service names, error messages, and dependency chains

Recommended additional check:

govulncheck ./...  # requires: go install golang.org/x/vuln/cmd/govulncheck@latest

License

MIT

Documentation

Overview

Package auditlog provides an audit-log plugin for samber/do v2 that tracks every service registration, invocation, shutdown, and health check with timestamps, dependency graph inference, build duration measurement, and provider type tracking.

Each Event carries a ServiceType (ProviderType) identifying the provider kind (lazy, eager, transient, alias). ServiceInfo includes IsHealthchecker and IsShutdowner capabilities detected via do.ExplainInjector.

Config.Validate() checks configuration constraints. Export formats include JSON reports, NDJSON event streams, CSV/TSV, self-contained HTML, Mermaid, PlantUML, Graphviz DOT, and D2 diagrams.

templ: version: v0.3.1020

Example (Validate)
package main

import (
	"fmt"

	auditlog "github.com/larsartmann/samber-do-auditlog"
)

func main() {
	cfg := auditlog.Config{ContainerID: "my-app"}
	fmt.Println("valid:", cfg.Validate())

	cfg = auditlog.Config{ContainerID: "my/app"}
	fmt.Println("invalid:", cfg.Validate() != nil)

}
Output:
valid: <nil>
invalid: true

Index

Examples

Constants

View Source
const EnvKeyEnabled = "DO_AUDITLOG_ENABLED"

EnvKeyEnabled is the environment variable that controls audit logging. Set to "true", "1", or "yes" to enable. Any other value (or unset) disables it.

View Source
const RootScopeName = "[root]"

RootScopeName is the canonical name for the root scope in samber/do v2.

View Source
const SchemaVersion = "0.2.0"

SchemaVersion is the current report schema version.

Variables

View Source
var (
	ErrEmpty         = errors.New("ndjson input is empty")
	ErrNoEvents      = errors.New("ndjson input contains no events")
	ErrOversizedLine = errors.New("ndjson line exceeds maximum size")
)

Errors returned by ReadEvents.

Functions

func CompareServiceRefs added in v0.1.0

func CompareServiceRefs(a, b ServiceRef) int

CompareServiceRefs is the canonical sort ordering for ServiceRef slices: primary by ServiceName, secondary by ScopeID. Used by report builders and diff output so all ServiceRef lists are consistently ordered.

func DefaultTableOpts added in v0.3.0

func DefaultTableOpts() output.RenderOptions

DefaultTableOpts returns the default RenderOptions for table export. Convenience for callers who don't need custom rendering options.

func JSONSchema added in v0.1.0

func JSONSchema() string

JSONSchema returns the canonical JSON Schema (Draft 2020-12) describing the Report export format, derived from this package's public types. Consumers can use it to validate exported report JSON or to generate client code.

func LoadReport added in v0.1.0

func LoadReport(path string, opts ...LoadOption) (Report, Format, error)

LoadReport reads a report from a file path, auto-detecting the format.

JSON files (single Report object) are loaded via MigrateReport, which handles both v0.1.0 and v0.2.0 schemas. NDJSON files (line-delimited events) are loaded via ReadEvents + ReplayEvents.

Returns the loaded Report, the detected Format, and any error.

func LoadReportFromBytes added in v0.1.0

func LoadReportFromBytes(data []byte, format Format) (Report, Format, error)

LoadReportFromBytes loads a report from raw bytes with format detection.

func LoadReportFromReader added in v0.1.0

func LoadReportFromReader(reader io.Reader, format Format) (Report, Format, error)

LoadReportFromReader reads a report from an io.Reader. If format is FormatAuto, the entire content is read and the format is detected by inspecting the first non-blank line for a "version" key (JSON Report) or "event_type" key (NDJSON event).

func RedriveReportStatuses added in v0.2.0

func RedriveReportStatuses(report *Report)

RedriveReportStatuses calls RederiveStatus on every service in the report. Used by MigrateReport (repair) and property-test fixture builders (sanitize).

Types

type Config

type Config struct {
	// Enabled turns audit logging on or off. When false the plugin is a no-op.
	// If left as zero-value (false), New() checks the DO_AUDITLOG_ENABLED env var.
	Enabled bool
	// ContainerID is an optional human-readable identifier for the injector.
	ContainerID string
	// OnEvent is called after each event is captured. Must not block.
	// Called sequentially in hook order. Nil disables the callback.
	OnEvent func(Event)
	// MaxEvents caps the number of events stored in memory. When 0 (default),
	// events grow without bound. When > 0, the recorder stops appending new
	// events after reaching the cap and increments DroppedEventCount.
	// Use this to prevent OOM in long-running processes.
	MaxEvents int
	// InitialEventCapacity pre-allocates the events slice to avoid runtime
	// growslice reallocations. When 0, defaults to 1024. Set this to the
	// expected number of events for your workload to eliminate slice growth cost.
	InitialEventCapacity int
}

Config controls the audit log plugin behaviour.

func (Config) Validate

func (c Config) Validate() error

type DiffResult added in v0.0.4

type DiffResult struct {
	// AddedServices are services present in `other` but not in `r`.
	AddedServices []ServiceRef `json:"added_services,omitempty"`
	// RemovedServices are services present in `r` but not in `other`.
	RemovedServices []ServiceRef `json:"removed_services,omitempty"`
	// ChangedServices are services present in both with different fields.
	ChangedServices []ServiceDiff `json:"changed_services,omitempty"`
	// EventCountDelta is other.EventCount - r.EventCount.
	EventCountDelta int `json:"event_count_delta"`
}

DiffResult describes the differences between two Reports. All slices are nil when empty (no allocation for identical reports).

func (DiffResult) IsEmpty added in v0.0.4

func (d DiffResult) IsEmpty() bool

IsEmpty returns true when no differences were found.

type Event

type Event struct {
	ServiceRef

	Sequence    int          `json:"sequence"`
	Timestamp   time.Time    `json:"timestamp"`
	EventType   EventType    `json:"event_type"`
	Phase       Phase        `json:"phase"`
	ServiceType ProviderType `json:"service_type,omitempty"`
	ContainerID string       `json:"container_id"`
	DurationMs  *float64     `json:"duration_ms,omitempty"`
	Error       *string      `json:"error,omitempty"`
}

Event is a single, timestamped observation from the DI container lifecycle.

func ReadEvents added in v0.1.0

func ReadEvents(reader io.Reader) ([]Event, error)

ReadEvents reads line-delimited JSON events from reader. Each line must be a single JSON-encoded Event object. Blank lines are skipped. Returns the parsed events in order.

func (Event) Duration

func (e Event) Duration() float64

Duration returns the event duration in milliseconds, or 0 if unavailable.

func (Event) HasError

func (e Event) HasError() bool

HasError returns true if the event recorded an error.

func (Event) IsAfter

func (e Event) IsAfter() bool

IsAfter returns true if the event is the end (after) phase of an operation.

func (Event) IsBefore

func (e Event) IsBefore() bool

IsBefore returns true if the event is the start (before) phase of an operation.

func (Event) IsHealthCheck

func (e Event) IsHealthCheck() bool

IsHealthCheck returns true if the event is a health check event.

func (Event) IsInvocation

func (e Event) IsInvocation() bool

IsInvocation returns true if the event is an invocation event.

func (Event) IsRegistration

func (e Event) IsRegistration() bool

IsRegistration returns true if the event is a registration event.

func (Event) IsShutdown

func (e Event) IsShutdown() bool

IsShutdown returns true if the event is a shutdown event.

type EventMeta added in v0.0.3

type EventMeta struct {
	Label string `json:"label"`
	Color string `json:"color"`
}

EventMeta holds display info for an EventType.

type EventType

type EventType string

EventType categorizes audit log events.

const (
	EventTypeRegistration EventType = "registration"
	EventTypeInvocation   EventType = "invocation"
	EventTypeShutdown     EventType = "shutdown"
	EventTypeHealthCheck  EventType = "health_check"
)

func (EventType) Color added in v0.1.0

func (e EventType) Color() string

Color returns the CSS color token for this event type, used in the HTML visualization.

func (EventType) IsKnown added in v0.1.0

func (e EventType) IsKnown() bool

IsKnown returns true if the event type is a recognized value.

func (EventType) Label added in v0.1.0

func (e EventType) Label() string

Label returns the human-readable display label for this event type.

type Format added in v0.1.0

type Format int

Format identifies the serialization format of a report file.

const (
	// FormatAuto auto-detects JSON vs NDJSON by inspecting the first line.
	FormatAuto Format = iota
	// FormatJSON is a single JSON Report object (contains "version" key).
	FormatJSON
	// FormatNDJSON is newline-delimited Event objects (contains "event_type" key).
	FormatNDJSON
)

func (Format) String added in v0.1.0

func (f Format) String() string

String returns the human-readable format name.

type LoadOption added in v0.1.0

type LoadOption func(*loadConfig)

LoadOption configures LoadReport behavior.

func WithFormat added in v0.1.0

func WithFormat(format Format) LoadOption

WithFormat forces a specific format instead of auto-detection.

type Phase

type Phase string

Phase indicates whether an event is the start or end of an operation.

const (
	PhaseBefore Phase = "before"
	PhaseAfter  Phase = "after"
)

func (Phase) IsKnown added in v0.1.0

func (p Phase) IsKnown() bool

IsKnown returns true if the phase is a recognized value.

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin wraps a samber/do v2 container with audit logging hooks.

func New

func New(config Config) (*Plugin, error)

New creates an audit log plugin.

When Config.Enabled is false (the zero value), New checks the DO_AUDITLOG_ENABLED environment variable. Set it to "true", "1", or "yes" to enable audit logging without changing code. Explicitly setting Enabled to true overrides the env var.

If ContainerID is empty it defaults to "default".

Returns an error if Config.Validate() fails (e.g., ContainerID contains path separators).

Example
plugin, injector := newPluginAndInjectorWithID("my-app")

do.ProvideValue(injector, "hello")

_ = do.MustInvoke[string](injector)

report := plugin.Report()
fmt.Println("services:", report.ServiceCount)
Output:
services: 1

func (*Plugin) DroppedEventCount added in v0.0.3

func (p *Plugin) DroppedEventCount() int64

DroppedEventCount returns the number of events dropped due to Config.MaxEvents.

func (*Plugin) Events

func (p *Plugin) Events() []Event

Events returns a defensive copy of all captured events.

func (*Plugin) EventsCount

func (p *Plugin) EventsCount() int

EventsCount returns the number of captured events without copying the slice.

func (*Plugin) ExportEventsToNDJSON

func (p *Plugin) ExportEventsToNDJSON(path string) error

ExportEventsToNDJSON writes every captured event as a line-delimited JSON stream to path.

func (*Plugin) ExportFilteredToFile

func (p *Plugin) ExportFilteredToFile(path string, opts ...ReportOption) error

ExportFilteredToFile writes a filtered Report as indented JSON to path.

func (*Plugin) ExportToCSV added in v0.1.0

func (p *Plugin) ExportToCSV(path string) error

ExportToCSV writes the Report services as comma-separated values to path.

func (*Plugin) ExportToD2 added in v0.2.0

func (p *Plugin) ExportToD2(path string) error

ExportToD2 writes the dependency graph as a D2 diagram to path.

func (*Plugin) ExportToDOT added in v0.2.0

func (p *Plugin) ExportToDOT(path string) error

ExportToDOT writes the dependency graph as a Graphviz DOT digraph to path.

func (*Plugin) ExportToFile

func (p *Plugin) ExportToFile(path string) error

ExportToFile writes the full Report as indented JSON to path.

Example
plugin := mustNew(auditlog.Config{Enabled: true})
injector := do.NewWithOpts(plugin.Opts())

do.ProvideValue(injector, 42.0)

_ = do.MustInvoke[float64](injector)

path := os.Args[0] + ".audit.json"

err := plugin.ExportToFile(path)
if err != nil {
	fmt.Println("export error:", err)

	return
}

fmt.Println("exported")
Output:
exported

func (*Plugin) ExportToHTML

func (p *Plugin) ExportToHTML(path string) error

ExportToHTML writes a self-contained HTML visualization to a file.

func (*Plugin) ExportToHTMLTree added in v0.3.0

func (p *Plugin) ExportToHTMLTree(path string) error

ExportToHTMLTree writes the service dependency DAG as an HTML tree to path.

func (*Plugin) ExportToMermaid added in v0.2.0

func (p *Plugin) ExportToMermaid(path string) error

ExportToMermaid writes the dependency graph as a Mermaid flowchart to path.

func (*Plugin) ExportToPlantUML added in v0.2.0

func (p *Plugin) ExportToPlantUML(path string) error

ExportToPlantUML writes the dependency graph as a PlantUML component diagram to path.

func (*Plugin) ExportToTSV added in v0.1.0

func (p *Plugin) ExportToTSV(path string) error

ExportToTSV writes the Report services as tab-separated values to path.

func (*Plugin) ExportToTable added in v0.3.0

func (p *Plugin) ExportToTable(path string, format output.Format, opts output.RenderOptions) error

ExportToTable writes the service summary table to path in the specified format.

func (*Plugin) ExportToTree added in v0.3.0

func (p *Plugin) ExportToTree(path string) error

ExportToTree writes the service dependency DAG as an ASCII tree to path.

func (*Plugin) Opts

func (p *Plugin) Opts() *do.InjectorOpts

Opts returns a *do.InjectorOpts ready to pass to do.NewWithOpts. When Enabled is false the returned opts are harmless no-ops.

func (*Plugin) RecordHealthCheck

func (p *Plugin) RecordHealthCheck(injector do.Injector) map[string]error

RecordHealthCheck performs health checks on all services in the injector and records the results as audit events. It wraps injector.HealthCheck() with audit logging for each service result.

When the plugin is disabled, it delegates directly to the injector without recording.

Returns the same map[string]error as the underlying call (nil error = healthy).

Example
plugin := mustNew(auditlog.Config{Enabled: true})
injector := do.NewWithOpts(plugin.Opts())

type dbConn struct{ Connected bool }

do.ProvideNamed(injector, "health-db", func(_ do.Injector) (*dbConn, error) {
	return &dbConn{Connected: true}, nil
})

_ = do.MustInvokeNamed[*dbConn](injector, "health-db")

results := plugin.RecordHealthCheck(injector)
fmt.Println("services checked:", len(results))
Output:
services checked: 1

func (*Plugin) RecordHealthCheckWithContext

func (p *Plugin) RecordHealthCheckWithContext(ctx context.Context, injector do.Injector) map[string]error

RecordHealthCheckWithContext performs health checks on all services in the injector and records the results as audit events. It wraps injector.HealthCheckWithContext(ctx) with audit logging for each service result.

When the plugin is disabled, it delegates directly to the injector without recording.

Returns the same map[string]error as the underlying call (nil error = healthy).

func (*Plugin) Report

func (p *Plugin) Report() Report

Report returns a consolidated snapshot of everything observed so far.

Example
plugin := mustNew(auditlog.Config{Enabled: true})
injector := do.NewWithOpts(plugin.Opts())

type reportConfig struct{ Val string }

do.ProvideNamed(injector, "report-cfg", func(_ do.Injector) (*reportConfig, error) {
	return &reportConfig{Val: "prod"}, nil
})

_ = do.MustInvokeNamed[*reportConfig](injector, "report-cfg")

report := plugin.Report()

svc := report.ServiceByName("report-cfg")
if svc != nil {
	fmt.Println("found service")
}
Output:
found service

func (*Plugin) ReportFiltered

func (p *Plugin) ReportFiltered(opts ...ReportOption) Report

ReportFiltered returns a filtered Report with the given options applied.

func (*Plugin) WriteD2 added in v0.2.0

func (p *Plugin) WriteD2(writer io.Writer) error

WriteD2 writes the dependency graph as a D2 diagram to writer.

func (*Plugin) WriteDOT added in v0.2.0

func (p *Plugin) WriteDOT(writer io.Writer) error

WriteDOT writes the dependency graph as a Graphviz DOT digraph to writer.

func (*Plugin) WriteEventsNDJSON

func (p *Plugin) WriteEventsNDJSON(writer io.Writer) error

WriteEventsNDJSON writes every captured event as a line-delimited JSON stream to writer.

func (*Plugin) WriteHTML

func (p *Plugin) WriteHTML(writer io.Writer) error

WriteHTML writes a self-contained HTML visualization to writer.

func (*Plugin) WriteHTMLTree added in v0.3.0

func (p *Plugin) WriteHTMLTree(writer io.Writer) error

WriteHTMLTree writes the service dependency DAG as an HTML nested list tree to writer.

func (*Plugin) WriteMermaid added in v0.2.0

func (p *Plugin) WriteMermaid(writer io.Writer) error

WriteMermaid writes the dependency graph as a Mermaid flowchart to writer.

func (*Plugin) WritePlantUML added in v0.2.0

func (p *Plugin) WritePlantUML(writer io.Writer) error

WritePlantUML writes the dependency graph as a PlantUML component diagram to writer.

func (*Plugin) WriteReportCSV added in v0.1.0

func (p *Plugin) WriteReportCSV(writer io.Writer) error

WriteReportCSV writes the Report services as comma-separated values to writer.

func (*Plugin) WriteReportJSON

func (p *Plugin) WriteReportJSON(writer io.Writer) error

WriteReportJSON writes the full Report as indented JSON to writer.

func (*Plugin) WriteReportTSV added in v0.1.0

func (p *Plugin) WriteReportTSV(writer io.Writer) error

WriteReportTSV writes the Report services as tab-separated values to writer.

func (*Plugin) WriteTable added in v0.3.0

func (p *Plugin) WriteTable(writer io.Writer, format output.Format, opts output.RenderOptions) error

WriteTable writes the service summary as a table in the specified format to writer.

func (*Plugin) WriteTree added in v0.3.0

func (p *Plugin) WriteTree(writer io.Writer) error

WriteTree writes the service dependency DAG as an ASCII tree to writer.

type ProviderMeta added in v0.0.3

type ProviderMeta struct {
	Icon  string `json:"icon"`
	Label string `json:"label"`
}

ProviderMeta holds display info for a ProviderType.

type ProviderType

type ProviderType string

ProviderType describes how a service was registered in the DI container.

const (
	ProviderTypeLazy      ProviderType = "lazy"
	ProviderTypeEager     ProviderType = "eager"
	ProviderTypeTransient ProviderType = "transient"
	ProviderTypeAlias     ProviderType = "alias"
)

func (ProviderType) Icon

func (p ProviderType) Icon() string

Icon returns the samber/do canonical emoji for this provider type.

func (ProviderType) IsKnown

func (p ProviderType) IsKnown() bool

IsKnown returns true if the provider type is a recognized value.

func (ProviderType) Label added in v0.1.0

func (p ProviderType) Label() string

Label returns the human-readable display label for this provider type.

func (ProviderType) String

func (p ProviderType) String() string

String returns the provider type name.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder captures DI lifecycle events in-memory with minimal overhead.

Locking Protocol

All mutable state is protected by a single sync.RWMutex (mu):

Write path:  mu.Lock()   — all hook methods (OnBefore*, OnAfter*, RecordHealthCheck)
Read path:   mu.RLock()  — BuildReport, Events, EventsCount, ResolveServiceScope

The invocation counter (invocationSeq) uses atomic.Int64, eliminating a separate mutex. Sequence numbers use a separate per-recorder atomic.Int64, also lock-free.

The onEvent callback is always called outside the lock to prevent user code from blocking or deadlocking the recorder.

Critical: enrichCapabilities and do.ExplainInjector

BuildReport copies the scopes map under mu.RLock, then releases the lock BEFORE calling enrichCapabilities. This is mandatory because do.ExplainInjector acquires internal samber/do locks that would deadlock if called from inside any hook (which holds mu).

func NewRecorder

func NewRecorder(containerID string, onEvent func(Event)) *Recorder

NewRecorder creates a new event recorder.

func (*Recorder) BuildReport

func (r *Recorder) BuildReport() Report

BuildReport assembles a machine-readable Report from all captured events.

func (*Recorder) DroppedEventCount added in v0.0.3

func (r *Recorder) DroppedEventCount() int64

DroppedEventCount returns the number of events dropped due to MaxEvents cap.

func (*Recorder) Events

func (r *Recorder) Events() []Event

Events returns a defensive copy of all captured events.

func (*Recorder) EventsCount

func (r *Recorder) EventsCount() int

EventsCount returns the number of captured events without copying the slice.

func (*Recorder) OnAfterInvocation

func (r *Recorder) OnAfterInvocation(scope *do.Scope, serviceName string, err error)

func (*Recorder) OnAfterRegistration

func (r *Recorder) OnAfterRegistration(scope *do.Scope, serviceName string)

func (*Recorder) OnAfterShutdown

func (r *Recorder) OnAfterShutdown(scope *do.Scope, serviceName string, err error)

func (*Recorder) OnBeforeInvocation

func (r *Recorder) OnBeforeInvocation(scope *do.Scope, serviceName string)

func (*Recorder) OnBeforeRegistration

func (r *Recorder) OnBeforeRegistration(scope *do.Scope, serviceName string)

func (*Recorder) OnBeforeShutdown

func (r *Recorder) OnBeforeShutdown(scope *do.Scope, serviceName string)

func (*Recorder) RecordHealthCheck

func (r *Recorder) RecordHealthCheck(scopeID, scopeName, serviceName string, err error)

RecordHealthCheck records a single health check result for a service.

func (*Recorder) ResolveServiceScope

func (r *Recorder) ResolveServiceScope(injector do.Injector, serviceName string) (string, string, bool)

ResolveServiceScope finds the scope metadata for a service by name. Returns (scopeID, scopeName, true) if found, or ("", "", false) otherwise.

type Report

type Report struct {
	Version                 string    `json:"version"`
	ContainerID             string    `json:"container_id"`
	ExportedAt              time.Time `json:"exported_at"`
	EventCount              int       `json:"event_count"`
	ServiceCount            int       `json:"service_count"`
	ScopeCount              int       `json:"scope_count"`
	TotalBuildDurationMs    float64   `json:"total_build_duration_ms"`
	TotalShutdownDurationMs float64   `json:"total_shutdown_duration_ms"`
	ShutdownSucceeded       bool      `json:"shutdown_succeeded"`
	// HealthCheckSucceeded is true when at least one service was health-checked
	// and all passed. It is false when no health checks ran (HealthCheckedCount == 0)
	// or when any service failed its check.
	HealthCheckSucceeded bool `json:"health_check_succeeded"`
	HealthCheckedCount   int  `json:"health_checked_count"`
	// DroppedEventCount is the number of events dropped due to Config.MaxEvents.
	// Always 0 when MaxEvents is 0 (unlimited).
	DroppedEventCount int64 `json:"dropped_event_count"`
	// Reconstructed is true when the report was built by ReplayEvents from a
	// flat event stream rather than from live container hooks. Capability
	// flags (IsHealthchecker, IsShutdowner) are always false on reconstructed
	// reports, and the scope tree may be flattened.
	Reconstructed bool          `json:"reconstructed,omitempty"`
	Events        []Event       `json:"events,omitempty"`
	Services      []ServiceInfo `json:"services"`
	ScopeTree     ScopeNode     `json:"scope_tree"`
}

Report is a consolidated, machine-readable snapshot of the audit log.

func MergeReports added in v0.1.0

func MergeReports(reports []Report) (Report, error)

MergeReports combines multiple reports (e.g. from different containers or scopes) into a single report. Events are concatenated (preserving sequence order per-report but offsetting later reports to avoid collisions). Services are merged: services with the same scopeID + serviceName from different reports are kept separately only if they differ; duplicates are deduplicated. The scope trees are merged by union of all scopes.

The resulting report uses SchemaVersion, the latest ExportedAt, and a combined containerID ("merged"). All aggregate fields are recomputed.

func MigrateReport

func MigrateReport(data []byte) (Report, error)

MigrateReport takes a raw JSON byte slice representing a report exported by a previous schema version and returns a Report compatible with the current SchemaVersion. Unknown fields are preserved through round-tripping.

In addition to upgrading older schemas, MigrateReport always re-derives the denormalized count and aggregate fields (EventCount, ServiceCount, ScopeCount, durations, health flags, per-service Status) from the actual data. This means current-schema input is also repaired: stale or hand-edited reports that would fail Validate() are normalized so the returned Report is always valid. The implied contract is "repair/normalize -> current", not just "upgrade old -> current".

For v0.1.0 → v0.2.0 the migration adds:

  • scope_count, total_build_duration_ms, total_shutdown_duration_ms, shutdown_succeeded
  • health_check_succeeded, health_checked_count (always false/0 for old reports)
  • service_type, status, is_healthchecker, is_shutdowner (zero values)

func NewReport added in v0.1.0

func NewReport(
	version, containerID string,
	exportedAt time.Time,
	events []Event,
	services []ServiceInfo,
	scopeTree ScopeNode,
) (Report, error)

NewReport constructs a validated Report from its core data: the immutable identity/metadata fields and the three data slices (events, services, scope tree). It is the public, validated counterpart to the internal buildReportFromCore path used by BuildReport, Filtered, MigrateReport and ReplayEvents.

Per-service Status is re-derived from the underlying error/timestamp fields (via ServiceInfo.DeriveStatus), so callers never need to compute Status by hand and cannot construct a report whose Status would drift. All denormalized aggregate fields (counts, durations, health flags) are derived from the data.

Returns an error if the inputs are structurally inconsistent in a way that cannot be repaired by re-derivation.

func ReplayEvents added in v0.1.0

func ReplayEvents(events []Event) (Report, error)

ReplayEvents reconstructs a Report from a flat event stream.

This is the inverse of the hook-based recording path: instead of live *do.Scope callbacks mutating a Recorder, it processes already-captured events to rebuild the same serviceRecord/scope state, then assembles a Report via the same buildReportFromCore finalizer.

Limitations (documented, not fixable without additional data):

  • IsHealthchecker and IsShutdowner are always false (they require a live do.ExplainInjector call on a *do.Scope reference).
  • Scope tree hierarchy is flattened (events carry scope_id/scope_name but not parent_id). The first-seen scope becomes root; all others are its direct children.
  • DurationMs values are taken from the events themselves, not recomputed from wall-clock time.

The returned Report has Reconstructed=true so consumers can detect these limitations.

func (Report) Diff added in v0.0.4

func (r Report) Diff(other Report) DiffResult

Diff compares this report with another and returns the structural and status differences. Useful for regression-testing DI graphs across deploys.

The comparison key is (scope_id, service_name). Timestamps and durations are intentionally ignored — only structural changes (added/removed services, dependency edges, status transitions, error appearances) are reported.

func (Report) EventsByRef

func (r Report) EventsByRef(scopeID, serviceName string) []Event

EventsByRef returns all events for the given scope ID and service name.

func (Report) EventsByService

func (r Report) EventsByService(serviceName string) []Event

EventsByService returns all events for the given service name.

func (Report) EventsByType

func (r Report) EventsByType(t EventType) []Event

EventsByType returns all events matching the given event type.

func (Report) FailedServices

func (r Report) FailedServices() []ServiceInfo

FailedServices returns all services with invocation or shutdown errors.

func (Report) Filtered

func (r Report) Filtered(opts ...ReportOption) Report

Filtered returns a new Report with the given filters applied. Services and events that don't match any filter are removed. Summary fields (counts, durations) are recomputed from the filtered data. The scope tree is pruned to only include scopes with matching services.

Example
plugin := mustNew(auditlog.Config{Enabled: true})
injector := do.NewWithOpts(plugin.Opts())

provideString(injector, "alpha", "a")
provideString(injector, "beta", "b")

_ = do.MustInvokeNamed[string](injector, "alpha")
_ = do.MustInvokeNamed[string](injector, "beta")

filtered := plugin.ReportFiltered(
	auditlog.WithServicesByName("alpha"),
)

fmt.Println("filtered services:", filtered.ServiceCount)
Output:
filtered services: 1

func (Report) Index added in v0.0.2

func (r Report) Index() ReportIndex

Index builds a lookup index for O(1) report queries. Useful when performing multiple lookups on the same report.

func (Report) ServiceByName

func (r Report) ServiceByName(name string) *ServiceInfo

ServiceByName returns the first ServiceInfo matching the given exact service name. Returns nil if no service matches. For scoped lookup, use ServiceByRef.

func (Report) ServiceByRef

func (r Report) ServiceByRef(scopeID, serviceName string) *ServiceInfo

ServiceByRef returns the ServiceInfo matching the given scope ID and service name. Returns nil if no service matches.

func (Report) ServicesByScope

func (r Report) ServicesByScope(scopeID string) []ServiceInfo

ServicesByScope returns all services in the given scope.

func (Report) UnhealthyServices

func (r Report) UnhealthyServices() []ServiceInfo

UnhealthyServices returns all services with a health check error.

func (Report) Validate added in v0.0.3

func (r Report) Validate() error

Validate checks internal consistency of the report: denormalized count fields must match the actual slice/tree lengths. Returns nil if consistent, or an error describing the first discrepancy found.

func (Report) WriteCSV added in v0.1.0

func (r Report) WriteCSV(writer io.Writer) error

WriteCSV writes all services as comma-separated values to the writer. The first row is a header. Pointer fields render as empty strings when nil. Dependencies and dependents are semicolon-separated "scope/name" refs.

func (Report) WriteD2 added in v0.2.0

func (r Report) WriteD2(writer io.Writer) error

WriteD2 writes a D2 diagram representing the dependency graph to writer. Each service is a node; edges point from dependent -> dependency. The diagram title is set to the container ID for self-documenting output, and the warm-amber palette is applied per-node via D2 style directives. Edges are deduplicated locally via dedupGraphEdges because the D2 renderer has no built-in DedupEdges. See also WriteMermaid, WritePlantUML, and WriteDOT for the other diagram formats.

Example
plugin := setupNamedRendererInjector()

var buf bytes.Buffer

err := plugin.Report().WriteD2(&buf)
if err != nil {
	fmt.Println("error:", err)

	return
}

fmt.Println("has edge:", bytes.Contains(buf.Bytes(), []byte("->")))
Output:
has edge: true

func (Report) WriteDOT added in v0.1.0

func (r Report) WriteDOT(writer io.Writer) error

WriteDOT writes a Graphviz DOT digraph representing the dependency graph. Each service is a node; edges point from dependent -> dependency. The output is valid input for `dot -Tsvg` / `dot -Tpng`. Nodes carry the warm-amber palette via per-node fillcolor/color attributes.

func (Report) WriteHTML added in v0.1.0

func (r Report) WriteHTML(writer io.Writer) error

WriteHTML renders a self-contained HTML visualization of the report to writer. This enables offline report rendering from a loaded Report (e.g. via LoadReport) without a live Plugin/container.

func (Report) WriteHTMLTree added in v0.3.0

func (r Report) WriteHTMLTree(writer io.Writer) error

WriteHTMLTree writes the service dependency DAG as an HTML nested list tree. Nodes are labeled with service name and provider-type icon.

func (Report) WriteHTMLTreeString added in v0.3.0

func (r Report) WriteHTMLTreeString() (string, error)

WriteHTMLTreeString returns the HTML tree as a string.

func (Report) WriteJSON added in v0.0.4

func (r Report) WriteJSON(writer io.Writer) error

WriteJSON writes the full report as indented JSON to the writer.

func (Report) WriteMermaid

func (r Report) WriteMermaid(writer io.Writer) error

WriteMermaid writes a Mermaid flowchart representing the dependency graph. Each service is a node; edges point from dependent -> dependency. The warm -amber palette is applied per-node via style directives.

Example
plugin := setupNamedRendererInjector()

var buf bytes.Buffer

err := plugin.Report().WriteMermaid(&buf)
if err != nil {
	fmt.Println("error:", err)

	return
}

fmt.Println("has header:", bytes.Contains(buf.Bytes(), []byte("flowchart TD")))
Output:
has header: true

func (Report) WriteNDJSON added in v0.0.4

func (r Report) WriteNDJSON(writer io.Writer) error

WriteNDJSON writes every event as a line-delimited JSON object (NDJSON). Operates directly on the Report.Events slice without a defensive copy, unlike Plugin.WriteEventsNDJSON which copies first.

func (Report) WritePlantUML added in v0.0.2

func (r Report) WritePlantUML(writer io.Writer) error

WritePlantUML writes a PlantUML component diagram representing the dependency graph. Each service is a component; edges point from dependent -> dependency. The warm-amber palette is applied per-node via PlantUML color specs. Paste the output into any tool that renders PlantUML.

func (Report) WriteTSV added in v0.1.0

func (r Report) WriteTSV(writer io.Writer) error

WriteTSV writes all services as tab-separated values to the writer. Identical to WriteCSV but with a tab delimiter for tools that prefer TSV.

func (Report) WriteTable added in v0.3.0

func (r Report) WriteTable(writer io.Writer, format output.Format, opts output.RenderOptions) error

WriteTable writes the service summary as a table in the specified format. Supported formats (when respective sub-modules are imported): table, json, csv, tsv, markdown, xml, d2, yaml, html, tree, mermaid, dot, jsonl, asciidoc, toml, plantuml.

func (Report) WriteTableString added in v0.3.0

func (r Report) WriteTableString(format output.Format, opts output.RenderOptions) (string, error)

WriteTableString returns the service summary table as a string in the specified format. See WriteTable for supported formats.

func (Report) WriteTree added in v0.3.0

func (r Report) WriteTree(writer io.Writer) error

WriteTree writes the service dependency DAG as an ASCII tree. Nodes are labeled with service name and provider-type icon.

func (Report) WriteTreeString added in v0.3.0

func (r Report) WriteTreeString() (string, error)

WriteTreeString returns the ASCII tree as a string.

type ReportIndex added in v0.0.2

type ReportIndex struct {
	ByName       map[string]*ServiceInfo
	ByRef        map[string]*ServiceInfo
	ByScope      map[string][]ServiceInfo
	EventsByName map[string][]Event
	EventsByRef  map[string][]Event
	EventsByType map[EventType][]Event
}

ReportIndex provides O(1) lookups into a Report. Build it once with report.Index() and reuse it for multiple queries.

type ReportOption

type ReportOption func(*reportFilter)

ReportOption is a functional option for filtering a Report.

func WithEventsByType

func WithEventsByType(eventType EventType) ReportOption

WithEventsByType filters the report to only include events with the given event type.

func WithScope

func WithScope(scopeID string) ReportOption

WithScope filters the report to only include services and events in the given scope.

func WithServicesByName

func WithServicesByName(names ...string) ReportOption

WithServicesByName filters the report to only include services with the given names.

func WithServicesByType

func WithServicesByType(providerType ProviderType) ReportOption

WithServicesByType filters the report to only include services with the given provider type.

func WithTimeRange

func WithTimeRange(from, to time.Time) ReportOption

WithTimeRange filters the report to only include events within the given time range.

type ScopeNode

type ScopeNode struct {
	ID       string      `json:"id"`
	Name     string      `json:"name"`
	Services []string    `json:"services,omitempty"`
	Children []ScopeNode `json:"children,omitempty"`
}

ScopeNode represents the scope hierarchy for visualization.

type ServiceDiff added in v0.0.4

type ServiceDiff struct {
	ServiceRef

	StatusChanged         bool `json:"status_changed"`
	InvocationCountDelta  int  `json:"invocation_count_delta"`
	HealthCheckCountDelta int  `json:"health_check_count_delta"`
	HasNewError           bool `json:"has_new_error"`
}

ServiceDiff describes changes to a service that exists in both reports.

type ServiceInfo

type ServiceInfo struct {
	ServiceRef

	Status               ServiceStatus `json:"status"`
	ServiceType          ProviderType  `json:"service_type"`
	RegisteredAt         time.Time     `json:"registered_at"`
	FirstInvokedAt       *time.Time    `json:"first_invoked_at,omitempty"`
	InvocationCount      int           `json:"invocation_count"`
	InvocationOrder      int           `json:"invocation_order"`
	FirstBuildDurationMs *float64      `json:"first_build_duration_ms,omitempty"`
	Dependencies         []ServiceRef  `json:"dependencies,omitempty"`
	Dependents           []ServiceRef  `json:"dependents,omitempty"`
	ShutdownAt           *time.Time    `json:"shutdown_at,omitempty"`
	ShutdownDurationMs   *float64      `json:"shutdown_duration_ms,omitempty"`
	ShutdownError        *string       `json:"shutdown_error,omitempty"`
	InvocationError      *string       `json:"invocation_error,omitempty"`
	IsHealthchecker      bool          `json:"is_healthchecker"`
	IsShutdowner         bool          `json:"is_shutdowner"`

	LastHealthCheckAt *time.Time `json:"last_health_check_at,omitempty"`
	HealthCheckError  *string    `json:"health_check_error,omitempty"`
	HealthCheckCount  int        `json:"health_check_count"`
}

ServiceInfo aggregates all observed data for a single service.

func (*ServiceInfo) DeriveStatus added in v0.0.4

func (s *ServiceInfo) DeriveStatus() ServiceStatus

DeriveStatus computes the lifecycle status from the service's own error pointers and invocation/shutdown timestamps. This is the canonical derivation — the stored Status field should always be populated via this method so it can never drift from the underlying data.

func (*ServiceInfo) HasHealthError

func (s *ServiceInfo) HasHealthError() bool

HasHealthError returns true if the service has a health check error.

func (*ServiceInfo) RederiveStatus added in v0.2.0

func (s *ServiceInfo) RederiveStatus()

RederiveStatus sets Status to the value of DeriveStatus() in place. Use this on *ServiceInfo to repair stale or hand-edited statuses so the report always passes Validate().

func (*ServiceInfo) Uptime

func (s *ServiceInfo) Uptime() time.Duration

Uptime returns the duration since the service was registered.

type ServiceRef

type ServiceRef struct {
	ScopeID     string `json:"scope_id"`
	ScopeName   string `json:"scope_name"`
	ServiceName string `json:"service_name"`
}

ServiceRef identifies a service within a specific scope. Embedded in Event and ServiceInfo for JSON flattening.

func (ServiceRef) IsRoot

func (r ServiceRef) IsRoot() bool

IsRoot returns true if the service belongs to the root scope.

func (ServiceRef) String

func (r ServiceRef) String() string

String returns a human-readable "scope/name" format for the service reference. Root scope services return just the service name.

type ServiceStatus

type ServiceStatus string

ServiceStatus describes the lifecycle state of a service.

const (
	ServiceStatusRegistered      ServiceStatus = "registered"
	ServiceStatusActive          ServiceStatus = "active"
	ServiceStatusInvocationError ServiceStatus = "invocation_error"
	ServiceStatusShutdown        ServiceStatus = "shutdown"
	ServiceStatusShutdownError   ServiceStatus = "shutdown_error"
)

func (ServiceStatus) Icon added in v0.1.0

func (s ServiceStatus) Icon() string

Icon returns the display emoji for this service status.

func (ServiceStatus) IsError

func (s ServiceStatus) IsError() bool

IsError returns true if the service has an invocation or shutdown error.

func (ServiceStatus) IsKnown added in v0.1.0

func (s ServiceStatus) IsKnown() bool

IsKnown returns true if the service status is a recognized value.

type StatusMeta added in v0.0.3

type StatusMeta struct {
	Icon string `json:"icon"`
}

StatusMeta holds display info for a ServiceStatus.

type TypeMetadata added in v0.0.3

type TypeMetadata struct {
	Providers map[string]ProviderMeta `json:"providers"`
	Statuses  map[string]StatusMeta   `json:"statuses"`
	Events    map[string]EventMeta    `json:"events"`
}

TypeMetadata provides display metadata (icons, labels, colors) for all enum types used in the HTML visualization. It is injected into the template as JSON so that JavaScript reads from a single Go-authoritative source instead of maintaining parallel hardcoded constants.

func BuildTypeMetadata added in v0.0.3

func BuildTypeMetadata() TypeMetadata

BuildTypeMetadata constructs display metadata from the Go enum constants. This is the single source of truth — the HTML template's JavaScript reads from the injected JSON rather than maintaining parallel constant definitions.

Directories

Path Synopsis
cmd
auditlog command
Package main implements the auditlog CLI: report conversion, inspection, diffing and validation built on the samber-do-auditlog library.
Package main implements the auditlog CLI: report conversion, inspection, diffing and validation built on the samber-do-auditlog library.
genschema command
Package main is the JSON Schema generator for the auditlog report format.
Package main is the JSON Schema generator for the auditlog report format.
Package main demonstrates EVERY major samber/do v2 feature, all observed by the audit-log plugin.
Package main demonstrates EVERY major samber/do v2 feature, all observed by the audit-log plugin.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL