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" gonet "net"
gohttp "net/http" gohttp "net/http"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"runtime/debug" "runtime/debug"
"sync" "sync"
@@ -147,7 +148,12 @@ func (a *api) Reload() error {
a.errorChan = make(chan error, 1) 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() { store, err := configstore.NewJSON(a.config.path, func() {
a.errorChan <- ErrConfigReload a.errorChan <- ErrConfigReload
@@ -183,31 +189,54 @@ func (a *api) Reload() error {
break break
} }
buffer := log.NewBufferWriter(loglevel, cfg.Log.MaxLines) buffer := log.NewBufferWriter(cfg.Log.MaxLines)
var writer log.Writer
logger = logger.WithOutput(log.NewLevelRewriter( if cfg.Log.Target.Output == "stdout" {
log.NewMultiWriter( writer = log.NewConsoleWriter(
log.NewTopicWriter( os.Stdout,
log.NewConsoleWriter(a.log.writer, loglevel, true), true,
cfg.Log.Topics, )
), } else if cfg.Log.Target.Output == "file" {
buffer, writer = log.NewFileWriter(
), cfg.Log.Target.Path,
[]log.LevelRewriteRule{ log.NewJSONFormatter(),
// 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" } else {
{ writer = log.NewConsoleWriter(
Level: log.Ldebug, os.Stderr,
Component: "HTTP", true,
Match: map[string]string{ )
"client": "^(::1|127.0.0.1)$", }
"method": "^(PUT|POST|DELETE)$",
"status_text": "^Unauthorized$", logger = logger.WithOutput(
"user_agent": "^Lavf/", 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{ logfields := log.Fields{
"application": app.Name, "application": app.Name,
@@ -1297,4 +1326,6 @@ func (a *api) Destroy() {
a.memfs.DeleteAll() a.memfs.DeleteAll()
a.memfs = nil a.memfs = nil
} }
a.log.logger.core.Close()
} }

View File

@@ -17,7 +17,12 @@ import (
) )
func main() { 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", "from": "ffmpeg4",
"to": "ffmpeg5", "to": "ffmpeg5",
}) })
@@ -65,6 +70,27 @@ func doMigration(logger log.Logger, configstore cfgstore.Store) error {
return fmt.Errorf("the configuration contains errors: %v", messages) 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{ ff, err := ffmpeg.New(ffmpeg.Config{
Binary: cfg.FFmpeg.Binary, Binary: cfg.FFmpeg.Binary,
}) })

View File

@@ -13,7 +13,12 @@ import (
) )
func main() { 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")) configfile := cfgstore.Location(os.Getenv("CORE_CONFIGFILE"))
@@ -33,8 +38,6 @@ func doImport(logger log.Logger, configstore cfgstore.Store) error {
logger = log.New("") logger = log.New("")
} }
logger.Info().Log("Database import")
cfg := configstore.Get() cfg := configstore.Get()
// Merging the persisted config with the environment variables // 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) 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 ...") logger.Info().Log("Checking for database ...")
// Check if there's a v1.json from the old Restreamer // 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.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.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.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 // DB
d.vars.Register(value.NewMustDir(&d.DB.Dir, "./config"), "db.dir", "CORE_DB_DIR", nil, "Directory for holding the operational data", false, false) 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"` 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"` Topics []string `json:"topics"`
MaxLines int `json:"max_lines"` 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"` } `json:"log"`
DB struct { DB struct {
Dir string `json:"dir"` Dir string `json:"dir"`
@@ -182,7 +186,6 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
data.Address = d.Address data.Address = d.Address
data.CheckForUpdates = d.CheckForUpdates data.CheckForUpdates = d.CheckForUpdates
data.Log = d.Log
data.DB = d.DB data.DB = d.DB
data.Host = d.Host data.Host = d.Host
data.API = d.API data.API = d.API
@@ -195,8 +198,6 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
data.Service = d.Service data.Service = d.Service
data.Router = d.Router data.Router = d.Router
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Host.Name = copy.Slice(d.Host.Name) data.Host.Name = copy.Slice(d.Host.Name)
data.API.Access.HTTP.Allow = copy.Slice(d.API.Access.HTTP.Allow) 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 data.Storage.Memory = d.Storage.Memory
// Actual changes // 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.Profiling = d.Debug.Profiling
data.Debug.ForceGC = d.Debug.ForceGC data.Debug.ForceGC = d.Debug.ForceGC
data.Debug.MemoryLimit = 0 data.Debug.MemoryLimit = 0
@@ -263,7 +270,6 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) {
data.Address = d.Address data.Address = d.Address
data.CheckForUpdates = d.CheckForUpdates data.CheckForUpdates = d.CheckForUpdates
data.Log = d.Log
data.DB = d.DB data.DB = d.DB
data.Host = d.Host data.Host = d.Host
data.API = d.API data.API = d.API
@@ -276,8 +282,6 @@ func DowngradeV3toV2(d *Data) (*v2.Data, error) {
data.Service = d.Service data.Service = d.Service
data.Router = d.Router data.Router = d.Router
data.Log.Topics = copy.Slice(d.Log.Topics)
data.Host.Name = copy.Slice(d.Host.Name) data.Host.Name = copy.Slice(d.Host.Name)
data.API.Access.HTTP.Allow = copy.Slice(d.API.Access.HTTP.Allow) 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) data.Router.Routes = copy.StringMap(d.Router.Routes)
// Actual changes // 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.Profiling = d.Debug.Profiling
data.Debug.ForceGC = d.Debug.ForceGC data.Debug.ForceGC = d.Debug.ForceGC

View File

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

View File

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

View File

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

View File

@@ -5,25 +5,25 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require"
) )
func TestLoglevelNames(t *testing.T) { func TestLoglevelNames(t *testing.T) {
assert.Equal(t, "DEBUG", Ldebug.String()) require.Equal(t, "DEBUG", Ldebug.String())
assert.Equal(t, "ERROR", Lerror.String()) require.Equal(t, "ERROR", Lerror.String())
assert.Equal(t, "WARN", Lwarn.String()) require.Equal(t, "WARN", Lwarn.String())
assert.Equal(t, "INFO", Linfo.String()) require.Equal(t, "INFO", Linfo.String())
assert.Equal(t, `SILENT`, Lsilent.String()) require.Equal(t, `SILENT`, Lsilent.String())
} }
func TestLogColorToNotTTY(t *testing.T) { func TestLogColorToNotTTY(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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) 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) { func TestLogContext(t *testing.T) {
@@ -31,7 +31,7 @@ func TestLogContext(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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.Debug().Log("debug")
logger.Info().Log("info") logger.Info().Log("info")
@@ -53,19 +53,19 @@ func TestLogContext(t *testing.T) {
lenWithoutCtx := buffer.Len() lenWithoutCtx := buffer.Len()
buffer.Reset() 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) { func TestLogClone(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Info().Log("info")
writer.Flush() writer.Flush()
assert.Contains(t, buffer.String(), `component="test"`) require.Contains(t, buffer.String(), `component="test"`)
buffer.Reset() buffer.Reset()
@@ -74,33 +74,33 @@ func TestLogClone(t *testing.T) {
logger2.Info().Log("info") logger2.Info().Log("info")
writer.Flush() writer.Flush()
assert.Contains(t, buffer.String(), `component="tset"`) require.Contains(t, buffer.String(), `component="tset"`)
} }
func TestLogSilent(t *testing.T) { func TestLogSilent(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Debug().Log("debug")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Info().Log("info") logger.Info().Log("info")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Warn().Log("warn") logger.Warn().Log("warn")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Error().Log("error") logger.Error().Log("error")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
} }
@@ -108,26 +108,26 @@ func TestLogDebug(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Debug().Log("debug")
writer.Flush() 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() buffer.Reset()
logger.Info().Log("info") logger.Info().Log("info")
writer.Flush() 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() buffer.Reset()
logger.Warn().Log("warn") logger.Warn().Log("warn")
writer.Flush() 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() buffer.Reset()
logger.Error().Log("error") logger.Error().Log("error")
writer.Flush() 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() buffer.Reset()
} }
@@ -135,26 +135,26 @@ func TestLogInfo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Debug().Log("debug")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Info().Log("info") logger.Info().Log("info")
writer.Flush() 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() buffer.Reset()
logger.Warn().Log("warn") logger.Warn().Log("warn")
writer.Flush() 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() buffer.Reset()
logger.Error().Log("error") logger.Error().Log("error")
writer.Flush() 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() buffer.Reset()
} }
@@ -162,26 +162,26 @@ func TestLogWarn(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Debug().Log("debug")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Info().Log("info") logger.Info().Log("info")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Warn().Log("warn") logger.Warn().Log("warn")
writer.Flush() 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() buffer.Reset()
logger.Error().Log("error") logger.Error().Log("error")
writer.Flush() 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() buffer.Reset()
} }
@@ -189,25 +189,25 @@ func TestLogError(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
writer := bufio.NewWriter(&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") logger.Debug().Log("debug")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Info().Log("info") logger.Info().Log("info")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Warn().Log("warn") logger.Warn().Log("warn")
writer.Flush() writer.Flush()
assert.Equal(t, 0, buffer.Len(), "Buffer should be empty") require.Equal(t, 0, buffer.Len(), "Buffer should be empty")
buffer.Reset() buffer.Reset()
logger.Error().Log("error") logger.Error().Log("error")
writer.Flush() 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() 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 { type Writer interface {
Write(e *Event) error 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 { type jsonWriter struct {
writer io.Writer writer io.Writer
level Level
formatter Formatter formatter Formatter
} }
func NewJSONWriter(w io.Writer, level Level) Writer { func NewJSONWriter(w io.Writer) Writer {
writer := &jsonWriter{ writer := &jsonWriter{
writer: w, writer: w,
level: level,
formatter: NewJSONFormatter(), formatter: NewJSONFormatter(),
} }
@@ -32,25 +64,21 @@ func NewJSONWriter(w io.Writer, level Level) Writer {
} }
func (w *jsonWriter) Write(e *Event) error { 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)) _, err := w.writer.Write(w.formatter.Bytes(e))
return err return err
} }
func (w *jsonWriter) Close() {}
type consoleWriter struct { type consoleWriter struct {
writer io.Writer writer io.Writer
level Level
formatter Formatter formatter Formatter
} }
func NewConsoleWriter(w io.Writer, level Level, useColor bool) Writer { func NewConsoleWriter(w io.Writer, useColor bool) Writer {
writer := &consoleWriter{ writer := &consoleWriter{
writer: w, writer: w,
level: level,
} }
color := useColor color := useColor
@@ -71,15 +99,13 @@ func NewConsoleWriter(w io.Writer, level Level, useColor bool) Writer {
} }
func (w *consoleWriter) Write(e *Event) error { 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)) _, err := w.writer.Write(w.formatter.Bytes(e))
return err return err
} }
func (w *consoleWriter) Close() {}
type topicWriter struct { type topicWriter struct {
writer Writer writer Writer
topics map[string]struct{} topics map[string]struct{}
@@ -112,6 +138,10 @@ func (w *topicWriter) Write(e *Event) error {
return err return err
} }
func (w *topicWriter) Close() {
w.writer.Close()
}
type levelRewriter struct { type levelRewriter struct {
writer Writer writer Writer
rules []levelRewriteRule rules []levelRewriteRule
@@ -182,6 +212,10 @@ rules:
return w.writer.Write(e) return w.writer.Write(e)
} }
func (w *levelRewriter) Close() {
w.writer.Close()
}
type syncWriter struct { type syncWriter struct {
mu sync.Mutex mu sync.Mutex
writer Writer writer Writer
@@ -193,11 +227,15 @@ func NewSyncWriter(writer Writer) Writer {
} }
} }
func (s *syncWriter) Write(e *Event) error { func (w *syncWriter) Write(e *Event) error {
s.mu.Lock() w.mu.Lock()
defer s.mu.Unlock() defer w.mu.Unlock()
return s.writer.Write(e) return w.writer.Write(e)
}
func (w *syncWriter) Close() {
w.writer.Close()
} }
type multiWriter struct { type multiWriter struct {
@@ -212,8 +250,8 @@ func NewMultiWriter(writer ...Writer) Writer {
return mw return mw
} }
func (m *multiWriter) Write(e *Event) error { func (w *multiWriter) Write(e *Event) error {
for _, w := range m.writer { for _, w := range w.writer {
if err := w.Write(e); err != nil { if err := w.Write(e); err != nil {
return err return err
} }
@@ -222,6 +260,12 @@ func (m *multiWriter) Write(e *Event) error {
return nil return nil
} }
func (w *multiWriter) Close() {
for _, w := range w.writer {
w.Close()
}
}
type BufferWriter interface { type BufferWriter interface {
Writer Writer
Events() []*Event Events() []*Event
@@ -230,13 +274,10 @@ type BufferWriter interface {
type bufferWriter struct { type bufferWriter struct {
lines *ring.Ring lines *ring.Ring
lock sync.RWMutex lock sync.RWMutex
level Level
} }
func NewBufferWriter(level Level, lines int) BufferWriter { func NewBufferWriter(lines int) BufferWriter {
b := &bufferWriter{ b := &bufferWriter{}
level: level,
}
if lines > 0 { if lines > 0 {
b.lines = ring.New(lines) b.lines = ring.New(lines)
@@ -245,33 +286,31 @@ func NewBufferWriter(level Level, lines int) BufferWriter {
return b return b
} }
func (b *bufferWriter) Write(e *Event) error { func (w *bufferWriter) Write(e *Event) error {
if b.level < e.Level || e.Level == Lsilent { w.lock.Lock()
return nil defer w.lock.Unlock()
}
b.lock.Lock() if w.lines != nil {
defer b.lock.Unlock() w.lines.Value = e.clone()
w.lines = w.lines.Next()
if b.lines != nil {
b.lines.Value = e.clone()
b.lines = b.lines.Next()
} }
return nil return nil
} }
func (b *bufferWriter) Events() []*Event { func (w *bufferWriter) Close() {}
func (w *bufferWriter) Events() []*Event {
var lines = []*Event{} var lines = []*Event{}
if b.lines == nil { if w.lines == nil {
return lines return lines
} }
b.lock.RLock() w.lock.RLock()
defer b.lock.RUnlock() defer w.lock.RUnlock()
b.lines.Do(func(l interface{}) { w.lines.Do(func(l interface{}) {
if l == nil { if l == nil {
return return
} }
@@ -281,3 +320,32 @@ func (b *bufferWriter) Events() []*Event {
return lines 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() { 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")) configfile := store.Location(os.Getenv("CORE_CONFIGFILE"))
@@ -54,6 +62,8 @@ func main() {
signal.Notify(quit, os.Interrupt) signal.Notify(quit, os.Interrupt)
<-quit <-quit
logger.Close()
// Stop the app // Stop the app
app.Destroy() app.Destroy()
} }