0
\$\begingroup\$

The package is essentially a small wrapper around the stdlib logger that provides a very simple, easy to use logger that can toggle between different log levels (DEBUG, INFO, WARNING, ERROR). Log levels control which log messages actually get written to the io.Writer. Also adds the ability to get new references/pointers to any previously instantiated loggers.

Mainly looking for feedback about things like concurrency safety and memory leaks, but any other feedback is also welcome.

Code:

//Package simplelog provides a simple, easy to use logger that can toggle between
//different log levels (DEBUG, INFO, WARNING, ERROR). Most of the actual
//logging is done with the stdlib log.Logger, simplelog mainly controls
//controls which messages get written to logs via log levels.
package simplelog
import (
 "fmt"
 "io"
 "log"
 "path/filepath"
 "runtime"
 "sync"
)
//Log level constants
const (
 DEBUG = 0
 INFO = 10
 WARNING = 20
 ERROR = 30
)
//Pkg level lock for reading and writing to the package level and
//a map of all logs. allLogs tracks all existing Loggers.
var (
 pkgLock sync.Mutex
 allLogs = make(map[string]*Logger)
)
//getfileinfo gets the calling filename and line number.
func getfileinfo() string {
 _, filename, line, ok := runtime.Caller(2)
 if !ok {
 filename = "Unknown"
 line = 0
 }
 return fmt.Sprintf("%s:%d: ", filepath.Base(filename), line)
}
type Logger struct {
 name string //The same string used when calling Get()
 level int //Determines which of the loggers are allowed to write data
 debugLogger *log.Logger //Independent loggers for Independent prefixes/loglevels
 infoLogger *log.Logger //Independent loggers for Independent prefixes/loglevels
 warningLogger *log.Logger //Independent loggers for Independent prefixes/loglevels
 errorLogger *log.Logger //Independent loggers for Independent prefixes/loglevels
 logfileinfo bool //Whether or not to log the calling file and line number
 lock sync.Mutex //Extra concurrency protection
}
//SetLevel sets the log level for the respective Logger.
//Each level is an int that can be set via pkg constants
//e.g. (simplelog.DEBUG) or via literal ints. Messages sent
//to Loggers with a log level lower than the current level are
//not written.
func (logger *Logger) SetLevel(level int) {
 logger.lock.Lock()
 defer logger.lock.Unlock()
 logger.level = level
}
//Log level = 0 | simplelog.DEBUG
func (logger *Logger) Debug(msg string) {
 logger.lock.Lock()
 defer logger.lock.Unlock()
 if logger.level > DEBUG {
 return
 }
 if logger.logfileinfo {
 msg = getfileinfo() + msg
 }
 logger.debugLogger.Println(msg)
}
//Log level = 10 | simplelog.INFO
func (logger *Logger) Info(msg string) {
 logger.lock.Lock()
 defer logger.lock.Unlock()
 if logger.level > INFO {
 return
 }
 if logger.logfileinfo {
 msg = getfileinfo() + msg
 }
 logger.infoLogger.Println(msg)
}
//Log level = 20 | simplelog.WARNING
func (logger *Logger) Warning(msg string) {
 logger.lock.Lock()
 defer logger.lock.Unlock()
 if logger.level > WARNING {
 return
 }
 if logger.logfileinfo {
 msg = getfileinfo() + msg
 }
 logger.warningLogger.Println(msg)
}
//Log level = 30 | simplelog.ERROR
func (logger *Logger) Err(msg string) {
 logger.lock.Lock()
 defer logger.lock.Unlock()
 if logger.level > ERROR {
 return
 }
 if logger.logfileinfo {
 msg = getfileinfo() + msg
 }
 logger.errorLogger.Println(msg)
}
/*
New is the constructor for simplelog. It returns a pointer to a lvlLogger.
 name - Can be used to get a new pointer to an existing log via simplelog.Get(name)
 dest - Sets the destination to which log messages will be written
 logfileinfo - Whether or not to include filename:line in message (e.g. main.go:30)
 flags - The same flags you would pass into the stdlib log.New()
 Defaults to "log.Ldate | log.Ltime | log.Lmsgprefix" if nothing is passed.
 `log.Lshortfile` will always report as this pkg, so use logfileinfo param instead.
*/
func New(name string, dest io.Writer, logfileinfo bool, flags ...int) *Logger {
 pkgLock.Lock()
 defer pkgLock.Unlock()
 _, alreadyExists := allLogs[name]
 if alreadyExists {
 panic(fmt.Sprintf("simplelog: Unable to create logger with name \"%s\", name already in use", name))
 }
 var logflags int
 if flags == nil {
 logflags = log.Ldate | log.Ltime | log.Lmsgprefix
 } else {
 logflags = flags[0]
 }
 debugLogger := log.New(dest, "DEBUG: ", logflags)
 infoLogger := log.New(dest, "INFO: ", logflags)
 warningLogger := log.New(dest, "WARNING: ", logflags)
 errorLogger := log.New(dest, "Error: ", logflags)
 returnlogger := &Logger{
 name: name,
 debugLogger: debugLogger,
 infoLogger: infoLogger,
 warningLogger: warningLogger,
 errorLogger: errorLogger,
 logfileinfo: logfileinfo,
 }
 allLogs[name] = returnlogger
 return returnlogger
}
//Get returns a reference to an existing Logger if one exists, otherwise nil.
//Uses the name string that was used to create the log via simplelog.New()
func Get(name string) *Logger {
 pkgLock.Lock()
 defer pkgLock.Unlock()
 if foundlog, isfound := allLogs[name]; isfound {
 return foundlog
 } else {
 return nil
 }
}

Usage:

package main
import (
 "os"
 "simplelog"
)
func main() {
 logfile, _ := os.OpenFile("testlog.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0777)
 mylogger := simplelog.New("Mylog", logfile, true)
 mylogger.SetLevel(simplelog.DEBUG)
 mylogger.Debug("First msg")
 mylogger.Info("First msg")
 mylogger.Warning("First msg")
 mylogger.Err("First msg")
 anotherPointer := simplelog.Get("Mylog")
 anotherPointer.Info("Another pointer")
 anotherPointer.Info("--------------")
 anotherPointer.SetLevel(simplelog.WARNING)
 anotherPointer.Debug("Warnings and higher")
 anotherPointer.Info("Warnings and higher")
 anotherPointer.Warning("Warnings and higher")
 anotherPointer.Err("Warnings and higher")
}

testlog.txt output:

2022年01月14日 12:13:03 DEBUG: testapp.go:13: First msg
2022年01月14日 12:13:03 INFO: testapp.go:14: First msg
2022年01月14日 12:13:03 WARNING: testapp.go:15: First msg
2022年01月14日 12:13:03 ERROR: testapp.go:16: First msg
2022年01月14日 12:13:03 INFO: testapp.go:19: Another pointer
2022年01月14日 12:13:03 INFO: testapp.go:20: --------------
2022年01月14日 12:13:03 WARNING: testapp.go:25: Warnings and higher
2022年01月14日 12:13:03 ERROR: testapp.go:26: Warnings and higher

To stdout with no flags:

mylogger := simplelog.New("naked logger", os.Stdout, false, 0)
mylogger.Info("Just a prefix")

Stdout:

INFO: Just a prefix
asked Jan 14, 2022 at 20:31
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Is file size a concern in your context?
Perhaps to include a maximum size in bytes for your files could be a useful constraint.

answered Jan 20, 2022 at 14:52
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.