-
We're trying to extend Zap by making it send errors to Sentry through a new In sentryCore := sentrycore.New()
loggerConfig := logging.ZapConfig() // "logging" just has our shared configuration, nothing special
loggerConfig.Level = logLevel
logger, err := loggerConfig.Build()
if err != nil {
log.Fatal(err)
}
withSentry := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
return zapcore.NewTee(&sentryCore, c)
})
logger = logger.WithOptions(withSentry).With(zap.String("test", "value")) The problem is that if we use // in middleware.go
withSentry := zap.WrapCore(func(alreadyWrappedOnce zapcore.Core) zapcore.Core {
return zapcore.NewTee(&sentryCore, alreadyWrappedOnce)
}) But calling What we want to do in the middleware:
I think what we'd need for this is a way of disassembling the original Maybe I can't see the forest for the trees and there's a much better solution? Big picture is we want some way of sending errors to sentry and having all the logger fields added to the Sentry scope for that error. So hooks won't work for this. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hey @cideM. Depending on what information you need in the middleware, Minimal information As a starting point: if the information you need is all present in a log For background, the func (c *sentryCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
if ent.LoggerName != "default" {
return ce.AddCore(ent, c.otherCore)
}
return ce.AddCore(ent, c)
} Per-request information from middleware If the information the Sentry Core needs must come from the middleware, For example, suppose that your middleware is extracting a type SentryInfo struct {
HubAddr string
Tags []string
}
func WithSentryInfo(info SentryInfo) zapcore.Field {
return zapcore.Field{
Key: "__sentryInfo",
// Important to use SkipType here so that if this field accidentally
// makes it to another Core, the system just ignores it.
Type: zapcore.SkipType,
Interface: info,
// Note that ^this operation costs an allocation.
}
} The middleware will use the above with the Logger's logger = logger.With(WithSentryInfo(SentryInfo{...})) In the Sentry Core's With method, you'll keep an eye out for a SentryInfo func (c *sentryCore) With(fields []zapcore.Field) zapcore.Core {
// In-place slice filtering.
// See https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating.
newFields := fields[:0]
addr := c.addr
tags := c.tags
for _, f := range fields {
if f.Key == "__sentryInfo" && f.Type == zapcore.SkipType {
if info, ok := f.Interface.(SentryInfo); ok {
addr = info.HubAddr
tags = append(tags, info.Tags...)
continue // don't keep this field
}
}
newFields = append(newFields, f)
}
fields = newFields
return &sentryCore{
addr: addr,
tags: tags,
// ...whatever you do with the fields right now...
}
} Again, this is a bit of a hack, but it exploits the Per-log-site information This is super unlikely, func (c *sentryCore) Write(ent zapcore.Entry, fields []zapcore.Field) error {
newFields := fields[:0]
addr := c.addr
tags := c.tags
for _, f := range fields {
// ...same code as above...
}
fields = newFields
client := c.client.ForAddr(addr)
// ...
} So any log site can do: log.Info("foo", WithSentryInfo(override)) Hope this helps with your problem. |
Beta Was this translation helpful? Give feedback.
Hey @cideM. Depending on what information you need in the middleware,
there are a couple ways to make your Sentry Core behave nicely here.
Minimal information
As a starting point: if the information you need is all present in a log
entry's metadata (the
Entry
), you can do this work in aCore.Check
implementation.
For background, the
Core.Check
method is expected to return aCheckedEntry
on which it has registered one or more other cores that are interested in
logging that entry.
There's nothing stopping the Sentry Core from putting a different instance
on the checked entry: