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

New API for package log (post-GopherCon) #76

Merged
merged 11 commits into from Jul 15, 2015

Conversation

Projects
None yet
5 participants
@peterbourgon
Member

peterbourgon commented Jul 11, 2015

Show outdated Hide outdated log2/logfmt.go
@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 11, 2015

Member

@peterbourgon I plan to work on integrating this code into the real log package. I wanted to let you know to avoid duplicated effort.

Member

ChrisHines commented Jul 11, 2015

@peterbourgon I plan to work on integrating this code into the real log package. I wanted to let you know to avoid duplicated effort.

@peterbourgon

This comment has been minimized.

Show comment
Hide comment
@peterbourgon

peterbourgon Jul 11, 2015

Member

@ChrisHines Sure! I'll leave it alone for the weekend.

Member

peterbourgon commented Jul 11, 2015

@ChrisHines Sure! I'll leave it alone for the weekend.

@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 13, 2015

Member

Still a WIP. I kept log.With instead of naming it log.NewContext. Think about which you like better.

The above commit allows

lc := log.With(logger, "k1", "v2")
lc = lc.With("k2", "v2")
lc.Log("k3", "v3")

Curiously, the following is equivalent to the above:

lc := log.With(logger, "k1", "v2")
lc = log.With(lc, "k2", "v2")   // use the package func instead of method on Context.
lc.Log("k3", "v3")

Is the convenience worth the weight of having two ways to do the same thing?

Also, should we add the following interface and define log.With to check for and return a WithLogger instead of a *Context?

type WithLogger interface {
    Logger
    With(keyvals ...interface{}) WithLogger
}
Member

ChrisHines commented Jul 13, 2015

Still a WIP. I kept log.With instead of naming it log.NewContext. Think about which you like better.

The above commit allows

lc := log.With(logger, "k1", "v2")
lc = lc.With("k2", "v2")
lc.Log("k3", "v3")

Curiously, the following is equivalent to the above:

lc := log.With(logger, "k1", "v2")
lc = log.With(lc, "k2", "v2")   // use the package func instead of method on Context.
lc.Log("k3", "v3")

Is the convenience worth the weight of having two ways to do the same thing?

Also, should we add the following interface and define log.With to check for and return a WithLogger instead of a *Context?

type WithLogger interface {
    Logger
    With(keyvals ...interface{}) WithLogger
}
@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 13, 2015

Member

Responding to my above comment, maybe the Context constructor should look like:

func NewContext(logger Logger) *Context

Usage would then look like below, and it would avoid the overlap between the constructor and With method without increasing verbosity much.

lc := log.NewContext(logger).With("k1", "v1")
lc = lc.With("k2", "v2")
lc.Log("k3", "v3")

I've talked myself into this change already. Commit coming soon.

Member

ChrisHines commented Jul 13, 2015

Responding to my above comment, maybe the Context constructor should look like:

func NewContext(logger Logger) *Context

Usage would then look like below, and it would avoid the overlap between the constructor and With method without increasing verbosity much.

lc := log.NewContext(logger).With("k1", "v1")
lc = lc.With("k2", "v2")
lc.Log("k3", "v3")

I've talked myself into this change already. Commit coming soon.

@peterbourgon

This comment has been minimized.

Show comment
Hide comment
@peterbourgon

peterbourgon Jul 13, 2015

Member

Feedback:

  • I vaguely like NewContext better than With, as it's more explicit about the fact that it allocates a new object.
  • I vaguely dislike package funcs that take objects as arguments because it seems to invert the natural relationship between data and behavior. That is, I expect the package to hold types and constructors, and behavior to be implemented on the types. So if you see value in log.With I guess that's fine as long as we also keep Context.With and Logger.With.
  • I don't see any reason to elide the keyval args from the constructor, as they're optional.
Member

peterbourgon commented Jul 13, 2015

Feedback:

  • I vaguely like NewContext better than With, as it's more explicit about the fact that it allocates a new object.
  • I vaguely dislike package funcs that take objects as arguments because it seems to invert the natural relationship between data and behavior. That is, I expect the package to hold types and constructors, and behavior to be implemented on the types. So if you see value in log.With I guess that's fine as long as we also keep Context.With and Logger.With.
  • I don't see any reason to elide the keyval args from the constructor, as they're optional.
@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 13, 2015

Member

@peterbourgon Thanks for the feedback. I think we already talked about your first two points at GopherCon and I'm happy with them.

