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