Commit 7dab6e0054d7ea9c7e325445b0bdde90571d4038

Authored by Marko Tikvić
1 parent 8d63648a35
Exists in master

call stack for error tracing

Showing 1 changed file with 23 additions and 10 deletions   Show diff stats
1 package gologger 1 package gologger
2 2
3 import ( 3 import (
4 "bytes" 4 "bytes"
5 "encoding/json" 5 "encoding/json"
6 "fmt" 6 "fmt"
7 "net/http" 7 "net/http"
8 "net/http/httputil" 8 "net/http/httputil"
9 "os" 9 "os"
10 "path/filepath" 10 "path/filepath"
11 "runtime" 11 "runtime"
12 "strings" 12 "strings"
13 "sync" 13 "sync"
14 "time" 14 "time"
15 ) 15 )
16 16
17 const dateTimeFormat = "2006-01-02 15:04:05" 17 const dateTimeFormat = "2006-01-02 15:04:05"
18 18
19 // Block ... 19 // Block ...
20 const ( 20 const (
21 MaxLogSize5MB int64 = 5 * 1024 * 1024 21 MaxLogSize5MB int64 = 5 * 1024 * 1024
22 MaxLogSize1MB int64 = 1 * 1024 * 1024 22 MaxLogSize1MB int64 = 1 * 1024 * 1024
23 MaxLogSize500KB int64 = 500 * 1024 23 MaxLogSize500KB int64 = 500 * 1024
24 MaxLogSize100KB int64 = 100 * 1024 24 MaxLogSize100KB int64 = 100 * 1024
25 MaxLogSize512B int64 = 512 25 MaxLogSize512B int64 = 512
26 ) 26 )
27 27
28 // Logger ... 28 // Logger ...
29 type Logger struct { 29 type Logger struct {
30 mu *sync.Mutex 30 mu *sync.Mutex
31 outputFile *os.File 31 outputFile *os.File
32 32
33 outputFileName string 33 outputFileName string
34 fullName string 34 fullName string
35 maxFileSize int64 35 maxFileSize int64
36 36
37 splitCount int 37 splitCount int
38 38
39 directory string 39 directory string
40 } 40 }
41 41
42 // New ... 42 // New ...
43 func New(name, dir string, maxFileSize int64) (logger *Logger, err error) { 43 func New(name, dir string, maxFileSize int64) (logger *Logger, err error) {
44 logger = &Logger{} 44 logger = &Logger{}
45 45
46 logger.outputFileName = name 46 logger.outputFileName = name
47 logger.mu = &sync.Mutex{} 47 logger.mu = &sync.Mutex{}
48 logger.maxFileSize = maxFileSize 48 logger.maxFileSize = maxFileSize
49 logger.directory = dir 49 logger.directory = dir
50 50
51 err = os.Mkdir(dir, os.ModePerm) 51 err = os.Mkdir(dir, os.ModePerm)
52 if err != nil { 52 if err != nil {
53 if !os.IsExist(err) { 53 if !os.IsExist(err) {
54 return nil, err 54 return nil, err
55 } 55 }
56 } 56 }
57 57
58 date := strings.Replace(time.Now().Format(dateTimeFormat), ":", ".", -1) 58 date := strings.Replace(time.Now().Format(dateTimeFormat), ":", ".", -1)
59 logger.outputFileName += " " + date 59 logger.outputFileName += " " + date
60 logger.fullName = logger.outputFileName + ".txt" 60 logger.fullName = logger.outputFileName + ".txt"
61 path := filepath.Join(dir, logger.fullName) 61 path := filepath.Join(dir, logger.fullName)
62 if logger.outputFile, err = os.Create(path); err != nil { 62 if logger.outputFile, err = os.Create(path); err != nil {
63 return nil, err 63 return nil, err
64 } 64 }
65 65
66 return logger, nil 66 return logger, nil
67 } 67 }
68 68
69 // Log ... 69 // Log ...
70 func (l *Logger) Log(format string, v ...interface{}) { 70 func (l *Logger) Log(format string, v ...interface{}) {
71 if l.outputFile == nil { 71 if l.outputFile == nil {
72 return 72 return
73 } 73 }
74 74
75 l.mu.Lock() 75 l.mu.Lock()
76 defer l.mu.Unlock() 76 defer l.mu.Unlock()
77 77
78 msg := fmt.Sprintf(format, v...) 78 msg := fmt.Sprintf(format, v...)
79 s := time.Now().Format(dateTimeFormat) + ": " + msg + "\n" 79 s := time.Now().Format(dateTimeFormat) + ": " + msg + "\n"
80 if l.shouldSplit(len(s)) { 80 if l.shouldSplit(len(s)) {
81 l.split() 81 l.split()
82 } 82 }
83 l.outputFile.WriteString(s) 83 l.outputFile.WriteString(s)
84 } 84 }
85 85
86 // Print ... 86 // Print ...
87 func (l *Logger) Print(format string, v ...interface{}) { 87 func (l *Logger) Print(format string, v ...interface{}) {
88 msg := fmt.Sprintf(format, v...) 88 msg := fmt.Sprintf(format, v...)
89 fmt.Printf("%s: %s\n", time.Now().Format(dateTimeFormat), msg) 89 fmt.Printf("%s: %s\n", time.Now().Format(dateTimeFormat), msg)
90 } 90 }
91 91
92 // CallerFilenameAndLineNumber ... 92 const MaxStackDepth = 10000
93 func CallerFilenameAndLineNumber() (string, int) { 93
94 _, path, line, _ := runtime.Caller(2) 94 // CallStack
95 file := filepath.Base(path) 95 func CallStack() (stack string) {
96 return file, line 96 callers := make([]uintptr, MaxStackDepth) // min 1
97 stackDepth := runtime.Callers(2, callers)
98 frames := runtime.CallersFrames(callers)
99
100 for i := 0; i < stackDepth; i++ {
101 if i >= 2 { // skip runtime and startup
102 frame, next := frames.Next()
103 stack += fmt.Sprintf("[%s %d]\n", frame.File, frame.Line)
104 if !next {
105 break
106 }
107 }
108 }
109
110 return
97 } 111 }
98 112
99 // PrintTrace ... 113 // PrintTrace ...
100 func (l *Logger) PrintTrace(format string, v ...interface{}) { 114 func (l *Logger) PrintTrace(format string, v ...interface{}) {
101 file, line := CallerFilenameAndLineNumber() 115 stack := CallStack()
102
103 msg := fmt.Sprintf(format, v...) 116 msg := fmt.Sprintf(format, v...)
104 fmt.Printf("%s: %s %d: %s\n", time.Now().Format(dateTimeFormat), file, line, msg) 117 fmt.Printf("%s:\n%s\n%s\n", time.Now().Format(dateTimeFormat), stack, msg)
105 } 118 }
106 119
107 // Trace ... 120 // Trace ...
108 func (l *Logger) Trace(format string, v ...interface{}) { 121 func (l *Logger) Trace(format string, v ...interface{}) {
109 if l.outputFile == nil { 122 if l.outputFile == nil {
110 return 123 return
111 } 124 }
112 125
113 l.mu.Lock() 126 l.mu.Lock()
114 defer l.mu.Unlock() 127 defer l.mu.Unlock()
115 128
116 file, line := CallerFilenameAndLineNumber() 129 stack := CallStack()
117 130
118 msg := fmt.Sprintf(format, v...) 131 msg := fmt.Sprintf(format, v...)
119 s := fmt.Sprintf("%s: %s %d: %s\n", time.Now().Format(dateTimeFormat), file, line, msg) 132 s := fmt.Sprintf("%s:\n%s\n%s\n", time.Now().Format(dateTimeFormat), stack, msg)
120 133
121 if l.shouldSplit(len(s)) { 134 if l.shouldSplit(len(s)) {
122 l.split() 135 l.split()
123 } 136 }
124 l.outputFile.WriteString(s) 137 l.outputFile.WriteString(s)
125 } 138 }
126 139
127 // PrintAndTrace ... 140 // PrintAndTrace ...
128 func (l *Logger) PrintAndTrace(format string, v ...interface{}) { 141 func (l *Logger) PrintAndTrace(format string, v ...interface{}) {
129 l.Print(format, v...) 142 l.Print(format, v...)
130 l.Trace(format, v...) 143 l.Trace(format, v...)
131 } 144 }
132 145
133 // LogHTTPRequest ... 146 // LogHTTPRequest ...
134 func (l *Logger) LogHTTPRequest(req *http.Request, userID string) string { 147 func (l *Logger) LogHTTPRequest(req *http.Request, userID string) string {
135 if userID == "" { 148 if userID == "" {
136 userID = "-" 149 userID = "-"
137 } 150 }
138 151
139 var b strings.Builder 152 var b strings.Builder
140 153
141 b.WriteString("Request:\n") 154 b.WriteString("Request:\n")
142 // CLF-like header 155 // CLF-like header
143 fmt.Fprintf(&b, "%s %s %s\n", req.RemoteAddr, userID, time.Now().Format(dateTimeFormat)) 156 fmt.Fprintf(&b, "%s %s %s\n", req.RemoteAddr, userID, time.Now().Format(dateTimeFormat))
144 157
145 body, err := httputil.DumpRequest(req, true) 158 body, err := httputil.DumpRequest(req, true)
146 if err != nil { 159 if err != nil {
147 fmt.Fprintf(os.Stderr, "%v\n", err) 160 fmt.Fprintf(os.Stderr, "%v\n", err)
148 } 161 }
149 162
150 const sepStr = "\r\n\r\n" 163 const sepStr = "\r\n\r\n"
151 sepIndex := bytes.Index(body, []byte(sepStr)) 164 sepIndex := bytes.Index(body, []byte(sepStr))
152 if sepIndex == -1 { 165 if sepIndex == -1 {
153 b.WriteString(string(body) + "\n\n") 166 b.WriteString(string(body) + "\n\n")
154 } else { 167 } else {
155 sepIndex += len(sepStr) 168 sepIndex += len(sepStr)
156 payload, _ := printJSON(body[sepIndex:]) 169 payload, _ := printJSON(body[sepIndex:])
157 b.WriteString(string(body[:sepIndex]) + string(payload) + "\n\n") 170 b.WriteString(string(body[:sepIndex]) + string(payload) + "\n\n")
158 } 171 }
159 172
160 return b.String() 173 return b.String()
161 } 174 }
162 175
163 const splitLine = "==============================================================" 176 const splitLine = "=============================================================="
164 177
165 // LogHTTPResponse ... 178 // LogHTTPResponse ...
166 func (l *Logger) LogHTTPResponse(status int, duration time.Duration, size int) string { 179 func (l *Logger) LogHTTPResponse(status int, duration time.Duration, size int) string {
167 return fmt.Sprintf("Response:\n%d %v %dB\n%s\n", status, duration, size, splitLine) 180 return fmt.Sprintf("Response:\n%d %v %dB\n%s\n", status, duration, size, splitLine)
168 } 181 }
169 182
170 // CombineHTTPLogs ... 183 // CombineHTTPLogs ...
171 func (l *Logger) CombineHTTPLogs(in string, out string) { 184 func (l *Logger) CombineHTTPLogs(in string, out string) {
172 if l.outputFile == nil { 185 if l.outputFile == nil {
173 return 186 return
174 } 187 }
175 188
176 l.mu.Lock() 189 l.mu.Lock()
177 defer l.mu.Unlock() 190 defer l.mu.Unlock()
178 191
179 msg := in + out 192 msg := in + out
180 if l.shouldSplit(len(msg)) { 193 if l.shouldSplit(len(msg)) {
181 l.split() 194 l.split()
182 } 195 }
183 l.outputFile.WriteString(msg) 196 l.outputFile.WriteString(msg)
184 } 197 }
185 198
186 // Close ... 199 // Close ...
187 func (l *Logger) Close() error { 200 func (l *Logger) Close() error {
188 if l.outputFile == nil { 201 if l.outputFile == nil {
189 return nil 202 return nil
190 } 203 }
191 204
192 return l.outputFile.Close() 205 return l.outputFile.Close()
193 } 206 }
194 207
195 func (l *Logger) split() error { 208 func (l *Logger) split() error {
196 if l.outputFile == nil { 209 if l.outputFile == nil {
197 return nil 210 return nil
198 } 211 }
199 212
200 // close old file 213 // close old file
201 err := l.outputFile.Close() 214 err := l.outputFile.Close()
202 if err != nil { 215 if err != nil {
203 return err 216 return err
204 } 217 }
205 218
206 // open new file 219 // open new file
207 l.splitCount++ 220 l.splitCount++
208 path := filepath.Join(l.directory, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt") 221 path := filepath.Join(l.directory, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt")
209 l.outputFile, err = os.Create(path) 222 l.outputFile, err = os.Create(path)
210 223
211 return err 224 return err
212 } 225 }
213 226
214 func (l *Logger) shouldSplit(nextEntrySize int) bool { 227 func (l *Logger) shouldSplit(nextEntrySize int) bool {
215 stats, _ := l.outputFile.Stat() 228 stats, _ := l.outputFile.Stat()
216 return int64(nextEntrySize) >= (l.maxFileSize - stats.Size()) 229 return int64(nextEntrySize) >= (l.maxFileSize - stats.Size())
217 } 230 }
218 231
219 func printJSON(in []byte) (out []byte, err error) { 232 func printJSON(in []byte) (out []byte, err error) {
220 var buf bytes.Buffer 233 var buf bytes.Buffer
221 err = json.Indent(&buf, in, "", " ") 234 err = json.Indent(&buf, in, "", " ")
222 return buf.Bytes(), err 235 return buf.Bytes(), err
223 } 236 }