LogContext represents a hierarchical logging context. Contexts can be
nested and will automatically generate a prefix based on their
parent/child relationship.
Prefix returns the context prefix in the form "parent:child". An empty
context results in an empty string.
{
if c == nil {
return ""
}
if c.Parent == nil {
return c.Name
}
parent := c.Parent.Prefix()
if parent == "" {
return c.Name
}
if c.Name == "" {
return parent
}
return parent + ":" + c.Name
}
NewLogContext creates a new LogContext optionally linked to a parent.
{
return &LogContext{Name: name, Parent: parent}
}
Logger represents a logger for the application.
{
l.mu.Lock()
defer l.mu.Unlock()
if l.ErrIndex == nil {
l.ErrIndex = make(map[string]int)
}
idx := l.ErrIndex[prefix]
l.ErrIndex[prefix] = idx + 1
return idx
}
InfoCtx logs an informational message using the provided context.
{
prefix := ctx.Prefix()
formatted := fmt.Sprintf("%s:info:%s", prefix, msg)
l.File.Info().Msg(formatted)
l.Term.Info().Msg(formatted)
}
WarnCtx logs a warning message using the provided context.
{
prefix := ctx.Prefix()
formatted := fmt.Sprintf("%s:warn:%s", prefix, msg)
l.File.Warn().Msg(formatted)
l.Term.Warn().Msg(formatted)
}
ErrorCtx logs an error message using the provided context. The error index
is automatically incremented per-context to provide consistent progression.
{
prefix := ctx.Prefix()
idx := l.nextErrIndex(prefix)
formatted := fmt.Sprintf("%s:err(%d):%s", prefix, idx, msg)
l.File.Error().Msg(formatted)
l.Term.Error().Msg(formatted)
}
getLogPath returns the path to the log directory, if the user is running as
root, the logs will be stored in /var/vlogs/, otherwise the logs will be
stored in ~/.vlogs.
{
var logPath string
if os.Geteuid() == 0 {
logPath = "/var/vlogs/"
} else {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get user home directory: %v", err)
}
logPath = filepath.Join(homeDir, ".vlogs")
}
// we have to create the directory if it doesn't exist
if _, err := os.Stat(logPath); os.IsNotExist(err) {
err := os.MkdirAll(logPath, 0755)
if err != nil {
return "", fmt.Errorf("failed to create log directory: %v", err)
}
}
return logPath, nil
}
NewLogger creates a new logger for the application, each logger has
a file logger and a console logger. The file logger is used to log
to the vlogs directory, while the console logger is used to log to
the console.
logger, err := logs.NewLogger(app)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
logger.File.Info().Msg("Batman reached the file logger")
logger.Console.Info().Msg("Batman reached the console logger")
since we use structured logging, we can also log with fields:
logger.File.Info().Str("where", "file").Msg("Batman is saving Gotham")
logger.Console.Info().Str("where", "console").Msg("Batman is saving Gotham")
{
vLogger := Logger{}
vLogger.ErrIndex = make(map[string]int)
// preparing the file logger
logPath, err := getLogPath()
if err != nil {
return vLogger, err
}
vLogFile := filepath.Join(logPath, domain, "log.json")
vLogger.File = log.Logger{
Level: log.ParseLevel("info"),
Writer: &log.FileWriter{
Filename: vLogFile,
FileMode: 0600,
MaxSize: 500 * 1024 * 1024,
MaxBackups: 7,
EnsureFolder: true,
LocalTime: true,
TimeFormat: "15:04:05",
Cleaner: func(filename string, maxBackups int, matches []os.FileInfo) {
var dir = filepath.Dir(filename)
for i, fi := range matches {
filename := filepath.Join(dir, fi.Name())
switch {
case i > maxBackups:
os.Remove(filename)
case !strings.HasSuffix(filename, ".gz"):
go exec.Command("nice", "gzip", filename).Run()
}
}
},
},
}
// setting up the rotation for the file logger
runner := cron.New(cron.WithLocation(time.Local))
runner.AddFunc("0 0 * * *", func() { vLogger.File.Writer.(*log.FileWriter).Rotate() })
go runner.Run()
// preparing the console logger
vLogger.Term = log.Logger{
TimeFormat: "15:04:05",
Caller: 1,
Writer: &log.ConsoleWriter{
Formatter: formatLog,
EndWithMessage: true,
},
}
return vLogger, nil
}
formatLog formats the log message with appropriate colors for log level
{
var color, three string
// Determine color and abbreviation for log level
switch a.Level {
case "trace":
color, three = Magenta, "TRC"
case "debug":
color, three = Yellow, "DBG"
case "info":
color, three = Green, "INF"
case "warn":
color, three = Red, "WRN"
case "error":
color, three = Red, "ERR"
case "fatal":
color, three = Red, "FTL"
case "panic":
color, three = Red, "PNC"
default:
color, three = Gray, "???"
}
// Format the log message
formattedLog := fmt.Sprintf("%s%s%s ", color, three, Reset)
formattedLog += fmt.Sprintf("%s>%s", Cyan, Reset)
formattedLog += fmt.Sprintf(" %s\n", a.Message)
return fmt.Fprint(w, formattedLog)
}
import "fmt"
import "sync"
import "github.com/phuslu/log"
import "fmt"
import "io"
import "os"
import "os/exec"
import "path/filepath"
import "strings"
import "time"
import "github.com/phuslu/log"
import "github.com/robfig/cron/v3"