From 69f5dc4496937f1d340e5269b5f047e31d4cd2ff Mon Sep 17 00:00:00 2001 From: steenzout Date: Mon, 23 Nov 2015 15:54:31 -0700 Subject: [PATCH] Logger interface changes: - new Run() - new Status() Logger implementations can now use a channel to receive messages to be processed in a go routine. Simple log manager mutex changed to struct. LogManager interface changes: - Run() now requires the caller to pass the LogMessage channel --- gol.go | 7 ++- internal/examples/application/log.go | 10 +-- loggers/simple/simple.go | 62 +++++++++++++++++-- loggers/simple/simple_test.go | 91 ++++++++++++++++++++++++---- managers/simple/simple.go | 57 ++++++++++------- managers/simple/simple_test.go | 13 ++-- 6 files changed, 188 insertions(+), 52 deletions(-) diff --git a/gol.go b/gol.go index a303c69..ad7b644 100644 --- a/gol.go +++ b/gol.go @@ -32,13 +32,16 @@ type LogFormatter interface { // Logger the interface a log message consumer must implement. type Logger interface { + Close() Filter() LogFilter Formatter() LogFormatter - Writer() io.Writer + Run(chan *LogMessage) Send(*LogMessage) error SetFilter(LogFilter) error SetFormatter(LogFormatter) error SetWriter(io.Writer) error + Status() bool + Writer() io.Writer } // LoggerManager the interface to manage an application set of loggers. @@ -50,7 +53,7 @@ type LoggerManager interface { IsEnabled(n string) (bool, error) List() []string Register(n string, l Logger) error - Run() + Run(chan *LogMessage) Send(*LogMessage) (err error) } diff --git a/internal/examples/application/log.go b/internal/examples/application/log.go index 2f01f07..c4df104 100644 --- a/internal/examples/application/log.go +++ b/internal/examples/application/log.go @@ -29,22 +29,22 @@ import ( manager_simple "github.com/mediaFORGE/gol/managers/simple" ) -// LogWorkers the number of log message workers. -const LogWorkers = 4 - // Log holds the application LogManager instance. var Log gol.LoggerManager func init() { fmt.Println("init():start") - Log = manager_simple.New(LogWorkers) + Log = manager_simple.New() f := filter_severity.New(field_severity.Info) formatter := formatters.Text{} logger := logger_simple.New(f, formatter, os.Stdout) Log.Register("main", logger) - Log.Run() + channel := make(chan *gol.LogMessage, 10) + Log.Run(channel) Log.Send(gol.NewInfo("message", "main.Log has been configured")) + channel <- gol.NewInfo("message", "this message was sent directly to the log manager channel") + fmt.Println("init():end") } diff --git a/loggers/simple/simple.go b/loggers/simple/simple.go index c5d402c..a19baa5 100644 --- a/loggers/simple/simple.go +++ b/loggers/simple/simple.go @@ -19,12 +19,17 @@ package simple import ( "fmt" "io" + "sync" "github.com/mediaFORGE/gol" ) // Logger generic struct for a logger. type Logger struct { + channel chan *gol.LogMessage + mutex sync.Mutex + waitGroup sync.WaitGroup + status bool filter gol.LogFilter formatter gol.LogFormatter writer io.Writer @@ -32,7 +37,33 @@ type Logger struct { // New creates and initializes a generic logger struct. func New(f gol.LogFilter, fmt gol.LogFormatter, w io.Writer) *Logger { - return &Logger{f, fmt, w} + return &Logger{ + mutex: sync.Mutex{}, + waitGroup: sync.WaitGroup{}, + status: false, + filter: f, + formatter: fmt, + writer: w, + } +} + +// Close closes the log message channel and waits for all processing to complete. +func (l *Logger) Close() { + l.mutex.Lock() + defer l.mutex.Unlock() + + if l.status { + l.waitGroup.Wait() + l.status = false + } +} + +// process processes messages from logger channel. +func (l *Logger) process() { + for msg := range l.channel { + l.Send(msg) + } + l.waitGroup.Done() } // Filter returns the logger filter. @@ -45,9 +76,18 @@ func (l *Logger) Formatter() gol.LogFormatter { return l.formatter } -// Writer returns the logger writer. -func (l *Logger) Writer() io.Writer { - return l.writer +// Run runs a go routine to process messages from the logger channel. +func (l *Logger) Run(c chan *gol.LogMessage) { + l.mutex.Lock() + defer l.mutex.Unlock() + + if !l.status { + l.waitGroup.Add(1) + l.channel = c + + go l.process() + l.status = true + } } // Send process log message. @@ -101,4 +141,18 @@ func (l *Logger) SetWriter(w io.Writer) error { return nil } +// Status returns the logger running status. +// True means the logger go routine is running; False otherwise. +func (l *Logger) Status() bool { + l.mutex.Lock() + defer l.mutex.Unlock() + + return l.status +} + +// Writer returns the logger writer. +func (l *Logger) Writer() io.Writer { + return l.writer +} + var _ gol.Logger = (*Logger)(nil) diff --git a/loggers/simple/simple_test.go b/loggers/simple/simple_test.go index 55b86d0..78382d2 100644 --- a/loggers/simple/simple_test.go +++ b/loggers/simple/simple_test.go @@ -21,7 +21,7 @@ import ( "github.com/mediaFORGE/gol" "github.com/mediaFORGE/gol/internal/mock" - "github.com/mediaFORGE/gol/loggers/simple" + logger_simple "github.com/mediaFORGE/gol/loggers/simple" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -34,14 +34,23 @@ type LoggerTestSuite struct { type setupLogTest struct { setUp func( msg *gol.LogMessage, mf *mock.LogFilter, mfmt *mock.LogFormatter, mw *mock.Writer, - ) *simple.Logger + ) *logger_simple.Logger message *gol.LogMessage output string } +func (s *LoggerTestSuite) TestClose() { + + l := logger_simple.New(nil, nil, nil) + + assert.False(s.T(), l.Status()) + l.Close() + assert.False(s.T(), l.Status()) +} + func (s *LoggerTestSuite) TestGetSetFilter() { - l := simple.New(nil, nil, nil) + l := logger_simple.New(nil, nil, nil) assert.Nil(s.T(), l.Filter()) assert.Nil(s.T(), l.SetFilter(&mock.LogFilter{})) @@ -52,7 +61,7 @@ func (s *LoggerTestSuite) TestGetSetFilter() { func (s *LoggerTestSuite) TestGetSetFormatter() { - l := simple.New(nil, nil, nil) + l := logger_simple.New(nil, nil, nil) assert.Nil(s.T(), l.Formatter()) assert.Nil(s.T(), l.SetFormatter(&mock.LogFormatter{})) @@ -63,7 +72,7 @@ func (s *LoggerTestSuite) TestGetSetFormatter() { func (s *LoggerTestSuite) TestGetSetWriter() { - l := simple.New(nil, nil, nil) + l := logger_simple.New(nil, nil, nil) assert.Nil(s.T(), l.Writer()) assert.Nil(s.T(), l.SetWriter(&mock.Writer{})) @@ -72,18 +81,74 @@ func (s *LoggerTestSuite) TestGetSetWriter() { assert.Error(s.T(), l.SetWriter(nil)) } +func (s *LoggerTestSuite) TestRun() { + + in := map[string]setupLogTest{ + "error": setupLogTest{ + setUp: func( + msg *gol.LogMessage, mf *mock.LogFilter, mfmt *mock.LogFormatter, mw *mock.Writer, + ) (logger *logger_simple.Logger) { + mf.Mock.On("Filter", msg).Return(false, nil) + mfmt.Mock.On("Format", msg).Return("ERROR", nil) + mw.Mock.On("Write", []byte("ERROR")).Return(5, nil) + + logger = logger_simple.New(mf, mfmt, mw) + + return + }, + message: gol.NewError(), + output: "ERROR", + }, + "info": setupLogTest{ + setUp: func( + msg *gol.LogMessage, mf *mock.LogFilter, mfmt *mock.LogFormatter, mw *mock.Writer, + ) (logger *logger_simple.Logger) { + mf.Mock.On("Filter", msg).Return(true, nil) + + logger = logger_simple.New(mf, mfmt, mw) + + return + }, + message: gol.NewInfo(), + output: "", + }, + } + + for _, t := range in { + mf := &mock.LogFilter{} + mfmt := &mock.LogFormatter{} + mw := &mock.Writer{} + + logger := t.setUp(t.message, mf, mfmt, mw) + + c := make(chan *gol.LogMessage, 1) + assert.False(s.T(), logger.Status()) + logger.Run(c) + assert.True(s.T(), logger.Status()) + + c <- t.message + close(c) + logger.Close() + assert.False(s.T(), logger.Status()) + + mf.AssertExpectations(s.T()) + mfmt.AssertExpectations(s.T()) + mw.AssertExpectations(s.T()) + } +} + func (s *LoggerTestSuite) TestSend() { in := map[string]setupLogTest{ "error": setupLogTest{ setUp: func( msg *gol.LogMessage, mf *mock.LogFilter, mfmt *mock.LogFormatter, mw *mock.Writer, - ) (logger *simple.Logger) { + ) (logger *logger_simple.Logger) { mf.Mock.On("Filter", msg).Return(false, nil) mfmt.Mock.On("Format", msg).Return("ERROR", nil) mw.Mock.On("Write", []byte("ERROR")).Return(5, nil) - logger = simple.New(mf, mfmt, mw) + logger = logger_simple.New(mf, mfmt, mw) return }, @@ -93,10 +158,10 @@ func (s *LoggerTestSuite) TestSend() { "info": setupLogTest{ setUp: func( msg *gol.LogMessage, mf *mock.LogFilter, mfmt *mock.LogFormatter, mw *mock.Writer, - ) (logger *simple.Logger) { + ) (logger *logger_simple.Logger) { mf.Mock.On("Filter", msg).Return(true, nil) - logger = simple.New(mf, mfmt, mw) + logger = logger_simple.New(mf, mfmt, mw) return }, @@ -123,7 +188,7 @@ func (s *LoggerTestSuite) TestSendNilMessage() { mf := &mock.LogFilter{} mfmt := &mock.LogFormatter{} mw := &mock.Writer{} - logger := simple.New(mf, mfmt, mw) + logger := logger_simple.New(mf, mfmt, mw) assert.Nil(s.T(), logger.Send(nil)) } @@ -133,7 +198,7 @@ func (s *LoggerTestSuite) TestSendNilFormatter() { mf := &mock.LogFilter{} mf.Mock.On("Filter", msg).Return(false, nil) - logger := simple.New(mf, nil, nil) + logger := logger_simple.New(mf, nil, nil) assert.Error(s.T(), logger.Send(msg)) } @@ -145,7 +210,7 @@ func (s *LoggerTestSuite) TestSendFormatError() { mfmt := &mock.LogFormatter{} mfmt.Mock.On("Format", msg).Return("", fmt.Errorf("unknown")) - logger := simple.New(mf, mfmt, nil) + logger := logger_simple.New(mf, mfmt, nil) assert.Error(s.T(), logger.Send(msg)) } @@ -157,7 +222,7 @@ func (s *LoggerTestSuite) TestSendNilWriter() { mfmt := &mock.LogFormatter{} mfmt.Mock.On("Format", msg).Return("ERROR", nil) - logger := simple.New(mf, mfmt, nil) + logger := logger_simple.New(mf, mfmt, nil) assert.Error(s.T(), logger.Send(msg)) } diff --git a/managers/simple/simple.go b/managers/simple/simple.go index 53e2bdd..445374b 100644 --- a/managers/simple/simple.go +++ b/managers/simple/simple.go @@ -25,8 +25,9 @@ import ( // entry the internal structure that links a logger to a status. type entry struct { - logger gol.Logger - status bool + channel chan *gol.LogMessage + logger gol.Logger + status bool } // Manager generic struct for a logger manager. @@ -34,31 +35,42 @@ type Manager struct { capacity int loggers map[string]entry channel chan *gol.LogMessage + mutex sync.Mutex waitGroup sync.WaitGroup status bool } -// mutex lock to guarantee only one Run() goroutine is running per LogManager instance. -var mutex = &sync.Mutex{} - // New creates a simple implementation of a logger manager. -func New(cap int) gol.LoggerManager { +func New() gol.LoggerManager { return &Manager{ - capacity: cap, - loggers: make(map[string]entry), - channel: make(chan *gol.LogMessage, cap), + loggers: make(map[string]entry), + mutex: sync.Mutex{}, } } // Close closes the log message channel and waits for all processing to complete. func (m *Manager) Close() { + m.mutex.Lock() + defer m.mutex.Unlock() + close(m.channel) m.waitGroup.Wait() + + for _, e := range m.loggers { + e.status = false + close(e.channel) + e.logger.Close() + } + m.status = false } // Deregister removes the logger with the given name from the manager. func (m *Manager) Deregister(n string) (err error) { if _, ok := m.loggers[n]; ok { + e := m.loggers[n] + e.status = false + close(e.channel) + delete(m.loggers, n) } else { err = fmt.Errorf("No logger registered as %s", n) @@ -122,26 +134,29 @@ func (m *Manager) Register(n string, l gol.Logger) error { if l == nil { return fmt.Errorf("Cannot register a nil logger") } + lchan := make(chan *gol.LogMessage, 1) + l.Run(lchan) m.loggers[n] = entry{ - logger: l, - status: true, + channel: lchan, + logger: l, + status: true, } return nil } // Run start a goroutine that will distribute all messages in // the LogManager channel to each registered and enabled logger. -func (m *Manager) Run() { - mutex.Lock() +func (m *Manager) Run(c chan *gol.LogMessage) { + m.mutex.Lock() + defer m.mutex.Unlock() + if !m.status { m.status = true - m.waitGroup.Add(m.capacity) + m.waitGroup.Add(1) + m.channel = c - for i := 0; i < m.capacity; i++ { - go m.process() - } + go m.process() } - mutex.Unlock() } // Send process log message. @@ -161,9 +176,9 @@ func (m *Manager) process() { } func (m *Manager) sendMessageToLoggers(msg *gol.LogMessage) { - for _, l := range m.loggers { - if l.status { - l.logger.Send(msg) + for _, e := range m.loggers { + if e.status { + e.channel <- msg } } } diff --git a/managers/simple/simple_test.go b/managers/simple/simple_test.go index d3550a0..5ecb341 100644 --- a/managers/simple/simple_test.go +++ b/managers/simple/simple_test.go @@ -31,14 +31,10 @@ import ( "github.com/stretchr/testify/suite" ) -const ( - // Capacity the number of messages the log message channel can hold. - Capacity = 1 -) - type ManagerTestSuite struct { suite.Suite manager gol.LoggerManager + channel chan *gol.LogMessage } func (s *ManagerTestSuite) testIsEnabled(n string, b bool, e error) { @@ -54,11 +50,13 @@ func (s *ManagerTestSuite) testIsEnabled(n string, b bool, e error) { } func (s *ManagerTestSuite) SetupTest() { - s.manager = manager_simple.New(Capacity) + s.manager = manager_simple.New() + s.channel = make(chan *gol.LogMessage, 1) } func (s *ManagerTestSuite) TeardownTest() { s.manager.Close() + close(s.channel) } func (s *ManagerTestSuite) TestDeregister() { @@ -165,7 +163,8 @@ func (s *ManagerTestSuite) TestSend() { s.manager.Register("l1", l1) s.manager.Register("l2", l2) - s.manager.Run() + s.manager.Run(s.channel) + assert.Nil(s.T(), s.manager.Send(m)) time.Sleep(1 * time.Second) s.manager.Close()