-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathhelper-error.go
More file actions
124 lines (104 loc) · 3.03 KB
/
Copy pathhelper-error.go
File metadata and controls
124 lines (104 loc) · 3.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package helper
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"regexp"
"runtime"
"time"
)
var logEPrefixRE = regexp.MustCompile(`(?m)^[\s\n]*LogE:\s\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.\d{3}\s`)
func ErrAddLineTimeFileInfo(err error) error {
if err == nil {
return nil
}
if alreadyLogPrefixed(err) { // detect existing prefix anywhere in unwrap chain
return err
}
return fmt.Errorf("%s%w", logPrefix(0), err) // prefix once, preserve cause
}
func ErrNewAddLineTimeFileInfo(msg string) error {
return errors.New(logPrefix(0) + msg)
}
func addLineTimeFileInfo(msg string) string {
return logPrefix(0) + msg
}
// idempotent check that walks the unwrap chain for existing LogE prefix
func alreadyLogPrefixed(err error) (prefixed bool) {
if err == nil {
return false
}
// prevent panics from Error/Unwrap implementations from crashing the caller
defer func() {
if r := recover(); r != nil {
prefixed = false // on panic, allow caller to add prefix instead of skipping it
}
}()
type singleUnwrapper interface{ Unwrap() error }
type multiUnwrapper interface{ Unwrap() []error }
const maxWalk = 256 // cap traversal to prevent cycles from hanging
seenComparable := make(map[error]struct{})
seenPtr := make(map[uintptr]struct{}) // track non-comparable errors by pointer
stack := []error{err}
steps := 0
for len(stack) > 0 {
if steps++; steps > maxWalk { // fail-safe against pathological cycles
return false // treat exhaustion as not-prefixed so caller still annotates once
}
e := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if e == nil {
continue
}
// use reflect.Type.Comparable to avoid panic-based probing
if rt := reflect.TypeOf(e); rt != nil && rt.Comparable() {
if _, exists := seenComparable[e]; exists {
continue
}
seenComparable[e] = struct{}{}
} else { // dedupe non-comparable errors when pointer identity is available
v := reflect.ValueOf(e)
if v.Kind() == reflect.Pointer || v.Kind() == reflect.UnsafePointer {
if ptr := v.Pointer(); ptr != 0 {
if _, ok := seenPtr[ptr]; ok {
continue
}
seenPtr[ptr] = struct{}{}
}
}
}
msg := e.Error()
// anchored pattern to avoid false positives on incidental "LogE:" text
if logEPrefixRE.MatchString(msg) {
return true
}
if mw, ok := e.(multiUnwrapper); ok {
stack = append(stack, mw.Unwrap()...)
continue
}
if sw, ok := e.(singleUnwrapper); ok {
stack = append(stack, sw.Unwrap())
}
}
return false
}
// logPrefix builds the LogE prefix with caller/time info.
func logPrefix(skip int) string { // new helper for shared caller/time logic
_, file, line, ok := runtime.Caller(skip + 2) // skip to capture the immediate caller
if !ok {
file = "unknown"
line = 0
}
file = filepath.ToSlash(file)
base := filepath.Base(file)
dir := filepath.Base(filepath.Dir(file))
shortFile := base
if dir != "." && dir != "/" && dir != "" {
shortFile = dir + "/" + base
}
return fmt.Sprintf("\nLogE: %v %v:%v: ",
time.Now().UTC().Format("2006-01-02 15:04:05.000"),
shortFile,
line)
}