Commit e2880d3fb1868efdd79b800054a2a22ad0b43d45
1 parent
62a0d7a8a7
Exists in
master
integraded golloger
Showing
5 changed files
with
308 additions
and
5 deletions
Show diff stats
gologger/http_logs.go
... | ... | @@ -0,0 +1,66 @@ |
1 | +package gologger | |
2 | + | |
3 | +import ( | |
4 | + "bytes" | |
5 | + "fmt" | |
6 | + "net/http" | |
7 | + "net/http/httputil" | |
8 | + "os" | |
9 | + "strings" | |
10 | + "time" | |
11 | +) | |
12 | + | |
13 | +const splitLine = "==============================================================" | |
14 | + | |
15 | +// LogHTTPRequest ... | |
16 | +func (l *Logger) LogHTTPRequest(req *http.Request, userID string) string { | |
17 | + if userID == "" { | |
18 | + userID = "-" | |
19 | + } | |
20 | + | |
21 | + var b strings.Builder | |
22 | + | |
23 | + b.WriteString("Request:\n") | |
24 | + // CLF-like header | |
25 | + fmt.Fprintf(&b, "%s %s %s\n", req.RemoteAddr, userID, time.Now().Format(dateTimeFormat)) | |
26 | + | |
27 | + body, err := httputil.DumpRequest(req, true) | |
28 | + if err != nil { | |
29 | + fmt.Fprintf(os.Stderr, "%v\n", err) | |
30 | + } | |
31 | + | |
32 | + const sepStr = "\r\n\r\n" | |
33 | + sepIndex := bytes.Index(body, []byte(sepStr)) | |
34 | + if sepIndex == -1 { | |
35 | + b.WriteString(string(body) + "\n\n") | |
36 | + } else { | |
37 | + sepIndex += len(sepStr) | |
38 | + payload, _ := printJSON(body[sepIndex:]) | |
39 | + b.WriteString(string(body[:sepIndex]) + string(payload) + "\n\n") | |
40 | + } | |
41 | + | |
42 | + return b.String() | |
43 | +} | |
44 | + | |
45 | +// LogHTTPResponse ... | |
46 | +func (l *Logger) LogHTTPResponse(data []byte, status int, startTime time.Time) string { | |
47 | + duration := time.Now().Sub(startTime) | |
48 | + jsonData, _ := printJSON(data) | |
49 | + return fmt.Sprintf("Response:\n%d %v %dB\n%s\n%s\n\n", status, duration, len(data), jsonData, splitLine) | |
50 | +} | |
51 | + | |
52 | +// CombineHTTPLogs ... | |
53 | +func (l *Logger) CombineHTTPLogs(in string, out string) { | |
54 | + if l.outputFile == nil { | |
55 | + return | |
56 | + } | |
57 | + | |
58 | + l.mu.Lock() | |
59 | + defer l.mu.Unlock() | |
60 | + | |
61 | + msg := in + out | |
62 | + if l.shouldSplit(len(msg)) { | |
63 | + l.split() | |
64 | + } | |
65 | + l.outputFile.WriteString(msg) | |
66 | +} | ... | ... |
gologger/main.go
... | ... | @@ -0,0 +1,162 @@ |
1 | +package gologger | |
2 | + | |
3 | +import ( | |
4 | + "bytes" | |
5 | + "encoding/json" | |
6 | + "fmt" | |
7 | + "os" | |
8 | + "path/filepath" | |
9 | + "strings" | |
10 | + "sync" | |
11 | + "time" | |
12 | +) | |
13 | + | |
14 | +const dateTimeFormat = "2006-01-02 15:04:05" | |
15 | + | |
16 | +// Block ... | |
17 | +const ( | |
18 | + MaxLogSize5MB int64 = 5 * 1024 * 1024 | |
19 | + MaxLogSize1MB int64 = 1 * 1024 * 1024 | |
20 | + MaxLogSize500KB int64 = 500 * 1024 | |
21 | + MaxLogSize100KB int64 = 100 * 1024 | |
22 | + MaxLogSize512B int64 = 512 | |
23 | +) | |
24 | + | |
25 | +// Logger ... | |
26 | +type Logger struct { | |
27 | + mu *sync.Mutex | |
28 | + outputFile *os.File | |
29 | + | |
30 | + outputFileName string | |
31 | + fullName string | |
32 | + maxFileSize int64 | |
33 | + | |
34 | + splitCount int | |
35 | + | |
36 | + directory string | |
37 | +} | |
38 | + | |
39 | +// New ... | |
40 | +func New(name, dir string, maxFileSize int64) (logger *Logger, err error) { | |
41 | + logger = &Logger{} | |
42 | + | |
43 | + logger.outputFileName = name | |
44 | + logger.mu = &sync.Mutex{} | |
45 | + logger.maxFileSize = maxFileSize | |
46 | + logger.directory = dir | |
47 | + | |
48 | + err = os.Mkdir(dir, os.ModePerm) | |
49 | + if err != nil { | |
50 | + if !os.IsExist(err) { | |
51 | + return nil, err | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + date := strings.Replace(time.Now().Format(dateTimeFormat), ":", ".", -1) | |
56 | + logger.outputFileName += " " + date | |
57 | + logger.fullName = logger.outputFileName + ".txt" | |
58 | + path := filepath.Join(dir, logger.fullName) | |
59 | + if logger.outputFile, err = os.Create(path); err != nil { | |
60 | + return nil, err | |
61 | + } | |
62 | + | |
63 | + return logger, nil | |
64 | +} | |
65 | + | |
66 | +// Log ... | |
67 | +func (l *Logger) Log(format string, v ...interface{}) { | |
68 | + if l.outputFile == nil { | |
69 | + return | |
70 | + } | |
71 | + | |
72 | + l.mu.Lock() | |
73 | + defer l.mu.Unlock() | |
74 | + | |
75 | + msg := fmt.Sprintf(format, v...) | |
76 | + s := time.Now().Format(dateTimeFormat) + ": " + msg + "\n" | |
77 | + if l.shouldSplit(len(s)) { | |
78 | + l.split() | |
79 | + } | |
80 | + l.outputFile.WriteString(s) | |
81 | +} | |
82 | + | |
83 | +// Print ... | |
84 | +func (l *Logger) Print(format string, v ...interface{}) { | |
85 | + msg := fmt.Sprintf(format, v...) | |
86 | + fmt.Printf("%s: %s\n", time.Now().Format(dateTimeFormat), msg) | |
87 | +} | |
88 | + | |
89 | +// Trace ... | |
90 | +func (l *Logger) Trace(format string, v ...interface{}) string { | |
91 | + if l.outputFile == nil { | |
92 | + return "" | |
93 | + } | |
94 | + | |
95 | + l.mu.Lock() | |
96 | + defer l.mu.Unlock() | |
97 | + | |
98 | + s := getTrace(format, v...) + "\n" | |
99 | + | |
100 | + if l.shouldSplit(len(s)) { | |
101 | + l.split() | |
102 | + } | |
103 | + l.outputFile.WriteString(s) | |
104 | + | |
105 | + return s | |
106 | +} | |
107 | + | |
108 | +// PrintTrace ... | |
109 | +func (l *Logger) PrintTrace(format string, v ...interface{}) { | |
110 | + s := getTrace(format, v...) | |
111 | + fmt.Printf("%s\n", s) | |
112 | +} | |
113 | + | |
114 | +// PrintAndTrace ... | |
115 | +func (l *Logger) PrintAndTrace(format string, v ...interface{}) { | |
116 | + t := l.Trace(format, v...) | |
117 | + fmt.Println(t) | |
118 | +} | |
119 | + | |
120 | +// Close ... | |
121 | +func (l *Logger) Close() error { | |
122 | + if l.outputFile == nil { | |
123 | + return nil | |
124 | + } | |
125 | + | |
126 | + return l.outputFile.Close() | |
127 | +} | |
128 | + | |
129 | +func (l *Logger) split() error { | |
130 | + if l.outputFile == nil { | |
131 | + return nil | |
132 | + } | |
133 | + | |
134 | + // close old file | |
135 | + err := l.outputFile.Close() | |
136 | + if err != nil { | |
137 | + return err | |
138 | + } | |
139 | + | |
140 | + // open new file | |
141 | + l.splitCount++ | |
142 | + path := filepath.Join(l.directory, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt") | |
143 | + l.outputFile, err = os.Create(path) | |
144 | + | |
145 | + return err | |
146 | +} | |
147 | + | |
148 | +func (l *Logger) shouldSplit(nextEntrySize int) bool { | |
149 | + stats, _ := l.outputFile.Stat() | |
150 | + return int64(nextEntrySize) >= (l.maxFileSize - stats.Size()) | |
151 | +} | |
152 | + | |
153 | +func printJSON(in []byte) (out []byte, err error) { | |
154 | + var buf bytes.Buffer | |
155 | + err = json.Indent(&buf, in, "", " ") | |
156 | + return buf.Bytes(), err | |
157 | +} | |
158 | + | |
159 | +// GetOutDir ... | |
160 | +func (l *Logger) GetOutDir() string { | |
161 | + return l.directory | |
162 | +} | ... | ... |
gologger/tracing.go
... | ... | @@ -0,0 +1,65 @@ |
1 | +package gologger | |
2 | + | |
3 | +import ( | |
4 | + "fmt" | |
5 | + "math" | |
6 | + "runtime" | |
7 | + "strings" | |
8 | + "time" | |
9 | +) | |
10 | + | |
11 | +// getCallStack | |
12 | +func getCallStack() (stack []string) { | |
13 | + const ( | |
14 | + maxStackDepth = 10000 | |
15 | + skipRuntime = 2 // skip runtime and startup | |
16 | + skipCallers = 2 | |
17 | + thisPackage = "gologger" | |
18 | + ) | |
19 | + | |
20 | + callers := make([]uintptr, maxStackDepth) // min 1 | |
21 | + | |
22 | + stackDepth := runtime.Callers(skipCallers, callers) | |
23 | + frames := runtime.CallersFrames(callers) | |
24 | + | |
25 | + for i := 0; i < skipRuntime; i++ { | |
26 | + frames.Next() | |
27 | + } | |
28 | + | |
29 | + for i := 0; i < stackDepth-skipRuntime-skipCallers; i++ { | |
30 | + frame, next := frames.Next() | |
31 | + if !strings.Contains(frame.File, thisPackage) { | |
32 | + stack = append(stack, fmt.Sprintf("[%s %d]", frame.File, frame.Line)) | |
33 | + } | |
34 | + if !next { | |
35 | + break | |
36 | + } | |
37 | + } | |
38 | + | |
39 | + reverseStack(stack) | |
40 | + | |
41 | + return stack | |
42 | +} | |
43 | + | |
44 | +// in place | |
45 | +func reverseStack(stack []string) { | |
46 | + middle := int(math.Floor(float64(len(stack)) / 2.0)) | |
47 | + | |
48 | + lastIndex := len(stack) - 1 | |
49 | + for i := 0; i < middle; i++ { | |
50 | + stack[i], stack[lastIndex] = stack[lastIndex], stack[i] | |
51 | + lastIndex-- | |
52 | + } | |
53 | +} | |
54 | + | |
55 | +func getTrace(format string, v ...interface{}) (t string) { | |
56 | + stack := getCallStack() | |
57 | + | |
58 | + t = time.Now().Format(dateTimeFormat) + ":\n" | |
59 | + for _, s := range stack { | |
60 | + t += s + "\n" | |
61 | + } | |
62 | + t += fmt.Sprintf(format, v...) + "\n" | |
63 | + | |
64 | + return t | |
65 | +} | ... | ... |
http.go
... | ... | @@ -11,6 +11,7 @@ type StatusRecorder struct { |
11 | 11 | writer http.ResponseWriter |
12 | 12 | status int |
13 | 13 | size int |
14 | + data []byte | |
14 | 15 | } |
15 | 16 | |
16 | 17 | // NewStatusRecorder ... |
... | ... | @@ -19,6 +20,7 @@ func NewStatusRecorder(w http.ResponseWriter) *StatusRecorder { |
19 | 20 | writer: w, |
20 | 21 | status: 0, |
21 | 22 | size: 0, |
23 | + data: nil, | |
22 | 24 | } |
23 | 25 | } |
24 | 26 | |
... | ... | @@ -31,6 +33,10 @@ func (r *StatusRecorder) WriteHeader(code int) { |
31 | 33 | // Write is a wrapper for http.ResponseWriter interface |
32 | 34 | func (r *StatusRecorder) Write(in []byte) (int, error) { |
33 | 35 | r.size = len(in) |
36 | + if r.status >= 400 { | |
37 | + r.data = make([]byte, len(in)) | |
38 | + copy(r.data, in) | |
39 | + } | |
34 | 40 | return r.writer.Write(in) |
35 | 41 | } |
36 | 42 | |
... | ... | @@ -49,6 +55,11 @@ func (r *StatusRecorder) Size() int { |
49 | 55 | return r.size |
50 | 56 | } |
51 | 57 | |
58 | +// Size ... | |
59 | +func (r *StatusRecorder) Data() []byte { | |
60 | + return r.data | |
61 | +} | |
62 | + | |
52 | 63 | // NotFoundHandlerFunc writes HTTP error 404 to w. |
53 | 64 | func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { |
54 | 65 | SetAccessControlHeaders(w) | ... | ... |
middleware/middleware.go
... | ... | @@ -73,10 +73,10 @@ func CloseLogger() { |
73 | 73 | } |
74 | 74 | |
75 | 75 | // LogHTTP ... |
76 | -func LogHTTP(h http.HandlerFunc) http.HandlerFunc { | |
76 | +func LogHTTP(hfunc http.HandlerFunc) http.HandlerFunc { | |
77 | 77 | return func(w http.ResponseWriter, req *http.Request) { |
78 | 78 | if httpLogger == nil { |
79 | - h(w, req) | |
79 | + hfunc(w, req) | |
80 | 80 | return |
81 | 81 | } |
82 | 82 | |
... | ... | @@ -87,10 +87,9 @@ func LogHTTP(h http.HandlerFunc) http.HandlerFunc { |
87 | 87 | |
88 | 88 | rec := web.NewStatusRecorder(w) |
89 | 89 | |
90 | - h(rec, req) | |
90 | + hfunc(rec, req) | |
91 | 91 | |
92 | - t2 := time.Now() | |
93 | - out := httpLogger.LogHTTPResponse(rec.Status(), t2.Sub(t1), rec.Size()) | |
92 | + out := httpLogger.LogHTTPResponse(rec.Data(), rec.Status(), t1) | |
94 | 93 | |
95 | 94 | httpLogger.CombineHTTPLogs(in, out) |
96 | 95 | } | ... | ... |