add logrus hook to track recent log entries
parent
904ddb956d
commit
3856515789
@ -0,0 +1,125 @@
|
||||
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Parity Public License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package history
|
||||
|
||||
// Package history implements a logrus hook that provides access
|
||||
// to log recent log activity.
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// default log entry limit.
|
||||
const defaultLimit = 250
|
||||
|
||||
// Level is the log level.
|
||||
type Level string
|
||||
|
||||
// log levels.
|
||||
const (
|
||||
LevelError = Level("error")
|
||||
LevelWarn = Level("warn")
|
||||
LevelInfo = Level("info")
|
||||
LevelDebug = Level("debug")
|
||||
LevelTrace = Level("trace")
|
||||
)
|
||||
|
||||
// Entry provides a log entry.
|
||||
type Entry struct {
|
||||
Level Level
|
||||
Message string
|
||||
Data map[string]interface{}
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// Hook is a logrus hook that track the log history.
|
||||
type Hook struct {
|
||||
sync.RWMutex
|
||||
limit int
|
||||
entries []*Entry
|
||||
}
|
||||
|
||||
// New returns a new history hook.
|
||||
func New() *Hook {
|
||||
return NewLimit(defaultLimit)
|
||||
}
|
||||
|
||||
// NewLimit returns a new history hook with a custom
|
||||
// history limit.
|
||||
func NewLimit(limit int) *Hook {
|
||||
return &Hook{limit: limit}
|
||||
}
|
||||
|
||||
// Fire receives the log entry.
|
||||
func (h *Hook) Fire(e *logrus.Entry) error {
|
||||
h.Lock()
|
||||
if len(h.entries) >= h.limit {
|
||||
h.entries = h.entries[1:]
|
||||
}
|
||||
h.entries = append(h.entries, &Entry{
|
||||
Level: convertLevel(e.Level),
|
||||
Data: convertFields(e.Data),
|
||||
Time: e.Time,
|
||||
Message: e.Message,
|
||||
})
|
||||
h.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Levels returns the supported log levels.
|
||||
func (h *Hook) Levels() []logrus.Level {
|
||||
return logrus.AllLevels
|
||||
}
|
||||
|
||||
// Entries returns a list of all entries.
|
||||
func (h *Hook) Entries() []*Entry {
|
||||
h.RLock()
|
||||
defer h.RUnlock()
|
||||
entries := make([]*Entry, len(h.entries))
|
||||
for i, entry := range h.entries {
|
||||
entries[i] = copyEntry(entry)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
// helper funtion copies an entry for threadsafe access.
|
||||
func copyEntry(src *Entry) *Entry {
|
||||
dst := new(Entry)
|
||||
*dst = *src
|
||||
dst.Data = map[string]interface{}{}
|
||||
for k, v := range src.Data {
|
||||
dst.Data[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// helper function converts a logrus.Level to the local type.
|
||||
func convertLevel(level logrus.Level) Level {
|
||||
switch level {
|
||||
case logrus.PanicLevel:
|
||||
return LevelError
|
||||
case logrus.FatalLevel:
|
||||
return LevelError
|
||||
case logrus.WarnLevel:
|
||||
return LevelWarn
|
||||
case logrus.DebugLevel:
|
||||
return LevelDebug
|
||||
case logrus.TraceLevel:
|
||||
return LevelTrace
|
||||
default:
|
||||
return LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// helper fucntion copies logrus.Fields to a basic map.
|
||||
func convertFields(src logrus.Fields) map[string]interface{} {
|
||||
dst := map[string]interface{}{}
|
||||
for k, v := range src {
|
||||
dst[k] = v
|
||||
}
|
||||
return dst
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Copyright 2019 Drone.IO Inc. All rights reserved.
|
||||
// Use of this source code is governed by the Parity Public License
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package history
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestLevels(t *testing.T) {
|
||||
hook := New()
|
||||
if diff := cmp.Diff(hook.Levels(), logrus.AllLevels); diff != "" {
|
||||
t.Errorf("Hook should return all levels")
|
||||
t.Log(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertLevels(t *testing.T) {
|
||||
tests := []struct {
|
||||
before logrus.Level
|
||||
after Level
|
||||
}{
|
||||
{logrus.PanicLevel, LevelError},
|
||||
{logrus.FatalLevel, LevelError},
|
||||
{logrus.WarnLevel, LevelWarn},
|
||||
{logrus.InfoLevel, LevelInfo},
|
||||
{logrus.DebugLevel, LevelDebug},
|
||||
{logrus.TraceLevel, LevelTrace},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got, want := convertLevel(test.before), test.after; got != want {
|
||||
t.Errorf("Want entry level %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimit(t *testing.T) {
|
||||
hook := NewLimit(4)
|
||||
hook.Fire(&logrus.Entry{})
|
||||
hook.Fire(&logrus.Entry{})
|
||||
hook.Fire(&logrus.Entry{})
|
||||
hook.Fire(&logrus.Entry{})
|
||||
hook.Fire(&logrus.Entry{})
|
||||
if got, want := len(hook.entries), 4; got != want {
|
||||
t.Errorf("Expect entries pruned to %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistory(t *testing.T) {
|
||||
hook := New()
|
||||
|
||||
now := time.Now()
|
||||
hook.Fire(&logrus.Entry{
|
||||
Level: logrus.DebugLevel,
|
||||
Message: "foo",
|
||||
Data: logrus.Fields{"foo": "bar"},
|
||||
Time: now,
|
||||
})
|
||||
|
||||
hook.Fire(&logrus.Entry{
|
||||
Level: logrus.InfoLevel,
|
||||
Message: "bar",
|
||||
Data: logrus.Fields{"baz": "qux"},
|
||||
Time: now,
|
||||
})
|
||||
|
||||
if len(hook.entries) != 2 {
|
||||
t.Errorf("Expected 2 hooks added to history")
|
||||
}
|
||||
|
||||
entries := hook.Entries()
|
||||
if len(entries) != 2 {
|
||||
t.Errorf("Expected 2 hooks returned")
|
||||
}
|
||||
if entries[0] == hook.entries[0] {
|
||||
t.Errorf("Expect copy of entries, got a reference")
|
||||
}
|
||||
if entries[1] == hook.entries[1] {
|
||||
t.Errorf("Expect copy of entries, got a reference")
|
||||
}
|
||||
|
||||
expect := []*Entry{
|
||||
{
|
||||
Level: LevelDebug,
|
||||
Message: "foo",
|
||||
Data: logrus.Fields{"foo": "bar"},
|
||||
Time: now,
|
||||
},
|
||||
{
|
||||
Level: LevelInfo,
|
||||
Message: "bar",
|
||||
Data: logrus.Fields{"baz": "qux"},
|
||||
Time: now,
|
||||
},
|
||||
}
|
||||
if diff := cmp.Diff(entries, expect); diff != "" {
|
||||
t.Errorf("Entries should return an exact copy of all entries")
|
||||
t.Log(diff)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue