Skip to content
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

logp: add rate limited logger #256

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,43 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


--------------------------------------------------------------------------------
Dependency : golang.org/x/time
Version: v0.8.0
Licence type (autodetected): BSD-3-Clause
--------------------------------------------------------------------------------

Contents of probable licence file $GOMODCACHE/golang.org/x/[email protected]/LICENSE:

Copyright 2009 The Go Authors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


--------------------------------------------------------------------------------
Dependency : gopkg.in/mcuadros/go-syslog.v2
Version: v2.3.0
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
golang.org/x/net v0.26.0
golang.org/x/sys v0.21.0
golang.org/x/text v0.16.0
golang.org/x/time v0.8.0
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
gopkg.in/yaml.v2 v2.4.0
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down
95 changes: 68 additions & 27 deletions logp/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,30 @@ import (
"bytes"
"fmt"
"io"
"time"

"go.elastic.co/ecszap"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/time/rate"
)

// LogOption configures a Logger.
type LogOption = zap.Option

// Logger logs messages to the configured output.
type Logger struct {
logger *zap.Logger
sugar *zap.SugaredLogger
logger *zap.Logger
sugar *zap.SugaredLogger
limiter *rate.Sometimes
}

func newLogger(rootLogger *zap.Logger, selector string, options ...LogOption) *Logger {
log := rootLogger.
WithOptions(zap.AddCallerSkip(1)).
WithOptions(options...).
Named(selector)
return &Logger{log, log.Sugar()}
return &Logger{log, log.Sugar(), nil}
}

// NewLogger returns a new Logger labeled with the name of the selector. This
Expand Down Expand Up @@ -81,62 +84,70 @@ func NewInMemory(selector string, encCfg zapcore.EncoderConfig) (*Logger, *bytes
return logger, &buff
}

func (l *Logger) do(f func()) {
if l.limiter != nil {
l.limiter.Do(f)
return
}
f()
}

mauri870 marked this conversation as resolved.
Show resolved Hide resolved
// WithOptions returns a clone of l with options applied.
func (l *Logger) WithOptions(options ...LogOption) *Logger {
cloned := l.logger.WithOptions(options...)
return &Logger{cloned, cloned.Sugar()}
return &Logger{cloned, cloned.Sugar(), nil}
}

// With creates a child logger and adds structured context to it. Fields added
// to the child don't affect the parent, and vice versa.
func (l *Logger) With(args ...interface{}) *Logger {
sugar := l.sugar.With(args...)
return &Logger{sugar.Desugar(), sugar}
return &Logger{sugar.Desugar(), sugar, nil}
}

// Named adds a new path segment to the logger's name. Segments are joined by
// periods.
func (l *Logger) Named(name string) *Logger {
logger := l.logger.Named(name)
return &Logger{logger, logger.Sugar()}
return &Logger{logger, logger.Sugar(), nil}
}

// Sprint

// Debug uses fmt.Sprint to construct and log a message.
func (l *Logger) Debug(args ...interface{}) {
l.sugar.Debug(args...)
l.do(func() { l.sugar.Debug(args...) })
}

// Info uses fmt.Sprint to construct and log a message.
func (l *Logger) Info(args ...interface{}) {
l.sugar.Info(args...)
l.do(func() { l.sugar.Info(args...) })
}

// Warn uses fmt.Sprint to construct and log a message.
func (l *Logger) Warn(args ...interface{}) {
l.sugar.Warn(args...)
l.do(func() { l.sugar.Warn(args...) })
}

// Error uses fmt.Sprint to construct and log a message.
func (l *Logger) Error(args ...interface{}) {
l.sugar.Error(args...)
l.do(func() { l.sugar.Error(args...) })
}

// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit(1).
func (l *Logger) Fatal(args ...interface{}) {
l.sugar.Fatal(args...)
l.do(func() { l.sugar.Fatal(args...) })
}

// Panic uses fmt.Sprint to construct and log a message, then panics.
func (l *Logger) Panic(args ...interface{}) {
l.sugar.Panic(args...)
l.do(func() { l.sugar.Panic(args...) })
}

// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics.
func (l *Logger) DPanic(args ...interface{}) {
l.sugar.DPanic(args...)
l.do(func() { l.sugar.DPanic(args...) })
}

