Commit 28a6cc60c98e9c7f1e88ab4b376fb993c39b1d82

Authored by Marko Tikvić
1 parent e2880d3fb1
Exists in master

Integrated logger and improved QuickSort

gologger/http_logs.go
1 package gologger File was deleted
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 }
67 1 package gologger
gologger/main.go
1 package gologger File was deleted
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 }
163 1 package gologger
gologger/tracing.go
1 package gologger File was deleted
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 }
66 1 package gologger
logger/http_logs.go
File was created 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 }
67
File was created 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 }
163
logger/tracing.go
File was created 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 }
66
1 package webutility 1 package webutility
2 2
3 type QSortDirection int 3 import "sort"
4 4
5 const ( 5 // QuickSort quicksorts que.
6 QSortAscending QSortDirection = iota 6 func QuickSort(que sort.Interface, low, high int, reverse bool) {
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) {
19 if low >= high { 7 if low >= high {
20 return 8 return
21 } 9 }
22 10
23 if dir != QSortAscending && dir != QSortDescending { 11 index := quickSortPartition(que, low, high, reverse)
24 return 12 QuickSort(que, low, index-1, reverse)
25 } 13 QuickSort(que, index+1, high, reverse)
26
27 index := partition(que, low, high, dir)
28 Quicksort(que, low, index-1, dir)
29 Quicksort(que, index+1, high, dir)
30 } 14 }
31 15
32 func partition(que QuickSortable, low, high int, dir QSortDirection) int { 16 func quickSortPartition(que sort.Interface, low, high int, reverse bool) 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
42 i := low - 1 17 i := low - 1
43 for j := low; j <= high-1; j++ { 18 for j := low; j <= high-1; j++ {
44 if que.Compare(j, high) == swap { 19 if !reverse {
45 i++ 20 if que.Less(j, high) {
46 que.Swap(i, j) 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 que.Swap(i+1, high) 31 que.Swap(i+1, high)
51 return i + 1 32 return i + 1
52 } 33 }
53 34
54 func BubbleSort(arr []int64) { 35 func BubbleSort(arr []int64) {
55 for i := 0; i < len(arr)-1; i++ { 36 for i := 0; i < len(arr)-1; i++ {
56 for j := i; j < len(arr); j++ { 37 for j := i; j < len(arr); j++ {
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "fmt" 5 "fmt"
6 "net/http" 6 "net/http"
7 7
8 "git.to-net.rs/marko.tikvic/gologger" 8 "git.to-net.rs/marko.tikvic/webutility/logger"
9 "github.com/gorilla/mux" 9 "github.com/gorilla/mux"
10 ) 10 )
11 11
12 type Server struct { 12 type Server struct {
13 DB *sql.DB 13 DB *sql.DB
14 Router *mux.Router 14 Router *mux.Router
15 Logger *gologger.Logger 15 Logger *logger.Logger
16 Port string 16 Port string
17 DBs map[string]*sql.DB 17 DBs map[string]*sql.DB
18 dsn map[string]string 18 dsn map[string]string
19 } 19 }
20 20
21 func NewODBCServer(dsn, port, logDir string) (s *Server, err error) { 21 func NewODBCServer(dsn, port, logDir string) (s *Server, err error) {
22 s = new(Server) 22 s = new(Server)
23 23
24 s.Port = port 24 s.Port = port
25 25
26 if s.DB, err = sql.Open("odbc", fmt.Sprintf("DSN=%s;", dsn)); err != nil { 26 if s.DB, err = sql.Open("odbc", fmt.Sprintf("DSN=%s;", dsn)); err != nil {
27 return nil, err 27 return nil, err
28 } 28 }
29 29
30 s.Router = mux.NewRouter() 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 return nil, fmt.Errorf("can't create logger: %s", err.Error()) 33 return nil, fmt.Errorf("can't create logger: %s", err.Error())
34 } 34 }
35 35
36 s.DBs = make(map[string]*sql.DB) 36 s.DBs = make(map[string]*sql.DB)
37 s.DBs["default"] = s.DB 37 s.DBs["default"] = s.DB
38 38
39 s.dsn = make(map[string]string) 39 s.dsn = make(map[string]string)
40 s.dsn["default"] = dsn 40 s.dsn["default"] = dsn
41 41
42 return s, nil 42 return s, nil
43 } 43 }
44 44
45 func (s *Server) Run() { 45 func (s *Server) Run() {
46 s.Logger.Print("Server listening on %s", s.Port) 46 s.Logger.Print("Server listening on %s", s.Port)
47 s.Logger.PrintAndTrace(http.ListenAndServe(s.Port, s.Router).Error()) 47 s.Logger.PrintAndTrace(http.ListenAndServe(s.Port, s.Router).Error())
48 } 48 }
49 49
50 func (s *Server) Cleanup() { 50 func (s *Server) Cleanup() {
51 if s.DB != nil { 51 if s.DB != nil {
52 s.DB.Close() 52 s.DB.Close()
53 } 53 }
54 54
55 if s.Logger != nil { 55 if s.Logger != nil {
56 s.Logger.Close() 56 s.Logger.Close()
57 } 57 }
58 } 58 }
59 59
60 func (s *Server) StartTransaction() (*sql.Tx, error) { 60 func (s *Server) StartTransaction() (*sql.Tx, error) {
61 return s.DB.Begin() 61 return s.DB.Begin()
62 } 62 }
63 63
64 func CommitChanges(tx *sql.Tx, err *error, opt ...error) { 64 func CommitChanges(tx *sql.Tx, err *error, opt ...error) {
65 if *err != nil { 65 if *err != nil {
66 tx.Rollback() 66 tx.Rollback()
67 return 67 return
68 } 68 }
69 69
70 for _, e := range opt { 70 for _, e := range opt {
71 if e != nil { 71 if e != nil {
72 tx.Rollback() 72 tx.Rollback()
73 return 73 return
74 } 74 }
75 } 75 }
76 76
77 if *err = tx.Commit(); *err != nil { 77 if *err = tx.Commit(); *err != nil {
78 tx.Rollback() 78 tx.Rollback()
79 } 79 }
80 } 80 }
81 81