On 8/15/17 7:14 AM, Chris Hines wrote:
I would be curious what you think of github.com/go-kit/kit/log (and
related "sub"-packages). See my talk about it's design here:
Video: https://youtu.be/ojhFQNgyZO4?list=FLcxNiie7-UD8znndwDn7PFw
Slides: https://speakerdeck.com/chrishines/go-kit-log-package
I can see what you're going for with that, and in a way it's
demonstrating some sugar issues in go. For some reason, folk expect to
be able to write logging statements without an awful lot of unsightly
brackets and constructors :-).
To quickly round them up, this module uses what I consider to be Perl
5-esque "slurpy hashes":
// Unstructured
log.Printf("HTTP server listening on %s", addr)
// Structured
logger.Log("transport","HTTP","addr",
addr,"msg","listening")<https://github.com/go-kit/kit/tree/master/log#usage>
Whereas logrus steers you towards a map[string]interface{}:
func main() {
log.WithFields(log.Fields{
"animal":"walrus",
}).Info("A walrus appears")
}
And zap uses type-safe individual field constructors:
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt",3),
zap.Duration("backoff", time.Second),
)
Of these, I lean towards the zap style because it's not significantly
more verbose (IMHO), it's more type safe, and faster.
However, the syntax sugar issue has meant that zap now also have a
similar interface to kit:
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt",3,
"backoff", time.Second,
)
One common element is the ability to create a logger with tags pre-built:
// kit
logger = log.With(logger,"instance_id",123)
// logrus
logger = logrus.WithFields(logrus.Fields{"instance_id": 123})
// zap
logger = logger.With(zap.Int("instance_id", 123))
IMHO appropriate use of this feature is critical to sane log analysis as
soon as your app gets non-trivial: distributed, sharded, microservices,
async, etc - non-linear, multiuser and multicore: it can simply save you
down the track if you tag the request ID, customer ID, and particularly
the value of a loop variable for log statements inside inner loops.
So I guess, what I found was that once I did that using zap, I really
don't care that much about the syntax sugar. I would write things like:
for i, v := range arr {
logger := logger.With(zap.Int("itemIdx", i))
if err := v.Process(); err != nil {
logger.Error("failed to process", zap.Object("error", err))
}
}
And so the individual log statements don't go over the magic length at
which blub programmers scoff, turn up their noses and say, "see, this is
why go is so much worse than blub". It's also a good abstraction. And
did I mention it's fast? I really like the way that it never combines
the log line into something heavy like a map[string]interface{}, and
that it just logs its fields in the order they were passed - so it's
more like a traditional log line, just with JSON boundaries.
So anyway, yeah that's where I stand on that API - I can see how you got
there, and I watched a structured logging talk at GopherCon 2016 which
set me down this path, but after investigation I just couldn't trade
performance, memory efficiency and a good abstraction for a little
syntax sugar.
I'm not quite sure what the language or stdlib could do to make this
better, other than shipping something like zap in stdlib. It's never
really appropriate to use anything other than stdlib logging to log with
in a library, and that's the nub of the problem: it means libraries will
pretty much always have unstructured logging. The best you can do is to
log each "printf" argument to its own field, but that's not a very good
answer especially with the "message" is "%s: %s %s" or something.
Sam
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.