pax_global_header00006660000000000000000000000064143255531160014517gustar00rootroot0000000000000052 comment=2a10d735646893406303a4848f8fa336e545af26 golang-github-go-logr-logr-1.2.3/000077500000000000000000000000001432555311600165565ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/.github/000077500000000000000000000000001432555311600201165ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/.github/workflows/000077500000000000000000000000001432555311600221535ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/.github/workflows/apidiff.yaml000066400000000000000000000013701432555311600244420ustar00rootroot00000000000000name: Run apidiff on: [ pull_request ] jobs: apidiff: runs-on: ubuntu-latest if: github.base_ref steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: 1.16.x - name: Add GOBIN to PATH run: echo "PATH=$(go env GOPATH)/bin:$PATH" >>$GITHUB_ENV - name: Install dependencies run: GO111MODULE=off go get golang.org/x/exp/cmd/apidiff - name: Checkout old code uses: actions/checkout@v2 with: ref: ${{ github.base_ref }} path: "old" - name: Checkout new code uses: actions/checkout@v2 with: path: "new" - name: APIDiff run: ./_tools/apidiff.sh -d ../old working-directory: "new" golang-github-go-logr-logr-1.2.3/.github/workflows/lint.yaml000066400000000000000000000010651432555311600240070ustar00rootroot00000000000000name: Run lint on: [ push, pull_request ] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Lint uses: golangci/golangci-lint-action@v2 with: # version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest # Optional: show only new issues if it's a pull request. The default value is `false`. # only-new-issues: true # Read args from .golangci.yaml # args: golang-github-go-logr-logr-1.2.3/.github/workflows/tests.yaml000066400000000000000000000007751432555311600242120ustar00rootroot00000000000000name: Run tests on: [ push, pull_request ] jobs: test: strategy: matrix: version: [ '1.15', '1.16', '1.17' ] platform: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.version }} - name: Checkout code uses: actions/checkout@v2 - name: Build run: go build -v ./... - name: Test run: go test -v -race ./... golang-github-go-logr-logr-1.2.3/.golangci.yaml000066400000000000000000000006511432555311600213050ustar00rootroot00000000000000run: timeout: 1m tests: true linters: disable-all: true enable: - asciicheck - deadcode - errcheck - forcetypeassert - gocritic - gofmt - goimports - gosimple - govet - ineffassign - misspell - revive - staticcheck - structcheck - typecheck - unused - varcheck issues: exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 10 golang-github-go-logr-logr-1.2.3/CHANGELOG.md000066400000000000000000000002141432555311600203640ustar00rootroot00000000000000# CHANGELOG ## v1.0.0-rc1 This is the first logged release. Major changes (including breaking changes) have occurred since earlier tags. golang-github-go-logr-logr-1.2.3/CONTRIBUTING.md000066400000000000000000000011031432555311600210020ustar00rootroot00000000000000# Contributing Logr is open to pull-requests, provided they fit within the intended scope of the project. Specifically, this library aims to be VERY small and minimalist, with no external dependencies. ## Compatibility This project intends to follow [semantic versioning](http://semver.org) and is very strict about compatibility. Any proposed changes MUST follow those rules. ## Performance As a logging library, logr must be as light-weight as possible. Any proposed code change must include results of running the [benchmark](./benchmark) before and after the change. golang-github-go-logr-logr-1.2.3/LICENSE000066400000000000000000000261351432555311600175720ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-go-logr-logr-1.2.3/README.md000066400000000000000000000277701432555311600200520ustar00rootroot00000000000000# A minimal logging API for Go [![Go Reference](https://pkg.go.dev/badge/github.com/go-logr/logr.svg)](https://pkg.go.dev/github.com/go-logr/logr) 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][warning-makes-no-sense]. 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. 2. 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](https://github.com/go-logr/logr/tree/master/funcr) - **a testing.T** (for use in Go tests, with JSON-like output): [testr](https://github.com/go-logr/logr/tree/master/testr) - **github.com/google/glog**: [glogr](https://github.com/go-logr/glogr) - **k8s.io/klog** (for Kubernetes): [klogr](https://git.k8s.io/klog/klogr) - **a testing.T** (with klog-like text output): [ktesting](https://git.k8s.io/klog/ktesting) - **go.uber.org/zap**: [zapr](https://github.com/go-logr/zapr) - **log** (the Go standard library logger): [stdr](https://github.com/go-logr/stdr) - **github.com/sirupsen/logrus**: [logrusr](https://github.com/bombsimon/logrusr) - **github.com/wojas/genericr**: [genericr](https://github.com/wojas/genericr) (makes it easy to implement your own backend) - **logfmt** (Heroku style [logging](https://www.brandur.org/logfmt)): [logfmtr](https://github.com/iand/logfmtr) - **github.com/rs/zerolog**: [zerologr](https://github.com/go-logr/zerologr) - **github.com/go-kit/log**: [gokitlogr](https://github.com/tonglil/gokitlogr) (also compatible with github.com/go-kit/kit/log since v0.12.0) - **bytes.Buffer** (writing to a buffer): [bufrlogr](https://github.com/tonglil/buflogr) (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][warning-makes-no-sense]. Then read [Differences from Dave's ideas](#differences-from-daves-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](https://github.com/uber-go/zap#performance) 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](https://en.wiktionary.org/wiki/lowerCamelCase) for more complex ones. Kubernetes is one example of a project that has [adopted that convention](https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments). 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. [warning-makes-no-sense]: http://dave.cheney.net/2015/11/05/lets-talk-about-logging golang-github-go-logr-logr-1.2.3/_tools/000077500000000000000000000000001432555311600200555ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/_tools/apidiff.sh000077500000000000000000000062631432555311600220250ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2020 The Kubernetes Authors. # Copyright 2021 The logr Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail function usage { local script="$(basename $0)" echo >&2 "Usage: ${script} [-r | -d ] This script should be run at the root of a module. -r Compare the exported API of the local working copy with the exported API of the local repo at the specified branch or tag. -d Compare the exported API of the local working copy with the exported API of the specified directory, which should point to the root of a different version of the same module. Examples: ${script} -r master ${script} -r v1.10.0 ${script} -r release-1.10 ${script} -d /path/to/historical/version " exit 1 } ref="" dir="" while getopts r:d: o do case "$o" in r) ref="$OPTARG";; d) dir="$OPTARG";; [?]) usage;; esac done # If REF and DIR are empty, print usage and error if [[ -z "${ref}" && -z "${dir}" ]]; then usage; fi # If REF and DIR are both set, print usage and error if [[ -n "${ref}" && -n "${dir}" ]]; then usage; fi if ! which apidiff > /dev/null; then echo "Installing golang.org/x/exp/cmd/apidiff" pushd "${TMPDIR:-/tmp}" > /dev/null GO111MODULE=off go get golang.org/x/exp/cmd/apidiff popd > /dev/null fi output=$(mktemp -d -t "apidiff.output.XXXX") cleanup_output () { rm -fr "${output}"; } trap cleanup_output EXIT # If ref is set, clone . to temp dir at $ref, and set $dir to the temp dir clone="" base="${dir}" if [[ -n "${ref}" ]]; then base="${ref}" clone=$(mktemp -d -t "apidiff.clone.XXXX") cleanup_clone_and_output () { rm -fr "${clone}"; cleanup_output; } trap cleanup_clone_and_output EXIT git clone . -q --no-tags "${clone}" git -C "${clone}" co "${ref}" dir="${clone}" fi pushd "${dir}" >/dev/null echo "Inspecting API of ${base}" go list ./... > packages.txt for pkg in $(cat packages.txt); do mkdir -p "${output}/${pkg}" apidiff -w "${output}/${pkg}/apidiff.output" "${pkg}" done popd >/dev/null retval=0 echo "Comparing with ${base}" for pkg in $(go list ./...); do # New packages are ok if [ ! -f "${output}/${pkg}/apidiff.output" ]; then continue fi # Check for incompatible changes to previous packages incompatible=$(apidiff -incompatible "${output}/${pkg}/apidiff.output" "${pkg}") if [[ -n "${incompatible}" ]]; then echo >&2 "FAIL: ${pkg} contains incompatible changes: ${incompatible} " retval=1 fi done # Check for removed packages removed=$(comm -23 "${dir}/packages.txt" <(go list ./...)) if [[ -n "${removed}" ]]; then echo >&2 "FAIL: removed packages: ${removed} " retval=1 fi exit $retval golang-github-go-logr-logr-1.2.3/benchmark/000077500000000000000000000000001432555311600205105ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/benchmark/README.md000066400000000000000000000004701432555311600217700ustar00rootroot00000000000000# Benchmarking logr Any major changes to the logr library must be benchmarked before and after the change. ## Running the benchmark ``` $ go test -bench='.' -test.benchmem ./benchmark/ ``` ## Fixing the benchmark If you think this benchmark can be improved, you are probably correct! PRs are very welcome. golang-github-go-logr-logr-1.2.3/benchmark/benchmark_test.go000066400000000000000000000147061432555311600240400ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr import ( "fmt" "testing" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) //go:noinline func doInfoOneArg(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.Info("this is", "a", "string") } } //go:noinline func doInfoSeveralArgs(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.Info("multi", "bool", true, "string", "str", "int", 42, "float", 3.14, "struct", struct{ X, Y int }{93, 76}) } } //go:noinline func doInfoWithValues(b *testing.B, log logr.Logger) { log = log.WithValues("k1", "str", "k2", 222, "k3", true, "k4", 1.0) for i := 0; i < b.N; i++ { log.Info("multi", "bool", true, "string", "str", "int", 42, "float", 3.14, "struct", struct{ X, Y int }{93, 76}) } } //go:noinline func doV0Info(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.V(0).Info("multi", "bool", true, "string", "str", "int", 42, "float", 3.14, "struct", struct{ X, Y int }{93, 76}) } } //go:noinline func doV9Info(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.V(9).Info("multi", "bool", true, "string", "str", "int", 42, "float", 3.14, "struct", struct{ X, Y int }{93, 76}) } } //go:noinline func doError(b *testing.B, log logr.Logger) { err := fmt.Errorf("error message") for i := 0; i < b.N; i++ { log.Error(err, "multi", "bool", true, "string", "str", "int", 42, "float", 3.14, "struct", struct{ X, Y int }{93, 76}) } } //go:noinline func doWithValues(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { l := log.WithValues("k1", "v1", "k2", "v2") _ = l } } //go:noinline func doWithName(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { l := log.WithName("name") _ = l } } //go:noinline func doWithCallDepth(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { l := log.WithCallDepth(1) _ = l } } type Tstringer struct{ s string } func (t Tstringer) String() string { return t.s } //go:noinline func doStringerValue(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.Info("this is", "a", Tstringer{"stringer"}) } } type Terror struct{ s string } func (t Terror) Error() string { return t.s } //go:noinline func doErrorValue(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.Info("this is", "an", Terror{"error"}) } } type Tmarshaler struct{ s string } func (t Tmarshaler) MarshalLog() interface{} { return t.s } //go:noinline func doMarshalerValue(b *testing.B, log logr.Logger) { for i := 0; i < b.N; i++ { log.Info("this is", "a", Tmarshaler{"marshaler"}) } } func BenchmarkDiscardLogInfoOneArg(b *testing.B) { var log logr.Logger = logr.Discard() doInfoOneArg(b, log) } func BenchmarkDiscardLogInfoSeveralArgs(b *testing.B) { var log logr.Logger = logr.Discard() doInfoSeveralArgs(b, log) } func BenchmarkDiscardLogInfoWithValues(b *testing.B) { var log logr.Logger = logr.Discard() doInfoWithValues(b, log) } func BenchmarkDiscardLogV0Info(b *testing.B) { var log logr.Logger = logr.Discard() doV0Info(b, log) } func BenchmarkDiscardLogV9Info(b *testing.B) { var log logr.Logger = logr.Discard() doV9Info(b, log) } func BenchmarkDiscardLogError(b *testing.B) { var log logr.Logger = logr.Discard() doError(b, log) } func BenchmarkDiscardWithValues(b *testing.B) { var log logr.Logger = logr.Discard() doWithValues(b, log) } func BenchmarkDiscardWithName(b *testing.B) { var log logr.Logger = logr.Discard() doWithName(b, log) } func noopKV(prefix, args string) {} func noopJSON(obj string) {} func BenchmarkFuncrLogInfoOneArg(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doInfoOneArg(b, log) } func BenchmarkFuncrJSONLogInfoOneArg(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doInfoOneArg(b, log) } func BenchmarkFuncrLogInfoSeveralArgs(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doInfoSeveralArgs(b, log) } func BenchmarkFuncrJSONLogInfoSeveralArgs(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doInfoSeveralArgs(b, log) } func BenchmarkFuncrLogInfoWithValues(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doInfoWithValues(b, log) } func BenchmarkFuncrJSONLogInfoWithValues(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doInfoWithValues(b, log) } func BenchmarkFuncrLogV0Info(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doV0Info(b, log) } func BenchmarkFuncrJSONLogV0Info(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doV0Info(b, log) } func BenchmarkFuncrLogV9Info(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doV9Info(b, log) } func BenchmarkFuncrJSONLogV9Info(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doV9Info(b, log) } func BenchmarkFuncrLogError(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doError(b, log) } func BenchmarkFuncrJSONLogError(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doError(b, log) } func BenchmarkFuncrWithValues(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithValues(b, log) } func BenchmarkFuncrWithName(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithName(b, log) } func BenchmarkFuncrWithCallDepth(b *testing.B) { var log logr.Logger = funcr.New(noopKV, funcr.Options{}) doWithCallDepth(b, log) } func BenchmarkFuncrJSONLogInfoStringerValue(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doStringerValue(b, log) } func BenchmarkFuncrJSONLogInfoErrorValue(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doErrorValue(b, log) } func BenchmarkFuncrJSONLogInfoMarshalerValue(b *testing.B) { var log logr.Logger = funcr.NewJSON(noopJSON, funcr.Options{}) doMarshalerValue(b, log) } golang-github-go-logr-logr-1.2.3/discard.go000066400000000000000000000026201432555311600205160ustar00rootroot00000000000000/* Copyright 2020 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr // Discard returns a Logger that discards all messages logged to it. It can be // used whenever the caller is not interested in the logs. Logger instances // produced by this function always compare as equal. func Discard() Logger { return Logger{ level: 0, sink: discardLogSink{}, } } // discardLogSink is a LogSink that discards all messages. type discardLogSink struct{} // Verify that it actually implements the interface var _ LogSink = discardLogSink{} func (l discardLogSink) Init(RuntimeInfo) { } func (l discardLogSink) Enabled(int) bool { return false } func (l discardLogSink) Info(int, string, ...interface{}) { } func (l discardLogSink) Error(error, string, ...interface{}) { } func (l discardLogSink) WithValues(...interface{}) LogSink { return l } func (l discardLogSink) WithName(string) LogSink { return l } golang-github-go-logr-logr-1.2.3/discard_test.go000066400000000000000000000024331432555311600215570ustar00rootroot00000000000000/* Copyright 2020 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr import ( "errors" "reflect" "testing" ) func TestDiscard(t *testing.T) { l := Discard() if _, ok := l.GetSink().(discardLogSink); !ok { t.Error("did not return the expected underlying type") } // Verify that none of the methods panic, there is not more we can test. l.WithName("discard").WithValues("z", 5).Info("Hello world") l.Info("Hello world", "x", 1, "y", 2) l.V(1).Error(errors.New("foo"), "a", 123) if l.Enabled() { t.Error("discardLogSink must always say it is disabled") } } func TestComparable(t *testing.T) { a := Discard() if !reflect.TypeOf(a).Comparable() { t.Fatal("discardLogSink must be comparable") } b := Discard() if a != b { t.Fatal("any two discardLogSink must be equal") } } golang-github-go-logr-logr-1.2.3/example_marshaler_secret_test.go000066400000000000000000000024041432555311600252020ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr_test import ( "github.com/go-logr/logr" ) // ComplexObjectRef contains more fields than it wants to get logged. type ComplexObjectRef struct { Name string Namespace string Secret string } func (ref ComplexObjectRef) MarshalLog() interface{} { return struct { Name, Namespace string }{ Name: ref.Name, Namespace: ref.Namespace, } } var _ logr.Marshaler = ComplexObjectRef{} func ExampleMarshaler_secret() { l := NewStdoutLogger() secret := ComplexObjectRef{Namespace: "kube-system", Name: "some-secret", Secret: "do-not-log-me"} l.Info("simplified", "secret", secret) // Output: // "level"=0 "msg"="simplified" "secret"={"Name":"some-secret","Namespace":"kube-system"} } golang-github-go-logr-logr-1.2.3/example_marshaler_test.go000066400000000000000000000032201432555311600236320ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr_test import ( "github.com/go-logr/logr" ) // ObjectRef references a Kubernetes object type ObjectRef struct { Name string `json:"name"` Namespace string `json:"namespace,omitempty"` } func (ref ObjectRef) String() string { if ref.Namespace != "" { return ref.Namespace + "/" + ref.Name } return ref.Name } func (ref ObjectRef) MarshalLog() interface{} { // We implement fmt.Stringer for non-structured logging, but we want the // raw struct when using structured logs. Some logr implementations call // String if it is present, so we want to convert this struct to something // that doesn't have that method. type forLog ObjectRef // methods do not survive type definitions return forLog(ref) } var _ logr.Marshaler = ObjectRef{} func ExampleMarshaler() { l := NewStdoutLogger() pod := ObjectRef{Namespace: "kube-system", Name: "some-pod"} l.Info("as string", "pod", pod.String()) l.Info("as struct", "pod", pod) // Output: // "level"=0 "msg"="as string" "pod"="kube-system/some-pod" // "level"=0 "msg"="as struct" "pod"={"name":"some-pod","namespace":"kube-system"} } golang-github-go-logr-logr-1.2.3/example_test.go000066400000000000000000000026311432555311600216010ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr_test import ( "fmt" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) // NewStdoutLogger returns a logr.Logger that prints to stdout. func NewStdoutLogger() logr.Logger { return funcr.New(func(prefix, args string) { if prefix != "" { _ = fmt.Sprintf("%s: %s\n", prefix, args) } else { fmt.Println(args) } }, funcr.Options{}) } func Example() { l := NewStdoutLogger() l.Info("default info log", "stringVal", "value", "intVal", 12345) l.V(0).Info("V(0) info log", "stringVal", "value", "intVal", 12345) l.Error(fmt.Errorf("an error"), "error log", "stringVal", "value", "intVal", 12345) // Output: // "level"=0 "msg"="default info log" "stringVal"="value" "intVal"=12345 // "level"=0 "msg"="V(0) info log" "stringVal"="value" "intVal"=12345 // "msg"="error log" "error"="an error" "stringVal"="value" "intVal"=12345 } golang-github-go-logr-logr-1.2.3/examples/000077500000000000000000000000001432555311600203745ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/examples/tab_logger.go000066400000000000000000000045711432555311600230370ustar00rootroot00000000000000/* Copyright 2019 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "fmt" "os" "text/tabwriter" "github.com/go-logr/logr" ) // tabLogSink is a sample logr.LogSink that logs to stderr. // It's terribly inefficient, and is only a basic example. type tabLogSink struct { name string keyValues map[string]interface{} writer *tabwriter.Writer } var _ logr.LogSink = &tabLogSink{} // Note that Init usually takes a pointer so it can modify the receiver to save // runtime info. func (_ *tabLogSink) Init(info logr.RuntimeInfo) { } func (_ tabLogSink) Enabled(level int) bool { return true } func (l tabLogSink) Info(level int, msg string, kvs ...interface{}) { fmt.Fprintf(l.writer, "%s\t%s\t", l.name, msg) for k, v := range l.keyValues { fmt.Fprintf(l.writer, "%s: %+v ", k, v) } for i := 0; i < len(kvs); i += 2 { fmt.Fprintf(l.writer, "%s: %+v ", kvs[i], kvs[i+1]) } fmt.Fprintf(l.writer, "\n") l.writer.Flush() } func (l tabLogSink) Error(err error, msg string, kvs ...interface{}) { kvs = append(kvs, "error", err) l.Info(0, msg, kvs...) } func (l tabLogSink) WithName(name string) logr.LogSink { return &tabLogSink{ name: l.name + "." + name, keyValues: l.keyValues, writer: l.writer, } } func (l tabLogSink) WithValues(kvs ...interface{}) logr.LogSink { newMap := make(map[string]interface{}, len(l.keyValues)+len(kvs)/2) for k, v := range l.keyValues { newMap[k] = v } for i := 0; i < len(kvs); i += 2 { newMap[kvs[i].(string)] = kvs[i+1] } return &tabLogSink{ name: l.name, keyValues: newMap, writer: l.writer, } } // NewTabLogger is the main entry-point to this implementation. App developers // call this somewhere near main() and thenceforth only deal with logr.Logger. func NewTabLogger() logr.Logger { sink := &tabLogSink{ writer: tabwriter.NewWriter(os.Stderr, 40, 8, 2, '\t', 0), } return logr.New(sink) } golang-github-go-logr-logr-1.2.3/examples/usage_example.go000066400000000000000000000074051432555311600235500ustar00rootroot00000000000000/* Copyright 2019 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "fmt" "math/rand" "time" "github.com/go-logr/logr" ) // This application demonstrates the usage of logger. // It's a simple reconciliation loop that pretends to // receive notifications about updates from a some API // server, make some changes, and then submit updates of // its own. // This uses object-based logging. It's also possible // (but a bit trickier) to use file-level "base" loggers. var objectMap = map[string]Object{ "obj1": Object{ Name: "obj1", Kind: "one", Details: 33, }, "obj2": Object{ Name: "obj2", Kind: "two", Details: "hi", }, "obj3": Object{ Name: "obj3", Kind: "one", Details: 1, }, } type Object struct { Name string Kind string Details interface{} } type Client struct { objects map[string]Object log logr.Logger } func (c *Client) Get(key string) (Object, error) { c.log.V(1).Info("fetching object", "key", key) obj, ok := c.objects[key] if !ok { return Object{}, fmt.Errorf("no object %s exists", key) } c.log.V(1).Info("pretending to deserialize object", "key", key, "json", "[insert real json here]") return obj, nil } func (c *Client) Save(obj Object) error { c.log.V(1).Info("saving object", "key", obj.Name, "object", obj) if rand.Intn(2) == 0 { return fmt.Errorf("couldn't save to %s", obj.Name) } c.log.V(1).Info("pretending to post object", "key", obj.Name, "url", "https://fake.test") return nil } func (c *Client) WatchNext() string { time.Sleep(2*time.Second) keyInd := rand.Intn(len(c.objects)) currInd := 0 for key := range c.objects { if currInd == keyInd { return key } currInd++ } c.log.Info("watch ended") return "" } type Controller struct { log logr.Logger expectedKind string client *Client } func (c *Controller) Run() { c.log.Info("starting reconciliation") for key := c.client.WatchNext(); key != ""; key = c.client.WatchNext() { // we can make more specific loggers if we always want to attach a particular named value log := c.log.WithValues("key", key) // fetch our object obj, err := c.client.Get(key) if err != nil { log.Error(err, "unable to reconcile object") continue } // make sure it's as expected if obj.Kind != c.expectedKind { log.Error(nil, "got object that wasn't expected kind", "actual-kind", obj.Kind, "object", obj) continue } // always log the object with log messages log = log.WithValues("object", obj) log.V(1).Info("reconciling object for key") // Do some complicated updates updates obj.Details = obj.Details.(int) * 2 // actually save the updates log.V(1).Info("updating object", "details", obj.Details) if err := c.client.Save(obj); err != nil { log.Error(err, "unable to reconcile object") } } c.log.Info("stopping reconciliation") } func NewController(log logr.Logger, objectKind string) *Controller { ctrlLogger := log.WithName("controller").WithName(objectKind) client := &Client{ log: ctrlLogger.WithName("client"), objects: objectMap, } return &Controller{ log: ctrlLogger, expectedKind: objectKind, client: client, } } func main() { // use a fake implementation just for demonstration purposes log := NewTabLogger() // update objects with the "one" kind ctrl := NewController(log, "one") ctrl.Run() } golang-github-go-logr-logr-1.2.3/funcr/000077500000000000000000000000001432555311600176735ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/funcr/example/000077500000000000000000000000001432555311600213265ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/funcr/example/main.go000066400000000000000000000026611432555311600226060ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "fmt" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) type e struct { str string } func (e e) Error() string { return e.str } func helper(log logr.Logger, msg string) { helper2(log, msg) } func helper2(log logr.Logger, msg string) { log.WithCallDepth(2).Info(msg) } func main() { log := funcr.New( func(pfx, args string) { fmt.Println(pfx, args) }, funcr.Options{ LogCaller: funcr.All, LogTimestamp: true, Verbosity: 1, }) example(log.WithValues("module", "example")) } func example(log logr.Logger) { log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1}) log.V(1).Info("you should see this") log.V(1).V(1).Info("you should NOT see this") log.Error(nil, "uh oh", "trouble", true, "reasons", []float64{0.1, 0.11, 3.14}) log.Error(e{"an error occurred"}, "goodbye", "code", -1) helper(log, "thru a helper") } golang-github-go-logr-logr-1.2.3/funcr/example_formatter_test.go000066400000000000000000000052511432555311600250020ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package funcr_test import ( "fmt" "strings" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) // NewStdoutLogger returns a logr.Logger that prints to stdout. // It demonstrates how to implement a custom With* function which // controls whether INFO or ERROR are printed in front of the log // message. func NewStdoutLogger() logr.Logger { l := &stdoutlogger{ Formatter: funcr.NewFormatter(funcr.Options{}), } return logr.New(l) } type stdoutlogger struct { funcr.Formatter logMsgType bool } func (l stdoutlogger) WithName(name string) logr.LogSink { l.Formatter.AddName(name) return &l } func (l stdoutlogger) WithValues(kvList ...interface{}) logr.LogSink { l.Formatter.AddValues(kvList) return &l } func (l stdoutlogger) WithCallDepth(depth int) logr.LogSink { l.Formatter.AddCallDepth(depth) return &l } func (l stdoutlogger) Info(level int, msg string, kvList ...interface{}) { prefix, args := l.FormatInfo(level, msg, kvList) l.write("INFO", prefix, args) } func (l stdoutlogger) Error(err error, msg string, kvList ...interface{}) { prefix, args := l.FormatError(err, msg, kvList) l.write("ERROR", prefix, args) } func (l stdoutlogger) write(msgType, prefix, args string) { var parts []string if l.logMsgType { parts = append(parts, msgType) } if prefix != "" { parts = append(parts, prefix) } parts = append(parts, args) fmt.Println(strings.Join(parts, ": ")) } // WithLogMsgType returns a copy of the logger with new settings for // logging the message type. It returns the original logger if the // underlying LogSink is not a stdoutlogger. func WithLogMsgType(log logr.Logger, logMsgType bool) logr.Logger { if l, ok := log.GetSink().(*stdoutlogger); ok { clone := *l clone.logMsgType = logMsgType log = log.WithSink(&clone) } return log } // Assert conformance to the interfaces. var _ logr.LogSink = &stdoutlogger{} var _ logr.CallDepthLogSink = &stdoutlogger{} func ExampleFormatter() { l := NewStdoutLogger() l.Info("no message type") WithLogMsgType(l, true).Info("with message type") // Output: // "level"=0 "msg"="no message type" // INFO: "level"=0 "msg"="with message type" } golang-github-go-logr-logr-1.2.3/funcr/example_test.go000066400000000000000000000074551432555311600227270ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package funcr_test import ( "fmt" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) func ExampleNew() { var log logr.Logger = funcr.New(func(prefix, args string) { fmt.Println(prefix, args) }, funcr.Options{}) log = log.WithName("MyLogger") log = log.WithValues("savedKey", "savedValue") log.Info("the message", "key", "value") // Output: MyLogger "level"=0 "msg"="the message" "savedKey"="savedValue" "key"="value" } func ExampleNewJSON() { var log logr.Logger = funcr.NewJSON(func(obj string) { fmt.Println(obj) }, funcr.Options{}) log = log.WithName("MyLogger") log = log.WithValues("savedKey", "savedValue") log.Info("the message", "key", "value") // Output: {"logger":"MyLogger","level":0,"msg":"the message","savedKey":"savedValue","key":"value"} } func ExampleUnderlier() { var log logr.Logger = funcr.New(func(prefix, args string) { fmt.Println(prefix, args) }, funcr.Options{}) if underlier, ok := log.GetSink().(funcr.Underlier); ok { fn := underlier.GetUnderlying() fn("hello", "world") } // Output: hello world } func ExampleOptions() { var log logr.Logger = funcr.NewJSON( func(obj string) { fmt.Println(obj) }, funcr.Options{ LogCaller: funcr.All, Verbosity: 1, // V(2) and higher is ignored. }) log.V(0).Info("V(0) message", "key", "value") log.V(1).Info("V(1) message", "key", "value") log.V(2).Info("V(2) message", "key", "value") // Output: // {"logger":"","caller":{"file":"example_test.go","line":67},"level":0,"msg":"V(0) message","key":"value"} // {"logger":"","caller":{"file":"example_test.go","line":68},"level":1,"msg":"V(1) message","key":"value"} } func ExampleOptions_renderHooks() { // prefix all builtin keys with "log:" prefixSpecialKeys := func(kvList []interface{}) []interface{} { for i := 0; i < len(kvList); i += 2 { k, _ := kvList[i].(string) kvList[i] = "log:" + k } return kvList } // present saved values as a single JSON object valuesAsObject := func(kvList []interface{}) []interface{} { return []interface{}{"labels", funcr.PseudoStruct(kvList)} } var log logr.Logger = funcr.NewJSON( func(obj string) { fmt.Println(obj) }, funcr.Options{ RenderBuiltinsHook: prefixSpecialKeys, RenderValuesHook: valuesAsObject, }) log = log.WithName("MyLogger") log = log.WithValues("savedKey1", "savedVal1") log = log.WithValues("savedKey2", "savedVal2") log.Info("the message", "key", "value") // Output: {"log:logger":"MyLogger","log:level":0,"log:msg":"the message","labels":{"savedKey1":"savedVal1","savedKey2":"savedVal2"},"key":"value"} } func ExamplePseudoStruct() { var log logr.Logger = funcr.NewJSON( func(obj string) { fmt.Println(obj) }, funcr.Options{}) kv := []interface{}{ "field1", 12345, "field2", true, } log.Info("the message", "key", funcr.PseudoStruct(kv)) // Output: {"logger":"","level":0,"msg":"the message","key":{"field1":12345,"field2":true}} } func ExampleOptions_maxLogDepth() { type List struct { Next *List } l := List{} l.Next = &l // recursive var log logr.Logger = funcr.NewJSON( func(obj string) { fmt.Println(obj) }, funcr.Options{MaxLogDepth: 4}) log.Info("recursive", "list", l) // Output: {"logger":"","level":0,"msg":"recursive","list":{"Next":{"Next":{"Next":{"Next":{"Next":""}}}}}} } golang-github-go-logr-logr-1.2.3/funcr/funcr.go000066400000000000000000000563461432555311600213550ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package funcr implements formatting of structured log messages and // optionally captures the call site and timestamp. // // The simplest way to use it is via its implementation of a // github.com/go-logr/logr.LogSink with output through an arbitrary // "write" function. See New and NewJSON for details. // // Custom LogSinks // // For users who need more control, a funcr.Formatter can be embedded inside // your own custom LogSink implementation. This is useful when the LogSink // needs to implement additional methods, for example. // // Formatting // // This will respect logr.Marshaler, fmt.Stringer, and error interfaces for // values which are being logged. When rendering a struct, funcr will use Go's // standard JSON tags (all except "string"). package funcr import ( "bytes" "encoding" "fmt" "path/filepath" "reflect" "runtime" "strconv" "strings" "time" "github.com/go-logr/logr" ) // New returns a logr.Logger which is implemented by an arbitrary function. func New(fn func(prefix, args string), opts Options) logr.Logger { return logr.New(newSink(fn, NewFormatter(opts))) } // NewJSON returns a logr.Logger which is implemented by an arbitrary function // and produces JSON output. func NewJSON(fn func(obj string), opts Options) logr.Logger { fnWrapper := func(_, obj string) { fn(obj) } return logr.New(newSink(fnWrapper, NewFormatterJSON(opts))) } // Underlier exposes access to the underlying logging function. Since // callers only have a logr.Logger, they have to know which // implementation is in use, so this interface is less of an // abstraction and more of a way to test type conversion. type Underlier interface { GetUnderlying() func(prefix, args string) } func newSink(fn func(prefix, args string), formatter Formatter) logr.LogSink { l := &fnlogger{ Formatter: formatter, write: fn, } // For skipping fnlogger.Info and fnlogger.Error. l.Formatter.AddCallDepth(1) return l } // Options carries parameters which influence the way logs are generated. type Options struct { // LogCaller tells funcr to add a "caller" key to some or all log lines. // This has some overhead, so some users might not want it. LogCaller MessageClass // LogCallerFunc tells funcr to also log the calling function name. This // has no effect if caller logging is not enabled (see Options.LogCaller). LogCallerFunc bool // LogTimestamp tells funcr to add a "ts" key to log lines. This has some // overhead, so some users might not want it. LogTimestamp bool // TimestampFormat tells funcr how to render timestamps when LogTimestamp // is enabled. If not specified, a default format will be used. For more // details, see docs for Go's time.Layout. TimestampFormat string // Verbosity tells funcr which V logs to produce. Higher values enable // more logs. Info logs at or below this level will be written, while logs // above this level will be discarded. Verbosity int // RenderBuiltinsHook allows users to mutate the list of key-value pairs // while a log line is being rendered. The kvList argument follows logr // conventions - each pair of slice elements is comprised of a string key // and an arbitrary value (verified and sanitized before calling this // hook). The value returned must follow the same conventions. This hook // can be used to audit or modify logged data. For example, you might want // to prefix all of funcr's built-in keys with some string. This hook is // only called for built-in (provided by funcr itself) key-value pairs. // Equivalent hooks are offered for key-value pairs saved via // logr.Logger.WithValues or Formatter.AddValues (see RenderValuesHook) and // for user-provided pairs (see RenderArgsHook). RenderBuiltinsHook func(kvList []interface{}) []interface{} // RenderValuesHook is the same as RenderBuiltinsHook, except that it is // only called for key-value pairs saved via logr.Logger.WithValues. See // RenderBuiltinsHook for more details. RenderValuesHook func(kvList []interface{}) []interface{} // RenderArgsHook is the same as RenderBuiltinsHook, except that it is only // called for key-value pairs passed directly to Info and Error. See // RenderBuiltinsHook for more details. RenderArgsHook func(kvList []interface{}) []interface{} // MaxLogDepth tells funcr how many levels of nested fields (e.g. a struct // that contains a struct, etc.) it may log. Every time it finds a struct, // slice, array, or map the depth is increased by one. When the maximum is // reached, the value will be converted to a string indicating that the max // depth has been exceeded. If this field is not specified, a default // value will be used. MaxLogDepth int } // MessageClass indicates which category or categories of messages to consider. type MessageClass int const ( // None ignores all message classes. None MessageClass = iota // All considers all message classes. All // Info only considers info messages. Info // Error only considers error messages. Error ) // fnlogger inherits some of its LogSink implementation from Formatter // and just needs to add some glue code. type fnlogger struct { Formatter write func(prefix, args string) } func (l fnlogger) WithName(name string) logr.LogSink { l.Formatter.AddName(name) return &l } func (l fnlogger) WithValues(kvList ...interface{}) logr.LogSink { l.Formatter.AddValues(kvList) return &l } func (l fnlogger) WithCallDepth(depth int) logr.LogSink { l.Formatter.AddCallDepth(depth) return &l } func (l fnlogger) Info(level int, msg string, kvList ...interface{}) { prefix, args := l.FormatInfo(level, msg, kvList) l.write(prefix, args) } func (l fnlogger) Error(err error, msg string, kvList ...interface{}) { prefix, args := l.FormatError(err, msg, kvList) l.write(prefix, args) } func (l fnlogger) GetUnderlying() func(prefix, args string) { return l.write } // Assert conformance to the interfaces. var _ logr.LogSink = &fnlogger{} var _ logr.CallDepthLogSink = &fnlogger{} var _ Underlier = &fnlogger{} // NewFormatter constructs a Formatter which emits a JSON-like key=value format. func NewFormatter(opts Options) Formatter { return newFormatter(opts, outputKeyValue) } // NewFormatterJSON constructs a Formatter which emits strict JSON. func NewFormatterJSON(opts Options) Formatter { return newFormatter(opts, outputJSON) } // Defaults for Options. const defaultTimestampFormat = "2006-01-02 15:04:05.000000" const defaultMaxLogDepth = 16 func newFormatter(opts Options, outfmt outputFormat) Formatter { if opts.TimestampFormat == "" { opts.TimestampFormat = defaultTimestampFormat } if opts.MaxLogDepth == 0 { opts.MaxLogDepth = defaultMaxLogDepth } f := Formatter{ outputFormat: outfmt, prefix: "", values: nil, depth: 0, opts: opts, } return f } // Formatter is an opaque struct which can be embedded in a LogSink // implementation. It should be constructed with NewFormatter. Some of // its methods directly implement logr.LogSink. type Formatter struct { outputFormat outputFormat prefix string values []interface{} valuesStr string depth int opts Options } // outputFormat indicates which outputFormat to use. type outputFormat int const ( // outputKeyValue emits a JSON-like key=value format, but not strict JSON. outputKeyValue outputFormat = iota // outputJSON emits strict JSON. outputJSON ) // PseudoStruct is a list of key-value pairs that gets logged as a struct. type PseudoStruct []interface{} // render produces a log line, ready to use. func (f Formatter) render(builtins, args []interface{}) string { // Empirically bytes.Buffer is faster than strings.Builder for this. buf := bytes.NewBuffer(make([]byte, 0, 1024)) if f.outputFormat == outputJSON { buf.WriteByte('{') } vals := builtins if hook := f.opts.RenderBuiltinsHook; hook != nil { vals = hook(f.sanitize(vals)) } f.flatten(buf, vals, false, false) // keys are ours, no need to escape continuing := len(builtins) > 0 if len(f.valuesStr) > 0 { if continuing { if f.outputFormat == outputJSON { buf.WriteByte(',') } else { buf.WriteByte(' ') } } continuing = true buf.WriteString(f.valuesStr) } vals = args if hook := f.opts.RenderArgsHook; hook != nil { vals = hook(f.sanitize(vals)) } f.flatten(buf, vals, continuing, true) // escape user-provided keys if f.outputFormat == outputJSON { buf.WriteByte('}') } return buf.String() } // flatten renders a list of key-value pairs into a buffer. If continuing is // true, it assumes that the buffer has previous values and will emit a // separator (which depends on the output format) before the first pair it // writes. If escapeKeys is true, the keys are assumed to have // non-JSON-compatible characters in them and must be evaluated for escapes. // // This function returns a potentially modified version of kvList, which // ensures that there is a value for every key (adding a value if needed) and // that each key is a string (substituting a key if needed). func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} { // This logic overlaps with sanitize() but saves one type-cast per key, // which can be measurable. if len(kvList)%2 != 0 { kvList = append(kvList, noValue) } for i := 0; i < len(kvList); i += 2 { k, ok := kvList[i].(string) if !ok { k = f.nonStringKey(kvList[i]) kvList[i] = k } v := kvList[i+1] if i > 0 || continuing { if f.outputFormat == outputJSON { buf.WriteByte(',') } else { // In theory the format could be something we don't understand. In // practice, we control it, so it won't be. buf.WriteByte(' ') } } if escapeKeys { buf.WriteString(prettyString(k)) } else { // this is faster buf.WriteByte('"') buf.WriteString(k) buf.WriteByte('"') } if f.outputFormat == outputJSON { buf.WriteByte(':') } else { buf.WriteByte('=') } buf.WriteString(f.pretty(v)) } return kvList } func (f Formatter) pretty(value interface{}) string { return f.prettyWithFlags(value, 0, 0) } const ( flagRawStruct = 0x1 // do not print braces on structs ) // TODO: This is not fast. Most of the overhead goes here. func (f Formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { if depth > f.opts.MaxLogDepth { return `""` } // Handle types that take full control of logging. if v, ok := value.(logr.Marshaler); ok { // Replace the value with what the type wants to get logged. // That then gets handled below via reflection. value = invokeMarshaler(v) } // Handle types that want to format themselves. switch v := value.(type) { case fmt.Stringer: value = invokeStringer(v) case error: value = invokeError(v) } // Handling the most common types without reflect is a small perf win. switch v := value.(type) { case bool: return strconv.FormatBool(v) case string: return prettyString(v) case int: return strconv.FormatInt(int64(v), 10) case int8: return strconv.FormatInt(int64(v), 10) case int16: return strconv.FormatInt(int64(v), 10) case int32: return strconv.FormatInt(int64(v), 10) case int64: return strconv.FormatInt(int64(v), 10) case uint: return strconv.FormatUint(uint64(v), 10) case uint8: return strconv.FormatUint(uint64(v), 10) case uint16: return strconv.FormatUint(uint64(v), 10) case uint32: return strconv.FormatUint(uint64(v), 10) case uint64: return strconv.FormatUint(v, 10) case uintptr: return strconv.FormatUint(uint64(v), 10) case float32: return strconv.FormatFloat(float64(v), 'f', -1, 32) case float64: return strconv.FormatFloat(v, 'f', -1, 64) case complex64: return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` case complex128: return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` case PseudoStruct: buf := bytes.NewBuffer(make([]byte, 0, 1024)) v = f.sanitize(v) if flags&flagRawStruct == 0 { buf.WriteByte('{') } for i := 0; i < len(v); i += 2 { if i > 0 { buf.WriteByte(',') } k, _ := v[i].(string) // sanitize() above means no need to check success // arbitrary keys might need escaping buf.WriteString(prettyString(k)) buf.WriteByte(':') buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) } if flags&flagRawStruct == 0 { buf.WriteByte('}') } return buf.String() } buf := bytes.NewBuffer(make([]byte, 0, 256)) t := reflect.TypeOf(value) if t == nil { return "null" } v := reflect.ValueOf(value) switch t.Kind() { case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.String: return prettyString(v.String()) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(int64(v.Int()), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return strconv.FormatUint(uint64(v.Uint()), 10) case reflect.Float32: return strconv.FormatFloat(float64(v.Float()), 'f', -1, 32) case reflect.Float64: return strconv.FormatFloat(v.Float(), 'f', -1, 64) case reflect.Complex64: return `"` + strconv.FormatComplex(complex128(v.Complex()), 'f', -1, 64) + `"` case reflect.Complex128: return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` case reflect.Struct: if flags&flagRawStruct == 0 { buf.WriteByte('{') } for i := 0; i < t.NumField(); i++ { fld := t.Field(i) if fld.PkgPath != "" { // reflect says this field is only defined for non-exported fields. continue } if !v.Field(i).CanInterface() { // reflect isn't clear exactly what this means, but we can't use it. continue } name := "" omitempty := false if tag, found := fld.Tag.Lookup("json"); found { if tag == "-" { continue } if comma := strings.Index(tag, ","); comma != -1 { if n := tag[:comma]; n != "" { name = n } rest := tag[comma:] if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { omitempty = true } } else { name = tag } } if omitempty && isEmpty(v.Field(i)) { continue } if i > 0 { buf.WriteByte(',') } if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) continue } if name == "" { name = fld.Name } // field names can't contain characters which need escaping buf.WriteByte('"') buf.WriteString(name) buf.WriteByte('"') buf.WriteByte(':') buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) } if flags&flagRawStruct == 0 { buf.WriteByte('}') } return buf.String() case reflect.Slice, reflect.Array: buf.WriteByte('[') for i := 0; i < v.Len(); i++ { if i > 0 { buf.WriteByte(',') } e := v.Index(i) buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) } buf.WriteByte(']') return buf.String() case reflect.Map: buf.WriteByte('{') // This does not sort the map keys, for best perf. it := v.MapRange() i := 0 for it.Next() { if i > 0 { buf.WriteByte(',') } // If a map key supports TextMarshaler, use it. keystr := "" if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { txt, err := m.MarshalText() if err != nil { keystr = fmt.Sprintf("", err.Error()) } else { keystr = string(txt) } keystr = prettyString(keystr) } else { // prettyWithFlags will produce already-escaped values keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) if t.Key().Kind() != reflect.String { // JSON only does string keys. Unlike Go's standard JSON, we'll // convert just about anything to a string. keystr = prettyString(keystr) } } buf.WriteString(keystr) buf.WriteByte(':') buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) i++ } buf.WriteByte('}') return buf.String() case reflect.Ptr, reflect.Interface: if v.IsNil() { return "null" } return f.prettyWithFlags(v.Elem().Interface(), 0, depth) } return fmt.Sprintf(`""`, t.Kind().String()) } func prettyString(s string) string { // Avoid escaping (which does allocations) if we can. if needsEscape(s) { return strconv.Quote(s) } b := bytes.NewBuffer(make([]byte, 0, 1024)) b.WriteByte('"') b.WriteString(s) b.WriteByte('"') return b.String() } // needsEscape determines whether the input string needs to be escaped or not, // without doing any allocations. func needsEscape(s string) bool { for _, r := range s { if !strconv.IsPrint(r) || r == '\\' || r == '"' { return true } } return false } func isEmpty(v reflect.Value) bool { switch v.Kind() { case reflect.Array, reflect.Map, reflect.Slice, reflect.String: return v.Len() == 0 case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return v.Float() == 0 case reflect.Complex64, reflect.Complex128: return v.Complex() == 0 case reflect.Interface, reflect.Ptr: return v.IsNil() } return false } func invokeMarshaler(m logr.Marshaler) (ret interface{}) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return m.MarshalLog() } func invokeStringer(s fmt.Stringer) (ret string) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return s.String() } func invokeError(e error) (ret string) { defer func() { if r := recover(); r != nil { ret = fmt.Sprintf("", r) } }() return e.Error() } // Caller represents the original call site for a log line, after considering // logr.Logger.WithCallDepth and logr.Logger.WithCallStackHelper. The File and // Line fields will always be provided, while the Func field is optional. // Users can set the render hook fields in Options to examine logged key-value // pairs, one of which will be {"caller", Caller} if the Options.LogCaller // field is enabled for the given MessageClass. type Caller struct { // File is the basename of the file for this call site. File string `json:"file"` // Line is the line number in the file for this call site. Line int `json:"line"` // Func is the function name for this call site, or empty if // Options.LogCallerFunc is not enabled. Func string `json:"function,omitempty"` } func (f Formatter) caller() Caller { // +1 for this frame, +1 for Info/Error. pc, file, line, ok := runtime.Caller(f.depth + 2) if !ok { return Caller{"", 0, ""} } fn := "" if f.opts.LogCallerFunc { if fp := runtime.FuncForPC(pc); fp != nil { fn = fp.Name() } } return Caller{filepath.Base(file), line, fn} } const noValue = "" func (f Formatter) nonStringKey(v interface{}) string { return fmt.Sprintf("", f.snippet(v)) } // snippet produces a short snippet string of an arbitrary value. func (f Formatter) snippet(v interface{}) string { const snipLen = 16 snip := f.pretty(v) if len(snip) > snipLen { snip = snip[:snipLen] } return snip } // sanitize ensures that a list of key-value pairs has a value for every key // (adding a value if needed) and that each key is a string (substituting a key // if needed). func (f Formatter) sanitize(kvList []interface{}) []interface{} { if len(kvList)%2 != 0 { kvList = append(kvList, noValue) } for i := 0; i < len(kvList); i += 2 { _, ok := kvList[i].(string) if !ok { kvList[i] = f.nonStringKey(kvList[i]) } } return kvList } // Init configures this Formatter from runtime info, such as the call depth // imposed by logr itself. // Note that this receiver is a pointer, so depth can be saved. func (f *Formatter) Init(info logr.RuntimeInfo) { f.depth += info.CallDepth } // Enabled checks whether an info message at the given level should be logged. func (f Formatter) Enabled(level int) bool { return level <= f.opts.Verbosity } // GetDepth returns the current depth of this Formatter. This is useful for // implementations which do their own caller attribution. func (f Formatter) GetDepth() int { return f.depth } // FormatInfo renders an Info log message into strings. The prefix will be // empty when no names were set (via AddNames), or when the output is // configured for JSON. func (f Formatter) FormatInfo(level int, msg string, kvList []interface{}) (prefix, argsStr string) { args := make([]interface{}, 0, 64) // using a constant here impacts perf prefix = f.prefix if f.outputFormat == outputJSON { args = append(args, "logger", prefix) prefix = "" } if f.opts.LogTimestamp { args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) } if policy := f.opts.LogCaller; policy == All || policy == Info { args = append(args, "caller", f.caller()) } args = append(args, "level", level, "msg", msg) return prefix, f.render(args, kvList) } // FormatError renders an Error log message into strings. The prefix will be // empty when no names were set (via AddNames), or when the output is // configured for JSON. func (f Formatter) FormatError(err error, msg string, kvList []interface{}) (prefix, argsStr string) { args := make([]interface{}, 0, 64) // using a constant here impacts perf prefix = f.prefix if f.outputFormat == outputJSON { args = append(args, "logger", prefix) prefix = "" } if f.opts.LogTimestamp { args = append(args, "ts", time.Now().Format(f.opts.TimestampFormat)) } if policy := f.opts.LogCaller; policy == All || policy == Error { args = append(args, "caller", f.caller()) } args = append(args, "msg", msg) var loggableErr interface{} if err != nil { loggableErr = err.Error() } args = append(args, "error", loggableErr) return f.prefix, f.render(args, kvList) } // AddName appends the specified name. funcr uses '/' characters to separate // name elements. Callers should not pass '/' in the provided name string, but // this library does not actually enforce that. func (f *Formatter) AddName(name string) { if len(f.prefix) > 0 { f.prefix += "/" } f.prefix += name } // AddValues adds key-value pairs to the set of saved values to be logged with // each log line. func (f *Formatter) AddValues(kvList []interface{}) { // Three slice args forces a copy. n := len(f.values) f.values = append(f.values[:n:n], kvList...) vals := f.values if hook := f.opts.RenderValuesHook; hook != nil { vals = hook(f.sanitize(vals)) } // Pre-render values, so we don't have to do it on each Info/Error call. buf := bytes.NewBuffer(make([]byte, 0, 1024)) f.flatten(buf, vals, false, true) // escape user-provided keys f.valuesStr = buf.String() } // AddCallDepth increases the number of stack-frames to skip when attributing // the log line to a file and line. func (f *Formatter) AddCallDepth(depth int) { f.depth += depth } golang-github-go-logr-logr-1.2.3/funcr/funcr_test.go000066400000000000000000000761731432555311600224140ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package funcr import ( "encoding/json" "fmt" "path/filepath" "reflect" "runtime" "testing" "github.com/go-logr/logr" ) // Will be handled via reflection instead of type assertions. type substr string func ptrint(i int) *int { return &i } func ptrstr(s string) *string { return &s } // point implements encoding.TextMarshaler and can be used as a map key. type point struct{ x, y int } func (p point) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("(%d, %d)", p.x, p.y)), nil } // pointErr implements encoding.TextMarshaler but returns an error. type pointErr struct{ x, y int } func (p pointErr) MarshalText() ([]byte, error) { return nil, fmt.Errorf("uh oh: %d, %d", p.x, p.y) } // Logging this should result in the MarshalLog() value. type Tmarshaler struct{ val string } func (t Tmarshaler) MarshalLog() interface{} { return struct{ Inner string }{"I am a logr.Marshaler"} } func (t Tmarshaler) String() string { return "String(): you should not see this" } func (t Tmarshaler) Error() string { return "Error(): you should not see this" } // Logging this should result in a panic. type Tmarshalerpanic struct{ val string } func (t Tmarshalerpanic) MarshalLog() interface{} { panic("Tmarshalerpanic") } // Logging this should result in the String() value. type Tstringer struct{ val string } func (t Tstringer) String() string { return "I am a fmt.Stringer" } func (t Tstringer) Error() string { return "Error(): you should not see this" } // Logging this should result in a panic. type Tstringerpanic struct{ val string } func (t Tstringerpanic) String() string { panic("Tstringerpanic") } // Logging this should result in the Error() value. type Terror struct{ val string } func (t Terror) Error() string { return "I am an error" } // Logging this should result in a panic. type Terrorpanic struct{ val string } func (t Terrorpanic) Error() string { panic("Terrorpanic") } type TjsontagsString struct { String1 string `json:"string1"` // renamed String2 string `json:"-"` // ignored String3 string `json:"-,"` // named "-" String4 string `json:"string4,omitempty"` // renamed, ignore if empty String5 string `json:","` // no-op String6 string `json:",omitempty"` // ignore if empty } type TjsontagsBool struct { Bool1 bool `json:"bool1"` // renamed Bool2 bool `json:"-"` // ignored Bool3 bool `json:"-,"` // named "-" Bool4 bool `json:"bool4,omitempty"` // renamed, ignore if empty Bool5 bool `json:","` // no-op Bool6 bool `json:",omitempty"` // ignore if empty } type TjsontagsInt struct { Int1 int `json:"int1"` // renamed Int2 int `json:"-"` // ignored Int3 int `json:"-,"` // named "-" Int4 int `json:"int4,omitempty"` // renamed, ignore if empty Int5 int `json:","` // no-op Int6 int `json:",omitempty"` // ignore if empty } type TjsontagsUint struct { Uint1 uint `json:"uint1"` // renamed Uint2 uint `json:"-"` // ignored Uint3 uint `json:"-,"` // named "-" Uint4 uint `json:"uint4,omitempty"` // renamed, ignore if empty Uint5 uint `json:","` // no-op Uint6 uint `json:",omitempty"` // ignore if empty } type TjsontagsFloat struct { Float1 float64 `json:"float1"` // renamed Float2 float64 `json:"-"` // ignored Float3 float64 `json:"-,"` // named "-" Float4 float64 `json:"float4,omitempty"` // renamed, ignore if empty Float5 float64 `json:","` // no-op Float6 float64 `json:",omitempty"` // ignore if empty } type TjsontagsComplex struct { Complex1 complex128 `json:"complex1"` // renamed Complex2 complex128 `json:"-"` // ignored Complex3 complex128 `json:"-,"` // named "-" Complex4 complex128 `json:"complex4,omitempty"` // renamed, ignore if empty Complex5 complex128 `json:","` // no-op Complex6 complex128 `json:",omitempty"` // ignore if empty } type TjsontagsPtr struct { Ptr1 *string `json:"ptr1"` // renamed Ptr2 *string `json:"-"` // ignored Ptr3 *string `json:"-,"` // named "-" Ptr4 *string `json:"ptr4,omitempty"` // renamed, ignore if empty Ptr5 *string `json:","` // no-op Ptr6 *string `json:",omitempty"` // ignore if empty } type TjsontagsArray struct { Array1 [2]string `json:"array1"` // renamed Array2 [2]string `json:"-"` // ignored Array3 [2]string `json:"-,"` // named "-" Array4 [2]string `json:"array4,omitempty"` // renamed, ignore if empty Array5 [2]string `json:","` // no-op Array6 [2]string `json:",omitempty"` // ignore if empty } type TjsontagsSlice struct { Slice1 []string `json:"slice1"` // renamed Slice2 []string `json:"-"` // ignored Slice3 []string `json:"-,"` // named "-" Slice4 []string `json:"slice4,omitempty"` // renamed, ignore if empty Slice5 []string `json:","` // no-op Slice6 []string `json:",omitempty"` // ignore if empty } type TjsontagsMap struct { Map1 map[string]string `json:"map1"` // renamed Map2 map[string]string `json:"-"` // ignored Map3 map[string]string `json:"-,"` // named "-" Map4 map[string]string `json:"map4,omitempty"` // renamed, ignore if empty Map5 map[string]string `json:","` // no-op Map6 map[string]string `json:",omitempty"` // ignore if empty } type Tinnerstruct struct { Inner string } type Tinnerint int type Tinnermap map[string]string type Tinnerslice []string type Tembedstruct struct { Tinnerstruct Outer string } type Tembednonstruct struct { Tinnerint Tinnermap Tinnerslice } type Tinner1 Tinnerstruct type Tinner2 Tinnerstruct type Tinner3 Tinnerstruct type Tinner4 Tinnerstruct type Tinner5 Tinnerstruct type Tinner6 Tinnerstruct type Tembedjsontags struct { Outer string Tinner1 `json:"inner1"` Tinner2 `json:"-"` Tinner3 `json:"-,"` Tinner4 `json:"inner4,omitempty"` Tinner5 `json:","` Tinner6 `json:"inner6,omitempty"` } func TestPretty(t *testing.T) { // used below newStr := func(s string) *string { return &s } cases := []struct { val interface{} exp string // used in cases where JSON can't handle it }{{ val: "strval", }, { val: "strval\nwith\t\"escapes\"", }, { val: substr("substrval"), }, { val: substr("substrval\nwith\t\"escapes\""), }, { val: true, }, { val: false, }, { val: int(93), }, { val: int8(93), }, { val: int16(93), }, { val: int32(93), }, { val: int64(93), }, { val: int(-93), }, { val: int8(-93), }, { val: int16(-93), }, { val: int32(-93), }, { val: int64(-93), }, { val: uint(93), }, { val: uint8(93), }, { val: uint16(93), }, { val: uint32(93), }, { val: uint64(93), }, { val: uintptr(93), }, { val: float32(93.76), }, { val: float64(93.76), }, { val: complex64(93i), exp: `"(0+93i)"`, }, { val: complex128(93i), exp: `"(0+93i)"`, }, { val: ptrint(93), }, { val: ptrstr("pstrval"), }, { val: []int{}, }, { val: []int(nil), exp: `[]`, }, { val: []int{9, 3, 7, 6}, }, { val: []string{"str", "with\tescape"}, }, { val: []substr{"substr", "with\tescape"}, }, { val: [4]int{9, 3, 7, 6}, }, { val: [2]string{"str", "with\tescape"}, }, { val: [2]substr{"substr", "with\tescape"}, }, { val: struct { Int int notExported string String string }{ 93, "you should not see this", "seventy-six", }, }, { val: map[string]int{}, }, { val: map[string]int(nil), exp: `{}`, }, { val: map[string]int{ "nine": 3, }, }, { val: map[string]int{ "with\tescape": 76, }, }, { val: map[substr]int{ "nine": 3, }, }, { val: map[substr]int{ "with\tescape": 76, }, }, { val: map[int]int{ 9: 3, }, }, { val: map[float64]int{ 9.5: 3, }, exp: `{"9.5":3}`, }, { val: map[point]int{ {x: 1, y: 2}: 3, }, }, { val: map[pointErr]int{ {x: 1, y: 2}: 3, }, exp: `{"":3}`, }, { val: struct { X int `json:"x"` Y int `json:"y"` }{ 93, 76, }, }, { val: struct { X []int Y map[int]int Z struct{ P, Q int } }{ []int{9, 3, 7, 6}, map[int]int{9: 3}, struct{ P, Q int }{9, 3}, }, }, { val: []struct{ X, Y string }{ {"nine", "three"}, {"seven", "six"}, {"with\t", "\tescapes"}, }, }, { val: struct { A *int B *int C interface{} D interface{} }{ B: ptrint(1), D: interface{}(2), }, }, { val: Tmarshaler{"foobar"}, exp: `{"Inner":"I am a logr.Marshaler"}`, }, { val: &Tmarshaler{"foobar"}, exp: `{"Inner":"I am a logr.Marshaler"}`, }, { val: (*Tmarshaler)(nil), exp: `""`, }, { val: Tmarshalerpanic{"foobar"}, exp: `""`, }, { val: Tstringer{"foobar"}, exp: `"I am a fmt.Stringer"`, }, { val: &Tstringer{"foobar"}, exp: `"I am a fmt.Stringer"`, }, { val: (*Tstringer)(nil), exp: `""`, }, { val: Tstringerpanic{"foobar"}, exp: `""`, }, { val: Terror{"foobar"}, exp: `"I am an error"`, }, { val: &Terror{"foobar"}, exp: `"I am an error"`, }, { val: (*Terror)(nil), exp: `""`, }, { val: Terrorpanic{"foobar"}, exp: `""`, }, { val: TjsontagsString{ String1: "v1", String2: "v2", String3: "v3", String4: "v4", String5: "v5", String6: "v6", }, }, { val: TjsontagsString{}, }, { val: TjsontagsBool{ Bool1: true, Bool2: true, Bool3: true, Bool4: true, Bool5: true, Bool6: true, }, }, { val: TjsontagsBool{}, }, { val: TjsontagsInt{ Int1: 1, Int2: 2, Int3: 3, Int4: 4, Int5: 5, Int6: 6, }, }, { val: TjsontagsInt{}, }, { val: TjsontagsUint{ Uint1: 1, Uint2: 2, Uint3: 3, Uint4: 4, Uint5: 5, Uint6: 6, }, }, { val: TjsontagsUint{}, }, { val: TjsontagsFloat{ Float1: 1.1, Float2: 2.2, Float3: 3.3, Float4: 4.4, Float5: 5.5, Float6: 6.6, }, }, { val: TjsontagsFloat{}, }, { val: TjsontagsComplex{ Complex1: 1i, Complex2: 2i, Complex3: 3i, Complex4: 4i, Complex5: 5i, Complex6: 6i, }, exp: `{"complex1":"(0+1i)","-":"(0+3i)","complex4":"(0+4i)","Complex5":"(0+5i)","Complex6":"(0+6i)"}`, }, { val: TjsontagsComplex{}, exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`, }, { val: TjsontagsPtr{ Ptr1: newStr("1"), Ptr2: newStr("2"), Ptr3: newStr("3"), Ptr4: newStr("4"), Ptr5: newStr("5"), Ptr6: newStr("6"), }, }, { val: TjsontagsPtr{}, }, { val: TjsontagsArray{ Array1: [2]string{"v1", "v1"}, Array2: [2]string{"v2", "v2"}, Array3: [2]string{"v3", "v3"}, Array4: [2]string{"v4", "v4"}, Array5: [2]string{"v5", "v5"}, Array6: [2]string{"v6", "v6"}, }, }, { val: TjsontagsArray{}, }, { val: TjsontagsSlice{ Slice1: []string{"v1", "v1"}, Slice2: []string{"v2", "v2"}, Slice3: []string{"v3", "v3"}, Slice4: []string{"v4", "v4"}, Slice5: []string{"v5", "v5"}, Slice6: []string{"v6", "v6"}, }, }, { val: TjsontagsSlice{}, exp: `{"slice1":[],"-":[],"Slice5":[]}`, }, { val: TjsontagsMap{ Map1: map[string]string{"k1": "v1"}, Map2: map[string]string{"k2": "v2"}, Map3: map[string]string{"k3": "v3"}, Map4: map[string]string{"k4": "v4"}, Map5: map[string]string{"k5": "v5"}, Map6: map[string]string{"k6": "v6"}, }, }, { val: TjsontagsMap{}, exp: `{"map1":{},"-":{},"Map5":{}}`, }, { val: Tembedstruct{}, }, { val: Tembednonstruct{}, exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`, }, { val: Tembedjsontags{}, }, { val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})), exp: `{"f1":1,"f2":true,"f3":[]}`, }, { val: map[TjsontagsString]int{ {String1: `"quoted"`, String4: `unquoted`}: 1, }, exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`, }, { val: map[TjsontagsInt]int{ {Int1: 1, Int2: 2}: 3, }, exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`, }, { val: map[[2]struct{ S string }]int{ {{S: `"quoted"`}, {S: "unquoted"}}: 1, }, exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`, }, { val: TjsontagsComplex{}, exp: `{"complex1":"(0+0i)","-":"(0+0i)","Complex5":"(0+0i)"}`, }, { val: TjsontagsPtr{ Ptr1: newStr("1"), Ptr2: newStr("2"), Ptr3: newStr("3"), Ptr4: newStr("4"), Ptr5: newStr("5"), Ptr6: newStr("6"), }, }, { val: TjsontagsPtr{}, }, { val: TjsontagsArray{ Array1: [2]string{"v1", "v1"}, Array2: [2]string{"v2", "v2"}, Array3: [2]string{"v3", "v3"}, Array4: [2]string{"v4", "v4"}, Array5: [2]string{"v5", "v5"}, Array6: [2]string{"v6", "v6"}, }, }, { val: TjsontagsArray{}, }, { val: TjsontagsSlice{ Slice1: []string{"v1", "v1"}, Slice2: []string{"v2", "v2"}, Slice3: []string{"v3", "v3"}, Slice4: []string{"v4", "v4"}, Slice5: []string{"v5", "v5"}, Slice6: []string{"v6", "v6"}, }, }, { val: TjsontagsSlice{}, exp: `{"slice1":[],"-":[],"Slice5":[]}`, }, { val: TjsontagsMap{ Map1: map[string]string{"k1": "v1"}, Map2: map[string]string{"k2": "v2"}, Map3: map[string]string{"k3": "v3"}, Map4: map[string]string{"k4": "v4"}, Map5: map[string]string{"k5": "v5"}, Map6: map[string]string{"k6": "v6"}, }, }, { val: TjsontagsMap{}, exp: `{"map1":{},"-":{},"Map5":{}}`, }, { val: Tembedstruct{}, }, { val: Tembednonstruct{}, exp: `{"Tinnerint":0,"Tinnermap":{},"Tinnerslice":[]}`, }, { val: Tembedjsontags{}, }, { val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})), exp: `{"f1":1,"f2":true,"f3":[]}`, }, { val: map[TjsontagsString]int{ {String1: `"quoted"`, String4: `unquoted`}: 1, }, exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`, }, { val: map[TjsontagsInt]int{ {Int1: 1, Int2: 2}: 3, }, exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`, }, { val: map[[2]struct{ S string }]int{ {{S: `"quoted"`}, {S: "unquoted"}}: 1, }, exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`, }} f := NewFormatter(Options{}) for i, tc := range cases { ours := f.pretty(tc.val) want := "" if tc.exp != "" { want = tc.exp } else { jb, err := json.Marshal(tc.val) if err != nil { t.Fatalf("[%d]: unexpected error: %v\ngot: %q", i, err, ours) } want = string(jb) } if ours != want { t.Errorf("[%d]:\n\texpected %q\n\tgot %q", i, want, ours) } } } func makeKV(args ...interface{}) []interface{} { return args } func TestRender(t *testing.T) { testCases := []struct { name string builtins []interface{} values []interface{} args []interface{} expectKV string expectJSON string }{{ name: "nil", expectKV: "", expectJSON: "{}", }, { name: "empty", builtins: []interface{}{}, values: []interface{}{}, args: []interface{}{}, expectKV: "", expectJSON: "{}", }, { name: "primitives", builtins: makeKV("int1", 1, "int2", 2), values: makeKV("str1", "ABC", "str2", "DEF"), args: makeKV("bool1", true, "bool2", false), expectKV: `"int1"=1 "int2"=2 "str1"="ABC" "str2"="DEF" "bool1"=true "bool2"=false`, expectJSON: `{"int1":1,"int2":2,"str1":"ABC","str2":"DEF","bool1":true,"bool2":false}`, }, { name: "pseudo structs", builtins: makeKV("int", PseudoStruct(makeKV("intsub", 1))), values: makeKV("str", PseudoStruct(makeKV("strsub", "2"))), args: makeKV("bool", PseudoStruct(makeKV("boolsub", true))), expectKV: `"int"={"intsub":1} "str"={"strsub":"2"} "bool"={"boolsub":true}`, expectJSON: `{"int":{"intsub":1},"str":{"strsub":"2"},"bool":{"boolsub":true}}`, }, { name: "escapes", builtins: makeKV("\"1\"", 1), // will not be escaped, but should never happen values: makeKV("\tstr", "ABC"), // escaped args: makeKV("bool\n", true), // escaped expectKV: `""1""=1 "\tstr"="ABC" "bool\n"=true`, expectJSON: `{""1"":1,"\tstr":"ABC","bool\n":true}`, }, { name: "missing value", builtins: makeKV("builtin"), values: makeKV("value"), args: makeKV("arg"), expectKV: `"builtin"="" "value"="" "arg"=""`, expectJSON: `{"builtin":"","value":"","arg":""}`, }, { name: "non-string key int", builtins: makeKV(123, "val"), // should never happen values: makeKV(456, "val"), args: makeKV(789, "val"), expectKV: `""="val" ""="val" ""="val"`, expectJSON: `{"":"val","":"val","":"val"}`, }, { name: "non-string key struct", builtins: makeKV(struct { // will not be escaped, but should never happen F1 string F2 int }{"builtin", 123}, "val"), values: makeKV(struct { F1 string F2 int }{"value", 456}, "val"), args: makeKV(struct { F1 string F2 int }{"arg", 789}, "val"), expectKV: `""="val" ""="val" ""="val"`, expectJSON: `{"":"val","":"val","":"val"}`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { test := func(t *testing.T, formatter Formatter, expect string) { formatter.AddValues(tc.values) r := formatter.render(tc.builtins, tc.args) if r != expect { t.Errorf("wrong output:\nexpected %v\n got %v", expect, r) } } t.Run("KV", func(t *testing.T) { test(t, NewFormatter(Options{}), tc.expectKV) }) t.Run("JSON", func(t *testing.T) { test(t, NewFormatterJSON(Options{}), tc.expectJSON) }) }) } } func TestSanitize(t *testing.T) { testCases := []struct { name string kv []interface{} expect []interface{} }{{ name: "empty", kv: []interface{}{}, expect: []interface{}{}, }, { name: "already sane", kv: makeKV("int", 1, "str", "ABC", "bool", true), expect: makeKV("int", 1, "str", "ABC", "bool", true), }, { name: "missing value", kv: makeKV("key"), expect: makeKV("key", ""), }, { name: "non-string key int", kv: makeKV(123, "val"), expect: makeKV("", "val"), }, { name: "non-string key struct", kv: makeKV(struct { F1 string F2 int }{"f1", 8675309}, "val"), expect: makeKV(``, "val"), }} f := NewFormatterJSON(Options{}) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { r := f.sanitize(tc.kv) if !reflect.DeepEqual(r, tc.expect) { t.Errorf("wrong output:\nexpected %q\n got %q", tc.expect, r) } }) } } func TestEnabled(t *testing.T) { t.Run("default V", func(t *testing.T) { log := newSink(func(prefix, args string) {}, NewFormatter(Options{})) if !log.Enabled(0) { t.Errorf("expected true") } if log.Enabled(1) { t.Errorf("expected false") } }) t.Run("V=9", func(t *testing.T) { log := newSink(func(prefix, args string) {}, NewFormatter(Options{Verbosity: 9})) if !log.Enabled(8) { t.Errorf("expected true") } if !log.Enabled(9) { t.Errorf("expected true") } if log.Enabled(10) { t.Errorf("expected false") } }) } type capture struct { log string } func (c *capture) Func(prefix, args string) { c.log = prefix + " " + args } func TestInfo(t *testing.T) { testCases := []struct { name string args []interface{} expect string }{{ name: "just msg", args: makeKV(), expect: ` "level"=0 "msg"="msg"`, }, { name: "primitives", args: makeKV("int", 1, "str", "ABC", "bool", true), expect: ` "level"=0 "msg"="msg" "int"=1 "str"="ABC" "bool"=true`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) sink.Info(0, "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestInfoWithCaller(t *testing.T) { t.Run("LogCaller=All", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } sink.Error(fmt.Errorf("error"), "msg") _, file, line, _ = runtime.Caller(0) expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=All, LogCallerFunc=true", func(t *testing.T) { thisFunc := "github.com/go-logr/logr/funcr.TestInfoWithCaller.func2" cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All, LogCallerFunc: true})) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "level"=0 "msg"="msg"`, filepath.Base(file), line-1, thisFunc) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } sink.Error(fmt.Errorf("error"), "msg") _, file, line, _ = runtime.Caller(0) expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d,"function":%q} "msg"="msg" "error"="error"`, filepath.Base(file), line-1, thisFunc) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=Info", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } sink.Error(fmt.Errorf("error"), "msg") expect = ` "msg"="msg" "error"="error"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=Error", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) sink.Info(0, "msg") expect := ` "level"=0 "msg"="msg"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } sink.Error(fmt.Errorf("error"), "msg") _, file, line, _ := runtime.Caller(0) expect = fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="error"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=None", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) sink.Info(0, "msg") expect := ` "level"=0 "msg"="msg"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } sink.Error(fmt.Errorf("error"), "msg") expect = ` "msg"="msg" "error"="error"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) } func TestError(t *testing.T) { testCases := []struct { name string args []interface{} expect string }{{ name: "just msg", args: makeKV(), expect: ` "msg"="msg" "error"="err"`, }, { name: "primitives", args: makeKV("int", 1, "str", "ABC", "bool", true), expect: ` "msg"="msg" "error"="err" "int"=1 "str"="ABC" "bool"=true`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) sink.Error(fmt.Errorf("err"), "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestErrorWithCaller(t *testing.T) { t.Run("LogCaller=All", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) sink.Error(fmt.Errorf("err"), "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=Error", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Error})) sink.Error(fmt.Errorf("err"), "msg") _, file, line, _ := runtime.Caller(0) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line-1) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=Info", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: Info})) sink.Error(fmt.Errorf("err"), "msg") expect := ` "msg"="msg" "error"="err"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) t.Run("LogCaller=None", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: None})) sink.Error(fmt.Errorf("err"), "msg") expect := ` "msg"="msg" "error"="err"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) } func TestInfoWithName(t *testing.T) { testCases := []struct { name string names []string args []interface{} expect string }{{ name: "one", names: []string{"pfx1"}, args: makeKV("k", "v"), expect: `pfx1 "level"=0 "msg"="msg" "k"="v"`, }, { name: "two", names: []string{"pfx1", "pfx2"}, args: makeKV("k", "v"), expect: `pfx1/pfx2 "level"=0 "msg"="msg" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) for _, n := range tc.names { sink = sink.WithName(n) } sink.Info(0, "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestErrorWithName(t *testing.T) { testCases := []struct { name string names []string args []interface{} expect string }{{ name: "one", names: []string{"pfx1"}, args: makeKV("k", "v"), expect: `pfx1 "msg"="msg" "error"="err" "k"="v"`, }, { name: "two", names: []string{"pfx1", "pfx2"}, args: makeKV("k", "v"), expect: `pfx1/pfx2 "msg"="msg" "error"="err" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) for _, n := range tc.names { sink = sink.WithName(n) } sink.Error(fmt.Errorf("err"), "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestInfoWithValues(t *testing.T) { testCases := []struct { name string values []interface{} args []interface{} expect string }{{ name: "zero", values: makeKV(), args: makeKV("k", "v"), expect: ` "level"=0 "msg"="msg" "k"="v"`, }, { name: "one", values: makeKV("one", 1), args: makeKV("k", "v"), expect: ` "level"=0 "msg"="msg" "one"=1 "k"="v"`, }, { name: "two", values: makeKV("one", 1, "two", 2), args: makeKV("k", "v"), expect: ` "level"=0 "msg"="msg" "one"=1 "two"=2 "k"="v"`, }, { name: "dangling", values: makeKV("dangling"), args: makeKV("k", "v"), expect: ` "level"=0 "msg"="msg" "dangling"="" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) sink = sink.WithValues(tc.values...) sink.Info(0, "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestErrorWithValues(t *testing.T) { testCases := []struct { name string values []interface{} args []interface{} expect string }{{ name: "zero", values: makeKV(), args: makeKV("k", "v"), expect: ` "msg"="msg" "error"="err" "k"="v"`, }, { name: "one", values: makeKV("one", 1), args: makeKV("k", "v"), expect: ` "msg"="msg" "error"="err" "one"=1 "k"="v"`, }, { name: "two", values: makeKV("one", 1, "two", 2), args: makeKV("k", "v"), expect: ` "msg"="msg" "error"="err" "one"=1 "two"=2 "k"="v"`, }, { name: "dangling", values: makeKV("dangling"), args: makeKV("k", "v"), expect: ` "msg"="msg" "error"="err" "dangling"="" "k"="v"`, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{})) sink = sink.WithValues(tc.values...) sink.Error(fmt.Errorf("err"), "msg", tc.args...) if cap.log != tc.expect { t.Errorf("\nexpected %q\n got %q", tc.expect, cap.log) } }) } } func TestInfoWithCallDepth(t *testing.T) { t.Run("one", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) dSink, _ := sink.(logr.CallDepthLogSink) sink = dSink.WithCallDepth(1) sink.Info(0, "msg") _, file, line, _ := runtime.Caller(1) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "level"=0 "msg"="msg"`, filepath.Base(file), line) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) } func TestErrorWithCallDepth(t *testing.T) { t.Run("one", func(t *testing.T) { cap := &capture{} sink := newSink(cap.Func, NewFormatter(Options{LogCaller: All})) dSink, _ := sink.(logr.CallDepthLogSink) sink = dSink.WithCallDepth(1) sink.Error(fmt.Errorf("err"), "msg") _, file, line, _ := runtime.Caller(1) expect := fmt.Sprintf(` "caller"={"file":%q,"line":%d} "msg"="msg" "error"="err"`, filepath.Base(file), line) if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } }) } func TestOptionsTimestampFormat(t *testing.T) { cap := &capture{} // This timestamp format contains none of the characters that are // considered placeholders, so will produce a constant result. sink := newSink(cap.Func, NewFormatter(Options{LogTimestamp: true, TimestampFormat: "TIMESTAMP"})) dSink, _ := sink.(logr.CallDepthLogSink) sink = dSink.WithCallDepth(1) sink.Info(0, "msg") expect := ` "ts"="TIMESTAMP" "level"=0 "msg"="msg"` if cap.log != expect { t.Errorf("\nexpected %q\n got %q", expect, cap.log) } } golang-github-go-logr-logr-1.2.3/go.mod000066400000000000000000000000501432555311600176570ustar00rootroot00000000000000module github.com/go-logr/logr go 1.16 golang-github-go-logr-logr-1.2.3/logr.go000066400000000000000000000502301432555311600200500ustar00rootroot00000000000000/* Copyright 2019 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This design derives from Dave Cheney's blog: // http://dave.cheney.net/2015/11/05/lets-talk-about-logging // Package logr defines a general-purpose logging API and abstract interfaces // to back that API. Packages in the Go ecosystem can depend on this package, // while callers can implement logging with whatever backend is appropriate. // // Usage // // Logging is done using a Logger instance. Logger is a concrete type with // methods, which defers the actual logging to a LogSink interface. The main // methods of Logger are Info() and Error(). Arguments to Info() and Error() // are key/value pairs rather than printf-style formatted strings, emphasizing // "structured logging". // // With Go's standard log package, we might write: // log.Printf("setting target value %s", targetValue) // // With logr's structured logging, we'd write: // logger.Info("setting target", "value", targetValue) // // Errors are much the same. Instead of: // log.Printf("failed to open the pod bay door for user %s: %v", user, err) // // We'd write: // logger.Error(err, "failed to open the pod bay door", "user", user) // // Info() and Error() are very similar, but they are separate methods so that // LogSink implementations can choose to do things like attach additional // information (such as stack traces) on calls to Error(). Error() messages are // always logged, regardless of the current verbosity. If there is no error // instance available, passing nil is valid. // // Verbosity // // Often we want to log information only when the application in "verbose // mode". To write log lines that are more verbose, Logger has a V() method. // The higher the V-level of a log line, the less critical it is considered. // Log-lines with V-levels that are not enabled (as per the LogSink) will not // be written. Level V(0) is the default, and logger.V(0).Info() has the same // meaning as logger.Info(). Negative V-levels have the same meaning as V(0). // Error messages do not have a verbosity level and are always logged. // // Where we might have written: // if flVerbose >= 2 { // log.Printf("an unusual thing happened") // } // // We can write: // logger.V(2).Info("an unusual thing happened") // // Logger Names // // Logger instances can have name strings so that all messages logged through // that instance have additional context. For example, you might want to add // a subsystem name: // // logger.WithName("compactor").Info("started", "time", time.Now()) // // The WithName() method returns a new Logger, which can be passed to // constructors or other functions for further use. Repeated use of WithName() // will accumulate name "segments". These name segments will be joined in some // way by the LogSink implementation. It is strongly recommended that name // segments contain simple identifiers (letters, digits, and hyphen), and do // not contain characters that could muddle the log output or confuse the // joining operation (e.g. whitespace, commas, periods, slashes, brackets, // quotes, etc). // // Saved Values // // Logger instances can store any number of key/value pairs, which will be // logged alongside all messages logged through that instance. For example, // you might want to create a Logger instance per managed object: // // With the standard log package, we might write: // log.Printf("decided to set field foo to value %q for object %s/%s", // targetValue, object.Namespace, object.Name) // // With logr we'd write: // // Elsewhere: set up the logger to log the object name. // obj.logger = mainLogger.WithValues( // "name", obj.name, "namespace", obj.namespace) // // // later on... // obj.logger.Info("setting foo", "value", targetValue) // // Best Practices // // Logger has very few hard rules, with the goal that LogSink implementations // might have a lot of freedom to differentiate. There are, however, some // things to consider. // // The log message consists of a constant message attached to the log line. // This should generally be a simple description of what's occurring, and should // never be a format string. Variable information can then be attached using // named values. // // Keys are arbitrary strings, but should generally be constant values. Values // may be any Go value, but how the value is formatted is determined by the // LogSink implementation. // // Logger instances are meant to be passed around by value. Code that receives // such a value can call its methods without having to check whether the // instance is ready for use. // // Calling methods with the null logger (Logger{}) as instance will crash // because it has no LogSink. Therefore this null logger should never be passed // around. For cases where passing a logger is optional, a pointer to Logger // should be used. // // Key Naming Conventions // // Keys are not strictly required to conform to any specification or regex, but // it is recommended that they: // * be human-readable and meaningful (not auto-generated or simple ordinals) // * be constant (not dependent on input data) // * contain only printable characters // * not contain whitespace or punctuation // * use lower case for simple keys and lowerCamelCase for more complex ones // // These guidelines help ensure that log data is processed properly regardless // of the log implementation. For example, log implementations will try to // output JSON data or will store data for later database (e.g. SQL) queries. // // While users are generally free to use key names of their choice, it's // generally best to avoid using the following keys, as they're frequently used // by implementations: // * "caller": the calling information (file/line) of a particular log line // * "error": the underlying error value in the `Error` method // * "level": the log level // * "logger": the name of the associated logger // * "msg": the log message // * "stacktrace": the stack trace associated with a particular log line or // error (often from the `Error` message) // * "ts": the timestamp for a log line // // Implementations are encouraged to make use of these keys to represent the // above concepts, when necessary (for example, in a pure-JSON output form, it // would be necessary to represent at least message and timestamp as ordinary // named values). // // Break Glass // // Implementations may choose to give callers access to the underlying // logging implementation. The recommended pattern for this is: // // Underlier exposes access to the underlying logging implementation. // // Since callers only have a logr.Logger, they have to know which // // implementation is in use, so this interface is less of an abstraction // // and more of way to test type conversion. // type Underlier interface { // GetUnderlying() // } // // Logger grants access to the sink to enable type assertions like this: // func DoSomethingWithImpl(log logr.Logger) { // if underlier, ok := log.GetSink()(impl.Underlier) { // implLogger := underlier.GetUnderlying() // ... // } // } // // Custom `With*` functions can be implemented by copying the complete // Logger struct and replacing the sink in the copy: // // WithFooBar changes the foobar parameter in the log sink and returns a // // new logger with that modified sink. It does nothing for loggers where // // the sink doesn't support that parameter. // func WithFoobar(log logr.Logger, foobar int) logr.Logger { // if foobarLogSink, ok := log.GetSink()(FoobarSink); ok { // log = log.WithSink(foobarLogSink.WithFooBar(foobar)) // } // return log // } // // Don't use New to construct a new Logger with a LogSink retrieved from an // existing Logger. Source code attribution might not work correctly and // unexported fields in Logger get lost. // // Beware that the same LogSink instance may be shared by different logger // instances. Calling functions that modify the LogSink will affect all of // those. package logr import ( "context" ) // New returns a new Logger instance. This is primarily used by libraries // implementing LogSink, rather than end users. func New(sink LogSink) Logger { logger := Logger{} logger.setSink(sink) sink.Init(runtimeInfo) return logger } // setSink stores the sink and updates any related fields. It mutates the // logger and thus is only safe to use for loggers that are not currently being // used concurrently. func (l *Logger) setSink(sink LogSink) { l.sink = sink } // GetSink returns the stored sink. func (l Logger) GetSink() LogSink { return l.sink } // WithSink returns a copy of the logger with the new sink. func (l Logger) WithSink(sink LogSink) Logger { l.setSink(sink) return l } // Logger is an interface to an abstract logging implementation. This is a // concrete type for performance reasons, but all the real work is passed on to // a LogSink. Implementations of LogSink should provide their own constructors // that return Logger, not LogSink. // // The underlying sink can be accessed through GetSink and be modified through // WithSink. This enables the implementation of custom extensions (see "Break // Glass" in the package documentation). Normally the sink should be used only // indirectly. type Logger struct { sink LogSink level int } // Enabled tests whether this Logger is enabled. For example, commandline // flags might be used to set the logging verbosity and disable some info logs. func (l Logger) Enabled() bool { return l.sink.Enabled(l.level) } // Info logs a non-error message with the given key/value pairs as context. // // The msg argument should be used to add some constant description to the log // line. The key/value pairs can then be used to add additional variable // information. The key/value pairs must alternate string keys and arbitrary // values. func (l Logger) Info(msg string, keysAndValues ...interface{}) { if l.Enabled() { if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } l.sink.Info(l.level, msg, keysAndValues...) } } // Error logs an error, with the given message and key/value pairs as context. // It functions similarly to Info, but may have unique behavior, and should be // preferred for logging errors (see the package documentations for more // information). The log message will always be emitted, regardless of // verbosity level. // // The msg argument should be used to add context to any underlying error, // while the err argument should be used to attach the actual error that // triggered this log line, if present. The err parameter is optional // and nil may be passed instead of an error instance. func (l Logger) Error(err error, msg string, keysAndValues ...interface{}) { if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { withHelper.GetCallStackHelper()() } l.sink.Error(err, msg, keysAndValues...) } // V returns a new Logger instance for a specific verbosity level, relative to // this Logger. In other words, V-levels are additive. A higher verbosity // level means a log message is less important. Negative V-levels are treated // as 0. func (l Logger) V(level int) Logger { if level < 0 { level = 0 } l.level += level return l } // WithValues returns a new Logger instance with additional key/value pairs. // See Info for documentation on how key/value pairs work. func (l Logger) WithValues(keysAndValues ...interface{}) Logger { l.setSink(l.sink.WithValues(keysAndValues...)) return l } // WithName returns a new Logger instance with the specified name element added // to the Logger's name. Successive calls with WithName append additional // suffixes to the Logger's name. It's strongly recommended that name segments // contain only letters, digits, and hyphens (see the package documentation for // more information). func (l Logger) WithName(name string) Logger { l.setSink(l.sink.WithName(name)) return l } // WithCallDepth returns a Logger instance that offsets the call stack by the // specified number of frames when logging call site information, if possible. // This is useful for users who have helper functions between the "real" call // site and the actual calls to Logger methods. If depth is 0 the attribution // should be to the direct caller of this function. If depth is 1 the // attribution should skip 1 call frame, and so on. Successive calls to this // are additive. // // If the underlying log implementation supports a WithCallDepth(int) method, // it will be called and the result returned. If the implementation does not // support CallDepthLogSink, the original Logger will be returned. // // To skip one level, WithCallStackHelper() should be used instead of // WithCallDepth(1) because it works with implementions that support the // CallDepthLogSink and/or CallStackHelperLogSink interfaces. func (l Logger) WithCallDepth(depth int) Logger { if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(depth)) } return l } // WithCallStackHelper returns a new Logger instance that skips the direct // caller when logging call site information, if possible. This is useful for // users who have helper functions between the "real" call site and the actual // calls to Logger methods and want to support loggers which depend on marking // each individual helper function, like loggers based on testing.T. // // In addition to using that new logger instance, callers also must call the // returned function. // // If the underlying log implementation supports a WithCallDepth(int) method, // WithCallDepth(1) will be called to produce a new logger. If it supports a // WithCallStackHelper() method, that will be also called. If the // implementation does not support either of these, the original Logger will be // returned. func (l Logger) WithCallStackHelper() (func(), Logger) { var helper func() if withCallDepth, ok := l.sink.(CallDepthLogSink); ok { l.setSink(withCallDepth.WithCallDepth(1)) } if withHelper, ok := l.sink.(CallStackHelperLogSink); ok { helper = withHelper.GetCallStackHelper() } else { helper = func() {} } return helper, l } // contextKey is how we find Loggers in a context.Context. type contextKey struct{} // FromContext returns a Logger from ctx or an error if no Logger is found. func FromContext(ctx context.Context) (Logger, error) { if v, ok := ctx.Value(contextKey{}).(Logger); ok { return v, nil } return Logger{}, notFoundError{} } // notFoundError exists to carry an IsNotFound method. type notFoundError struct{} func (notFoundError) Error() string { return "no logr.Logger was present" } func (notFoundError) IsNotFound() bool { return true } // FromContextOrDiscard returns a Logger from ctx. If no Logger is found, this // returns a Logger that discards all log messages. func FromContextOrDiscard(ctx context.Context) Logger { if v, ok := ctx.Value(contextKey{}).(Logger); ok { return v } return Discard() } // NewContext returns a new Context, derived from ctx, which carries the // provided Logger. func NewContext(ctx context.Context, logger Logger) context.Context { return context.WithValue(ctx, contextKey{}, logger) } // RuntimeInfo holds information that the logr "core" library knows which // LogSinks might want to know. type RuntimeInfo struct { // CallDepth is the number of call frames the logr library adds between the // end-user and the LogSink. LogSink implementations which choose to print // the original logging site (e.g. file & line) should climb this many // additional frames to find it. CallDepth int } // runtimeInfo is a static global. It must not be changed at run time. var runtimeInfo = RuntimeInfo{ CallDepth: 1, } // LogSink represents a logging implementation. End-users will generally not // interact with this type. type LogSink interface { // Init receives optional information about the logr library for LogSink // implementations that need it. Init(info RuntimeInfo) // Enabled tests whether this LogSink is enabled at the specified V-level. // For example, commandline flags might be used to set the logging // verbosity and disable some info logs. Enabled(level int) bool // Info logs a non-error message with the given key/value pairs as context. // The level argument is provided for optional logging. This method will // only be called when Enabled(level) is true. See Logger.Info for more // details. Info(level int, msg string, keysAndValues ...interface{}) // Error logs an error, with the given message and key/value pairs as // context. See Logger.Error for more details. Error(err error, msg string, keysAndValues ...interface{}) // WithValues returns a new LogSink with additional key/value pairs. See // Logger.WithValues for more details. WithValues(keysAndValues ...interface{}) LogSink // WithName returns a new LogSink with the specified name appended. See // Logger.WithName for more details. WithName(name string) LogSink } // CallDepthLogSink represents a Logger that knows how to climb the call stack // to identify the original call site and can offset the depth by a specified // number of frames. This is useful for users who have helper functions // between the "real" call site and the actual calls to Logger methods. // Implementations that log information about the call site (such as file, // function, or line) would otherwise log information about the intermediate // helper functions. // // This is an optional interface and implementations are not required to // support it. type CallDepthLogSink interface { // WithCallDepth returns a LogSink that will offset the call // stack by the specified number of frames when logging call // site information. // // If depth is 0, the LogSink should skip exactly the number // of call frames defined in RuntimeInfo.CallDepth when Info // or Error are called, i.e. the attribution should be to the // direct caller of Logger.Info or Logger.Error. // // If depth is 1 the attribution should skip 1 call frame, and so on. // Successive calls to this are additive. WithCallDepth(depth int) LogSink } // CallStackHelperLogSink represents a Logger that knows how to climb // the call stack to identify the original call site and can skip // intermediate helper functions if they mark themselves as // helper. Go's testing package uses that approach. // // This is useful for users who have helper functions between the // "real" call site and the actual calls to Logger methods. // Implementations that log information about the call site (such as // file, function, or line) would otherwise log information about the // intermediate helper functions. // // This is an optional interface and implementations are not required // to support it. Implementations that choose to support this must not // simply implement it as WithCallDepth(1), because // Logger.WithCallStackHelper will call both methods if they are // present. This should only be implemented for LogSinks that actually // need it, as with testing.T. type CallStackHelperLogSink interface { // GetCallStackHelper returns a function that must be called // to mark the direct caller as helper function when logging // call site information. GetCallStackHelper() func() } // Marshaler is an optional interface that logged values may choose to // implement. Loggers with structured output, such as JSON, should // log the object return by the MarshalLog method instead of the // original value. type Marshaler interface { // MarshalLog can be used to: // - ensure that structs are not logged as strings when the original // value has a String method: return a different type without a // String method // - select which fields of a complex type should get logged: // return a simpler struct with fewer fields // - log unexported fields: return a different struct // with exported fields // // It may return any value of any type. MarshalLog() interface{} } golang-github-go-logr-logr-1.2.3/logr_test.go000066400000000000000000000235111432555311600211110ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logr import ( "context" "fmt" "reflect" "testing" ) // testLogSink is a Logger just for testing that calls optional hooks on each method. type testLogSink struct { fnInit func(ri RuntimeInfo) fnEnabled func(lvl int) bool fnInfo func(lvl int, msg string, kv ...interface{}) fnError func(err error, msg string, kv ...interface{}) fnWithValues func(kv ...interface{}) fnWithName func(name string) } var _ LogSink = &testLogSink{} func (l *testLogSink) Init(ri RuntimeInfo) { if l.fnInit != nil { l.fnInit(ri) } } func (l *testLogSink) Enabled(lvl int) bool { if l.fnEnabled != nil { return l.fnEnabled(lvl) } return false } func (l *testLogSink) Info(lvl int, msg string, kv ...interface{}) { if l.fnInfo != nil { l.fnInfo(lvl, msg, kv...) } } func (l *testLogSink) Error(err error, msg string, kv ...interface{}) { if l.fnError != nil { l.fnError(err, msg, kv...) } } func (l *testLogSink) WithValues(kv ...interface{}) LogSink { if l.fnWithValues != nil { l.fnWithValues(kv...) } out := *l return &out } func (l *testLogSink) WithName(name string) LogSink { if l.fnWithName != nil { l.fnWithName(name) } out := *l return &out } type testCallDepthLogSink struct { testLogSink callDepth int fnWithCallDepth func(depth int) } var _ CallDepthLogSink = &testCallDepthLogSink{} func (l *testCallDepthLogSink) WithCallDepth(depth int) LogSink { if l.fnWithCallDepth != nil { l.fnWithCallDepth(depth) } out := *l out.callDepth += depth return &out } func TestNew(t *testing.T) { calledInit := 0 sink := &testLogSink{} sink.fnInit = func(ri RuntimeInfo) { if ri.CallDepth != 1 { t.Errorf("expected runtimeInfo.CallDepth = 1, got %d", ri.CallDepth) } calledInit++ } logger := New(sink) if logger.sink == nil { t.Errorf("expected sink to be set, got %v", logger.sink) } if calledInit != 1 { t.Errorf("expected sink.Init() to be called once, got %d", calledInit) } if _, ok := logger.sink.(CallDepthLogSink); ok { t.Errorf("expected conversion to CallDepthLogSink to fail") } } func TestNewCachesCallDepthInterface(t *testing.T) { sink := &testCallDepthLogSink{} logger := New(sink) if _, ok := logger.sink.(CallDepthLogSink); !ok { t.Errorf("expected conversion to CallDepthLogSink to succeed") } } func TestEnabled(t *testing.T) { calledEnabled := 0 sink := &testLogSink{} sink.fnEnabled = func(lvl int) bool { calledEnabled++ return true } logger := New(sink) if en := logger.Enabled(); en != true { t.Errorf("expected true") } if calledEnabled != 1 { t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled) } } func TestError(t *testing.T) { calledError := 0 errInput := fmt.Errorf("error") msgInput := "msg" kvInput := []interface{}{0, 1, 2} sink := &testLogSink{} sink.fnError = func(err error, msg string, kv ...interface{}) { calledError++ if err != errInput { t.Errorf("unexpected err input, got %v", err) } if msg != msgInput { t.Errorf("unexpected msg input, got %q", msg) } if !reflect.DeepEqual(kv, kvInput) { t.Errorf("unexpected kv input, got %v", kv) } } logger := New(sink) logger.Error(errInput, msgInput, kvInput...) if calledError != 1 { t.Errorf("expected sink.Error() to be called once, got %d", calledError) } } func TestV(t *testing.T) { sink := &testLogSink{} logger := New(sink) if l := logger.V(0); l.level != 0 { t.Errorf("expected level 0, got %d", l.level) } if l := logger.V(93); l.level != 93 { t.Errorf("expected level 93, got %d", l.level) } if l := logger.V(70).V(6); l.level != 76 { t.Errorf("expected level 76, got %d", l.level) } if l := logger.V(-1); l.level != 0 { t.Errorf("expected level 0, got %d", l.level) } if l := logger.V(1).V(-1); l.level != 1 { t.Errorf("expected level 1, got %d", l.level) } } func TestInfo(t *testing.T) { calledEnabled := 0 calledInfo := 0 lvlInput := 0 msgInput := "msg" kvInput := []interface{}{0, 1, 2} sink := &testLogSink{} sink.fnEnabled = func(lvl int) bool { calledEnabled++ return lvl < 100 } sink.fnInfo = func(lvl int, msg string, kv ...interface{}) { calledInfo++ if lvl != lvlInput { t.Errorf("unexpected lvl input, got %v", lvl) } if msg != msgInput { t.Errorf("unexpected msg input, got %q", msg) } if !reflect.DeepEqual(kv, kvInput) { t.Errorf("unexpected kv input, got %v", kv) } } logger := New(sink) calledEnabled = 0 calledInfo = 0 lvlInput = 0 logger.Info(msgInput, kvInput...) if calledEnabled != 1 { t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled) } if calledInfo != 1 { t.Errorf("expected sink.Info() to be called once, got %d", calledInfo) } calledEnabled = 0 calledInfo = 0 lvlInput = 0 logger.V(0).Info(msgInput, kvInput...) if calledEnabled != 1 { t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled) } if calledInfo != 1 { t.Errorf("expected sink.Info() to be called once, got %d", calledInfo) } calledEnabled = 0 calledInfo = 0 lvlInput = 93 logger.V(93).Info(msgInput, kvInput...) if calledEnabled != 1 { t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled) } if calledInfo != 1 { t.Errorf("expected sink.Info() to be called once, got %d", calledInfo) } calledEnabled = 0 calledInfo = 0 lvlInput = 100 logger.V(100).Info(msgInput, kvInput...) if calledEnabled != 1 { t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled) } if calledInfo != 0 { t.Errorf("expected sink.Info() to not be called, got %d", calledInfo) } } func TestWithValues(t *testing.T) { calledWithValues := 0 kvInput := []interface{}{"zero", 0, "one", 1, "two", 2} sink := &testLogSink{} sink.fnWithValues = func(kv ...interface{}) { calledWithValues++ if !reflect.DeepEqual(kv, kvInput) { t.Errorf("unexpected kv input, got %v", kv) } } logger := New(sink) out := logger.WithValues(kvInput...) if calledWithValues != 1 { t.Errorf("expected sink.WithValues() to be called once, got %d", calledWithValues) } if p, _ := out.sink.(*testLogSink); p == sink { t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p) } } func TestWithName(t *testing.T) { calledWithName := 0 nameInput := "name" sink := &testLogSink{} sink.fnWithName = func(name string) { calledWithName++ if name != nameInput { t.Errorf("unexpected name input, got %q", name) } } logger := New(sink) out := logger.WithName(nameInput) if calledWithName != 1 { t.Errorf("expected sink.WithName() to be called once, got %d", calledWithName) } if p, _ := out.sink.(*testLogSink); p == sink { t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p) } } func TestWithCallDepthNotImplemented(t *testing.T) { depthInput := 7 sink := &testLogSink{} logger := New(sink) out := logger.WithCallDepth(depthInput) if p, _ := out.sink.(*testLogSink); p != sink { t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p) } } func TestWithCallDepthImplemented(t *testing.T) { calledWithCallDepth := 0 depthInput := 7 sink := &testCallDepthLogSink{} sink.fnWithCallDepth = func(depth int) { calledWithCallDepth++ if depth != depthInput { t.Errorf("unexpected depth input, got %d", depth) } } logger := New(sink) out := logger.WithCallDepth(depthInput) if calledWithCallDepth != 1 { t.Errorf("expected sink.WithCallDepth() to be called once, got %d", calledWithCallDepth) } p, _ := out.sink.(*testCallDepthLogSink) if p == sink { t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p) } if p.callDepth != depthInput { t.Errorf("expected sink to have call depth %d, got %d", depthInput, p.callDepth) } } func TestWithCallDepthIncremental(t *testing.T) { calledWithCallDepth := 0 depthInput := 7 sink := &testCallDepthLogSink{} sink.fnWithCallDepth = func(depth int) { calledWithCallDepth++ if depth != 1 { t.Errorf("unexpected depth input, got %d", depth) } } logger := New(sink) out := logger for i := 0; i < depthInput; i++ { out = out.WithCallDepth(1) } if calledWithCallDepth != depthInput { t.Errorf("expected sink.WithCallDepth() to be called %d times, got %d", depthInput, calledWithCallDepth) } p, _ := out.sink.(*testCallDepthLogSink) if p == sink { t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p) } if p.callDepth != depthInput { t.Errorf("expected sink to have call depth %d, got %d", depthInput, p.callDepth) } } func TestContext(t *testing.T) { ctx := context.TODO() if out, err := FromContext(ctx); err == nil { t.Errorf("expected error, got %#v", out) } else if _, ok := err.(notFoundError); !ok { t.Errorf("expected a notFoundError, got %#v", err) } out := FromContextOrDiscard(ctx) if _, ok := out.sink.(discardLogSink); !ok { t.Errorf("expected a discardLogSink, got %#v", out) } sink := &testLogSink{} logger := New(sink) lctx := NewContext(ctx, logger) if out, err := FromContext(lctx); err != nil { t.Errorf("unexpected error: %v", err) } else if p, _ := out.sink.(*testLogSink); p != sink { t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p) } out = FromContextOrDiscard(lctx) if p, _ := out.sink.(*testLogSink); p != sink { t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p) } } golang-github-go-logr-logr-1.2.3/testing/000077500000000000000000000000001432555311600202335ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/testing/test.go000066400000000000000000000026771432555311600215550ustar00rootroot00000000000000/* Copyright 2019 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package testing provides support for using logr in tests. // Deprecated. See github.com/go-logr/logr/testr instead. package testing import "github.com/go-logr/logr/testr" // NewTestLogger returns a logr.Logger that prints through a testing.T object. // Deprecated. See github.com/go-logr/logr/testr.New instead. var NewTestLogger = testr.New // Options carries parameters which influence the way logs are generated. // Deprecated. See github.com/go-logr/logr/testr.Options instead. type Options = testr.Options // NewTestLoggerWithOptions returns a logr.Logger that prints through a testing.T object. // Deprecated. See github.com/go-logr/logr/testr.NewWithOptions instead. var NewTestLoggerWithOptions = testr.NewWithOptions // Underlier exposes access to the underlying testing.T instance. // Deprecated. See github.com/go-logr/logr/testr.Underlier instead. type Underlier = testr.Underlier golang-github-go-logr-logr-1.2.3/testing/test_test.go000066400000000000000000000023411432555311600226000ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testing import ( "fmt" "testing" "github.com/go-logr/logr" ) func TestLogger(t *testing.T) { log := NewTestLogger(t) log.Info("info") log.V(0).Info("V(0).info") log.V(1).Info("v(1).info") log.Error(fmt.Errorf("error"), "error") log.WithName("testing").Info("with prefix") Helper(log, "hello world") log = NewTestLoggerWithOptions(t, Options{ LogTimestamp: true, Verbosity: 1, }) log.V(1).Info("v(1).info with options") } func Helper(log logr.Logger, msg string) { helper, log := log.WithCallStackHelper() helper() helper2(log, msg) } func helper2(log logr.Logger, msg string) { helper, log := log.WithCallStackHelper() helper() log.Info(msg) } golang-github-go-logr-logr-1.2.3/testr/000077500000000000000000000000001432555311600177175ustar00rootroot00000000000000golang-github-go-logr-logr-1.2.3/testr/testr.go000066400000000000000000000057461432555311600214230ustar00rootroot00000000000000/* Copyright 2019 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package testr provides support for using logr in tests. package testr import ( "testing" "github.com/go-logr/logr" "github.com/go-logr/logr/funcr" ) // New returns a logr.Logger that prints through a testing.T object. // Info logs are only enabled at V(0). func New(t *testing.T) logr.Logger { l := &testlogger{ Formatter: funcr.NewFormatter(funcr.Options{}), t: t, } return logr.New(l) } // Options carries parameters which influence the way logs are generated. type Options struct { // LogTimestamp tells the logger to add a "ts" key to log // lines. This has some overhead, so some users might not want // it. LogTimestamp bool // Verbosity tells the logger which V logs to be write. // Higher values enable more logs. Verbosity int } // NewWithOptions returns a logr.Logger that prints through a testing.T object. // In contrast to the simpler New, output formatting can be configured. func NewWithOptions(t *testing.T, opts Options) logr.Logger { l := &testlogger{ Formatter: funcr.NewFormatter(funcr.Options{ LogTimestamp: opts.LogTimestamp, Verbosity: opts.Verbosity, }), t: t, } return logr.New(l) } // Underlier exposes access to the underlying testing.T instance. Since // callers only have a logr.Logger, they have to know which // implementation is in use, so this interface is less of an // abstraction and more of a way to test type conversion. type Underlier interface { GetUnderlying() *testing.T } type testlogger struct { funcr.Formatter t *testing.T } func (l testlogger) WithName(name string) logr.LogSink { l.Formatter.AddName(name) return &l } func (l testlogger) WithValues(kvList ...interface{}) logr.LogSink { l.Formatter.AddValues(kvList) return &l } func (l testlogger) GetCallStackHelper() func() { return l.t.Helper } func (l testlogger) Info(level int, msg string, kvList ...interface{}) { prefix, args := l.FormatInfo(level, msg, kvList) l.t.Helper() if prefix != "" { l.t.Logf("%s: %s", prefix, args) } else { l.t.Log(args) } } func (l testlogger) Error(err error, msg string, kvList ...interface{}) { prefix, args := l.FormatError(err, msg, kvList) l.t.Helper() if prefix != "" { l.t.Logf("%s: %s", prefix, args) } else { l.t.Log(args) } } func (l testlogger) GetUnderlying() *testing.T { return l.t } // Assert conformance to the interfaces. var _ logr.LogSink = &testlogger{} var _ logr.CallStackHelperLogSink = &testlogger{} var _ Underlier = &testlogger{} golang-github-go-logr-logr-1.2.3/testr/testr_test.go000066400000000000000000000023131432555311600224450ustar00rootroot00000000000000/* Copyright 2021 The logr Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package testr import ( "fmt" "testing" "github.com/go-logr/logr" ) func TestLogger(t *testing.T) { log := New(t) log.Info("info") log.V(0).Info("V(0).info") log.V(1).Info("v(1).info") log.Error(fmt.Errorf("error"), "error") log.WithName("testing").Info("with prefix") Helper(log, "hello world") log = NewWithOptions(t, Options{ LogTimestamp: true, Verbosity: 1, }) log.V(1).Info("v(1).info with options") } func Helper(log logr.Logger, msg string) { helper, log := log.WithCallStackHelper() helper() helper2(log, msg) } func helper2(log logr.Logger, msg string) { helper, log := log.WithCallStackHelper() helper() log.Info(msg) }