This document outlines the planned logging architecture for Statifier, introducing a flexible adapter-based logging system that supports both production and test environments with automatic metadata extraction.
- Single Adapter per StateChart: Simplified approach with one logging adapter per state chart instance
- Automatic Metadata Extraction: LogManager automatically extracts core metadata from StateChart
- Test-Friendly: TestAdapter stores logs in StateChart for clean test output
- Configuration-Driven: Adapter selection via application config or runtime options
- Breaking Change: No backward compatibility requirements for this implementation
defprotocol Statifier.Logging.Adapter do
@doc "Log a message at the specified level, returning updated state_chart"
def log(adapter, state_chart, level, message, metadata)
@doc "Check if a log level is enabled for this adapter"
def enabled?(adapter, level)
enddefmodule Statifier.StateChart do
defstruct [
# ... existing fields ...
log_adapter: nil, # Single adapter instance
log_level: :info, # Minimum level for this StateChart
logs: [] # Simple array of log entries
]
end- Integrates with Elixir's Logger
- Used in production environments
- Returns unchanged StateChart
- Stores logs in StateChart's
logsfield - Prevents test output pollution
- Supports optional max_entries for circular buffer behavior
defmodule Statifier.Logging.LogManager do
@doc "Log with automatic metadata extraction"
def log(state_chart, level, message, additional_metadata \\ %{})
# Convenience functions
def trace(state_chart, message, metadata \\ %{})
def debug(state_chart, message, metadata \\ %{})
def info(state_chart, message, metadata \\ %{})
def warn(state_chart, message, metadata \\ %{})
def error(state_chart, message, metadata \\ %{})
endThe LogManager automatically extracts:
current_state: Active leaf states from configurationevent: Current event being processed (if any)
Additional metadata can be provided by callers for context-specific information:
action_type: Type of action (log_action, raise_action, assign_action, etc.)phase: Execution phase (onentry, onexit, transition)target: Transition target statelocation: Assignment location for assign actions
# config/config.exs (production)
config :statifier,
default_log_adapter: {Statifier.Logging.ElixirLoggerAdapter, [logger_module: Logger]},
default_log_level: :info
# config/test.exs (test environment)
config :statifier,
default_log_adapter: {Statifier.Logging.TestAdapter, [max_entries: 100]},
default_log_level: :debug{:ok, state_chart} = Interpreter.initialize(document, [
log_adapter: {Statifier.Logging.TestAdapter, [max_entries: 50]},
log_level: :trace
])# Logs to Elixir Logger with automatic metadata
state_chart = LogManager.info(state_chart, "Processing transition", %{
target: "next_state",
action_type: "transition"
})
# Automatically includes current_state and event# Logs stored in state_chart.logs
{:ok, state_chart} = Interpreter.initialize(document)
state_chart = LogManager.debug(state_chart, "Debug info", %{action_type: "test"})
# Inspect captured logs in tests
assert [%{level: :debug, message: "Debug info"}] = state_chart.logsLogger.info("Raising event '#{event_name}'")state_chart = LogManager.info(state_chart, "Raising event '#{event_name}'", %{
action_type: "raise_action"
})- Create
Statifier.Logging.Adapterprotocol - Implement
Statifier.Logging.ElixirLoggerAdapter - Implement
Statifier.Logging.TestAdapter - Update
Statifier.StateChartstructure - Implement
Statifier.Logging.LogManagerwith automatic metadata - Add application configuration support
- Update
Interpreter.initialize/2to configure logging
- Replace all
Logger.*calls withLogManager.*calls throughout codebase:lib/statifier/actions/log_action.exlib/statifier/actions/raise_action.exlib/statifier/actions/action_executor.exlib/statifier/actions/assign_action.exlib/statifier/datamodel.exlib/statifier/evaluator.ex
- Configure TestAdapter for test environment
- Update tests to use and inspect
state_chart.logs - Add comprehensive test coverage for logging system
- Update documentation and examples
- Create Adapter protocol
- Implement ElixirLoggerAdapter
- Implement TestAdapter
- Update StateChart structure
- Create LogManager with automatic metadata extraction
- Add application configuration support
- Update Interpreter.initialize/2
- Add test environment configuration
- Document configuration options
- Replace Logger calls in actions modules
- Replace Logger calls in datamodel module
- Replace Logger calls in evaluator module
- Update all metadata to use new pattern
- Configure TestAdapter for test suite
- Update existing tests for new logging
- Add logging-specific test cases
- Verify clean test output
- Update API documentation
- Add usage examples
- Update CHANGELOG.md
- Create migration guide
- Clean Test Output: TestAdapter prevents log pollution during test runs
- Consistent Metadata: Automatic extraction ensures uniform logging
- Flexible Configuration: Easy switching between adapters via config
- Future-Ready: Foundation for GenServer-based long-lived interpreters
- Simplified API: Callers only provide context-specific metadata
- Type Safety: Protocol-based design with clear contracts
- Multiple adapters per StateChart (if needed)
- Runtime logging reconfiguration helpers
- Additional built-in adapters (File, Database, etc.)
- Log filtering and routing capabilities
- Performance optimizations for high-throughput scenarios
- Integration with distributed tracing systems
- This is a breaking change - no backward compatibility maintained
- TestAdapter stores logs in StateChart, not in adapter instance
- Log level filtering happens at both StateChart and Adapter levels
- Metadata extraction is automatic but can be overridden when needed