diff --git a/docs/architecture/diagrams/plantuml/class_diagram.plantuml b/docs/architecture/diagrams/plantuml/class_diagram.plantuml index 8ab606c..5e510e7 100644 --- a/docs/architecture/diagrams/plantuml/class_diagram.plantuml +++ b/docs/architecture/diagrams/plantuml/class_diagram.plantuml @@ -26,19 +26,25 @@ package pkg.logger { } package handler { interface Interface { - + Level() loglevel.LogLevel - + SetLevel(level loglevel.LogLevel) + + Writer() : io.Writer + + FromLevel() loglevel.LogLevel + + SetFromLevel(fromLevel loglevel.LogLevel) + + ToLevel() loglevel.LogLevel + + SetToLevel(toLevel loglevel.LogLevel) + Formatter() : formatter.Interface + Write(logName : string, level : loglevel.LogLevel, message : string, parameters : ...any) } struct Handler implements Interface { - ~ level : loglevel.LogLevel + ~ fromLevel : loglevel.LogLevel + ~ toLevel : loglevel.LogLevel ~ formatter : formatter.Interface ~ writer : io.Writer - ~ errorWriter : io.Writer ~ consoleSupportsANSIColors : func() bool - + Level() : loglevel.LogLevel + + Writer() : io.Writer + + FromLevel() : loglevel.LogLevel + SetLevel(level : loglevel.LogLevel) + + ToLevel() : loglevel.LogLevel + + SetToLevel(level : loglevel.LogLevel) + Formatter() : formatter.Interface + Write(logName : string, level : loglevel.LogLevel, message : string, parameters : ...any) } @@ -46,9 +52,10 @@ package pkg.logger { ~ osOpenFile : os.OpenFile(name : string, flag : int, perm : FileMode) : (*File, error) ~ osStdout : *File ~ osStderr : *File - + New(level : loglevel.LogLevel, newFormatter : formatter.Interface, writer : io.Writer, errorWriter : io.Writer) : *Handler - + NewConsoleHandler(level : loglevel.LogLevel, newFormatter : formatter.Interface) : *Handler - + NewFileHandler(level : loglevel.LogLevel, newFormatter : formatter.Interface, file : string) : *Handler + + New(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, newFormatter : formatter.Interface, writer : io.Writer, errorWriter : io.Writer) : *Handler + + NewConsoleHandler(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, newFormatter : formatter.Interface) : *Handler + + NewConsoleErrorHandler(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, newFormatter : formatter.Interface) : *Handler + + NewFileHandler(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, newFormatter : formatter.Interface, file : string) : *Handler ~ consoleSupportsANSIColors() : bool } @@ -58,9 +65,11 @@ package pkg.logger { stereotype LogLevel <> { + String() : string + DigitRepresentation() : int + + Next() : LogLevel + + Previous() : LogLevel } class "<>" { - + None : loglevel.LogLevel + + All : loglevel.LogLevel + Trace : loglevel.LogLevel + Debug : loglevel.LogLevel + Verbose : loglevel.LogLevel @@ -72,6 +81,7 @@ package pkg.logger { + Alert : loglevel.LogLevel + Critical : loglevel.LogLevel + Emergency : loglevel.LogLevel + + Null : loglevel.LogLevel } "<>" ..> LogLevel : uses } @@ -80,7 +90,8 @@ package pkg.logger { + Name() : string + SetName(name : string) + Handlers() : []handler.Interface - + AddHandler(handler : handler.Interface) + + AddHandler(handlerInterface : handler.Interface) + + RemoveHandler(handlerInterface : handler.Interface) } struct baseLogger implements baseLoggerInterface { ~ name : string @@ -89,12 +100,14 @@ package pkg.logger { + Name() : string + SetName(name : string) + Handlers() : []handler.Interface - + AddHandler(handler : handler.Interface) + + AddHandler(handlerInterface : handler.Interface) + + RemoveHandler(handlerInterface : handler.Interface) } interface Interface { + Name() : string + Handlers() : []handler.Interface - + AddHandler(handler : handler.Interface) + + AddHandler(handlerInterface : handler.Interface) + + RemoveHandler(handlerInterface : handler.Interface) + Trace(message : string, parameters : ...any) + Debug(message : string, parameters : ...any) + Verbose(message : string, parameters : ...any) @@ -111,7 +124,8 @@ package pkg.logger { ~ baseLogger : baseLoggerInterface + Name() : string + Handlers() : []handler.Interface - + AddHandler(handler : handler.Interface) + + AddHandler(handlerInterface : handler.Interface) + + RemoveHandler(handlerInterface : handler.Interface) + Trace(message : string, parameters : ...any) + Debug(message : string, parameters : ...any) + Verbose(message : string, parameters : ...any) @@ -124,11 +138,32 @@ package pkg.logger { + Critical(message : string, parameters : ...any) + Emergency(message : string, parameters : ...any) } + struct Configuration { + ~ fromLevel : loglevel.LogLevel + ~ toLevel : loglevel.LogLevel + ~ template : string + ~ file : string + ~ name : string + } + stereotype Option <> {} class "<>" { ~ rootLogger : *Logger + ~ fromLevel : loglevel.LogLevel + ~ toLevel : loglevel.LogLevel + ~ template : string + ~ init() + New(name : string) : *Logger - + GetDefaultLogger() : *Logger - + SetLevel(level : loglevel.LogLevel) + + WithFromLevel(fromLevel : loglevel.LogLevel) : Option + + WithToLevel(toLevel : loglevel.LogLevel) : Option + + WithTemplate(template : string) : Option + + WithFile(file : string) : Option + + WithName(name : string) : Option + + NewConfiguration(options : ...Option) : *Configuration + + Configure(configuration : *Configuration) + + Name() : string + + Template() : string + + FromLevel() : loglevel.LogLevel + + ToLevel() : loglevel.LogLevel + Trace(message : string, parameters : ...any) + Debug(message : string, parameters : ...any) + Verbose(message : string, parameters : ...any) @@ -144,6 +179,9 @@ package pkg.logger { Logger *-- baseLoggerInterface "<>" ..> Logger : uses + "<>" ..> Option : uses + "<>" ..> Configuration : uses + Option ..> Configuration : uses } pkg.logger.handler.Handler *-- pkg.logger.loglevel.LogLevel : contains diff --git a/docs/architecture/diagrams/plantuml/create_new_logger.plantuml b/docs/architecture/diagrams/plantuml/create_new_logger.plantuml index dd49070..5287410 100644 --- a/docs/architecture/diagrams/plantuml/create_new_logger.plantuml +++ b/docs/architecture/diagrams/plantuml/create_new_logger.plantuml @@ -16,9 +16,9 @@ main -> formatter: New(template) activate formatter deactivate formatter -main -> handler: NewConsoleHandler(level, formatter) +main -> handler: NewConsoleHandler(fromLevel, toLevel, formatter) activate handler -handler -> handler: New(level, formatter, writer, errorWriter) +handler -> handler: New(fromLevel, toLevel, formatter, writer) deactivate handler note right of main: Calling AddHandler on the instance of Logger returned by New diff --git a/docs/architecture/diagrams/png/class_diagram.png b/docs/architecture/diagrams/png/class_diagram.png index ae5b5ff..49b2f03 100644 Binary files a/docs/architecture/diagrams/png/class_diagram.png and b/docs/architecture/diagrams/png/class_diagram.png differ diff --git a/docs/architecture/diagrams/png/create_new_logger.png b/docs/architecture/diagrams/png/create_new_logger.png index 4971b0e..7bda906 100644 Binary files a/docs/architecture/diagrams/png/create_new_logger.png and b/docs/architecture/diagrams/png/create_new_logger.png differ diff --git a/docs/architecture/diagrams/svg/class_diagram.svg b/docs/architecture/diagrams/svg/class_diagram.svg index 12e1aa8..e30c923 100644 --- a/docs/architecture/diagrams/svg/class_diagram.svg +++ b/docs/architecture/diagrams/svg/class_diagram.svg @@ -1,807 +1,1063 @@ + height="1570.14px" preserveAspectRatio="none" style="width:1282px;height:1570px;background:#FFFFFF;" version="1.1" + viewBox="0 0 1282 1570" width="1282.22px" zoomAndPan="magnify"> - - - pkg + + + pkg - - - logger + + + logger - - - formatter + + + formatter - - - handler + + + handler - - - loglevel + + + loglevel - - - + + - baseLoggerInterface - - - - - Log(level : loglevel.LogLevel, message : string, parameters : ...any) - - - Name() : string - - - SetName(name : string) - - - Handlers() : []handler.Interface - - - AddHandler(handler : handler.Interface) + baseLoggerInterface + + + + + Log(level : loglevel.LogLevel, message : string, parameters : ...any) + + + Name() : string + + + SetName(name : string) + + + Handlers() : []handler.Interface + + + AddHandler(handlerInterface : handler.Interface) + + + RemoveHandler(handlerInterface : handler.Interface) - - - + + - baseLogger - - - - name : string - - - handlers : []handler.Interface - - - - Log(level : loglevel.LogLevel, message : string, parameters : ...any) - - - Name() : string - - - SetName(name : string) - - - Handlers() : []handler.Interface - - - AddHandler(handler : handler.Interface) + baseLogger + + + + name : string + + + handlers : []handler.Interface + + + + Log(level : loglevel.LogLevel, message : string, parameters : ...any) + + + Name() : string + + + SetName(name : string) + + + Handlers() : []handler.Interface + + + AddHandler(handlerInterface : handler.Interface) + + + RemoveHandler(handlerInterface : handler.Interface) - - - + + - Interface - - - - - Name() : string - - - Handlers() : []handler.Interface - - - AddHandler(handler : handler.Interface) - - - Trace(message : string, parameters : ...any) - - - Debug(message : string, parameters : ...any) - - - Verbose(message : string, parameters : ...any) - - - Info(message : string, parameters : ...any) - - - Notice(message : string, parameters : ...any) - - - Warning(message : string, parameters : ...any) - - - Severe(message : string, parameters : ...any) - - - Error(message : string, parameters : ...any) - - - Alert(message : string, parameters : ...any) - - - Critical(message : string, parameters : ...any) - - - Emergency(message : string, parameters : ...any) + Interface + + + + + Name() : string + + + Handlers() : []handler.Interface + + + AddHandler(handlerInterface : handler.Interface) + + + RemoveHandler(handlerInterface : handler.Interface) + + + Trace(message : string, parameters : ...any) + + + Debug(message : string, parameters : ...any) + + + Verbose(message : string, parameters : ...any) + + + Info(message : string, parameters : ...any) + + + Notice(message : string, parameters : ...any) + + + Warning(message : string, parameters : ...any) + + + Severe(message : string, parameters : ...any) + + + Error(message : string, parameters : ...any) + + + Alert(message : string, parameters : ...any) + + + Critical(message : string, parameters : ...any) + + + Emergency(message : string, parameters : ...any) - - - + + - Logger - - - - baseLogger : baseLoggerInterface - - - - Name() : string - - - Handlers() : []handler.Interface - - - AddHandler(handler : handler.Interface) - - - Trace(message : string, parameters : ...any) - - - Debug(message : string, parameters : ...any) - - - Verbose(message : string, parameters : ...any) - - - Info(message : string, parameters : ...any) - - - Notice(message : string, parameters : ...any) - - - Warning(message : string, parameters : ...any) - - - Severe(message : string, parameters : ...any) - - - Error(message : string, parameters : ...any) - - - Alert(message : string, parameters : ...any) - - - Critical(message : string, parameters : ...any) - - - Emergency(message : string, parameters : ...any) + Logger + + + + baseLogger : baseLoggerInterface + + + + Name() : string + + + Handlers() : []handler.Interface + + + AddHandler(handlerInterface : handler.Interface) + + + RemoveHandler(handlerInterface : handler.Interface) + + + Trace(message : string, parameters : ...any) + + + Debug(message : string, parameters : ...any) + + + Verbose(message : string, parameters : ...any) + + + Info(message : string, parameters : ...any) + + + Notice(message : string, parameters : ...any) + + + Warning(message : string, parameters : ...any) + + + Severe(message : string, parameters : ...any) + + + Error(message : string, parameters : ...any) + + + Alert(message : string, parameters : ...any) + + + Critical(message : string, parameters : ...any) + + + Emergency(message : string, parameters : ...any) + + + + + + + Configuration + + + + fromLevel : loglevel.LogLevel + + + toLevel : loglevel.LogLevel + + + template : string + + + file : string + + + name : string + + + + + + + + «func(*Configuration)» + Option + + + - - - + + - «module» - - - - rootLogger : *Logger - - - - New(name : string) : *Logger - - - GetDefaultLogger() : *Logger - - - SetLevel(level : loglevel.LogLevel) - - - Trace(message : string, parameters : ...any) - - - Debug(message : string, parameters : ...any) - - - Verbose(message : string, parameters : ...any) - - - Info(message : string, parameters : ...any) - - - Notice(message : string, parameters : ...any) - - - Warning(message : string, parameters : ...any) - - - Severe(message : string, parameters : ...any) - - - Error(message : string, parameters : ...any) - - - Alert(message : string, parameters : ...any) - - - Critical(message : string, parameters : ...any) - - - Emergency(message : string, parameters : ...any) + «module» + + + + rootLogger : *Logger + + + fromLevel : loglevel.LogLevel + + + toLevel : loglevel.LogLevel + + + template : string + + + + init() + + + New(name : string) : *Logger + + + WithFromLevel(fromLevel : loglevel.LogLevel) : Option + + + WithToLevel(toLevel : loglevel.LogLevel) : Option + + + WithTemplate(template : string) : Option + + + WithFile(file : string) : Option + + + WithName(name : string) : Option + + + NewConfiguration(options : ...Option) : *Configuration + + + Configure(configuration : *Configuration) + + + Name() : string + + + Template() : string + + + FromLevel() : loglevel.LogLevel + + + ToLevel() : loglevel.LogLevel + + + Trace(message : string, parameters : ...any) + + + Debug(message : string, parameters : ...any) + + + Verbose(message : string, parameters : ...any) + + + Info(message : string, parameters : ...any) + + + Notice(message : string, parameters : ...any) + + + Warning(message : string, parameters : ...any) + + + Severe(message : string, parameters : ...any) + + + Error(message : string, parameters : ...any) + + + Alert(message : string, parameters : ...any) + + + Critical(message : string, parameters : ...any) + + + Emergency(message : string, parameters : ...any) - - - + + - Interface - - - - - Template() : string - - - Format(message : string, loggerName : string, level : loglevel.LogLevel, + Interface + + + + + Template() : string + + + Format(message : string, loggerName : string, level : loglevel.LogLevel, colored : bool) : string - - - + + - Formatter - - - - template : string - - - - IsEqual(anotherFormatter : *Formatter) : bool - - - EvaluatePreset(message : string, loggerName : string, level : + Formatter + + + + template : string + + + + IsEqual(anotherFormatter : *Formatter) : bool + + + EvaluatePreset(message : string, loggerName : string, level : loglevel.LogLevel) : map[string]string - - Template() : string + + Template() : string - - Format(message : string, loggerName : string, level : loglevel.LogLevel, + + Format(message : string, loggerName : string, level : loglevel.LogLevel, colored : bool) : string - - - + + - «module» - - - - logLevelColors : map[loglevel.LogLevel]string - - - resetColor : string - - - - New(template : string) : *Formatter + «module» + + + + logLevelColors : map[loglevel.LogLevel]string + + + resetColor : string + + + + New(template : string) : *Formatter - - - + + - Interface - - - - - Level() loglevel.LogLevel - - - SetLevel(level loglevel.LogLevel) - - - Formatter() : formatter.Interface - - - Write(logName : string, level : loglevel.LogLevel, message : string, parameters : - ...any) + Interface + + + + + Writer() : io.Writer + + + FromLevel() loglevel.LogLevel + + + SetFromLevel(fromLevel loglevel.LogLevel) + + + ToLevel() loglevel.LogLevel + + + SetToLevel(toLevel loglevel.LogLevel) + + + Formatter() : formatter.Interface + + + Write(logName : string, level : loglevel.LogLevel, message : string, + parameters : ...any) - - - + + - Handler - - - - level : loglevel.LogLevel - - - formatter : formatter.Interface - - - writer : io.Writer - - - errorWriter : io.Writer - - - - consoleSupportsANSIColors : func() bool - - - Level() : loglevel.LogLevel - - - SetLevel(level : loglevel.LogLevel) - - - Formatter() : formatter.Interface - - - Write(logName : string, level : loglevel.LogLevel, message : string, + Handler + + + + fromLevel : loglevel.LogLevel + + + toLevel : loglevel.LogLevel + + + formatter : formatter.Interface + + + writer : io.Writer + + + + consoleSupportsANSIColors : func() bool + + + Writer() : io.Writer + + + FromLevel() : loglevel.LogLevel + + + SetLevel(level : loglevel.LogLevel) + + + ToLevel() : loglevel.LogLevel + + + SetToLevel(level : loglevel.LogLevel) + + + Formatter() : formatter.Interface + + + Write(logName : string, level : loglevel.LogLevel, message : string, parameters : ...any) - - - + + - «module» - - - - osStdout : *File - - - osStderr : *File - - - - osOpenFile : os.OpenFile(name : string, flag : int, perm : FileMode) : (*File, + «module» + + + + osStdout : *File + + + osStderr : *File + + + + osOpenFile : os.OpenFile(name : string, flag : int, perm : FileMode) : (*File, error) - - New(level : loglevel.LogLevel, newFormatter : formatter.Interface, writer : - io.Writer, errorWriter : io.Writer) : *Handler - - - NewConsoleHandler(level : loglevel.LogLevel, newFormatter : formatter.Interface) : - *Handler - - - NewFileHandler(level : loglevel.LogLevel, newFormatter : formatter.Interface, file : - string) : *Handler - - - consoleSupportsANSIColors() : bool + + New(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, newFormatter : + formatter.Interface, writer : io.Writer, errorWriter : io.Writer) : *Handler + + + NewConsoleHandler(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, + newFormatter : formatter.Interface) : *Handler + + + NewConsoleErrorHandler(fromLevel : loglevel.LogLevel, toLevel : + loglevel.LogLevel, newFormatter : formatter.Interface) : *Handler + + + NewFileHandler(fromLevel : loglevel.LogLevel, toLevel : loglevel.LogLevel, + newFormatter : formatter.Interface, file : string) : *Handler + + + consoleSupportsANSIColors() : bool - - - + + - «int» - - LogLevel - - - - - String() : string - - - DigitRepresentation() : int + «int» + + LogLevel + + + + + String() : string + + + DigitRepresentation() : int + + + Next() : LogLevel + + + Previous() : LogLevel - - - + + - «module» - - - - None : loglevel.LogLevel - - - Trace : loglevel.LogLevel - - - Debug : loglevel.LogLevel - - - Verbose : loglevel.LogLevel - - - Info : loglevel.LogLevel - - - Notice : loglevel.LogLevel - - - Warning : loglevel.LogLevel - - - Severe : loglevel.LogLevel - - - Error : loglevel.LogLevel - - - Alert : loglevel.LogLevel - - - Critical : loglevel.LogLevel - - - Emergency : loglevel.LogLevel - - + «module» + + + + All : loglevel.LogLevel + + + Trace : loglevel.LogLevel + + + Debug : loglevel.LogLevel + + + Verbose : loglevel.LogLevel + + + Info : loglevel.LogLevel + + + Notice : loglevel.LogLevel + + + Warning : loglevel.LogLevel + + + Severe : loglevel.LogLevel + + + Error : loglevel.LogLevel + + + Alert : loglevel.LogLevel + + + Critical : loglevel.LogLevel + + + Emergency : loglevel.LogLevel + + + Null : loglevel.LogLevel + + - - + + - + style="stroke:#181818;stroke-width:0.61;stroke-dasharray:7.0,7.0;"/> - uses + points="681.7055,1447.286,678.1921,1442.4126,679.0707,1445.7496,675.7338,1446.6282,681.7055,1447.286" + style="stroke:#181818;stroke-width:0.61;"/> + uses - - + + - + - uses + points="686.2683,1122.278,681.9917,1118.0584,683.4143,1121.2022,680.2705,1122.6248,686.2683,1122.278" + style="stroke:#181818;stroke-width:0.61;"/> + uses - + style="stroke:#181818;stroke-width:0.61;stroke-dasharray:7.0,7.0;"/> - uses + points="1165.4355,1324.005,1168.0137,1318.5785,1165.5127,1320.956,1163.1353,1318.455,1165.4355,1324.005" + style="stroke:#181818;stroke-width:0.61;"/> + uses - - + style="stroke:#181818;stroke-width:0.61;stroke-dasharray:7.0,7.0;"/> + - - + + - - + + - + + + uses + + + + - uses + points="667.9561,486.8166,669.7988,481.0984,667.632,483.7839,664.9465,481.617,667.9561,486.8166" + style="stroke:#181818;stroke-width:0.61;"/> + uses + + + + + + uses + + + + + + uses - + - contains + points="1007.2869,1267.8057,1009.2855,1271.7242,1013.6713,1271.3865,1011.6727,1267.468,1007.2869,1267.8057" + style="stroke:#181818;stroke-width:0.61;"/> + contains - + - contains + points="867.0113,1267.8484,864.3761,1271.3705,866.6138,1275.1576,869.2489,1271.6355,867.0113,1267.8484" + style="stroke:#181818;stroke-width:0.61;"/> + contains - - - contains + + + contains - 0..* + 0..* - + \ No newline at end of file diff --git a/docs/architecture/diagrams/svg/create_new_logger.svg b/docs/architecture/diagrams/svg/create_new_logger.svg index 92579ec..71f9550 100644 --- a/docs/architecture/diagrams/svg/create_new_logger.svg +++ b/docs/architecture/diagrams/svg/create_new_logger.svg @@ -1 +1,116 @@ -mainmainloggerloggerformatterformatterhandlerhandler1New(name)2New(template)3NewConsoleHandler(level, formatter)4New(level, formatter, writer, errorWriter)Calling AddHandler on the instance of Logger returned by New5AddHandler(handler) \ No newline at end of file + + + + + + + + + + + + + main + + + main + + + logger + + + logger + + + formatter + + + formatter + + + handler + + + handler + + + + + + + 1 + + New(name) + + + + 2 + + New(template) + + + + 3 + + NewConsoleHandler(fromLevel, toLevel, formatter) + + + + + + 4 + + New(fromLevel, toLevel, formatter, writer) + + + + Calling AddHandler on the instance of Logger returned by New + + + + 5 + + AddHandler(handler) + + + \ No newline at end of file diff --git a/examples/customlogger/main.go b/examples/customlogger/main.go index 64bb3d7..bc82630 100644 --- a/examples/customlogger/main.go +++ b/examples/customlogger/main.go @@ -14,7 +14,7 @@ func main() { applicationLogger.Debug("This message will not be displayed, because there are no handlers registered.") applicationFormatter := formatter.New("%(isotime) [%(level)] %(message)") - consoleHandler := handler.NewConsoleHandler(loglevel.Debug, applicationFormatter) + consoleHandler := handler.NewConsoleHandler(loglevel.Debug, loglevel.Null, applicationFormatter) applicationLogger.AddHandler(consoleHandler) applicationLogger.Warning("This message will be displayed.") @@ -23,7 +23,7 @@ func main() { applicationLogger.Trace("This message will not be displayed, because Trace has lower level than Debug.") - consoleHandler.SetLevel(loglevel.Trace) + consoleHandler.SetFromLevel(loglevel.Trace) applicationLogger.Trace("This message will be displayed, because LogLevel has been changed to Trace.") } diff --git a/examples/defaultlogger/main.go b/examples/defaultlogger/main.go index b70a9e3..1964bec 100644 --- a/examples/defaultlogger/main.go +++ b/examples/defaultlogger/main.go @@ -11,7 +11,7 @@ func main() { logger.Warning("This message will be displayed.") - logger.SetLevel(loglevel.None) + logger.Configure(logger.NewConfiguration(logger.WithFromLevel(loglevel.All))) logger.Debug("This message will be displayed, because LogLevel has been changed.") } diff --git a/examples/logtofile/main.go b/examples/logtofile/main.go index ed3fc04..f749c3e 100644 --- a/examples/logtofile/main.go +++ b/examples/logtofile/main.go @@ -24,7 +24,7 @@ func main() { applicationLogger := logger.New("file-logger") applicationFormatter := formatter.New("%(isotime) [%(level)] %(message)") - fileHandler := handler.NewFileHandler(loglevel.Warning, applicationFormatter, fmt.Sprintf("%s/file.log", directory)) + fileHandler := handler.NewFileHandler(loglevel.Warning, loglevel.Null, applicationFormatter, fmt.Sprintf("%s/file.log", directory)) applicationLogger.AddHandler(fileHandler) applicationLogger.Warning("This file has only Warning level logs or higher.") diff --git a/pkg/logger/handler/handler.go b/pkg/logger/handler/handler.go index 889261f..fa51b90 100644 --- a/pkg/logger/handler/handler.go +++ b/pkg/logger/handler/handler.go @@ -17,59 +17,84 @@ var osStderr = os.Stderr // Interface represents interface that shall be satisfied by Handler. type Interface interface { - Level() loglevel.LogLevel - SetLevel(level loglevel.LogLevel) + Writer() io.Writer + FromLevel() loglevel.LogLevel + SetFromLevel(fromLevel loglevel.LogLevel) + ToLevel() loglevel.LogLevel + SetToLevel(toLevel loglevel.LogLevel) Formatter() formatter.Interface Write(logName string, level loglevel.LogLevel, message string, parameters ...any) } // Handler struct contains information where it shall write log message, how to -// format them and their log level. +// format them and their log fromLevel. type Handler struct { - level loglevel.LogLevel + fromLevel loglevel.LogLevel + toLevel loglevel.LogLevel formatter formatter.Interface writer io.Writer - errorWriter io.Writer consoleSupportsANSIColors func() bool } // New create a new instance of the Handler. -func New(level loglevel.LogLevel, newFormatter formatter.Interface, writer io.Writer, errorWriter io.Writer) *Handler { +func New(fromLevel loglevel.LogLevel, toLevel loglevel.LogLevel, newFormatter formatter.Interface, writer io.Writer) *Handler { return &Handler{ - level: level, + fromLevel: fromLevel, + toLevel: toLevel, formatter: newFormatter, writer: writer, - errorWriter: errorWriter, consoleSupportsANSIColors: consoleSupportsANSIColors, } } // NewConsoleHandler create a new instance of the Handler that writes log -// messages to the os.Stdout and os.Stderr respectively. -func NewConsoleHandler(level loglevel.LogLevel, newFormatter formatter.Interface) *Handler { - return New(level, newFormatter, osStdout, osStderr) +// messages to the os.Stdout. +func NewConsoleHandler(fromLevel loglevel.LogLevel, toLevel loglevel.LogLevel, newFormatter formatter.Interface) *Handler { + return New(fromLevel, toLevel, newFormatter, osStdout) +} + +// NewConsoleErrorHandler create a new instance of the Handler that writes log +// messages to the os.Stderr. +func NewConsoleErrorHandler(fromLevel loglevel.LogLevel, toLevel loglevel.LogLevel, newFormatter formatter.Interface) *Handler { + return New(fromLevel, toLevel, newFormatter, osStderr) } // NewFileHandler creates a new instance of the Handler that writes log message // to the log file. -func NewFileHandler(level loglevel.LogLevel, newFormatter formatter.Interface, file string) *Handler { +func NewFileHandler(fromLevel loglevel.LogLevel, toLevel loglevel.LogLevel, newFormatter formatter.Interface, file string) *Handler { writer, err := osOpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Println(err) + return nil } - return New(level, newFormatter, writer, writer) + return New(fromLevel, toLevel, newFormatter, writer) +} + +// Writer returns writer of the Handler. +func (handler *Handler) Writer() io.Writer { + return handler.writer } -// Level returns log level of the Handler. -func (handler *Handler) Level() loglevel.LogLevel { - return handler.level +// FromLevel returns log fromLevel of the Handler. +func (handler *Handler) FromLevel() loglevel.LogLevel { + return handler.fromLevel } -// SetLevel sets a new log level for the Handler. -func (handler *Handler) SetLevel(level loglevel.LogLevel) { - handler.level = level +// SetFromLevel sets a new log fromLevel for the Handler. +func (handler *Handler) SetFromLevel(fromLevel loglevel.LogLevel) { + handler.fromLevel = fromLevel +} + +// ToLevel returns log toLevel of the Handler. +func (handler *Handler) ToLevel() loglevel.LogLevel { + return handler.toLevel +} + +// SetToLevel sets a new log toLevel for the Handler. +func (handler *Handler) SetToLevel(toLevel loglevel.LogLevel) { + handler.toLevel = toLevel } // Formatter returns formatter.Interface used by the Handler. @@ -79,23 +104,21 @@ func (handler *Handler) Formatter() formatter.Interface { // Write writes log message to the defined by the Handler writer. func (handler *Handler) Write(logName string, level loglevel.LogLevel, message string, parameters ...any) { - formattedMessage := fmt.Sprintf(message, parameters...) - - writer := handler.writer - - if level >= loglevel.Error { - writer = handler.errorWriter + if level.DigitRepresentation() < handler.fromLevel.DigitRepresentation() || level.DigitRepresentation() > handler.toLevel.DigitRepresentation() { + return } + formattedMessage := fmt.Sprintf(message, parameters...) + var colored = false - if handler.consoleSupportsANSIColors() && (writer == osStdout || writer == osStderr) { + if handler.consoleSupportsANSIColors() && (handler.writer == osStdout || handler.writer == osStderr) { colored = true } log := handler.formatter.Format(formattedMessage, logName, level, colored) - if _, err := writer.Write([]byte(log)); err != nil { + if _, err := handler.writer.Write([]byte(log)); err != nil { fmt.Println(err) } } diff --git a/pkg/logger/handler/handler_test.go b/pkg/logger/handler/handler_test.go index 0af0022..ea7662c 100644 --- a/pkg/logger/handler/handler_test.go +++ b/pkg/logger/handler/handler_test.go @@ -19,17 +19,14 @@ var testFile = "/tmp/test_file.log" func TestNew(t *testing.T) { newFormatter := formatter.New(template) - newHandler := New(loglevel.Debug, newFormatter, os.Stdout, os.Stderr) + newHandler := New(loglevel.Debug, loglevel.Null, newFormatter, os.Stdout) - testutils.AssertEquals(t, loglevel.Debug, newHandler.level) + testutils.AssertEquals(t, loglevel.Debug, newHandler.fromLevel) + testutils.AssertEquals(t, loglevel.Null, newHandler.toLevel) if newHandler.writer != os.Stdout { t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stdout, newHandler.writer) } - - if newHandler.errorWriter != os.Stderr { - t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stderr, newHandler.errorWriter) - } } // BenchmarkNew performs benchmarking of the New(). @@ -37,7 +34,7 @@ func BenchmarkNew(b *testing.B) { newFormatter := formatter.New(template) for index := 0; index < b.N; index++ { - New(loglevel.Debug, newFormatter, os.Stdout, os.Stderr) + New(loglevel.Debug, loglevel.Null, newFormatter, os.Stdout) } } @@ -46,17 +43,13 @@ func BenchmarkNew(b *testing.B) { func TestNewConsoleHandler(t *testing.T) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) - testutils.AssertEquals(t, loglevel.Debug, newHandler.level) + testutils.AssertEquals(t, loglevel.Debug, newHandler.fromLevel) if newHandler.writer != os.Stdout { t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stdout, newHandler.writer) } - - if newHandler.errorWriter != os.Stderr { - t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stderr, newHandler.errorWriter) - } } // BenchmarkNewConsoleHandler performs benchmarking of the NewConsoleHandler(). @@ -64,7 +57,7 @@ func BenchmarkNewConsoleHandler(b *testing.B) { newFormatter := formatter.New(template) for index := 0; index < b.N; index++ { - NewConsoleHandler(loglevel.Debug, newFormatter) + NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) } } @@ -80,17 +73,27 @@ func TestNewFileHandler(t *testing.T) { osOpenFile = mockOpenFile - newHandler := NewFileHandler(loglevel.Debug, newFormatter, testFile) + newHandler := NewFileHandler(loglevel.Debug, loglevel.Null, newFormatter, testFile) - testutils.AssertEquals(t, loglevel.Debug, newHandler.level) + testutils.AssertEquals(t, loglevel.Debug, newHandler.fromLevel) if newHandler.writer != os.Stdout { t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stdout, newHandler.writer) } +} - if newHandler.errorWriter != os.Stdout { - t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stdout, newHandler.errorWriter) +// TestNewFileHandlerError test that NewFileHandler returns error if file cannot +// be opened. +func TestNewFileHandlerError(t *testing.T) { + newFormatter := formatter.New(template) + + osOpenFile = func(_ string, _ int, _ os.FileMode) (*os.File, error) { + return nil, fmt.Errorf("error") } + + newHandler := NewFileHandler(loglevel.Debug, loglevel.Null, newFormatter, testFile) + + testutils.AssertEquals(t, nil, newHandler) } // BenchmarkNewFileHandler performs benchmarking of the NewFileHandler(). @@ -100,54 +103,125 @@ func BenchmarkNewFileHandler(b *testing.B) { osOpenFile = mockOpenFile for index := 0; index < b.N; index++ { - NewFileHandler(loglevel.Debug, newFormatter, testFile) + NewFileHandler(loglevel.Debug, loglevel.Null, newFormatter, testFile) } } -// TestHandler_Level test that Handler.Level() returns log level for the Handler. -func TestHandler_Level(t *testing.T) { +// TestHandler_Writer test that Handler.Writer() returns writer for the Handler. +func TestHandler_Writer(t *testing.T) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) - testutils.AssertEquals(t, newHandler.level, newHandler.Level()) + if newHandler.Writer() != os.Stdout { + t.Fatalf("writer is not the same. expected: %v, actual: %v", os.Stdout, newHandler.Writer()) + } } -// BenchmarkHandler_Level performs benchmarking of the Handler.Level(). -func BenchmarkHandler_Level(b *testing.B) { +// BenchmarkHandler_Writer performs benchmarking of the Handler.Writer(). +func BenchmarkHandler_Writer(b *testing.B) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) for index := 0; index < b.N; index++ { - newHandler.Level() + newHandler.Writer() } } -// TestHandler_Level test that Handler.SetLevel() set a new log level for the -// Handler. -func TestHandler_SetLevel(t *testing.T) { +// TestHandler_FromLevel test that Handler.FromLevel() returns log fromLevel for +// the Handler. +func TestHandler_FromLevel(t *testing.T) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + testutils.AssertEquals(t, newHandler.fromLevel, newHandler.FromLevel()) +} + +// BenchmarkHandler_FromLevel performs benchmarking of the Handler.FromLevel(). +func BenchmarkHandler_FromLevel(b *testing.B) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + for index := 0; index < b.N; index++ { + newHandler.FromLevel() + } +} + +// TestHandler_SetFromLevel test that Handler.SetFromLevel() set a new log toLevel for +// the Handler. +func TestHandler_SetFromLevel(t *testing.T) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) newLevel := loglevel.Info - newHandler.SetLevel(newLevel) + newHandler.SetFromLevel(newLevel) - testutils.AssertEquals(t, newLevel, newHandler.level) + testutils.AssertEquals(t, newLevel, newHandler.fromLevel) } -// BenchmarkHandler_Level performs benchmarking of the Handler.SetLevel(). -func BenchmarkHandler_SetLevel(b *testing.B) { +// BenchmarkHandler_SetFromLevel performs benchmarking of the +// Handler.SetFromLevel(). +func BenchmarkHandler_SetFromLevel(b *testing.B) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) newLevel := loglevel.Info for index := 0; index < b.N; index++ { - newHandler.SetLevel(newLevel) + newHandler.SetFromLevel(newLevel) + } +} + +// TestHandler_ToLevel test that Handler.ToLevel() returns log toLevel for the Handler. +func TestHandler_ToLevel(t *testing.T) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + testutils.AssertEquals(t, newHandler.toLevel, newHandler.ToLevel()) +} + +// BenchmarkHandler_ToLevel performs benchmarking of the Handler.ToLevel(). +func BenchmarkHandler_ToLevel(b *testing.B) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + for index := 0; index < b.N; index++ { + newHandler.ToLevel() + } +} + +// TestHandler_SetToLevel test that Handler.SetToLevel() set a new log toLevel +// for the Handler. +func TestHandler_SetToLevel(t *testing.T) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + newLevel := loglevel.Info + + newHandler.SetToLevel(newLevel) + + testutils.AssertEquals(t, newLevel, newHandler.toLevel) +} + +// BenchmarkHandler_SetToLevel performs benchmarking of the Handler.SetToLevel(). +func BenchmarkHandler_SetToLevel(b *testing.B) { + newFormatter := formatter.New(template) + + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) + + newLevel := loglevel.Info + + for index := 0; index < b.N; index++ { + newHandler.SetToLevel(newLevel) } } @@ -156,7 +230,7 @@ func BenchmarkHandler_SetLevel(b *testing.B) { func TestHandler_Formatter(t *testing.T) { var newFormatter formatter.Interface = formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) testutils.AssertEquals(t, newFormatter, newHandler.Formatter()) } @@ -165,58 +239,76 @@ func TestHandler_Formatter(t *testing.T) { func BenchmarkHandler_Formatter(b *testing.B) { var newFormatter formatter.Interface = formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) for index := 0; index < b.N; index++ { newHandler.Formatter() } } +// setupHandler is a helper function to setup a new handler for testing purposes. +func setupHandler(fromLevel, toLevel loglevel.LogLevel, supportsANSI bool, formatterTemplate string) *Handler { + newFormatter := formatter.New(formatterTemplate) + newHandler := NewConsoleHandler(fromLevel, toLevel, newFormatter) + newHandler.consoleSupportsANSIColors = func() bool { + return supportsANSI + } + return newHandler +} + // TestHandler_Write tests that Handler.Write() writes formatted log to the // correct writer. func TestHandler_Write(t *testing.T) { logName := "test" logLevel := loglevel.Debug - logLevelError := loglevel.Error message := "Test message." - formattedStdoutMessage := fmt.Sprintf("%s%s:%s:%s%s\n", "\033[36m", logLevel.String(), logName, message, "\033[0m") - formattedStderrMessage := fmt.Sprintf("%s%s:%s:%s%s\n", "\033[31m", logLevelError.String(), logName, message, "\033[0m") + originalStdout := osStdout - newFormatter := formatter.New(template) + readerStdout, writerStdout, _ := os.Pipe() + + osStdout = writerStdout + + handler := setupHandler(loglevel.Debug, loglevel.Null, true, template) var bufferStdout bytes.Buffer - var bufferStderr bytes.Buffer + + handler.Write(logName, logLevel, message) + + _ = writerStdout.Close() + + _, _ = io.Copy(&bufferStdout, readerStdout) + + osStdout = originalStdout + + testutils.AssertEquals(t, "\033[36mdebug:test:Test message.\033[0m\n", bufferStdout.String()) +} + +// TestHandler_WriteError tests that Handler.Write() returns error if writer fails. +func TestHandler_WriteError(t *testing.T) { + logName := "test" + logLevel := loglevel.Debug + message := "Test message." originalStdout := osStdout - originalStderr := osStderr readerStdout, writerStdout, _ := os.Pipe() - readerStderr, writerStderr, _ := os.Pipe() osStdout = writerStdout - osStderr = writerStderr - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + handler := setupHandler(loglevel.Warning, loglevel.Null, false, template) - newHandler.consoleSupportsANSIColors = func() bool { - return true - } + var bufferStdout bytes.Buffer - newHandler.Write(logName, logLevel, message) - newHandler.Write(logName, logLevelError, message) + handler.Write(logName, logLevel, message) _ = writerStdout.Close() - _ = writerStderr.Close() _, _ = io.Copy(&bufferStdout, readerStdout) - _, _ = io.Copy(&bufferStderr, readerStderr) osStdout = originalStdout - osStderr = originalStderr - testutils.AssertEquals(t, formattedStdoutMessage, bufferStdout.String()) - testutils.AssertEquals(t, formattedStderrMessage, bufferStderr.String()) + testutils.AssertEquals(t, "", bufferStdout.String()) } // BenchmarkHandler_Write performs benchmarking of the Handler.Write(). @@ -227,7 +319,7 @@ func BenchmarkHandler_Write(b *testing.B) { newFormatter := formatter.New(template) - newHandler := NewConsoleHandler(loglevel.Debug, newFormatter) + newHandler := NewConsoleHandler(loglevel.Debug, loglevel.Null, newFormatter) newHandler.writer = io.Discard diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index c35771e..14d5883 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -7,7 +7,14 @@ import ( "github.com/dl1998/go-logging/pkg/logger/loglevel" ) -var rootLogger = GetDefaultLogger() +var rootLogger *Logger +var fromLevel loglevel.LogLevel +var toLevel loglevel.LogLevel +var template string + +func init() { + Configure(NewConfiguration()) +} // baseLoggerInterface defines low level logging interface. type baseLoggerInterface interface { @@ -15,7 +22,8 @@ type baseLoggerInterface interface { Name() string SetName(name string) Handlers() []handler.Interface - AddHandler(handler handler.Interface) + AddHandler(handlerInterface handler.Interface) + RemoveHandler(handlerInterface handler.Interface) } // baseLogger struct contains basic fields for the logger. @@ -27,9 +35,7 @@ type baseLogger struct { // Log logs interpolated message with the provided loglevel.LogLevel. func (logger *baseLogger) Log(level loglevel.LogLevel, message string, parameters ...any) { for _, registeredHandler := range logger.handlers { - if level >= registeredHandler.Level() { - registeredHandler.Write(logger.name, level, message, parameters...) - } + registeredHandler.Write(logger.name, level, message, parameters...) } } @@ -50,15 +56,27 @@ func (logger *baseLogger) Handlers() []handler.Interface { } // AddHandler register a new handler.Interface for the baseLogger. -func (logger *baseLogger) AddHandler(handler handler.Interface) { - logger.handlers = append(logger.handlers, handler) +func (logger *baseLogger) AddHandler(handlerInterface handler.Interface) { + logger.handlers = append(logger.handlers, handlerInterface) +} + +// RemoveHandler removes a handler.Interface from the baseLogger handlers. +func (logger *baseLogger) RemoveHandler(handlerInterface handler.Interface) { + newSlice := make([]handler.Interface, 0) + for _, element := range logger.handlers { + if element != handlerInterface { + newSlice = append(newSlice, element) + } + } + logger.handlers = newSlice } // Interface represents interface that shall be satisfied by Logger. type Interface interface { Name() string Handlers() []handler.Interface - AddHandler(handler handler.Interface) + AddHandler(handlerInterface handler.Interface) + RemoveHandler(handlerInterface handler.Interface) Trace(message string, parameters ...any) Debug(message string, parameters ...any) Verbose(message string, parameters ...any) @@ -87,19 +105,6 @@ func New(name string) *Logger { } } -// GetDefaultLogger creates a new default logger. -func GetDefaultLogger() *Logger { - newLogger := New("root") - - newFormatter := formatter.New("%(level):%(name):%(message)") - - newHandler := handler.NewConsoleHandler(loglevel.Warning, newFormatter) - - newLogger.baseLogger.AddHandler(newHandler) - - return newLogger -} - // Name returns logger name for the Logger. func (logger *Logger) Name() string { return logger.baseLogger.Name() @@ -112,8 +117,13 @@ func (logger *Logger) Handlers() []handler.Interface { } // AddHandler registers a new handler.Interface for the Logger. -func (logger *Logger) AddHandler(handler handler.Interface) { - logger.baseLogger.AddHandler(handler) +func (logger *Logger) AddHandler(handlerInterface handler.Interface) { + logger.baseLogger.AddHandler(handlerInterface) +} + +// RemoveHandler removes a handler.Interface from the Logger handlers. +func (logger *Logger) RemoveHandler(handlerInterface handler.Interface) { + logger.baseLogger.RemoveHandler(handlerInterface) } // Trace logs a new message using Logger with loglevel.Trace level. @@ -171,14 +181,134 @@ func (logger *Logger) Emergency(message string, parameters ...any) { logger.baseLogger.Log(loglevel.Emergency, message, parameters...) } -// SetLevel sets a new loglevel.LogLevel for the default logger. -func SetLevel(level loglevel.LogLevel) { - handlerInterface := rootLogger.baseLogger.Handlers()[0] - if handlerInterface != nil { - handlerInterface.SetLevel(level) +// Configuration struct contains configuration for the logger. +type Configuration struct { + fromLevel loglevel.LogLevel + toLevel loglevel.LogLevel + template string + file string + name string +} + +// Option represents option for the Configuration. +type Option func(*Configuration) + +// WithFromLevel sets fromLevel for the Configuration. +func WithFromLevel(fromLevel loglevel.LogLevel) Option { + return func(configuration *Configuration) { + configuration.fromLevel = fromLevel + } +} + +// WithToLevel sets toLevel for the Configuration. +func WithToLevel(toLevel loglevel.LogLevel) Option { + return func(configuration *Configuration) { + configuration.toLevel = toLevel + } +} + +// WithTemplate sets template for the Configuration. +func WithTemplate(template string) Option { + return func(configuration *Configuration) { + configuration.template = template + } +} + +// WithFile sets file for the Configuration. +func WithFile(file string) Option { + return func(configuration *Configuration) { + configuration.file = file + } +} + +// WithName sets name for the Configuration. +func WithName(name string) Option { + return func(configuration *Configuration) { + configuration.name = name } } +// NewConfiguration creates a new instance of the Configuration. +func NewConfiguration(options ...Option) *Configuration { + newConfiguration := &Configuration{ + fromLevel: loglevel.Warning, + toLevel: loglevel.Null, + template: "%(level):%(name):%(message)", + file: "", + name: "root", + } + + for _, option := range options { + option(newConfiguration) + } + + return newConfiguration +} + +// Configure configures the logger with the provided configuration. +func Configure(configuration *Configuration) { + if configuration.fromLevel.DigitRepresentation() > configuration.toLevel.DigitRepresentation() { + panic("fromLevel cannot be higher than toLevel") + } + + fromLevel = configuration.fromLevel + toLevel = configuration.toLevel + template = configuration.template + + newLogger := New(configuration.name) + + defaultFormatter := formatter.New(configuration.template) + + var createStdoutHandler = configuration.fromLevel.DigitRepresentation() <= loglevel.Severe.DigitRepresentation() + var createStderrHandler = configuration.toLevel.DigitRepresentation() >= loglevel.Error.DigitRepresentation() + var createFileHandler = configuration.file != "" + + if createStdoutHandler { + stdoutToLevel := toLevel + if stdoutToLevel > loglevel.Severe { + stdoutToLevel = loglevel.Severe + } + newHandler := handler.NewConsoleHandler(configuration.fromLevel, stdoutToLevel, defaultFormatter) + newLogger.baseLogger.AddHandler(newHandler) + } + + if createStderrHandler { + stderrFromLevel := fromLevel + if stderrFromLevel < loglevel.Error { + stderrFromLevel = loglevel.Error + } + newHandler := handler.NewConsoleErrorHandler(stderrFromLevel, configuration.toLevel, defaultFormatter) + newLogger.baseLogger.AddHandler(newHandler) + } + + if createFileHandler { + newHandler := handler.NewFileHandler(configuration.fromLevel, configuration.toLevel, defaultFormatter, configuration.file) + newLogger.baseLogger.AddHandler(newHandler) + } + + rootLogger = newLogger +} + +// Name returns name of the rootLogger. +func Name() string { + return rootLogger.Name() +} + +// Template returns template of the rootLogger. +func Template() string { + return template +} + +// FromLevel returns fromLevel of the rootLogger. +func FromLevel() loglevel.LogLevel { + return fromLevel +} + +// ToLevel returns toLevel of the rootLogger. +func ToLevel() loglevel.LogLevel { + return toLevel +} + // Trace logs a new message using default logger with loglevel.Trace level. func Trace(message string, parameters ...any) { rootLogger.Trace(message, parameters...) diff --git a/pkg/logger/logger_test.go b/pkg/logger/logger_test.go index 07f3767..4cc9a35 100644 --- a/pkg/logger/logger_test.go +++ b/pkg/logger/logger_test.go @@ -6,6 +6,7 @@ import ( "github.com/dl1998/go-logging/pkg/logger/formatter" "github.com/dl1998/go-logging/pkg/logger/handler" "github.com/dl1998/go-logging/pkg/logger/loglevel" + "io" "testing" ) @@ -68,24 +69,60 @@ func (mock *MockLogger) Handlers() []handler.Interface { } // AddHandler mocks AddHandler from baseLogger. -func (mock *MockLogger) AddHandler(handler handler.Interface) { +func (mock *MockLogger) AddHandler(handlerInterface handler.Interface) { mock.CalledName = "AddHandler" mock.Called = true - mock.Parameters = append(make([]any, 0), handler) + mock.Parameters = append(make([]any, 0), handlerInterface) + mock.Return = nil +} + +// RemoveHandler mocks RemoveHandler from baseLogger. +func (mock *MockLogger) RemoveHandler(handlerInterface handler.Interface) { + mock.CalledName = "RemoveHandler" + mock.Called = true + mock.Parameters = append(make([]any, 0), handlerInterface) mock.Return = nil } // MockHandler is used to mock Handler. type MockHandler struct { + writer io.Writer CalledName string Called bool Parameters []any Return any } -// Level mocks Level from Handler. -func (mock *MockHandler) Level() loglevel.LogLevel { - mock.CalledName = "Level" +// Writer mocks Writer from Handler. +func (mock *MockHandler) Writer() io.Writer { + mock.CalledName = "Writer" + mock.Called = true + mock.Parameters = make([]any, 0) + mock.Return = mock.writer + return mock.writer +} + +// FromLevel mocks FromLevel from Handler. +func (mock *MockHandler) FromLevel() loglevel.LogLevel { + mock.CalledName = "FromLevel" + mock.Called = true + mock.Parameters = make([]any, 0) + returnValue := loglevel.Debug + mock.Return = returnValue + return returnValue +} + +// SetFromLevel mocks SetFromLevel from Handler. +func (mock *MockHandler) SetFromLevel(level loglevel.LogLevel) { + mock.CalledName = "SetFromLevel" + mock.Called = true + mock.Parameters = append(make([]any, 0), level) + mock.Return = nil +} + +// ToLevel mocks ToLevel from Handler. +func (mock *MockHandler) ToLevel() loglevel.LogLevel { + mock.CalledName = "ToLevel" mock.Called = true mock.Parameters = make([]any, 0) returnValue := loglevel.Debug @@ -93,9 +130,9 @@ func (mock *MockHandler) Level() loglevel.LogLevel { return returnValue } -// SetLevel mocks SetLevel from Handler. -func (mock *MockHandler) SetLevel(level loglevel.LogLevel) { - mock.CalledName = "SetLevel" +// SetToLevel mocks SetToLevel from Handler. +func (mock *MockHandler) SetToLevel(level loglevel.LogLevel) { + mock.CalledName = "SetToLevel" mock.Called = true mock.Parameters = append(make([]any, 0), level) mock.Return = nil @@ -276,46 +313,50 @@ func BenchmarkBaseLogger_AddHandler(b *testing.B) { } } -// TestNew tests that New creates a new logger. -func TestNew(t *testing.T) { - newLogger := New(loggerName) +// TestBaseLogger_RemoveHandler tests that baseLogger.RemoveHandler removes a +// Handler from the list of handlers. +func TestBaseLogger_RemoveHandler(t *testing.T) { + newHandler := &MockHandler{} - testutils.AssertEquals(t, loggerName, newLogger.Name()) + newBaseLogger := &baseLogger{ + name: loggerName, + handlers: []handler.Interface{newHandler}, + } - handlersSize := len(newLogger.Handlers()) + newBaseLogger.RemoveHandler(newHandler) - testutils.AssertEquals(t, 0, handlersSize) + testutils.AssertEquals(t, make([]handler.Interface, 0), newBaseLogger.handlers) } -// BenchmarkNew perform benchmarking of the New(). -func BenchmarkNew(b *testing.B) { +// BenchmarkBaseLogger_RemoveHandler perform benchmarking of the baseLogger.RemoveHandler(). +func BenchmarkBaseLogger_RemoveHandler(b *testing.B) { + newHandler := &MockHandler{} + + newBaseLogger := &baseLogger{ + name: loggerName, + handlers: []handler.Interface{newHandler}, + } + for index := 0; index < b.N; index++ { - New(loggerName) + newBaseLogger.RemoveHandler(newHandler) } } -// TestGetDefaultLogger tests that GetDefaultLogger returns default logger -// instance. -func TestGetDefaultLogger(t *testing.T) { - rootLoggerName := "root" - - rootLogger := GetDefaultLogger() - - testutils.AssertEquals(t, rootLoggerName, rootLogger.Name()) +// TestNew tests that New creates a new logger. +func TestNew(t *testing.T) { + newLogger := New(loggerName) - handlersSize := len(rootLogger.Handlers()) + testutils.AssertEquals(t, loggerName, newLogger.Name()) - handlerInterface := rootLogger.Handlers()[0] + handlersSize := len(newLogger.Handlers()) - testutils.AssertEquals(t, 1, handlersSize) - testutils.AssertEquals(t, loglevel.Warning, handlerInterface.Level()) - testutils.AssertEquals(t, "%(level):%(name):%(message)", handlerInterface.Formatter().Template()) + testutils.AssertEquals(t, 0, handlersSize) } -// BenchmarkGetDefaultLogger perform benchmarking of the GetDefaultLogger(). -func BenchmarkGetDefaultLogger(b *testing.B) { +// BenchmarkNew perform benchmarking of the New(). +func BenchmarkNew(b *testing.B) { for index := 0; index < b.N; index++ { - GetDefaultLogger() + New(loggerName) } } @@ -389,6 +430,36 @@ func BenchmarkLogger_AddHandler(b *testing.B) { } } +// TestLogger_RemoveHandler tests that Logger.RemoveHandler removes a Handler from the list of handlers. +func TestLogger_RemoveHandler(t *testing.T) { + mockHandler1 := &MockHandler{} + mockHandler2 := &MockHandler{} + + newLogger := &Logger{baseLogger: &baseLogger{ + name: loggerName, + handlers: []handler.Interface{mockHandler1, mockHandler2}, + }} + + newLogger.RemoveHandler(mockHandler1) + newLogger.RemoveHandler(mockHandler2) + + testutils.AssertEquals(t, make([]handler.Interface, 0), newLogger.baseLogger.Handlers()) +} + +// BenchmarkLogger_RemoveHandler perform benchmarking of the Logger.RemoveHandler(). +func BenchmarkLogger_RemoveHandler(b *testing.B) { + mockHandler := &MockHandler{} + + newLogger := &Logger{baseLogger: &baseLogger{ + name: loggerName, + handlers: []handler.Interface{mockHandler}, + }} + + for index := 0; index < b.N; index++ { + newLogger.RemoveHandler(mockHandler) + } +} + // TestLogger_Trace tests that Logger.Trace logs message with parameters on trace // level. func TestLogger_Trace(t *testing.T) { @@ -664,33 +735,292 @@ func BenchmarkLogger_Emergency(b *testing.B) { } } -// TestSetLevel tests that SetLevel set a new log level for the default logger. -func TestSetLevel(t *testing.T) { - mockHandler := &MockHandler{} - mockLogger := &MockLogger{} - mockLogger.handlers = []handler.Interface{mockHandler} +// TestWithFromLevel tests that WithFromLevel sets the from level in the Configuration. +func TestWithFromLevel(t *testing.T) { + configuration := NewConfiguration() - rootLogger = &Logger{baseLogger: mockLogger} + option := WithFromLevel(loglevel.Trace) + + option(configuration) - newLevel := loglevel.Error + testutils.AssertEquals(t, configuration.fromLevel, loglevel.Trace) +} - SetLevel(newLevel) +// BenchmarkWithFromLevel perform benchmarking of the WithFromLevel(). +func BenchmarkWithFromLevel(b *testing.B) { + configuration := NewConfiguration() - testutils.AssertEquals(t, newLevel, mockHandler.Parameters[0].(loglevel.LogLevel)) + option := WithFromLevel(loglevel.Trace) + + for index := 0; index < b.N; index++ { + option(configuration) + } } -// BenchmarkSetLevel perform benchmarking of the SetLevel(). -func BenchmarkSetLevel(b *testing.B) { - mockHandler := &MockHandler{} - mockLogger := &MockLogger{} - mockLogger.handlers = []handler.Interface{mockHandler} +// TestWithToLevel tests that WithToLevel sets the to level in the Configuration. +func TestWithToLevel(t *testing.T) { + configuration := NewConfiguration() - rootLogger = &Logger{baseLogger: mockLogger} + option := WithToLevel(loglevel.Trace) + + option(configuration) + + testutils.AssertEquals(t, configuration.toLevel, loglevel.Trace) +} + +// BenchmarkWithToLevel perform benchmarking of the WithToLevel(). +func BenchmarkWithToLevel(b *testing.B) { + configuration := NewConfiguration() + + option := WithToLevel(loglevel.Trace) + + for index := 0; index < b.N; index++ { + option(configuration) + } +} + +// TestWithTemplate tests that WithTemplate sets the template in the +// Configuration. +func TestWithTemplate(t *testing.T) { + configuration := NewConfiguration() + + template := "%(message):%(name):%(level)" + + option := WithTemplate(template) + + option(configuration) + + testutils.AssertEquals(t, configuration.template, template) +} + +// BenchmarkWithTemplate perform benchmarking of the WithTemplate(). +func BenchmarkWithTemplate(b *testing.B) { + configuration := NewConfiguration() + + template := "%(message):%(name):%(level)" + + option := WithTemplate(template) + + for index := 0; index < b.N; index++ { + option(configuration) + } +} + +// TestWithFile tests that WithFile sets the file in the Configuration. +func TestWithFile(t *testing.T) { + configuration := NewConfiguration() + + file := "file.log" + + option := WithFile(file) + + option(configuration) + + testutils.AssertEquals(t, configuration.file, file) +} + +// BenchmarkWithFile perform benchmarking of the WithFile(). +func BenchmarkWithFile(b *testing.B) { + configuration := NewConfiguration() + + file := "file.log" + + option := WithFile(file) + + for index := 0; index < b.N; index++ { + option(configuration) + } +} + +// TestWithName tests that WithName sets the name in the Configuration. +func TestWithName(t *testing.T) { + configuration := NewConfiguration() + + name := "test" + + option := WithName(name) + + option(configuration) + + testutils.AssertEquals(t, configuration.name, name) +} + +// BenchmarkWithName perform benchmarking of the WithName(). +func BenchmarkWithName(b *testing.B) { + configuration := NewConfiguration() + + name := "test" + + option := WithName(name) + + for index := 0; index < b.N; index++ { + option(configuration) + } +} + +// TestNewConfiguration tests that NewConfiguration creates a new Configuration. +func TestNewConfiguration(t *testing.T) { + tests := map[string]struct { + options []Option + expectedFromLevel loglevel.LogLevel + expectedToLevel loglevel.LogLevel + expectedTemplate string + expectedFile string + expectedName string + }{ + "Empty": { + options: []Option{}, + expectedFromLevel: loglevel.Warning, + expectedToLevel: loglevel.Null, + expectedTemplate: "%(level):%(name):%(message)", + expectedFile: "", + expectedName: "root", + }, + "Non Standard": { + options: []Option{ + WithFromLevel(loglevel.All), + WithToLevel(loglevel.Emergency), + WithTemplate("%(message):%(name):%(level)"), + WithFile("file.log"), + WithName("test"), + }, + expectedFromLevel: loglevel.All, + expectedToLevel: loglevel.Emergency, + expectedTemplate: "%(message):%(name):%(level)", + expectedFile: "file.log", + expectedName: "test", + }, + } + for name, configuration := range tests { + t.Run(name, func(t *testing.T) { + newConfiguration := NewConfiguration(configuration.options...) + + testutils.AssertEquals(t, configuration.expectedFromLevel, newConfiguration.fromLevel) + testutils.AssertEquals(t, configuration.expectedToLevel, newConfiguration.toLevel) + testutils.AssertEquals(t, configuration.expectedTemplate, newConfiguration.template) + testutils.AssertEquals(t, configuration.expectedFile, newConfiguration.file) + testutils.AssertEquals(t, configuration.expectedName, newConfiguration.name) + }) + } +} + +// BenchmarkNewConfiguration perform benchmarking of the NewConfiguration(). +func BenchmarkNewConfiguration(b *testing.B) { + for index := 0; index < b.N; index++ { + NewConfiguration() + } +} + +// TestConfigure tests that Configure sets the configuration for the default +// logger. +func TestConfigure(t *testing.T) { + configuration := NewConfiguration( + WithFromLevel(loglevel.All), + WithToLevel(loglevel.Emergency), + WithTemplate("%(message):%(name):%(level)"), + WithFile(""), + WithName("test"), + ) + + Configure(configuration) + + testutils.AssertEquals(t, "test", rootLogger.baseLogger.Name()) + testutils.AssertEquals(t, 2, len(rootLogger.baseLogger.Handlers())) +} + +// TestConfigure_IncorrectLevels tests that Configure returns an error when +// 'from' level is greater than 'to' level. +func TestConfigure_IncorrectLevels(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("The code did not panic when 'from' level was greater than 'to' level") + } + }() + + configuration := NewConfiguration( + WithFromLevel(loglevel.Warning), + WithToLevel(loglevel.Debug), + ) + + Configure(configuration) +} + +// BenchmarkConfigure perform benchmarking of the Configure(). +func BenchmarkConfigure(b *testing.B) { + configuration := NewConfiguration( + WithFromLevel(loglevel.All), + WithToLevel(loglevel.Emergency), + WithTemplate("%(message):%(name):%(level)"), + WithFile(""), + WithName("test"), + ) + + for index := 0; index < b.N; index++ { + Configure(configuration) + } +} + +// TestName tests that Name returns name of the default logger. +func TestName(t *testing.T) { + Configure(NewConfiguration()) + + testutils.AssertEquals(t, "root", Name()) +} + +// BenchmarkName perform benchmarking of the Name(). +func BenchmarkName(b *testing.B) { + Configure(NewConfiguration()) + + for index := 0; index < b.N; index++ { + Name() + } +} + +// TestTemplate tests that Template returns template of the default logger. +func TestTemplate(t *testing.T) { + Configure(NewConfiguration()) + + testutils.AssertEquals(t, "%(level):%(name):%(message)", Template()) +} + +// BenchmarkTemplate perform benchmarking of the Template(). +func BenchmarkTemplate(b *testing.B) { + Configure(NewConfiguration()) + + for index := 0; index < b.N; index++ { + Template() + } +} + +// TestFromLevel tests that FromLevel returns from level of the default logger. +func TestFromLevel(t *testing.T) { + Configure(NewConfiguration()) + + testutils.AssertEquals(t, loglevel.Warning, FromLevel()) +} + +// BenchmarkFromLevel perform benchmarking of the FromLevel(). +func BenchmarkFromLevel(b *testing.B) { + Configure(NewConfiguration()) + + for index := 0; index < b.N; index++ { + FromLevel() + } +} + +// TestToLevel tests that ToLevel returns to level of the default logger. +func TestToLevel(t *testing.T) { + Configure(NewConfiguration()) + + testutils.AssertEquals(t, loglevel.Null, ToLevel()) +} - newLevel := loglevel.Error +// BenchmarkToLevel perform benchmarking of the ToLevel(). +func BenchmarkToLevel(b *testing.B) { + Configure(NewConfiguration()) for index := 0; index < b.N; index++ { - SetLevel(newLevel) + ToLevel() } } diff --git a/pkg/logger/loglevel/loglevel.go b/pkg/logger/loglevel/loglevel.go index 8429f27..cffa8d7 100644 --- a/pkg/logger/loglevel/loglevel.go +++ b/pkg/logger/loglevel/loglevel.go @@ -3,8 +3,10 @@ package loglevel type LogLevel int +const step = 5 + const ( - None = LogLevel(iota * 5) + All = LogLevel(iota * step) Trace Debug Verbose @@ -16,12 +18,13 @@ const ( Alert Critical Emergency + Null ) // String returns string representation of the LogLevel. -func (level *LogLevel) String() string { +func (level LogLevel) String() string { mapping := map[LogLevel]string{ - None: "none", + All: "all", Trace: "trace", Debug: "debug", Verbose: "verbose", @@ -33,11 +36,28 @@ func (level *LogLevel) String() string { Alert: "alert", Critical: "critical", Emergency: "emergency", + Null: "null", } - return mapping[*level] + return mapping[level] } // DigitRepresentation returns digit representations of the LogLevel. -func (level *LogLevel) DigitRepresentation() int { - return int(*level) +func (level LogLevel) DigitRepresentation() int { + return int(level) +} + +// Next returns next LogLevel. +func (level LogLevel) Next() LogLevel { + if level == Null { + return level + } + return LogLevel(level.DigitRepresentation() + step) +} + +// Previous returns previous LogLevel. +func (level LogLevel) Previous() LogLevel { + if level == All { + return level + } + return LogLevel(level.DigitRepresentation() - step) } diff --git a/pkg/logger/loglevel/loglevel_test.go b/pkg/logger/loglevel/loglevel_test.go index ea965b0..e711c62 100644 --- a/pkg/logger/loglevel/loglevel_test.go +++ b/pkg/logger/loglevel/loglevel_test.go @@ -12,7 +12,7 @@ func TestLogLevel_String(t *testing.T) { input LogLevel expected string }{ - {None, "none"}, + {All, "all"}, {Trace, "trace"}, {Debug, "debug"}, {Verbose, "verbose"}, @@ -24,6 +24,7 @@ func TestLogLevel_String(t *testing.T) { {Alert, "alert"}, {Critical, "critical"}, {Emergency, "emergency"}, + {Null, "null"}, } for index := range parameters { @@ -47,7 +48,7 @@ func TestLogLevel_DigitRepresentation(t *testing.T) { input LogLevel expected int }{ - {None, 0}, + {All, 0}, {Trace, 5}, {Debug, 10}, {Verbose, 15}, @@ -67,7 +68,8 @@ func TestLogLevel_DigitRepresentation(t *testing.T) { } } -// BenchmarkLogLevel_DigitRepresentation performs benchmarking of the LogLevel.DigitRepresentation(). +// BenchmarkLogLevel_DigitRepresentation performs benchmarking of the +// LogLevel.DigitRepresentation(). func BenchmarkLogLevel_DigitRepresentation(b *testing.B) { level := Debug @@ -75,3 +77,75 @@ func BenchmarkLogLevel_DigitRepresentation(b *testing.B) { level.DigitRepresentation() } } + +// TestLogLevel_Next tests that LogLevel returns next LogLevel. +func TestLogLevel_Next(t *testing.T) { + parameters := []struct { + input LogLevel + expected LogLevel + }{ + {All, Trace}, + {Trace, Debug}, + {Debug, Verbose}, + {Verbose, Info}, + {Info, Notice}, + {Notice, Warning}, + {Warning, Severe}, + {Severe, Error}, + {Error, Alert}, + {Alert, Critical}, + {Critical, Emergency}, + {Emergency, Null}, + {Null, Null}, + } + + for index := range parameters { + actual := parameters[index].input.Next() + testutils.AssertEquals(t, parameters[index].expected, actual) + } +} + +// BenchmarkLogLevel_Next performs benchmarking of the LogLevel.Next(). +func BenchmarkLogLevel_Next(b *testing.B) { + level := Debug + + for index := 0; index < b.N; index++ { + level.Next() + } +} + +// TestLogLevel_Previous tests that LogLevel returns previous LogLevel. +func TestLogLevel_Previous(t *testing.T) { + parameters := []struct { + input LogLevel + expected LogLevel + }{ + {All, All}, + {Trace, All}, + {Debug, Trace}, + {Verbose, Debug}, + {Info, Verbose}, + {Notice, Info}, + {Warning, Notice}, + {Severe, Warning}, + {Error, Severe}, + {Alert, Error}, + {Critical, Alert}, + {Emergency, Critical}, + {Null, Emergency}, + } + + for index := range parameters { + actual := parameters[index].input.Previous() + testutils.AssertEquals(t, parameters[index].expected, actual) + } +} + +// BenchmarkLogLevel_Previous performs benchmarking of the LogLevel.Previous(). +func BenchmarkLogLevel_Previous(b *testing.B) { + level := Debug + + for index := 0; index < b.N; index++ { + level.Previous() + } +}