Initial commit

This commit is contained in:
Nise Void 2018-05-08 16:30:55 +02:00
commit 3cbefefec5
Signed by: NiseVoid
GPG Key ID: FBA14AC83EA602F3
5 changed files with 279 additions and 0 deletions

26
formatter.go Normal file
View 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
View 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
View 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
View 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
View 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)
}