customizable subsystem loggers
This commit is contained in:
44
lifecycle.go
44
lifecycle.go
@@ -20,14 +20,24 @@ type LifecycleOpts struct {
|
|||||||
// Lifecycle represents the core application structure. Lifecycle manages
|
// Lifecycle represents the core application structure. Lifecycle manages
|
||||||
// resources providing an orchestrator for setup and teardown of sub-systems.
|
// resources providing an orchestrator for setup and teardown of sub-systems.
|
||||||
type Lifecycle struct {
|
type Lifecycle struct {
|
||||||
|
// Logger is the logger for the lifecycle. If not set, it will be initialized
|
||||||
|
// by the lifecycle itself using the default logger.
|
||||||
|
logger *slog.Logger
|
||||||
|
|
||||||
subsystems []*Subsystem
|
subsystems []*Subsystem
|
||||||
setupTracker map[string]bool
|
setupTracker map[string]bool
|
||||||
teardownTracker map[string]bool
|
teardownTracker map[string]bool
|
||||||
opts LifecycleOpts
|
opts LifecycleOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLifecycle creates a new Lifecycle instance with the given subsystems.
|
// NewLifecycle creates a new Lifecycle instance with a default logger and the
|
||||||
func NewLifecycle(subsystems ...*Subsystem) *Lifecycle {
|
// given subsystems. It panics if any subsystem has a duplicate name.
|
||||||
|
func NewLifecycle(defaultLogger *slog.Logger, subsystems ...*Subsystem) *Lifecycle {
|
||||||
|
// Use the provided logger or initialize a default one
|
||||||
|
if defaultLogger == nil {
|
||||||
|
defaultLogger = slog.Default()
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure subsystems are unique
|
// Ensure subsystems are unique
|
||||||
unique := make(map[string]bool)
|
unique := make(map[string]bool)
|
||||||
for _, sub := range subsystems {
|
for _, sub := range subsystems {
|
||||||
@@ -38,6 +48,7 @@ func NewLifecycle(subsystems ...*Subsystem) *Lifecycle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Lifecycle{
|
return &Lifecycle{
|
||||||
|
logger: defaultLogger,
|
||||||
subsystems: subsystems,
|
subsystems: subsystems,
|
||||||
setupTracker: make(map[string]bool),
|
setupTracker: make(map[string]bool),
|
||||||
teardownTracker: make(map[string]bool),
|
teardownTracker: make(map[string]bool),
|
||||||
@@ -57,14 +68,14 @@ func (app *Lifecycle) WithOpts(opts LifecycleOpts) *Lifecycle {
|
|||||||
func (app *Lifecycle) Setup() error {
|
func (app *Lifecycle) Setup() error {
|
||||||
setupCount := 0
|
setupCount := 0
|
||||||
for _, sub := range app.subsystems {
|
for _, sub := range app.subsystems {
|
||||||
if ok, err := app.setupSingle(sub); err != nil {
|
if ok, err := app.setupSingle(sub, app.logger); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
setupCount++
|
setupCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("Lifecycle sub-systems initialized, backend setup complete",
|
app.logger.Info("Lifecycle sub-systems initialized, backend setup complete",
|
||||||
"count", setupCount, "subsystems", mapToString(app.setupTracker))
|
"count", setupCount, "subsystems", mapToString(app.setupTracker))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -83,17 +94,25 @@ func (app *Lifecycle) Teardown() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("All sub-systems torn down, backend teardown complete",
|
app.logger.Info("All sub-systems torn down, backend teardown complete",
|
||||||
"count", teardownCount, "subsystems", mapToString(app.teardownTracker))
|
"count", teardownCount, "subsystems", mapToString(app.teardownTracker))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Require adds sub-system(s) to the lifecycle and immediately runs any setup
|
// Require adds sub-system(s) to the lifecycle and immediately runs any setup
|
||||||
// function. Relevant when a sub-system is not part of the main application
|
// functions. Relevant when a sub-system is not part of the main application
|
||||||
// but may still be conditionally necessary. Any sub-systems that are already
|
// but may still be conditionally necessary. Any sub-systems that are already
|
||||||
// set up are ignored.
|
// set up are ignored.
|
||||||
func (app *Lifecycle) Require(subsystems ...*Subsystem) error {
|
func (app *Lifecycle) Require(subsystems ...*Subsystem) error {
|
||||||
|
return app.RequireL(app.logger, subsystems...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequireL adds sub-system(s) to the lifecycle with a specific logger and
|
||||||
|
// immediately runs any setup functions. See Require for more details.
|
||||||
|
// This variation is useful when you need to set up sub-systems with a non-
|
||||||
|
// default logger.
|
||||||
|
func (app *Lifecycle) RequireL(logger *slog.Logger, subsystems ...*Subsystem) error {
|
||||||
for _, sub := range subsystems {
|
for _, sub := range subsystems {
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
return fmt.Errorf("sub-system is nil")
|
return fmt.Errorf("sub-system is nil")
|
||||||
@@ -101,7 +120,7 @@ func (app *Lifecycle) Require(subsystems ...*Subsystem) error {
|
|||||||
|
|
||||||
// Check if the sub-system is already set up
|
// Check if the sub-system is already set up
|
||||||
if _, ok := app.setupTracker[sub.name]; ok {
|
if _, ok := app.setupTracker[sub.name]; ok {
|
||||||
slog.Warn("sub-system already set up, ignoring", "subsystem", sub.name)
|
app.logger.Warn("sub-system already set up, ignoring", "subsystem", sub.name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,22 +128,25 @@ func (app *Lifecycle) Require(subsystems ...*Subsystem) error {
|
|||||||
app.subsystems = append(app.subsystems, sub)
|
app.subsystems = append(app.subsystems, sub)
|
||||||
|
|
||||||
// Run the setup function for the sub-system
|
// Run the setup function for the sub-system
|
||||||
if _, err := app.setupSingle(sub); err != nil {
|
if _, err := app.setupSingle(sub, logger); err != nil {
|
||||||
return fmt.Errorf("error setting up required subsystem '%s': %w", sub.name, err)
|
return fmt.Errorf("error setting up required subsystem '%s': %w", sub.name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Info("New sub-systems initialized", "all", mapToString(app.setupTracker))
|
app.logger.Info("New sub-systems initialized", "all", mapToString(app.setupTracker))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupSingle is a helper function to set up a single sub-system.
|
// setupSingle is a helper function to set up a single sub-system.
|
||||||
func (app *Lifecycle) setupSingle(sub *Subsystem) (bool, error) {
|
func (app *Lifecycle) setupSingle(sub *Subsystem, logger *slog.Logger) (bool, error) {
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
return false, fmt.Errorf("sub-system is nil")
|
return false, fmt.Errorf("sub-system is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the logger for the sub-system
|
||||||
|
sub.logger = logger
|
||||||
|
|
||||||
// Check if all dependencies are satisfied
|
// Check if all dependencies are satisfied
|
||||||
for _, dep := range sub.depends {
|
for _, dep := range sub.depends {
|
||||||
if _, ok := app.setupTracker[dep.name]; !ok {
|
if _, ok := app.setupTracker[dep.name]; !ok {
|
||||||
@@ -132,7 +154,7 @@ func (app *Lifecycle) setupSingle(sub *Subsystem) (bool, error) {
|
|||||||
return false, fmt.Errorf("dependency '%s' not satisfied for '%s'", dep.name, sub.name)
|
return false, fmt.Errorf("dependency '%s' not satisfied for '%s'", dep.name, sub.name)
|
||||||
} else {
|
} else {
|
||||||
// Attempt to set up the dependency
|
// Attempt to set up the dependency
|
||||||
if _, err := app.setupSingle(dep); err != nil {
|
if _, err := app.setupSingle(dep, logger); err != nil {
|
||||||
return false, fmt.Errorf("error setting up dependency '%s' for '%s': %w", dep.name, sub.name, err)
|
return false, fmt.Errorf("error setting up dependency '%s' for '%s': %w", dep.name, sub.name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
subsystem.go
14
subsystem.go
@@ -1,8 +1,14 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
// Subsystem represents a sub-system of the application, with its setup and
|
// Subsystem represents a sub-system of the application, with its setup and
|
||||||
// teardown functions and dependencies.
|
// teardown functions and dependencies.
|
||||||
type Subsystem struct {
|
type Subsystem struct {
|
||||||
|
logger *slog.Logger
|
||||||
name string
|
name string
|
||||||
setup setupFn
|
setup setupFn
|
||||||
teardown teardownFn
|
teardown teardownFn
|
||||||
@@ -28,3 +34,11 @@ func NewSubsystem(name string, opts SubsystemOpts) *Subsystem {
|
|||||||
depends: opts.Depends,
|
depends: opts.Depends,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger returns the logger for the subsystem.
|
||||||
|
func (s *Subsystem) Logger() *slog.Logger {
|
||||||
|
if s.logger == nil {
|
||||||
|
panic(fmt.Sprintf("subsytem %s used before logger was initialized", s.name))
|
||||||
|
}
|
||||||
|
return s.logger
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user