Skip to content

Commit

Permalink
New log encoder to report errors through stackdriver (#42)
Browse files Browse the repository at this point in the history
* new encoder,  zapdriver config

* Update logger.go

* stacktrace into message only on ErrorLevel

* inherit from zapcore.Encoder

* alter error with >= zapcore.ErrorLevel

* if stacktrace is present put it in the message

* test of grpc interceptor report on error

* Revert "test of grpc interceptor report on error"

This reverts commit 5accb62.

* implement clone function
  • Loading branch information
michalderdak authored Mar 17, 2021
1 parent 225ad19 commit f7e60f5
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 36 deletions.
42 changes: 42 additions & 0 deletions encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package zapvml

import (
"regexp"

"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
)

func newEncoder(cfg zapcore.EncoderConfig) (zapcore.Encoder, error) {
return &Encoder{zapcore.NewJSONEncoder(cfg)}, nil
}

// Wraps zapcore.Encoder to customize stack traces to be picked up by Stackdriver error reporting.
// The following issue might make this unnecessary:
// https://github.com/uber-go/zap/issues/514
type Encoder struct {
zapcore.Encoder
}

// multiline pattern to match the function name line
var functionNamePattern = regexp.MustCompile(`(?m)^(\S+)$`)

func (s *Encoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
if ent.Stack != "" {
// Make the message look like a real panic, so Stackdriver error reporting picks it up.
// This used to need the string "panic: " at the beginning, but no longer seems to need it!
// ent.Message = "panic: " + ent.Message + "\n\ngoroutine 1 [running]:\n"
ent.Message = ent.Message + "\n\ngoroutine 1 [running]:\n"

// Trial-and-error: On App Engine Standard go111 the () are needed after function calls
// zap does not add them, so hack it with a regexp
replaced := functionNamePattern.ReplaceAllString(ent.Stack, "$1(...)")
ent.Message += replaced
ent.Stack = ""
}
return s.Encoder.EncodeEntry(ent, fields)
}

func (s *Encoder) Clone() zapcore.Encoder {
return &Encoder{s.Encoder.Clone()}
}
56 changes: 20 additions & 36 deletions logger.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package zapvml

import (
"os"

"github.com/blendle/zapdriver"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
Expand All @@ -18,8 +16,9 @@ var (
)

type Config struct {
Level zapcore.Level `required:"true" default:"warn"`
Debug bool `required:"true" default:"false"`
Level zapcore.Level `required:"true" default:"warn"`
Debug bool `required:"true" default:"false"`
ServiceName string `required:"true" default:"default_service"`
}

func Init(globalLevel zapcore.Level) {
Expand All @@ -34,43 +33,28 @@ func init() {
panic(err)
}

Level = zap.NewAtomicLevelAt(cfg.Level)

// High-priority output should also go to standard error, and low-priority
// output should also go to standard out.
// It is useful for Kubernetes deployment.
// Kubernetes interprets os.Stdout log items as INFO and os.Stderr log items
// as ERROR by default.
highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= Level.Level() && lvl < zapcore.ErrorLevel
})

// Output channels
consoleInfos := zapcore.Lock(os.Stdout)
consoleErrors := zapcore.Lock(os.Stderr)
err := zap.RegisterEncoder("stackdriver-json", newEncoder)
if err != nil {
panic(err)
}

// Setup Config and Encoder
var ecfg zapcore.EncoderConfig
var enc zapcore.Encoder
var config zap.Config
if cfg.Debug {
ecfg = zapdriver.NewDevelopmentEncoderConfig()
enc = zapcore.NewConsoleEncoder(ecfg)
config = zapdriver.NewDevelopmentConfig()
} else {
ecfg = zapdriver.NewProductionEncoderConfig()
enc = zapcore.NewJSONEncoder(ecfg)
config = zapdriver.NewProductionConfig()
}

config.Encoding = "stackdriver-json"

Log, err = config.Build(zapdriver.WrapCore(
zapdriver.ReportAllErrors(true),
zapdriver.ServiceName(cfg.ServiceName),
))
if err != nil {
panic(err)
}

// Join the outputs, encoders, and level-handling functions into
// zapcore.
core := zapcore.NewTee(
zapcore.NewCore(enc, consoleErrors, highPriority),
zapcore.NewCore(enc, consoleInfos, lowPriority),
)
// From a zapcore.Core, it's easy to construct a Logger.
Log = zap.New(core)
zap.RedirectStdLog(Log)
}

Expand Down

0 comments on commit f7e60f5

Please sign in to comment.