Skip to content

Commit

Permalink
Merge pull request #19 from voi-go/add-metrics-to-logger
Browse files Browse the repository at this point in the history
Add possibility for metrics from the zap logger
  • Loading branch information
drPytho committed May 16, 2020
2 parents b2bb0bf + 3e46832 commit 7811f59
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 84 deletions.
116 changes: 113 additions & 3 deletions logger.go
Expand Up @@ -4,22 +4,132 @@ import (
"os"
"time"

"github.com/blendle/zapdriver"
"github.com/prometheus/client_golang/prometheus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

func newLogger(level zapcore.Level, encoder zapcore.Encoder) (*zap.Logger, zap.AtomicLevel) {
func (s *SVC) newLogger(level zapcore.Level, encoder zapcore.Encoder) (*zap.Logger, zap.AtomicLevel) {
atom := zap.NewAtomicLevel()
atom.SetLevel(level)

zapOpts := append(s.zapOpts, zap.ErrorOutput(zapcore.Lock(os.Stderr)), zap.AddCaller())

logger := zap.New(zapcore.NewSampler(zapcore.NewCore(
encoder,
zapcore.Lock(os.Stdout),
atom,
), time.Second, 100, 10),
zap.ErrorOutput(zapcore.Lock(os.Stderr)),
zap.AddCaller(),
zapOpts...,
)

return logger, atom
}

// WithZapMetrics will add a hook to the zap logger and emit metrics to prometheus
// based on log level and log name.
func WithZapMetrics() Option {
return func(s *SVC) error {
requestCount := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "logger_emitted_entries",
Help: "Number of log messages emitted.",
},
[]string{"level", "logger_name"},
)
if err := prometheus.Register(requestCount); err != nil {
return err
}

s.zapOpts = append(s.zapOpts,
zap.Hooks(func(e zapcore.Entry) error {
counter, err := requestCount.GetMetricWithLabelValues(e.Level.String(), e.LoggerName)
if err != nil {
return err
}
counter.Inc()
return nil
}))
return nil
}
}

// WithLogger is an option that allows you to provide your own customized logger.
func WithLogger(logger *zap.Logger, atom zap.AtomicLevel) Option {
return func(s *SVC) error {
return assignLogger(s, logger, atom)
}
}

// WithDevelopmentLogger is an option that uses a zap Logger with
// configurations set meant to be used for development.
func WithDevelopmentLogger() Option {
return func(s *SVC) error {
logger, atom := s.newLogger(
zapcore.DebugLevel,
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
)
logger = logger.With(zap.String("app", s.Name), zap.String("version", s.Version))
return assignLogger(s, logger, atom)
}
}

// WithProductionLogger is an option that uses a zap Logger with configurations
// set meant to be used for production.
func WithProductionLogger() Option {
return func(s *SVC) error {
logger, atom := s.newLogger(
zapcore.InfoLevel,
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
)
logger = logger.With(zap.String("app", s.Name), zap.String("version", s.Version))
return assignLogger(s, logger, atom)
}
}

// WithConsoleLogger is an option that uses a zap Logger with configurations
// set meant to be used for debugging in the console.
func WithConsoleLogger(level zapcore.Level) Option {
return func(s *SVC) error {
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.RFC3339TimeEncoder

logger, atom := s.newLogger(
level,
zapcore.NewConsoleEncoder(config),
)
return assignLogger(s, logger, atom)
}
}

// WithStackdriverLogger is an option that uses a zap Logger with configurations
// set meant to be used for production and is compliant with the GCP/Stackdriver format.
func WithStackdriverLogger(level zapcore.Level) Option {
return func(s *SVC) error {
logger, atom := s.newLogger(
level,
zapcore.NewJSONEncoder(zapdriver.NewProductionEncoderConfig()),
)
logger = logger.With(zapdriver.ServiceContext(s.Name), zapdriver.Label("version", s.Version))
return assignLogger(s, logger, atom)
}
}

func assignLogger(s *SVC, logger *zap.Logger, atom zap.AtomicLevel) error {
stdLogger, err := zap.NewStdLogAt(logger, zapcore.ErrorLevel)
if err != nil {
return err
}
undo, err := zap.RedirectStdLogAt(logger, zapcore.ErrorLevel)
if err != nil {
return err
}

s.logger = logger
s.stdLogger = stdLogger
s.atom = atom
s.loggerRedirectUndo = undo

return nil
}
81 changes: 0 additions & 81 deletions options.go
Expand Up @@ -7,11 +7,9 @@ import (
"net/http/pprof"
"time"

"github.com/blendle/zapdriver"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

// Option defines SVC's option type.
Expand Down Expand Up @@ -44,85 +42,6 @@ func WithRouter(router *http.ServeMux) Option {
}
}

// WithLogger is an option that allows you to provide your own customized logger.
func WithLogger(logger *zap.Logger, atom zap.AtomicLevel) Option {
return func(s *SVC) error {
return assignLogger(s, logger, atom)
}
}

// WithDevelopmentLogger is an option that uses a zap Logger with
// configurations set meant to be used for development.
func WithDevelopmentLogger() Option {
return func(s *SVC) error {
logger, atom := newLogger(
zapcore.DebugLevel,
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
)
logger = logger.With(zap.String("app", s.Name), zap.String("version", s.Version))
return assignLogger(s, logger, atom)
}
}

// WithProductionLogger is an option that uses a zap Logger with configurations
// set meant to be used for production.
func WithProductionLogger() Option {
return func(s *SVC) error {
logger, atom := newLogger(
zapcore.InfoLevel,
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
)
logger = logger.With(zap.String("app", s.Name), zap.String("version", s.Version))
return assignLogger(s, logger, atom)
}
}

// WithConsoleLogger is an option that uses a zap Logger with configurations
// set meant to be used for debugging in the console.
func WithConsoleLogger(level zapcore.Level) Option {
return func(s *SVC) error {
config := zap.NewProductionEncoderConfig()
config.EncodeTime = zapcore.RFC3339TimeEncoder

logger, atom := newLogger(
level,
zapcore.NewConsoleEncoder(config),
)
return assignLogger(s, logger, atom)
}
}

// WithStackdriverLogger is an option that uses a zap Logger with configurations
// set meant to be used for production and is compliant with the GCP/Stackdriver format.
func WithStackdriverLogger(level zapcore.Level) Option {
return func(s *SVC) error {
logger, atom := newLogger(
level,
zapcore.NewJSONEncoder(zapdriver.NewProductionEncoderConfig()),
)
logger = logger.With(zapdriver.ServiceContext(s.Name), zapdriver.Label("version", s.Version))
return assignLogger(s, logger, atom)
}
}

func assignLogger(s *SVC, logger *zap.Logger, atom zap.AtomicLevel) error {
stdLogger, err := zap.NewStdLogAt(logger, zapcore.ErrorLevel)
if err != nil {
return err
}
undo, err := zap.RedirectStdLogAt(logger, zapcore.ErrorLevel)
if err != nil {
return err
}

s.logger = logger
s.stdLogger = stdLogger
s.atom = atom
s.loggerRedirectUndo = undo

return nil
}

// WithLogLevelHandlers is an option that sets up HTTP routes to read write the
// log level.
func WithLogLevelHandlers() Option {
Expand Down
1 change: 1 addition & 0 deletions svc.go
Expand Up @@ -31,6 +31,7 @@ type SVC struct {
signals chan os.Signal

logger *zap.Logger
zapOpts []zap.Option
stdLogger *log.Logger
atom zap.AtomicLevel
loggerRedirectUndo func()
Expand Down

0 comments on commit 7811f59

Please sign in to comment.