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

Reduce number of log messages produced by default Client #14

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
15 changes: 11 additions & 4 deletions jira.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,20 @@ type Client struct {
// Both the Jira REST API and retryablehttp.DefaultRetryPolicy implement RFC 6585 section 4, in
// which the server responds with status code 429 if too many requests have been sent and the
// client backs off for at least the number of seconds given in the Retry-After response header
// before retrying; this means that a retryablehttp.Client with default settings is all that is
// needed to obey Jira's API rate limits.
var defaultClient = retryablehttp.NewClient()
// before retrying; this means that a retryablehttp.Client will obey Jira's API rate limits out of
// the box.
var defaultClient *retryablehttp.Client

// defaultTransport is the underlying Transport used by the other Transports in this package if no
// other Transport is specified. It ensures that failed requests are automatically retried.
var defaultTransport = &retryablehttp.RoundTripper{Client: defaultClient}
var defaultTransport *retryablehttp.RoundTripper

func init() {
defaultClient = retryablehttp.NewClient()
defaultClient.Logger = logger

defaultTransport = &retryablehttp.RoundTripper{Client: defaultClient}
}

// NewClient returns a new Jira API client.
// If a nil httpClient is provided, a retryablehttp.Client with default settings will be used; this
Expand Down
114 changes: 114 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package jira

import (
"context"
"io"
"log/slog"
"os"
)

const (
levelTrace = slog.Level(-8)
levelDebug = slog.LevelDebug
levelInfo = slog.LevelInfo
levelWarning = slog.LevelWarn
levelError = slog.LevelError
levelPanic = slog.Level(12)
levelFatal = slog.Level(16)

// logLevelEnvKey is the name of the environment variable whose value defines the logging level of
// the logger used by the default client.
logLevelEnvKey = "VERBOSITY"
)

// logger is the logger used by the default client. It writes log messages to stderr.
var logger = slog.New(newRetryablehttpHandler(os.Stderr))

// retryablehttpHandler handles log messages produced for LeveledLoggers by go-retryablehttp. It
// wraps another handler, mutating the log records it receives before forwarding them on to the
// wrapped handler.
type retryablehttpHandler struct {
// handler is the wrapped handler.
handler slog.Handler

// level is the minimum level for which messages should be logged.
level slog.Level
}

// newRetryablehttpHandler creates a retryablehttpHandler whose minimum logging level is the value
// of the VERBOSITY environment variable.
func newRetryablehttpHandler(output io.Writer) *retryablehttpHandler {
return &retryablehttpHandler{
handler: slog.NewTextHandler(output, nil),
level: defaultLogLevel(),
}
}

// Enabled reports whether the handler handles records at the given level. The handler ignores
// records whose level is lower.
//
// Enabled is called before Handle for performance reasons. Because retryablehttpHandler changes the
// level of some messages in Handle, records cannot be rejected at this point solely because of
// their level, so Enabled always returns true. However, Handle will only forward them on to the
// wrapped handler if their level is at least h.level.
func (h *retryablehttpHandler) Enabled(_ context.Context, _ slog.Level) bool {
return true
}

// WithAttrs returns a new retryablehttpHandler whose attributes consists of h's attributes followed
// by attrs.
func (h *retryablehttpHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &retryablehttpHandler{
handler: h.handler.WithAttrs(attrs),
level: h.level,
}
}

// WithGroup returns a new retryablehttpHandler with the given group appended to the receiver's
// existing groups.
func (h *retryablehttpHandler) WithGroup(name string) slog.Handler {
return &retryablehttpHandler{
handler: h.handler.WithGroup(name),
level: h.level,
}
}

// Handle processes a record created by a retryablehttp.Client and potentially forwards it on to the
// wrapped handler.
//
// Handle mutates records as follows:
// - records with a "retrying request" message (which are created by retryablehttp.Client when a
// request fails due to a server-side error or a retryable client-side error, e.g. when the Jira
// API's rate limits have been exceeded) are increased from debug level to warning level, to make
// them more visible.
func (h *retryablehttpHandler) Handle(ctx context.Context, r slog.Record) error {
if r.Message == "retrying request" {
r = r.Clone()
r.Level = levelWarning
}
if r.Level < h.level {
return nil
}
return h.handler.Handle(ctx, r)
}

func defaultLogLevel() slog.Level {
switch os.Getenv(logLevelEnvKey) {
case "TRACE":
return levelTrace
case "DEBUG":
return levelDebug
case "INFO":
return levelInfo
case "WARNING":
return levelWarning
case "ERROR":
return levelError
case "PANIC":
return levelPanic
case "FATAL":
return levelFatal
default:
return levelInfo
}
}