Initial commit
This commit is contained in:
commit
3cbefefec5
26
formatter.go
Normal file
26
formatter.go
Normal file
@ -0,0 +1,26 @@
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// TextFormatter logs in a human readable format
|
||||
type TextFormatter struct{}
|
||||
|
||||
// FormatMessage implements LogFormatType
|
||||
func (l TextFormatter) FormatMessage(w io.Writer, entry LogEntry) {
|
||||
fmt.Fprintln(w, entry.Level, entry.Time.Format(`[2006-01-02 15:04:05]`), entry.Message)
|
||||
|
||||
if len(entry.StackTrace) > 0 {
|
||||
tw := tabwriter.NewWriter(w, 1, 4, 4, ' ', 0)
|
||||
for _, v := range entry.StackTrace {
|
||||
fmt.Fprint(tw, ` `, v.Function, "\t", v.File+`:`+strconv.Itoa(v.Line), "\n")
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
w.Write([]byte{'\n'})
|
||||
}
|
93
log.go
Normal file
93
log.go
Normal file
@ -0,0 +1,93 @@
|
||||
package flog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// All log functions
|
||||
var (
|
||||
Debug = logFunc(LevelDebug)
|
||||
Info = logFunc(LevelInfo)
|
||||
Warning = logFunc(LevelWarning)
|
||||
Error = logFunc(LevelError)
|
||||
Critical = logFunc(LevelCritical)
|
||||
)
|
||||
|
||||
func logFunc(level LevelType) func(...interface{}) {
|
||||
return func(message ...interface{}) {
|
||||
writeMessage(level, message)
|
||||
}
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
|
||||
func writeMessage(level LevelType, message []interface{}) {
|
||||
if MinLevel > level {
|
||||
return
|
||||
}
|
||||
|
||||
entry := LogEntry{
|
||||
Level: level,
|
||||
Time: time.Now(),
|
||||
Message: fmt.Sprint(message...),
|
||||
}
|
||||
|
||||
if MinStackLevel <= level {
|
||||
entry.StackTrace = getStackTrace()
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Format.FormatMessage(getOutput(), entry)
|
||||
}
|
||||
|
||||
func getStackTrace() (stackTrace []StackTraceEntry) {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
|
||||
pc := make([]uintptr, 50)
|
||||
entries := runtime.Callers(2, pc)
|
||||
frames := runtime.CallersFrames(pc[:entries])
|
||||
|
||||
more, firstLine := true, false
|
||||
for more {
|
||||
var frame runtime.Frame
|
||||
frame, more = frames.Next()
|
||||
|
||||
if !firstLine && frame.File == filename { // Skip frames from the flog cal
|
||||
continue
|
||||
}
|
||||
firstLine = true
|
||||
|
||||
if frame.Function == `runtime.gopanic` { // If a panic occurred, start at the frame that called panic
|
||||
stackTrace = nil
|
||||
continue
|
||||
}
|
||||
|
||||
stackTrace = append(stackTrace, StackTraceEntry{
|
||||
Function: cleanFunction(frame.Function),
|
||||
File: cleanFilename(frame.File),
|
||||
Line: frame.Line,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func cleanFunction(f string) string {
|
||||
parts := strings.Split(f, `/vendor/`)
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
func cleanFilename(file string) string {
|
||||
parts := strings.Split(file, `/src/`)
|
||||
if len(parts) < 2 {
|
||||
return file
|
||||
}
|
||||
|
||||
return strings.Join(parts[1:], `/`)
|
||||
}
|
92
log_test.go
Normal file
92
log_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
package flog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetOutput(t *testing.T) {
|
||||
assert.Equal(t, os.Stdout, getOutput())
|
||||
|
||||
b := bytes.Buffer{}
|
||||
Output = &b
|
||||
assert.Equal(t, &b, getOutput())
|
||||
|
||||
Output = nil
|
||||
}
|
||||
|
||||
func TestLogFormat(t *testing.T) {
|
||||
b := bytes.Buffer{}
|
||||
MinLevel, Output = LevelDebug, &b
|
||||
|
||||
Debug(`Testtt`)
|
||||
assert.True(t, strings.HasPrefix(b.String(), `debug [`))
|
||||
b.Reset()
|
||||
|
||||
Info(`Testtt`)
|
||||
assert.True(t, strings.HasPrefix(b.String(), `info [`))
|
||||
|
||||
MinLevel, Output = LevelInfo, nil
|
||||
}
|
||||
|
||||
func TestLogLevel(t *testing.T) {
|
||||
b := bytes.Buffer{}
|
||||
MinLevel, Output = LevelWarning, &b
|
||||
|
||||
Info(`Testtt`)
|
||||
assert.True(t, b.Len() == 0)
|
||||
b.Reset()
|
||||
|
||||
Warning(`Testtt`)
|
||||
assert.False(t, b.Len() == 0)
|
||||
b.Reset()
|
||||
|
||||
MinLevel = LevelError
|
||||
|
||||
Warning(`Testtt`)
|
||||
assert.True(t, b.Len() == 0)
|
||||
b.Reset()
|
||||
|
||||
MinLevel, Output = LevelInfo, nil
|
||||
}
|
||||
|
||||
func TestStackTrace(t *testing.T) {
|
||||
b := bytes.Buffer{}
|
||||
Output = &b
|
||||
|
||||
Error(`Testtt`)
|
||||
checkStackTrace(t, &b, `git.fuyu.moe/Fuyu/flog.TestStackTrace`)
|
||||
|
||||
b.Reset()
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
Critical(v)
|
||||
}
|
||||
}()
|
||||
panic(`Testtt`)
|
||||
}()
|
||||
checkStackTrace(t, &b, `git.fuyu.moe/Fuyu/flog.TestStackTrace.func1`)
|
||||
|
||||
Output = nil
|
||||
}
|
||||
|
||||
func checkStackTrace(t *testing.T, b *bytes.Buffer, f string) {
|
||||
rdr := bufio.NewReader(b)
|
||||
_, _ = rdr.ReadBytes('\n')
|
||||
|
||||
data, err := rdr.ReadBytes('\n')
|
||||
if err != nil {
|
||||
t.Fatal(`Got error while reading:`, err)
|
||||
}
|
||||
|
||||
parts := strings.Split(strings.TrimSpace(string(data)), ` `)
|
||||
assert.Equal(t, f, parts[0])
|
||||
}
|
23
settings.go
Normal file
23
settings.go
Normal file
@ -0,0 +1,23 @@
|
||||
package flog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Log settings
|
||||
var (
|
||||
MinLevel = LevelInfo
|
||||
MinStackLevel = LevelError
|
||||
|
||||
Format LogFormatType = TextFormatter{}
|
||||
Output io.Writer
|
||||
)
|
||||
|
||||
func getOutput() io.Writer {
|
||||
if Output != nil {
|
||||
return Output
|
||||
}
|
||||
|
||||
return os.Stdout
|
||||
}
|
45
type.go
Normal file
45
type.go
Normal file
@ -0,0 +1,45 @@
|
||||
package flog
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LevelType defines the level type
|
||||
type LevelType uint8
|
||||
|
||||
// All possible log levels
|
||||
const (
|
||||
LevelDebug LevelType = iota
|
||||
LevelInfo
|
||||
LevelWarning
|
||||
LevelError
|
||||
LevelCritical
|
||||
)
|
||||
|
||||
var levels = []string{`debug`, `info`, `warning`, `error`, `critical`}
|
||||
|
||||
// String returns the name of the level as string
|
||||
func (l LevelType) String() string {
|
||||
return levels[l]
|
||||
}
|
||||
|
||||
// LogEntry is an entry for the log
|
||||
type LogEntry struct {
|
||||
Level LevelType `json:"level"`
|
||||
Time time.Time `json:"time"`
|
||||
Message string `json:"message"`
|
||||
StackTrace []StackTraceEntry `json:"stack_trace,omitempty"`
|
||||
}
|
||||
|
||||
// StackTraceEntry is an entry in the stack trace
|
||||
type StackTraceEntry struct {
|
||||
Function string `json:"function"`
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
}
|
||||
|
||||
// LogFormatType defines the log format
|
||||
type LogFormatType interface {
|
||||
FormatMessage(io.Writer, LogEntry)
|
||||
}
|
Loading…
Reference in New Issue
Block a user