As for your last point, I might have agreed until I started writing the documentation. Eliding the keyvals from the constructor simplified the docs quite a bit. In general maintaining the separation of concerns feels to me like less is more in this case. It also means that you only have to look for one identifier when searching for places where context is added.

I have a new issue to raise. I think we agreed last week that leveled logging should be split into its own package. I have started working on that change and have run into a problem. The design for leveled logging we worked on at the conference requires access to the unexported fields of log.Context to maintain the desired placement of the level key/value pair in the overall sequence. The best solution to this problem I have come up with so far is to add a Context.WithPrefix method that allows adding keyvals to the beginning of a context. Thoughts?

Member

ChrisHines commented Jul 13, 2015

@peterbourgon Thanks for the feedback. I think we already talked about your first two points at GopherCon and I'm happy with them.

As for your last point, I might have agreed until I started writing the documentation. Eliding the keyvals from the constructor simplified the docs quite a bit. In general maintaining the separation of concerns feels to me like less is more in this case. It also means that you only have to look for one identifier when searching for places where context is added.

I have a new issue to raise. I think we agreed last week that leveled logging should be split into its own package. I have started working on that change and have run into a problem. The design for leveled logging we worked on at the conference requires access to the unexported fields of log.Context to maintain the desired placement of the level key/value pair in the overall sequence. The best solution to this problem I have come up with so far is to add a Context.WithPrefix method that allows adding keyvals to the beginning of a context. Thoughts?

@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 14, 2015

Member

The new API ideas are fully integrated and ready for review. Please take a look.

Member

ChrisHines commented Jul 14, 2015

The new API ideas are fully integrated and ready for review. Please take a look.

warnValue: "warn",
errorValue: "error",
critValue: "crit",
}

This comment has been minimized.

@peterbourgon

peterbourgon Jul 14, 2015

Member

Since we already have a Levels struct, can we put these config params directly in it, and have the Option type take a Levels struct (pointer)? One less type to worry about.

@peterbourgon

peterbourgon Jul 14, 2015

Member

Since we already have a Levels struct, can we put these config params directly in it, and have the Option type take a Levels struct (pointer)? One less type to worry about.

This comment has been minimized.

@ChrisHines

ChrisHines Jul 14, 2015

Member

Yes, I have combined them.

@ChrisHines

ChrisHines Jul 14, 2015

Member

Yes, I have combined them.

@peterbourgon

This comment has been minimized.

Show comment
Hide comment
@peterbourgon

peterbourgon Jul 14, 2015

Member

I really like the feel of this. It looks like you've ported everything to package log, so I've deleted package log2. Minus that one comment about the Levels option struct, I'm good to go with this!

Member

peterbourgon commented Jul 14, 2015

I really like the feel of this. It looks like you've ported everything to package log, so I've deleted package log2. Minus that one comment about the Levels option struct, I'm good to go with this!

Show outdated Hide outdated log/levels/levels.go
Show outdated Hide outdated log/levels/levels.go
@ChrisHines

This comment has been minimized.

Show comment
Hide comment
@ChrisHines

ChrisHines Jul 14, 2015

Member

One final review and then I think we can merge this.

Member

ChrisHines commented Jul 14, 2015

One final review and then I think we can merge this.

@peterbourgon

This comment has been minimized.

Show comment
Hide comment
@peterbourgon

peterbourgon Jul 15, 2015

Member

This seems to be heading back toward what we have in the master branch with a separate logger/context for each level. The downside to this approach is that each call to Levels.With must update all the leveled contexts. It seems difficult to justify the overhead of calling With on this many contexts because to break even the application must create more than five subsequent log events in the scope of the context.

I've repeated this reasoning a couple of times in chat, now. To pre-empt further questions, can we put this explanation as a doc comment, or comment on the appropriate method/type?

Member

peterbourgon commented Jul 15, 2015

This seems to be heading back toward what we have in the master branch with a separate logger/context for each level. The downside to this approach is that each call to Levels.With must update all the leveled contexts. It seems difficult to justify the overhead of calling With on this many contexts because to break even the application must create more than five subsequent log events in the scope of the context.

I've repeated this reasoning a couple of times in chat, now. To pre-empt further questions, can we put this explanation as a doc comment, or comment on the appropriate method/type?

@peterbourgon

This comment has been minimized.

Show comment
Hide comment
@peterbourgon

peterbourgon Jul 15, 2015

Member

Otherwise :shipit:!

Member

peterbourgon commented Jul 15, 2015

Otherwise :shipit:!

Document implementation tradeoff and reason for choosing to favor a c…
…heap WIth method at the expense of the Log method.

