Browse Source

Initial commit

tags/v1.0.0
Nise Void 1 year ago
commit
3cbefefec5
Signed by: NiseVoid <nisevoid@gmail.com> GPG Key ID: FBA14AC83EA602F3
5 changed files with 279 additions and 0 deletions
  1. 26
    0
      formatter.go
  2. 93
    0
      log.go
  3. 92
    0
      log_test.go
  4. 23
    0
      settings.go
  5. 45
    0
      type.go

+ 26
- 0
formatter.go View File

@@ -0,0 +1,26 @@
1
+package flog
2
+
3
+import (
4
+	"fmt"
5
+	"io"
6
+	"strconv"
7
+	"text/tabwriter"
8
+)
9
+
10
+// TextFormatter logs in a human readable format
11
+type TextFormatter struct{}
12
+
13
+// FormatMessage implements LogFormatType
14
+func (l TextFormatter) FormatMessage(w io.Writer, entry LogEntry) {
15
+	fmt.Fprintln(w, entry.Level, entry.Time.Format(`[2006-01-02 15:04:05]`), entry.Message)
16
+
17
+	if len(entry.StackTrace) > 0 {
18
+		tw := tabwriter.NewWriter(w, 1, 4, 4, ' ', 0)
19
+		for _, v := range entry.StackTrace {
20
+			fmt.Fprint(tw, `    `, v.Function, "\t", v.File+`:`+strconv.Itoa(v.Line), "\n")
21
+		}
22
+		tw.Flush()
23
+	}
24
+
25
+	w.Write([]byte{'\n'})
26
+}

+ 93
- 0
log.go View File

@@ -0,0 +1,93 @@
1
+package flog
2
+
3
+import (
4
+	"fmt"
5
+	"runtime"
6
+	"strings"
7
+	"sync"
8
+	"time"
9
+)
10
+
11
+// All log functions
12
+var (
13
+	Debug    = logFunc(LevelDebug)
14
+	Info     = logFunc(LevelInfo)
15
+	Warning  = logFunc(LevelWarning)
16
+	Error    = logFunc(LevelError)
17
+	Critical = logFunc(LevelCritical)
18
+)
19
+
20
+func logFunc(level LevelType) func(...interface{}) {
21
+	return func(message ...interface{}) {
22
+		writeMessage(level, message)
23
+	}
24
+}
25
+
26
+var mu sync.Mutex
27
+
28
+func writeMessage(level LevelType, message []interface{}) {
29
+	if MinLevel > level {
30
+		return
31
+	}
32
+
33
+	entry := LogEntry{
34
+		Level:   level,
35
+		Time:    time.Now(),
36
+		Message: fmt.Sprint(message...),
37
+	}
38
+
39
+	if MinStackLevel <= level {
40
+		entry.StackTrace = getStackTrace()
41
+	}
42
+
43
+	mu.Lock()
44
+	defer mu.Unlock()
45
+
46
+	Format.FormatMessage(getOutput(), entry)
47
+}
48
+
49
+func getStackTrace() (stackTrace []StackTraceEntry) {
50
+	_, filename, _, _ := runtime.Caller(1)
51
+
52
+	pc := make([]uintptr, 50)
53
+	entries := runtime.Callers(2, pc)
54
+	frames := runtime.CallersFrames(pc[:entries])
55
+
56
+	more, firstLine := true, false
57
+	for more {
58
+		var frame runtime.Frame
59
+		frame, more = frames.Next()
60
+
61
+		if !firstLine && frame.File == filename { // Skip frames from the flog cal
62
+			continue
63
+		}
64
+		firstLine = true
65
+
66
+		if frame.Function == `runtime.gopanic` { // If a panic occurred, start at the frame that called panic
67
+			stackTrace = nil
68
+			continue
69
+		}
70
+
71
+		stackTrace = append(stackTrace, StackTraceEntry{
72
+			Function: cleanFunction(frame.Function),
73
+			File:     cleanFilename(frame.File),
74
+			Line:     frame.Line,
75
+		})
76
+	}
77
+
78
+	return
79
+}
80
+
81
+func cleanFunction(f string) string {
82
+	parts := strings.Split(f, `/vendor/`)
83
+	return parts[len(parts)-1]
84
+}
85
+
86
+func cleanFilename(file string) string {
87
+	parts := strings.Split(file, `/src/`)
88
+	if len(parts) < 2 {
89
+		return file
90
+	}
91
+
92
+	return strings.Join(parts[1:], `/`)
93
+}

+ 92
- 0
log_test.go View File

