Add goroutine numbers to log lines

This commit is contained in:
2025-03-27 19:19:39 +01:00
parent e847e5c3ce
commit 66bcf21d79
4 changed files with 201 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
package logger
import (
"bytes"
"fmt"
"io"
"log"
@@ -57,6 +58,7 @@ type Logger struct {
useColors bool
callerOffset int
defaultFields map[string]interface{}
showGoroutine bool
}
var (
@@ -104,6 +106,7 @@ func New(out io.Writer, prefix string, flag int) *Logger {
useColors: true,
callerOffset: 0,
defaultFields: make(map[string]interface{}),
showGoroutine: true,
}
}
@@ -139,6 +142,20 @@ func (l *Logger) SetCallerOffset(offset int) {
l.callerOffset = offset
}
// SetShowGoroutine sets whether to include goroutine ID in log messages
func (l *Logger) SetShowGoroutine(show bool) {
l.mu.Lock()
defer l.mu.Unlock()
l.showGoroutine = show
}
// ShowGoroutine returns whether goroutine ID is included in log messages
func (l *Logger) ShowGoroutine() bool {
l.mu.Lock()
defer l.mu.Unlock()
return l.showGoroutine
}
// WithField adds a field to the logger's context
func (l *Logger) WithField(key string, value interface{}) *Logger {
newLogger := &Logger{
@@ -149,6 +166,7 @@ func (l *Logger) WithField(key string, value interface{}) *Logger {
useColors: l.useColors,
callerOffset: l.callerOffset,
defaultFields: make(map[string]interface{}),
showGoroutine: l.showGoroutine,
}
// Copy existing fields
@@ -171,6 +189,7 @@ func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
useColors: l.useColors,
callerOffset: l.callerOffset,
defaultFields: make(map[string]interface{}),
showGoroutine: l.showGoroutine,
}
// Copy existing fields
@@ -185,6 +204,17 @@ func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
return newLogger
}
// GetGoroutineID extracts the goroutine ID from the runtime stack
func GetGoroutineID() string {
buf := make([]byte, 64)
n := runtime.Stack(buf, false)
// Format of first line is "goroutine N [state]:"
// We only need the N part
buf = buf[:n]
idField := bytes.Fields(bytes.Split(buf, []byte{':'})[0])[1]
return string(idField)
}
// formatMessage formats a log message with level, time, file, and line information
func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{}) string {
var msg string
@@ -257,8 +287,15 @@ func (l *Logger) formatMessage(level LogLevel, format string, args ...interface{
}
}
return fmt.Sprintf("%s%14s%-30s%s[%s%s%s]%s %s\n",
l.prefix, timeStr, caller, levelColor, levelNames[level], resetColor, fields, resetColor, msg)
// Add goroutine ID if enabled
var goroutineStr string
if l.showGoroutine {
goroutineID := GetGoroutineID()
goroutineStr = fmt.Sprintf("[g:%s] ", goroutineID)
}
return fmt.Sprintf("%s%s%s%s%s[%s%s%s]%s %s\n",
l.prefix, timeStr, caller, goroutineStr, levelColor, levelNames[level], resetColor, fields, resetColor, msg)
}
// log logs a message at the specified level
@@ -341,6 +378,16 @@ func Trace(format string, args ...interface{}) {
DefaultLogger.Trace(format, args...)
}
// LogPanic logs a panic error and its stack trace
func LogPanic(r interface{}) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
stack := make([]byte, 4096)
n := runtime.Stack(stack, false)
DefaultLogger.Error("PANIC: %v\n%s", r, stack[:n])
}
// SetLevel sets the log level for the default logger
func SetLevel(level LogLevel) {
if DefaultLogger == nil {
@@ -373,3 +420,19 @@ func WithFields(fields map[string]interface{}) *Logger {
}
return DefaultLogger.WithFields(fields)
}
// SetShowGoroutine enables or disables goroutine ID display in the default logger
func SetShowGoroutine(show bool) {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
DefaultLogger.SetShowGoroutine(show)
}
// ShowGoroutine returns whether goroutine ID is included in default logger's messages
func ShowGoroutine() bool {
if DefaultLogger == nil {
Init(defaultLogLevel)
}
return DefaultLogger.ShowGoroutine()
}

49
logger/panic_handler.go Normal file
View File

@@ -0,0 +1,49 @@
package logger
import (
"fmt"
"runtime/debug"
)
// PanicHandler handles a panic and logs it
func PanicHandler() {
if r := recover(); r != nil {
goroutineID := GetGoroutineID()
stackTrace := debug.Stack()
Error("PANIC in goroutine %s: %v\n%s", goroutineID, r, stackTrace)
}
}
// SafeGo launches a goroutine with panic recovery
// Usage: logger.SafeGo(func() { ... your code ... })
func SafeGo(f func()) {
go func() {
defer PanicHandler()
f()
}()
}
// SafeGoWithArgs launches a goroutine with panic recovery and passes arguments
// Usage: logger.SafeGoWithArgs(func(arg1, arg2 interface{}) { ... }, "value1", 42)
func SafeGoWithArgs(f func(...interface{}), args ...interface{}) {
go func() {
defer PanicHandler()
f(args...)
}()
}
// SafeExec executes a function with panic recovery
// Useful for code that should not panic
func SafeExec(f func()) (err error) {
defer func() {
if r := recover(); r != nil {
goroutineID := GetGoroutineID()
stackTrace := debug.Stack()
Error("PANIC in goroutine %s: %v\n%s", goroutineID, r, stackTrace)
err = fmt.Errorf("panic recovered: %v", r)
}
}()
f()
return nil
}