upload logging.go
This commit is contained in:
286
logging.go
Executable file
286
logging.go
Executable file
@@ -0,0 +1,286 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
COLOR_RESET = "\033[0m"
|
||||||
|
COLOR_RED = "\033[91m"
|
||||||
|
COLOR_YELLOW = "\033[93m"
|
||||||
|
COLOR_GREEN = "\033[92m"
|
||||||
|
COLOR_BLUE = "\033[94m"
|
||||||
|
COLOR_CYAN = "\033[96m"
|
||||||
|
COLOR_WHITE = "\033[97m"
|
||||||
|
COLOR_GRAY = "\033[90m"
|
||||||
|
COLOR_GREY = COLOR_GRAY
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Debug LogLevel = iota
|
||||||
|
Info
|
||||||
|
Warn
|
||||||
|
Error
|
||||||
|
Critical
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l LogLevel) String() string {
|
||||||
|
switch l {
|
||||||
|
case Debug:
|
||||||
|
return "DEBUG"
|
||||||
|
case Info:
|
||||||
|
return "INFO"
|
||||||
|
case Warn:
|
||||||
|
return "WARN"
|
||||||
|
case Error:
|
||||||
|
return "ERROR"
|
||||||
|
case Critical:
|
||||||
|
return "CRITICAL"
|
||||||
|
default:
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var LevelColorMap = map[LogLevel]string{
|
||||||
|
Debug: COLOR_GRAY,
|
||||||
|
Info: COLOR_WHITE,
|
||||||
|
Warn: COLOR_YELLOW,
|
||||||
|
Error: COLOR_RED,
|
||||||
|
Critical: COLOR_RED,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
Format(level LogLevel, timestamp time.Time, message string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConsoleFormatter struct {
|
||||||
|
TimestampFormatter string
|
||||||
|
UseColor bool
|
||||||
|
LevelColors map[LogLevel]string
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsoleFormatter(timestampFormat string, useColor bool, levelColors map[LogLevel]string) *ConsoleFormatter {
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = "[02/01/06 15:04:05]"
|
||||||
|
}
|
||||||
|
if levelColors == nil {
|
||||||
|
levelColors = LevelColorMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ConsoleFormatter{
|
||||||
|
TimestampFormatter: timestampFormat,
|
||||||
|
UseColor: useColor,
|
||||||
|
LevelColors: levelColors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ConsoleFormatter) Format(level LogLevel, timestamp time.Time, message string) string {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
|
timestampStr := timestamp.Format(f.TimestampFormatter)
|
||||||
|
levelStr := level.String()
|
||||||
|
color, ok := f.LevelColors[level]
|
||||||
|
if !ok {
|
||||||
|
color = COLOR_WHITE
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.UseColor && isTTY() {
|
||||||
|
return fmt.Sprintf("%s %s[%s]%s %s", timestampStr, color, levelStr, COLOR_RESET, message)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s [%s] %s", timestampStr, levelStr, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileFormatter struct {
|
||||||
|
TimestampFormat string
|
||||||
|
LoggerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileFormatter(timestampFormat, loggerName string) *FileFormatter {
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = "2006-01-02 15:04:05"
|
||||||
|
}
|
||||||
|
return &FileFormatter{
|
||||||
|
TimestampFormat: timestampFormat,
|
||||||
|
LoggerName: loggerName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FileFormatter) Format(level LogLevel, timestamp time.Time, message string) string {
|
||||||
|
return fmt.Sprintf("[ %s | %s | %s ] %s", timestamp.Format(f.TimestampFormat), f.LoggerName, level.String(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
name string
|
||||||
|
level LogLevel
|
||||||
|
consoleColor bool
|
||||||
|
logDir string
|
||||||
|
logFilenamePattern string
|
||||||
|
formatter Formatter
|
||||||
|
consoleWriter io.Writer
|
||||||
|
fileWriter io.WriteCloser
|
||||||
|
fileLogger *log.Logger
|
||||||
|
fileFormatter *FileFormatter
|
||||||
|
mu sync.Mutex
|
||||||
|
initialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalLogger *Logger
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLogger(
|
||||||
|
loggerName string,
|
||||||
|
logLevel LogLevel,
|
||||||
|
consoleColor bool,
|
||||||
|
logDir string,
|
||||||
|
logFilenamePattern string,
|
||||||
|
) *Logger {
|
||||||
|
if loggerName == "" {
|
||||||
|
loggerName = "main"
|
||||||
|
}
|
||||||
|
if logDir == "" {
|
||||||
|
logDir = "logs"
|
||||||
|
}
|
||||||
|
if logFilenamePattern == "" {
|
||||||
|
logFilenamePattern = "2006-01-02"
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Logger{
|
||||||
|
name: loggerName,
|
||||||
|
level: logLevel,
|
||||||
|
consoleColor: consoleColor,
|
||||||
|
logDir: logDir,
|
||||||
|
logFilenamePattern: logFilenamePattern,
|
||||||
|
consoleWriter: os.Stdout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) init() {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
|
if l.initialized {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(l.logDir, 0755); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error creating log directory %s: %v\n", l.logDir, err)
|
||||||
|
} else {
|
||||||
|
logFilePath := l.genUniqueLogFilename()
|
||||||
|
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error opening log file %s: %v\n", logFilePath, err)
|
||||||
|
} else {
|
||||||
|
l.fileWriter = file
|
||||||
|
if l.fileWriter != nil {
|
||||||
|
l.fileFormatter = NewFileFormatter("", l.name)
|
||||||
|
}
|
||||||
|
l.fileLogger = log.New(l.fileWriter, "", 0)
|
||||||
|
fmt.Printf("Log file will be saved to %s\n", logFilePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l.formatter = NewConsoleFormatter("", l.consoleColor, nil)
|
||||||
|
|
||||||
|
l.initialized = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) genUniqueLogFilename() string {
|
||||||
|
now := time.Now()
|
||||||
|
baseFilename := now.Format(l.logFilenamePattern)
|
||||||
|
logFilePath := filepath.Join(l.logDir, fmt.Sprintf("%s.log", baseFilename))
|
||||||
|
|
||||||
|
if _, err := os.Stat(logFilePath); os.IsNotExist(err) {
|
||||||
|
return logFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
counter := 1
|
||||||
|
for {
|
||||||
|
suffixedFilePath := filepath.Join(l.logDir, fmt.Sprintf("%s_%d.log", baseFilename, counter))
|
||||||
|
if _, err := os.Stat(suffixedFilePath); os.IsNotExist(err) {
|
||||||
|
return suffixedFilePath
|
||||||
|
}
|
||||||
|
counter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Log(level LogLevel, message string) {
|
||||||
|
if !l.initialized {
|
||||||
|
l.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
if level < l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
if l.formatter != nil {
|
||||||
|
formattedMsg := l.formatter.Format(level, now, message)
|
||||||
|
fmt.Fprintln(l.consoleWriter, formattedMsg)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(l.consoleWriter, "%s [%s] %s\n", now.Format("[15:04:05]"), level.String(), message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.fileLogger != nil && l.fileFormatter != nil {
|
||||||
|
formattedFileMsg := l.fileFormatter.Format(level, now, message)
|
||||||
|
l.fileLogger.Println(formattedFileMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(message string, v ...any) {
|
||||||
|
l.Log(Debug, fmt.Sprintf(message, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(message string, v ...any) {
|
||||||
|
l.Log(Warn, fmt.Sprintf(message, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(message string, v ...any) {
|
||||||
|
l.Log(Info, fmt.Sprintf(message, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(message string, v ...any) {
|
||||||
|
l.Log(Error, fmt.Sprintf(message, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Critical(message string, v ...any) {
|
||||||
|
l.Log(Critical, fmt.Sprintf(message, v...))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Close() error {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
if l.fileWriter != nil {
|
||||||
|
err := l.fileWriter.Close()
|
||||||
|
l.fileWriter = nil
|
||||||
|
l.fileLogger = nil
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogger() *Logger {
|
||||||
|
once.Do(func() {
|
||||||
|
globalLogger = NewLogger("shizuku", Debug, true, "logs", "2006-01-02")
|
||||||
|
})
|
||||||
|
return globalLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTTY() bool {
|
||||||
|
return os.Getenv("TERM") != "" && term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user