@@ -0,0 +1,92 @@
1
+package flog
2
+
3
+import (
4
+	"bufio"
5
+	"bytes"
6
+	"os"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/stretchr/testify/assert"
11
+)
12
+
13
+func TestGetOutput(t *testing.T) {
14
+	assert.Equal(t, os.Stdout, getOutput())
15
+
16
+	b := bytes.Buffer{}
17
+	Output = &b
18
+	assert.Equal(t, &b, getOutput())
19
+
20
+	Output = nil
21
+}
22
+
23
+func TestLogFormat(t *testing.T) {
24
+	b := bytes.Buffer{}
25
+	MinLevel, Output = LevelDebug, &b
26
+
27
+	Debug(`Testtt`)
28
+	assert.True(t, strings.HasPrefix(b.String(), `debug [`))
29
+	b.Reset()
30
+
31
+	Info(`Testtt`)
32
+	assert.True(t, strings.HasPrefix(b.String(), `info [`))
33
+
34
+	MinLevel, Output = LevelInfo, nil
35
+}
36
+
37
+func TestLogLevel(t *testing.T) {
38
+	b := bytes.Buffer{}
39
+	MinLevel, Output = LevelWarning, &b
40
+
41
+	Info(`Testtt`)
42
+	assert.True(t, b.Len() == 0)
43
+	b.Reset()
44
+
45
+	Warning(`Testtt`)
46
+	assert.False(t, b.Len() == 0)
47
+	b.Reset()
48
+
49
+	MinLevel = LevelError
50
+
51
+	Warning(`Testtt`)
52
+	assert.True(t, b.Len() == 0)
53
+	b.Reset()
54
+
55
+	MinLevel, Output = LevelInfo, nil
56
+}
57
+
58
+func TestStackTrace(t *testing.T) {
59
+	b := bytes.Buffer{}
60
+	Output = &b
61
+
62
+	Error(`Testtt`)
63
+	checkStackTrace(t, &b, `git.fuyu.moe/Fuyu/flog.TestStackTrace`)
64
+
65
+	b.Reset()
66
+
67
+	func() {
68
+		defer func() {
69
+			v := recover()
70
+			if v != nil {
71
+				Critical(v)
72
+			}
73
+		}()
74
+		panic(`Testtt`)
75
+	}()
76
+	checkStackTrace(t, &b, `git.fuyu.moe/Fuyu/flog.TestStackTrace.func1`)
77
+
78
+	Output = nil
79
+}
80
+
81
+func checkStackTrace(t *testing.T, b *bytes.Buffer, f string) {
82
+	rdr := bufio.NewReader(b)
83
+	_, _ = rdr.ReadBytes('\n')
84
+
85
+	data, err := rdr.ReadBytes('\n')
86
+	if err != nil {
87
+		t.Fatal(`Got error while reading:`, err)
88
+	}
89
+
90
+	parts := strings.Split(strings.TrimSpace(string(data)), ` `)
91
+	assert.Equal(t, f, parts[0])
92
+}

+ 23
- 0
settings.go View File

@@ -0,0 +1,23 @@
1
+package flog
2
+
3
+import (
4
+	"io"
5
+	"os"
6
+)
7
+
8
+// Log settings
9
+var (
10
+	MinLevel      = LevelInfo
11
+	MinStackLevel = LevelError
12
+
13
+	Format LogFormatType = TextFormatter{}
14
+	Output io.Writer
15
+)
16
+
17
+func getOutput() io.Writer {
18
+	if Output != nil {
19
+		return Output
20
+	}
21
+
22
+	return os.Stdout
23
+}

+ 45
- 0
type.go View File

@@ -0,0 +1,45 @@
1
+package flog
2
+
3
+import (
4
+	"io"
5
+	"time"
6
+)
7
+
8
+// LevelType defines the level type
9
+type LevelType uint8
10
+
11
+// All possible log levels
12
+const (
13
+	LevelDebug LevelType = iota
14
+	LevelInfo
15
+	LevelWarning
16
+	LevelError
17
+	LevelCritical
18
+)
19
+
20
+var levels = []string{`debug`, `info`, `warning`, `error`, `critical`}
21
+
22
+// String returns the name of the level as string
23
+func (l LevelType) String() string {
24
+	return levels[l]
25
+}
26
+
27
+// LogEntry is an entry for the log
28
+type LogEntry struct {
29
+	Level      LevelType         `json:"level"`
30
+	Time       time.Time         `json:"time"`
31
+	Message    string            `json:"message"`
32
+	StackTrace []StackTraceEntry `json:"stack_trace,omitempty"`
33
+}
34
+
35
+// StackTraceEntry is an entry in the stack trace
36
+type StackTraceEntry struct {
37
+	Function string `json:"function"`
38
+	File     string `json:"file"`
39
+	Line     int    `json:"line"`
40
+}
41
+
42
+// LogFormatType defines the log format
43
+type LogFormatType interface {
44
+	FormatMessage(io.Writer, LogEntry)
45
+}

Loading…
Cancel
Save