3 Commits

Author SHA1 Message Date
Ingo Oppermann
4a12b0293f Use configured logging target 2023-01-03 11:54:48 +01:00
Ingo Oppermann
f472fe150f Merge branch 'dev' into logging 2023-01-03 11:45:50 +01:00
Ingo Oppermann
37e00407cc Allow to define a logging target 2023-01-03 11:28:57 +01:00
12 changed files with 353 additions and 139 deletions

View File

@@ -10,6 +10,7 @@ import (
gonet "net"
gohttp "net/http"
"net/url"
"os"
"path/filepath"
"runtime/debug"
"sync"
@@ -147,7 +148,12 @@ func (a *api) Reload() error {
a.errorChan = make(chan error, 1)
}
logger := log.New("Core").WithOutput(log.NewConsoleWriter(a.log.writer, log.Lwarn, true))
logger := log.New("Core").WithOutput(
log.NewLevelWriter(
log.NewConsoleWriter(a.log.writer, true),
log.Lwarn,
),
)
store, err := configstore.NewJSON(a.config.path, func() {
a.errorChan <- ErrConfigReload
@@ -183,31 +189,54 @@ func (a *api) Reload() error {
break
}
buffer := log.NewBufferWriter(loglevel, cfg.Log.MaxLines)
buffer := log.NewBufferWriter(cfg.Log.MaxLines)
var writer log.Writer
logger = logger.WithOutput(log.NewLevelRewriter(
log.NewMultiWriter(
log.NewTopicWriter(
log.NewConsoleWriter(a.log.writer, loglevel, true),
cfg.Log.Topics,
),
buffer,
),
[]log.LevelRewriteRule{
// FFmpeg annoyance, move all warnings about unathorized access to memfs from ffmpeg to debug level
// ts=2022-04-28T07:24:27Z level=WARN component="HTTP" address=":8080" client="::1" latency_ms=0 method="PUT" path="/memfs/00a10a69-416a-4cd5-9d4f-6d88ed3dd7f5_0917.ts" proto="HTTP/1.1" size_bytes=65 status=401 status_text="Unauthorized" user_agent="Lavf/58.76.100"
{
Level: log.Ldebug,
Component: "HTTP",
Match: map[string]string{
"client": "^(::1|127.0.0.1)$",
"method": "^(PUT|POST|DELETE)$",
"status_text": "^Unauthorized$",
"user_agent": "^Lavf/",
if cfg.Log.Target.Output == "stdout" {
writer = log.NewConsoleWriter(
os.Stdout,
true,
)
} else if cfg.Log.Target.Output == "file" {
writer = log.NewFileWriter(
cfg.Log.Target.Path,
log.NewJSONFormatter(),
)
} else {
writer = log.NewConsoleWriter(
os.Stderr,
true,
)
}
logger = logger.WithOutput(
log.NewLevelWriter(
log.NewLevelRewriter(
log.NewMultiWriter(
log.NewTopicWriter(
writer,
cfg.Log.Topics,
),
buffer,
),
[]log.LevelRewriteRule{
// FFmpeg annoyance, move all warnings about unathorized access to memfs from ffmpeg to debug level
// ts=2022-04-28T07:24:27Z level=WARN component="HTTP" address=":8080" client="::1" latency_ms=0 method="PUT" path="/memfs/00a10a69-416a-4cd5-9d4f-6d88ed3dd7f5_0917.ts" proto="HTTP/1.1" size_bytes=65 status=401 status_text="Unauthorized" user_agent="Lavf/58.76.100"
{
Level: log.Ldebug,
Component: "HTTP",
Match: map[string]string{
"client": "^(::1|127.0.0.1)$",
"method": "^(PUT|POST|DELETE)$",
"status_text": "^Unauthorized$",
"user_agent": "^Lavf/",
},
},
},
},
},
))
),
loglevel,
),
)
logfields := log.Fields{
"application": app.Name,
@@ -1297,4 +1326,6 @@ func (a *api) Destroy() {
a.memfs.DeleteAll()
a.memfs = nil
}
a.log.logger.core.Close()
}

View File

@@ -17,7 +17,12 @@ import (
)
func main() {
logger := log.New("Migration").WithOutput(log.NewConsoleWriter(os.Stderr, log.Linfo, true)).WithFields(log.Fields{
logger := log.New("Migration").WithOutput(
log.NewLevelWriter(
log.NewConsoleWriter(os.Stderr, true),
log.Linfo,
),
).WithFields(log.Fields{
"from": "ffmpeg4",
"to": "ffmpeg5",
})
@@ -65,6 +70,27 @@ func doMigration(logger log.Logger, configstore cfgstore.Store) error {
return fmt.Errorf("the configuration contains errors: %v", messages)
}
var writer log.Writer
if cfg.Log.Target.Output == "stdout" {
writer = log.NewConsoleWriter(
os.Stdout,
true,
)
} else if cfg.Log.Target.Output == "file" {
writer = log.NewFileWriter(
cfg.Log.Target.Path,
log.NewJSONFormatter(),
)
} else {
writer = log.NewConsoleWriter(
os.Stderr,
true,
)
}
logger = logger.WithOutput(writer)
ff, err := ffmpeg.New(ffmpeg.Config{
Binary: cfg.FFmpeg.Binary,
})

View File

@@ -13,7 +13,12 @@ import (
)
func main() {
logger := log.New("Import").WithOutput(log.NewConsoleWriter(os.Stderr, log.Linfo, true)).WithField("version", "v1")
logger := log.New("Import").WithOutput(
log.NewLevelWriter(
log.NewConsoleWriter(os.Stderr, true),
log.Linfo,
),
).WithField("version", "v1")
configfile := cfgstore.Location(os.Getenv("CORE_CONFIGFILE"))
@@ -33,8 +38,6 @@ func doImport(logger log.Logger, configstore cfgstore.Store) error {
logger = log.New("")
}
logger.Info().Log("Database import")
cfg := configstore.Get()
// Merging the persisted config with the environment variables
@@ -60,6 +63,27 @@ func doImport(logger log.Logger, configstore cfgstore.Store) error {
return fmt.Errorf("the configuration contains errors: %v", messages)
}
var writer log.Writer
if cfg.Log.Target.Output == "stdout" {
writer = log.NewConsoleWriter(
os.Stdout,
true,
)
} else if cfg.Log.Target.Output == "file" {
writer = log.NewFileWriter(
cfg.Log.Target.Path,
log.NewJSONFormatter(),
)
} else {
writer = log.NewConsoleWriter(
os.Stderr,
true,
)
}
logger = logger.WithOutput(writer)
logger.Info().Log("Checking for database ...")
// Check if there's a v1.json from the old Restreamer

View File

@@ -141,6 +141,8 @@ func (d *Config) init() {
d.vars.Register(value.NewString(&d.Log.Level, "info"), "log.level", "CORE_LOG_LEVEL", nil, "Loglevel: silent, error, warn, info, debug", false, false)
d.vars.Register(value.NewStringList(&d.Log.Topics, []string{}, ","), "log.topics", "CORE_LOG_TOPICS", nil, "Show only selected log topics", false, false)
d.vars.Register(value.NewInt(&d.Log.MaxLines, 1000), "log.max_lines", "CORE_LOG_MAXLINES", nil, "Number of latest log lines to keep in memory", false, false)
d.vars.Register(value.NewString(&d.Log.Target.Output, "stderr"), "log.target.output", "CORE_LOG_TARGET_OUTPUT", nil, "Where to write the logs to: stdout, stderr, file", false, false)
d.vars.Register(value.NewString(&d.Log.Target.Path, ""), "log.target.path", "CORE_LOG_TARGET_PATH", nil, "Path to log file if output is 'file'", false, false)
// DB
d.vars.Register(value.NewMustDir(&d.DB.Dir, "./config"), "db.dir", "CORE_DB_DIR", nil, "Directory for holding the operational data", false, false)

View File

@@ -22,6 +22,10 @@ type Data struct {
Level string `json:"level" enums:"debug,info,warn,error,silent" jsonschema:"enum=debug,enum=info,enum=warn,enum=error,enum=silent"`
Topics []string `json:"topics"`
MaxLines int `json:"max_lines"`
Target struct {
Output string `json:"name"`
Path string `json:"path"`
} `json:"target"` // discard, stderr, stdout, file:/path/to/file.log
} `json:"log"`
DB struct {
Dir string `json:"dir"`
@@ -182,7 +186,6 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
data.Address = d.Address
data.CheckForUpdates = d.CheckForUpdates
data.Log = d.Log
data.DB = d.DB
data.Host = d.Host
data.API = d.API
@@ -195,8 +198,6 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
data.Service = d.Service
data.Router = d.Router
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Host.Name = copy.Slice(d.Host.Name)
data.API.Access.HTTP.Allow = copy.Slice(d.API.Access.HTTP.Allow)
@@ -228,6 +229,12 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
data.Storage.Memory = d.Storage.Memory
// Actual changes
data.Log.Level = d.Log.Level
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Log.MaxLines = d.Log.MaxLines
data.Log.Target.Output = "stderr"
data.Log.Target.Path = ""
data.Debug.Profiling = d.Debug.Profiling
data.Debug.ForceGC = d.Debug.ForceGC
data.Debug.MemoryLimit = 0
@@ -263,7 +270,6 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) {
data.Address = d.Address
data.CheckForUpdates = d.CheckForUpdates
data.Log = d.Log
data.DB = d.DB
data.Host = d.Host
data.API = d.API
@@ -276,8 +282,6 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) {
data.Service = d.Service
data.Router = d.Router
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Host.Name = copy.Slice(d.Host.Name)
data.API.Access.HTTP.Allow = copy.Slice(d.API.Access.HTTP.Allow)
@@ -302,6 +306,10 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) {
data.Router.Routes = copy.StringMap(d.Router.Routes)
// Actual changes
data.Log.Level = d.Log.Level
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Log.MaxLines = d.Log.MaxLines
data.Debug.Profiling = d.Debug.Profiling
data.Debug.ForceGC = d.Debug.ForceGC

View File

@@ -12,7 +12,7 @@ import (
func (r *queryResolver) Log(ctx context.Context) ([]string, error) {
if r.LogBuffer == nil {
r.LogBuffer = log.NewBufferWriter(log.Lsilent, 1)
r.LogBuffer = log.NewBufferWriter(1)
}
events := r.LogBuffer.Events()

View File

@@ -22,7 +22,7 @@ func NewLog(buffer log.BufferWriter) *LogHandler {
}
if l.buffer == nil {
l.buffer = log.NewBufferWriter(log.Lsilent, 1)
l.buffer = log.NewBufferWriter(1)
}
return l

View File

@@ -14,28 +14,29 @@ import (
type Level uint
const (
Lsilent Level = 0
Lerror Level = 1
Lwarn Level = 2
Linfo Level = 3
Ldebug Level = 4
Lsilent Level = 0b0000
Lerror Level = 0b0001
Lwarn Level = 0b0010
Linfo Level = 0b0100
Ldebug Level = 0b1000
)
// String returns a string representing the log level.
func (level Level) String() string {
names := []string{
"SILENT",
"ERROR",
"WARN",
"INFO",
"DEBUG",
}
if level > Ldebug {
switch level {
case Lsilent:
return "SILENT"
case Lerror:
return "ERROR"
case Lwarn:
return "WARN"
case Linfo:
return "INFO"
case Ldebug:
return "DEBUG"
default:
return `¯\_(ツ)_/¯`
}
return names[level]
}
func (level *Level) MarshalJSON() ([]byte, error) {
@@ -97,6 +98,9 @@ type Logger interface {
// Write implements the io.Writer interface such that it can be used in e.g. the
// the log/Logger facility. Messages will be printed with debug level.
Write(p []byte) (int, error)
// Close closes the underlying writer.
Close()
}
// logger is an implementation of the Logger interface.
@@ -184,6 +188,10 @@ func (l *logger) Write(p []byte) (int, error) {
return newEvent(l).Write(p)
}
func (l *logger) Close() {
l.output.Close()
}
type Event struct {
logger *logger
@@ -352,12 +360,6 @@ func (l *Event) Write(p []byte) (int, error) {
return len(p), nil
}
type Eventx struct {
Time time.Time `json:"ts"`
Level Level `json:"level"`
Component string `json:"component"`
Reference string `json:"ref"`
Message string `json:"message"`
Caller string `json:"caller"`
Detail interface{} `json:"detail"`
func (l *Event) Close() {
l.logger.Close()
}

View File

@@ -5,25 +5,25 @@ import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoglevelNames(t *testing.T) {
assert.Equal(t, "DEBUG", Ldebug.String())
assert.Equal(t, "ERROR", Lerror.String())
assert.Equal(t, "WARN", Lwarn.String())
assert.Equal(t, "INFO", Linfo.String())
assert.Equal(t, `SILENT`, Lsilent.String())
require.Equal(t, "DEBUG", Ldebug.String())
require.Equal(t, "ERROR", Lerror.String())
require.Equal(t, "WARN", Lwarn.String())
require.Equal(t, "INFO", Linfo.String())
require.Equal(t, `SILENT`, Lsilent.String())
}
func TestLogColorToNotTTY(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
w := NewConsoleWriter(writer, Linfo, true).(*syncWriter)
w := NewLevelWriter(NewConsoleWriter(writer, true), Linfo).(*levelWriter).writer.(*syncWriter)
formatter := w.writer.(*consoleWriter).formatter.(*consoleFormatter)
assert.NotEqual(t, true, formatter.color, "Color should not be used on a buffer logger")
require.NotEqual(t, true, formatter.color, "Color should not be used on a buffer logger")
}
func TestLogContext(t *testing.T) {
@@ -31,7 +31,7 @@ func TestLogContext(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("component").WithOutput(NewConsoleWriter(writer, Ldebug, false))
logger := New("component").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Ldebug))
logger.Debug().Log("debug")
logger.Info().Log("info")
@@ -53,19 +53,19 @@ func TestLogContext(t *testing.T) {
lenWithoutCtx := buffer.Len()
buffer.Reset()
assert.Greater(t, lenWithCtx, lenWithoutCtx, "Log line length without context is not shorter than with context")
require.Greater(t, lenWithCtx, lenWithoutCtx, "Log line length without context is not shorter than with context")
}
func TestLogClone(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Linfo, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Linfo))
logger.Info().Log("info")
writer.Flush()
assert.Contains(t, buffer.String(), `component="test"`)
require.Contains(t, buffer.String(), `component="test"`)
buffer.Reset()
@@ -74,33 +74,33 @@ func TestLogClone(t *testing.T) {
logger2.Info().Log("info")
writer.Flush()
assert.Contains(t, buffer.String(), `component="tset"`)
require.Contains(t, buffer.String(), `component="tset"`)
}
func TestLogSilent(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Lsilent, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Lsilent))
logger.Debug().Log("debug")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Info().Log("info")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Warn().Log("warn")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Error().Log("error")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
}
@@ -108,26 +108,26 @@ func TestLogDebug(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Ldebug, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Ldebug))
logger.Debug().Log("debug")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Info().Log("info")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Warn().Log("warn")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Error().Log("error")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
}
@@ -135,26 +135,26 @@ func TestLogInfo(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Linfo, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Linfo))
logger.Debug().Log("debug")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Info().Log("info")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Warn().Log("warn")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Error().Log("error")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
}
@@ -162,26 +162,26 @@ func TestLogWarn(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Lwarn, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Lwarn))
logger.Debug().Log("debug")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Info().Log("info")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Warn().Log("warn")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
logger.Error().Log("error")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
}
@@ -189,25 +189,25 @@ func TestLogError(t *testing.T) {
var buffer bytes.Buffer
writer := bufio.NewWriter(&buffer)
logger := New("test").WithOutput(NewConsoleWriter(writer, Lerror, false))
logger := New("test").WithOutput(NewLevelWriter(NewConsoleWriter(writer, false), Lerror))
logger.Debug().Log("debug")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Info().Log("info")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Warn().Log("warn")
writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty")
require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset()
logger.Error().Log("error")
writer.Flush()
assert.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
require.NotEqual(t, 0, buffer.Len(), "Buffer should not be empty")
buffer.Reset()
}

43
log/output.go Normal file
View File

@@ -0,0 +1,43 @@
package log
import (
"io"
"os"
"github.com/mattn/go-isatty"
)
type consoleOutput struct {
writer io.Writer
formatter Formatter
}
func NewConsoleOutput(w io.Writer, useColor bool) Writer {
writer := &consoleOutput{
writer: w,
}
color := useColor
if color {
if w, ok := w.(*os.File); ok {
if !isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()) {
color = false
}
} else {
color = false
}
}
writer.formatter = NewConsoleFormatter(color)
return NewSyncWriter(writer)
}
func (w *consoleOutput) Write(e *Event) error {
_, err := w.writer.Write(w.formatter.Bytes(e))
return err
}
func (w *consoleOutput) Close() {}

View File

@@ -13,18 +13,50 @@ import (
type Writer interface {
Write(e *Event) error
Close()
}
type discardWriter struct{}
func NewDiscardWriter() Writer {
return &discardWriter{}
}
func (w *discardWriter) Write(e *Event) error { return nil }
func (w *discardWriter) Close() {}
type levelWriter struct {
writer Writer
level Level
}
func NewLevelWriter(w Writer, level Level) Writer {
return &levelWriter{
writer: w,
level: level,
}
}
func (w *levelWriter) Write(e *Event) error {
if w.level < e.Level || e.Level == Lsilent {
return nil
}
return w.writer.Write(e)
}
func (w *levelWriter) Close() {
w.writer.Close()
}
type jsonWriter struct {
writer io.Writer
level Level
formatter Formatter
}
func NewJSONWriter(w io.Writer, level Level) Writer {
func NewJSONWriter(w io.Writer) Writer {
writer := &jsonWriter{
writer: w,
level: level,
formatter: NewJSONFormatter(),
}
@@ -32,25 +64,21 @@ func NewJSONWriter(w io.Writer, level Level) Writer {
}
func (w *jsonWriter) Write(e *Event) error {
if w.level < e.Level || e.Level == Lsilent {
return nil
}
_, err := w.writer.Write(w.formatter.Bytes(e))
return err
}
func (w *jsonWriter) Close() {}
type consoleWriter struct {
writer io.Writer
level Level
formatter Formatter
}
func NewConsoleWriter(w io.Writer, level Level, useColor bool) Writer {
func NewConsoleWriter(w io.Writer, useColor bool) Writer {
writer := &consoleWriter{
writer: w,
level: level,
}
color := useColor
@@ -71,15 +99,13 @@ func NewConsoleWriter(w io.Writer, level Level, useColor bool) Writer {
}
func (w *consoleWriter) Write(e *Event) error {
if w.level < e.Level || e.Level == Lsilent {
return nil
}
_, err := w.writer.Write(w.formatter.Bytes(e))
return err
}
func (w *consoleWriter) Close() {}
type topicWriter struct {
writer Writer
topics map[string]struct{}
@@ -112,6 +138,10 @@ func (w *topicWriter) Write(e *Event) error {
return err
}
func (w *topicWriter) Close() {
w.writer.Close()
}
type levelRewriter struct {
writer Writer
rules []levelRewriteRule
@@ -182,6 +212,10 @@ rules:
return w.writer.Write(e)
}
func (w *levelRewriter) Close() {
w.writer.Close()
}
type syncWriter struct {
mu sync.Mutex
writer Writer
@@ -193,11 +227,15 @@ func NewSyncWriter(writer Writer) Writer {
}
}
func (s *syncWriter) Write(e *Event) error {
s.mu.Lock()
defer s.mu.Unlock()
func (w *syncWriter) Write(e *Event) error {
w.mu.Lock()
defer w.mu.Unlock()
return s.writer.Write(e)
return w.writer.Write(e)
}
func (w *syncWriter) Close() {
w.writer.Close()
}
type multiWriter struct {
@@ -212,8 +250,8 @@ func NewMultiWriter(writer ...Writer) Writer {
return mw
}
func (m *multiWriter) Write(e *Event) error {
for _, w := range m.writer {
func (w *multiWriter) Write(e *Event) error {
for _, w := range w.writer {
if err := w.Write(e); err != nil {
return err
}
@@ -222,6 +260,12 @@ func (m *multiWriter) Write(e *Event) error {
return nil
}
func (w *multiWriter) Close() {
for _, w := range w.writer {
w.Close()
}
}
type BufferWriter interface {
Writer
Events() []*Event
@@ -230,13 +274,10 @@ type BufferWriter interface {
type bufferWriter struct {
lines *ring.Ring
lock sync.RWMutex
level Level
}
func NewBufferWriter(level Level, lines int) BufferWriter {
b := &bufferWriter{
level: level,
}
func NewBufferWriter(lines int) BufferWriter {
b := &bufferWriter{}
if lines > 0 {
b.lines = ring.New(lines)
@@ -245,33 +286,31 @@ func NewBufferWriter(level Level, lines int) BufferWriter {
return b
}
func (b *bufferWriter) Write(e *Event) error {
if b.level < e.Level || e.Level == Lsilent {
return nil
}
func (w *bufferWriter) Write(e *Event) error {
w.lock.Lock()
defer w.lock.Unlock()
b.lock.Lock()
defer b.lock.Unlock()
if b.lines != nil {
b.lines.Value = e.clone()
b.lines = b.lines.Next()
if w.lines != nil {
w.lines.Value = e.clone()
w.lines = w.lines.Next()
}
return nil
}
func (b *bufferWriter) Events() []*Event {
func (w *bufferWriter) Close() {}
func (w *bufferWriter) Events() []*Event {
var lines = []*Event{}
if b.lines == nil {
if w.lines == nil {
return lines
}
b.lock.RLock()
defer b.lock.RUnlock()
w.lock.RLock()
defer w.lock.RUnlock()
b.lines.Do(func(l interface{}) {
w.lines.Do(func(l interface{}) {
if l == nil {
return
}
@@ -281,3 +320,32 @@ func (b *bufferWriter) Events() []*Event {
return lines
}
type fileWriter struct {
writer *os.File
formatter Formatter
}
func NewFileWriter(path string, formatter Formatter) Writer {
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR|os.O_SYNC, 0600)
if err != nil {
return NewDiscardWriter()
}
writer := &fileWriter{
writer: file,
formatter: formatter,
}
return NewSyncWriter(writer)
}
func (w *fileWriter) Write(e *Event) error {
_, err := w.writer.Write(append(w.formatter.Bytes(e), '\n'))
return err
}
func (w *fileWriter) Close() {
w.writer.Close()
}

12
main.go
View File

@@ -12,7 +12,15 @@ import (
)
func main() {
logger := log.New("Core").WithOutput(log.NewConsoleWriter(os.Stderr, log.Lwarn, true))
logger := log.New("Core").WithOutput(
log.NewLevelWriter(
log.NewConsoleWriter(
os.Stderr,
true,
),
log.Lwarn,
),
)
configfile := store.Location(os.Getenv("CORE_CONFIGFILE"))
@@ -54,6 +62,8 @@ func main() {
signal.Notify(quit, os.Interrupt)
<-quit
logger.Close()
// Stop the app
app.Destroy()
}