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

WIP: Sentry logger #184

Closed
wants to merge 22 commits into from
Closed

WIP: Sentry logger #184

wants to merge 22 commits into from

Conversation

glibsm
Copy link
Contributor

@glibsm glibsm commented Nov 18, 2016

This Sentry Logger can be used in combination with the forthcoming TeeLogger to easily report errors, or other types of logs as well, to Sentry.

It is written in a separate package zap/sentry, so the users of zap do not inherit a transitive dependence, but I think that quite a few if the current users might be interested in this Sentry integration.

It allows for somewhat flexible setup about how and when to send sentry packets through Capturer interface. Provided with it are two examples: memory one used for testing and a non-blocking one (without checking the outcome of the packet send). If speed is no main concern, another BlockingCapturer could be added in the future that will wait on the outcome of the packet send.

TODO: Benchmark?

}

// Default to live sentry packet capturer
if l.Capturer == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a Client option instead of the dsn string?

  • then you wouldn't need an error return here, since it's the caller's responsibility to create and pass a raven client
  • other capturer forms could be handled by other options (e.g. a BatchedClient option)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is one small problem with this @jcorbin : it means that people using this logger would have to import raven-go as well as zap/sentry (which also imports raven-go) into their projects. If the two api versions don't match then there be trouble.

I wonder if this is a safer approach, even though it requires some more error validation.

Copy link
Contributor

@akshayjshah akshayjshah Nov 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible for a single Go binary to have two versions of the same package running around - this isn't node! :)

Agreed with @jcorbin - we should take a Client here instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akshayjshah I'm not for a moment suggesting that there will be two versions of a binary running around, I was simply talking about glide and dependency management.

This change would mean that people using zap/sentry would also have to import raven-go directly in order to use the sentry logger. That means they could lock in the version of raven-go in their glide.yaml, which then can lead to trouble. We run into this situation pretty frequently with uberfx, since we're acting as a middle man for a lot of other libraries.

If both of you don't think that it is going to be a usability issue, I have no problem with making the change otherwise.

}

// WithCapturer allows to specify which packet capturer should be used
func WithCapturer(c Capturer) func(l *Logger) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This With prefix isn't a pattern that the core zap options follow, maybe we should drop it here too?

}
}

// WithMinLevel provides a slice of levels for which sentry packaet should be sent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not actually a slice anymore, artifact of prior form?

Ideally, the sentry logger should use the new unified level options introduced by #180; however the sentry logger here doesn't currently take any of the core zap options for its embedded Meta.


// WithTraceCfg allows to change the number of skipped frames, number of context lines and
// list of go prefixes that are considered "in-app", i.e. "github.com/uber-go/zap"
func WithTraceCfg(skip, context int, prefixes []string) func(l *Logger) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could (probably should) just be three separate options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started that way, then consolidated :(


// Panic sends Sentry information provided minimum threshold is met
func (l *Logger) Panic(msg string, fields ...zap.Field) {
l.log(zap.PanicLevel, msg, fields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should panec() after log

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have never heard of panEc ;)
will make the change


// Fatal sends Sentry information provided minimum threshold is met
func (l *Logger) Fatal(msg string, fields ...zap.Field) {
l.log(zap.FatalLevel, msg, fields)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should os.Exit(1) after log.

@akshayjshah maybe we should move the _exit symbol into a sub-package like github.com/uber-go/zap/internal so that it can be shared by other zap-included concrete implementations for testing? (as bonus, this would be one less reason why tests couldn't be in their *_test package form)


// Notify sentry if the log level meets minimum threshold
func (l *Logger) log(lvl zap.Level, msg string, fields []zap.Field) {
if lvl >= l.minLevel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be using Meta.Enabled, if we could do away with the minLevel field here and just use Meta's leveling support

Copy link
Contributor

@akshayjshah akshayjshah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments throughout; the most important are about the way we're handling the extra fields, which looks incorrect.

Please also add some benchmarks here; I suspect that using a zwrap.KeyValueMap and copying repeatedly will be very, very slow.

//
// Allows for a variety of implementations of how to send Sentry packets.
// For more performance sensisitve systems, it might make sense to batch
// rather than opening op a connection on each send.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: "sensitive" and "up". There are a number of other typos in the comments here - can you run a spell checker?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't know what's vscode's problem. I've fixed it once before to run spell check in comments, seems like I need to look into it again.

// THE SOFTWARE.

// Package sentry provides a logger which will automatically send events to sentry
// over a certain set threshold
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make doc comments full sentences, with periods or other punctuation at the ends of sentences.

raven "github.com/getsentry/raven-go"
"github.com/pkg/errors"
"github.com/uber-go/zap"
"github.com/uber-go/zap/zwrap"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also follow our in-house style guide and group imports. The zap imports should be in a group above the third-party imports.

zap.InfoLevel: raven.INFO,
zap.WarnLevel: raven.WARNING,
zap.ErrorLevel: raven.ERROR,
zap.PanicLevel: raven.ERROR,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is raven.FATAL more appropriate here? I'd definitely want to distinguish panic-level logs from garden-variety errors.

Let's also include zap.DebugLevel, just for completeness.

var (
_platform = "go"
_traceContextLines = 3
_traceSkipFrames = 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these three be constants instead?


// Notify sentry if the log level meets minimum threshold.
func (l *Logger) log(lvl zap.Level, msg string, fields []zap.Field) {
if lvl >= l.minLevel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to structure your code to minimize indentation (similar to https://github.com/golang/go/wiki/CodeReviewComments#indent-error-flow) - in this case, return early if the level doesn't meet the min, which allows the meat of the method to be de-dented.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good tip, thank you.

extra := l.extra
for _, f := range fields {
f.AddTo(extra)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this is mutating the receiver, which isn't what we want.

}
}

l.Capture(packet)
Copy link
Contributor

@akshayjshah akshayjshah Nov 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for now. You may want to talk to the upstream raven-go maintainers to see if they're willing to expose a more performant API. Perhaps CaptureBytes([]byte), so we can use zap's JSONEncoder and incrementally serialize this payload, passing the JSON-encoded bytes here? If that doesn't work, I can't imagine it's that difficult to fire off HTTP requests to the Sentry API without using raven-go.

/cc @sectioneight

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is definitely on the list.

There are a bunch of PRs/issues into raven-go along the same lines: not very flexible external API. We plan to help with the situation if we can.

Even though, I still think using their lower-level API to capture a packet is better than doing it by hand ourselves, but could be convinced either way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see what the benchmarks look like.


type memCapturer struct {
packets []*raven.Packet
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is anyone going to use the memCapturer outside the tests in this package? If not, let's un-export all these types and remove the WithCapturer option - your tests can just mutate logger.Capturer.

)

// Logger automatically sends logs above a certain threshold to Sentry.
type Logger struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export this type, or can New just return a zap.Logger? If possible, I'd prefer that - you can cast back to a *logger in your tests.

@glibsm glibsm changed the title Sentry logger WIP: Sentry logger Nov 30, 2016
@glibsm glibsm closed this Dec 8, 2016
@akshayjshah akshayjshah deleted the sentry-logger branch January 24, 2017 17:24
@HJFinch
Copy link

HJFinch commented Dec 24, 2017

Hey there,
do you have any information why this was closed/deleted? The topic itself would be very interesting and it would be a great feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants