Skip to content

Commit

Permalink
Ability to build logger with custom SinkFactories
Browse files Browse the repository at this point in the history
  • Loading branch information
dimroc committed Apr 11, 2018
1 parent f85c78b commit e41e067
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 26 deletions.
42 changes: 38 additions & 4 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
package zap

import (
"io"
"io/ioutil"
"os"
"sort"
"time"

Expand Down Expand Up @@ -164,12 +167,19 @@ func NewDevelopmentConfig() Config {

// Build constructs a logger from the Config and Options.
func (cfg Config) Build(opts ...Option) (*Logger, error) {
return cfg.BuildWithSinks(DefaultSinkFactories(), opts...)
}

// BuildWithSinks uses the map of sink factories to constrct a logger from the
// passed options. For example, the option "stdout" will construct a logger
// that routes output to the stdout sink.
func (cfg Config) BuildWithSinks(sf map[string]SinkFactory, opts ...Option) (*Logger, error) {
enc, err := cfg.buildEncoder()
if err != nil {
return nil, err
}

sink, errSink, err := cfg.openSinks()
sink, errSink, err := cfg.openSinks(sf)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -225,12 +235,12 @@ func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
return opts
}

func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
sink, closeOut, err := Open(cfg.OutputPaths...)
func (cfg Config) openSinks(sf map[string]SinkFactory) (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
sink, closeOut, err := Open(sf, cfg.OutputPaths...)
if err != nil {
return nil, nil, err
}
errSink, _, err := Open(cfg.ErrorOutputPaths...)
errSink, _, err := Open(sf, cfg.ErrorOutputPaths...)
if err != nil {
closeOut()
return nil, nil, err
Expand All @@ -241,3 +251,27 @@ func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error)
func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
return newEncoder(cfg.Encoding, cfg.EncoderConfig)
}

// SinkFactory defines the Create() method used to create sink writers and
// closers.
type SinkFactory interface {
Create() (zapcore.WriteSyncer, io.Closer)
}

// DefaultSinkFactories generates a map of regularly used sink factories,
// like stdout and stderr.
func DefaultSinkFactories() map[string]SinkFactory {
return map[string]SinkFactory{
"stdout": stdFactory{os.Stdout},
"stderr": stdFactory{os.Stderr},
}
}

type stdFactory struct {
io *os.File
}

// Create returns the wrapped io and a no-op closer.
func (s stdFactory) Create() (zapcore.WriteSyncer, io.Closer) {
return s.io, ioutil.NopCloser(s.io)
}
38 changes: 19 additions & 19 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package zap

import (
"io"
"io/ioutil"
"os"

Expand All @@ -29,15 +30,17 @@ import (
"go.uber.org/multierr"
)

// Open is a high-level wrapper that takes a variadic number of paths, opens or
// creates each of the specified files, and combines them into a locked
// WriteSyncer. It also returns any error encountered and a function to close
// any opened files.
// Open is a high-level wrapper that takes a map of sink factories and a
// variadic number of paths. The map of sink factories customize how to open
// and close a particular path, with the default being the creation of a file
// at said path.
// It then combines all of the writers into a locked WriteSyncer. It also
// returns any error encountered and a function to close any opened files.
//
// Passing no paths returns a no-op WriteSyncer. The special paths "stdout" and
// "stderr" are interpreted as os.Stdout and os.Stderr, respectively.
func Open(paths ...string) (zapcore.WriteSyncer, func(), error) {
writers, close, err := open(paths)
func Open(sf map[string]SinkFactory, paths ...string) (zapcore.WriteSyncer, func(), error) {
writers, close, err := open(sf, paths)
if err != nil {
return nil, nil, err
}
Expand All @@ -46,31 +49,28 @@ func Open(paths ...string) (zapcore.WriteSyncer, func(), error) {
return writer, close, nil
}

func open(paths []string) ([]zapcore.WriteSyncer, func(), error) {
func open(sf map[string]SinkFactory, paths []string) ([]zapcore.WriteSyncer, func(), error) {
var openErr error
writers := make([]zapcore.WriteSyncer, 0, len(paths))
files := make([]*os.File, 0, len(paths))
closers := make([]io.Closer, 0, len(paths))
close := func() {
for _, f := range files {
f.Close()
for _, c := range closers {
c.Close()
}
}
for _, path := range paths {
switch path {
case "stdout":
writers = append(writers, os.Stdout)
// Don't close standard out.
continue
case "stderr":
writers = append(writers, os.Stderr)
// Don't close standard error.
factory, ok := sf[path]
if ok {
writer, closer := factory.Create()
writers = append(writers, writer)
closers = append(closers, closer)
continue
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
openErr = multierr.Append(openErr, err)
if err == nil {
writers = append(writers, f)
files = append(files, f)
closers = append(closers, f)
}
}

Expand Down
6 changes: 3 additions & 3 deletions writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
)

func TestOpenNoPaths(t *testing.T) {
ws, cleanup, err := Open()
ws, cleanup, err := Open(DefaultSinkFactories())
defer cleanup()

assert.NoError(t, err, "Expected opening no paths to succeed.")
Expand Down Expand Up @@ -65,7 +65,7 @@ func TestOpen(t *testing.T) {
}

for _, tt := range tests {
wss, cleanup, err := open(tt.paths)
wss, cleanup, err := open(DefaultSinkFactories(), tt.paths)
if err == nil {
defer cleanup()
}
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestOpenFails(t *testing.T) {
}

for _, tt := range tests {
_, cleanup, err := Open(tt.paths...)
_, cleanup, err := Open(DefaultSinkFactories(), tt.paths...)
require.Nil(t, cleanup, "Cleanup function should never be nil")
assert.Error(t, err, "Open with non-existent directory should fail")
}
Expand Down

0 comments on commit e41e067

Please sign in to comment.