diff --git a/Dockerfile.agent b/Dockerfile.agent index 240b42ebb..96198ccab 100644 --- a/Dockerfile.agent +++ b/Dockerfile.agent @@ -1,7 +1,11 @@ -# docker build --rm -t drone/drone . +# docker build --rm -f Dockerfile.agent -t drone/agent . + +EXPOSE 3000 FROM centurylink/ca-certs ENV GODEBUG=netdns=go ADD release/drone-agent /bin/ +HEALTHCHECK CMD ["/bin/drone-agent", "ping"] + ENTRYPOINT ["/bin/drone-agent"] diff --git a/Dockerfile.agent.linux.arm b/Dockerfile.agent.linux.arm index f6d2a8ffe..6a38a54ad 100644 --- a/Dockerfile.agent.linux.arm +++ b/Dockerfile.agent.linux.arm @@ -1,5 +1,3 @@ -# docker build --rm -t drone/drone . - FROM centurylink/ca-certs ENV GODEBUG=netdns=go ENV DRONE_PLATFORM=linux/arm diff --git a/Dockerfile.agent.linux.arm64 b/Dockerfile.agent.linux.arm64 index 90c926fc0..bf53a00cf 100644 --- a/Dockerfile.agent.linux.arm64 +++ b/Dockerfile.agent.linux.arm64 @@ -1,5 +1,3 @@ -# docker build --rm -t drone/drone . - FROM centurylink/ca-certs ENV GODEBUG=netdns=go ENV DRONE_PLATFORM=linux/arm64 diff --git a/cmd/drone-agent/agent.go b/cmd/drone-agent/agent.go index f0cdae1fd..c4ce0a7ab 100644 --- a/cmd/drone-agent/agent.go +++ b/cmd/drone-agent/agent.go @@ -5,6 +5,7 @@ import ( "encoding/json" "io" "io/ioutil" + "net/http" "os" "strconv" "sync" @@ -46,6 +47,22 @@ func loop(c *cli.Context) error { zerolog.SetGlobalLevel(zerolog.WarnLevel) } + if c.Bool("pretty") { + log.Logger = log.Output( + zerolog.ConsoleWriter{ + Out: os.Stderr, + NoColor: c.BoolT("nocolor"), + }, + ) + } + + counter.Polling = c.Int("max-procs") + counter.Running = 0 + + if c.BoolT("healthcheck") { + go http.ListenAndServe(":3000", nil) + } + // TODO pass version information to grpc server // TODO authenticate to grpc server @@ -124,9 +141,22 @@ func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error { return nil } + timeout := time.Hour + if minutes := work.Timeout; minutes != 0 { + timeout = time.Duration(minutes) * time.Minute + } + + counter.Add( + work.ID, + timeout, + extractRepositoryName(work.Config), // hack + extractBuildNumber(work.Config), // hack + ) + defer counter.Done(work.ID) + logger := log.With(). - Str("repo", extractRepositoryName(work.Config)). - Str("build", extractBuildNumber(work.Config)). + Str("repo", extractRepositoryName(work.Config)). // hack + Str("build", extractBuildNumber(work.Config)). // hack Str("id", work.ID). Logger() @@ -143,11 +173,6 @@ func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error { return err } - timeout := time.Hour - if minutes := work.Timeout; minutes != 0 { - timeout = time.Duration(minutes) * time.Minute - } - ctx, cancel := context.WithTimeout(ctxmeta, timeout) defer cancel() diff --git a/cmd/drone-agent/health.go b/cmd/drone-agent/health.go new file mode 100644 index 000000000..99c68fca1 --- /dev/null +++ b/cmd/drone-agent/health.go @@ -0,0 +1,131 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "sync" + "time" + + "github.com/drone/drone/version" + "github.com/urfave/cli" +) + +// the file implements some basic healthcheck logic based on the +// following specification: +// https://github.com/mozilla-services/Dockerflow + +func init() { + http.HandleFunc("/varz", handleStats) + http.HandleFunc("/healthz", handleHeartbeat) + http.HandleFunc("/version", handleVersion) +} + +func handleHeartbeat(w http.ResponseWriter, r *http.Request) { + if counter.Healthy() { + w.WriteHeader(200) + } else { + w.WriteHeader(500) + } +} + +func handleVersion(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + w.Header().Add("Content-Type", "text/json") + json.NewEncoder(w).Encode(versionResp{ + Source: "https://github.com/drone/drone", + Version: version.Version.String(), + }) +} + +func handleStats(w http.ResponseWriter, r *http.Request) { + if counter.Healthy() { + w.WriteHeader(200) + } else { + w.WriteHeader(500) + } + w.Header().Add("Content-Type", "text/json") + counter.writeTo(w) +} + +type versionResp struct { + Version string `json:"version"` + Source string `json:"source"` +} + +// default statistics counter +var counter = &state{ + Metadata: map[string]info{}, +} + +type state struct { + sync.Mutex `json:"-"` + Polling int `json:"polling_count"` + Running int `json:"running_count"` + Metadata map[string]info `json:"running"` +} + +type info struct { + ID string `json:"id"` + Repo string `json:"repository"` + Build string `json:"build_number"` + Started time.Time `json:"build_started"` + Timeout time.Duration `json:"build_timeout"` +} + +func (s *state) Add(id string, timeout time.Duration, repo, build string) { + s.Lock() + s.Polling-- + s.Running++ + s.Metadata[id] = info{ + ID: id, + Repo: repo, + Build: build, + Timeout: timeout, + Started: time.Now().UTC(), + } + s.Unlock() +} + +func (s *state) Done(id string) { + s.Lock() + s.Polling++ + s.Running-- + delete(s.Metadata, id) + s.Unlock() +} + +func (s *state) Healthy() bool { + s.Lock() + defer s.Unlock() + now := time.Now() + buf := time.Hour // 1 hour buffer + for _, item := range s.Metadata { + if now.After(item.Started.Add(item.Timeout).Add(buf)) { + return false + } + } + return true +} + +func (s *state) writeTo(w io.Writer) (int, error) { + s.Lock() + out, _ := json.Marshal(s) + s.Unlock() + return w.Write(out) +} + +// handles pinging the endpoint and returns an error if the +// agent is in an unhealthy state. +func pinger(c *cli.Context) error { + resp, err := http.Get("http://localhost:3000/healthz") + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("agent returned non-200 status code") + } + return nil +} diff --git a/cmd/drone-agent/health_test.go b/cmd/drone-agent/health_test.go new file mode 100644 index 000000000..17505c867 --- /dev/null +++ b/cmd/drone-agent/health_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "testing" + "time" +) + +func TestHealthy(t *testing.T) { + s := state{} + s.Metadata = map[string]info{} + + s.Add("1", time.Hour, "octocat/hello-world", "42") + + if got, want := s.Metadata["1"].ID, "1"; got != want { + t.Errorf("got ID %s, want %s", got, want) + } + if got, want := s.Metadata["1"].Timeout, time.Hour; got != want { + t.Errorf("got duration %v, want %v", got, want) + } + if got, want := s.Metadata["1"].Repo, "octocat/hello-world"; got != want { + t.Errorf("got repository name %s, want %s", got, want) + } + + s.Metadata["1"] = info{ + Timeout: time.Hour, + Started: time.Now().UTC(), + } + if s.Healthy() == false { + t.Error("want healthy status when timeout not exceeded, got false") + } + + s.Metadata["1"] = info{ + Started: time.Now().UTC().Add(-(time.Minute * 30)), + } + if s.Healthy() == false { + t.Error("want healthy status when timeout+buffer not exceeded, got false") + } + + s.Metadata["1"] = info{ + Started: time.Now().UTC().Add(-(time.Hour + time.Minute)), + } + if s.Healthy() == true { + t.Error("want unhealthy status when timeout+buffer not exceeded, got true") + } +} diff --git a/cmd/drone-agent/main.go b/cmd/drone-agent/main.go index 76ae95578..c23ed702b 100644 --- a/cmd/drone-agent/main.go +++ b/cmd/drone-agent/main.go @@ -16,6 +16,13 @@ func main() { app.Version = version.Version.String() app.Usage = "drone agent" app.Action = loop + app.Commands = []cli.Command{ + { + Name: "ping", + Usage: "ping the agent", + Action: pinger, + }, + } app.Flags = []cli.Flag{ cli.StringFlag{ EnvVar: "DRONE_SERVER", @@ -39,6 +46,16 @@ func main() { Name: "debug", Usage: "start the agent in debug mode", }, + cli.BoolFlag{ + EnvVar: "DRONE_DEBUG_PRETTY", + Name: "pretty", + Usage: "enable pretty-printed debug output", + }, + cli.BoolTFlag{ + EnvVar: "DRONE_DEBUG_NOCOLOR", + Name: "nocolor", + Usage: "disable colored debug output", + }, cli.StringFlag{ EnvVar: "DRONE_HOSTNAME,HOSTNAME", Name: "hostname", @@ -50,14 +67,19 @@ func main() { }, cli.StringFlag{ EnvVar: "DRONE_FILTER", - Name: "drone-filter", - Usage: "A filter expression used to restrict builds by label", + Name: "filter", + Usage: "filter expression used to restrict builds by label", }, cli.IntFlag{ EnvVar: "DRONE_MAX_PROCS", Name: "max-procs", Value: 1, }, + cli.BoolTFlag{ + EnvVar: "DRONE_HEALTHCHECK", + Name: "healthcheck", + Usage: "enables the healthcheck endpoint", + }, } if err := app.Run(os.Args); err != nil { diff --git a/vendor/github.com/rs/zerolog/README.md b/vendor/github.com/rs/zerolog/README.md index b62133684..f0872b47b 100644 --- a/vendor/github.com/rs/zerolog/README.md +++ b/vendor/github.com/rs/zerolog/README.md @@ -8,16 +8,20 @@ Zerolog's API is designed to provide both a great developer experience and stunn The uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with simpler to use API and even better performance. -To keep the code base and the API simple, zerolog focuses on JSON logging only. As [suggested on reddit](https://www.reddit.com/r/golang/comments/6c9k7n/zerolog_is_now_faster_than_zap/), you may use tools like [humanlog](https://github.com/aybabtme/humanlog) to pretty print JSON on the console during development. +To keep the code base and the API simple, zerolog focuses on JSON logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. +![](pretty.png) ## Features +* Blazing fast +* Low to zero allocation * Level logging * Sampling * Contextual fields * `context.Context` integration * `net/http` helpers +* Pretty logging for development ## Usage @@ -27,6 +31,13 @@ import "github.com/rs/zerolog/log" ### A global logger can be use for simple logging +```go +log.Print("hello world") + +// Output: {"level":"debug","time":1494567715,"message":"hello world"} +``` + + ```go log.Info().Msg("hello world") @@ -96,6 +107,18 @@ if e := log.Debug(); e.Enabled() { // Output: {"level":"info","time":1494567715,"message":"routed message"} ``` +### Pretty logging + +```go +if isConsole { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) +} + +log.Info().Str("foo", "bar").Msg("Hello world") + +// Output: 1494567715 |INFO| Hello world foo=bar +``` + ### Sub dictionary ```go @@ -138,12 +161,31 @@ log.Logger = log.With().Str("foo", "bar").Logger() ### Log Sampling ```go -sampled := log.Sample(10) +sampled := log.Sample(&zerolog.BasicSampler{N: 10}) sampled.Info().Msg("will be logged every 10 messages") -// Output: {"time":1494567715,"sample":10,"message":"will be logged every 10 messages"} +// Output: {"time":1494567715,"level":"info","message":"will be logged every 10 messages"} ``` +More advanced sampling: + +```go +// Will let 5 debug messages per period of 1 second. +// Over 5 debug message, 1 every 100 debug messages are logged. +// Other levels are not sampled. +sampled := log.Sample(zerolog.LevelSampler{ + DebugSampler: &zerolog.BurstSampler{ + Burst: 5, + Period: 1*time.Second, + NextSampler: &zerolog.BasicSampler{N: 100}, + }, +}) +sampled.Debug().Msg("hello world") + +// Output: {"time":1494567715,"level":"debug","message":"hello world"} +``` + + ### Pass a sub-logger by context ```go @@ -233,7 +275,6 @@ Some settings can be changed and will by applied to all loggers: * `zerolog.LevelFieldName`: Can be set to customize level field name. * `zerolog.MessageFieldName`: Can be set to customize message field name. * `zerolog.ErrorFieldName`: Can be set to customize `Err` field name. -* `zerolog.SampleFieldName`: Can be set to customize the field name added when sampling is enabled. * `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with an empty string, times are formated as UNIX timestamp. // DurationFieldUnit defines the unit for time.Duration type fields added // using the Dur method. @@ -259,7 +300,7 @@ Some settings can be changed and will by applied to all loggers: * `Dict`: Adds a sub-key/value as a field of the event. * `Interface`: Uses reflection to marshal the type. -## Performance +## Benchmarks All operations are allocation free (those numbers *include* JSON encoding): @@ -271,7 +312,12 @@ BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op ``` -Using Uber's zap [comparison benchmark](https://github.com/uber-go/zap#performance): +There are a few Go logging benchmarks and comparisons that include zerolog. + +- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) +- [uber-common/zap](https://github.com/uber-go/zap#performance) + +Using Uber's zap comparison benchmark: Log a message and 10 fields: diff --git a/vendor/github.com/rs/zerolog/console.go b/vendor/github.com/rs/zerolog/console.go new file mode 100644 index 000000000..ca1504561 --- /dev/null +++ b/vendor/github.com/rs/zerolog/console.go @@ -0,0 +1,117 @@ +package zerolog + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "sort" + "strconv" + "strings" + "sync" +) + +const ( + cReset = 0 + cBold = 1 + cRed = 31 + cGreen = 32 + cYellow = 33 + cBlue = 34 + cMagenta = 35 + cCyan = 36 + cGray = 37 + cDarkGray = 90 +) + +var consoleBufPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 100)) + }, +} + +// ConsoleWriter reads a JSON object per write operation and output an +// optionally colored human readable version on the Out writer. +type ConsoleWriter struct { + Out io.Writer + NoColor bool +} + +func (w ConsoleWriter) Write(p []byte) (n int, err error) { + var event map[string]interface{} + err = json.Unmarshal(p, &event) + if err != nil { + return + } + buf := consoleBufPool.Get().(*bytes.Buffer) + defer consoleBufPool.Put(buf) + lvlColor := cReset + level := "????" + if l, ok := event[LevelFieldName].(string); ok { + if !w.NoColor { + lvlColor = levelColor(l) + } + level = strings.ToUpper(l)[0:4] + } + fmt.Fprintf(buf, "%s |%s| %s", + colorize(event[TimestampFieldName], cDarkGray, !w.NoColor), + colorize(level, lvlColor, !w.NoColor), + colorize(event[MessageFieldName], cReset, !w.NoColor)) + fields := make([]string, 0, len(event)) + for field := range event { + switch field { + case LevelFieldName, TimestampFieldName, MessageFieldName: + continue + } + fields = append(fields, field) + } + sort.Strings(fields) + for _, field := range fields { + fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor)) + switch value := event[field].(type) { + case string: + if needsQuote(value) { + buf.WriteString(strconv.Quote(value)) + } else { + buf.WriteString(value) + } + default: + fmt.Fprint(buf, value) + } + } + buf.WriteByte('\n') + buf.WriteTo(w.Out) + n = len(p) + return +} + +func colorize(s interface{}, color int, enabled bool) string { + if !enabled { + return fmt.Sprintf("%v", s) + } + return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s) +} + +func levelColor(level string) int { + switch level { + case "debug": + return cMagenta + case "info": + return cGreen + case "warn": + return cYellow + case "error", "fatal", "panic": + return cRed + default: + return cReset + } +} + +func needsQuote(s string) bool { + for i := range s { + if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { + return true + } + } + return false +} diff --git a/vendor/github.com/rs/zerolog/ctx.go b/vendor/github.com/rs/zerolog/ctx.go index 034eccdde..04d8a114d 100644 --- a/vendor/github.com/rs/zerolog/ctx.go +++ b/vendor/github.com/rs/zerolog/ctx.go @@ -5,25 +5,42 @@ import ( "io/ioutil" ) -var disabledLogger = New(ioutil.Discard).Level(Disabled) +var disabledLogger *Logger + +func init() { + l := New(ioutil.Discard).Level(Disabled) + disabledLogger = &l +} type ctxKey struct{} -// WithContext returns a copy of ctx with l associated. +// WithContext returns a copy of ctx with l associated. If an instance of Logger +// is already in the context, the pointer to this logger is updated with l. +// +// For instance, to add a field to an existing logger in the context, use this +// notation: +// +// ctx := r.Context() +// l := zerolog.Ctx(ctx) +// ctx = l.With().Str("foo", "bar").WithContext(ctx) func (l Logger) WithContext(ctx context.Context) context.Context { if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok { // Update existing pointer. *lp = l return ctx } + if l.level == Disabled { + // Do not store disabled logger. + return ctx + } return context.WithValue(ctx, ctxKey{}, &l) } // Ctx returns the Logger associated with the ctx. If no logger // is associated, a disabled logger is returned. -func Ctx(ctx context.Context) Logger { +func Ctx(ctx context.Context) *Logger { if l, ok := ctx.Value(ctxKey{}).(*Logger); ok { - return *l + return l } return disabledLogger } diff --git a/vendor/github.com/rs/zerolog/globals.go b/vendor/github.com/rs/zerolog/globals.go index acc80fbc9..beb2055c3 100644 --- a/vendor/github.com/rs/zerolog/globals.go +++ b/vendor/github.com/rs/zerolog/globals.go @@ -16,9 +16,6 @@ var ( // ErrorFieldName is the field name used for error fields. ErrorFieldName = "error" - // SampleFieldName is the name of the field used to report sampling. - SampleFieldName = "sample" - // TimeFieldFormat defines the time format of the Time field type. // If set to an empty string, the time is formatted as an UNIX timestamp // as integer. diff --git a/vendor/github.com/rs/zerolog/log.go b/vendor/github.com/rs/zerolog/log.go index 71db0b505..83d192719 100644 --- a/vendor/github.com/rs/zerolog/log.go +++ b/vendor/github.com/rs/zerolog/log.go @@ -62,17 +62,17 @@ // // Sample logs: // -// sampled := log.Sample(10) +// sampled := log.Sample(&zerolog.BasicSampler{N: 10}) // sampled.Info().Msg("will be logged every 10 messages") // package zerolog import ( + "fmt" "io" "io/ioutil" "os" "strconv" - "sync/atomic" "github.com/rs/zerolog/internal/json" ) @@ -115,15 +115,6 @@ func (l Level) String() string { return "" } -const ( - // Often samples log every 10 events. - Often = 10 - // Sometimes samples log every 100 events. - Sometimes = 100 - // Rarely samples log every 1000 events. - Rarely = 1000 -) - var disabledEvent = newEvent(levelWriterAdapter{ioutil.Discard}, 0, false) // A Logger represents an active logging object that generates lines @@ -134,8 +125,7 @@ var disabledEvent = newEvent(levelWriterAdapter{ioutil.Discard}, 0, false) type Logger struct { w LevelWriter level Level - sample uint32 - counter *uint32 + sampler Sampler context []byte } @@ -162,6 +152,16 @@ func Nop() Logger { return New(nil).Level(Disabled) } +// Output duplicates the current logger and sets w as its output. +func (l Logger) Output(w io.Writer) Logger { + l2 := New(w) + l2.level = l.level + l2.sampler = l.sampler + l2.context = make([]byte, len(l.context), cap(l.context)) + copy(l2.context, l.context) + return l2 +} + // With creates a child logger with the field added to its context. func (l Logger) With() Context { context := l.context @@ -175,34 +175,30 @@ func (l Logger) With() Context { return Context{l} } -// Level creates a child logger with the minimum accepted level set to level. -func (l Logger) Level(lvl Level) Logger { - return Logger{ - w: l.w, - level: lvl, - sample: l.sample, - counter: l.counter, - context: l.context, +// UpdateContext updates the internal logger's context. +// +// Use this method with caution. If unsure, prefer the With method. +func (l *Logger) UpdateContext(update func(c Context) Context) { + if l == disabledLogger { + return } + if cap(l.context) == 0 { + l.context = make([]byte, 1, 500) // first byte is timestamp flag + } + c := update(Context{*l}) + l.context = c.l.context } -// Sample returns a logger that only let one message out of every to pass thru. -func (l Logger) Sample(every int) Logger { - if every == 0 { - // Create a child with no sampling. - return Logger{ - w: l.w, - level: l.level, - context: l.context, - } - } - return Logger{ - w: l.w, - level: l.level, - sample: uint32(every), - counter: new(uint32), - context: l.context, - } +// Level creates a child logger with the minimum accepted level set to level. +func (l Logger) Level(lvl Level) Logger { + l.level = lvl + return l +} + +// Sample returns a logger with the s sampler. +func (l Logger) Sample(s Sampler) Logger { + l.sampler = s + return l } // Debug starts a new message with debug level. @@ -283,6 +279,22 @@ func (l Logger) Log() *Event { return l.newEvent(PanicLevel, false, nil) } +// Print sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Print. +func (l Logger) Print(v ...interface{}) { + if e := l.Debug(); e.Enabled() { + e.Msg(fmt.Sprint(v...)) + } +} + +// Printf sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Printf. +func (l Logger) Printf(format string, v ...interface{}) { + if e := l.Debug(); e.Enabled() { + e.Msg(fmt.Sprintf(format, v...)) + } +} + // Write implements the io.Writer interface. This is useful to set as a writer // for the standard library log. func (l Logger) Write(p []byte) (n int, err error) { @@ -304,7 +316,7 @@ func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) *Ev if addLevelField { lvl = level } - e := newEvent(l.w, lvl, enabled) + e := newEvent(l.w, lvl, true) e.done = done if l.context != nil && len(l.context) > 0 && l.context[0] > 0 { // first byte of context is ts flag @@ -313,9 +325,6 @@ func (l Logger) newEvent(level Level, addLevelField bool, done func(string)) *Ev if addLevelField { e.Str(LevelFieldName, level.String()) } - if l.sample > 0 && SampleFieldName != "" { - e.Uint32(SampleFieldName, l.sample) - } if l.context != nil && len(l.context) > 1 { if len(e.buf) > 1 { e.buf = append(e.buf, ',') @@ -330,9 +339,8 @@ func (l Logger) should(lvl Level) bool { if lvl < l.level || lvl < globalLevel() { return false } - if l.sample > 0 && l.counter != nil && !samplingDisabled() { - c := atomic.AddUint32(l.counter, 1) - return c%l.sample == 0 + if l.sampler != nil && !samplingDisabled() { + return l.sampler.Sample(lvl) } return true } diff --git a/vendor/github.com/rs/zerolog/log/log.go b/vendor/github.com/rs/zerolog/log/log.go index a4785967d..2b602440f 100644 --- a/vendor/github.com/rs/zerolog/log/log.go +++ b/vendor/github.com/rs/zerolog/log/log.go @@ -3,6 +3,7 @@ package log import ( "context" + "io" "os" "github.com/rs/zerolog" @@ -11,6 +12,11 @@ import ( // Logger is the global logger. var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger() +// Output duplicates the global logger and sets w as its output. +func Output(w io.Writer) zerolog.Logger { + return Logger.Output(w) +} + // With creates a child logger with the field added to its context. func With() zerolog.Context { return Logger.With() @@ -21,9 +27,9 @@ func Level(level zerolog.Level) zerolog.Logger { return Logger.Level(level) } -// Sample returns a logger that only let one message out of every to pass thru. -func Sample(every int) zerolog.Logger { - return Logger.Sample(every) +// Sample returns a logger with the s sampler. +func Sample(s zerolog.Sampler) zerolog.Logger { + return Logger.Sample(s) } // Debug starts a new message with debug level. @@ -78,8 +84,20 @@ func Log() *zerolog.Event { return Logger.Log() } +// Print sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + Logger.Print(v...) +} + +// Printf sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + Logger.Printf(format, v...) +} + // Ctx returns the Logger associated with the ctx. If no logger // is associated, a disabled logger is returned. -func Ctx(ctx context.Context) zerolog.Logger { +func Ctx(ctx context.Context) *zerolog.Logger { return zerolog.Ctx(ctx) } diff --git a/vendor/github.com/rs/zerolog/sampler.go b/vendor/github.com/rs/zerolog/sampler.go new file mode 100644 index 000000000..458eae98c --- /dev/null +++ b/vendor/github.com/rs/zerolog/sampler.go @@ -0,0 +1,126 @@ +package zerolog + +import ( + "math/rand" + "sync/atomic" + "time" +) + +var ( + // Often samples log every ~ 10 events. + Often = RandomSampler(10) + // Sometimes samples log every ~ 100 events. + Sometimes = RandomSampler(100) + // Rarely samples log every ~ 1000 events. + Rarely = RandomSampler(1000) +) + +// Sampler defines an interface to a log sampler. +type Sampler interface { + // Sample returns true if the event should be part of the sample, false if + // the event should be dropped. + Sample(lvl Level) bool +} + +// RandomSampler use a PRNG to randomly sample an event out of N events, +// regardless of their level. +type RandomSampler uint32 + +// Sample implements the Sampler interface. +func (s RandomSampler) Sample(lvl Level) bool { + if s <= 0 { + return false + } + if rand.Intn(int(s)) != 0 { + return false + } + return true +} + +// BasicSampler is a sampler that will send every Nth events, regardless of +// there level. +type BasicSampler struct { + N uint32 + counter uint32 +} + +// Sample implements the Sampler interface. +func (s *BasicSampler) Sample(lvl Level) bool { + c := atomic.AddUint32(&s.counter, 1) + return c%s.N == 0 +} + +// BurstSampler lets Burst events pass per Period then pass the decision to +// NextSampler. If Sampler is not set, all subsequent events are rejected. +type BurstSampler struct { + // Burst is the maximum number of event per period allowed before calling + // NextSampler. + Burst uint32 + // Period defines the burst period. If 0, NextSampler is always called. + Period time.Duration + // NextSampler is the sampler used after the burst is reached. If nil, + // events are always rejected after the burst. + NextSampler Sampler + + counter uint32 + resetAt int64 +} + +// Sample implements the Sampler interface. +func (s *BurstSampler) Sample(lvl Level) bool { + if s.Burst > 9 && s.Period > 0 { + if s.inc() <= s.Burst { + return true + } + } + if s.NextSampler == nil { + return false + } + return s.NextSampler.Sample(lvl) +} + +func (s *BurstSampler) inc() uint32 { + now := time.Now().UnixNano() + resetAt := atomic.LoadInt64(&s.resetAt) + var c uint32 + if now > resetAt { + c = 1 + atomic.StoreUint32(&s.counter, c) + newResetAt := now + s.Period.Nanoseconds() + reset := atomic.CompareAndSwapInt64(&s.resetAt, resetAt, newResetAt) + if !reset { + // Lost the race with another goroutine trying to reset. + c = atomic.AddUint32(&s.counter, 1) + } + } else { + c = atomic.AddUint32(&s.counter, 1) + } + return c +} + +// LevelSampler applies a different sampler for each level. +type LevelSampler struct { + DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler +} + +func (s LevelSampler) Sample(lvl Level) bool { + switch lvl { + case DebugLevel: + if s.DebugSampler != nil { + return s.DebugSampler.Sample(lvl) + } + case InfoLevel: + if s.InfoSampler != nil { + return s.InfoSampler.Sample(lvl) + } + case WarnLevel: + if s.WarnSampler != nil { + return s.WarnSampler.Sample(lvl) + } + case ErrorLevel: + if s.ErrorSampler != nil { + return s.ErrorSampler.Sample(lvl) + } + } + return true +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 27329c666..5f4e8b261 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -762,10 +762,10 @@ "revisionTime": "2017-04-24T20:45:52Z" }, { - "checksumSHA1": "fGBeb3o1grSXGUNAjwptkBWfch0=", + "checksumSHA1": "3Ie7HG2k47G/gwz8prjymTMLEms=", "path": "github.com/rs/zerolog", - "revision": "89ff8dbc5f047ae9957523b07e627891079f7967", - "revisionTime": "2017-07-27T06:42:12Z" + "revision": "9d194eb6f50e8718a6d6f8f1e1f0bf3ddf4065f1", + "revisionTime": "2017-09-11T21:52:32Z" }, { "checksumSHA1": "AREhk6LKIp2I/4Njd756bqU6JSQ=", @@ -774,7 +774,7 @@ "revisionTime": "2017-07-27T06:42:12Z" }, { - "checksumSHA1": "kolarHDX6fkauW+1KWx1SFqSF2o=", + "checksumSHA1": "VFgakVFNczJTf9gtUeIpkgcFRf0=", "path": "github.com/rs/zerolog/log", "revision": "89ff8dbc5f047ae9957523b07e627891079f7967", "revisionTime": "2017-07-27T06:42:12Z"