Compare View
Commits (2)
Showing
7 changed files
Show diff stats
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) | ... | ... |
logger/http_logs.go
... | ... | @@ -0,0 +1,66 @@ |
1 | +package logger | |
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 | +} | ... | ... |
logger/main.go
... | ... | @@ -0,0 +1,162 @@ |
1 | +package logger | |
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 | +// GetOutDir ... | |
130 | +func (l *Logger) GetOutDir() string { | |
131 | + return l.directory | |
132 | +} | |
133 | + | |
134 | +func (l *Logger) split() error { | |
135 | + if l.outputFile == nil { | |
136 | + return nil | |
137 | + } | |
138 | + | |
139 | + // close old file | |
140 | + err := l.outputFile.Close() | |
141 | + if err != nil { | |
142 | + return err | |
143 | + } | |
144 | + | |
145 | + // open new file | |
146 | + l.splitCount++ | |
147 | + path := filepath.Join(l.directory, l.outputFileName+fmt.Sprintf("(%d)", l.splitCount)+".txt") | |
148 | + l.outputFile, err = os.Create(path) | |
149 | + | |
150 | + return err | |
151 | +} | |
152 | + | |
153 | +func (l *Logger) shouldSplit(nextEntrySize int) bool { | |
154 | + stats, _ := l.outputFile.Stat() | |
155 | + return int64(nextEntrySize) >= (l.maxFileSize - stats.Size()) | |
156 | +} | |
157 | + | |
158 | +func printJSON(in []byte) (out []byte, err error) { | |
159 | + var buf bytes.Buffer | |
160 | + err = json.Indent(&buf, in, "", " ") | |
161 | + return buf.Bytes(), err | |
162 | +} | ... | ... |
logger/tracing.go
... | ... | @@ -0,0 +1,65 @@ |
1 | +package logger | |
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 = "logger" | |
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 | +} | ... | ... |
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 | } | ... | ... |
quicksort.go
1 | 1 | package webutility |
2 | 2 | |
3 | -type QSortDirection int | |
3 | +import "sort" | |
4 | 4 | |
5 | -const ( | |
6 | - QSortAscending QSortDirection = iota | |
7 | - QSortDescending | |
8 | -) | |
9 | - | |
10 | -// QuickSortable is an interface for quicksorting slices. | |
11 | -type QuickSortable interface { | |
12 | - Swap(i, j int) | |
13 | - Compare(i, j int) int | |
14 | - Len() int | |
15 | -} | |
16 | - | |
17 | -// Quicksort quicksorts que. | |
18 | -func Quicksort(que QuickSortable, low, high int, dir QSortDirection) { | |
5 | +// QuickSort quicksorts que. | |
6 | +func QuickSort(que sort.Interface, low, high int, reverse bool) { | |
19 | 7 | if low >= high { |
20 | 8 | return |
21 | 9 | } |
22 | 10 | |
23 | - if dir != QSortAscending && dir != QSortDescending { | |
24 | - return | |
25 | - } | |
26 | - | |
27 | - index := partition(que, low, high, dir) | |
28 | - Quicksort(que, low, index-1, dir) | |
29 | - Quicksort(que, index+1, high, dir) | |
11 | + index := quickSortPartition(que, low, high, reverse) | |
12 | + QuickSort(que, low, index-1, reverse) | |
13 | + QuickSort(que, index+1, high, reverse) | |
30 | 14 | } |
31 | 15 | |
32 | -func partition(que QuickSortable, low, high int, dir QSortDirection) int { | |
33 | - swap := 0 | |
34 | - // -1 -> i > j | |
35 | - // 1 -> i < j | |
36 | - if dir == QSortDescending { | |
37 | - swap = -1 | |
38 | - } else { | |
39 | - swap = 1 | |
40 | - } | |
41 | - | |
16 | +func quickSortPartition(que sort.Interface, low, high int, reverse bool) int { | |
42 | 17 | i := low - 1 |
43 | 18 | for j := low; j <= high-1; j++ { |
44 | - if que.Compare(j, high) == swap { | |
45 | - i++ | |
46 | - que.Swap(i, j) | |
19 | + if !reverse { | |
20 | + if que.Less(j, high) { | |
21 | + i++ | |
22 | + que.Swap(i, j) | |
23 | + } | |
24 | + } else { | |
25 | + if !que.Less(j, high) { | |
26 | + i++ | |
27 | + que.Swap(i, j) | |
28 | + } | |
47 | 29 | } |
48 | - continue | |
49 | 30 | } |
50 | 31 | que.Swap(i+1, high) |
51 | 32 | return i + 1 | ... | ... |
server.go
... | ... | @@ -5,14 +5,14 @@ import ( |
5 | 5 | "fmt" |
6 | 6 | "net/http" |
7 | 7 | |
8 | - "git.to-net.rs/marko.tikvic/gologger" | |
8 | + "git.to-net.rs/marko.tikvic/webutility/logger" | |
9 | 9 | "github.com/gorilla/mux" |
10 | 10 | ) |
11 | 11 | |
12 | 12 | type Server struct { |
13 | 13 | DB *sql.DB |
14 | 14 | Router *mux.Router |
15 | - Logger *gologger.Logger | |
15 | + Logger *logger.Logger | |
16 | 16 | Port string |
17 | 17 | DBs map[string]*sql.DB |
18 | 18 | dsn map[string]string |
... | ... | @@ -29,7 +29,7 @@ func NewODBCServer(dsn, port, logDir string) (s *Server, err error) { |
29 | 29 | |
30 | 30 | s.Router = mux.NewRouter() |
31 | 31 | |
32 | - if s.Logger, err = gologger.New("err", logDir, gologger.MaxLogSize1MB); err != nil { | |
32 | + if s.Logger, err = logger.New("err", logDir, logger.MaxLogSize1MB); err != nil { | |
33 | 33 | return nil, fmt.Errorf("can't create logger: %s", err.Error()) |
34 | 34 | } |
35 | 35 | ... | ... |