diff --git a/entry.go b/entry.go index 9b316a5..ae02b5f 100644 --- a/entry.go +++ b/entry.go @@ -16,15 +16,17 @@ package golog import ( "fmt" + "runtime" "time" ) type Entry struct { - Logger *Logger - Content []Content - Message string - Level Level - Time time.Time + Logger *Logger + Content []Content + Message string + Level Level + Time time.Time + CallStack []runtime.Frame } type Content struct { diff --git a/formatter_json.go b/formatter_json.go index 7d0be05..5e063b9 100644 --- a/formatter_json.go +++ b/formatter_json.go @@ -16,6 +16,7 @@ package golog import ( "encoding/json" + "fmt" "log" "time" ) @@ -47,6 +48,13 @@ func (formatter *FormatterJSON) Process(entry *Entry) { // add message formatter.data["message"] = entry.Message + // add call stack + if len(entry.CallStack) > 0 { + caller := entry.CallStack[0] + formatter.data["file"] = fmt.Sprintf("%s:%d", caller.File, caller.Line) + formatter.data["function"] = caller.Function + } + // add additional fields if formatter.FlatContent { for _, content := range entry.Content { diff --git a/formatter_keyvalue.go b/formatter_keyvalue.go index bae5084..ee20852 100644 --- a/formatter_keyvalue.go +++ b/formatter_keyvalue.go @@ -47,6 +47,13 @@ func (formatter *FormatterKeyValue) Process(entry *Entry) { // add message formatter.addKV("message", entry.Message) + // add call stack + if len(entry.CallStack) > 0 { + caller := entry.CallStack[0] + formatter.addKV("file", fmt.Sprintf("%s:%d", caller.File, caller.Line)) + formatter.addKV("function", caller.Function) + } + // add additional fields for _, content := range entry.Content { formatter.addKV(content.Key, content.Value) diff --git a/logger.go b/logger.go index d35a347..0f0bf2f 100644 --- a/logger.go +++ b/logger.go @@ -14,10 +14,17 @@ package golog -import "time" +import ( + "log" + "runtime" + "strings" + "time" +) var Default = NewLoggerDefault() +const maxCallersPCs = 20 + type Logger struct { Outputs []*Output } @@ -36,12 +43,68 @@ func (logger *Logger) LogEntry(entry *Entry) { // set execution time entry.Time = time.Now() + // set call stack + entry.CallStack = logger.callStack() + // run each output for _, output := range logger.Outputs { output.Send(entry) } } +func (logger *Logger) callStack() (response []runtime.Frame) { + // get call stack + pcs := make([]uintptr, maxCallersPCs) + runtime.Callers(1, pcs) + frames := runtime.CallersFrames(pcs) + + var self string + for { + // check for next frame + frame, more := frames.Next() + if !more { + break + } + + // extract self package + if self == "" { + self = logger.framePackage(frame) + log.Println("self:", self, frame.Function) // TODO: remove + continue + } + + // ignore self + currentPackage := logger.framePackage(frame) + log.Println("package:", currentPackage, frame.Function) // TODO: remove + if currentPackage != self { + response = append(response, frame) + } + } + + return +} + +func (logger *Logger) framePackage(frame runtime.Frame) string { + s := frame.Function + + // special handling, if no full qualified package exists + lastSlash := strings.LastIndexByte(s, '/') + if lastSlash == -1 { + firstDot := strings.IndexByte(s, '.') + if firstDot == -1 { + return s + } + return s[:firstDot] + } + + // extract package + firstDot := strings.IndexByte(s[:lastSlash], '.') + if firstDot == -1 { + return s[:lastSlash] + } + return s[:lastSlash+firstDot] +} + func (logger *Logger) Log(level Level, args ...any) { entry := NewEntry(logger) entry.Log(level, args...)