@ChrisHines ChrisHines changed the title from [WIP] New API for package log (post-GopherCon) to New API for package log (post-GopherCon) Jul 15, 2015

ChrisHines added a commit that referenced this pull request Jul 15, 2015

Merge pull request #76 from go-kit/gophercon-log-api
New API for package log (post-GopherCon)

Fixes #63 and some usability concerns.

@ChrisHines ChrisHines merged commit 8edd386 into master Jul 15, 2015

3 checks passed

ci/circleci Your tests passed on CircleCI!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details
import "github.com/go-kit/kit/log"
// Levels provides a leveled logging wrapper around a logger. It has five
// levels: debug, info, warning (warn), error, and critical (crit). If you

This comment has been minimized.

@sasha-s

sasha-s Jul 15, 2015

The way log levels are usually (in my practice) used:

  • there is a way to suppress levels up to a minimum severity
  • there is a way to configure a log sink for each log level (so that critical ones a teed to some alerting mechanism for example)

How does it work with levels.Levels?

@sasha-s

sasha-s Jul 15, 2015

The way log levels are usually (in my practice) used:

  • there is a way to suppress levels up to a minimum severity
  • there is a way to configure a log sink for each log level (so that critical ones a teed to some alerting mechanism for example)

How does it work with levels.Levels?

This comment has been minimized.

@peterbourgon

peterbourgon Jul 15, 2015

Member

This Levels type is very basic and doesn't support any of those use cases directly. To get them, you'd need to implement a new Levels type, which I think would be straightforward. If you think your usage generalizes to a lot of other organizations, I'd be happy to review a PR to add it as an additional type in package levels!

@peterbourgon

peterbourgon Jul 15, 2015

Member

This Levels type is very basic and doesn't support any of those use cases directly. To get them, you'd need to implement a new Levels type, which I think would be straightforward. If you think your usage generalizes to a lot of other organizations, I'd be happy to review a PR to add it as an additional type in package levels!

This comment has been minimized.

@ChrisHines

ChrisHines Jul 15, 2015

Member

I was expecting we would add support for something along those lines in the future. Before we cram it all into levels.Levels though, consider if it can be achieved with a level aware log.Logger.

@ChrisHines

ChrisHines Jul 15, 2015

Member

I was expecting we would add support for something along those lines in the future. Before we cram it all into levels.Levels though, consider if it can be achieved with a level aware log.Logger.

This comment has been minimized.

@sasha-s

sasha-s Jul 15, 2015

I am not sure how to do it with the current implementation of log levels.
I would need to extract current log level from the context so I can switch on it etc.
Also, what is the semantics for setting log level multiple times?
(in case of JSON sink the last one wins, if it is a regular loggger they are all printed).

@sasha-s

sasha-s Jul 15, 2015

I am not sure how to do it with the current implementation of log levels.
I would need to extract current log level from the context so I can switch on it etc.
Also, what is the semantics for setting log level multiple times?
(in case of JSON sink the last one wins, if it is a regular loggger they are all printed).

This comment has been minimized.

@peterbourgon

peterbourgon Jul 15, 2015

Member

If I understand you correctly, you wouldn't introspect the keyvals for a level key. Rather, you'd construct each leveled logger so that it's connected to the appropriate sink, and enabled/disabled according to the configured minimum severity.

If you supply multiple level keys, they will stack. Keyvals deliberately preserve order and don't dedupe.

@peterbourgon

peterbourgon Jul 15, 2015

Member

If I understand you correctly, you wouldn't introspect the keyvals for a level key. Rather, you'd construct each leveled logger so that it's connected to the appropriate sink, and enabled/disabled according to the configured minimum severity.

If you supply multiple level keys, they will stack. Keyvals deliberately preserve order and don't dedupe.

This comment has been minimized.

@ChrisHines

ChrisHines Jul 15, 2015

Member

@sasha-s Since this PR has already been merged I recommend that you write up the features you want as separate issues and we'll iterate on them.

@ChrisHines

ChrisHines Jul 15, 2015

Member

@sasha-s Since this PR has already been merged I recommend that you write up the features you want as separate issues and we'll iterate on them.

@peterbourgon peterbourgon deleted the gophercon-log-api branch Aug 2, 2015

guycook pushed a commit to codelingo/kit that referenced this pull request Oct 12, 2016

Merge pull request #76 from go-kit/gophercon-log-api
New API for package log (post-GopherCon)

Fixes #63 and some usability concerns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment