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 @@
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
- 0
log.go 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
- 0
log_test.go 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
- 0
settings.go 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
- 0
type.go 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)
}

Loading…
Cancel
Save