-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Re-introduce terse configuration #274
Conversation
@akshayjshah, thanks for your PR! By analyzing the history of the files in this pull request, we identified @jcorbin and @prashantv to be potential reviewers. |
zapcore/encoder.go
Outdated
@@ -80,16 +182,32 @@ type ObjectEncoder interface { | |||
// arrays even though they aren't typical in Go. Like slices, ArrayEncoders | |||
// aren't safe for concurrent use (though typical use shouldn't require locks). | |||
type ArrayEncoder interface { | |||
// Built-in types | |||
PrimitiveArrayEncoder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll break out this change into a separate PR - it was an oversight on my part. Passing an ArrayEncoder
to a TimeEncoder
opens us up to infinite recursion :/
1c6b27f
to
75f58f4
Compare
75f58f4
to
a18df2a
Compare
Amend `zapcore.Level`'s text unmarshaling logic to convert the zero-value string to the zero-value Level, then add text unmarshaling support to atomic levels too.
Provide an all-in-one wrapper around the open/tee/lock dance.
a18df2a
to
c9082a0
Compare
config.go
Outdated
// global CPU and I/O load that logging puts on your process while preserving a | ||
// representative subset of your logs. | ||
// | ||
// See zapcore.Sample for details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we mention that these values are per-second?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
config.go
Outdated
Development bool `json:"development",yaml:"development"` | ||
// DisableCaller stops annotating logs with the calling function's file | ||
// name and line number. | ||
DisableCaller bool `json:"disable_caller",yaml:"disable_caller"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason for snake case in JSON? I tend to use the google JSON style guide which suggests camelCase
https://google.github.io/styleguide/jsoncstyleguide.xml#Property_Name_Format
I'm not sure what's appropriate for YAML, don't think there's any standard, I've seen hyphernated-case
as well as camelCase
and snake_case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like everyone just ports the convention from their primary programming language. FB's API is snake-cased, Twitter's API is snake-cased, and the portions of the AWS APIs I've used are snake-cased.
I don't really care much though. Happy to change if you have a strong preference.
config.go
Outdated
// DisableCaller stops annotating logs with the calling function's file | ||
// name and line number. | ||
DisableCaller bool `json:"disable_caller",yaml:"disable_caller"` | ||
// DisableStacktrace completely disables automatic stacktrace capturing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add information about when stack traces are captured (looks like error or higher for prod, and warn or higher for dev)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great minds do indeed think alike. 👍 👍
config.go
Outdated
EncodeTime: zapcore.EpochTimeEncoder, | ||
EncodeDuration: zapcore.SecondsDurationEncoder, | ||
}, | ||
OutputPaths: []string{"stdout"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the default output for logs be stderr?
logrus seems to use stderr by default: https://godoc.org/github.com/sirupsen/logrus#Logger
As does the standard go log
package.
(Ideally production configs would actually use a file, but that's not a great default)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, sounds good. This is yet another place where Go's standard lib chose to go down an unusual route :(
We'll have to disagree about prod logs - I still think they should log to standard out, and it's the process manager's job to pipe that to a file. That's the 12-factor standard.
config.go
Outdated
EncodeTime: zapcore.ISO8601TimeEncoder, | ||
EncodeDuration: zapcore.StringDurationEncoder, | ||
}, | ||
OutputPaths: []string{"stdout"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as earlier, might want to use stderr for all logs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.
} | ||
|
||
if cfg.Sampling != nil { | ||
opts = append(opts, WrapFacility(func(fac zapcore.Facility) zapcore.Facility { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(just a question) is there a difference between wrapping the facility when passing it into New
vs calling WrapFacility
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. This was just a bit easier organizationally - otherwise creating the facility needed its own helper method.
writer.go
Outdated
return zapcore.AddSync(ioutil.Discard), nil | ||
} | ||
writers, err := open(paths) | ||
return zapcore.Lock(zapcore.MultiWriteSyncer(writers...)), err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should MultiWriteSyncer
always lock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure exactly what you're asking here. Since we're opening paths, the WriteSyncers in question are files, so we always need a lock.
In general, though, I don't think MultiWriteSyncers should lock - it's perfectly reasonable to write to both a file and a Kafka client, in which case locking should happen underneath the tee layer.
writer.go
Outdated
func Open(paths ...string) (zapcore.WriteSyncer, error) { | ||
if len(paths) == 0 { | ||
return zapcore.AddSync(ioutil.Discard), nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we special case len(paths) == 1
and return the WriteSyncer
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
writer.go
Outdated
writers = append(writers, os.Stderr) | ||
continue | ||
} | ||
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we never read the file, I think we can use O_WRONLY
.
For the mask, it is then umasked, so create uses 0666
:
https://golang.org/src/os/file.go?s=7623:7662#L248
We should probably do the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
zapcore/sampler.go
Outdated
// Sample creates a facility that samples incoming entries. | ||
// Sample creates a Facility that samples incoming entries, which caps the CPU | ||
// and I/O load of logging while preserving a representative subset of your | ||
// logs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we mention the "optimized for performance over correctness" here as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I was saving that until the sampler fix lands, but this is a perfectly fine time to add it.
config.go
Outdated
) | ||
|
||
// SamplingConfig sets a sampling strategy for the logger. Sampling caps the | ||
// global CPU and I/O load that logging puts on your process while preserving a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to soften this to "while attempting to preserve a".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// Development puts the logger in development mode, which changes the | ||
// behavior of DPanicLevel and takes stacktraces more liberally. | ||
Development bool `json:"development",yaml:"development"` | ||
// DisableCaller stops annotating logs with the calling function's file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Say more about when it happens if not enabled, always? Only for warn and up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
config.go
Outdated
// DisableCaller stops annotating logs with the calling function's file | ||
// name and line number. | ||
DisableCaller bool `json:"disable_caller",yaml:"disable_caller"` | ||
// DisableStacktrace completely disables automatic stacktrace capturing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Say more about when it happens if not enabled, always? Only for error and up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// level, so calling Config.Level.SetLevel will atomically change the log | ||
// level of all loggers descended from this config. The zero value is | ||
// InfoLevel. | ||
Level AtomicLevel `json:"level",yaml:"level"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a:
Level Level
AllowLevelChange bool
And defaulting to static levels?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? The performance overhead is near-zero, and I'd always recommend using a dynamic level.
config.go
Outdated
DisableStacktrace bool `json:"disable_stacktrace",yaml:"disable_stacktrace"` | ||
// Sampling sets a sampling policy. A nil SamplingConfig disables sampling. | ||
Sampling *SamplingConfig `json:"sampling",yaml:"sampling"` | ||
// Encoding sets the logger's encoding. Valid values are "json" and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting, I would've expected more of a:
EnableConsole bool // maybe disable instead of enable
DisableStandardErrors bool
FileEncoding string
OutputPaths []string
ErrorOutputPaths []string
The idea being:
- stdout is always console encoded
- files are always json encoded
- errors at least go to stderr, and maybe also a file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad that we can support that setup, but I'm less of a fan of making that part of the quickstart. I remembered discussing it with you and played with it for a while, and I found it pretty jarring. IMO it's just too unusual.
If and when we add new encoders, I'd also like this config struct to support them. Again, I have msgpack in production in mind.
level.go
Outdated
// "panic", and "fatal"). | ||
func (lvl *AtomicLevel) UnmarshalText(text []byte) error { | ||
if lvl.l == nil { | ||
lvl.l = atomic.NewInt32(int32(InfoLevel)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to defer thus until after we've parsed without err.
level.go
Outdated
if err := l.UnmarshalText(text); err != nil { | ||
return err | ||
} | ||
lvl.SetLevel(l) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to:
if lvl.l == nil {
lvl.l = atomic.NewInt32(int32(l))
} else {
lvl.SetLevel(l)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
logger.go
Outdated
@@ -105,6 +99,16 @@ func (log *Logger) Named(s string) *Logger { | |||
return l | |||
} | |||
|
|||
// WithOptions clones the current Logger, applies the supplied Options, and | |||
// returns the result. It's safe to use concurrently. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"... and returns the clone." (... "cloned child logger." ?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
writer.go
Outdated
writers = append(writers, os.Stderr) | ||
continue | ||
} | ||
f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this were a default:
, you wouldn't need those continue
s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but then I'd need another level of nesting. To me, this is similar to an early return. Reasonable?
zapcore/sampler.go
Outdated
@@ -65,7 +65,13 @@ func fnv32a(s string) uint32 { | |||
return hash | |||
} | |||
|
|||
// Sample creates a facility that samples incoming entries. | |||
// Sample creates a Facility that samples incoming entries, which caps the CPU | |||
// and I/O load of logging while preserving a representative subset of your |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "attempts to preserve a"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Addressed all comments except:
I'll hold off on rebasing until all CR is resolved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, I can live with it :-)
Re-introduce a nice, terse way to configure sane-by-default loggers. Also, introduce a configuration struct for loggers, since many applications want to drive logger configuration from config instead of code.
This PR re-introduces a nice, terse way to configure sane-by-default loggers. It also introduces a configuration struct for loggers, since many applications want to drive logger configuration from config instead of code.