Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add option to split streams on severity #671

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ func init() {
// Can be any io.Writer, see below for File example
log.SetOutput(os.Stdout)

// Output log messages with error severity or above to a separate io.Writer
// Can be any io.Writer
log.SetErrOutput(os.Stderr)

// Only log the warning severity or above.
log.SetLevel(log.WarnLevel)
}
Expand Down
6 changes: 5 additions & 1 deletion entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ func (entry Entry) log(level Level, msg string) {
entry.Logger.mu.Unlock()
} else {
entry.Logger.mu.Lock()
_, err = entry.Logger.Out.Write(serialized)
if entry.Logger.ErrOut != nil && entry.Level <= ErrorLevel {
_, err = entry.Logger.ErrOut.Write(serialized)
} else {
_, err = entry.Logger.Out.Write(serialized)
}
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
Expand Down
8 changes: 8 additions & 0 deletions exported.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ func SetOutput(out io.Writer) {
std.Out = out
}

// SetErrOutput sets an optional, dedidated io.Writer for log messages with
// error severity or above
func SetErrOutput(errOut io.Writer) {
std.mu.Lock()
defer std.mu.Unlock()
std.ErrOut = errOut
}

// SetFormatter sets the standard logger formatter.
func SetFormatter(formatter Formatter) {
std.mu.Lock()
Expand Down
3 changes: 2 additions & 1 deletion formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const defaultTimestampFormat = time.RFC3339
//
// Any additional fields added with `WithField` or `WithFields` are also in
// `entry.Data`. Format is expected to return an array of bytes which are then
// logged to `logger.Out`.
// logged to `logger.Out` (or `logger.ErrOut`, if configured and warranted by
// the severity).
type Formatter interface {
Format(*Entry) ([]byte, error)
}
Expand Down
19 changes: 11 additions & 8 deletions logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ type Logger struct {
// file, or leave it default which is `os.Stderr`. You can also set this to
// something more adventorous, such as logging to Kafka.
Out io.Writer
// An optional, separate io.Writer for log messages with error severity or
// above
ErrOut io.Writer
// Hooks for the logger instance. These allow firing events based on logging
// levels and log entries. For example, to send errors to an error tracking
// service, log to StatsD or dump the core on fatal errors.
Hooks LevelHooks
// All log entries pass through the formatter before logged to Out. The
// included formatters are `TextFormatter` and `JSONFormatter` for which
// TextFormatter is the default. In development (when a TTY is attached) it
// logs with colors, but to a file it wouldn't. You can easily implement your
// own that implements the `Formatter` interface, see the `README` or included
// formatters for examples.
// All log entries pass through the formatter before being logged to Out or
// ErrOut. The included formatters are `TextFormatter` and `JSONFormatter` for
// which TextFormatter is the default. In development (when a TTY is attached)
// it logs with colors, but to a file it wouldn't. You can easily implement
// your own that implements the `Formatter` interface, see the `README` or
// included formatters for examples.
Formatter Formatter
// The logging level the logger should log at. This is typically (and defaults
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
Expand Down Expand Up @@ -55,8 +58,8 @@ func (mw *MutexWrap) Disable() {
}

// Creates a new logger. Configuration should be set by changing `Formatter`,
// `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own:
// `Out`, `ErrOut`, and `Hooks` directly on the default logger instance. You can
// also just instantiate your own:
//
// var log = &Logger{
// Out: os.Stderr,
Expand Down
53 changes: 53 additions & 0 deletions logrus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,56 @@ func TestEntryWriter(t *testing.T) {
assert.Equal(t, fields["foo"], "bar")
assert.Equal(t, fields["level"], "warning")
}

func TestLogsWithoutSplitStream(t *testing.T) {
var buffer bytes.Buffer

logger := New()
logger.Out = &buffer
logger.Level = DebugLevel

assert.Nil(t, logger.ErrOut)

bufferLen := 0
assert.Equal(t, bufferLen, buffer.Len())
// Assert that this one buffer grows as we log, regardless of severity
for _, logCall := range []func(...interface{}){
logger.Error,
logger.Warn,
logger.Info,
logger.Debug,
} {
logCall("foo")
assert.True(t, buffer.Len() > bufferLen)
bufferLen = buffer.Len()
}
}

func TestLogsWithSplitStream(t *testing.T) {
var buffer, errBuffer bytes.Buffer

logger := New()
logger.Out = &buffer
logger.ErrOut = &errBuffer
logger.Level = DebugLevel

bufferLen := 0
errBufferLen := 0
assert.Equal(t, bufferLen, buffer.Len())
assert.Equal(t, errBufferLen, buffer.Len())
// Assert that ONLY buffer grows as we log with severity below error
for _, logCall := range []func(...interface{}){
logger.Warn,
logger.Info,
logger.Debug,
} {
logCall("foo")
assert.True(t, buffer.Len() > bufferLen)
assert.Equal(t, errBufferLen, errBuffer.Len())
bufferLen = buffer.Len()
}
// Assert that ONLY errBuffer grows as we log with error severity
logger.Error("foo")
assert.True(t, errBuffer.Len() > errBufferLen)
assert.Equal(t, bufferLen, buffer.Len())
}
15 changes: 12 additions & 3 deletions text_formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,18 @@ type TextFormatter struct {
QuoteEmptyFields bool

// Whether the logger's out is to a terminal
isTerminal bool
isOutTerminal bool

// Whether the logger's errOut is to a terminal
isErrOutTerminal bool

sync.Once
}

func (f *TextFormatter) init(entry *Entry) {
if entry.Logger != nil {
f.isTerminal = f.checkIfTerminal(entry.Logger.Out)
f.isOutTerminal = f.checkIfTerminal(entry.Logger.Out)
f.isErrOutTerminal = f.checkIfTerminal(entry.Logger.ErrOut)
}
}

Expand Down Expand Up @@ -99,7 +103,12 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {

f.Do(func() { f.init(entry) })

isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors
var isColored bool
if entry.Logger.ErrOut != nil && entry.Level <= ErrorLevel {
isColored = (f.ForceColors || f.isErrOutTerminal) && !f.DisableColors
} else {
isColored = (f.ForceColors || f.isOutTerminal) && !f.DisableColors
}

timestampFormat := f.TimestampFormat
if timestampFormat == "" {
Expand Down