353 lines
6.5 KiB
Go
353 lines
6.5 KiB
Go
package logger
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// ANSI color codes
|
|
const (
|
|
colorReset = "\033[0m"
|
|
colorRed = "\033[31m"
|
|
colorGreen = "\033[32m"
|
|
colorYellow = "\033[33m"
|
|
colorBlue = "\033[34m"
|
|
colorPurple = "\033[35m"
|
|
colorCyan = "\033[36m"
|
|
colorGray = "\033[90m"
|
|
)
|
|
|
|
// Log types
|
|
const (
|
|
TypeNone = iota
|
|
TypeDebug
|
|
TypeInfo
|
|
TypeWarning
|
|
TypeError
|
|
TypeServer
|
|
TypeFatal
|
|
)
|
|
|
|
// Type properties
|
|
var typeProps = map[int]struct {
|
|
tag string
|
|
color string
|
|
}{
|
|
TypeDebug: {"D", colorCyan},
|
|
TypeInfo: {"I", colorBlue},
|
|
TypeWarning: {"W", colorYellow},
|
|
TypeError: {"E", colorRed},
|
|
TypeServer: {"S", colorGreen},
|
|
TypeFatal: {"F", colorPurple},
|
|
}
|
|
|
|
const timeFormat = "15:04:05"
|
|
|
|
var (
|
|
globalLogger *Logger
|
|
globalLoggerOnce sync.Once
|
|
)
|
|
|
|
type Logger struct {
|
|
writer io.Writer
|
|
useColors bool
|
|
timeFormat string
|
|
showTimestamp bool
|
|
mu sync.Mutex
|
|
debugMode atomic.Bool
|
|
}
|
|
|
|
func GetLogger() *Logger {
|
|
globalLoggerOnce.Do(func() {
|
|
globalLogger = newLogger(true, true)
|
|
})
|
|
return globalLogger
|
|
}
|
|
|
|
func InitGlobalLogger(useColors bool, showTimestamp bool) {
|
|
globalLogger = newLogger(useColors, showTimestamp)
|
|
}
|
|
|
|
func newLogger(useColors bool, showTimestamp bool) *Logger {
|
|
return &Logger{
|
|
writer: os.Stdout,
|
|
useColors: useColors,
|
|
timeFormat: timeFormat,
|
|
showTimestamp: showTimestamp,
|
|
}
|
|
}
|
|
|
|
func New(useColors bool, showTimestamp bool) *Logger {
|
|
return newLogger(useColors, showTimestamp)
|
|
}
|
|
|
|
func (l *Logger) SetOutput(w io.Writer) {
|
|
l.mu.Lock()
|
|
l.writer = w
|
|
l.mu.Unlock()
|
|
}
|
|
|
|
func (l *Logger) TimeFormat() string {
|
|
return l.timeFormat
|
|
}
|
|
|
|
func (l *Logger) SetTimeFormat(format string) {
|
|
l.mu.Lock()
|
|
l.timeFormat = format
|
|
l.mu.Unlock()
|
|
}
|
|
|
|
func (l *Logger) EnableTimestamp() {
|
|
l.showTimestamp = true
|
|
}
|
|
|
|
func (l *Logger) DisableTimestamp() {
|
|
l.showTimestamp = false
|
|
}
|
|
|
|
func (l *Logger) EnableColors() {
|
|
l.useColors = true
|
|
}
|
|
|
|
func (l *Logger) DisableColors() {
|
|
l.useColors = false
|
|
}
|
|
|
|
func (l *Logger) EnableDebug() {
|
|
l.debugMode.Store(true)
|
|
}
|
|
|
|
func (l *Logger) DisableDebug() {
|
|
l.debugMode.Store(false)
|
|
}
|
|
|
|
func (l *Logger) IsDebugEnabled() bool {
|
|
return l.debugMode.Load()
|
|
}
|
|
|
|
func (l *Logger) applyColor(text, color string) string {
|
|
if l.useColors {
|
|
return color + text + colorReset
|
|
}
|
|
return text
|
|
}
|
|
|
|
func stripAnsiColors(s string) string {
|
|
result := ""
|
|
inEscape := false
|
|
|
|
for _, c := range s {
|
|
if inEscape {
|
|
if c == 'm' {
|
|
inEscape = false
|
|
}
|
|
continue
|
|
}
|
|
if c == '\033' {
|
|
inEscape = true
|
|
continue
|
|
}
|
|
result += string(c)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (l *Logger) writeMessage(logType int, message string, rawMode bool) {
|
|
if rawMode {
|
|
l.mu.Lock()
|
|
_, _ = fmt.Fprint(l.writer, message+"\n")
|
|
l.mu.Unlock()
|
|
return
|
|
}
|
|
|
|
parts := []string{}
|
|
|
|
if l.showTimestamp {
|
|
timestamp := time.Now().Format(l.timeFormat)
|
|
if l.useColors {
|
|
timestamp = l.applyColor(timestamp, colorGray)
|
|
}
|
|
parts = append(parts, timestamp)
|
|
}
|
|
|
|
if logType != TypeNone {
|
|
props := typeProps[logType]
|
|
tag := "[" + props.tag + "]"
|
|
if l.useColors {
|
|
tag = l.applyColor(tag, props.color)
|
|
}
|
|
parts = append(parts, tag)
|
|
}
|
|
|
|
parts = append(parts, message)
|
|
logLine := strings.Join(parts, " ") + "\n"
|
|
|
|
l.mu.Lock()
|
|
_, _ = fmt.Fprint(l.writer, logLine)
|
|
if logType == TypeFatal {
|
|
if f, ok := l.writer.(*os.File); ok {
|
|
_ = f.Sync()
|
|
}
|
|
}
|
|
l.mu.Unlock()
|
|
}
|
|
|
|
func (l *Logger) log(logType int, format string, args ...any) {
|
|
// Only filter debug messages
|
|
if logType == TypeDebug && !l.debugMode.Load() {
|
|
return
|
|
}
|
|
|
|
var message string
|
|
if len(args) > 0 {
|
|
message = fmt.Sprintf(format, args...)
|
|
} else {
|
|
message = format
|
|
}
|
|
|
|
l.writeMessage(logType, message, false)
|
|
|
|
if logType == TypeFatal {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func (l *Logger) LogRaw(format string, args ...any) {
|
|
var message string
|
|
if len(args) > 0 {
|
|
message = fmt.Sprintf(format, args...)
|
|
} else {
|
|
message = format
|
|
}
|
|
|
|
if !l.useColors {
|
|
message = stripAnsiColors(message)
|
|
}
|
|
|
|
l.writeMessage(TypeInfo, message, true)
|
|
}
|
|
|
|
func (l *Logger) Debug(format string, args ...any) {
|
|
l.log(TypeDebug, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Info(format string, args ...any) {
|
|
l.log(TypeInfo, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Warning(format string, args ...any) {
|
|
l.log(TypeWarning, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Error(format string, args ...any) {
|
|
l.log(TypeError, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Fatal(format string, args ...any) {
|
|
l.log(TypeFatal, format, args...)
|
|
}
|
|
|
|
func (l *Logger) Server(format string, args ...any) {
|
|
l.log(TypeServer, format, args...)
|
|
}
|
|
|
|
func (l *Logger) LogRequest(statusCode int, method, path string, duration time.Duration) {
|
|
var statusColor string
|
|
|
|
switch {
|
|
case statusCode < 300:
|
|
statusColor = colorGreen
|
|
case statusCode < 400:
|
|
statusColor = colorCyan
|
|
case statusCode < 500:
|
|
statusColor = colorYellow
|
|
default:
|
|
statusColor = colorRed
|
|
}
|
|
|
|
var durationStr string
|
|
micros := duration.Microseconds()
|
|
if micros < 1000 {
|
|
durationStr = fmt.Sprintf("%.0fµs", float64(micros))
|
|
} else if micros < 1000000 {
|
|
durationStr = fmt.Sprintf("%.1fms", float64(micros)/1000)
|
|
} else {
|
|
durationStr = fmt.Sprintf("%.2fs", duration.Seconds())
|
|
}
|
|
|
|
message := fmt.Sprintf("%s %s %s %s",
|
|
l.applyColor("["+method+"]", colorGray),
|
|
l.applyColor(fmt.Sprintf("%d", statusCode), statusColor),
|
|
l.applyColor(path, colorGray),
|
|
l.applyColor(durationStr, colorGray),
|
|
)
|
|
|
|
l.writeMessage(TypeNone, message, false)
|
|
}
|
|
|
|
// Global functions
|
|
func Debug(format string, args ...any) {
|
|
GetLogger().Debug(format, args...)
|
|
}
|
|
|
|
func Info(format string, args ...any) {
|
|
GetLogger().Info(format, args...)
|
|
}
|
|
|
|
func Warning(format string, args ...any) {
|
|
GetLogger().Warning(format, args...)
|
|
}
|
|
|
|
func Error(format string, args ...any) {
|
|
GetLogger().Error(format, args...)
|
|
}
|
|
|
|
func Fatal(format string, args ...any) {
|
|
GetLogger().Fatal(format, args...)
|
|
}
|
|
|
|
func Server(format string, args ...any) {
|
|
GetLogger().Server(format, args...)
|
|
}
|
|
|
|
func LogRaw(format string, args ...any) {
|
|
GetLogger().LogRaw(format, args...)
|
|
}
|
|
|
|
func SetOutput(w io.Writer) {
|
|
GetLogger().SetOutput(w)
|
|
}
|
|
|
|
func TimeFormat() string {
|
|
return GetLogger().TimeFormat()
|
|
}
|
|
|
|
func EnableDebug() {
|
|
GetLogger().EnableDebug()
|
|
}
|
|
|
|
func DisableDebug() {
|
|
GetLogger().DisableDebug()
|
|
}
|
|
|
|
func IsDebugEnabled() bool {
|
|
return GetLogger().IsDebugEnabled()
|
|
}
|
|
|
|
func EnableTimestamp() {
|
|
GetLogger().EnableTimestamp()
|
|
}
|
|
|
|
func DisableTimestamp() {
|
|
GetLogger().DisableTimestamp()
|
|
}
|
|
|
|
func LogRequest(statusCode int, method, path string, duration time.Duration) {
|
|
GetLogger().LogRequest(statusCode, method, path, duration)
|
|
}
|