Skip to content

Commit

Permalink
Merge pull request #23 from tozny/feature/logging-interface
Browse files Browse the repository at this point in the history
Feature/logging interface
  • Loading branch information
Luke Woodward authored May 17, 2019
2 parents e3ceecf + f704464 commit 321c190
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 29 deletions.
65 changes: 36 additions & 29 deletions connectionmanager/connectionmanager.go → lifecycle/manager.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package connectionmanager
package lifecycle

import (
"sync"
Expand Down Expand Up @@ -26,32 +26,32 @@ type InitializerCloser interface {
// CloseFunc is a function that gracefully shuts down a connection as a side effect.
type CloseFunc func()

// ConnectionManager allows multiple items needing initialization or shutdown to be
// Manager allows multiple items needing initialization or shutdown to be
// managed as a group.
//
// Initialization and Close of connections are managed independently of each other. Once
// created the connection manager can accept any number of items supporting initialization,
// close, or both. The ManageInitialization, ManageClose, and ManageConnection methods can
// Initialization and Close of items are managed independently of each other. Once
// created the manager can accept any number of items supporting initialization,
// close, or both. The ManageInitialization, ManageClose, and ManageLifecycle methods can
// be called as many times as needed in any order to add managed items. They are variadic
// functions, so multiple items can be added in a single call.
//
// Initialization items will immediately start initialization in a separate go routine
// once the item is added to the ConnectionManager. An internally managed sync.WaitGroup
// is made available. Calling WG.Wait() on the ConnectionManager will block the current
// once the item is added to the lifecycle.Manager. An internally managed sync.WaitGroup
// is made available. Calling WG.Wait() on the lifecycle.Manager will block the current
// go routine until all initialization functions are complete.
//
// Closers are queued up internally running only when the ConnectionManager's Close method
// is called. The ConnectionManager runs each Close method in a separate go routine and blocks
// Closers are queued up internally running only when the lifecycle.Manager's Close method
// is called. The Manager runs each Close method in a separate go routine and blocks
// until all are complete.
type ConnectionManager struct {
type Manager struct {
closerChan chan CloseFunc
Close CloseFunc
close CloseFunc
WG sync.WaitGroup
}

// New initializes a new ConnectionManager object that can be used
// to manage the life of long lived remote connections such as to a database.
func New(logger *logging.ServiceLogger) ConnectionManager {
// NewManager initializes a new lifecycle.Manager object that can be used
// to manage the lifecycle of items such as connections to a database.
func NewManager(logger logging.Logger) Manager {
closerChan := make(chan CloseFunc)
shutdown := make(chan struct{})
var stopwg sync.WaitGroup
Expand All @@ -76,49 +76,56 @@ func New(logger *logging.ServiceLogger) ConnectionManager {
}(c)
}
}()
return ConnectionManager{
return Manager{
closerChan: closerChan,
Close: func() {
close: func() {
shutdown <- struct{}{}
stopwg.Wait()
},
}
}

// ManageInitialization allows the connection manager to accept any number of items
// ManageInitialization allows the lifecycle manager to accept any number of items
// matching the Initializer interface and initializes each in parallel. The wait
// group is managed to allow callers to block until all managed initialization
// methods are complete.
func (cm *ConnectionManager) ManageInitialization(initializers ...Initializer) {
func (m *Manager) ManageInitialization(initializers ...Initializer) {
for _, initializer := range initializers {
cm.WG.Add(1)
m.WG.Add(1)
go func(i Initializer) {
i.Initialize()
cm.WG.Done()
m.WG.Done()
}(initializer)
}
}

// ManageClose allow the connection manager to accept any number of items matching
// ManageClose allows the lifecycle manager to accept any number of items matching
// the Closer interface. It queues them up internally. When Close is called on
// the connection manager, all queued Close methods are executed in parallel.
// The close method blocks until managed Closers are complete.
func (cm *ConnectionManager) ManageClose(closers ...Closer) {
// the lifecycle manager, all queued Close methods are executed in parallel.
// The close method block until all managed Closers are complete.
func (m *Manager) ManageClose(closers ...Closer) {
for _, closer := range closers {
cm.closerChan <- closer.Close
m.closerChan <- closer.Close
}
}

// ManageConnection accepts any number of items matching the InitializerCloser
// ManageLifecycle accepts any number of items matching the InitializerCloser
// interface and manages both an item's initialization and close.
//
// The close method of the managed item is queued first to ensure it is present
// before running the item's initialization which happens immediately when calling
// the ManageInitialization method. Without this order, close may not get managed
// if something interupts before initialization is complete.
func (cm *ConnectionManager) ManageConnection(initializerClosers ...InitializerCloser) {
func (m *Manager) ManageLifecycle(initializerClosers ...InitializerCloser) {
for _, ic := range initializerClosers {
cm.ManageClose(ic)
cm.ManageInitialization(ic)
m.ManageClose(ic)
m.ManageInitialization(ic)
}
}

// Close runs the stored close function function, which will shut down all of
// the items that have been queued for cleanup. Each item is cleaned up in
// parallel. Close will block until all items have been successfully cleaned up.
func (m *Manager) Close() {
m.close()
}
24 changes: 24 additions & 0 deletions logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,33 @@ package logging
import (
"io"
"io/ioutil"
"log"
"os"
)

// Logger is an interface defining object that providers logging methods that are
// log level aware. This is especially useful when another interface supports
// logging with logging levels. This interface can be embedded.
type Logger interface {
Raw() *log.Logger
SetLevel(string)
Print(...interface{})
Printf(string, ...interface{})
Println(...interface{})
Debug(...interface{})
Debugf(string, ...interface{})
Debugln(...interface{})
Info(...interface{})
Infof(string, ...interface{})
Infoln(...interface{})
Error(...interface{})
Errorf(string, ...interface{})
Errorln(...interface{})
Critical(...interface{})
Criticalf(string, ...interface{})
Criticalln(...interface{})
}

// LogWriter maps string values to io.Writer interfaces intended for logging output.
//
// This function is intended to provide a standard way of mapping environment-based
Expand Down
6 changes: 6 additions & 0 deletions logging/servicelogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ func NewServiceLogger(out io.Writer, serviceName string, level string) ServiceLo
return logger
}

// Raw returns the underlying log.Logger for use in methods that do not support a full
// logging.Logger with log levels.
func (sl *ServiceLogger) Raw() *log.Logger {
return sl.Logger
}

// SetLevel allows the log level of the ServiceLogger to be updated based on
// supported log level strings. These include in order:
// - "OFF"
Expand Down

0 comments on commit 321c190

Please sign in to comment.