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"