// IsDebug checks to see if the given logger is Debug enabled.
Expand All @@ -148,38 +159,38 @@ func (l *Logger) IsDebug() bool {

// Debugf uses fmt.Sprintf to construct and log a message.
func (l *Logger) Debugf(format string, args ...interface{}) {
l.sugar.Debugf(format, args...)
l.do(func() { l.sugar.Debugf(format, args...) })
}

// Infof uses fmt.Sprintf to log a templated message.
func (l *Logger) Infof(format string, args ...interface{}) {
l.sugar.Infof(format, args...)
l.do(func() { l.sugar.Infof(format, args...) })
}

// Warnf uses fmt.Sprintf to log a templated message.
func (l *Logger) Warnf(format string, args ...interface{}) {
l.sugar.Warnf(format, args...)
l.do(func() { l.sugar.Warnf(format, args...) })
}

// Errorf uses fmt.Sprintf to log a templated message.
func (l *Logger) Errorf(format string, args ...interface{}) {
l.sugar.Errorf(format, args...)
l.do(func() { l.sugar.Errorf(format, args...) })
}

// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit(1).
func (l *Logger) Fatalf(format string, args ...interface{}) {
l.sugar.Fatalf(format, args...)
l.do(func() { l.sugar.Fatalf(format, args...) })
}

// Panicf uses fmt.Sprintf to log a templated message, then panics.
func (l *Logger) Panicf(format string, args ...interface{}) {
l.sugar.Panicf(format, args...)
l.do(func() { l.sugar.Panicf(format, args...) })
}

// DPanicf uses fmt.Sprintf to log a templated message. In development, the
// logger then panics.
func (l *Logger) DPanicf(format string, args ...interface{}) {
l.sugar.DPanicf(format, args...)
l.do(func() { l.sugar.DPanicf(format, args...) })
}

// With context (reflection based)
Expand All @@ -189,31 +200,31 @@ func (l *Logger) DPanicf(format string, args ...interface{}) {
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Debugw(msg string, keysAndValues ...interface{}) {
l.sugar.Debugw(msg, keysAndValues...)
l.do(func() { l.sugar.Debugw(msg, keysAndValues...) })
}

// Infow logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Infow(msg string, keysAndValues ...interface{}) {
l.sugar.Infow(msg, keysAndValues...)
l.do(func() { l.sugar.Infow(msg, keysAndValues...) })
}

// Warnw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Warnw(msg string, keysAndValues ...interface{}) {
l.sugar.Warnw(msg, keysAndValues...)
l.do(func() { l.sugar.Warnw(msg, keysAndValues...) })
}

// Errorw logs a message with some additional context. The additional context
// is added in the form of key-value pairs. The optimal way to write the value
// to the log message will be inferred by the value's type. To explicitly
// specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Errorw(msg string, keysAndValues ...interface{}) {
l.sugar.Errorw(msg, keysAndValues...)
l.do(func() { l.sugar.Errorw(msg, keysAndValues...) })
}

// Fatalw logs a message with some additional context, then calls os.Exit(1).
Expand All @@ -222,15 +233,15 @@ func (l *Logger) Errorw(msg string, keysAndValues ...interface{}) {
// type. To explicitly specify a type you can pass a Field such as
// logp.Stringer.
func (l *Logger) Fatalw(msg string, keysAndValues ...interface{}) {
l.sugar.Fatalw(msg, keysAndValues...)
l.do(func() { l.sugar.Fatalw(msg, keysAndValues...) })
}

// Panicw logs a message with some additional context, then panics. The
// additional context is added in the form of key-value pairs. The optimal way
// to write the value to the log message will be inferred by the value's type.
// To explicitly specify a type you can pass a Field such as logp.Stringer.
func (l *Logger) Panicw(msg string, keysAndValues ...interface{}) {
l.sugar.Panicw(msg, keysAndValues...)
l.do(func() { l.sugar.Panicw(msg, keysAndValues...) })
}

// DPanicw logs a message with some additional context. The logger panics only
Expand All @@ -239,7 +250,7 @@ func (l *Logger) Panicw(msg string, keysAndValues ...interface{}) {
// be inferred by the value's type. To explicitly specify a type you can pass a
// Field such as logp.Stringer.
func (l *Logger) DPanicw(msg string, keysAndValues ...interface{}) {
l.sugar.DPanicw(msg, keysAndValues...)
l.do(func() { l.sugar.DPanicw(msg, keysAndValues...) })
}

// Recover stops a panicking goroutine and logs an Error.
Expand Down Expand Up @@ -269,6 +280,36 @@ func (l *Logger) Close() error {
return nil
}

func (l *Logger) rateLimiter() *rate.Sometimes {
limiter := l.limiter
if limiter == nil {
limiter = &rate.Sometimes{}
}

return limiter
}

// Throttled returns a new Logger that logs at most once every period.
func (l *Logger) Throttled(period time.Duration) *Logger {
limiter := l.rateLimiter()
limiter.Interval = period
return &Logger{l.logger, l.sugar, limiter}
}

// Sampled returns a new Logger that logs every nth log message.
func (l *Logger) Sampled(nth int) *Logger {
limiter := l.rateLimiter()
limiter.Every = nth
return &Logger{l.logger, l.sugar, limiter}
}

// Limited returns a new Logger that logs the first n log messages.
func (l *Logger) Limited(n int) *Logger {
limiter := l.rateLimiter()
limiter.First = n
return &Logger{l.logger, l.sugar, limiter}
}

// L returns an unnamed global logger.
func L() *Logger {
return loadLogger().logger
Expand Down
Loading
Loading