client/vendor/github.com/go-logr/logr
Knative Automation 0b8c5d4159
upgrade to latest dependencies (#1857)
bumping google.golang.org/genproto/googleapis/rpc 1744710...2d3300f:
  > 2d3300f chore(all): update actions/checkout action to v4 (# 1052)
  > b8732ec chore(all): auto-regenerate .pb.go files (# 1051)
  > ccc8af3 chore(all): auto-regenerate .pb.go files (# 1050)
  > 6bfd019 chore(all): auto-regenerate .pb.go files (# 1047)
  > ca7cfce fix(analytics/admin/v1alpha): remove broken aliases (# 1045)
bumping google.golang.org/grpc 87bf02a...62726d4:
  > 62726d4 update version to 1.58.1 (# 6629)
  > fa6d9ab cherry-pick 6610 and 6620 (# 6627)
  > 467fbf2 Change version to 1.58.1-dev (# 6580)
  > c2b0797 Change version to 1.58.0 (# 6579)
  > 0467e47 balancer/leastrequest: Cache atomic load and also add concurrent rpc test (# 6607)
  > 5d1c0ae leastrequest: fix data race in leastrequest picker (# 6606)
  > e26457d stream: swallow Header errors as we used to; RecvMsg can still return it (# 6591)
  > 4c9777c clusterresolver: fix deadlock when dns resolver responds inline with update or error at build time (# 6563)
  > 81b9df2 idle: move idleness manager to separate package and ~13s of tests into it (# 6566)
  > 7d35b8e test: speed up TestServiceConfigTimeoutTD from 1.8s to 0.03s (# 6571)
  > d51b3f4 interop/grpc_testing: update protos from grpc-proto repo (# 6567)
  > fe1519e client: fix ClientStream.Header() behavior (# 6557)
  > 8a2c220 cdsbalancer: test cleanup part 2/N (# 6554)
  > 7f66074 vet.sh: fix interface{} check for macos (# 6561)
  > b07bf5d cdsbalancer: test cleanup part 1/N (# 6546)
  > 33f9fa2 test: speed up two tests (# 6558)
  > aca07ce xds/internal/xdsclient: Add least request support in xDS (# 6517)
  > e5d8eac test: improve and speed up channelz keepalive test (# 6556)
  > ebf0b4e idle: speed up test by 5x even while running 2x more iterations (# 6555)
  > 7d3996f grpctest: use an interface instead of reflection (# 6553)
  > cc705fe interop: regenerate pb.gos (# 6551)
  > 3e92504 status: optimize GRPCStatus() calls (# 6539)
  > 402ba09 pick_first: de-experiment pick first (# 6549)
  > 2821d7f resolver: remove outdated Target examples (# 6547)
  > 53d1f23 benchmark: update proper benchmark binary to use larger buffers (# 6537)
  > fbff2ab *: update `interface{}` to `any` and `go.mod` version to `go 1.19` (# 6544)
  > e40da66 clientconn: release lock when returning from enterIdleMode() (# 6538)
  > dbbc983 balancer/leastrequest: Add least request balancer (# 6510)
  > a010079 *: remove references to old versions of go (# 6545)
  > 03d32b9 orca: update example and interop to use StateListener (# 6529)
  > c2bc22c testing: update Go versions tested to 1.19-1.21 (# 6543)
  > 879faf6 test: update client state subscriber test to be not flaky and more stressful about rapid updates (# 6512)
  > f3e94ec xds: improve error message when matched route on client is not of type RouteActionRoute (# 6248)
  > bb41067 balancergroup: do not cache closed sub-balancers by default (# 6523)
  > 68704f8 gracefulswitch, stub: remove last UpdateSubConnState references (# 6533)
  > 4900699 balancer/rls, xds/wrrlocality: stop forwarding UpdateSubConnState calls (# 6532)
  > ebc3c51 internal/balancergroup: remove usage of UpdateSubConnState (# 6528)
  > 5da2731 balancer/weightedtarget: stop forwarding UpdateSubConnState calls (# 6525)
  > 182b0ad interop/grpc_testing: regenerate protos (# 6534)
  > e274152 rls: fix flaky test introduced by # 6514 (# 6535)
  > 61a1f77 balancer/weightedroundrobin: migrate to StateListener (# 6530)
  > 175c84c xds/ringhash: use StateListener instead of UpdateSubConnState (# 6522)
  > 3fa17cc test: speed up test that was taking 10 seconds to timeout (# 6531)
  > 694cb64 xds/clusterresolver: stop forwarding UpdateSubConnState calls (# 6526)
  > 8f51ca8 tests: stop using UpdateSubConnState (# 6527)
  > cea77bb xds/clustermanager: stop forwarding UpdateSubConnState calls (# 6519)
  > ce68413 xds/priority: stop forwarding UpdateSubConnState calls (# 6521)
  > dceb6ee xds/clusterimpl: stop forwarding UpdateSubConnState calls (# 6518)
  > 8def12a xds/outlierdetection: Stop handling UpdateSubConnState forwarding (# 6520)
  > 67a8e73 multiple/test: use stub balancer instead of defining wrapped balancers (# 6514)
  > 92b481a test: allow set request/response size in interop soak test (# 6513)
  > 07609e1 benchmark: restore old buffer size values for published benchmarks (# 6516)
  > 2059c6e grpc: report connectivity state changes on the ClientConn for Subscribers (# 6437)
  > 4832deb test: clean up deadlines set in tests (# 6506)
  > 9c46304 xds/cdsbalancer: stop handling subconn state updates (# 6509)
  > e9a4e94 base: update base balancer for new APIs (# 6503)
  > 6c0c69e all: replace RemoveSubConn with Shutdown as much as possible (# 6505)
  > 28ac6ef xdsclient: make watch timer a no-op if authority is closed (# 6502)
  > d06ab0d pickfirst: receive state updates via callback instead of UpdateSubConnState (# 6495)
  > 7aceafc balancer: add SubConn.Shutdown; deprecate Balancer.RemoveSubConn (# 6493)
  > 4fe8d3d balancer: fix tests not properly updating subconn states (# 6501)
  > 8ebe462 outlierdetection: fix unconditional calls of child UpdateSubConnState (# 6500)
  > 5d3d9d7 grpc: perform a blocking close of the balancer in ccb (# 6497)
  > ecc5645 clusterresolver: fix a flaky test (# 6499)
  > b9356e3 client: fix race between connection error and subconn shutdown (# 6494)
  > 2db7b17 test/xds: increase default test timeout (# 6498)
  > 8f496b2 test/kokoro: Add bootstrap generator test into Go Kokoro script (# 6463)
  > 0246373 testutils: remove TestSubConns for future extensibility (# 6492)
  > c635404 balancer: add StateListener to NewSubConnOptions for SubConn state updates (# 6481)
  > 94df716 resolver: State: add Endpoints and deprecate Addresses (# 6471)
  > 20c51a9 pickfirst: add tests for resolver error scenarios (# 6484)
  > b8d36ca pickfirst: add prefix logging (# 6482)
  > 5ce5686 pickfirst: guard config parsing on GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG (# 6470)
  > 41d1232 resolver/weighted_round_robin: remove experimental suffix from name (# 6477)
  > 2aa2615 clusterresolver: comply with A37 for handling errors from discovery mechanisms (# 6461)
  > d7f45cd xds/server: create the xDS client when the xDS enabled gRPC server is created (# 6446)
  > f1fc2ca clientconn: add channel ID to some idleness logs (# 6459)
  > 9bb44fb transport: use a sync.Pool to share per-connection write buffer (# 6309)
  > d524b40 multiple: update dependencies after 1.57 branch cut (# 6452)
  > 7aab9c0 stats: Add RPC event for blocking for a picker update (# 6422)
  > 02946a3 resolver: remove deprecated AddressType (# 6451)
  > 919fe35 Change version to 1.58.0-dev (# 6450)

Signed-off-by: Knative Automation <automation@knative.team>
2023-09-19 09:38:57 +00:00
..
.golangci.yaml upgrade to latest dependencies (#1857) 2023-09-19 09:38:57 +00:00
CHANGELOG.md upgrade to latest dependencies (#1565) 2022-01-14 05:06:44 -08:00
CONTRIBUTING.md upgrade to latest dependencies (#1565) 2022-01-14 05:06:44 -08:00
LICENSE Pin knative/pkg to release-0.17 (#976) 2020-08-11 12:23:05 -07:00
README.md Update spf13/cobra deps (#1734) 2022-09-22 10:36:12 +00:00
discard.go upgrade to latest dependencies (#1857) 2023-09-19 09:38:57 +00:00
logr.go upgrade to latest dependencies (#1857) 2023-09-19 09:38:57 +00:00

README.md

A minimal logging API for Go

Go Reference

logr offers an(other) opinion on how Go programs and libraries can do logging without becoming coupled to a particular logging implementation. This is not an implementation of logging - it is an API. In fact it is two APIs with two different sets of users.

The Logger type is intended for application and library authors. It provides a relatively small API which can be used everywhere you want to emit logs. It defers the actual act of writing logs (to files, to stdout, or whatever) to the LogSink interface.

The LogSink interface is intended for logging library implementers. It is a pure interface which can be implemented by logging frameworks to provide the actual logging functionality.

This decoupling allows application and library developers to write code in terms of logr.Logger (which has very low dependency fan-out) while the implementation of logging is managed "up stack" (e.g. in or near main().) Application developers can then switch out implementations as necessary.

Many people assert that libraries should not be logging, and as such efforts like this are pointless. Those people are welcome to convince the authors of the tens-of-thousands of libraries that DO write logs that they are all wrong. In the meantime, logr takes a more practical approach.

Typical usage

Somewhere, early in an application's life, it will make a decision about which logging library (implementation) it actually wants to use. Something like:

    func main() {
        // ... other setup code ...

        // Create the "root" logger.  We have chosen the "logimpl" implementation,
        // which takes some initial parameters and returns a logr.Logger.
        logger := logimpl.New(param1, param2)

        // ... other setup code ...

Most apps will call into other libraries, create structures to govern the flow, etc. The logr.Logger object can be passed to these other libraries, stored in structs, or even used as a package-global variable, if needed. For example:

    app := createTheAppObject(logger)
    app.Run()

Outside of this early setup, no other packages need to know about the choice of implementation. They write logs in terms of the logr.Logger that they received:

    type appObject struct {
        // ... other fields ...
        logger logr.Logger
        // ... other fields ...
    }

    func (app *appObject) Run() {
        app.logger.Info("starting up", "timestamp", time.Now())

        // ... app code ...

Background

If the Go standard library had defined an interface for logging, this project probably would not be needed. Alas, here we are.

Inspiration

Before you consider this package, please read this blog post by the inimitable Dave Cheney. We really appreciate what he has to say, and it largely aligns with our own experiences.

Differences from Dave's ideas

The main differences are:

  1. Dave basically proposes doing away with the notion of a logging API in favor of fmt.Printf(). We disagree, especially when you consider things like output locations, timestamps, file and line decorations, and structured logging. This package restricts the logging API to just 2 types of logs: info and error.

Info logs are things you want to tell the user which are not errors. Error logs are, well, errors. If your code receives an error from a subordinate function call and is logging that error and not returning it, use error logs.

  1. Verbosity-levels on info logs. This gives developers a chance to indicate arbitrary grades of importance for info logs, without assigning names with semantic meaning such as "warning", "trace", and "debug." Superficially this may feel very similar, but the primary difference is the lack of semantics. Because verbosity is a numerical value, it's safe to assume that an app running with higher verbosity means more (and less important) logs will be generated.

Implementations (non-exhaustive)

There are implementations for the following logging libraries:

  • a function (can bridge to non-structured libraries): funcr
  • a testing.T (for use in Go tests, with JSON-like output): testr
  • github.com/google/glog: glogr
  • k8s.io/klog (for Kubernetes): klogr
  • a testing.T (with klog-like text output): ktesting
  • go.uber.org/zap: zapr
  • log (the Go standard library logger): stdr
  • github.com/sirupsen/logrus: logrusr
  • github.com/wojas/genericr: genericr (makes it easy to implement your own backend)
  • logfmt (Heroku style logging): logfmtr
  • github.com/rs/zerolog: zerologr
  • github.com/go-kit/log: gokitlogr (also compatible with github.com/go-kit/kit/log since v0.12.0)
  • bytes.Buffer (writing to a buffer): bufrlogr (useful for ensuring values were logged, like during testing)

FAQ

Conceptual

Why structured logging?

  • Structured logs are more easily queryable: Since you've got key-value pairs, it's much easier to query your structured logs for particular values by filtering on the contents of a particular key -- think searching request logs for error codes, Kubernetes reconcilers for the name and namespace of the reconciled object, etc.

  • Structured logging makes it easier to have cross-referenceable logs: Similarly to searchability, if you maintain conventions around your keys, it becomes easy to gather all log lines related to a particular concept.

  • Structured logs allow better dimensions of filtering: if you have structure to your logs, you've got more precise control over how much information is logged -- you might choose in a particular configuration to log certain keys but not others, only log lines where a certain key matches a certain value, etc., instead of just having v-levels and names to key off of.

  • Structured logs better represent structured data: sometimes, the data that you want to log is inherently structured (think tuple-link objects.) Structured logs allow you to preserve that structure when outputting.

Why V-levels?

V-levels give operators an easy way to control the chattiness of log operations. V-levels provide a way for a given package to distinguish the relative importance or verbosity of a given log message. Then, if a particular logger or package is logging too many messages, the user of the package can simply change the v-levels for that library.

Why not named levels, like Info/Warning/Error?

Read Dave Cheney's post. Then read Differences from Dave's ideas.

Why not allow format strings, too?

Format strings negate many of the benefits of structured logs:

  • They're not easily searchable without resorting to fuzzy searching, regular expressions, etc.

  • They don't store structured data well, since contents are flattened into a string.

  • They're not cross-referenceable.

  • They don't compress easily, since the message is not constant.

(Unless you turn positional parameters into key-value pairs with numerical keys, at which point you've gotten key-value logging with meaningless keys.)

Practical

Why key-value pairs, and not a map?

Key-value pairs are much easier to optimize, especially around allocations. Zap (a structured logger that inspired logr's interface) has performance measurements that show this quite nicely.

While the interface ends up being a little less obvious, you get potentially better performance, plus avoid making users type map[string]string{} every time they want to log.

What if my V-levels differ between libraries?

That's fine. Control your V-levels on a per-logger basis, and use the WithName method to pass different loggers to different libraries.

Generally, you should take care to ensure that you have relatively consistent V-levels within a given logger, however, as this makes deciding on what verbosity of logs to request easier.

But I really want to use a format string!

That's not actually a question. Assuming your question is "how do I convert my mental model of logging with format strings to logging with constant messages":

  1. Figure out what the error actually is, as you'd write in a TL;DR style, and use that as a message.

  2. For every place you'd write a format specifier, look to the word before it, and add that as a key value pair.

For instance, consider the following examples (all taken from spots in the Kubernetes codebase):

  • klog.V(4).Infof("Client is returning errors: code %v, error %v", responseCode, err) becomes logger.Error(err, "client returned an error", "code", responseCode)

  • klog.V(4).Infof("Got a Retry-After %ds response for attempt %d to %v", seconds, retries, url) becomes logger.V(4).Info("got a retry-after response when requesting url", "attempt", retries, "after seconds", seconds, "url", url)

If you really must use a format string, use it in a key's value, and call fmt.Sprintf yourself. For instance: log.Printf("unable to reflect over type %T") becomes logger.Info("unable to reflect over type", "type", fmt.Sprintf("%T")). In general though, the cases where this is necessary should be few and far between.

How do I choose my V-levels?

This is basically the only hard constraint: increase V-levels to denote more verbose or more debug-y logs.

Otherwise, you can start out with 0 as "you always want to see this", 1 as "common logging that you might possibly want to turn off", and 10 as "I would like to performance-test your log collection stack."

Then gradually choose levels in between as you need them, working your way down from 10 (for debug and trace style logs) and up from 1 (for chattier info-type logs.)

How do I choose my keys?

Keys are fairly flexible, and can hold more or less any string value. For best compatibility with implementations and consistency with existing code in other projects, there are a few conventions you should consider.

  • Make your keys human-readable.
  • Constant keys are generally a good idea.
  • Be consistent across your codebase.
  • Keys should naturally match parts of the message string.
  • Use lower case for simple keys and lowerCamelCase for more complex ones. Kubernetes is one example of a project that has adopted that convention.

While key names are mostly unrestricted (and spaces are acceptable), it's generally a good idea to stick to printable ascii characters, or at least match the general character set of your log lines.

Why should keys be constant values?

The point of structured logging is to make later log processing easier. Your keys are, effectively, the schema of each log message. If you use different keys across instances of the same log line, you will make your structured logs much harder to use. Sprintf() is for values, not for keys!

Why is this not a pure interface?

The Logger type is implemented as a struct in order to allow the Go compiler to optimize things like high-V Info logs that are not triggered. Not all of these implementations are implemented yet, but this structure was suggested as a way to ensure they can be implemented. All of the real work is behind the LogSink interface.