Search code examples
gologging

Simple logging in Golang


I’m aware this might be a duplicate of How to implement level-based logging in Golang?, but I’d appreciate additional advice specific to my situation, perhaps for 11 years something new came up :D.

I’m have already used Go’s built-in log package, which works fine, but I’d like to enhance, see an example of simple but still usable implementation. I've got those criterias in mind:

  • Separating logging into its own package.
  • Handling different log levels (e.g., info, warning, error).
  • Structuring the system to allow for both error and general message logs.
  • Maybe use a slog instead of plain log

Solution

  • If you really need something simple, here is how I usually write for pet-projects:

    package logging
    
    import (
        "fmt"
        "log"
        "os"
        "regexp"
        "runtime"
    )
    
    type logLevel int
    
    const (
        INFO logLevel = iota
        ERR
    )
    
    type application struct {
        level    logLevel
        errorLog *log.Logger
        infoLog  *log.Logger
    }
    
    var infoLog = log.New(os.Stdout, "INFO: \t", log.Ltime)
    var errorLog = log.New(os.Stderr, "ERROR: \t", log.Ltime)
    
    var path string
    
    func NewLogger(Level logLevel) *application {
        return &application{
            level:    Level,
            errorLog: errorLog,
            infoLog:  infoLog,
        }
    }
    
    func (l *application) Info(msg ...any) {
        if l.level <= INFO {
            file, line := getCaller()
            l.infoLog.Printf("[%s : %d] \n\n%s\n\n", file, line, fmt.Sprint(msg...))
        }
    }
    
    func (l *application) Error(err ...any) {
        if l.level <= ERR {
            file, line := getCaller()
            l.errorLog.Fatalf("[%s : %d] \n\n%s\n\n", file, line, fmt.Sprint(err...))
        }
    }
    
    func getCaller() (string, int) {
        key := os.Getenv("KEY_WORD") // assign your folder name, so we can crop no-required part
    
        _, file, line, ok := runtime.Caller(2)
        if !ok {
            log.Fatal("runtime caller has an error")
        }
    
        if key == "" {
            fmt.Print("key is empty")
            return file, line // Return without modifying if key is not set
        }
    
        regExp, _ := regexp.Compile(".*" + regexp.QuoteMeta(key)) // regex for deleting left side 
    
        file = regExp.ReplaceAllString(file, key)
    
        return file, line
    }
    

    It is not super advance or something, but still does the job well, main feature is, perhaps, levelling logs and errors into different streams, and showing path to file, where error did occur.

    If your goal is study - look up at question you attached,

    there are a bunch of examples. Look at them and try to advance snippet I provided in the way you like