pax_global_header00006660000000000000000000000064143255545060014523gustar00rootroot0000000000000052 comment=8f6e34b2b04536b5bd10a80bcc3d15372d0af3f9 golang-k8s-klog-2.80.1/000077500000000000000000000000001432555450600145175ustar00rootroot00000000000000golang-k8s-klog-2.80.1/.github/000077500000000000000000000000001432555450600160575ustar00rootroot00000000000000golang-k8s-klog-2.80.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001432555450600202425ustar00rootroot00000000000000golang-k8s-klog-2.80.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000005221432555450600227330ustar00rootroot00000000000000--- name: Bug report about: Tell us about a problem you are experiencing --- /kind bug **What steps did you take and what happened:** [A clear and concise description of what the bug is.] **What did you expect to happen:** **Anything else you would like to add:** [Miscellaneous information that will assist in solving the issue.] golang-k8s-klog-2.80.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000004641432555450600237730ustar00rootroot00000000000000--- name: Feature enhancement request about: Suggest an idea for this project --- /kind feature **Describe the solution you'd like** [A clear and concise description of what you want to happen.] **Anything else you would like to add:** [Miscellaneous information that will assist in solving the issue.] golang-k8s-klog-2.80.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000027301432555450600216620ustar00rootroot00000000000000 **What this PR does / why we need it**: **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Special notes for your reviewer**: _Please confirm that if this PR changes any image versions, then that's the sole change this PR makes._ **Release note**: ```release-note ```golang-k8s-klog-2.80.1/.github/workflows/000077500000000000000000000000001432555450600201145ustar00rootroot00000000000000golang-k8s-klog-2.80.1/.github/workflows/test.yml000066400000000000000000000032561432555450600216240ustar00rootroot00000000000000name: Test on: [push, pull_request] jobs: test: strategy: matrix: go-version: [1.17, 1.18, 1.19] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test klog run: | go get -t -v ./... go test -v -race ./... - name: Test examples run: cd examples && go test -v -race ./... lint: runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v1 - name: Checkout code uses: actions/checkout@v2 - name: Lint run: | docker run --rm -v `pwd`:/go/src/k8s.io/klog -w /go/src/k8s.io/klog \ golangci/golangci-lint:v1.23.8 golangci-lint run --disable-all -v \ -E govet -E misspell -E gofmt -E ineffassign -E golint apidiff: runs-on: ubuntu-latest if: github.base_ref steps: - name: Install Go uses: actions/setup-go@v1 with: go-version: 1.18 - 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: ./hack/verify-apidiff.sh -d ../old working-directory: "new" golang-k8s-klog-2.80.1/.gitignore000066400000000000000000000003271432555450600165110ustar00rootroot00000000000000# OSX leaves these everywhere on SMB shares ._* # OSX trash .DS_Store # Eclipse files .classpath .project .settings/** # Files generated by JetBrains IDEs, e.g. IntelliJ IDEA .idea/ *.iml # Vscode files .vscode golang-k8s-klog-2.80.1/CONTRIBUTING.md000066400000000000000000000030501432555450600167460ustar00rootroot00000000000000# Contributing Guidelines Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ ## Getting Started We have full documentation on how to get started contributing here: - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests - [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing) - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers ## Mentorship - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers! ## Contact Information - [Slack](https://kubernetes.slack.com/messages/sig-architecture) - [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture) golang-k8s-klog-2.80.1/LICENSE000066400000000000000000000240411432555450600155250ustar00rootroot00000000000000Apache 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: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and 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 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-k8s-klog-2.80.1/OWNERS000066400000000000000000000003311432555450600154540ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - harshanarayana - pohly approvers: - dims - thockin - serathius emeritus_approvers: - brancz - justinsb - lavalamp - piosz - tallclair golang-k8s-klog-2.80.1/README.md000066400000000000000000000122241432555450600157770ustar00rootroot00000000000000klog ==== klog is a permanent fork of https://github.com/golang/glog. ## Why was klog created? The decision to create klog was one that wasn't made lightly, but it was necessary due to some drawbacks that are present in [glog](https://github.com/golang/glog). Ultimately, the fork was created due to glog not being under active development; this can be seen in the glog README: > The code in this repo [...] is not itself under development This makes us unable to solve many use cases without a fork. The factors that contributed to needing feature development are listed below: * `glog` [presents a lot "gotchas"](https://github.com/kubernetes/kubernetes/issues/61006) and introduces challenges in containerized environments, all of which aren't well documented. * `glog` doesn't provide an easy way to test logs, which detracts from the stability of software using it * A long term goal is to implement a logging interface that allows us to add context, change output format, etc. Historical context is available here: * https://github.com/kubernetes/kubernetes/issues/61006 * https://github.com/kubernetes/kubernetes/issues/70264 * https://groups.google.com/forum/#!msg/kubernetes-sig-architecture/wCWiWf3Juzs/hXRVBH90CgAJ * https://groups.google.com/forum/#!msg/kubernetes-dev/7vnijOMhLS0/1oRiNtigBgAJ ## Release versioning Semantic versioning is used in this repository. It contains several Go modules with different levels of stability: - `k8s.io/klog/v2` - stable API, `vX.Y.Z` tags - `examples` - no stable API, no tags, no intention to ever stabilize Exempt from the API stability guarantee are items (packages, functions, etc.) which are marked explicitly as `EXPERIMENTAL` in their docs comment. Those may still change in incompatible ways or get removed entirely. This can only be used for code that is used in tests to avoid situations where non-test code from two different Kubernetes dependencies depends on incompatible releases of klog because an experimental API was changed. ---- How to use klog =============== - Replace imports for `"github.com/golang/glog"` with `"k8s.io/klog/v2"` - Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags - You can now use `log_file` instead of `log_dir` for logging to a single file (See `examples/log_file/usage_log_file.go`) - If you want to redirect everything logged using klog somewhere else (say syslog!), you can use `klog.SetOutput()` method and supply a `io.Writer`. (See `examples/set_output/usage_set_output.go`) - For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md)) - See our documentation on [pkg.go.dev/k8s.io](https://pkg.go.dev/k8s.io/klog). **NOTE**: please use the newer go versions that support semantic import versioning in modules, ideally go 1.11.4 or greater. ### Coexisting with klog/v2 See [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2. ### Coexisting with glog This package can be used side by side with glog. [This example](examples/coexist_glog/coexist_glog.go) shows how to initialize and synchronize flags from the global `flag.CommandLine` FlagSet. In addition, the example makes use of stderr as combined output by setting `alsologtostderr` (or `logtostderr`) to `true`. ## Community, discussion, contribution, and support Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/). You can reach the maintainers of this project at: - [Slack](https://kubernetes.slack.com/messages/klog) - [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture) ### Code of conduct Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). ---- glog ==== Leveled execution logs for Go. This is an efficient pure Go implementation of leveled logs in the manner of the open source C++ package https://github.com/google/glog By binding methods to booleans it is possible to use the log package without paying the expense of evaluating the arguments to the log. Through the -vmodule flag, the package also provides fine-grained control over logging at the file level. The comment from glog.go introduces the ideas: Package glog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. It provides functions Info, Warning, Error, Fatal, plus formatting variants such as Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. Basic examples: glog.Info("Prepare to repel boarders") glog.Fatalf("Initialization failed: %s", err) See the documentation of the V function for an explanation of these examples: if glog.V(2) { glog.Info("Starting transaction...") } glog.V(2).Infoln("Processed", nItems, "elements") The repository contains an open source version of the log package used inside Google. The master copy of the source lives inside Google, not here. The code in this repo is for export only and is not itself under development. Feature requests will be ignored. Send bug reports to golang-nuts@googlegroups.com. golang-k8s-klog-2.80.1/RELEASE.md000066400000000000000000000007741432555450600161310ustar00rootroot00000000000000# Release Process The `klog` is released on an as-needed basis. The process is as follows: 1. An issue is proposing a new release with a changelog since the last release 1. All [OWNERS](OWNERS) must LGTM this release 1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION` 1. The release issue is closed 1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released` golang-k8s-klog-2.80.1/SECURITY.md000066400000000000000000000020551432555450600163120ustar00rootroot00000000000000# Security Policy ## Security Announcements Join the [kubernetes-security-announce] group for security and vulnerability announcements. You can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss]. ## Reporting a Vulnerability Instructions for reporting a vulnerability can be found on the [Kubernetes Security and Disclosure Information] page. ## Supported Versions Information about supported Kubernetes versions can be found on the [Kubernetes version and version skew support policy] page on the Kubernetes website. [kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce [kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50 [Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions [Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability golang-k8s-klog-2.80.1/SECURITY_CONTACTS000066400000000000000000000011111432555450600172010ustar00rootroot00000000000000# Defined below are the security contacts for this repo. # # They are the contact point for the Product Security Committee to reach out # to for triaging and handling of incoming issues. # # The below names agree to abide by the # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) # and will be removed and replaced if they violate that agreement. # # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # INSTRUCTIONS AT https://kubernetes.io/security/ dims thockin justinsb tallclair piosz brancz DirectXMan12 lavalamp golang-k8s-klog-2.80.1/code-of-conduct.md000066400000000000000000000002241432555450600200100ustar00rootroot00000000000000# Kubernetes Community Code of Conduct Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) golang-k8s-klog-2.80.1/contextual.go000066400000000000000000000142111432555450600172330ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 klog import ( "context" "github.com/go-logr/logr" ) // This file provides the implementation of // https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/1602-structured-logging // // SetLogger and ClearLogger were originally added to klog.go and got moved // here. Contextual logging adds a way to retrieve a Logger for direct logging // without the logging calls in klog.go. // // The global variables are expected to be modified only during sequential // parts of a program (init, serial tests) and therefore are not protected by // mutex locking. var ( // klogLogger is used as fallback for logging through the normal klog code // when no Logger is set. klogLogger logr.Logger = logr.New(&klogger{}) ) // SetLogger sets a Logger implementation that will be used as backing // implementation of the traditional klog log calls. klog will do its own // verbosity checks before calling logger.V().Info. logger.Error is always // called, regardless of the klog verbosity settings. // // If set, all log lines will be suppressed from the regular output, and // redirected to the logr implementation. // Use as: // // ... // klog.SetLogger(zapr.NewLogger(zapLog)) // // To remove a backing logr implemention, use ClearLogger. Setting an // empty logger with SetLogger(logr.Logger{}) does not work. // // Modifying the logger is not thread-safe and should be done while no other // goroutines invoke log calls, usually during program initialization. func SetLogger(logger logr.Logger) { SetLoggerWithOptions(logger) } // SetLoggerWithOptions is a more flexible version of SetLogger. Without // additional options, it behaves exactly like SetLogger. By passing // ContextualLogger(true) as option, it can be used to set a logger that then // will also get called directly by applications which retrieve it via // FromContext, Background, or TODO. // // Supporting direct calls is recommended because it avoids the overhead of // routing log entries through klogr into klog and then into the actual Logger // backend. func SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) { logging.logger = &logger logging.loggerOptions = loggerOptions{} for _, opt := range opts { opt(&logging.loggerOptions) } } // ContextualLogger determines whether the logger passed to // SetLoggerWithOptions may also get called directly. Such a logger cannot rely // on verbosity checking in klog. func ContextualLogger(enabled bool) LoggerOption { return func(o *loggerOptions) { o.contextualLogger = enabled } } // FlushLogger provides a callback for flushing data buffered by the logger. func FlushLogger(flush func()) LoggerOption { return func(o *loggerOptions) { o.flush = flush } } // LoggerOption implements the functional parameter paradigm for // SetLoggerWithOptions. type LoggerOption func(o *loggerOptions) type loggerOptions struct { contextualLogger bool flush func() } // ClearLogger removes a backing Logger implementation if one was set earlier // with SetLogger. // // Modifying the logger is not thread-safe and should be done while no other // goroutines invoke log calls, usually during program initialization. func ClearLogger() { logging.logger = nil logging.loggerOptions = loggerOptions{} } // EnableContextualLogging controls whether contextual logging is enabled. // By default it is enabled. When disabled, FromContext avoids looking up // the logger in the context and always returns the global logger. // LoggerWithValues, LoggerWithName, and NewContext become no-ops // and return their input logger respectively context. This may be useful // to avoid the additional overhead for contextual logging. // // This must be called during initialization before goroutines are started. func EnableContextualLogging(enabled bool) { logging.contextualLoggingEnabled = enabled } // FromContext retrieves a logger set by the caller or, if not set, // falls back to the program's global logger (a Logger instance or klog // itself). func FromContext(ctx context.Context) Logger { if logging.contextualLoggingEnabled { if logger, err := logr.FromContext(ctx); err == nil { return logger } } return Background() } // TODO can be used as a last resort by code that has no means of // receiving a logger from its caller. FromContext or an explicit logger // parameter should be used instead. func TODO() Logger { return Background() } // Background retrieves the fallback logger. It should not be called before // that logger was initialized by the program and not by code that should // better receive a logger via its parameters. TODO can be used as a temporary // solution for such code. func Background() Logger { if logging.loggerOptions.contextualLogger { // Is non-nil because logging.loggerOptions.contextualLogger is // only true if a logger was set. return *logging.logger } return klogLogger } // LoggerWithValues returns logger.WithValues(...kv) when // contextual logging is enabled, otherwise the logger. func LoggerWithValues(logger Logger, kv ...interface{}) Logger { if logging.contextualLoggingEnabled { return logger.WithValues(kv...) } return logger } // LoggerWithName returns logger.WithName(name) when contextual logging is // enabled, otherwise the logger. func LoggerWithName(logger Logger, name string) Logger { if logging.contextualLoggingEnabled { return logger.WithName(name) } return logger } // NewContext returns logr.NewContext(ctx, logger) when // contextual logging is enabled, otherwise ctx. func NewContext(ctx context.Context, logger Logger) context.Context { if logging.contextualLoggingEnabled { return logr.NewContext(ctx, logger) } return ctx } golang-k8s-klog-2.80.1/contextual_test.go000066400000000000000000000034711432555450600203000ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 klog_test import ( "fmt" "github.com/go-logr/logr" "k8s.io/klog/v2" ) func ExampleSetLogger() { defer klog.ClearLogger() // Logger is only used as backend, Background() returns klogr. klog.SetLogger(logr.Discard()) fmt.Printf("logger after SetLogger: %T\n", klog.Background().GetSink()) // Logger is only used as backend, Background() returns klogr. klog.SetLoggerWithOptions(logr.Discard(), klog.ContextualLogger(false)) fmt.Printf("logger after SetLoggerWithOptions with ContextualLogger(false): %T\n", klog.Background().GetSink()) // Logger is used as backend and directly. klog.SetLoggerWithOptions(logr.Discard(), klog.ContextualLogger(true)) fmt.Printf("logger after SetLoggerWithOptions with ContextualLogger(true): %T\n", klog.Background().GetSink()) // Output: // logger after SetLogger: *klog.klogger // logger after SetLoggerWithOptions with ContextualLogger(false): *klog.klogger // logger after SetLoggerWithOptions with ContextualLogger(true): logr.discardLogSink } func ExampleFlushLogger() { defer klog.ClearLogger() // This simple logger doesn't need flushing, but others might. klog.SetLoggerWithOptions(logr.Discard(), klog.FlushLogger(func() { fmt.Print("flushing...") })) klog.Flush() // Output: // flushing... } golang-k8s-klog-2.80.1/examples/000077500000000000000000000000001432555450600163355ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/benchmarks/000077500000000000000000000000001432555450600204525ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/benchmarks/benchmarks_test.go000066400000000000000000000116201432555450600241550ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 benchmarks import ( "flag" "fmt" "io" "testing" "github.com/go-logr/logr" "github.com/go-logr/zapr" "go.uber.org/zap" "go.uber.org/zap/zapcore" "k8s.io/klog/v2" ) const ( verbosityThreshold = 10 ) func init() { // klog gets configured so that it writes to a single output file that // will be set during tests with SetOutput. klog.InitFlags(nil) flag.Set("v", fmt.Sprintf("%d", verbosityThreshold)) flag.Set("log_file", "/dev/null") flag.Set("logtostderr", "false") flag.Set("alsologtostderr", "false") flag.Set("stderrthreshold", "10") } type testcase struct { name string generate func() interface{} } func BenchmarkOutput(b *testing.B) { // We'll run each benchmark for different output formatting. configs := map[string]struct { init, cleanup func() }{ "klog": { init: func() { klog.SetOutput(discard{}) }, }, "zapr": { init: func() { klog.SetLogger(newZaprLogger()) }, cleanup: func() { klog.ClearLogger() }, }, } // Each benchmark tests formatting of one key/value pair, with // different values. The order is relevant here. var tests []testcase for length := 0; length <= 100; length += 10 { arg := make([]interface{}, length) for i := 0; i < length; i++ { arg[i] = KMetadataMock{Name: "a", NS: "a"} } tests = append(tests, testcase{ name: fmt.Sprintf("objects/%d", length), generate: func() interface{} { return klog.KObjSlice(arg) }, }) } // Verbosity checks may influence the result. verbosity := map[string]func(value interface{}){ "no-verbosity-check": func(value interface{}) { klog.InfoS("test", "key", value) }, "pass-verbosity-check": func(value interface{}) { klog.V(verbosityThreshold).InfoS("test", "key", value) }, "fail-verbosity-check": func(value interface{}) { klog.V(verbosityThreshold+1).InfoS("test", "key", value) }, "non-standard-int-key-check": func(value interface{}) { klog.InfoS("test", 1, value) }, "non-standard-struct-key-check": func(value interface{}) { klog.InfoS("test", struct{ key string }{"test"}, value) }, "non-standard-map-key-check": func(value interface{}) { klog.InfoS("test", map[string]bool{"key": true}, value) }, "pass-verbosity-non-standard-int-key-check": func(value interface{}) { klog.V(verbosityThreshold).InfoS("test", 1, value) }, "pass-verbosity-non-standard-struct-key-check": func(value interface{}) { klog.V(verbosityThreshold).InfoS("test", struct{ key string }{"test"}, value) }, "pass-verbosity-non-standard-map-key-check": func(value interface{}) { klog.V(verbosityThreshold).InfoS("test", map[string]bool{"key": true}, value) }, "fail-verbosity-non-standard-int-key-check": func(value interface{}) { klog.V(verbosityThreshold+1).InfoS("test", 1, value) }, "fail-verbosity-non-standard-struct-key-check": func(value interface{}) { klog.V(verbosityThreshold+1).InfoS("test", struct{ key string }{"test"}, value) }, "fail-verbosity-non-standard-map-key-check": func(value interface{}) { klog.V(verbosityThreshold+1).InfoS("test", map[string]bool{"key": true}, value) }, } for name, config := range configs { b.Run(name, func(b *testing.B) { if config.cleanup != nil { defer config.cleanup() } config.init() for name, logCall := range verbosity { b.Run(name, func(b *testing.B) { for _, testcase := range tests { b.Run(testcase.name, func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { logCall(testcase.generate()) } }) } }) } }) } } func newZaprLogger() logr.Logger { encoderConfig := &zapcore.EncoderConfig{ MessageKey: "msg", CallerKey: "caller", NameKey: "logger", EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } encoder := zapcore.NewJSONEncoder(*encoderConfig) zapV := -zapcore.Level(verbosityThreshold) core := zapcore.NewCore(encoder, zapcore.AddSync(discard{}), zapV) l := zap.New(core, zap.WithCaller(true)) logger := zapr.NewLoggerWithOptions(l, zapr.LogInfoLevel("v"), zapr.ErrorKey("err")) return logger } type KMetadataMock struct { Name, NS string } func (m KMetadataMock) GetName() string { return m.Name } func (m KMetadataMock) GetNamespace() string { return m.NS } type discard struct{} var _ io.Writer = discard{} func (discard) Write(p []byte) (int, error) { return len(p), nil } golang-k8s-klog-2.80.1/examples/coexist_glog/000077500000000000000000000000001432555450600210235ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/coexist_glog/coexist_glog.go000066400000000000000000000010171432555450600240370ustar00rootroot00000000000000package main import ( "flag" "github.com/golang/glog" "k8s.io/klog/v2" ) func main() { flag.Set("alsologtostderr", "true") flag.Parse() klogFlags := flag.NewFlagSet("klog", flag.ExitOnError) klog.InitFlags(klogFlags) // Sync the glog and klog flags. flag.CommandLine.VisitAll(func(f1 *flag.Flag) { f2 := klogFlags.Lookup(f1.Name) if f2 != nil { value := f1.Value.String() f2.Value.Set(value) } }) glog.Info("hello from glog!") klog.Info("nice to meet you, I'm klog") glog.Flush() klog.Flush() } golang-k8s-klog-2.80.1/examples/coexist_klog_v1_and_v2/000077500000000000000000000000001432555450600226665ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/coexist_klog_v1_and_v2/coexist_klog_v1_and_v2.go000066400000000000000000000053171432555450600275540ustar00rootroot00000000000000package main import ( "flag" klogv1 "k8s.io/klog" klogv2 "k8s.io/klog/v2" ) // OutputCallDepth is the stack depth where we can find the origin of this call const OutputCallDepth = 6 // DefaultPrefixLength is the length of the log prefix that we have to strip out const DefaultPrefixLength = 53 // klogWriter is used in SetOutputBySeverity call below to redirect // any calls to klogv1 to end up in klogv2 type klogWriter struct{} func (kw klogWriter) Write(p []byte) (n int, err error) { if len(p) < DefaultPrefixLength { klogv2.InfoDepth(OutputCallDepth, string(p)) return len(p), nil } if p[0] == 'I' { klogv2.InfoDepth(OutputCallDepth, string(p[DefaultPrefixLength:])) } else if p[0] == 'W' { klogv2.WarningDepth(OutputCallDepth, string(p[DefaultPrefixLength:])) } else if p[0] == 'E' { klogv2.ErrorDepth(OutputCallDepth, string(p[DefaultPrefixLength:])) } else if p[0] == 'F' { klogv2.FatalDepth(OutputCallDepth, string(p[DefaultPrefixLength:])) } else { klogv2.InfoDepth(OutputCallDepth, string(p[DefaultPrefixLength:])) } return len(p), nil } func main() { // initialize klog/v2, can also bind to a local flagset if desired klogv2.InitFlags(nil) // In this example, we want to show you that all the lines logged // end up in the myfile.log. You do NOT need them in your application // as all these flags are set up from the command line typically flag.Set("logtostderr", "false") // By default klog logs to stderr, switch that off flag.Set("alsologtostderr", "false") // false is default, but this is informative flag.Set("stderrthreshold", "FATAL") // stderrthreshold defaults to ERROR, we don't want anything in stderr flag.Set("log_file", "myfile.log") // log to a file // parse klog/v2 flags flag.Parse() // make sure we flush before exiting defer klogv2.Flush() // BEGIN : hack to redirect klogv1 calls to klog v2 // Tell klog NOT to log into STDERR. Otherwise, we risk // certain kinds of API errors getting logged into a directory not // available in a `FROM scratch` Docker container, causing us to abort var klogv1Flags flag.FlagSet klogv1.InitFlags(&klogv1Flags) klogv1Flags.Set("logtostderr", "false") // By default klog v1 logs to stderr, switch that off klogv1Flags.Set("stderrthreshold", "FATAL") // stderrthreshold defaults to ERROR, use this if you // don't want anything in your stderr klogv1.SetOutputBySeverity("INFO", klogWriter{}) // tell klog v1 to use the writer // END : hack to redirect klogv1 calls to klog v2 // Now you can mix klogv1 and v2 in the same code base klogv2.Info("hello from klog (v2)!") klogv1.Info("hello from klog (v1)!") klogv1.Warning("beware from klog (v1)!") klogv1.Error("error from klog (v1)!") klogv2.Info("nice to meet you (v2)") } golang-k8s-klog-2.80.1/examples/coexist_klog_v1_and_v2/go.mod000066400000000000000000000001641432555450600237750ustar00rootroot00000000000000module k8s.io/klog/examples/coexist_klog_v1_and_v2 go 1.13 require ( k8s.io/klog v1.0.0 k8s.io/klog/v2 v2.0.0 ) golang-k8s-klog-2.80.1/examples/flushing/000077500000000000000000000000001432555450600201545ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/flushing/flushing_test.go000066400000000000000000000016321432555450600233630ustar00rootroot00000000000000package main import ( "flag" "testing" "go.uber.org/goleak" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) // By default klog writes to stderr. Setting logtostderr to false makes klog // write to a log file. flag.Set("logtostderr", "false") flag.Set("log_file", "myfile.log") flag.Parse() // Info writes the first log message. When the first log file is created, // a flushDaemon is started to frequently flush bytes to the file. klog.Info("nice to meet you") // klog won't ever stop this flushDaemon. To exit without leaking a goroutine, // the daemon can be stopped manually. klog.StopFlushDaemon() // After you stopped the flushDaemon, you can still manually flush. klog.Info("bye") klog.Flush() } func TestLeakingFlushDaemon(t *testing.T) { // goleak detects leaking goroutines. defer goleak.VerifyNone(t) // Without calling StopFlushDaemon in main, this test will fail. main() } golang-k8s-klog-2.80.1/examples/go.mod000066400000000000000000000004141432555450600174420ustar00rootroot00000000000000module example go 1.13 require ( github.com/go-logr/logr v1.2.2 github.com/go-logr/zapr v1.2.3 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b go.uber.org/goleak v1.1.12 go.uber.org/zap v1.19.0 k8s.io/klog/v2 v2.30.0 ) replace k8s.io/klog/v2 => ../ golang-k8s-klog-2.80.1/examples/go.sum000066400000000000000000000225541432555450600175000ustar00rootroot00000000000000github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.2 h1:5YNlIL6oZLydaV4dOFjL8YpgXF/tPeTbnpatnu3cq6o= github.com/go-logr/zapr v1.2.2/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= golang-k8s-klog-2.80.1/examples/klogr/000077500000000000000000000000001432555450600174535ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/klogr/main.go000066400000000000000000000010551432555450600207270ustar00rootroot00000000000000package main import ( "flag" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" ) type myError struct { str string } func (e myError) Error() string { return e.str } func main() { klog.InitFlags(nil) flag.Set("v", "3") flag.Parse() log := klogr.New().WithName("MyName").WithValues("user", "you") log.Info("hello", "val1", 1, "val2", map[string]int{"k": 1}) log.V(3).Info("nice to meet you") log.Error(nil, "uh oh", "trouble", true, "reasons", []float64{0.1, 0.11, 3.14}) log.Error(myError{"an error occurred"}, "goodbye", "code", -1) klog.Flush() } golang-k8s-klog-2.80.1/examples/log_file/000077500000000000000000000000001432555450600201155ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/log_file/usage_log_file.go000066400000000000000000000005021432555450600234050ustar00rootroot00000000000000package main import ( "flag" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) // By default klog writes to stderr. Setting logtostderr to false makes klog // write to a log file. flag.Set("logtostderr", "false") flag.Set("log_file", "myfile.log") flag.Parse() klog.Info("nice to meet you") klog.Flush() } golang-k8s-klog-2.80.1/examples/output_test/000077500000000000000000000000001432555450600207345ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/output_test/output_test.go000066400000000000000000000206551432555450600236720ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 output_test shows how to use k8s.io/klog/v2/test // and provides unit testing with dependencies that wouldn't // be acceptable for the main module. package output_test import ( "io" "testing" "github.com/go-logr/logr" "github.com/go-logr/zapr" "go.uber.org/zap" "go.uber.org/zap/zapcore" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" "k8s.io/klog/v2/test" "k8s.io/klog/v2/textlogger" ) func init() { test.InitKlog() } // TestKlogOutput tests klog output without a logger. func TestKlogOutput(t *testing.T) { test.Output(t, test.OutputConfig{}) } // TestTextloggerOutput tests the textlogger, directly and as backend. func TestTextloggerOutput(t *testing.T) { newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { config := textlogger.NewConfig( textlogger.Verbosity(v), textlogger.Output(out), ) if err := config.VModule().Set(vmodule); err != nil { panic(err) } return textlogger.NewLogger(config) } t.Run("direct", func(t *testing.T) { test.Output(t, test.OutputConfig{NewLogger: newLogger, SupportsVModule: true}) }) t.Run("klog-backend", func(t *testing.T) { test.Output(t, test.OutputConfig{NewLogger: newLogger, AsBackend: true}) }) } // TestZaprOutput tests the zapr, directly and as backend. func TestZaprOutput(t *testing.T) { newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { return newZaprLogger(out, v) } t.Run("direct", func(t *testing.T) { test.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: test.ZaprOutputMappingDirect()}) }) t.Run("klog-backend", func(t *testing.T) { test.Output(t, test.OutputConfig{NewLogger: newLogger, AsBackend: true, ExpectedOutputMapping: test.ZaprOutputMappingIndirect()}) }) } // TestKlogrOutput tests klogr output via klog. func TestKlogrOutput(t *testing.T) { test.Output(t, test.OutputConfig{ NewLogger: func(out io.Writer, v int, vmodule string) logr.Logger { return klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog)) }, }) } // TestKlogrStackText tests klogr.klogr -> klog -> text logger. func TestKlogrStackText(t *testing.T) { newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { // Backend: text output. config := textlogger.NewConfig( textlogger.Verbosity(v), textlogger.Output(out), ) if err := config.VModule().Set(vmodule); err != nil { panic(err) } klog.SetLogger(textlogger.NewLogger(config)) // Frontend: klogr. return klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog)) } test.Output(t, test.OutputConfig{NewLogger: newLogger, SupportsVModule: true}) } // TestKlogrStackKlogr tests klogr.klogr -> klog -> zapr. // // This exposes whether verbosity is passed through correctly // (https://github.com/kubernetes/klog/issues/294) because klogr logging // records that. func TestKlogrStackZapr(t *testing.T) { mapping := test.ZaprOutputMappingIndirect() // klogr doesn't warn about invalid KVs and just inserts // "(MISSING)". for key, value := range map[string]string{ `I output.go:] "odd arguments" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd arguments","v":0,"akey":"avalue","akey2":"(MISSING)"} `, `I output.go:] "both odd" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"both odd","v":0,"basekey1":"basevar1","basekey2":"(MISSING)","akey":"avalue","akey2":"(MISSING)"} `, `I output.go:] "integer keys" %!s(int=1)="value" %!s(int=2)="value2" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":1} {"caller":"test/output.go:","msg":"integer keys","v":0} `, `I output.go:] "struct keys" {name}="value" test="other value" key="val" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{}} {"caller":"test/output.go:","msg":"struct keys","v":0} `, `I output.go:] "map keys" map[test:%!s(bool=true)]="test" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{"test":true}} {"caller":"test/output.go:","msg":"map keys","v":0} `, } { mapping[key] = value } newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { // Backend: zapr as configured in k8s.io/component-base/logs/json. klog.SetLogger(newZaprLogger(out, v)) // Frontend: klogr. return klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog)) } test.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: mapping}) } // TestKlogrInternalStackText tests klog.klogr (the simplified version used for contextual logging) -> klog -> text logger. func TestKlogrInternalStackText(t *testing.T) { newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { // Backend: text output. config := textlogger.NewConfig( textlogger.Verbosity(v), textlogger.Output(out), ) if err := config.VModule().Set(vmodule); err != nil { panic(err) } klog.SetLogger(textlogger.NewLogger(config)) // Frontend: internal klogr. return klog.NewKlogr() } test.Output(t, test.OutputConfig{NewLogger: newLogger, SupportsVModule: true}) } // TestKlogrInternalStackKlogr tests klog.klogr (the simplified version used for contextual logging) -> klog -> zapr. // // This exposes whether verbosity is passed through correctly // (https://github.com/kubernetes/klog/issues/294) because klogr logging // records that. func TestKlogrInternalStackZapr(t *testing.T) { mapping := test.ZaprOutputMappingIndirect() // klogr doesn't warn about invalid KVs and just inserts // "(MISSING)". for key, value := range map[string]string{ `I output.go:] "odd arguments" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd arguments","v":0,"akey":"avalue","akey2":"(MISSING)"} `, `I output.go:] "both odd" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"both odd","v":0,"basekey1":"basevar1","basekey2":"(MISSING)","akey":"avalue","akey2":"(MISSING)"} `, `I output.go:] "integer keys" %!s(int=1)="value" %!s(int=2)="value2" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":1} {"caller":"test/output.go:","msg":"integer keys","v":0} `, `I output.go:] "struct keys" {name}="value" test="other value" key="val" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{}} {"caller":"test/output.go:","msg":"struct keys","v":0} `, `I output.go:] "map keys" map[test:%!s(bool=true)]="test" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{"test":true}} {"caller":"test/output.go:","msg":"map keys","v":0} `, } { mapping[key] = value } newLogger := func(out io.Writer, v int, vmodule string) logr.Logger { // Backend: zapr as configured in k8s.io/component-base/logs/json. klog.SetLogger(newZaprLogger(out, v)) // Frontend: internal klogr. return klog.NewKlogr() } test.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: mapping}) } func newZaprLogger(out io.Writer, v int) logr.Logger { encoderConfig := &zapcore.EncoderConfig{ MessageKey: "msg", CallerKey: "caller", NameKey: "logger", EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } encoder := zapcore.NewJSONEncoder(*encoderConfig) zapV := -zapcore.Level(v) core := zapcore.NewCore(encoder, zapcore.AddSync(out), zapV) l := zap.New(core, zap.WithCaller(true)) logger := zapr.NewLoggerWithOptions(l, zapr.LogInfoLevel("v"), zapr.ErrorKey("err")) return logger } golang-k8s-klog-2.80.1/examples/set_output/000077500000000000000000000000001432555450600205505ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/set_output/usage_set_output.go000066400000000000000000000005051432555450600244760ustar00rootroot00000000000000package main import ( "bytes" "flag" "fmt" "k8s.io/klog/v2" ) func main() { klog.InitFlags(nil) flag.Set("logtostderr", "false") flag.Set("alsologtostderr", "false") flag.Parse() buf := new(bytes.Buffer) klog.SetOutput(buf) klog.Info("nice to meet you") klog.Flush() fmt.Printf("LOGGED: %s", buf.String()) } golang-k8s-klog-2.80.1/examples/structured_logging/000077500000000000000000000000001432555450600222475ustar00rootroot00000000000000golang-k8s-klog-2.80.1/examples/structured_logging/structured_logging.go000066400000000000000000000034501432555450600265120ustar00rootroot00000000000000package main import ( "flag" "k8s.io/klog/v2" ) // MyStruct will be logged via %+v type MyStruct struct { Name string Data string internal int } // MyStringer will be logged as string, with String providing that string. type MyString MyStruct func (m MyString) String() string { return m.Name + ": " + m.Data } func main() { klog.InitFlags(nil) flag.Parse() someData := MyStruct{ Name: "hello", Data: "world", } longData := MyStruct{ Name: "long", Data: `Multiple lines with quite a bit of text.`, } logData := MyStruct{ Name: "log output from some program", Data: `I0000 12:00:00.000000 123456 main.go:42] Starting E0000 12:00:01.000000 123456 main.go:43] Failed for some reason `, } stringData := MyString(longData) klog.Infof("someData printed using InfoF: %v", someData) klog.Infof("longData printed using InfoF: %v", longData) klog.Infof(`stringData printed using InfoF, with the message across multiple lines: %v`, stringData) klog.Infof("logData printed using InfoF:\n%v", logData) klog.Info("=============================================") klog.InfoS("using InfoS", "someData", someData) klog.InfoS("using InfoS", "longData", longData) klog.InfoS(`using InfoS with the message across multiple lines`, "int", 1, "stringData", stringData, "str", "another value") klog.InfoS("using InfoS", "logData", logData) klog.InfoS("using InfoS", "boolean", true, "int", 1, "float", 0.1) // The Kubernetes recommendation is to start the message with uppercase // and not end with punctuation. See // https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md klog.InfoS("Did something", "item", "foobar") // Not recommended, but also works. klog.InfoS("This is a full sentence.", "item", "foobar") } golang-k8s-klog-2.80.1/exit.go000066400000000000000000000045211432555450600160210ustar00rootroot00000000000000// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ // // Copyright 2013 Google Inc. All Rights Reserved. // Copyright 2022 The Kubernetes 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 klog import ( "fmt" "os" "time" ) var ( // ExitFlushTimeout is the timeout that klog has traditionally used during // calls like Fatal or Exit when flushing log data right before exiting. // Applications that replace those calls and do not have some specific // requirements like "exit immediately" can use this value as parameter // for FlushAndExit. // // Can be set for testing purpose or to change the application's // default. ExitFlushTimeout = 10 * time.Second // OsExit is the function called by FlushAndExit to terminate the program. // // Can be set for testing purpose or to change the application's // default behavior. Note that the function should not simply return // because callers of functions like Fatal will not expect that. OsExit = os.Exit ) // FlushAndExit flushes log data for a certain amount of time and then calls // os.Exit. Combined with some logging call it provides a replacement for // traditional calls like Fatal or Exit. func FlushAndExit(flushTimeout time.Duration, exitCode int) { timeoutFlush(flushTimeout) OsExit(exitCode) } // timeoutFlush calls Flush and returns when it completes or after timeout // elapses, whichever happens first. This is needed because the hooks invoked // by Flush may deadlock when klog.Fatal is called from a hook that holds // a lock. Flushing also might take too long. func timeoutFlush(timeout time.Duration) { done := make(chan bool, 1) go func() { Flush() // calls logging.lockAndFlushAll() done <- true }() select { case <-done: case <-time.After(timeout): fmt.Fprintln(os.Stderr, "klog: Flush took longer than", timeout) } } golang-k8s-klog-2.80.1/exit_test.go000066400000000000000000000025611432555450600170620ustar00rootroot00000000000000// Copyright 2022 The Kubernetes 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 klog_test import ( "flag" "fmt" "os" "k8s.io/klog/v2" ) func ExampleFlushAndExit() { // Set up klog so that we can test it below. var fs flag.FlagSet klog.InitFlags(&fs) fs.Set("skip_headers", "true") defer flag.Set("skip_headers", "false") fs.Set("logtostderr", "false") defer fs.Set("logtostderr", "true") klog.SetOutput(os.Stdout) defer klog.SetOutput(nil) klog.OsExit = func(exitCode int) { fmt.Printf("os.Exit(%d)\n", exitCode) } // If we were to return or exit without flushing, this message would // get lost because it is buffered in memory by klog when writing to // files. Output to stderr is not buffered. klog.InfoS("exiting...") exitCode := 10 klog.FlushAndExit(klog.ExitFlushTimeout, exitCode) // Output: // "exiting..." // os.Exit(10) } golang-k8s-klog-2.80.1/go.mod000066400000000000000000000001071432555450600156230ustar00rootroot00000000000000module k8s.io/klog/v2 go 1.13 require github.com/go-logr/logr v1.2.0 golang-k8s-klog-2.80.1/go.sum000066400000000000000000000002451432555450600156530ustar00rootroot00000000000000github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= golang-k8s-klog-2.80.1/hack/000077500000000000000000000000001432555450600154255ustar00rootroot00000000000000golang-k8s-klog-2.80.1/hack/verify-apidiff.sh000077500000000000000000000062071432555450600206750ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2020 The Kubernetes 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 -b "${ref}" "${clone}" 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-k8s-klog-2.80.1/imports.go000066400000000000000000000020751432555450600165470ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 klog import ( "github.com/go-logr/logr" ) // The reason for providing these aliases is to allow code to work with logr // without directly importing it. // Logger in this package is exactly the same as logr.Logger. type Logger = logr.Logger // LogSink in this package is exactly the same as logr.LogSink. type LogSink = logr.LogSink // Runtimeinfo in this package is exactly the same as logr.RuntimeInfo. type RuntimeInfo = logr.RuntimeInfo var ( // New is an alias for logr.New. New = logr.New ) golang-k8s-klog-2.80.1/integration_tests/000077500000000000000000000000001432555450600202645ustar00rootroot00000000000000golang-k8s-klog-2.80.1/integration_tests/internal/000077500000000000000000000000001432555450600221005ustar00rootroot00000000000000golang-k8s-klog-2.80.1/integration_tests/internal/main.go000066400000000000000000000020051432555450600233500ustar00rootroot00000000000000/* This file is intended to be used as a standin for a klog'ed executable. It is called by the integration test via `go run` and with different klog flags to assert on klog behaviour, especially where klog logs its output when different combinations of the klog flags are at play. This file is not intended to be used outside of the integration tests and is not supposed to be a (good) example on how to use klog. */ package main import ( "flag" "fmt" "os" "k8s.io/klog/v2" ) func main() { infoLogLine := getEnvOrDie("KLOG_INFO_LOG") warningLogLine := getEnvOrDie("KLOG_WARNING_LOG") errorLogLine := getEnvOrDie("KLOG_ERROR_LOG") fatalLogLine := getEnvOrDie("KLOG_FATAL_LOG") klog.InitFlags(nil) flag.Parse() klog.Info(infoLogLine) klog.Warning(warningLogLine) klog.Error(errorLogLine) klog.Flush() klog.Fatal(fatalLogLine) } func getEnvOrDie(name string) string { val, ok := os.LookupEnv(name) if !ok { fmt.Fprintf(os.Stderr, name+" could not be found in environment") os.Exit(1) } return val } golang-k8s-klog-2.80.1/integration_tests/klog_test.go000066400000000000000000000266611432555450600226210ustar00rootroot00000000000000package integration_tests_test import ( "bytes" "fmt" "io" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "testing" ) const ( infoLog = "this is a info log line" warningLog = "this is a warning log line" errorLog = "this is a error log line" fatalLog = "this is a fatal log line" ) // res is a type alias to a slice of pointers to regular expressions. type res = []*regexp.Regexp var ( infoLogRE = regexp.MustCompile(regexp.QuoteMeta(infoLog)) warningLogRE = regexp.MustCompile(regexp.QuoteMeta(warningLog)) errorLogRE = regexp.MustCompile(regexp.QuoteMeta(errorLog)) fatalLogRE = regexp.MustCompile(regexp.QuoteMeta(fatalLog)) stackTraceRE = regexp.MustCompile(`\ngoroutine \d+ \[[^]]+\]:\n`) allLogREs = res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE, stackTraceRE} defaultExpectedInDirREs = map[int]res{ 0: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE, infoLogRE}, 1: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE}, 2: {stackTraceRE, fatalLogRE, errorLogRE}, 3: {stackTraceRE, fatalLogRE}, } expectedOneOutputInDirREs = map[int]res{ 0: {infoLogRE}, 1: {warningLogRE}, 2: {errorLogRE}, 3: {fatalLogRE}, } defaultNotExpectedInDirREs = map[int]res{ 0: {}, 1: {infoLogRE}, 2: {infoLogRE, warningLogRE}, 3: {infoLogRE, warningLogRE, errorLogRE}, } ) func TestDestinationsWithDifferentFlags(t *testing.T) { tests := map[string]struct { // logfile states if the flag -log_file should be set logfile bool // logdir states if the flag -log_dir should be set logdir bool // flags is for additional flags to pass to the klog'ed executable flags []string // expectedLogFile states if we generally expect the log file to exist. // If this is not set, we expect the file not to exist and will error if it // does. expectedLogFile bool // expectedLogDir states if we generally expect the log files in the log // dir to exist. // If this is not set, we expect the log files in the log dir not to exist and // will error if they do. expectedLogDir bool // expectedOnStderr is a list of REs we expect to find on stderr expectedOnStderr res // notExpectedOnStderr is a list of REs that we must not find on stderr notExpectedOnStderr res // expectedInFile is a list of REs we expect to find in the log file expectedInFile res // notExpectedInFile is a list of REs we must not find in the log file notExpectedInFile res // expectedInDir is a list of REs we expect to find in the log files in the // log dir, specified by log severity (0 = warning, 1 = info, ...) expectedInDir map[int]res // notExpectedInDir is a list of REs we must not find in the log files in // the log dir, specified by log severity (0 = warning, 1 = info, ...) notExpectedInDir map[int]res }{ "default flags": { // Everything, EXCEPT the trace on fatal, goes to stderr expectedOnStderr: res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE}, notExpectedOnStderr: res{stackTraceRE}, }, "everything disabled": { // Nothing, including the trace on fatal, is showing anywhere flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"}, notExpectedOnStderr: allLogREs, }, "everything disabled but low stderrthreshold": { // Everything above -stderrthreshold, including the trace on fatal, will // be logged to stderr, even if we set -logtostderr to false. flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1"}, expectedOnStderr: res{warningLogRE, errorLogRE, stackTraceRE}, notExpectedOnStderr: res{infoLogRE}, }, "with logtostderr only": { // Everything, EXCEPT the trace on fatal, goes to stderr flags: []string{"-logtostderr=true", "-alsologtostderr=false", "-stderrthreshold=1000"}, expectedOnStderr: res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE}, notExpectedOnStderr: res{stackTraceRE}, }, "with log file only": { // Everything, including the trace on fatal, goes to the single log file logfile: true, flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"}, expectedLogFile: true, notExpectedOnStderr: allLogREs, expectedInFile: allLogREs, }, "with log dir only": { // Everything, including the trace on fatal, goes to the log files in the log dir logdir: true, flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"}, expectedLogDir: true, notExpectedOnStderr: allLogREs, expectedInDir: defaultExpectedInDirREs, notExpectedInDir: defaultNotExpectedInDirREs, }, "with log dir only and one_output": { // Everything, including the trace on fatal, goes to the log files in the log dir logdir: true, flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000", "-one_output=true"}, expectedLogDir: true, notExpectedOnStderr: allLogREs, expectedInDir: expectedOneOutputInDirREs, notExpectedInDir: defaultNotExpectedInDirREs, }, "with log dir and logtostderr": { // Everything, EXCEPT the trace on fatal, goes to stderr. The -log_dir is // ignored, nothing goes to the log files in the log dir. logdir: true, flags: []string{"-logtostderr=true", "-alsologtostderr=false", "-stderrthreshold=1000"}, expectedOnStderr: res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE}, notExpectedOnStderr: res{stackTraceRE}, }, "with log file and log dir": { // Everything, including the trace on fatal, goes to the single log file. // The -log_dir is ignored, nothing goes to the log file in the log dir. logdir: true, logfile: true, flags: []string{"-logtostderr=false", "-alsologtostderr=false", "-stderrthreshold=1000"}, expectedLogFile: true, notExpectedOnStderr: allLogREs, expectedInFile: allLogREs, }, "with log file and alsologtostderr": { // Everything, including the trace on fatal, goes to the single log file // AND to stderr. flags: []string{"-alsologtostderr=true", "-logtostderr=false", "-stderrthreshold=1000"}, logfile: true, expectedLogFile: true, expectedOnStderr: allLogREs, expectedInFile: allLogREs, }, "with log dir and alsologtostderr": { // Everything, including the trace on fatal, goes to the log file in the // log dir AND to stderr. logdir: true, flags: []string{"-alsologtostderr=true", "-logtostderr=false", "-stderrthreshold=1000"}, expectedLogDir: true, expectedOnStderr: allLogREs, expectedInDir: defaultExpectedInDirREs, notExpectedInDir: defaultNotExpectedInDirREs, }, "with log dir, alsologtostderr and one_output": { // Everything, including the trace on fatal, goes to the log file in the // log dir AND to stderr. logdir: true, flags: []string{"-alsologtostderr=true", "-logtostderr=false", "-stderrthreshold=1000", "-one_output=true"}, expectedLogDir: true, expectedOnStderr: allLogREs, expectedInDir: expectedOneOutputInDirREs, notExpectedInDir: defaultNotExpectedInDirREs, }, } binaryFileExtention := "" if runtime.GOOS == "windows" { binaryFileExtention = ".exe" } for tcName, tc := range tests { tc := tc t.Run(tcName, func(t *testing.T) { t.Parallel() withTmpDir(t, func(logdir string) { // :: Setup flags := tc.flags stderr := &bytes.Buffer{} logfile := filepath.Join(logdir, "the_single_log_file") // /some/tmp/dir/the_single_log_file if tc.logfile { flags = append(flags, "-log_file="+logfile) } if tc.logdir { flags = append(flags, "-log_dir="+logdir) } // :: Execute klogRun(t, flags, stderr) // :: Assert // check stderr checkForLogs(t, tc.expectedOnStderr, tc.notExpectedOnStderr, stderr.String(), "stderr") // check log_file if tc.expectedLogFile { content := getFileContent(t, logfile) checkForLogs(t, tc.expectedInFile, tc.notExpectedInFile, content, "logfile") } else { assertFileIsAbsent(t, logfile) } // check files in log_dir for level, levelName := range logFileLevels { binaryName := "main" + binaryFileExtention logfile, err := getLogFilePath(logdir, binaryName, levelName) if tc.expectedLogDir { if err != nil { t.Errorf("Unable to find log file: %v", err) } content := getFileContent(t, logfile) checkForLogs(t, tc.expectedInDir[level], tc.notExpectedInDir[level], content, "logfile["+logfile+"]") } else { if err == nil { t.Errorf("Unexpectedly found log file %s", logfile) } } } }) }) } } const klogExampleGoFile = "./internal/main.go" // klogRun spawns a simple executable that uses klog, to later inspect its // stderr and potentially created log files func klogRun(t *testing.T, flags []string, stderr io.Writer) { callFlags := []string{"run", klogExampleGoFile} callFlags = append(callFlags, flags...) cmd := exec.Command("go", callFlags...) cmd.Stderr = stderr cmd.Env = append(os.Environ(), "KLOG_INFO_LOG="+infoLog, "KLOG_WARNING_LOG="+warningLog, "KLOG_ERROR_LOG="+errorLog, "KLOG_FATAL_LOG="+fatalLog, ) err := cmd.Run() if _, ok := err.(*exec.ExitError); !ok { t.Fatalf("Run failed: %v", err) } } var logFileLevels = map[int]string{ 0: "INFO", 1: "WARNING", 2: "ERROR", 3: "FATAL", } func getFileContent(t *testing.T, filePath string) string { content, err := ioutil.ReadFile(filePath) if err != nil { t.Errorf("Could not read file '%s': %v", filePath, err) } return string(content) } func assertFileIsAbsent(t *testing.T, filePath string) { if _, err := os.Stat(filePath); !os.IsNotExist(err) { t.Errorf("Expected file '%s' not to exist", filePath) } } func checkForLogs(t *testing.T, expected, disallowed res, content, name string) { for _, re := range expected { checkExpected(t, true, name, content, re) } for _, re := range disallowed { checkExpected(t, false, name, content, re) } } func checkExpected(t *testing.T, expected bool, where string, haystack string, needle *regexp.Regexp) { found := needle.MatchString(haystack) if expected && !found { t.Errorf("Expected to find '%s' in %s", needle, where) } if !expected && found { t.Errorf("Expected not to find '%s' in %s", needle, where) } } func withTmpDir(t *testing.T, f func(string)) { tmpDir, err := ioutil.TempDir("", "klog_e2e_") if err != nil { t.Fatalf("Could not create temp directory: %v", err) } defer func() { if err := os.RemoveAll(tmpDir); err != nil { t.Fatalf("Could not remove temp directory '%s': %v", tmpDir, err) } }() f(tmpDir) } // getLogFileFromDir returns the path of either the symbolic link to the logfile, or the the logfile itself. This must // be done as the creation of a symlink is not guaranteed on any platform. On Windows, only users with administration // privileges can create a symlink. func getLogFilePath(dir, binaryName, levelName string) (string, error) { symlink := filepath.Join(dir, binaryName+"."+levelName) if _, err := os.Stat(symlink); err == nil { return symlink, nil } files, err := ioutil.ReadDir(dir) if err != nil { return "", fmt.Errorf("could not read directory %s: %v", dir, err) } var foundFile string for _, file := range files { if strings.HasPrefix(file.Name(), binaryName) && strings.Contains(file.Name(), levelName) { if foundFile != "" { return "", fmt.Errorf("found multiple matching files") } foundFile = file.Name() } } if foundFile != "" { return filepath.Join(dir, foundFile), nil } return "", fmt.Errorf("file missing from directory") } golang-k8s-klog-2.80.1/internal/000077500000000000000000000000001432555450600163335ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/buffer/000077500000000000000000000000001432555450600176045ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/buffer/buffer.go000066400000000000000000000102121432555450600214000ustar00rootroot00000000000000// Copyright 2013 Google Inc. All Rights Reserved. // Copyright 2022 The Kubernetes 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 buffer provides a cache for byte.Buffer instances that can be reused // to avoid frequent allocation and deallocation. It also has utility code // for log header formatting that use these buffers. package buffer import ( "bytes" "os" "sync" "time" "k8s.io/klog/v2/internal/severity" ) var ( // Pid is inserted into log headers. Can be overridden for tests. Pid = os.Getpid() ) // Buffer holds a single byte.Buffer for reuse. The zero value is ready for // use. It also provides some helper methods for output formatting. type Buffer struct { bytes.Buffer Tmp [64]byte // temporary byte array for creating headers. next *Buffer } // Buffers manages the reuse of individual buffer instances. It is thread-safe. type Buffers struct { // mu protects the free list. It is separate from the main mutex // so buffers can be grabbed and printed to without holding the main lock, // for better parallelization. mu sync.Mutex // freeList is a list of byte buffers, maintained under mu. freeList *Buffer } // GetBuffer returns a new, ready-to-use buffer. func (bl *Buffers) GetBuffer() *Buffer { bl.mu.Lock() b := bl.freeList if b != nil { bl.freeList = b.next } bl.mu.Unlock() if b == nil { b = new(Buffer) } else { b.next = nil b.Reset() } return b } // PutBuffer returns a buffer to the free list. func (bl *Buffers) PutBuffer(b *Buffer) { if b.Len() >= 256 { // Let big buffers die a natural death. return } bl.mu.Lock() b.next = bl.freeList bl.freeList = b bl.mu.Unlock() } // Some custom tiny helper functions to print the log header efficiently. const digits = "0123456789" // twoDigits formats a zero-prefixed two-digit integer at buf.Tmp[i]. func (buf *Buffer) twoDigits(i, d int) { buf.Tmp[i+1] = digits[d%10] d /= 10 buf.Tmp[i] = digits[d%10] } // nDigits formats an n-digit integer at buf.Tmp[i], // padding with pad on the left. // It assumes d >= 0. func (buf *Buffer) nDigits(n, i, d int, pad byte) { j := n - 1 for ; j >= 0 && d > 0; j-- { buf.Tmp[i+j] = digits[d%10] d /= 10 } for ; j >= 0; j-- { buf.Tmp[i+j] = pad } } // someDigits formats a zero-prefixed variable-width integer at buf.Tmp[i]. func (buf *Buffer) someDigits(i, d int) int { // Print into the top, then copy down. We know there's space for at least // a 10-digit number. j := len(buf.Tmp) for { j-- buf.Tmp[j] = digits[d%10] d /= 10 if d == 0 { break } } return copy(buf.Tmp[i:], buf.Tmp[j:]) } // FormatHeader formats a log header using the provided file name and line number. func (buf *Buffer) FormatHeader(s severity.Severity, file string, line int, now time.Time) { if line < 0 { line = 0 // not a real line number, but acceptable to someDigits } if s > severity.FatalLog { s = severity.InfoLog // for safety. } // Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand. // It's worth about 3X. Fprintf is hard. _, month, day := now.Date() hour, minute, second := now.Clock() // Lmmdd hh:mm:ss.uuuuuu threadid file:line] buf.Tmp[0] = severity.Char[s] buf.twoDigits(1, int(month)) buf.twoDigits(3, day) buf.Tmp[5] = ' ' buf.twoDigits(6, hour) buf.Tmp[8] = ':' buf.twoDigits(9, minute) buf.Tmp[11] = ':' buf.twoDigits(12, second) buf.Tmp[14] = '.' buf.nDigits(6, 15, now.Nanosecond()/1000, '0') buf.Tmp[21] = ' ' buf.nDigits(7, 22, Pid, ' ') // TODO: should be TID buf.Tmp[29] = ' ' buf.Write(buf.Tmp[:30]) buf.WriteString(file) buf.Tmp[0] = ':' n := buf.someDigits(1, line) buf.Tmp[n+1] = ']' buf.Tmp[n+2] = ' ' buf.Write(buf.Tmp[:n+3]) } golang-k8s-klog-2.80.1/internal/clock/000077500000000000000000000000001432555450600174265ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/clock/README.md000066400000000000000000000003631432555450600207070ustar00rootroot00000000000000# Clock This package provides an interface for time-based operations. It allows mocking time for testing. This is a copy of k8s.io/utils/clock. We have to copy it to avoid a circular dependency (k8s.io/klog -> k8s.io/utils -> k8s.io/klog). golang-k8s-klog-2.80.1/internal/clock/clock.go000066400000000000000000000115471432555450600210600ustar00rootroot00000000000000/* Copyright 2014 The Kubernetes 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 clock import "time" // PassiveClock allows for injecting fake or real clocks into code // that needs to read the current time but does not support scheduling // activity in the future. type PassiveClock interface { Now() time.Time Since(time.Time) time.Duration } // Clock allows for injecting fake or real clocks into code that // needs to do arbitrary things based on time. type Clock interface { PassiveClock // After returns the channel of a new Timer. // This method does not allow to free/GC the backing timer before it fires. Use // NewTimer instead. After(d time.Duration) <-chan time.Time // NewTimer returns a new Timer. NewTimer(d time.Duration) Timer // Sleep sleeps for the provided duration d. // Consider making the sleep interruptible by using 'select' on a context channel and a timer channel. Sleep(d time.Duration) // Tick returns the channel of a new Ticker. // This method does not allow to free/GC the backing ticker. Use // NewTicker from WithTicker instead. Tick(d time.Duration) <-chan time.Time } // WithTicker allows for injecting fake or real clocks into code that // needs to do arbitrary things based on time. type WithTicker interface { Clock // NewTicker returns a new Ticker. NewTicker(time.Duration) Ticker } // WithDelayedExecution allows for injecting fake or real clocks into // code that needs to make use of AfterFunc functionality. type WithDelayedExecution interface { Clock // AfterFunc executes f in its own goroutine after waiting // for d duration and returns a Timer whose channel can be // closed by calling Stop() on the Timer. AfterFunc(d time.Duration, f func()) Timer } // WithTickerAndDelayedExecution allows for injecting fake or real clocks // into code that needs Ticker and AfterFunc functionality type WithTickerAndDelayedExecution interface { WithTicker // AfterFunc executes f in its own goroutine after waiting // for d duration and returns a Timer whose channel can be // closed by calling Stop() on the Timer. AfterFunc(d time.Duration, f func()) Timer } // Ticker defines the Ticker interface. type Ticker interface { C() <-chan time.Time Stop() } var _ = WithTicker(RealClock{}) // RealClock really calls time.Now() type RealClock struct{} // Now returns the current time. func (RealClock) Now() time.Time { return time.Now() } // Since returns time since the specified timestamp. func (RealClock) Since(ts time.Time) time.Duration { return time.Since(ts) } // After is the same as time.After(d). // This method does not allow to free/GC the backing timer before it fires. Use // NewTimer instead. func (RealClock) After(d time.Duration) <-chan time.Time { return time.After(d) } // NewTimer is the same as time.NewTimer(d) func (RealClock) NewTimer(d time.Duration) Timer { return &realTimer{ timer: time.NewTimer(d), } } // AfterFunc is the same as time.AfterFunc(d, f). func (RealClock) AfterFunc(d time.Duration, f func()) Timer { return &realTimer{ timer: time.AfterFunc(d, f), } } // Tick is the same as time.Tick(d) // This method does not allow to free/GC the backing ticker. Use // NewTicker instead. func (RealClock) Tick(d time.Duration) <-chan time.Time { return time.Tick(d) } // NewTicker returns a new Ticker. func (RealClock) NewTicker(d time.Duration) Ticker { return &realTicker{ ticker: time.NewTicker(d), } } // Sleep is the same as time.Sleep(d) // Consider making the sleep interruptible by using 'select' on a context channel and a timer channel. func (RealClock) Sleep(d time.Duration) { time.Sleep(d) } // Timer allows for injecting fake or real timers into code that // needs to do arbitrary things based on time. type Timer interface { C() <-chan time.Time Stop() bool Reset(d time.Duration) bool } var _ = Timer(&realTimer{}) // realTimer is backed by an actual time.Timer. type realTimer struct { timer *time.Timer } // C returns the underlying timer's channel. func (r *realTimer) C() <-chan time.Time { return r.timer.C } // Stop calls Stop() on the underlying timer. func (r *realTimer) Stop() bool { return r.timer.Stop() } // Reset calls Reset() on the underlying timer. func (r *realTimer) Reset(d time.Duration) bool { return r.timer.Reset(d) } type realTicker struct { ticker *time.Ticker } func (r *realTicker) C() <-chan time.Time { return r.ticker.C } func (r *realTicker) Stop() { r.ticker.Stop() } golang-k8s-klog-2.80.1/internal/clock/testing/000077500000000000000000000000001432555450600211035ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/clock/testing/fake_clock.go000066400000000000000000000214041432555450600235140ustar00rootroot00000000000000/* Copyright 2014 The Kubernetes 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 ( "sync" "time" "k8s.io/klog/v2/internal/clock" ) var ( _ = clock.PassiveClock(&FakePassiveClock{}) _ = clock.WithTicker(&FakeClock{}) _ = clock.Clock(&IntervalClock{}) ) // FakePassiveClock implements PassiveClock, but returns an arbitrary time. type FakePassiveClock struct { lock sync.RWMutex time time.Time } // FakeClock implements clock.Clock, but returns an arbitrary time. type FakeClock struct { FakePassiveClock // waiters are waiting for the fake time to pass their specified time waiters []*fakeClockWaiter } type fakeClockWaiter struct { targetTime time.Time stepInterval time.Duration skipIfBlocked bool destChan chan time.Time fired bool afterFunc func() } // NewFakePassiveClock returns a new FakePassiveClock. func NewFakePassiveClock(t time.Time) *FakePassiveClock { return &FakePassiveClock{ time: t, } } // NewFakeClock constructs a fake clock set to the provided time. func NewFakeClock(t time.Time) *FakeClock { return &FakeClock{ FakePassiveClock: *NewFakePassiveClock(t), } } // Now returns f's time. func (f *FakePassiveClock) Now() time.Time { f.lock.RLock() defer f.lock.RUnlock() return f.time } // Since returns time since the time in f. func (f *FakePassiveClock) Since(ts time.Time) time.Duration { f.lock.RLock() defer f.lock.RUnlock() return f.time.Sub(ts) } // SetTime sets the time on the FakePassiveClock. func (f *FakePassiveClock) SetTime(t time.Time) { f.lock.Lock() defer f.lock.Unlock() f.time = t } // After is the fake version of time.After(d). func (f *FakeClock) After(d time.Duration) <-chan time.Time { f.lock.Lock() defer f.lock.Unlock() stopTime := f.time.Add(d) ch := make(chan time.Time, 1) // Don't block! f.waiters = append(f.waiters, &fakeClockWaiter{ targetTime: stopTime, destChan: ch, }) return ch } // NewTimer constructs a fake timer, akin to time.NewTimer(d). func (f *FakeClock) NewTimer(d time.Duration) clock.Timer { f.lock.Lock() defer f.lock.Unlock() stopTime := f.time.Add(d) ch := make(chan time.Time, 1) // Don't block! timer := &fakeTimer{ fakeClock: f, waiter: fakeClockWaiter{ targetTime: stopTime, destChan: ch, }, } f.waiters = append(f.waiters, &timer.waiter) return timer } // AfterFunc is the Fake version of time.AfterFunc(d, cb). func (f *FakeClock) AfterFunc(d time.Duration, cb func()) clock.Timer { f.lock.Lock() defer f.lock.Unlock() stopTime := f.time.Add(d) ch := make(chan time.Time, 1) // Don't block! timer := &fakeTimer{ fakeClock: f, waiter: fakeClockWaiter{ targetTime: stopTime, destChan: ch, afterFunc: cb, }, } f.waiters = append(f.waiters, &timer.waiter) return timer } // Tick constructs a fake ticker, akin to time.Tick func (f *FakeClock) Tick(d time.Duration) <-chan time.Time { if d <= 0 { return nil } f.lock.Lock() defer f.lock.Unlock() tickTime := f.time.Add(d) ch := make(chan time.Time, 1) // hold one tick f.waiters = append(f.waiters, &fakeClockWaiter{ targetTime: tickTime, stepInterval: d, skipIfBlocked: true, destChan: ch, }) return ch } // NewTicker returns a new Ticker. func (f *FakeClock) NewTicker(d time.Duration) clock.Ticker { f.lock.Lock() defer f.lock.Unlock() tickTime := f.time.Add(d) ch := make(chan time.Time, 1) // hold one tick f.waiters = append(f.waiters, &fakeClockWaiter{ targetTime: tickTime, stepInterval: d, skipIfBlocked: true, destChan: ch, }) return &fakeTicker{ c: ch, } } // Step moves the clock by Duration and notifies anyone that's called After, // Tick, or NewTimer. func (f *FakeClock) Step(d time.Duration) { f.lock.Lock() defer f.lock.Unlock() f.setTimeLocked(f.time.Add(d)) } // SetTime sets the time. func (f *FakeClock) SetTime(t time.Time) { f.lock.Lock() defer f.lock.Unlock() f.setTimeLocked(t) } // Actually changes the time and checks any waiters. f must be write-locked. func (f *FakeClock) setTimeLocked(t time.Time) { f.time = t newWaiters := make([]*fakeClockWaiter, 0, len(f.waiters)) for i := range f.waiters { w := f.waiters[i] if !w.targetTime.After(t) { if w.skipIfBlocked { select { case w.destChan <- t: w.fired = true default: } } else { w.destChan <- t w.fired = true } if w.afterFunc != nil { w.afterFunc() } if w.stepInterval > 0 { for !w.targetTime.After(t) { w.targetTime = w.targetTime.Add(w.stepInterval) } newWaiters = append(newWaiters, w) } } else { newWaiters = append(newWaiters, f.waiters[i]) } } f.waiters = newWaiters } // HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can // write race-free tests). func (f *FakeClock) HasWaiters() bool { f.lock.RLock() defer f.lock.RUnlock() return len(f.waiters) > 0 } // Sleep is akin to time.Sleep func (f *FakeClock) Sleep(d time.Duration) { f.Step(d) } // IntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration. // IntervalClock technically implements the other methods of clock.Clock, but each implementation is just a panic. // // Deprecated: See SimpleIntervalClock for an alternative that only has the methods of PassiveClock. type IntervalClock struct { Time time.Time Duration time.Duration } // Now returns i's time. func (i *IntervalClock) Now() time.Time { i.Time = i.Time.Add(i.Duration) return i.Time } // Since returns time since the time in i. func (i *IntervalClock) Since(ts time.Time) time.Duration { return i.Time.Sub(ts) } // After is unimplemented, will panic. // TODO: make interval clock use FakeClock so this can be implemented. func (*IntervalClock) After(d time.Duration) <-chan time.Time { panic("IntervalClock doesn't implement After") } // NewTimer is unimplemented, will panic. // TODO: make interval clock use FakeClock so this can be implemented. func (*IntervalClock) NewTimer(d time.Duration) clock.Timer { panic("IntervalClock doesn't implement NewTimer") } // AfterFunc is unimplemented, will panic. // TODO: make interval clock use FakeClock so this can be implemented. func (*IntervalClock) AfterFunc(d time.Duration, f func()) clock.Timer { panic("IntervalClock doesn't implement AfterFunc") } // Tick is unimplemented, will panic. // TODO: make interval clock use FakeClock so this can be implemented. func (*IntervalClock) Tick(d time.Duration) <-chan time.Time { panic("IntervalClock doesn't implement Tick") } // NewTicker has no implementation yet and is omitted. // TODO: make interval clock use FakeClock so this can be implemented. func (*IntervalClock) NewTicker(d time.Duration) clock.Ticker { panic("IntervalClock doesn't implement NewTicker") } // Sleep is unimplemented, will panic. func (*IntervalClock) Sleep(d time.Duration) { panic("IntervalClock doesn't implement Sleep") } var _ = clock.Timer(&fakeTimer{}) // fakeTimer implements clock.Timer based on a FakeClock. type fakeTimer struct { fakeClock *FakeClock waiter fakeClockWaiter } // C returns the channel that notifies when this timer has fired. func (f *fakeTimer) C() <-chan time.Time { return f.waiter.destChan } // Stop stops the timer and returns true if the timer has not yet fired, or false otherwise. func (f *fakeTimer) Stop() bool { f.fakeClock.lock.Lock() defer f.fakeClock.lock.Unlock() newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters)) for i := range f.fakeClock.waiters { w := f.fakeClock.waiters[i] if w != &f.waiter { newWaiters = append(newWaiters, w) } } f.fakeClock.waiters = newWaiters return !f.waiter.fired } // Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet // fired, or false otherwise. func (f *fakeTimer) Reset(d time.Duration) bool { f.fakeClock.lock.Lock() defer f.fakeClock.lock.Unlock() active := !f.waiter.fired f.waiter.fired = false f.waiter.targetTime = f.fakeClock.time.Add(d) var isWaiting bool for i := range f.fakeClock.waiters { w := f.fakeClock.waiters[i] if w == &f.waiter { isWaiting = true break } } if !isWaiting { f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter) } return active } type fakeTicker struct { c <-chan time.Time } func (t *fakeTicker) C() <-chan time.Time { return t.c } func (t *fakeTicker) Stop() { } golang-k8s-klog-2.80.1/internal/clock/testing/simple_interval_clock.go000066400000000000000000000022161432555450600260030ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 ( "time" "k8s.io/klog/v2/internal/clock" ) var ( _ = clock.PassiveClock(&SimpleIntervalClock{}) ) // SimpleIntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration type SimpleIntervalClock struct { Time time.Time Duration time.Duration } // Now returns i's time. func (i *SimpleIntervalClock) Now() time.Time { i.Time = i.Time.Add(i.Duration) return i.Time } // Since returns time since the time in i. func (i *SimpleIntervalClock) Since(ts time.Time) time.Duration { return i.Time.Sub(ts) } golang-k8s-klog-2.80.1/internal/dbg/000077500000000000000000000000001432555450600170675ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/dbg/dbg.go000066400000000000000000000023671432555450600201620ustar00rootroot00000000000000// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ // // Copyright 2013 Google Inc. All Rights Reserved. // // 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 dbg provides some helper code for call traces. package dbg import ( "runtime" ) // Stacks is a wrapper for runtime.Stack that attempts to recover the data for // all goroutines or the calling one. func Stacks(all bool) []byte { // We don't know how big the traces are, so grow a few times if they don't fit. Start large, though. n := 10000 if all { n = 100000 } var trace []byte for i := 0; i < 5; i++ { trace = make([]byte, n) nbytes := runtime.Stack(trace, all) if nbytes < len(trace) { return trace[:nbytes] } n *= 2 } return trace } golang-k8s-klog-2.80.1/internal/serialize/000077500000000000000000000000001432555450600203225ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/serialize/keyvalues.go000066400000000000000000000172211432555450600226640ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 serialize import ( "bytes" "fmt" "strconv" "github.com/go-logr/logr" ) // WithValues implements LogSink.WithValues. The old key/value pairs are // assumed to be well-formed, the new ones are checked and padded if // necessary. It returns a new slice. func WithValues(oldKV, newKV []interface{}) []interface{} { if len(newKV) == 0 { return oldKV } newLen := len(oldKV) + len(newKV) hasMissingValue := newLen%2 != 0 if hasMissingValue { newLen++ } // The new LogSink must have its own slice. kv := make([]interface{}, 0, newLen) kv = append(kv, oldKV...) kv = append(kv, newKV...) if hasMissingValue { kv = append(kv, missingValue) } return kv } // MergeKVs deduplicates elements provided in two key/value slices. // // Keys in each slice are expected to be unique, so duplicates can only occur // when the first and second slice contain the same key. When that happens, the // key/value pair from the second slice is used. The first slice must be well-formed // (= even key/value pairs). The second one may have a missing value, in which // case the special "missing value" is added to the result. func MergeKVs(first, second []interface{}) []interface{} { maxLength := len(first) + (len(second)+1)/2*2 if maxLength == 0 { // Nothing to do at all. return nil } if len(first) == 0 && len(second)%2 == 0 { // Nothing to be overridden, second slice is well-formed // and can be used directly. return second } // Determine which keys are in the second slice so that we can skip // them when iterating over the first one. The code intentionally // favors performance over completeness: we assume that keys are string // constants and thus compare equal when the string values are equal. A // string constant being overridden by, for example, a fmt.Stringer is // not handled. overrides := map[interface{}]bool{} for i := 0; i < len(second); i += 2 { overrides[second[i]] = true } merged := make([]interface{}, 0, maxLength) for i := 0; i+1 < len(first); i += 2 { key := first[i] if overrides[key] { continue } merged = append(merged, key, first[i+1]) } merged = append(merged, second...) if len(merged)%2 != 0 { merged = append(merged, missingValue) } return merged } const missingValue = "(MISSING)" // KVListFormat serializes all key/value pairs into the provided buffer. // A space gets inserted before the first pair and between each pair. func KVListFormat(b *bytes.Buffer, keysAndValues ...interface{}) { for i := 0; i < len(keysAndValues); i += 2 { var v interface{} k := keysAndValues[i] if i+1 < len(keysAndValues) { v = keysAndValues[i+1] } else { v = missingValue } b.WriteByte(' ') // Keys are assumed to be well-formed according to // https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments // for the sake of performance. Keys with spaces, // special characters, etc. will break parsing. if sK, ok := k.(string); ok { // Avoid one allocation when the key is a string, which // normally it should be. b.WriteString(sK) } else { b.WriteString(fmt.Sprintf("%s", k)) } // The type checks are sorted so that more frequently used ones // come first because that is then faster in the common // cases. In Kubernetes, ObjectRef (a Stringer) is more common // than plain strings // (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235). switch v := v.(type) { case fmt.Stringer: writeStringValue(b, true, StringerToString(v)) case string: writeStringValue(b, true, v) case error: writeStringValue(b, true, ErrorToString(v)) case logr.Marshaler: value := MarshalerToValue(v) // A marshaler that returns a string is useful for // delayed formatting of complex values. We treat this // case like a normal string. This is useful for // multi-line support. // // We could do this by recursively formatting a value, // but that comes with the risk of infinite recursion // if a marshaler returns itself. Instead we call it // only once and rely on it returning the intended // value directly. switch value := value.(type) { case string: writeStringValue(b, true, value) default: writeStringValue(b, false, fmt.Sprintf("%+v", value)) } case []byte: // In https://github.com/kubernetes/klog/pull/237 it was decided // to format byte slices with "%+q". The advantages of that are: // - readable output if the bytes happen to be printable // - non-printable bytes get represented as unicode escape // sequences (\uxxxx) // // The downsides are that we cannot use the faster // strconv.Quote here and that multi-line output is not // supported. If developers know that a byte array is // printable and they want multi-line output, they can // convert the value to string before logging it. b.WriteByte('=') b.WriteString(fmt.Sprintf("%+q", v)) default: writeStringValue(b, false, fmt.Sprintf("%+v", v)) } } } // StringerToString converts a Stringer to a string, // handling panics if they occur. func StringerToString(s fmt.Stringer) (ret string) { defer func() { if err := recover(); err != nil { ret = fmt.Sprintf("", err) } }() ret = s.String() return } // MarshalerToValue invokes a marshaler and catches // panics. func MarshalerToValue(m logr.Marshaler) (ret interface{}) { defer func() { if err := recover(); err != nil { ret = fmt.Sprintf("", err) } }() ret = m.MarshalLog() return } // ErrorToString converts an error to a string, // handling panics if they occur. func ErrorToString(err error) (ret string) { defer func() { if err := recover(); err != nil { ret = fmt.Sprintf("", err) } }() ret = err.Error() return } func writeStringValue(b *bytes.Buffer, quote bool, v string) { data := []byte(v) index := bytes.IndexByte(data, '\n') if index == -1 { b.WriteByte('=') if quote { // Simple string, quote quotation marks and non-printable characters. b.WriteString(strconv.Quote(v)) return } // Non-string with no line breaks. b.WriteString(v) return } // Complex multi-line string, show as-is with indention like this: // I... "hello world" key=< // line 1 // line 2 // > // // Tabs indent the lines of the value while the end of string delimiter // is indented with a space. That has two purposes: // - visual difference between the two for a human reader because indention // will be different // - no ambiguity when some value line starts with the end delimiter // // One downside is that the output cannot distinguish between strings that // end with a line break and those that don't because the end delimiter // will always be on the next line. b.WriteString("=<\n") for index != -1 { b.WriteByte('\t') b.Write(data[0 : index+1]) data = data[index+1:] index = bytes.IndexByte(data, '\n') } if len(data) == 0 { // String ended with line break, don't add another. b.WriteString(" >") } else { // No line break at end of last line, write rest of string and // add one. b.WriteByte('\t') b.Write(data) b.WriteString("\n >") } } golang-k8s-klog-2.80.1/internal/serialize/keyvalues_test.go000066400000000000000000000234441432555450600237270ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 serialize_test import ( "bytes" "fmt" "reflect" "testing" "time" "k8s.io/klog/v2" "k8s.io/klog/v2/internal/serialize" "k8s.io/klog/v2/internal/test" ) // point conforms to fmt.Stringer interface as it implements the String() method type point struct { x int y int } // we now have a value receiver func (p point) String() string { return fmt.Sprintf("x=%d, y=%d", p.x, p.y) } type dummyStruct struct { key string value string } func (d *dummyStruct) MarshalLog() interface{} { return map[string]string{ "key-data": d.key, "value-data": d.value, } } type dummyStructWithStringMarshal struct { key string value string } func (d *dummyStructWithStringMarshal) MarshalLog() interface{} { return fmt.Sprintf("%s=%s", d.key, d.value) } // Test that kvListFormat works as advertised. func TestKvListFormat(t *testing.T) { var emptyPoint *point var testKVList = []struct { keysValues []interface{} want string }{ { keysValues: []interface{}{"data", &dummyStruct{key: "test", value: "info"}}, want: " data=map[key-data:test value-data:info]", }, { keysValues: []interface{}{"data", &dummyStructWithStringMarshal{key: "test", value: "info"}}, want: ` data="test=info"`, }, { keysValues: []interface{}{"pod", "kubedns"}, want: " pod=\"kubedns\"", }, { keysValues: []interface{}{"pod", "kubedns", "update", true}, want: " pod=\"kubedns\" update=true", }, { keysValues: []interface{}{"pod", "kubedns", "spec", struct { X int Y string N time.Time }{X: 76, Y: "strval", N: time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.UTC)}}, want: " pod=\"kubedns\" spec={X:76 Y:strval N:2006-01-02 15:04:05.06789 +0000 UTC}", }, { keysValues: []interface{}{"pod", "kubedns", "values", []int{8, 6, 7, 5, 3, 0, 9}}, want: " pod=\"kubedns\" values=[8 6 7 5 3 0 9]", }, { keysValues: []interface{}{"pod", "kubedns", "values", []string{"deployment", "svc", "configmap"}}, want: " pod=\"kubedns\" values=[deployment svc configmap]", }, { keysValues: []interface{}{"pod", "kubedns", "bytes", []byte("test case for byte array")}, want: " pod=\"kubedns\" bytes=\"test case for byte array\"", }, { keysValues: []interface{}{"pod", "kubedns", "bytes", []byte("��=� ⌘")}, want: " pod=\"kubedns\" bytes=\"\\ufffd\\ufffd=\\ufffd \\u2318\"", }, { keysValues: []interface{}{"multiLineString", `Hello world! Starts with tab. Starts with spaces. No whitespace.`, "pod", "kubedns", }, want: ` multiLineString=< Hello world! Starts with tab. Starts with spaces. No whitespace. > pod="kubedns"`, }, { keysValues: []interface{}{"pod", "kubedns", "maps", map[string]int{"three": 4}}, want: " pod=\"kubedns\" maps=map[three:4]", }, { keysValues: []interface{}{"pod", klog.KRef("kube-system", "kubedns"), "status", "ready"}, want: " pod=\"kube-system/kubedns\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KRef("", "kubedns"), "status", "ready"}, want: " pod=\"kubedns\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KObj(test.KMetadataMock{Name: "test-name", NS: "test-ns"}), "status", "ready"}, want: " pod=\"test-ns/test-name\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KObj(test.KMetadataMock{Name: "test-name", NS: ""}), "status", "ready"}, want: " pod=\"test-name\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KObj(nil), "status", "ready"}, want: " pod=\"\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KObj((*test.PtrKMetadataMock)(nil)), "status", "ready"}, want: " pod=\"\" status=\"ready\"", }, { keysValues: []interface{}{"pod", klog.KObj((*test.KMetadataMock)(nil)), "status", "ready"}, want: " pod=\"\" status=\"ready\"", }, { keysValues: []interface{}{"pods", klog.KObjs([]test.KMetadataMock{ { Name: "kube-dns", NS: "kube-system", }, { Name: "mi-conf", }, })}, want: " pods=[kube-system/kube-dns mi-conf]", }, { keysValues: []interface{}{"point-1", point{100, 200}, "point-2", emptyPoint}, want: " point-1=\"x=100, y=200\" point-2=\"\"", }, { keysValues: []interface{}{struct{ key string }{key: "k1"}, "value"}, want: " {k1}=\"value\"", }, { keysValues: []interface{}{1, "test"}, want: " %!s(int=1)=\"test\"", }, { keysValues: []interface{}{map[string]string{"k": "key"}, "value"}, want: " map[k:key]=\"value\"", }, } for _, d := range testKVList { b := &bytes.Buffer{} serialize.KVListFormat(b, d.keysValues...) if b.String() != d.want { t.Errorf("KVListFormat error:\n got:\n\t%s\nwant:\t%s", b.String(), d.want) } } } func TestDuplicates(t *testing.T) { for name, test := range map[string]struct { first, second []interface{} expected []interface{} }{ "empty": {}, "no-duplicates": { first: makeKV("a", 3), second: makeKV("b", 3), expected: append(makeKV("a", 3), makeKV("b", 3)...), }, "all-duplicates": { first: makeKV("a", 3), second: makeKV("a", 3), expected: makeKV("a", 3), }, "start-duplicate": { first: append([]interface{}{"x", 1}, makeKV("a", 3)...), second: append([]interface{}{"x", 2}, makeKV("b", 3)...), expected: append(append(makeKV("a", 3), "x", 2), makeKV("b", 3)...), }, "subset-first": { first: append([]interface{}{"x", 1}, makeKV("a", 3)...), second: append([]interface{}{"x", 2}, makeKV("a", 3)...), expected: append([]interface{}{"x", 2}, makeKV("a", 3)...), }, "subset-second": { first: append([]interface{}{"x", 1}, makeKV("a", 1)...), second: append([]interface{}{"x", 2}, makeKV("b", 2)...), expected: append(append(makeKV("a", 1), "x", 2), makeKV("b", 2)...), }, "end-duplicate": { first: append(makeKV("a", 3), "x", 1), second: append(makeKV("b", 3), "x", 2), expected: append(makeKV("a", 3), append(makeKV("b", 3), "x", 2)...), }, "middle-duplicate": { first: []interface{}{"a-0", 0, "x", 1, "a-1", 2}, second: []interface{}{"b-0", 0, "x", 2, "b-1", 2}, expected: []interface{}{"a-0", 0, "a-1", 2, "b-0", 0, "x", 2, "b-1", 2}, }, "internal-duplicates": { first: []interface{}{"a", 0, "x", 1, "a", 2}, second: []interface{}{"b", 0, "x", 2, "b", 2}, // This is the case where Merged keeps key/value pairs // that were already duplicated inside the slices, for // performance. expected: []interface{}{"a", 0, "a", 2, "b", 0, "x", 2, "b", 2}, }, } { t.Run(name, func(t *testing.T) { t.Run("Merged", func(t *testing.T) { actual := serialize.MergeKVs(test.first, test.second) expectEqual(t, "merged key/value pairs", test.expected, actual) }) }) } } // BenchmarkMergeKVs checks performance when MergeKVs is called with two slices. // In practice that is how the function is used. func BenchmarkMergeKVs(b *testing.B) { for firstLength := 0; firstLength < 10; firstLength++ { firstA := makeKV("a", firstLength) for secondLength := 0; secondLength < 10; secondLength++ { secondA := makeKV("a", secondLength) secondB := makeKV("b", secondLength) b.Run(fmt.Sprintf("%dx%d", firstLength, secondLength), func(b *testing.B) { // This is the most common case: all key/value pairs are kept. b.Run("no-duplicates", func(b *testing.B) { expected := append(firstA, secondB...) benchMergeKVs(b, expected, firstA, secondB) }) // Fairly unlikely... b.Run("all-duplicates", func(b *testing.B) { var expected []interface{} if firstLength > secondLength { expected = firstA[secondLength*2:] } expected = append(expected, secondA...) benchMergeKVs(b, expected, firstA, secondA) }) // First entry is the same. b.Run("start-duplicate", func(b *testing.B) { first := []interface{}{"x", 1} first = append(first, firstA...) second := []interface{}{"x", 1} second = append(second, secondB...) expected := append(firstA, second...) benchMergeKVs(b, expected, first, second) }) // Last entry is the same. b.Run("end-duplicate", func(b *testing.B) { first := firstA[:] first = append(first, "x", 1) second := secondB[:] second = append(second, "x", 1) expected := append(firstA, second...) benchMergeKVs(b, expected, first, second) }) }) } } } func makeKV(prefix string, length int) []interface{} { if length == 0 { return []interface{}{} } kv := make([]interface{}, 0, length*2) for i := 0; i < length; i++ { kv = append(kv, fmt.Sprintf("%s-%d", prefix, i), i) } return kv } func benchMergeKVs(b *testing.B, expected []interface{}, first, second []interface{}) { if len(expected) == 0 { expected = nil } actual := serialize.MergeKVs(first, second) expectEqual(b, "trimmed key/value pairs", expected, actual) b.ResetTimer() for i := 0; i < b.N; i++ { serialize.MergeKVs(first, second) } } func expectEqual(tb testing.TB, what string, expected, actual interface{}) { if !reflect.DeepEqual(expected, actual) { tb.Fatalf("Did not get correct %s. Expected:\n %v\nActual:\n %v", what, expected, actual) } } golang-k8s-klog-2.80.1/internal/severity/000077500000000000000000000000001432555450600202055ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/severity/severity.go000066400000000000000000000031421432555450600224060ustar00rootroot00000000000000// Copyright 2013 Google Inc. All Rights Reserved. // Copyright 2022 The Kubernetes 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 severity provides definitions for klog severity (info, warning, ...) package severity import ( "strings" ) // severity identifies the sort of log: info, warning etc. The binding to flag.Value // is handled in klog.go type Severity int32 // sync/atomic int32 // These constants identify the log levels in order of increasing severity. // A message written to a high-severity log file is also written to each // lower-severity log file. const ( InfoLog Severity = iota WarningLog ErrorLog FatalLog NumSeverity = 4 ) // Char contains one shortcut letter per severity level. const Char = "IWEF" // Name contains one name per severity level. var Name = []string{ InfoLog: "INFO", WarningLog: "WARNING", ErrorLog: "ERROR", FatalLog: "FATAL", } // ByName looks up a severity level by name. func ByName(s string) (Severity, bool) { s = strings.ToUpper(s) for i, name := range Name { if name == s { return Severity(i), true } } return 0, false } golang-k8s-klog-2.80.1/internal/test/000077500000000000000000000000001432555450600173125ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/test/mock.go000066400000000000000000000017341432555450600205770ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 test contains common code for klog tests. package test type KMetadataMock struct { Name, NS string } func (m KMetadataMock) GetName() string { return m.Name } func (m KMetadataMock) GetNamespace() string { return m.NS } type PtrKMetadataMock struct { Name, NS string } func (m *PtrKMetadataMock) GetName() string { return m.Name } func (m *PtrKMetadataMock) GetNamespace() string { return m.NS } golang-k8s-klog-2.80.1/internal/verbosity/000077500000000000000000000000001432555450600203615ustar00rootroot00000000000000golang-k8s-klog-2.80.1/internal/verbosity/helper_test.go000066400000000000000000000012321432555450600232240ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 verbosity func enabledInHelper(vs *VState, l Level) bool { return vs.Enabled(l, 0) } golang-k8s-klog-2.80.1/internal/verbosity/verbosity.go000066400000000000000000000210111432555450600227310ustar00rootroot00000000000000/* Copyright 2013 Google Inc. All Rights Reserved. Copyright 2022 The Kubernetes 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 verbosity import ( "bytes" "errors" "flag" "fmt" "path/filepath" "runtime" "strconv" "strings" "sync" "sync/atomic" ) // New returns a struct that implements -v and -vmodule support. Changing and // checking these settings is thread-safe, with all concurrency issues handled // internally. func New() *VState { vs := new(VState) // The two fields must have a pointer to the overal struct for their // implementation of Set. vs.vmodule.vs = vs vs.verbosity.vs = vs return vs } // Value is an extension that makes it possible to use the values in pflag. type Value interface { flag.Value Type() string } func (vs *VState) V() Value { return &vs.verbosity } func (vs *VState) VModule() Value { return &vs.vmodule } // VState contains settings and state. Some of its fields can be accessed // through atomic read/writes, in other cases a mutex must be held. type VState struct { mu sync.Mutex // These flags are modified only under lock, although verbosity may be fetched // safely using atomic.LoadInt32. vmodule moduleSpec // The state of the -vmodule flag. verbosity levelSpec // V logging level, the value of the -v flag/ // pcs is used in V to avoid an allocation when computing the caller's PC. pcs [1]uintptr // vmap is a cache of the V Level for each V() call site, identified by PC. // It is wiped whenever the vmodule flag changes state. vmap map[uintptr]Level // filterLength stores the length of the vmodule filter chain. If greater // than zero, it means vmodule is enabled. It may be read safely // using sync.LoadInt32, but is only modified under mu. filterLength int32 } // Level must be an int32 to support atomic read/writes. type Level int32 type levelSpec struct { vs *VState l Level } // get returns the value of the level. func (l *levelSpec) get() Level { return Level(atomic.LoadInt32((*int32)(&l.l))) } // set sets the value of the level. func (l *levelSpec) set(val Level) { atomic.StoreInt32((*int32)(&l.l), int32(val)) } // String is part of the flag.Value interface. func (l *levelSpec) String() string { return strconv.FormatInt(int64(l.l), 10) } // Get is part of the flag.Getter interface. It returns the // verbosity level as int32. func (l *levelSpec) Get() interface{} { return int32(l.l) } // Type is part of pflag.Value. func (l *levelSpec) Type() string { return "Level" } // Set is part of the flag.Value interface. func (l *levelSpec) Set(value string) error { v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } l.vs.mu.Lock() defer l.vs.mu.Unlock() l.vs.set(Level(v), l.vs.vmodule.filter, false) return nil } // moduleSpec represents the setting of the -vmodule flag. type moduleSpec struct { vs *VState filter []modulePat } // modulePat contains a filter for the -vmodule flag. // It holds a verbosity level and a file pattern to match. type modulePat struct { pattern string literal bool // The pattern is a literal string level Level } // match reports whether the file matches the pattern. It uses a string // comparison if the pattern contains no metacharacters. func (m *modulePat) match(file string) bool { if m.literal { return file == m.pattern } match, _ := filepath.Match(m.pattern, file) return match } func (m *moduleSpec) String() string { // Lock because the type is not atomic. TODO: clean this up. // Empty instances don't have and don't need a lock (can // happen when flag uses introspection). if m.vs != nil { m.vs.mu.Lock() defer m.vs.mu.Unlock() } var b bytes.Buffer for i, f := range m.filter { if i > 0 { b.WriteRune(',') } fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) } return b.String() } // Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the // struct is not exported. func (m *moduleSpec) Get() interface{} { return nil } // Type is part of pflag.Value func (m *moduleSpec) Type() string { return "pattern=N,..." } var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") // Set will sets module value // Syntax: -vmodule=recordio=2,file=1,gfs*=3 func (m *moduleSpec) Set(value string) error { var filter []modulePat for _, pat := range strings.Split(value, ",") { if len(pat) == 0 { // Empty strings such as from a trailing comma can be ignored. continue } patLev := strings.Split(pat, "=") if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { return errVmoduleSyntax } pattern := patLev[0] v, err := strconv.ParseInt(patLev[1], 10, 32) if err != nil { return errors.New("syntax error: expect comma-separated list of filename=N") } if v < 0 { return errors.New("negative value for vmodule level") } if v == 0 { continue // Ignore. It's harmless but no point in paying the overhead. } // TODO: check syntax of filter? filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) } m.vs.mu.Lock() defer m.vs.mu.Unlock() m.vs.set(m.vs.verbosity.l, filter, true) return nil } // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters // that require filepath.Match to be called to match the pattern. func isLiteral(pattern string) bool { return !strings.ContainsAny(pattern, `\*?[]`) } // set sets a consistent state for V logging. // The mutex must be held. func (vs *VState) set(l Level, filter []modulePat, setFilter bool) { // Turn verbosity off so V will not fire while we are in transition. vs.verbosity.set(0) // Ditto for filter length. atomic.StoreInt32(&vs.filterLength, 0) // Set the new filters and wipe the pc->Level map if the filter has changed. if setFilter { vs.vmodule.filter = filter vs.vmap = make(map[uintptr]Level) } // Things are consistent now, so enable filtering and verbosity. // They are enabled in order opposite to that in V. atomic.StoreInt32(&vs.filterLength, int32(len(filter))) vs.verbosity.set(l) } // Enabled checks whether logging is enabled at the given level. This must be // called with depth=0 when the caller of enabled will do the logging and // higher values when more stack levels need to be skipped. // // The mutex will be locked only if needed. func (vs *VState) Enabled(level Level, depth int) bool { // This function tries hard to be cheap unless there's work to do. // The fast path is two atomic loads and compares. // Here is a cheap but safe test to see if V logging is enabled globally. if vs.verbosity.get() >= level { return true } // It's off globally but vmodule may still be set. // Here is another cheap but safe test to see if vmodule is enabled. if atomic.LoadInt32(&vs.filterLength) > 0 { // Now we need a proper lock to use the logging structure. The pcs field // is shared so we must lock before accessing it. This is fairly expensive, // but if V logging is enabled we're slow anyway. vs.mu.Lock() defer vs.mu.Unlock() if runtime.Callers(depth+2, vs.pcs[:]) == 0 { return false } // runtime.Callers returns "return PCs", but we want // to look up the symbolic information for the call, // so subtract 1 from the PC. runtime.CallersFrames // would be cleaner, but allocates. pc := vs.pcs[0] - 1 v, ok := vs.vmap[pc] if !ok { v = vs.setV(pc) } return v >= level } return false } // setV computes and remembers the V level for a given PC // when vmodule is enabled. // File pattern matching takes the basename of the file, stripped // of its .go suffix, and uses filepath.Match, which is a little more // general than the *? matching used in C++. // Mutex is held. func (vs *VState) setV(pc uintptr) Level { fn := runtime.FuncForPC(pc) file, _ := fn.FileLine(pc) // The file is something like /a/b/c/d.go. We want just the d. if strings.HasSuffix(file, ".go") { file = file[:len(file)-3] } if slash := strings.LastIndex(file, "/"); slash >= 0 { file = file[slash+1:] } for _, filter := range vs.vmodule.filter { if filter.match(file) { vs.vmap[pc] = filter.level return filter.level } } vs.vmap[pc] = 0 return 0 } golang-k8s-klog-2.80.1/internal/verbosity/verbosity_test.go000066400000000000000000000047141432555450600240030ustar00rootroot00000000000000/* Copyright 2013 Google Inc. All Rights Reserved. Copyright 2022 The Kubernetes 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 verbosity import ( "testing" ) func TestV(t *testing.T) { vs := New() vs.verbosity.Set("2") depth := 0 if !vs.Enabled(1, depth) { t.Error("not enabled for 1") } if !vs.Enabled(2, depth) { t.Error("not enabled for 2") } if vs.Enabled(3, depth) { t.Error("enabled for 3") } } func TestVmoduleOn(t *testing.T) { vs := New() vs.vmodule.Set("verbosity_test=2") depth := 0 if !vs.Enabled(1, depth) { t.Error("not enabled for 1") } if !vs.Enabled(2, depth) { t.Error("not enabled for 2") } if vs.Enabled(3, depth) { t.Error("enabled for 3") } if enabledInHelper(vs, 1) { t.Error("enabled for helper at 1") } if enabledInHelper(vs, 2) { t.Error("enabled for helper at 2") } if enabledInHelper(vs, 3) { t.Error("enabled for helper at 3") } } // vGlobs are patterns that match/don't match this file at V=2. var vGlobs = map[string]bool{ // Easy to test the numeric match here. "verbosity_test=1": false, // If -vmodule sets V to 1, V(2) will fail. "verbosity_test=2": true, "verbosity_test=3": true, // If -vmodule sets V to 1, V(3) will succeed. // These all use 2 and check the patterns. All are true. "*=2": true, "?e*=2": true, "?????????_*=2": true, "??[arx]??????_*t=2": true, // These all use 2 and check the patterns. All are false. "*x=2": false, "m*=2": false, "??_*=2": false, "?[abc]?_*t=2": false, } // Test that vmodule globbing works as advertised. func testVmoduleGlob(pat string, match bool, t *testing.T) { vs := New() vs.vmodule.Set(pat) depth := 0 actual := vs.Enabled(2, depth) if actual != match { t.Errorf("incorrect match for %q: got %#v expected %#v", pat, actual, match) } } // Test that a vmodule globbing works as advertised. func TestVmoduleGlob(t *testing.T) { for glob, match := range vGlobs { testVmoduleGlob(glob, match, t) } } golang-k8s-klog-2.80.1/k8s_references.go000066400000000000000000000101561432555450600177570ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 klog import ( "fmt" "reflect" "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 fmt.Sprintf("%s/%s", ref.Namespace, ref.Name) } return ref.Name } // MarshalLog ensures that loggers with support for structured output will log // as a struct by removing the String method via a custom type. func (ref ObjectRef) MarshalLog() interface{} { type or ObjectRef return or(ref) } var _ logr.Marshaler = ObjectRef{} // KMetadata is a subset of the kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface // this interface may expand in the future, but will always be a subset of the // kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface type KMetadata interface { GetName() string GetNamespace() string } // KObj returns ObjectRef from ObjectMeta func KObj(obj KMetadata) ObjectRef { if obj == nil { return ObjectRef{} } if val := reflect.ValueOf(obj); val.Kind() == reflect.Ptr && val.IsNil() { return ObjectRef{} } return ObjectRef{ Name: obj.GetName(), Namespace: obj.GetNamespace(), } } // KRef returns ObjectRef from name and namespace func KRef(namespace, name string) ObjectRef { return ObjectRef{ Name: name, Namespace: namespace, } } // KObjs returns slice of ObjectRef from an slice of ObjectMeta // // DEPRECATED: Use KObjSlice instead, it has better performance. func KObjs(arg interface{}) []ObjectRef { s := reflect.ValueOf(arg) if s.Kind() != reflect.Slice { return nil } objectRefs := make([]ObjectRef, 0, s.Len()) for i := 0; i < s.Len(); i++ { if v, ok := s.Index(i).Interface().(KMetadata); ok { objectRefs = append(objectRefs, KObj(v)) } else { return nil } } return objectRefs } // KObjSlice takes a slice of objects that implement the KMetadata interface // and returns an object that gets logged as a slice of ObjectRef values or a // string containing those values, depending on whether the logger prefers text // output or structured output. // // An error string is logged when KObjSlice is not passed a suitable slice. // // Processing of the argument is delayed until the value actually gets logged, // in contrast to KObjs where that overhead is incurred regardless of whether // the result is needed. func KObjSlice(arg interface{}) interface{} { return kobjSlice{arg: arg} } type kobjSlice struct { arg interface{} } var _ fmt.Stringer = kobjSlice{} var _ logr.Marshaler = kobjSlice{} func (ks kobjSlice) String() string { objectRefs, err := ks.process() if err != nil { return err.Error() } return fmt.Sprintf("%v", objectRefs) } func (ks kobjSlice) MarshalLog() interface{} { objectRefs, err := ks.process() if err != nil { return err.Error() } return objectRefs } func (ks kobjSlice) process() ([]interface{}, error) { s := reflect.ValueOf(ks.arg) switch s.Kind() { case reflect.Invalid: // nil parameter, print as nil. return nil, nil case reflect.Slice: // Okay, handle below. default: return nil, fmt.Errorf("", ks.arg) } objectRefs := make([]interface{}, 0, s.Len()) for i := 0; i < s.Len(); i++ { item := s.Index(i).Interface() if item == nil { objectRefs = append(objectRefs, nil) } else if v, ok := item.(KMetadata); ok { objectRefs = append(objectRefs, KObj(v)) } else { return nil, fmt.Errorf("", item) } } return objectRefs, nil } golang-k8s-klog-2.80.1/klog.go000066400000000000000000001603101432555450600160030ustar00rootroot00000000000000// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ // // Copyright 2013 Google Inc. All Rights Reserved. // // 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 klog implements logging analogous to the Google-internal C++ INFO/ERROR/V setup. // It provides functions Info, Warning, Error, Fatal, plus formatting variants such as // Infof. It also provides V-style logging controlled by the -v and -vmodule=file=2 flags. // // Basic examples: // // klog.Info("Prepare to repel boarders") // // klog.Fatalf("Initialization failed: %s", err) // // See the documentation for the V function for an explanation of these examples: // // if klog.V(2) { // klog.Info("Starting transaction...") // } // // klog.V(2).Infoln("Processed", nItems, "elements") // // Log output is buffered and written periodically using Flush. Programs // should call Flush before exiting to guarantee all log output is written. // // By default, all log statements write to standard error. // This package provides several flags that modify this behavior. // As a result, flag.Parse must be called before any logging is done. // // -logtostderr=true // Logs are written to standard error instead of to files. // This shortcuts most of the usual output routing: // -alsologtostderr, -stderrthreshold and -log_dir have no // effect and output redirection at runtime with SetOutput is // ignored. // -alsologtostderr=false // Logs are written to standard error as well as to files. // -stderrthreshold=ERROR // Log events at or above this severity are logged to standard // error as well as to files. // -log_dir="" // Log files will be written to this directory instead of the // default temporary directory. // // Other flags provide aids to debugging. // // -log_backtrace_at="" // When set to a file and line number holding a logging statement, // such as // -log_backtrace_at=gopherflakes.go:234 // a stack trace will be written to the Info log whenever execution // hits that statement. (Unlike with -vmodule, the ".go" must be // present.) // -v=0 // Enable V-leveled logging at the specified level. // -vmodule="" // The syntax of the argument is a comma-separated list of pattern=N, // where pattern is a literal file name (minus the ".go" suffix) or // "glob" pattern and N is a V level. For instance, // -vmodule=gopher*=3 // sets the V level to 3 in all Go files whose names begin "gopher". package klog import ( "bufio" "bytes" "errors" "flag" "fmt" "io" stdLog "log" "math" "os" "path/filepath" "runtime" "strconv" "strings" "sync" "sync/atomic" "time" "github.com/go-logr/logr" "k8s.io/klog/v2/internal/buffer" "k8s.io/klog/v2/internal/clock" "k8s.io/klog/v2/internal/dbg" "k8s.io/klog/v2/internal/serialize" "k8s.io/klog/v2/internal/severity" ) // severityValue identifies the sort of log: info, warning etc. It also implements // the flag.Value interface. The -stderrthreshold flag is of type severity and // should be modified only through the flag.Value interface. The values match // the corresponding constants in C++. type severityValue struct { severity.Severity } // get returns the value of the severity. func (s *severityValue) get() severity.Severity { return severity.Severity(atomic.LoadInt32((*int32)(&s.Severity))) } // set sets the value of the severity. func (s *severityValue) set(val severity.Severity) { atomic.StoreInt32((*int32)(&s.Severity), int32(val)) } // String is part of the flag.Value interface. func (s *severityValue) String() string { return strconv.FormatInt(int64(s.Severity), 10) } // Get is part of the flag.Getter interface. func (s *severityValue) Get() interface{} { return s.Severity } // Set is part of the flag.Value interface. func (s *severityValue) Set(value string) error { var threshold severity.Severity // Is it a known name? if v, ok := severity.ByName(value); ok { threshold = v } else { v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } threshold = severity.Severity(v) } logging.stderrThreshold.set(threshold) return nil } // OutputStats tracks the number of output lines and bytes written. type OutputStats struct { lines int64 bytes int64 } // Lines returns the number of lines written. func (s *OutputStats) Lines() int64 { return atomic.LoadInt64(&s.lines) } // Bytes returns the number of bytes written. func (s *OutputStats) Bytes() int64 { return atomic.LoadInt64(&s.bytes) } // Stats tracks the number of lines of output and number of bytes // per severity level. Values must be read with atomic.LoadInt64. var Stats struct { Info, Warning, Error OutputStats } var severityStats = [severity.NumSeverity]*OutputStats{ severity.InfoLog: &Stats.Info, severity.WarningLog: &Stats.Warning, severity.ErrorLog: &Stats.Error, } // Level is exported because it appears in the arguments to V and is // the type of the v flag, which can be set programmatically. // It's a distinct type because we want to discriminate it from logType. // Variables of type level are only changed under logging.mu. // The -v flag is read only with atomic ops, so the state of the logging // module is consistent. // Level is treated as a sync/atomic int32. // Level specifies a level of verbosity for V logs. *Level implements // flag.Value; the -v flag is of type Level and should be modified // only through the flag.Value interface. type Level int32 // get returns the value of the Level. func (l *Level) get() Level { return Level(atomic.LoadInt32((*int32)(l))) } // set sets the value of the Level. func (l *Level) set(val Level) { atomic.StoreInt32((*int32)(l), int32(val)) } // String is part of the flag.Value interface. func (l *Level) String() string { return strconv.FormatInt(int64(*l), 10) } // Get is part of the flag.Getter interface. func (l *Level) Get() interface{} { return *l } // Set is part of the flag.Value interface. func (l *Level) Set(value string) error { v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } logging.mu.Lock() defer logging.mu.Unlock() logging.setVState(Level(v), logging.vmodule.filter, false) return nil } // moduleSpec represents the setting of the -vmodule flag. type moduleSpec struct { filter []modulePat } // modulePat contains a filter for the -vmodule flag. // It holds a verbosity level and a file pattern to match. type modulePat struct { pattern string literal bool // The pattern is a literal string level Level } // match reports whether the file matches the pattern. It uses a string // comparison if the pattern contains no metacharacters. func (m *modulePat) match(file string) bool { if m.literal { return file == m.pattern } match, _ := filepath.Match(m.pattern, file) return match } func (m *moduleSpec) String() string { // Lock because the type is not atomic. TODO: clean this up. logging.mu.Lock() defer logging.mu.Unlock() return m.serialize() } func (m *moduleSpec) serialize() string { var b bytes.Buffer for i, f := range m.filter { if i > 0 { b.WriteRune(',') } fmt.Fprintf(&b, "%s=%d", f.pattern, f.level) } return b.String() } // Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the // struct is not exported. func (m *moduleSpec) Get() interface{} { return nil } var errVmoduleSyntax = errors.New("syntax error: expect comma-separated list of filename=N") // Set will sets module value // Syntax: -vmodule=recordio=2,file=1,gfs*=3 func (m *moduleSpec) Set(value string) error { filter, err := parseModuleSpec(value) if err != nil { return err } logging.mu.Lock() defer logging.mu.Unlock() logging.setVState(logging.verbosity, filter, true) return nil } func parseModuleSpec(value string) ([]modulePat, error) { var filter []modulePat for _, pat := range strings.Split(value, ",") { if len(pat) == 0 { // Empty strings such as from a trailing comma can be ignored. continue } patLev := strings.Split(pat, "=") if len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 { return nil, errVmoduleSyntax } pattern := patLev[0] v, err := strconv.ParseInt(patLev[1], 10, 32) if err != nil { return nil, errors.New("syntax error: expect comma-separated list of filename=N") } if v < 0 { return nil, errors.New("negative value for vmodule level") } if v == 0 { continue // Ignore. It's harmless but no point in paying the overhead. } // TODO: check syntax of filter? filter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)}) } return filter, nil } // isLiteral reports whether the pattern is a literal string, that is, has no metacharacters // that require filepath.Match to be called to match the pattern. func isLiteral(pattern string) bool { return !strings.ContainsAny(pattern, `\*?[]`) } // traceLocation represents the setting of the -log_backtrace_at flag. type traceLocation struct { file string line int } // isSet reports whether the trace location has been specified. // logging.mu is held. func (t *traceLocation) isSet() bool { return t.line > 0 } // match reports whether the specified file and line matches the trace location. // The argument file name is the full path, not the basename specified in the flag. // logging.mu is held. func (t *traceLocation) match(file string, line int) bool { if t.line != line { return false } if i := strings.LastIndex(file, "/"); i >= 0 { file = file[i+1:] } return t.file == file } func (t *traceLocation) String() string { // Lock because the type is not atomic. TODO: clean this up. logging.mu.Lock() defer logging.mu.Unlock() return fmt.Sprintf("%s:%d", t.file, t.line) } // Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the // struct is not exported func (t *traceLocation) Get() interface{} { return nil } var errTraceSyntax = errors.New("syntax error: expect file.go:234") // Set will sets backtrace value // Syntax: -log_backtrace_at=gopherflakes.go:234 // Note that unlike vmodule the file extension is included here. func (t *traceLocation) Set(value string) error { if value == "" { // Unset. logging.mu.Lock() defer logging.mu.Unlock() t.line = 0 t.file = "" return nil } fields := strings.Split(value, ":") if len(fields) != 2 { return errTraceSyntax } file, line := fields[0], fields[1] if !strings.Contains(file, ".") { return errTraceSyntax } v, err := strconv.Atoi(line) if err != nil { return errTraceSyntax } if v <= 0 { return errors.New("negative or zero value for level") } logging.mu.Lock() defer logging.mu.Unlock() t.line = v t.file = file return nil } // flushSyncWriter is the interface satisfied by logging destinations. type flushSyncWriter interface { Flush() error Sync() error io.Writer } var logging loggingT var commandLine flag.FlagSet // init sets up the defaults and creates command line flags. func init() { commandLine.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory (no effect when -logtostderr=true)") commandLine.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file (no effect when -logtostderr=true)") commandLine.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 1800, "Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. "+ "If the value is 0, the maximum file size is unlimited.") commandLine.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files") commandLine.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files (no effect when -logtostderr=true)") logging.setVState(0, nil, false) commandLine.Var(&logging.verbosity, "v", "number for the log level verbosity") commandLine.BoolVar(&logging.addDirHeader, "add_dir_header", false, "If true, adds the file directory to the header of the log messages") commandLine.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages") commandLine.BoolVar(&logging.oneOutput, "one_output", false, "If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)") commandLine.BoolVar(&logging.skipLogHeaders, "skip_log_headers", false, "If true, avoid headers when opening log files (no effect when -logtostderr=true)") logging.stderrThreshold = severityValue{ Severity: severity.ErrorLog, // Default stderrThreshold is ERROR. } commandLine.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false)") commandLine.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") commandLine.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") logging.settings.contextualLoggingEnabled = true logging.flushD = newFlushDaemon(logging.lockAndFlushAll, nil) } // InitFlags is for explicitly initializing the flags. // It may get called repeatedly for different flagsets, but not // twice for the same one. May get called concurrently // to other goroutines using klog. However, only some flags // may get set concurrently (see implementation). func InitFlags(flagset *flag.FlagSet) { if flagset == nil { flagset = flag.CommandLine } commandLine.VisitAll(func(f *flag.Flag) { flagset.Var(f.Value, f.Name, f.Usage) }) } // Flush flushes all pending log I/O. func Flush() { logging.lockAndFlushAll() } // settings collects global settings. type settings struct { // contextualLoggingEnabled controls whether contextual logging is // active. Disabling it may have some small performance benefit. contextualLoggingEnabled bool // logger is the global Logger chosen by users of klog, nil if // none is available. logger *Logger // loggerOptions contains the options that were supplied for // globalLogger. loggerOptions loggerOptions // Boolean flags. Not handled atomically because the flag.Value interface // does not let us avoid the =true, and that shorthand is necessary for // compatibility. TODO: does this matter enough to fix? Seems unlikely. toStderr bool // The -logtostderr flag. alsoToStderr bool // The -alsologtostderr flag. // Level flag. Handled atomically. stderrThreshold severityValue // The -stderrthreshold flag. // Access to all of the following fields must be protected via a mutex. // file holds writer for each of the log types. file [severity.NumSeverity]flushSyncWriter // flushInterval is the interval for periodic flushing. If zero, // the global default will be used. flushInterval time.Duration // filterLength stores the length of the vmodule filter chain. If greater // than zero, it means vmodule is enabled. It may be read safely // using sync.LoadInt32, but is only modified under mu. filterLength int32 // traceLocation is the state of the -log_backtrace_at flag. traceLocation traceLocation // These flags are modified only under lock, although verbosity may be fetched // safely using atomic.LoadInt32. vmodule moduleSpec // The state of the -vmodule flag. verbosity Level // V logging level, the value of the -v flag/ // If non-empty, overrides the choice of directory in which to write logs. // See createLogDirs for the full list of possible destinations. logDir string // If non-empty, specifies the path of the file to write logs. mutually exclusive // with the log_dir option. logFile string // When logFile is specified, this limiter makes sure the logFile won't exceeds a certain size. When exceeds, the // logFile will be cleaned up. If this value is 0, no size limitation will be applied to logFile. logFileMaxSizeMB uint64 // If true, do not add the prefix headers, useful when used with SetOutput skipHeaders bool // If true, do not add the headers to log files skipLogHeaders bool // If true, add the file directory to the header addDirHeader bool // If true, messages will not be propagated to lower severity log levels oneOutput bool // If set, all output will be filtered through the filter. filter LogFilter } // deepCopy creates a copy that doesn't share anything with the original // instance. func (s settings) deepCopy() settings { // vmodule is a slice and would be shared, so we have copy it. filter := make([]modulePat, len(s.vmodule.filter)) for i := range s.vmodule.filter { filter[i] = s.vmodule.filter[i] } s.vmodule.filter = filter return s } // loggingT collects all the global state of the logging setup. type loggingT struct { settings // bufferCache maintains the free list. It uses its own mutex // so buffers can be grabbed and printed to without holding the main lock, // for better parallelization. bufferCache buffer.Buffers // flushD holds a flushDaemon that frequently flushes log file buffers. // Uses its own mutex. flushD *flushDaemon // mu protects the remaining elements of this structure and the fields // in settingsT which need a mutex lock. mu sync.Mutex // pcs is used in V to avoid an allocation when computing the caller's PC. pcs [1]uintptr // vmap is a cache of the V Level for each V() call site, identified by PC. // It is wiped whenever the vmodule flag changes state. vmap map[uintptr]Level } // setVState sets a consistent state for V logging. // l.mu is held. func (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) { // Turn verbosity off so V will not fire while we are in transition. l.verbosity.set(0) // Ditto for filter length. atomic.StoreInt32(&l.filterLength, 0) // Set the new filters and wipe the pc->Level map if the filter has changed. if setFilter { l.vmodule.filter = filter l.vmap = make(map[uintptr]Level) } // Things are consistent now, so enable filtering and verbosity. // They are enabled in order opposite to that in V. atomic.StoreInt32(&l.filterLength, int32(len(filter))) l.verbosity.set(verbosity) } var timeNow = time.Now // Stubbed out for testing. // CaptureState gathers information about all current klog settings. // The result can be used to restore those settings. func CaptureState() State { logging.mu.Lock() defer logging.mu.Unlock() return &state{ settings: logging.settings.deepCopy(), flushDRunning: logging.flushD.isRunning(), maxSize: MaxSize, } } // State stores a snapshot of klog settings. It gets created with CaptureState // and can be used to restore the entire state. Modifying individual settings // is supported via the command line flags. type State interface { // Restore restore the entire state. It may get called more than once. Restore() } type state struct { settings flushDRunning bool maxSize uint64 } func (s *state) Restore() { // This needs to be done before mutex locking. if s.flushDRunning && !logging.flushD.isRunning() { // This is not quite accurate: StartFlushDaemon might // have been called with some different interval. interval := s.flushInterval if interval == 0 { interval = flushInterval } logging.flushD.run(interval) } else if !s.flushDRunning && logging.flushD.isRunning() { logging.flushD.stop() } logging.mu.Lock() defer logging.mu.Unlock() logging.settings = s.settings logging.setVState(s.verbosity, s.vmodule.filter, true) MaxSize = s.maxSize } /* header formats a log header as defined by the C++ implementation. It returns a buffer containing the formatted header and the user's file and line number. The depth specifies how many stack frames above lives the source line to be identified in the log message. Log lines have this form: Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... where the fields are defined as follows: L A single character, representing the log level (eg 'I' for INFO) mm The month (zero padded; ie May is '05') dd The day (zero padded) hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds threadid The space-padded thread ID as returned by GetTID() file The file name line The line number msg The user-supplied message */ func (l *loggingT) header(s severity.Severity, depth int) (*buffer.Buffer, string, int) { _, file, line, ok := runtime.Caller(3 + depth) if !ok { file = "???" line = 1 } else { if slash := strings.LastIndex(file, "/"); slash >= 0 { path := file file = path[slash+1:] if l.addDirHeader { if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 { file = path[dirsep+1:] } } } } return l.formatHeader(s, file, line), file, line } // formatHeader formats a log header using the provided file name and line number. func (l *loggingT) formatHeader(s severity.Severity, file string, line int) *buffer.Buffer { buf := l.bufferCache.GetBuffer() if l.skipHeaders { return buf } now := timeNow() buf.FormatHeader(s, file, line, now) return buf } func (l *loggingT) println(s severity.Severity, logger *logr.Logger, filter LogFilter, args ...interface{}) { l.printlnDepth(s, logger, filter, 1, args...) } func (l *loggingT) printlnDepth(s severity.Severity, logger *logr.Logger, filter LogFilter, depth int, args ...interface{}) { buf, file, line := l.header(s, depth) // if logger is set, we clear the generated header as we rely on the backing // logger implementation to print headers if logger != nil { l.bufferCache.PutBuffer(buf) buf = l.bufferCache.GetBuffer() } if filter != nil { args = filter.Filter(args) } fmt.Fprintln(buf, args...) l.output(s, logger, buf, depth, file, line, false) } func (l *loggingT) print(s severity.Severity, logger *logr.Logger, filter LogFilter, args ...interface{}) { l.printDepth(s, logger, filter, 1, args...) } func (l *loggingT) printDepth(s severity.Severity, logger *logr.Logger, filter LogFilter, depth int, args ...interface{}) { buf, file, line := l.header(s, depth) // if logr is set, we clear the generated header as we rely on the backing // logr implementation to print headers if logger != nil { l.bufferCache.PutBuffer(buf) buf = l.bufferCache.GetBuffer() } if filter != nil { args = filter.Filter(args) } fmt.Fprint(buf, args...) if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } l.output(s, logger, buf, depth, file, line, false) } func (l *loggingT) printf(s severity.Severity, logger *logr.Logger, filter LogFilter, format string, args ...interface{}) { l.printfDepth(s, logger, filter, 1, format, args...) } func (l *loggingT) printfDepth(s severity.Severity, logger *logr.Logger, filter LogFilter, depth int, format string, args ...interface{}) { buf, file, line := l.header(s, depth) // if logr is set, we clear the generated header as we rely on the backing // logr implementation to print headers if logger != nil { l.bufferCache.PutBuffer(buf) buf = l.bufferCache.GetBuffer() } if filter != nil { format, args = filter.FilterF(format, args) } fmt.Fprintf(buf, format, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } l.output(s, logger, buf, depth, file, line, false) } // printWithFileLine behaves like print but uses the provided file and line number. If // alsoLogToStderr is true, the log message always appears on standard error; it // will also appear in the log file unless --logtostderr is set. func (l *loggingT) printWithFileLine(s severity.Severity, logger *logr.Logger, filter LogFilter, file string, line int, alsoToStderr bool, args ...interface{}) { buf := l.formatHeader(s, file, line) // if logr is set, we clear the generated header as we rely on the backing // logr implementation to print headers if logger != nil { l.bufferCache.PutBuffer(buf) buf = l.bufferCache.GetBuffer() } if filter != nil { args = filter.Filter(args) } fmt.Fprint(buf, args...) if buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } l.output(s, logger, buf, 2 /* depth */, file, line, alsoToStderr) } // if loggr is specified, will call loggr.Error, otherwise output with logging module. func (l *loggingT) errorS(err error, logger *logr.Logger, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) { if filter != nil { msg, keysAndValues = filter.FilterS(msg, keysAndValues) } if logger != nil { logger.WithCallDepth(depth+2).Error(err, msg, keysAndValues...) return } l.printS(err, severity.ErrorLog, depth+1, msg, keysAndValues...) } // if loggr is specified, will call loggr.Info, otherwise output with logging module. func (l *loggingT) infoS(logger *logr.Logger, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) { if filter != nil { msg, keysAndValues = filter.FilterS(msg, keysAndValues) } if logger != nil { logger.WithCallDepth(depth+2).Info(msg, keysAndValues...) return } l.printS(nil, severity.InfoLog, depth+1, msg, keysAndValues...) } // printS is called from infoS and errorS if loggr is not specified. // set log severity by s func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) { // Only create a new buffer if we don't have one cached. b := l.bufferCache.GetBuffer() // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. b.WriteString(strconv.Quote(msg)) if err != nil { serialize.KVListFormat(&b.Buffer, "err", err) } serialize.KVListFormat(&b.Buffer, keysAndValues...) l.printDepth(s, logging.logger, nil, depth+1, &b.Buffer) // Make the buffer available for reuse. l.bufferCache.PutBuffer(b) } // redirectBuffer is used to set an alternate destination for the logs type redirectBuffer struct { w io.Writer } func (rb *redirectBuffer) Sync() error { return nil } func (rb *redirectBuffer) Flush() error { return nil } func (rb *redirectBuffer) Write(bytes []byte) (n int, err error) { return rb.w.Write(bytes) } // SetOutput sets the output destination for all severities func SetOutput(w io.Writer) { logging.mu.Lock() defer logging.mu.Unlock() for s := severity.FatalLog; s >= severity.InfoLog; s-- { rb := &redirectBuffer{ w: w, } logging.file[s] = rb } } // SetOutputBySeverity sets the output destination for specific severity func SetOutputBySeverity(name string, w io.Writer) { logging.mu.Lock() defer logging.mu.Unlock() sev, ok := severity.ByName(name) if !ok { panic(fmt.Sprintf("SetOutputBySeverity(%q): unrecognized severity name", name)) } rb := &redirectBuffer{ w: w, } logging.file[sev] = rb } // LogToStderr sets whether to log exclusively to stderr, bypassing outputs func LogToStderr(stderr bool) { logging.mu.Lock() defer logging.mu.Unlock() logging.toStderr = stderr } // output writes the data to the log files and releases the buffer. func (l *loggingT) output(s severity.Severity, log *logr.Logger, buf *buffer.Buffer, depth int, file string, line int, alsoToStderr bool) { var isLocked = true l.mu.Lock() defer func() { if isLocked { // Unlock before returning in case that it wasn't done already. l.mu.Unlock() } }() if l.traceLocation.isSet() { if l.traceLocation.match(file, line) { buf.Write(dbg.Stacks(false)) } } data := buf.Bytes() if log != nil { // TODO: set 'severity' and caller information as structured log info // keysAndValues := []interface{}{"severity", severityName[s], "file", file, "line", line} if s == severity.ErrorLog { logging.logger.WithCallDepth(depth+3).Error(nil, string(data)) } else { log.WithCallDepth(depth + 3).Info(string(data)) } } else if l.toStderr { os.Stderr.Write(data) } else { if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { os.Stderr.Write(data) } if logging.logFile != "" { // Since we are using a single log file, all of the items in l.file array // will point to the same file, so just use one of them to write data. if l.file[severity.InfoLog] == nil { if err := l.createFiles(severity.InfoLog); err != nil { os.Stderr.Write(data) // Make sure the message appears somewhere. l.exit(err) } } l.file[severity.InfoLog].Write(data) } else { if l.file[s] == nil { if err := l.createFiles(s); err != nil { os.Stderr.Write(data) // Make sure the message appears somewhere. l.exit(err) } } if l.oneOutput { l.file[s].Write(data) } else { switch s { case severity.FatalLog: l.file[severity.FatalLog].Write(data) fallthrough case severity.ErrorLog: l.file[severity.ErrorLog].Write(data) fallthrough case severity.WarningLog: l.file[severity.WarningLog].Write(data) fallthrough case severity.InfoLog: l.file[severity.InfoLog].Write(data) } } } } if s == severity.FatalLog { // If we got here via Exit rather than Fatal, print no stacks. if atomic.LoadUint32(&fatalNoStacks) > 0 { l.mu.Unlock() isLocked = false timeoutFlush(ExitFlushTimeout) OsExit(1) } // Dump all goroutine stacks before exiting. // First, make sure we see the trace for the current goroutine on standard error. // If -logtostderr has been specified, the loop below will do that anyway // as the first stack in the full dump. if !l.toStderr { os.Stderr.Write(dbg.Stacks(false)) } // Write the stack trace for all goroutines to the files. trace := dbg.Stacks(true) logExitFunc = func(error) {} // If we get a write error, we'll still exit below. for log := severity.FatalLog; log >= severity.InfoLog; log-- { if f := l.file[log]; f != nil { // Can be nil if -logtostderr is set. f.Write(trace) } } l.mu.Unlock() isLocked = false timeoutFlush(ExitFlushTimeout) OsExit(255) // C++ uses -1, which is silly because it's anded with 255 anyway. } l.bufferCache.PutBuffer(buf) if stats := severityStats[s]; stats != nil { atomic.AddInt64(&stats.lines, 1) atomic.AddInt64(&stats.bytes, int64(len(data))) } } // logExitFunc provides a simple mechanism to override the default behavior // of exiting on error. Used in testing and to guarantee we reach a required exit // for fatal logs. Instead, exit could be a function rather than a method but that // would make its use clumsier. var logExitFunc func(error) // exit is called if there is trouble creating or writing log files. // It flushes the logs and exits the program; there's no point in hanging around. // l.mu is held. func (l *loggingT) exit(err error) { fmt.Fprintf(os.Stderr, "log: exiting because of error: %s\n", err) // If logExitFunc is set, we do that instead of exiting. if logExitFunc != nil { logExitFunc(err) return } l.flushAll() OsExit(2) } // syncBuffer joins a bufio.Writer to its underlying file, providing access to the // file's Sync method and providing a wrapper for the Write method that provides log // file rotation. There are conflicting methods, so the file cannot be embedded. // l.mu is held for all its methods. type syncBuffer struct { logger *loggingT *bufio.Writer file *os.File sev severity.Severity nbytes uint64 // The number of bytes written to this file maxbytes uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up. } func (sb *syncBuffer) Sync() error { return sb.file.Sync() } // CalculateMaxSize returns the real max size in bytes after considering the default max size and the flag options. func CalculateMaxSize() uint64 { if logging.logFile != "" { if logging.logFileMaxSizeMB == 0 { // If logFileMaxSizeMB is zero, we don't have limitations on the log size. return math.MaxUint64 } // Flag logFileMaxSizeMB is in MB for user convenience. return logging.logFileMaxSizeMB * 1024 * 1024 } // If "log_file" flag is not specified, the target file (sb.file) will be cleaned up when reaches a fixed size. return MaxSize } func (sb *syncBuffer) Write(p []byte) (n int, err error) { if sb.nbytes+uint64(len(p)) >= sb.maxbytes { if err := sb.rotateFile(time.Now(), false); err != nil { sb.logger.exit(err) } } n, err = sb.Writer.Write(p) sb.nbytes += uint64(n) if err != nil { sb.logger.exit(err) } return } // rotateFile closes the syncBuffer's file and starts a new one. // The startup argument indicates whether this is the initial startup of klog. // If startup is true, existing files are opened for appending instead of truncated. func (sb *syncBuffer) rotateFile(now time.Time, startup bool) error { if sb.file != nil { sb.Flush() sb.file.Close() } var err error sb.file, _, err = create(severity.Name[sb.sev], now, startup) if err != nil { return err } if startup { fileInfo, err := sb.file.Stat() if err != nil { return fmt.Errorf("file stat could not get fileinfo: %v", err) } // init file size sb.nbytes = uint64(fileInfo.Size()) } else { sb.nbytes = 0 } sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) if sb.logger.skipLogHeaders { return nil } // Write header. var buf bytes.Buffer fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) fmt.Fprintf(&buf, "Running on machine: %s\n", host) fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") n, err := sb.file.Write(buf.Bytes()) sb.nbytes += uint64(n) return err } // bufferSize sizes the buffer associated with each log file. It's large // so that log records can accumulate without the logging thread blocking // on disk I/O. The flushDaemon will block instead. const bufferSize = 256 * 1024 // createFiles creates all the log files for severity from sev down to infoLog. // l.mu is held. func (l *loggingT) createFiles(sev severity.Severity) error { interval := l.flushInterval if interval == 0 { interval = flushInterval } l.flushD.run(interval) now := time.Now() // Files are created in decreasing severity order, so as soon as we find one // has already been created, we can stop. for s := sev; s >= severity.InfoLog && l.file[s] == nil; s-- { sb := &syncBuffer{ logger: l, sev: s, maxbytes: CalculateMaxSize(), } if err := sb.rotateFile(now, true); err != nil { return err } l.file[s] = sb } return nil } const flushInterval = 5 * time.Second // flushDaemon periodically flushes the log file buffers. type flushDaemon struct { mu sync.Mutex clock clock.WithTicker flush func() stopC chan struct{} stopDone chan struct{} } // newFlushDaemon returns a new flushDaemon. If the passed clock is nil, a // clock.RealClock is used. func newFlushDaemon(flush func(), tickClock clock.WithTicker) *flushDaemon { if tickClock == nil { tickClock = clock.RealClock{} } return &flushDaemon{ flush: flush, clock: tickClock, } } // run starts a goroutine that periodically calls the daemons flush function. // Calling run on an already running daemon will have no effect. func (f *flushDaemon) run(interval time.Duration) { f.mu.Lock() defer f.mu.Unlock() if f.stopC != nil { // daemon already running return } f.stopC = make(chan struct{}, 1) f.stopDone = make(chan struct{}, 1) ticker := f.clock.NewTicker(interval) go func() { defer ticker.Stop() defer func() { f.stopDone <- struct{}{} }() for { select { case <-ticker.C(): f.flush() case <-f.stopC: f.flush() return } } }() } // stop stops the running flushDaemon and waits until the daemon has shut down. // Calling stop on a daemon that isn't running will have no effect. func (f *flushDaemon) stop() { f.mu.Lock() defer f.mu.Unlock() if f.stopC == nil { // daemon not running return } f.stopC <- struct{}{} <-f.stopDone f.stopC = nil f.stopDone = nil } // isRunning returns true if the flush daemon is running. func (f *flushDaemon) isRunning() bool { f.mu.Lock() defer f.mu.Unlock() return f.stopC != nil } // StopFlushDaemon stops the flush daemon, if running, and flushes once. // This prevents klog from leaking goroutines on shutdown. After stopping // the daemon, you can still manually flush buffers again by calling Flush(). func StopFlushDaemon() { logging.flushD.stop() } // StartFlushDaemon ensures that the flush daemon runs with the given delay // between flush calls. If it is already running, it gets restarted. func StartFlushDaemon(interval time.Duration) { StopFlushDaemon() logging.flushD.run(interval) } // lockAndFlushAll is like flushAll but locks l.mu first. func (l *loggingT) lockAndFlushAll() { l.mu.Lock() l.flushAll() l.mu.Unlock() } // flushAll flushes all the logs and attempts to "sync" their data to disk. // l.mu is held. func (l *loggingT) flushAll() { // Flush from fatal down, in case there's trouble flushing. for s := severity.FatalLog; s >= severity.InfoLog; s-- { file := l.file[s] if file != nil { file.Flush() // ignore error file.Sync() // ignore error } } if logging.loggerOptions.flush != nil { logging.loggerOptions.flush() } } // CopyStandardLogTo arranges for messages written to the Go "log" package's // default logs to also appear in the Google logs for the named and lower // severities. Subsequent changes to the standard log's default output location // or format may break this behavior. // // Valid names are "INFO", "WARNING", "ERROR", and "FATAL". If the name is not // recognized, CopyStandardLogTo panics. func CopyStandardLogTo(name string) { sev, ok := severity.ByName(name) if !ok { panic(fmt.Sprintf("log.CopyStandardLogTo(%q): unrecognized severity name", name)) } // Set a log format that captures the user's file and line: // d.go:23: message stdLog.SetFlags(stdLog.Lshortfile) stdLog.SetOutput(logBridge(sev)) } // logBridge provides the Write method that enables CopyStandardLogTo to connect // Go's standard logs to the logs provided by this package. type logBridge severity.Severity // Write parses the standard logging line and passes its components to the // logger for severity(lb). func (lb logBridge) Write(b []byte) (n int, err error) { var ( file = "???" line = 1 text string ) // Split "d.go:23: message" into "d.go", "23", and "message". if parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 { text = fmt.Sprintf("bad log format: %s", b) } else { file = string(parts[0]) text = string(parts[2][1:]) // skip leading space line, err = strconv.Atoi(string(parts[1])) if err != nil { text = fmt.Sprintf("bad line number: %s", b) line = 1 } } // printWithFileLine with alsoToStderr=true, so standard log messages // always appear on standard error. logging.printWithFileLine(severity.Severity(lb), logging.logger, logging.filter, file, line, true, text) return len(b), nil } // setV computes and remembers the V level for a given PC // when vmodule is enabled. // File pattern matching takes the basename of the file, stripped // of its .go suffix, and uses filepath.Match, which is a little more // general than the *? matching used in C++. // l.mu is held. func (l *loggingT) setV(pc uintptr) Level { fn := runtime.FuncForPC(pc) file, _ := fn.FileLine(pc) // The file is something like /a/b/c/d.go. We want just the d. if strings.HasSuffix(file, ".go") { file = file[:len(file)-3] } if slash := strings.LastIndex(file, "/"); slash >= 0 { file = file[slash+1:] } for _, filter := range l.vmodule.filter { if filter.match(file) { l.vmap[pc] = filter.level return filter.level } } l.vmap[pc] = 0 return 0 } // Verbose is a boolean type that implements Infof (like Printf) etc. // See the documentation of V for more information. type Verbose struct { enabled bool logr *logr.Logger } func newVerbose(level Level, b bool) Verbose { if logging.logger == nil { return Verbose{b, nil} } v := logging.logger.V(int(level)) return Verbose{b, &v} } // V reports whether verbosity at the call site is at least the requested level. // The returned value is a struct of type Verbose, which implements Info, Infoln // and Infof. These methods will write to the Info log if called. // Thus, one may write either // // if klog.V(2).Enabled() { klog.Info("log this") } // // or // // klog.V(2).Info("log this") // // The second form is shorter but the first is cheaper if logging is off because it does // not evaluate its arguments. // // Whether an individual call to V generates a log record depends on the setting of // the -v and -vmodule flags; both are off by default. The V call will log if its level // is less than or equal to the value of the -v flag, or alternatively if its level is // less than or equal to the value of the -vmodule pattern matching the source file // containing the call. func V(level Level) Verbose { // This function tries hard to be cheap unless there's work to do. // The fast path is two atomic loads and compares. // Here is a cheap but safe test to see if V logging is enabled globally. if logging.verbosity.get() >= level { return newVerbose(level, true) } // It's off globally but vmodule may still be set. // Here is another cheap but safe test to see if vmodule is enabled. if atomic.LoadInt32(&logging.filterLength) > 0 { // Now we need a proper lock to use the logging structure. The pcs field // is shared so we must lock before accessing it. This is fairly expensive, // but if V logging is enabled we're slow anyway. logging.mu.Lock() defer logging.mu.Unlock() if runtime.Callers(2, logging.pcs[:]) == 0 { return newVerbose(level, false) } // runtime.Callers returns "return PCs", but we want // to look up the symbolic information for the call, // so subtract 1 from the PC. runtime.CallersFrames // would be cleaner, but allocates. pc := logging.pcs[0] - 1 v, ok := logging.vmap[pc] if !ok { v = logging.setV(pc) } return newVerbose(level, v >= level) } return newVerbose(level, false) } // Enabled will return true if this log level is enabled, guarded by the value // of v. // See the documentation of V for usage. func (v Verbose) Enabled() bool { return v.enabled } // Info is equivalent to the global Info function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) Info(args ...interface{}) { if v.enabled { logging.print(severity.InfoLog, v.logr, logging.filter, args...) } } // InfoDepth is equivalent to the global InfoDepth function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) InfoDepth(depth int, args ...interface{}) { if v.enabled { logging.printDepth(severity.InfoLog, v.logr, logging.filter, depth, args...) } } // Infoln is equivalent to the global Infoln function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) Infoln(args ...interface{}) { if v.enabled { logging.println(severity.InfoLog, v.logr, logging.filter, args...) } } // InfolnDepth is equivalent to the global InfolnDepth function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) InfolnDepth(depth int, args ...interface{}) { if v.enabled { logging.printlnDepth(severity.InfoLog, v.logr, logging.filter, depth, args...) } } // Infof is equivalent to the global Infof function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) Infof(format string, args ...interface{}) { if v.enabled { logging.printf(severity.InfoLog, v.logr, logging.filter, format, args...) } } // InfofDepth is equivalent to the global InfofDepth function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) InfofDepth(depth int, format string, args ...interface{}) { if v.enabled { logging.printfDepth(severity.InfoLog, v.logr, logging.filter, depth, format, args...) } } // InfoS is equivalent to the global InfoS function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) InfoS(msg string, keysAndValues ...interface{}) { if v.enabled { logging.infoS(v.logr, logging.filter, 0, msg, keysAndValues...) } } // InfoSDepth acts as InfoS but uses depth to determine which call frame to log. // InfoSDepth(0, "msg") is the same as InfoS("msg"). func InfoSDepth(depth int, msg string, keysAndValues ...interface{}) { logging.infoS(logging.logger, logging.filter, depth, msg, keysAndValues...) } // InfoSDepth is equivalent to the global InfoSDepth function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) InfoSDepth(depth int, msg string, keysAndValues ...interface{}) { if v.enabled { logging.infoS(v.logr, logging.filter, depth, msg, keysAndValues...) } } // Deprecated: Use ErrorS instead. func (v Verbose) Error(err error, msg string, args ...interface{}) { if v.enabled { logging.errorS(err, v.logr, logging.filter, 0, msg, args...) } } // ErrorS is equivalent to the global Error function, guarded by the value of v. // See the documentation of V for usage. func (v Verbose) ErrorS(err error, msg string, keysAndValues ...interface{}) { if v.enabled { logging.errorS(err, v.logr, logging.filter, 0, msg, keysAndValues...) } } // Info logs to the INFO log. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. func Info(args ...interface{}) { logging.print(severity.InfoLog, logging.logger, logging.filter, args...) } // InfoDepth acts as Info but uses depth to determine which call frame to log. // InfoDepth(0, "msg") is the same as Info("msg"). func InfoDepth(depth int, args ...interface{}) { logging.printDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...) } // Infoln logs to the INFO log. // Arguments are handled in the manner of fmt.Println; a newline is always appended. func Infoln(args ...interface{}) { logging.println(severity.InfoLog, logging.logger, logging.filter, args...) } // InfolnDepth acts as Infoln but uses depth to determine which call frame to log. // InfolnDepth(0, "msg") is the same as Infoln("msg"). func InfolnDepth(depth int, args ...interface{}) { logging.printlnDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...) } // Infof logs to the INFO log. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. func Infof(format string, args ...interface{}) { logging.printf(severity.InfoLog, logging.logger, logging.filter, format, args...) } // InfofDepth acts as Infof but uses depth to determine which call frame to log. // InfofDepth(0, "msg", args...) is the same as Infof("msg", args...). func InfofDepth(depth int, format string, args ...interface{}) { logging.printfDepth(severity.InfoLog, logging.logger, logging.filter, depth, format, args...) } // InfoS structured logs to the INFO log. // The msg argument used to add constant description to the log line. // The key/value pairs would be join by "=" ; a newline is always appended. // // Basic examples: // >> klog.InfoS("Pod status updated", "pod", "kubedns", "status", "ready") // output: // >> I1025 00:15:15.525108 1 controller_utils.go:116] "Pod status updated" pod="kubedns" status="ready" func InfoS(msg string, keysAndValues ...interface{}) { logging.infoS(logging.logger, logging.filter, 0, msg, keysAndValues...) } // Warning logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. func Warning(args ...interface{}) { logging.print(severity.WarningLog, logging.logger, logging.filter, args...) } // WarningDepth acts as Warning but uses depth to determine which call frame to log. // WarningDepth(0, "msg") is the same as Warning("msg"). func WarningDepth(depth int, args ...interface{}) { logging.printDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...) } // Warningln logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is always appended. func Warningln(args ...interface{}) { logging.println(severity.WarningLog, logging.logger, logging.filter, args...) } // WarninglnDepth acts as Warningln but uses depth to determine which call frame to log. // WarninglnDepth(0, "msg") is the same as Warningln("msg"). func WarninglnDepth(depth int, args ...interface{}) { logging.printlnDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...) } // Warningf logs to the WARNING and INFO logs. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. func Warningf(format string, args ...interface{}) { logging.printf(severity.WarningLog, logging.logger, logging.filter, format, args...) } // WarningfDepth acts as Warningf but uses depth to determine which call frame to log. // WarningfDepth(0, "msg", args...) is the same as Warningf("msg", args...). func WarningfDepth(depth int, format string, args ...interface{}) { logging.printfDepth(severity.WarningLog, logging.logger, logging.filter, depth, format, args...) } // Error logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. func Error(args ...interface{}) { logging.print(severity.ErrorLog, logging.logger, logging.filter, args...) } // ErrorDepth acts as Error but uses depth to determine which call frame to log. // ErrorDepth(0, "msg") is the same as Error("msg"). func ErrorDepth(depth int, args ...interface{}) { logging.printDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...) } // Errorln logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Println; a newline is always appended. func Errorln(args ...interface{}) { logging.println(severity.ErrorLog, logging.logger, logging.filter, args...) } // ErrorlnDepth acts as Errorln but uses depth to determine which call frame to log. // ErrorlnDepth(0, "msg") is the same as Errorln("msg"). func ErrorlnDepth(depth int, args ...interface{}) { logging.printlnDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...) } // Errorf logs to the ERROR, WARNING, and INFO logs. // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. func Errorf(format string, args ...interface{}) { logging.printf(severity.ErrorLog, logging.logger, logging.filter, format, args...) } // ErrorfDepth acts as Errorf but uses depth to determine which call frame to log. // ErrorfDepth(0, "msg", args...) is the same as Errorf("msg", args...). func ErrorfDepth(depth int, format string, args ...interface{}) { logging.printfDepth(severity.ErrorLog, logging.logger, logging.filter, depth, format, args...) } // ErrorS structured logs to the ERROR, WARNING, and INFO logs. // the err argument used as "err" field of log line. // The msg argument used to add constant description to the log line. // The key/value pairs would be join by "=" ; a newline is always appended. // // Basic examples: // >> klog.ErrorS(err, "Failed to update pod status") // output: // >> E1025 00:15:15.525108 1 controller_utils.go:114] "Failed to update pod status" err="timeout" func ErrorS(err error, msg string, keysAndValues ...interface{}) { logging.errorS(err, logging.logger, logging.filter, 0, msg, keysAndValues...) } // ErrorSDepth acts as ErrorS but uses depth to determine which call frame to log. // ErrorSDepth(0, "msg") is the same as ErrorS("msg"). func ErrorSDepth(depth int, err error, msg string, keysAndValues ...interface{}) { logging.errorS(err, logging.logger, logging.filter, depth, msg, keysAndValues...) } // Fatal logs to the FATAL, ERROR, WARNING, and INFO logs, // prints stack trace(s), then calls OsExit(255). // // Stderr only receives a dump of the current goroutine's stack trace. Log files, // if there are any, receive a dump of the stack traces in all goroutines. // // Callers who want more control over handling of fatal events may instead use a // combination of different functions: // - some info or error logging function, optionally with a stack trace // value generated by github.com/go-logr/lib/dbg.Backtrace // - Flush to flush pending log data // - panic, os.Exit or returning to the caller with an error // // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. func Fatal(args ...interface{}) { logging.print(severity.FatalLog, logging.logger, logging.filter, args...) } // FatalDepth acts as Fatal but uses depth to determine which call frame to log. // FatalDepth(0, "msg") is the same as Fatal("msg"). func FatalDepth(depth int, args ...interface{}) { logging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...) } // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, // including a stack trace of all running goroutines, then calls OsExit(255). // Arguments are handled in the manner of fmt.Println; a newline is always appended. func Fatalln(args ...interface{}) { logging.println(severity.FatalLog, logging.logger, logging.filter, args...) } // FatallnDepth acts as Fatalln but uses depth to determine which call frame to log. // FatallnDepth(0, "msg") is the same as Fatalln("msg"). func FatallnDepth(depth int, args ...interface{}) { logging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...) } // Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs, // including a stack trace of all running goroutines, then calls OsExit(255). // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. func Fatalf(format string, args ...interface{}) { logging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...) } // FatalfDepth acts as Fatalf but uses depth to determine which call frame to log. // FatalfDepth(0, "msg", args...) is the same as Fatalf("msg", args...). func FatalfDepth(depth int, format string, args ...interface{}) { logging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...) } // fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks. // It allows Exit and relatives to use the Fatal logs. var fatalNoStacks uint32 // Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1). // Arguments are handled in the manner of fmt.Print; a newline is appended if missing. func Exit(args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.print(severity.FatalLog, logging.logger, logging.filter, args...) } // ExitDepth acts as Exit but uses depth to determine which call frame to log. // ExitDepth(0, "msg") is the same as Exit("msg"). func ExitDepth(depth int, args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...) } // Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1). func Exitln(args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.println(severity.FatalLog, logging.logger, logging.filter, args...) } // ExitlnDepth acts as Exitln but uses depth to determine which call frame to log. // ExitlnDepth(0, "msg") is the same as Exitln("msg"). func ExitlnDepth(depth int, args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...) } // Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1). // Arguments are handled in the manner of fmt.Printf; a newline is appended if missing. func Exitf(format string, args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...) } // ExitfDepth acts as Exitf but uses depth to determine which call frame to log. // ExitfDepth(0, "msg", args...) is the same as Exitf("msg", args...). func ExitfDepth(depth int, format string, args ...interface{}) { atomic.StoreUint32(&fatalNoStacks, 1) logging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...) } // LogFilter is a collection of functions that can filter all logging calls, // e.g. for sanitization of arguments and prevent accidental leaking of secrets. type LogFilter interface { Filter(args []interface{}) []interface{} FilterF(format string, args []interface{}) (string, []interface{}) FilterS(msg string, keysAndValues []interface{}) (string, []interface{}) } // SetLogFilter installs a filter that is used for all log calls. // // Modifying the filter is not thread-safe and should be done while no other // goroutines invoke log calls, usually during program initialization. func SetLogFilter(filter LogFilter) { logging.filter = filter } golang-k8s-klog-2.80.1/klog_file.go000066400000000000000000000071551432555450600170110ustar00rootroot00000000000000// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ // // Copyright 2013 Google Inc. All Rights Reserved. // // 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. // File I/O for logs. package klog import ( "errors" "fmt" "os" "path/filepath" "strings" "sync" "time" ) // MaxSize is the maximum size of a log file in bytes. var MaxSize uint64 = 1024 * 1024 * 1800 // logDirs lists the candidate directories for new log files. var logDirs []string func createLogDirs() { if logging.logDir != "" { logDirs = append(logDirs, logging.logDir) } logDirs = append(logDirs, os.TempDir()) } var ( pid = os.Getpid() program = filepath.Base(os.Args[0]) host = "unknownhost" userName = "unknownuser" userNameOnce sync.Once ) func init() { if h, err := os.Hostname(); err == nil { host = shortHostname(h) } } // shortHostname returns its argument, truncating at the first period. // For instance, given "www.google.com" it returns "www". func shortHostname(hostname string) string { if i := strings.Index(hostname, "."); i >= 0 { return hostname[:i] } return hostname } // logName returns a new log file name containing tag, with start time t, and // the name for the symlink for tag. func logName(tag string, t time.Time) (name, link string) { name = fmt.Sprintf("%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d", program, host, getUserName(), tag, t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), pid) return name, program + "." + tag } var onceLogDirs sync.Once // create creates a new log file and returns the file and its filename, which // contains tag ("INFO", "FATAL", etc.) and t. If the file is created // successfully, create also attempts to update the symlink for that tag, ignoring // errors. // The startup argument indicates whether this is the initial startup of klog. // If startup is true, existing files are opened for appending instead of truncated. func create(tag string, t time.Time, startup bool) (f *os.File, filename string, err error) { if logging.logFile != "" { f, err := openOrCreate(logging.logFile, startup) if err == nil { return f, logging.logFile, nil } return nil, "", fmt.Errorf("log: unable to create log: %v", err) } onceLogDirs.Do(createLogDirs) if len(logDirs) == 0 { return nil, "", errors.New("log: no log dirs") } name, link := logName(tag, t) var lastErr error for _, dir := range logDirs { fname := filepath.Join(dir, name) f, err := openOrCreate(fname, startup) if err == nil { symlink := filepath.Join(dir, link) os.Remove(symlink) // ignore err os.Symlink(name, symlink) // ignore err return f, fname, nil } lastErr = err } return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) } // The startup argument indicates whether this is the initial startup of klog. // If startup is true, existing files are opened for appending instead of truncated. func openOrCreate(name string, startup bool) (*os.File, error) { if startup { f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) return f, err } f, err := os.Create(name) return f, err } golang-k8s-klog-2.80.1/klog_file_others.go000066400000000000000000000003611432555450600203650ustar00rootroot00000000000000//go:build !windows // +build !windows package klog import ( "os/user" ) func getUserName() string { userNameOnce.Do(func() { current, err := user.Current() if err == nil { userName = current.Username } }) return userName } golang-k8s-klog-2.80.1/klog_file_windows.go000066400000000000000000000013151432555450600205530ustar00rootroot00000000000000//go:build windows // +build windows package klog import ( "os" "strings" ) func getUserName() string { userNameOnce.Do(func() { // On Windows, the Go 'user' package requires netapi32.dll. // This affects Windows Nano Server: // https://github.com/golang/go/issues/21867 // Fallback to using environment variables. u := os.Getenv("USERNAME") if len(u) == 0 { return } // Sanitize the USERNAME since it may contain filepath separators. u = strings.Replace(u, `\`, "_", -1) // user.Current().Username normally produces something like 'USERDOMAIN\USERNAME' d := os.Getenv("USERDOMAIN") if len(d) != 0 { userName = d + "_" + u } else { userName = u } }) return userName } golang-k8s-klog-2.80.1/klog_test.go000066400000000000000000001533111432555450600170450ustar00rootroot00000000000000// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/ // // Copyright 2013 Google Inc. All Rights Reserved. // // 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 klog import ( "bytes" "errors" "flag" "fmt" "io/ioutil" stdLog "log" "os" "path/filepath" "reflect" "regexp" "runtime" "strconv" "strings" "sync" "testing" "time" "github.com/go-logr/logr" "k8s.io/klog/v2/internal/buffer" testingclock "k8s.io/klog/v2/internal/clock/testing" "k8s.io/klog/v2/internal/severity" "k8s.io/klog/v2/internal/test" ) // TODO: This test package should be refactored so that tests cannot // interfere with each-other. // Test that shortHostname works as advertised. func TestShortHostname(t *testing.T) { for hostname, expect := range map[string]string{ "": "", "host": "host", "host.google.com": "host", } { if got := shortHostname(hostname); expect != got { t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got) } } } // flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter. type flushBuffer struct { bytes.Buffer } func (f *flushBuffer) Flush() error { return nil } func (f *flushBuffer) Sync() error { return nil } // swap sets the log writers and returns the old array. func (l *loggingT) swap(writers [severity.NumSeverity]flushSyncWriter) (old [severity.NumSeverity]flushSyncWriter) { l.mu.Lock() defer l.mu.Unlock() old = l.file for i, w := range writers { logging.file[i] = w } return } // newBuffers sets the log writers to all new byte buffers and returns the old array. func (l *loggingT) newBuffers() [severity.NumSeverity]flushSyncWriter { return l.swap([severity.NumSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)}) } // contents returns the specified log value as a string. func contents(s severity.Severity) string { return logging.file[s].(*flushBuffer).String() } // contains reports whether the string is contained in the log. func contains(s severity.Severity, str string, t *testing.T) bool { return strings.Contains(contents(s), str) } // setFlags configures the logging flags how the test expects them. func setFlags() { logging.toStderr = false logging.addDirHeader = false } // Test that Info works as advertised. func TestInfo(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) Info("test") if !contains(severity.InfoLog, "I", t) { t.Errorf("Info has wrong character: %q", contents(severity.InfoLog)) } if !contains(severity.InfoLog, "test", t) { t.Error("Info failed") } } func TestInfoDepth(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) f := func() { InfoDepth(1, "depth-test1") } // The next three lines must stay together _, _, wantLine, _ := runtime.Caller(0) InfoDepth(0, "depth-test0") f() msgs := strings.Split(strings.TrimSuffix(contents(severity.InfoLog), "\n"), "\n") if len(msgs) != 2 { t.Fatalf("Got %d lines, expected 2", len(msgs)) } for i, m := range msgs { if !strings.HasPrefix(m, "I") { t.Errorf("InfoDepth[%d] has wrong character: %q", i, m) } w := fmt.Sprintf("depth-test%d", i) if !strings.Contains(m, w) { t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m) } // pull out the line number (between : and ]) msg := m[strings.LastIndex(m, ":")+1:] x := strings.Index(msg, "]") if x < 0 { t.Errorf("InfoDepth[%d]: missing ']': %q", i, m) continue } line, err := strconv.Atoi(msg[:x]) if err != nil { t.Errorf("InfoDepth[%d]: bad line number: %q", i, m) continue } wantLine++ if wantLine != line { t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine) } } } func init() { CopyStandardLogTo("INFO") } // Test that CopyStandardLogTo panics on bad input. func TestCopyStandardLogToPanic(t *testing.T) { defer func() { if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") { t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s) } }() CopyStandardLogTo("LOG") } // Test that using the standard log package logs to INFO. func TestStandardLog(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) stdLog.Print("test") if !contains(severity.InfoLog, "I", t) { t.Errorf("Info has wrong character: %q", contents(severity.InfoLog)) } if !contains(severity.InfoLog, "test", t) { t.Error("Info failed") } } // Test that the header has the correct format. func TestHeader(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) defer func(previous func() time.Time) { timeNow = previous }(timeNow) timeNow = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } buffer.Pid = 1234 Info("test") var line int format := "I0102 15:04:05.067890 1234 klog_test.go:%d] test\n" n, err := fmt.Sscanf(contents(severity.InfoLog), format, &line) if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(severity.InfoLog)) } // Scanf treats multiple spaces as equivalent to a single space, // so check for correct space-padding also. want := fmt.Sprintf(format, line) if contents(severity.InfoLog) != want { t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(severity.InfoLog), want) } } func TestHeaderWithDir(t *testing.T) { defer CaptureState().Restore() setFlags() logging.addDirHeader = true defer logging.swap(logging.newBuffers()) defer func(previous func() time.Time) { timeNow = previous }(timeNow) timeNow = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } pid = 1234 Info("test") re := regexp.MustCompile(`I0102 15:04:05.067890 1234 (klog|v2)/klog_test.go:(\d+)] test\n`) if !re.MatchString(contents(severity.InfoLog)) { t.Errorf("log format error: line does not match regex:\n\t%q\n", contents(severity.InfoLog)) } } // Test that an Error log goes to Warning and Info. // Even in the Info log, the source character will be E, so the data should // all be identical. func TestError(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) Error("test") if !contains(severity.ErrorLog, "E", t) { t.Errorf("Error has wrong character: %q", contents(severity.ErrorLog)) } if !contains(severity.ErrorLog, "test", t) { t.Error("Error failed") } str := contents(severity.ErrorLog) if !contains(severity.WarningLog, str, t) { t.Error("Warning failed") } if !contains(severity.InfoLog, str, t) { t.Error("Info failed") } } // Test that an Error log does not goes to Warning and Info. // Even in the Info log, the source character will be E, so the data should // all be identical. func TestErrorWithOneOutput(t *testing.T) { defer CaptureState().Restore() setFlags() logging.oneOutput = true defer logging.swap(logging.newBuffers()) Error("test") if !contains(severity.ErrorLog, "E", t) { t.Errorf("Error has wrong character: %q", contents(severity.ErrorLog)) } if !contains(severity.ErrorLog, "test", t) { t.Error("Error failed") } str := contents(severity.ErrorLog) if contains(severity.WarningLog, str, t) { t.Error("Warning failed") } if contains(severity.InfoLog, str, t) { t.Error("Info failed") } } // Test that a Warning log goes to Info. // Even in the Info log, the source character will be W, so the data should // all be identical. func TestWarning(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) Warning("test") if !contains(severity.WarningLog, "W", t) { t.Errorf("Warning has wrong character: %q", contents(severity.WarningLog)) } if !contains(severity.WarningLog, "test", t) { t.Error("Warning failed") } str := contents(severity.WarningLog) if !contains(severity.InfoLog, str, t) { t.Error("Info failed") } } // Test that a Warning log does not goes to Info. // Even in the Info log, the source character will be W, so the data should // all be identical. func TestWarningWithOneOutput(t *testing.T) { defer CaptureState().Restore() setFlags() logging.oneOutput = true defer logging.swap(logging.newBuffers()) Warning("test") if !contains(severity.WarningLog, "W", t) { t.Errorf("Warning has wrong character: %q", contents(severity.WarningLog)) } if !contains(severity.WarningLog, "test", t) { t.Error("Warning failed") } str := contents(severity.WarningLog) if contains(severity.InfoLog, str, t) { t.Error("Info failed") } } // Test that a V log goes to Info. func TestV(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) logging.verbosity.Set("2") V(2).Info("test") if !contains(severity.InfoLog, "I", t) { t.Errorf("Info has wrong character: %q", contents(severity.InfoLog)) } if !contains(severity.InfoLog, "test", t) { t.Error("Info failed") } } // Test that a vmodule enables a log in this file. func TestVmoduleOn(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) logging.vmodule.Set("klog_test=2") if !V(1).Enabled() { t.Error("V not enabled for 1") } if !V(2).Enabled() { t.Error("V not enabled for 2") } if V(3).Enabled() { t.Error("V enabled for 3") } V(2).Info("test") if !contains(severity.InfoLog, "I", t) { t.Errorf("Info has wrong character: %q", contents(severity.InfoLog)) } if !contains(severity.InfoLog, "test", t) { t.Error("Info failed") } } // Test that a vmodule of another file does not enable a log in this file. func TestVmoduleOff(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) logging.vmodule.Set("notthisfile=2") for i := 1; i <= 3; i++ { if V(Level(i)).Enabled() { t.Errorf("V enabled for %d", i) } } V(2).Info("test") if contents(severity.InfoLog) != "" { t.Error("V logged incorrectly") } } func TestSetOutputDataRace(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) var wg sync.WaitGroup var daemons []*flushDaemon for i := 1; i <= 50; i++ { daemon := newFlushDaemon(logging.lockAndFlushAll, nil) daemon.run(time.Second) daemons = append(daemons, daemon) } for i := 1; i <= 50; i++ { wg.Add(1) go func() { defer wg.Done() SetOutput(ioutil.Discard) }() } for i := 1; i <= 50; i++ { daemon := newFlushDaemon(logging.lockAndFlushAll, nil) daemon.run(time.Second) daemons = append(daemons, daemon) } for i := 1; i <= 50; i++ { wg.Add(1) go func() { defer wg.Done() SetOutputBySeverity("INFO", ioutil.Discard) }() } for i := 1; i <= 50; i++ { daemon := newFlushDaemon(logging.lockAndFlushAll, nil) daemon.run(time.Second) daemons = append(daemons, daemon) } wg.Wait() for _, d := range daemons { d.stop() } } func TestLogToOutput(t *testing.T) { defer CaptureState().Restore() logging.toStderr = true defer logging.swap(logging.newBuffers()) buf := new(bytes.Buffer) SetOutput(buf) LogToStderr(false) Info("Does logging to an output work?") str := buf.String() if !strings.Contains(str, "Does logging to an output work?") { t.Fatalf("Expected %q to contain \"Does logging to an output work?\"", str) } } // vGlobs are patterns that match/don't match this file at V=2. var vGlobs = map[string]bool{ // Easy to test the numeric match here. "klog_test=1": false, // If -vmodule sets V to 1, V(2) will fail. "klog_test=2": true, "klog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed. // These all use 2 and check the patterns. All are true. "*=2": true, "?l*=2": true, "????_*=2": true, "??[mno]?_*t=2": true, // These all use 2 and check the patterns. All are false. "*x=2": false, "m*=2": false, "??_*=2": false, "?[abc]?_*t=2": false, } // Test that vmodule globbing works as advertised. func testVmoduleGlob(pat string, match bool, t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) logging.vmodule.Set(pat) if V(2).Enabled() != match { t.Errorf("incorrect match for %q: got %#v expected %#v", pat, V(2), match) } } // Test that a vmodule globbing works as advertised. func TestVmoduleGlob(t *testing.T) { for glob, match := range vGlobs { testVmoduleGlob(glob, match, t) } } func TestRollover(t *testing.T) { defer CaptureState().Restore() setFlags() var err error defer func(previous func(error)) { logExitFunc = previous }(logExitFunc) logExitFunc = func(e error) { err = e } MaxSize = 512 Info("x") // Be sure we have a file. info, ok := logging.file[severity.InfoLog].(*syncBuffer) if !ok { t.Fatal("info wasn't created") } if err != nil { t.Fatalf("info has initial error: %v", err) } fname0 := info.file.Name() Info(strings.Repeat("x", int(MaxSize))) // force a rollover if err != nil { t.Fatalf("info has error after big write: %v", err) } // Make sure the next log file gets a file name with a different // time stamp. // // TODO: determine whether we need to support subsecond log // rotation. C++ does not appear to handle this case (nor does it // handle Daylight Savings Time properly). time.Sleep(1 * time.Second) Info("x") // create a new file if err != nil { t.Fatalf("error after rotation: %v", err) } fname1 := info.file.Name() if fname0 == fname1 { t.Errorf("info.f.Name did not change: %v", fname0) } if info.nbytes >= info.maxbytes { t.Errorf("file size was not reset: %d", info.nbytes) } } func TestOpenAppendOnStart(t *testing.T) { const ( x string = "xxxxxxxxxx" y string = "yyyyyyyyyy" ) defer CaptureState().Restore() setFlags() var err error defer func(previous func(error)) { logExitFunc = previous }(logExitFunc) logExitFunc = func(e error) { err = e } f, err := ioutil.TempFile("", "test_klog_OpenAppendOnStart") if err != nil { t.Fatalf("unexpected error: %v", err) } defer os.Remove(f.Name()) logging.logFile = f.Name() // Erase files created by prior tests, for i := range logging.file { logging.file[i] = nil } // Logging creates the file Info(x) _, ok := logging.file[severity.InfoLog].(*syncBuffer) if !ok { t.Fatal("info wasn't created") } // ensure we wrote what we expected logging.flushAll() b, err := ioutil.ReadFile(logging.logFile) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(string(b), x) { t.Fatalf("got %s, missing expected Info log: %s", string(b), x) } // Set the file to nil so it gets "created" (opened) again on the next write. for i := range logging.file { logging.file[i] = nil } // Logging again should open the file again with O_APPEND instead of O_TRUNC Info(y) // ensure we wrote what we expected logging.lockAndFlushAll() b, err = ioutil.ReadFile(logging.logFile) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(string(b), y) { t.Fatalf("got %s, missing expected Info log: %s", string(b), y) } // The initial log message should be preserved across create calls. logging.lockAndFlushAll() b, err = ioutil.ReadFile(logging.logFile) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(string(b), x) { t.Fatalf("got %s, missing expected Info log: %s", string(b), x) } } func TestLogBacktraceAt(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) // The peculiar style of this code simplifies line counting and maintenance of the // tracing block below. var infoLine string setTraceLocation := func(file string, line int, ok bool, delta int) { if !ok { t.Fatal("could not get file:line") } _, file = filepath.Split(file) infoLine = fmt.Sprintf("%s:%d", file, line+delta) err := logging.traceLocation.Set(infoLine) if err != nil { t.Fatal("error setting log_backtrace_at: ", err) } } { // Start of tracing block. These lines know about each other's relative position. _, file, line, ok := runtime.Caller(0) setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls. Info("we want a stack trace here") } numAppearances := strings.Count(contents(severity.InfoLog), infoLine) if numAppearances < 2 { // Need 2 appearances, one in the log header and one in the trace: // log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here // ... // k8s.io/klog/klog_test.go:280 (0x41ba91) // ... // We could be more precise but that would require knowing the details // of the traceback format, which may not be dependable. t.Fatal("got no trace back; log is ", contents(severity.InfoLog)) } } func BenchmarkHeader(b *testing.B) { for i := 0; i < b.N; i++ { buf, _, _ := logging.header(severity.InfoLog, 0) logging.bufferCache.PutBuffer(buf) } } func BenchmarkHeaderWithDir(b *testing.B) { logging.addDirHeader = true for i := 0; i < b.N; i++ { buf, _, _ := logging.header(severity.InfoLog, 0) logging.bufferCache.PutBuffer(buf) } } // Ensure that benchmarks have side effects to avoid compiler optimization var result interface{} var enabled bool func BenchmarkV(b *testing.B) { var v Verbose for i := 0; i < b.N; i++ { v = V(10) } enabled = v.Enabled() } func BenchmarkKRef(b *testing.B) { var r ObjectRef for i := 0; i < b.N; i++ { r = KRef("namespace", "name") } result = r } func BenchmarkKObj(b *testing.B) { a := test.KMetadataMock{Name: "a", NS: "a"} var r ObjectRef for i := 0; i < b.N; i++ { r = KObj(&a) } result = r } // BenchmarkKObjs measures the (pretty typical) case // where KObjs is used in a V(5).InfoS call that never // emits a log entry because verbosity is lower than 5. // For performance when the result of KObjs gets formatted, // see examples/benchmarks. func BenchmarkKObjs(b *testing.B) { for length := 0; length <= 100; length += 10 { b.Run(fmt.Sprintf("%d", length), func(b *testing.B) { arg := make([]interface{}, length) for i := 0; i < length; i++ { arg[i] = test.KMetadataMock{Name: "a", NS: "a"} } b.ResetTimer() var r interface{} for i := 0; i < b.N; i++ { r = KObjs(arg) } result = r }) } } func BenchmarkLogs(b *testing.B) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) testFile, err := ioutil.TempFile("", "test.log") if err != nil { b.Fatal("unable to create temporary file") } defer os.Remove(testFile.Name()) logging.verbosity.Set("0") logging.toStderr = false logging.alsoToStderr = false logging.stderrThreshold = severityValue{ Severity: severity.FatalLog, } logging.logFile = testFile.Name() logging.swap([severity.NumSeverity]flushSyncWriter{nil, nil, nil, nil}) for i := 0; i < b.N; i++ { Error("error") Warning("warning") Info("info") } logging.flushAll() } // Test the logic on checking log size limitation. func TestFileSizeCheck(t *testing.T) { defer CaptureState().Restore() setFlags() testData := map[string]struct { testLogFile string testLogFileMaxSizeMB uint64 testCurrentSize uint64 expectedResult bool }{ "logFile not specified, exceeds max size": { testLogFile: "", testLogFileMaxSizeMB: 1, testCurrentSize: 1024 * 1024 * 2000, //exceeds the maxSize expectedResult: true, }, "logFile not specified, not exceeds max size": { testLogFile: "", testLogFileMaxSizeMB: 1, testCurrentSize: 1024 * 1024 * 1000, //smaller than the maxSize expectedResult: false, }, "logFile specified, exceeds max size": { testLogFile: "/tmp/test.log", testLogFileMaxSizeMB: 500, // 500MB testCurrentSize: 1024 * 1024 * 1000, //exceeds the logFileMaxSizeMB expectedResult: true, }, "logFile specified, not exceeds max size": { testLogFile: "/tmp/test.log", testLogFileMaxSizeMB: 500, // 500MB testCurrentSize: 1024 * 1024 * 300, //smaller than the logFileMaxSizeMB expectedResult: false, }, } for name, test := range testData { logging.logFile = test.testLogFile logging.logFileMaxSizeMB = test.testLogFileMaxSizeMB actualResult := test.testCurrentSize >= CalculateMaxSize() if test.expectedResult != actualResult { t.Fatalf("Error on test case '%v': Was expecting result equals %v, got %v", name, test.expectedResult, actualResult) } } } func TestInitFlags(t *testing.T) { defer CaptureState().Restore() setFlags() fs1 := flag.NewFlagSet("test1", flag.PanicOnError) InitFlags(fs1) fs1.Set("log_dir", "/test1") fs1.Set("log_file_max_size", "1") fs2 := flag.NewFlagSet("test2", flag.PanicOnError) InitFlags(fs2) if logging.logDir != "/test1" { t.Fatalf("Expected log_dir to be %q, got %q", "/test1", logging.logDir) } fs2.Set("log_file_max_size", "2048") if logging.logFileMaxSizeMB != 2048 { t.Fatal("Expected log_file_max_size to be 2048") } } func TestCommandLine(t *testing.T) { var fs flag.FlagSet InitFlags(&fs) expectedFlags := ` -add_dir_header If true, adds the file directory to the header of the log messages -alsologtostderr log to standard error as well as files (no effect when -logtostderr=true) -log_backtrace_at value when logging hits line file:N, emit a stack trace -log_dir string If non-empty, write log files in this directory (no effect when -logtostderr=true) -log_file string If non-empty, use this log file (no effect when -logtostderr=true) -log_file_max_size uint Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) -logtostderr log to standard error instead of files (default true) -one_output If true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true) -skip_headers If true, avoid header prefixes in the log messages -skip_log_headers If true, avoid headers when opening log files (no effect when -logtostderr=true) -stderrthreshold value logs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=false) (default 2) -v value number for the log level verbosity -vmodule value comma-separated list of pattern=N settings for file-filtered logging ` var output bytes.Buffer fs.SetOutput(&output) fs.PrintDefaults() actualFlags := output.String() if expectedFlags != actualFlags { t.Fatalf("Command line changed.\nExpected:\n%q\nActual:\n%q\n", expectedFlags, actualFlags) } } func TestInfoObjectRef(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) tests := []struct { name string ref ObjectRef want string }{ { name: "with ns", ref: ObjectRef{ Name: "test-name", Namespace: "test-ns", }, want: "test-ns/test-name", }, { name: "without ns", ref: ObjectRef{ Name: "test-name", Namespace: "", }, want: "test-name", }, { name: "empty", ref: ObjectRef{}, want: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { Info(tt.ref) if !contains(severity.InfoLog, tt.want, t) { t.Errorf("expected %v, got %v", tt.want, contents(severity.InfoLog)) } }) } } func TestKObj(t *testing.T) { tests := []struct { name string obj KMetadata want ObjectRef }{ { name: "nil passed as pointer KMetadata implementation", obj: (*test.PtrKMetadataMock)(nil), want: ObjectRef{}, }, { name: "empty struct passed as non-pointer KMetadata implementation", obj: test.KMetadataMock{}, want: ObjectRef{}, }, { name: "nil pointer passed to non-pointer KMetadata implementation", obj: (*test.KMetadataMock)(nil), want: ObjectRef{}, }, { name: "nil", obj: nil, want: ObjectRef{}, }, { name: "with ns", obj: &test.KMetadataMock{Name: "test-name", NS: "test-ns"}, want: ObjectRef{ Name: "test-name", Namespace: "test-ns", }, }, { name: "without ns", obj: &test.KMetadataMock{Name: "test-name", NS: ""}, want: ObjectRef{ Name: "test-name", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if KObj(tt.obj) != tt.want { t.Errorf("expected %v, got %v", tt.want, KObj(tt.obj)) } }) } } func TestKRef(t *testing.T) { tests := []struct { testname string name string namespace string want ObjectRef }{ { testname: "with ns", name: "test-name", namespace: "test-ns", want: ObjectRef{ Name: "test-name", Namespace: "test-ns", }, }, { testname: "without ns", name: "test-name", want: ObjectRef{ Name: "test-name", }, }, } for _, tt := range tests { t.Run(tt.testname, func(t *testing.T) { if KRef(tt.namespace, tt.name) != tt.want { t.Errorf("expected %v, got %v", tt.want, KRef(tt.namespace, tt.name)) } }) } } // Test that InfoS and InfoSDepth work as advertised. func TestInfoS(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) timeNow = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } pid = 1234 var testDataInfo = []struct { msg string format string keysValues []interface{} }{ { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" pod=\"kubedns\"\n", keysValues: []interface{}{"pod", "kubedns"}, }, { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" replicaNum=20\n", keysValues: []interface{}{"replicaNum", 20}, }, { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" err=\"test error\"\n", keysValues: []interface{}{"err", errors.New("test error")}, }, { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" err=\"test error\"\n", keysValues: []interface{}{"err", errors.New("test error")}, }, } functions := []func(msg string, keyAndValues ...interface{}){ InfoS, myInfoS, } for _, f := range functions { for _, data := range testDataInfo { logging.file[severity.InfoLog] = &flushBuffer{} f(data.msg, data.keysValues...) var line int n, err := fmt.Sscanf(contents(severity.InfoLog), data.format, &line) if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(severity.InfoLog)) } want := fmt.Sprintf(data.format, line) if contents(severity.InfoLog) != want { t.Errorf("InfoS has wrong format: \n got:\t%s\nwant:\t%s", contents(severity.InfoLog), want) } } } } // Test that Verbose.InfoS works as advertised. func TestVInfoS(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) timeNow = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } pid = 1234 myData := struct { Data string }{ Data: `This is some long text with a line break.`, } var testDataInfo = []struct { msg string format string keysValues []interface{} }{ { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" pod=\"kubedns\"\n", keysValues: []interface{}{"pod", "kubedns"}, }, { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" replicaNum=20\n", keysValues: []interface{}{"replicaNum", 20}, }, { msg: "test", format: "I0102 15:04:05.067890 1234 klog_test.go:%d] \"test\" err=\"test error\"\n", keysValues: []interface{}{"err", errors.New("test error")}, }, { msg: `first message line second message line`, format: `I0102 15:04:05.067890 1234 klog_test.go:%d] "first message line\nsecond message line" multiLine=< first value line second value line > `, keysValues: []interface{}{"multiLine", `first value line second value line`}, }, { msg: `message`, format: `I0102 15:04:05.067890 1234 klog_test.go:%d] "message" myData=< {Data:This is some long text with a line break.} > `, keysValues: []interface{}{"myData", myData}, }, } logging.verbosity.Set("2") for l := Level(0); l < Level(4); l++ { for _, data := range testDataInfo { logging.file[severity.InfoLog] = &flushBuffer{} V(l).InfoS(data.msg, data.keysValues...) var want string var line int if l <= 2 { n, err := fmt.Sscanf(contents(severity.InfoLog), data.format, &line) if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(severity.InfoLog)) } want = fmt.Sprintf(data.format, line) } else { want = "" } if contents(severity.InfoLog) != want { t.Errorf("V(%d).InfoS has unexpected output:\ngot:\n%s\nwant:\n%s\n", l, contents(severity.InfoLog), want) } } } } // Test that ErrorS and ErrorSDepth work as advertised. func TestErrorS(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) timeNow = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local) } logging.logFile = "" pid = 1234 functions := []func(err error, msg string, keyAndValues ...interface{}){ ErrorS, myErrorS, } for _, f := range functions { var errorList = []struct { err error format string }{ { err: fmt.Errorf("update status failed"), format: "E0102 15:04:05.067890 1234 klog_test.go:%d] \"Failed to update pod status\" err=\"update status failed\" pod=\"kubedns\"\n", }, { err: nil, format: "E0102 15:04:05.067890 1234 klog_test.go:%d] \"Failed to update pod status\" pod=\"kubedns\"\n", }, } for _, e := range errorList { logging.file[severity.ErrorLog] = &flushBuffer{} f(e.err, "Failed to update pod status", "pod", "kubedns") var line int n, err := fmt.Sscanf(contents(severity.ErrorLog), e.format, &line) if n != 1 || err != nil { t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(severity.ErrorLog)) } want := fmt.Sprintf(e.format, line) if contents(severity.ErrorLog) != want { t.Errorf("ErrorS has wrong format:\ngot:\n%s\nwant:\n%s\n", contents(severity.ErrorLog), want) } } } } func createTestValueOfLoggingT() *loggingT { l := new(loggingT) l.toStderr = true l.alsoToStderr = false l.stderrThreshold = severityValue{ Severity: severity.ErrorLog, } l.verbosity = Level(0) l.skipHeaders = false l.skipLogHeaders = false l.addDirHeader = false return l } func createTestValueOfModulePat(p string, li bool, le Level) modulePat { m := modulePat{} m.pattern = p m.literal = li m.level = le return m } func compareModuleSpec(a, b moduleSpec) bool { if len(a.filter) != len(b.filter) { return false } for i := 0; i < len(a.filter); i++ { if a.filter[i] != b.filter[i] { return false } } return true } func TestSetVState(t *testing.T) { //Target loggingT value want := createTestValueOfLoggingT() want.verbosity = Level(3) want.vmodule.filter = []modulePat{ createTestValueOfModulePat("recordio", true, Level(2)), createTestValueOfModulePat("file", true, Level(1)), createTestValueOfModulePat("gfs*", false, Level(3)), createTestValueOfModulePat("gopher*", false, Level(3)), } want.filterLength = 4 //loggingT value to which test is run target := createTestValueOfLoggingT() tf := []modulePat{ createTestValueOfModulePat("recordio", true, Level(2)), createTestValueOfModulePat("file", true, Level(1)), createTestValueOfModulePat("gfs*", false, Level(3)), createTestValueOfModulePat("gopher*", false, Level(3)), } target.setVState(Level(3), tf, true) if want.verbosity != target.verbosity || !compareModuleSpec(want.vmodule, target.vmodule) || want.filterLength != target.filterLength { t.Errorf("setVState method doesn't configure loggingT values' verbosity, vmodule or filterLength:\nwant:\n\tverbosity:\t%v\n\tvmodule:\t%v\n\tfilterLength:\t%v\ngot:\n\tverbosity:\t%v\n\tvmodule:\t%v\n\tfilterLength:\t%v", want.verbosity, want.vmodule, want.filterLength, target.verbosity, target.vmodule, target.filterLength) } } type sampleLogFilter struct{} func (f *sampleLogFilter) Filter(args []interface{}) []interface{} { for i, arg := range args { v, ok := arg.(string) if ok && strings.Contains(v, "filter me") { args[i] = "[FILTERED]" } } return args } func (f *sampleLogFilter) FilterF(format string, args []interface{}) (string, []interface{}) { return strings.Replace(format, "filter me", "[FILTERED]", 1), f.Filter(args) } func (f *sampleLogFilter) FilterS(msg string, keysAndValues []interface{}) (string, []interface{}) { return strings.Replace(msg, "filter me", "[FILTERED]", 1), f.Filter(keysAndValues) } func TestLogFilter(t *testing.T) { defer CaptureState().Restore() setFlags() defer logging.swap(logging.newBuffers()) SetLogFilter(&sampleLogFilter{}) funcs := []struct { name string logFunc func(args ...interface{}) severity severity.Severity }{{ name: "Info", logFunc: Info, severity: severity.InfoLog, }, { name: "InfoDepth", logFunc: func(args ...interface{}) { InfoDepth(1, args...) }, severity: severity.InfoLog, }, { name: "Infoln", logFunc: Infoln, severity: severity.InfoLog, }, { name: "Infof", logFunc: func(args ...interface{}) { Infof(args[0].(string), args[1:]...) }, severity: severity.InfoLog, }, { name: "InfoS", logFunc: func(args ...interface{}) { InfoS(args[0].(string), args[1:]...) }, severity: severity.InfoLog, }, { name: "Warning", logFunc: Warning, severity: severity.WarningLog, }, { name: "WarningDepth", logFunc: func(args ...interface{}) { WarningDepth(1, args...) }, severity: severity.WarningLog, }, { name: "Warningln", logFunc: Warningln, severity: severity.WarningLog, }, { name: "Warningf", logFunc: func(args ...interface{}) { Warningf(args[0].(string), args[1:]...) }, severity: severity.WarningLog, }, { name: "Error", logFunc: Error, severity: severity.ErrorLog, }, { name: "ErrorDepth", logFunc: func(args ...interface{}) { ErrorDepth(1, args...) }, severity: severity.ErrorLog, }, { name: "Errorln", logFunc: Errorln, severity: severity.ErrorLog, }, { name: "Errorf", logFunc: func(args ...interface{}) { Errorf(args[0].(string), args[1:]...) }, severity: severity.ErrorLog, }, { name: "ErrorS", logFunc: func(args ...interface{}) { ErrorS(errors.New("testerror"), args[0].(string), args[1:]...) }, severity: severity.ErrorLog, }, { name: "V().Info", logFunc: func(args ...interface{}) { V(0).Info(args...) }, severity: severity.InfoLog, }, { name: "V().Infoln", logFunc: func(args ...interface{}) { V(0).Infoln(args...) }, severity: severity.InfoLog, }, { name: "V().Infof", logFunc: func(args ...interface{}) { V(0).Infof(args[0].(string), args[1:]...) }, severity: severity.InfoLog, }, { name: "V().InfoS", logFunc: func(args ...interface{}) { V(0).InfoS(args[0].(string), args[1:]...) }, severity: severity.InfoLog, }, { name: "V().Error", logFunc: func(args ...interface{}) { V(0).Error(errors.New("test error"), args[0].(string), args[1:]...) }, severity: severity.ErrorLog, }, { name: "V().ErrorS", logFunc: func(args ...interface{}) { V(0).ErrorS(errors.New("test error"), args[0].(string), args[1:]...) }, severity: severity.ErrorLog, }} testcases := []struct { name string args []interface{} expectFiltered bool }{{ args: []interface{}{"%s:%s", "foo", "bar"}, expectFiltered: false, }, { args: []interface{}{"%s:%s", "foo", "filter me"}, expectFiltered: true, }, { args: []interface{}{"filter me %s:%s", "foo", "bar"}, expectFiltered: true, }} for _, f := range funcs { for _, tc := range testcases { logging.newBuffers() f.logFunc(tc.args...) got := contains(f.severity, "[FILTERED]", t) if got != tc.expectFiltered { t.Errorf("%s filter application failed, got %v, want %v", f.name, got, tc.expectFiltered) } } } } func TestInfoWithLogr(t *testing.T) { logger := new(testLogr) testDataInfo := []struct { msg string expected testLogrEntry }{{ msg: "foo", expected: testLogrEntry{ severity: severity.InfoLog, msg: "foo\n", }, }, { msg: "", expected: testLogrEntry{ severity: severity.InfoLog, msg: "\n", }, }} for _, data := range testDataInfo { t.Run(data.msg, func(t *testing.T) { l := logr.New(logger) defer CaptureState().Restore() SetLogger(l) defer logger.reset() Info(data.msg) if !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) { t.Errorf("expected: %+v; but got: %+v", []testLogrEntry{data.expected}, logger.entries) } }) } } func TestInfoSWithLogr(t *testing.T) { logger := new(testLogr) testDataInfo := []struct { msg string keysValues []interface{} expected testLogrEntry }{{ msg: "foo", keysValues: []interface{}{}, expected: testLogrEntry{ severity: severity.InfoLog, msg: "foo", keysAndValues: []interface{}{}, }, }, { msg: "bar", keysValues: []interface{}{"a", 1}, expected: testLogrEntry{ severity: severity.InfoLog, msg: "bar", keysAndValues: []interface{}{"a", 1}, }, }} for _, data := range testDataInfo { t.Run(data.msg, func(t *testing.T) { defer CaptureState().Restore() l := logr.New(logger) SetLogger(l) defer logger.reset() InfoS(data.msg, data.keysValues...) if !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) { t.Errorf("expected: %+v; but got: %+v", []testLogrEntry{data.expected}, logger.entries) } }) } } func TestErrorSWithLogr(t *testing.T) { logger := new(testLogr) testError := errors.New("testError") testDataInfo := []struct { err error msg string keysValues []interface{} expected testLogrEntry }{{ err: testError, msg: "foo1", keysValues: []interface{}{}, expected: testLogrEntry{ severity: severity.ErrorLog, msg: "foo1", keysAndValues: []interface{}{}, err: testError, }, }, { err: testError, msg: "bar1", keysValues: []interface{}{"a", 1}, expected: testLogrEntry{ severity: severity.ErrorLog, msg: "bar1", keysAndValues: []interface{}{"a", 1}, err: testError, }, }, { err: nil, msg: "foo2", keysValues: []interface{}{}, expected: testLogrEntry{ severity: severity.ErrorLog, msg: "foo2", keysAndValues: []interface{}{}, err: nil, }, }, { err: nil, msg: "bar2", keysValues: []interface{}{"a", 1}, expected: testLogrEntry{ severity: severity.ErrorLog, msg: "bar2", keysAndValues: []interface{}{"a", 1}, err: nil, }, }} for _, data := range testDataInfo { t.Run(data.msg, func(t *testing.T) { defer CaptureState().Restore() l := logr.New(logger) SetLogger(l) defer logger.reset() ErrorS(data.err, data.msg, data.keysValues...) if !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) { t.Errorf("expected: %+v; but got: %+v", []testLogrEntry{data.expected}, logger.entries) } }) } } func TestCallDepthLogr(t *testing.T) { logger := &callDepthTestLogr{} logger.resetCallDepth() testCases := []struct { name string logFn func() }{ { name: "Info log", logFn: func() { Info("info log") }, }, { name: "InfoDepth log", logFn: func() { InfoDepth(0, "infodepth log") }, }, { name: "InfoSDepth log", logFn: func() { InfoSDepth(0, "infoSDepth log") }, }, { name: "Warning log", logFn: func() { Warning("warning log") }, }, { name: "WarningDepth log", logFn: func() { WarningDepth(0, "warningdepth log") }, }, { name: "Error log", logFn: func() { Error("error log") }, }, { name: "ErrorDepth log", logFn: func() { ErrorDepth(0, "errordepth log") }, }, { name: "ErrorSDepth log", logFn: func() { ErrorSDepth(0, errors.New("some error"), "errorSDepth log") }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { l := logr.New(logger) defer ClearLogger() SetLogger(l) defer logger.reset() defer logger.resetCallDepth() // Keep these lines together. _, wantFile, wantLine, _ := runtime.Caller(0) tc.logFn() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) }) } } func TestCallDepthLogrInfoS(t *testing.T) { logger := &callDepthTestLogr{} logger.resetCallDepth() l := logr.New(logger) defer CaptureState().Restore() SetLogger(l) // Add wrapper to ensure callDepthTestLogr +2 offset is correct. logFunc := func() { InfoS("infoS log") } // Keep these lines together. _, wantFile, wantLine, _ := runtime.Caller(0) logFunc() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) } func TestCallDepthLogrErrorS(t *testing.T) { logger := &callDepthTestLogr{} logger.resetCallDepth() l := logr.New(logger) defer CaptureState().Restore() SetLogger(l) // Add wrapper to ensure callDepthTestLogr +2 offset is correct. logFunc := func() { ErrorS(errors.New("some error"), "errorS log") } // Keep these lines together. _, wantFile, wantLine, _ := runtime.Caller(0) logFunc() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) } func TestCallDepthLogrGoLog(t *testing.T) { defer CaptureState().Restore() logger := &callDepthTestLogr{} logger.resetCallDepth() l := logr.New(logger) SetLogger(l) CopyStandardLogTo("INFO") // Add wrapper to ensure callDepthTestLogr +2 offset is correct. logFunc := func() { stdLog.Print("some log") } // Keep these lines together. _, wantFile, wantLine, _ := runtime.Caller(0) logFunc() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) fmt.Println(logger.entries[0]) } // Test callDepthTestLogr logs the expected offsets. func TestCallDepthTestLogr(t *testing.T) { logger := &callDepthTestLogr{} logger.resetCallDepth() logFunc := func() { logger.Info(0, "some info log") } // Keep these lines together. _, wantFile, wantLine, _ := runtime.Caller(0) logFunc() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) logger.reset() logFunc = func() { logger.Error(errors.New("error"), "some error log") } // Keep these lines together. _, wantFile, wantLine, _ = runtime.Caller(0) logFunc() wantLine++ if len(logger.entries) != 1 { t.Errorf("expected a single log entry to be generated, got %d", len(logger.entries)) } checkLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0]) } type testLogr struct { entries []testLogrEntry mutex sync.Mutex } type testLogrEntry struct { severity severity.Severity msg string keysAndValues []interface{} err error } func (l *testLogr) reset() { l.mutex.Lock() defer l.mutex.Unlock() l.entries = []testLogrEntry{} } func (l *testLogr) Info(level int, msg string, keysAndValues ...interface{}) { l.mutex.Lock() defer l.mutex.Unlock() l.entries = append(l.entries, testLogrEntry{ severity: severity.InfoLog, msg: msg, keysAndValues: keysAndValues, }) } func (l *testLogr) Error(err error, msg string, keysAndValues ...interface{}) { l.mutex.Lock() defer l.mutex.Unlock() l.entries = append(l.entries, testLogrEntry{ severity: severity.ErrorLog, msg: msg, keysAndValues: keysAndValues, err: err, }) } func (l *testLogr) Init(info logr.RuntimeInfo) {} func (l *testLogr) Enabled(level int) bool { return true } func (l *testLogr) V(int) logr.Logger { panic("not implemented") } func (l *testLogr) WithName(string) logr.LogSink { panic("not implemented") } func (l *testLogr) WithValues(...interface{}) logr.LogSink { panic("not implemented") } func (l *testLogr) WithCallDepth(depth int) logr.LogSink { return l } var _ logr.LogSink = &testLogr{} var _ logr.CallDepthLogSink = &testLogr{} type callDepthTestLogr struct { testLogr callDepth int } func (l *callDepthTestLogr) resetCallDepth() { l.mutex.Lock() defer l.mutex.Unlock() l.callDepth = 0 } func (l *callDepthTestLogr) WithCallDepth(depth int) logr.LogSink { l.mutex.Lock() defer l.mutex.Unlock() // Note: Usually WithCallDepth would be implemented by cloning l // and setting the call depth on the clone. We modify l instead in // this test helper for simplicity. l.callDepth = depth + 1 return l } func (l *callDepthTestLogr) Info(level int, msg string, keysAndValues ...interface{}) { l.mutex.Lock() defer l.mutex.Unlock() // Add 2 to depth for the wrapper function caller and for invocation in // test case. _, file, line, _ := runtime.Caller(l.callDepth + 2) l.entries = append(l.entries, testLogrEntry{ severity: severity.InfoLog, msg: msg, keysAndValues: append([]interface{}{file, line}, keysAndValues...), }) } func (l *callDepthTestLogr) Error(err error, msg string, keysAndValues ...interface{}) { l.mutex.Lock() defer l.mutex.Unlock() // Add 2 to depth for the wrapper function caller and for invocation in // test case. _, file, line, _ := runtime.Caller(l.callDepth + 2) l.entries = append(l.entries, testLogrEntry{ severity: severity.ErrorLog, msg: msg, keysAndValues: append([]interface{}{file, line}, keysAndValues...), err: err, }) } var _ logr.LogSink = &callDepthTestLogr{} var _ logr.CallDepthLogSink = &callDepthTestLogr{} func checkLogrEntryCorrectCaller(t *testing.T, wantFile string, wantLine int, entry testLogrEntry) { t.Helper() want := fmt.Sprintf("%s:%d", wantFile, wantLine) // Log fields contain file and line number as first elements. got := fmt.Sprintf("%s:%d", entry.keysAndValues[0], entry.keysAndValues[1]) if want != got { t.Errorf("expected file and line %q but got %q", want, got) } } // existedFlag contains all existed flag, without KlogPrefix var existedFlag = map[string]struct{}{ "log_dir": {}, "add_dir_header": {}, "alsologtostderr": {}, "log_backtrace_at": {}, "log_file": {}, "log_file_max_size": {}, "logtostderr": {}, "one_output": {}, "skip_headers": {}, "skip_log_headers": {}, "stderrthreshold": {}, "v": {}, "vmodule": {}, } // KlogPrefix define new flag prefix const KlogPrefix string = "klog" // TestKlogFlagPrefix check every klog flag's prefix, exclude flag in existedFlag func TestKlogFlagPrefix(t *testing.T) { fs := &flag.FlagSet{} InitFlags(fs) fs.VisitAll(func(f *flag.Flag) { if _, found := existedFlag[f.Name]; !found { if !strings.HasPrefix(f.Name, KlogPrefix) { t.Errorf("flag %s not have klog prefix: %s", f.Name, KlogPrefix) } } }) } func TestKObjs(t *testing.T) { tests := []struct { name string obj interface{} want []ObjectRef }{ { name: "test for KObjs function with KMetadata slice", obj: []test.KMetadataMock{ { Name: "kube-dns", NS: "kube-system", }, { Name: "mi-conf", }, {}, }, want: []ObjectRef{ { Name: "kube-dns", Namespace: "kube-system", }, { Name: "mi-conf", }, {}, }, }, { name: "test for KObjs function with KMetadata pointer slice", obj: []*test.KMetadataMock{ { Name: "kube-dns", NS: "kube-system", }, { Name: "mi-conf", }, nil, }, want: []ObjectRef{ { Name: "kube-dns", Namespace: "kube-system", }, { Name: "mi-conf", }, {}, }, }, { name: "test for KObjs function with slice does not implement KMetadata", obj: []int{1, 2, 3, 4, 6}, want: nil, }, { name: "test for KObjs function with interface", obj: "test case", want: nil, }, { name: "test for KObjs function with nil", obj: nil, want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if !reflect.DeepEqual(KObjs(tt.obj), tt.want) { t.Errorf("\nwant:\t %v\n got:\t %v", tt.want, KObjs(tt.obj)) } }) } } // Benchmark test for lock with/without defer type structWithLock struct { m sync.Mutex n int64 } func BenchmarkWithoutDeferUnLock(b *testing.B) { s := structWithLock{} for i := 0; i < b.N; i++ { s.addWithoutDefer() } } func BenchmarkWithDeferUnLock(b *testing.B) { s := structWithLock{} for i := 0; i < b.N; i++ { s.addWithDefer() } } func (s *structWithLock) addWithoutDefer() { s.m.Lock() s.n++ s.m.Unlock() } func (s *structWithLock) addWithDefer() { s.m.Lock() defer s.m.Unlock() s.n++ } func TestFlushDaemon(t *testing.T) { for sev := severity.InfoLog; sev < severity.FatalLog; sev++ { flushed := make(chan struct{}, 1) spyFunc := func() { flushed <- struct{}{} } testClock := testingclock.NewFakeClock(time.Now()) testLog := loggingT{ settings: settings{ flushInterval: time.Second, }, flushD: newFlushDaemon(spyFunc, testClock), } // Calling testLog will call createFile, which should start the daemon. testLog.print(sev, nil, nil, "x") if !testLog.flushD.isRunning() { t.Error("expected flushD to be running") } timer := time.NewTimer(10 * time.Second) defer timer.Stop() testClock.Step(time.Second) select { case <-flushed: case <-timer.C: t.Fatal("flushDaemon didn't call flush function on tick") } timer = time.NewTimer(10 * time.Second) defer timer.Stop() testClock.Step(time.Second) select { case <-flushed: case <-timer.C: t.Fatal("flushDaemon didn't call flush function on second tick") } timer = time.NewTimer(10 * time.Second) defer timer.Stop() testLog.flushD.stop() select { case <-flushed: case <-timer.C: t.Fatal("flushDaemon didn't call flush function one last time on stop") } } } func TestStopFlushDaemon(t *testing.T) { logging.flushD.stop() logging.flushD = newFlushDaemon(func() {}, nil) logging.flushD.run(time.Second) if !logging.flushD.isRunning() { t.Error("expected flushD to be running") } StopFlushDaemon() if logging.flushD.isRunning() { t.Error("expected flushD to be stopped") } } func TestCaptureState(t *testing.T) { var fs flag.FlagSet InitFlags(&fs) // Capture state. oldState := map[string]string{} fs.VisitAll(func(f *flag.Flag) { oldState[f.Name] = f.Value.String() }) originalLogger := Background() file := logging.file // And through dedicated API. // Ensure we always restore. state := CaptureState() defer state.Restore() // Change state. for name, value := range map[string]string{ // All of these are non-standard values. "v": "10", "vmodule": "abc=2", "log_dir": "/tmp", "log_file_max_size": "10", "logtostderr": "false", "alsologtostderr": "true", "add_dir_header": "true", "skip_headers": "true", "one_output": "true", "skip_log_headers": "true", "stderrthreshold": "1", "log_backtrace_at": "foobar.go:100", } { f := fs.Lookup(name) if f == nil { t.Fatalf("could not look up %q", name) } currentValue := f.Value.String() if currentValue == value { t.Fatalf("%q is already set to non-default %q?!", name, value) } if err := f.Value.Set(value); err != nil { t.Fatalf("setting %q to %q: %v", name, value, err) } } StartFlushDaemon(time.Minute) if !logging.flushD.isRunning() { t.Error("Flush daemon should have been started.") } logger := logr.Discard() SetLoggerWithOptions(logger, ContextualLogger(true)) actualLogger := Background() if logger != actualLogger { t.Errorf("Background logger should be %v, got %v", logger, actualLogger) } buffer := bytes.Buffer{} SetOutput(&buffer) if file == logging.file { t.Error("Output files should have been modified.") } // Let klog restore the state. state.Restore() // Verify that the original state is back. fs.VisitAll(func(f *flag.Flag) { oldValue := oldState[f.Name] currentValue := f.Value.String() if oldValue != currentValue { t.Errorf("%q should have been restored to %q, is %q instead", f.Name, oldValue, currentValue) } }) if logging.flushD.isRunning() { t.Error("Flush daemon should have been stopped.") } actualLogger = Background() if originalLogger != actualLogger { t.Errorf("Background logger should be %v, got %v", originalLogger, actualLogger) } if file != logging.file { t.Errorf("Output files should have been restored to %v, got %v", file, logging.file) } } func TestSettingsDeepCopy(t *testing.T) { logger := logr.Discard() settings := settings{ logger: &logger, vmodule: moduleSpec{ filter: []modulePat{ {pattern: "a"}, {pattern: "b"}, {pattern: "c"}, }, }, } copy := settings.deepCopy() if !reflect.DeepEqual(settings, copy) { t.Fatalf("Copy not identical to original settings. Original:\n %+v\nCopy: %+v", settings, copy) } settings.vmodule.filter[1].pattern = "x" if copy.vmodule.filter[1].pattern == settings.vmodule.filter[1].pattern { t.Fatal("Copy should not have shared vmodule.filter.") } } golang-k8s-klog-2.80.1/klog_wrappers_test.go000066400000000000000000000020141432555450600207610ustar00rootroot00000000000000// Copyright 2020 The Kubernetes 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 klog // These helper functions must be in a separate source file because the // tests in klog_test.go compare the logged source code file name against // "klog_test.go". "klog_wrappers_test.go" must *not* be logged. func myInfoS(msg string, keyAndValues ...interface{}) { InfoSDepth(1, msg, keyAndValues...) } func myErrorS(err error, msg string, keyAndValues ...interface{}) { ErrorSDepth(1, err, msg, keyAndValues...) } golang-k8s-klog-2.80.1/klogr.go000066400000000000000000000046261432555450600161740ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 klog import ( "github.com/go-logr/logr" "k8s.io/klog/v2/internal/serialize" ) // NewKlogr returns a logger that is functionally identical to // klogr.NewWithOptions(klogr.FormatKlog), i.e. it passes through to klog. The // difference is that it uses a simpler implementation. func NewKlogr() Logger { return New(&klogger{}) } // klogger is a subset of klogr/klogr.go. It had to be copied to break an // import cycle (klogr wants to use klog, and klog wants to use klogr). type klogger struct { level int callDepth int prefix string values []interface{} } func (l *klogger) Init(info logr.RuntimeInfo) { l.callDepth += info.CallDepth } func (l klogger) Info(level int, msg string, kvList ...interface{}) { merged := serialize.MergeKVs(l.values, kvList) if l.prefix != "" { msg = l.prefix + ": " + msg } V(Level(level)).InfoSDepth(l.callDepth+1, msg, merged...) } func (l klogger) Enabled(level int) bool { return V(Level(level)).Enabled() } func (l klogger) Error(err error, msg string, kvList ...interface{}) { merged := serialize.MergeKVs(l.values, kvList) if l.prefix != "" { msg = l.prefix + ": " + msg } ErrorSDepth(l.callDepth+1, err, msg, merged...) } // WithName returns a new logr.Logger with the specified name appended. klogr // uses '/' characters to separate name elements. Callers should not pass '/' // in the provided name string, but this library does not actually enforce that. func (l klogger) WithName(name string) logr.LogSink { if len(l.prefix) > 0 { l.prefix = l.prefix + "/" } l.prefix += name return &l } func (l klogger) WithValues(kvList ...interface{}) logr.LogSink { l.values = serialize.WithValues(l.values, kvList) return &l } func (l klogger) WithCallDepth(depth int) logr.LogSink { l.callDepth += depth return &l } var _ logr.LogSink = &klogger{} var _ logr.CallDepthLogSink = &klogger{} golang-k8s-klog-2.80.1/klogr/000077500000000000000000000000001432555450600156355ustar00rootroot00000000000000golang-k8s-klog-2.80.1/klogr/README.md000066400000000000000000000015511432555450600171160ustar00rootroot00000000000000# Minimal Go logging using klog This package implements the [logr interface](https://github.com/go-logr/logr) in terms of Kubernetes' [klog](https://github.com/kubernetes/klog). This provides a relatively minimalist API to logging in Go, backed by a well-proven implementation. Because klogr was implemented before klog itself added supported for structured logging, the default in klogr is to serialize key/value pairs with JSON and log the result as text messages via klog. This does not work well when klog itself forwards output to a structured logger. Therefore the recommended approach is to let klogr pass all log messages through to klog and deal with structured logging there. Just beware that the output of klog without a structured logger is meant to be human-readable, in contrast to the JSON-based traditional format. This is a BETA grade implementation. golang-k8s-klog-2.80.1/klogr/calldepth-test/000077500000000000000000000000001432555450600205525ustar00rootroot00000000000000golang-k8s-klog-2.80.1/klogr/calldepth-test/call_depth_helper_test.go000066400000000000000000000005541432555450600256020ustar00rootroot00000000000000package calldepth import ( "github.com/go-logr/logr" ) // Putting these functions into a separate file makes it possible to validate that // their source code file is *not* logged because of WithCallDepth(1). func myInfo(l logr.Logger, msg string) { l.WithCallDepth(2).Info(msg) } func myInfo2(l logr.Logger, msg string) { myInfo(l.WithCallDepth(2), msg) } golang-k8s-klog-2.80.1/klogr/calldepth-test/call_depth_main_test.go000066400000000000000000000024341432555450600252460ustar00rootroot00000000000000// Package calldepth does black-box testing. // // Another intentional effect is that "go test" compiles // this into a separate binary which we need because // we have to configure klog differently that TestOutput. package calldepth import ( "bytes" "flag" "strings" "testing" "k8s.io/klog/v2" "k8s.io/klog/v2/klogr" ) func TestCallDepth(t *testing.T) { klog.InitFlags(nil) flag.CommandLine.Set("v", "10") flag.CommandLine.Set("skip_headers", "false") flag.CommandLine.Set("logtostderr", "false") flag.CommandLine.Set("alsologtostderr", "false") flag.CommandLine.Set("stderrthreshold", "10") flag.Parse() t.Run("call-depth", func(t *testing.T) { logr := klogr.New() // hijack the klog output tmpWriteBuffer := bytes.NewBuffer(nil) klog.SetOutput(tmpWriteBuffer) validate := func(t *testing.T) { output := tmpWriteBuffer.String() if !strings.Contains(output, "call_depth_main_test.go:") { t.Fatalf("output should have contained call_depth_main_test.go, got instead: %s", output) } } t.Run("direct", func(t *testing.T) { logr.Info("hello world") validate(t) }) t.Run("indirect", func(t *testing.T) { myInfo(logr, "hello world") validate(t) }) t.Run("nested", func(t *testing.T) { myInfo2(logr, "hello world") validate(t) }) }) } golang-k8s-klog-2.80.1/klogr/klogr.go000066400000000000000000000110171432555450600173020ustar00rootroot00000000000000// Package klogr implements github.com/go-logr/logr.Logger in terms of // k8s.io/klog. package klogr import ( "bytes" "encoding/json" "fmt" "sort" "strings" "github.com/go-logr/logr" "k8s.io/klog/v2" "k8s.io/klog/v2/internal/serialize" ) // Option is a functional option that reconfigures the logger created with New. type Option func(*klogger) // Format defines how log output is produced. type Format string const ( // FormatSerialize tells klogr to turn key/value pairs into text itself // before invoking klog. FormatSerialize Format = "Serialize" // FormatKlog tells klogr to pass all text messages and key/value pairs // directly to klog. Klog itself then serializes in a human-readable // format and optionally passes on to a structure logging backend. FormatKlog Format = "Klog" ) // WithFormat selects the output format. func WithFormat(format Format) Option { return func(l *klogger) { l.format = format } } // New returns a logr.Logger which serializes output itself // and writes it via klog. func New() logr.Logger { return NewWithOptions(WithFormat(FormatSerialize)) } // NewWithOptions returns a logr.Logger which serializes as determined // by the WithFormat option and writes via klog. The default is // FormatKlog. func NewWithOptions(options ...Option) logr.Logger { l := klogger{ level: 0, prefix: "", values: nil, format: FormatKlog, } for _, option := range options { option(&l) } return logr.New(&l) } type klogger struct { level int callDepth int prefix string values []interface{} format Format } func (l *klogger) Init(info logr.RuntimeInfo) { l.callDepth += info.CallDepth } func flatten(kvList ...interface{}) string { keys := make([]string, 0, len(kvList)) vals := make(map[string]interface{}, len(kvList)) for i := 0; i < len(kvList); i += 2 { k, ok := kvList[i].(string) if !ok { panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i]))) } var v interface{} if i+1 < len(kvList) { v = kvList[i+1] } // Only print each key once... if _, seen := vals[k]; !seen { keys = append(keys, k) } // ... with the latest value. vals[k] = v } sort.Strings(keys) buf := bytes.Buffer{} for i, k := range keys { v := vals[k] if i > 0 { buf.WriteRune(' ') } buf.WriteString(pretty(k)) buf.WriteString("=") buf.WriteString(pretty(v)) } return buf.String() } func pretty(value interface{}) string { if err, ok := value.(error); ok { if _, ok := value.(json.Marshaler); !ok { value = err.Error() } } buffer := &bytes.Buffer{} encoder := json.NewEncoder(buffer) encoder.SetEscapeHTML(false) encoder.Encode(value) return strings.TrimSpace(string(buffer.Bytes())) } func (l klogger) Info(level int, msg string, kvList ...interface{}) { switch l.format { case FormatSerialize: msgStr := flatten("msg", msg) merged := serialize.MergeKVs(l.values, kvList) kvStr := flatten(merged...) klog.V(klog.Level(level)).InfoDepth(l.callDepth+1, l.prefix, " ", msgStr, " ", kvStr) case FormatKlog: merged := serialize.MergeKVs(l.values, kvList) if l.prefix != "" { msg = l.prefix + ": " + msg } klog.V(klog.Level(level)).InfoSDepth(l.callDepth+1, msg, merged...) } } func (l klogger) Enabled(level int) bool { return klog.V(klog.Level(level)).Enabled() } func (l klogger) Error(err error, msg string, kvList ...interface{}) { msgStr := flatten("msg", msg) var loggableErr interface{} if err != nil { loggableErr = serialize.ErrorToString(err) } switch l.format { case FormatSerialize: errStr := flatten("error", loggableErr) merged := serialize.MergeKVs(l.values, kvList) kvStr := flatten(merged...) klog.ErrorDepth(l.callDepth+1, l.prefix, " ", msgStr, " ", errStr, " ", kvStr) case FormatKlog: merged := serialize.MergeKVs(l.values, kvList) if l.prefix != "" { msg = l.prefix + ": " + msg } klog.ErrorSDepth(l.callDepth+1, err, msg, merged...) } } // WithName returns a new logr.Logger with the specified name appended. klogr // uses '/' characters to separate name elements. Callers should not pass '/' // in the provided name string, but this library does not actually enforce that. func (l klogger) WithName(name string) logr.LogSink { if len(l.prefix) > 0 { l.prefix = l.prefix + "/" } l.prefix += name return &l } func (l klogger) WithValues(kvList ...interface{}) logr.LogSink { l.values = serialize.WithValues(l.values, kvList) return &l } func (l klogger) WithCallDepth(depth int) logr.LogSink { l.callDepth += depth return &l } var _ logr.LogSink = &klogger{} var _ logr.CallDepthLogSink = &klogger{} golang-k8s-klog-2.80.1/klogr/klogr_test.go000066400000000000000000000163551432555450600203530ustar00rootroot00000000000000package klogr import ( "bytes" "encoding/json" "errors" "flag" "strings" "testing" "k8s.io/klog/v2" "github.com/go-logr/logr" ) const ( formatDefault = "Default" formatNew = "New" ) func testOutput(t *testing.T, format string) { new := func() logr.Logger { switch format { case formatNew: return New() case formatDefault: return NewWithOptions() default: return NewWithOptions(WithFormat(Format(format))) } } tests := map[string]struct { klogr logr.Logger text string keysAndValues []interface{} err error expectedOutput string expectedKlogOutput string }{ "should log with values passed to keysAndValues": { klogr: new().V(0), text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: ` "msg"="test" "akey"="avalue" `, expectedKlogOutput: `"test" akey="avalue" `, }, "should log with name and values passed to keysAndValues": { klogr: new().V(0).WithName("me"), text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `me "msg"="test" "akey"="avalue" `, expectedKlogOutput: `"me: test" akey="avalue" `, }, "should log with multiple names and values passed to keysAndValues": { klogr: new().V(0).WithName("hello").WithName("world"), text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `hello/world "msg"="test" "akey"="avalue" `, expectedKlogOutput: `"hello/world: test" akey="avalue" `, }, "may print duplicate keys with the same value": { klogr: new().V(0), text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue"}, expectedOutput: ` "msg"="test" "akey"="avalue" `, expectedKlogOutput: `"test" akey="avalue" akey="avalue" `, }, "may print duplicate keys when the values are passed to Info": { klogr: new().V(0), text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue2"}, expectedOutput: ` "msg"="test" "akey"="avalue2" `, expectedKlogOutput: `"test" akey="avalue" akey="avalue2" `, }, "should only print the duplicate key that is passed to Info if one was passed to the logger": { klogr: new().WithValues("akey", "avalue"), text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: ` "msg"="test" "akey"="avalue" `, expectedKlogOutput: `"test" akey="avalue" `, }, "should sort within logger and parameter key/value pairs in the default format and dump the logger pairs first": { klogr: new().WithValues("akey9", "avalue9", "akey8", "avalue8", "akey1", "avalue1"), text: "test", keysAndValues: []interface{}{"akey5", "avalue5", "akey4", "avalue4"}, expectedOutput: ` "msg"="test" "akey1"="avalue1" "akey4"="avalue4" "akey5"="avalue5" "akey8"="avalue8" "akey9"="avalue9" `, expectedKlogOutput: `"test" akey9="avalue9" akey8="avalue8" akey1="avalue1" akey5="avalue5" akey4="avalue4" `, }, "should only print the key passed to Info when one is already set on the logger": { klogr: new().WithValues("akey", "avalue"), text: "test", keysAndValues: []interface{}{"akey", "avalue2"}, expectedOutput: ` "msg"="test" "akey"="avalue2" `, expectedKlogOutput: `"test" akey="avalue2" `, }, "should correctly handle odd-numbers of KVs": { klogr: new(), text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: ` "msg"="test" "akey"="avalue" "akey2"="(MISSING)" `, expectedKlogOutput: `"test" akey="avalue" akey2="(MISSING)" `, }, "should correctly handle odd-numbers of KVs in WithValue": { klogr: new().WithValues("keyWithoutValue"), text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey2"}, // klogr format sorts all key/value pairs. expectedOutput: ` "msg"="test" "akey"="avalue" "akey2"="(MISSING)" "keyWithoutValue"="(MISSING)" `, expectedKlogOutput: `"test" keyWithoutValue="(MISSING)" akey="avalue" akey2="(MISSING)" `, }, "should correctly html characters": { klogr: new(), text: "test", keysAndValues: []interface{}{"akey", "<&>"}, expectedOutput: ` "msg"="test" "akey"="<&>" `, expectedKlogOutput: `"test" akey="<&>" `, }, "should correctly handle odd-numbers of KVs in both log values and Info args": { klogr: new().WithValues("basekey1", "basevar1", "basekey2"), text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey2"}, // klogr format sorts all key/value pairs. expectedOutput: ` "msg"="test" "akey"="avalue" "akey2"="(MISSING)" "basekey1"="basevar1" "basekey2"="(MISSING)" `, expectedKlogOutput: `"test" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `, }, "should correctly print regular error types": { klogr: new().V(0), text: "test", keysAndValues: []interface{}{"err", errors.New("whoops")}, expectedOutput: ` "msg"="test" "err"="whoops" `, expectedKlogOutput: `"test" err="whoops" `, }, "should use MarshalJSON in the default format if an error type implements it": { klogr: new().V(0), text: "test", keysAndValues: []interface{}{"err", &customErrorJSON{"whoops"}}, expectedOutput: ` "msg"="test" "err"="WHOOPS" `, expectedKlogOutput: `"test" err="whoops" `, }, "should correctly print regular error types when using logr.Error": { klogr: new().V(0), text: "test", err: errors.New("whoops"), // The message is printed to three different log files (info, warning, error), so we see it three times in our output buffer. expectedOutput: ` "msg"="test" "error"="whoops" "msg"="test" "error"="whoops" "msg"="test" "error"="whoops" `, expectedKlogOutput: `"test" err="whoops" "test" err="whoops" "test" err="whoops" `, }, } for n, test := range tests { t.Run(n, func(t *testing.T) { // hijack the klog output tmpWriteBuffer := bytes.NewBuffer(nil) klog.SetOutput(tmpWriteBuffer) if test.err != nil { test.klogr.Error(test.err, test.text, test.keysAndValues...) } else { test.klogr.Info(test.text, test.keysAndValues...) } // call Flush to ensure the text isn't still buffered klog.Flush() actual := tmpWriteBuffer.String() expectedOutput := test.expectedOutput if format == string(FormatKlog) || format == formatDefault { expectedOutput = test.expectedKlogOutput } if actual != expectedOutput { t.Errorf("Expected:\n%s\nActual:\n%s\n", expectedOutput, actual) } }) } } func TestOutput(t *testing.T) { klog.InitFlags(nil) flag.CommandLine.Set("v", "10") flag.CommandLine.Set("skip_headers", "true") flag.CommandLine.Set("logtostderr", "false") flag.CommandLine.Set("alsologtostderr", "false") flag.CommandLine.Set("stderrthreshold", "10") flag.Parse() formats := []string{ formatNew, formatDefault, string(FormatSerialize), string(FormatKlog), } for _, format := range formats { t.Run(format, func(t *testing.T) { testOutput(t, format) }) } } type customErrorJSON struct { s string } func (e *customErrorJSON) Error() string { return e.s } func (e *customErrorJSON) MarshalJSON() ([]byte, error) { return json.Marshal(strings.ToUpper(e.s)) } golang-k8s-klog-2.80.1/ktesting/000077500000000000000000000000001432555450600163475ustar00rootroot00000000000000golang-k8s-klog-2.80.1/ktesting/contextual_test.go000066400000000000000000000022311432555450600221210ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Copyright 2020 Intel Coporation. SPDX-License-Identifier: Apache-2.0 */ package ktesting_test import ( "context" "testing" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" ) func TestContextual(t *testing.T) { logger, ctx := ktesting.NewTestContext(t) doSomething(ctx) // When contextual logging is disabled, the output goes to klog // instead of the testing logger. state := klog.CaptureState() defer state.Restore() klog.EnableContextualLogging(false) doSomething(ctx) testingLogger, ok := logger.GetSink().(ktesting.Underlier) if !ok { t.Fatal("Should have had a ktesting LogSink!?") } actual := testingLogger.GetBuffer().String() expected := `INFO hello world INFO foo: hello also from me ` if actual != expected { t.Errorf("mismatch in captured output, expected:\n%s\ngot:\n%s\n", expected, actual) } } func doSomething(ctx context.Context) { logger := klog.FromContext(ctx) logger.Info("hello world") logger = logger.WithName("foo") ctx = klog.NewContext(ctx, logger) doSomeMore(ctx) } func doSomeMore(ctx context.Context) { logger := klog.FromContext(ctx) logger.Info("hello also from me") } golang-k8s-klog-2.80.1/ktesting/example/000077500000000000000000000000001432555450600200025ustar00rootroot00000000000000golang-k8s-klog-2.80.1/ktesting/example/example_test.go000066400000000000000000000021761432555450600230310ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. SPDX-License-Identifier: Apache-2.0 */ package example import ( "fmt" "testing" "k8s.io/klog/v2" "k8s.io/klog/v2/internal/test" "k8s.io/klog/v2/ktesting" _ "k8s.io/klog/v2/ktesting/init" // add command line flags ) func TestKlogr(t *testing.T) { logger, _ := ktesting.NewTestContext(t) exampleOutput(logger) } type pair struct { a, b int } func (p pair) String() string { return fmt.Sprintf("(%d, %d)", p.a, p.b) } var _ fmt.Stringer = pair{} type err struct { msg string } func (e err) Error() string { return "failed: " + e.msg } var _ error = err{} func exampleOutput(logger klog.Logger) { logger.Info("hello world") logger.Error(err{msg: "some error"}, "failed") logger.V(1).Info("verbosity 1") logger.WithName("main").WithName("helper").Info("with prefix") obj := test.KMetadataMock{Name: "joe", NS: "kube-system"} logger.Info("key/value pairs", "int", 1, "float", 2.0, "pair", pair{a: 1, b: 2}, "raw", obj, "kobj", klog.KObj(obj), ) logger.V(4).Info("info message level 4") logger.V(5).Info("info message level 5") logger.V(6).Info("info message level 6") } golang-k8s-klog-2.80.1/ktesting/example_test.go000066400000000000000000000057341432555450600214010ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 ktesting_test import ( "errors" "fmt" "time" "k8s.io/klog/v2/ktesting" ) func ExampleUnderlier() { logger := ktesting.NewLogger(ktesting.NopTL{}, ktesting.NewConfig(ktesting.Verbosity(4))) logger.Error(errors.New("failure"), "I failed", "what", "something") logger.WithValues("request", 42).WithValues("anotherValue", "fish").Info("hello world") logger.WithValues("request", 42, "anotherValue", "fish").Info("hello world 2", "yetAnotherValue", "thanks") logger.WithName("example").Info("with name") logger.V(4).Info("higher verbosity") logger.V(5).Info("Not captured because of ktesting.Verbosity(4) above. Normally it would be captured because default verbosity is 5.") testingLogger, ok := logger.GetSink().(ktesting.Underlier) if !ok { panic("Should have had a ktesting LogSink!?") } t := testingLogger.GetUnderlying() t.Log("This goes to /dev/null...") buffer := testingLogger.GetBuffer() fmt.Printf("%s\n", buffer.String()) log := buffer.Data() for i, entry := range log { if i > 0 && entry.Timestamp.Sub(log[i-1].Timestamp).Nanoseconds() < 0 { fmt.Printf("Unexpected timestamp order: #%d %s > #%d %s", i-1, log[i-1].Timestamp, i, entry.Timestamp) } // Strip varying time stamp before dumping the struct. entry.Timestamp = time.Time{} fmt.Printf("log entry #%d: %+v\n", i, entry) } // Output: // ERROR I failed err="failure" what="something" // INFO hello world request=42 anotherValue="fish" // INFO hello world 2 request=42 anotherValue="fish" yetAnotherValue="thanks" // INFO example: with name // INFO higher verbosity // // log entry #0: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:ERROR Prefix: Message:I failed Verbosity:0 Err:failure WithKVList:[] ParameterKVList:[what something]} // log entry #1: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:hello world Verbosity:0 Err: WithKVList:[request 42 anotherValue fish] ParameterKVList:[]} // log entry #2: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:hello world 2 Verbosity:0 Err: WithKVList:[request 42 anotherValue fish] ParameterKVList:[yetAnotherValue thanks]} // log entry #3: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix:example Message:with name Verbosity:0 Err: WithKVList:[] ParameterKVList:[]} // log entry #4: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:higher verbosity Verbosity:4 Err: WithKVList:[] ParameterKVList:[]} } golang-k8s-klog-2.80.1/ktesting/init/000077500000000000000000000000001432555450600173125ustar00rootroot00000000000000golang-k8s-klog-2.80.1/ktesting/init/init.go000066400000000000000000000017401432555450600206060ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 init registers the command line flags for k8s.io/klogr/testing in // the flag.CommandLine. This is done during initialization, so merely // importing it is enough. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. package init import ( "flag" "k8s.io/klog/v2/ktesting" ) func init() { ktesting.DefaultConfig.AddFlags(flag.CommandLine) } golang-k8s-klog-2.80.1/ktesting/options.go000066400000000000000000000075551432555450600204050ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 ktesting import ( "flag" "strconv" "k8s.io/klog/v2/internal/verbosity" ) // Config influences logging in a test logger. To make this configurable via // command line flags, instantiate this once per program and use AddFlags to // bind command line flags to the instance before passing it to NewTestContext. // // Must be constructed with NewConfig. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type Config struct { vstate *verbosity.VState co configOptions } // ConfigOption implements functional parameters for NewConfig. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type ConfigOption func(co *configOptions) type configOptions struct { verbosityFlagName string vmoduleFlagName string verbosityDefault int } // VerbosityFlagName overrides the default -testing.v for the verbosity level. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func VerbosityFlagName(name string) ConfigOption { return func(co *configOptions) { co.verbosityFlagName = name } } // VModulFlagName overrides the default -testing.vmodule for the per-module // verbosity levels. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func VModuleFlagName(name string) ConfigOption { return func(co *configOptions) { co.vmoduleFlagName = name } } // Verbosity overrides the default verbosity level of 5. That default is higher // than in klog itself because it enables logging entries for "the steps // leading up to errors and warnings" and "troubleshooting" (see // https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions), // which is useful when debugging a failed test. `go test` only shows the log // output for failed tests. To see all output, use `go test -v`. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func Verbosity(level int) ConfigOption { return func(co *configOptions) { co.verbosityDefault = level } } // NewConfig returns a configuration with recommended defaults and optional // modifications. Command line flags are not bound to any FlagSet yet. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func NewConfig(opts ...ConfigOption) *Config { c := &Config{ co: configOptions{ verbosityFlagName: "testing.v", vmoduleFlagName: "testing.vmodule", verbosityDefault: 5, }, } for _, opt := range opts { opt(&c.co) } c.vstate = verbosity.New() c.vstate.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10)) return c } // AddFlags registers the command line flags that control the configuration. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func (c *Config) AddFlags(fs *flag.FlagSet) { fs.Var(c.vstate.V(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger") fs.Var(c.vstate.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns") } golang-k8s-klog-2.80.1/ktesting/setup.go000066400000000000000000000027721432555450600200460ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 ktesting import ( "context" "github.com/go-logr/logr" ) // DefaultConfig is the global default logging configuration for a unit // test. It is used by NewTestContext and k8s.io/klogr/testing/init. // // # Experimental // // Notice: This variable is EXPERIMENTAL and may be changed or removed in a // later release. var DefaultConfig = NewConfig() // NewTestContext returns a logger and context for use in a unit test case or // benchmark. The tl parameter can be a testing.T or testing.B pointer that // will receive all log output. Importing k8s.io/klogr/testing/init will add // command line flags that modify the configuration of that log output. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func NewTestContext(tl TL) (logr.Logger, context.Context) { logger := NewLogger(tl, DefaultConfig) ctx := logr.NewContext(context.Background(), logger) return logger, ctx } golang-k8s-klog-2.80.1/ktesting/testinglogger.go000066400000000000000000000254071432555450600215630ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Copyright 2020 Intel Coporation. 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 testinglogger contains an implementation of the logr interface // which is logging through a function like testing.TB.Log function. // Therefore it can be used in standard Go tests and Gingko test suites // to ensure that output is associated with the currently running test. // // In addition, the log data is captured in a buffer and can be used by the // test to verify that the code under test is logging as expected. To get // access to that data, cast the LogSink into the Underlier type and retrieve // it: // // logger := ktesting.NewLogger(...) // if testingLogger, ok := logger.GetSink().(ktesting.Underlier); ok { // t := testingLogger.GetUnderlying() // buffer := testingLogger.GetBuffer() // text := buffer.String() // log := buffer.Data() // // Serialization of the structured log parameters is done in the same way // as for klog.InfoS. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. package ktesting import ( "bytes" "strings" "sync" "time" "github.com/go-logr/logr" "k8s.io/klog/v2" "k8s.io/klog/v2/internal/dbg" "k8s.io/klog/v2/internal/serialize" "k8s.io/klog/v2/internal/verbosity" ) // TL is the relevant subset of testing.TB. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type TL interface { Helper() Log(args ...interface{}) } // NopTL implements TL with empty stubs. It can be used when only capturing // output in memory is relevant. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type NopTL struct{} func (n NopTL) Helper() {} func (n NopTL) Log(args ...interface{}) {} var _ TL = NopTL{} // NewLogger constructs a new logger for the given test interface. // // Beware that testing.T does not support logging after the test that // it was created for has completed. If a test leaks goroutines // and those goroutines log something after test completion, // that output will be printed via the global klog logger with // ` leaked goroutine` as prefix. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. func NewLogger(t TL, c *Config) logr.Logger { l := tlogger{ shared: &tloggerShared{ t: t, config: c, }, } type testCleanup interface { Cleanup(func()) Name() string } // Stopping the logging is optional and only done (and required) // for testing.T/B/F. if tb, ok := t.(testCleanup); ok { tb.Cleanup(l.shared.stop) l.shared.testName = tb.Name() } return logr.New(l) } // Buffer stores log entries as formatted text and structured data. // It is safe to use this concurrently. // // # Experimental // // Notice: This interface is EXPERIMENTAL and may be changed or removed in a // later release. type Buffer interface { // String returns the log entries in a format that is similar to the // klog text output. String() string // Data returns the log entries as structs. Data() Log } // Log contains log entries in the order in which they were generated. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type Log []LogEntry // DeepCopy returns a copy of the log. The error instance and key/value // pairs remain shared. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func (l Log) DeepCopy() Log { log := make(Log, 0, len(l)) log = append(log, l...) return log } // LogEntry represents all information captured for a log entry. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type LogEntry struct { // Timestamp stores the time when the log entry was created. Timestamp time.Time // Type is either LogInfo or LogError. Type LogType // Prefix contains the WithName strings concatenated with a slash. Prefix string // Message is the fixed log message string. Message string // Verbosity is always 0 for LogError. Verbosity int // Err is always nil for LogInfo. It may or may not be // nil for LogError. Err error // WithKVList are the concatenated key/value pairs from WithValues // calls. It's guaranteed to have an even number of entries because // the logger ensures that when WithValues is called. WithKVList []interface{} // ParameterKVList are the key/value pairs passed into the call, // without any validation. ParameterKVList []interface{} } // LogType determines whether a log entry was created with an Error or Info // call. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type LogType string const ( // LogError is the special value used for Error log entries. // // Experimental // // Notice: This value is EXPERIMENTAL and may be changed or removed in // a later release. LogError = LogType("ERROR") // LogInfo is the special value used for Info log entries. // // Experimental // // Notice: This value is EXPERIMENTAL and may be changed or removed in // a later release. LogInfo = LogType("INFO") ) // Underlier is implemented by the LogSink of this logger. It provides access // to additional APIs that are normally hidden behind the Logger API. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type Underlier interface { // GetUnderlying returns the testing instance that logging goes to. // It returns nil when the test has completed already. GetUnderlying() TL // GetBuffer grants access to the in-memory copy of the log entries. GetBuffer() Buffer } type buffer struct { mutex sync.Mutex text strings.Builder log Log } func (b *buffer) String() string { b.mutex.Lock() defer b.mutex.Unlock() return b.text.String() } func (b *buffer) Data() Log { b.mutex.Lock() defer b.mutex.Unlock() return b.log.DeepCopy() } // tloggerShared holds values that are the same for all LogSink instances. It // gets referenced by pointer in the tlogger struct. type tloggerShared struct { // mutex protects access to t. mutex sync.Mutex // t gets cleared when the test is completed. t TL // We warn once when a leaked goroutine is detected because // it logs after test completion. goroutineWarningDone bool testName string config *Config buffer buffer callDepth int } func (ls *tloggerShared) stop() { ls.mutex.Lock() defer ls.mutex.Unlock() ls.t = nil } // tlogger is the actual LogSink implementation. type tlogger struct { shared *tloggerShared prefix string values []interface{} } func (l tlogger) fallbackLogger() logr.Logger { logger := klog.Background().WithValues(l.values...).WithName(l.shared.testName + " leaked goroutine") if l.prefix != "" { logger = logger.WithName(l.prefix) } // Skip direct caller (= Error or Info) plus the logr wrapper. logger = logger.WithCallDepth(l.shared.callDepth + 1) if !l.shared.goroutineWarningDone { logger.WithCallDepth(1).Error(nil, "WARNING: test kept at least one goroutine running after test completion", "callstack", string(dbg.Stacks(false))) l.shared.goroutineWarningDone = true } return logger } func (l tlogger) Init(info logr.RuntimeInfo) { l.shared.callDepth = info.CallDepth } func (l tlogger) GetCallStackHelper() func() { l.shared.mutex.Lock() defer l.shared.mutex.Unlock() if l.shared.t == nil { return func() {} } return l.shared.t.Helper } func (l tlogger) Info(level int, msg string, kvList ...interface{}) { l.shared.mutex.Lock() defer l.shared.mutex.Unlock() if l.shared.t == nil { l.fallbackLogger().V(level).Info(msg, kvList...) return } l.shared.t.Helper() buffer := &bytes.Buffer{} merged := serialize.MergeKVs(l.values, kvList) serialize.KVListFormat(buffer, merged...) l.log(LogInfo, msg, level, buffer, nil, kvList) } func (l tlogger) Enabled(level int) bool { return l.shared.config.vstate.Enabled(verbosity.Level(level), 1) } func (l tlogger) Error(err error, msg string, kvList ...interface{}) { l.shared.mutex.Lock() defer l.shared.mutex.Unlock() if l.shared.t == nil { l.fallbackLogger().Error(err, msg, kvList...) return } l.shared.t.Helper() buffer := &bytes.Buffer{} if err != nil { serialize.KVListFormat(buffer, "err", err) } merged := serialize.MergeKVs(l.values, kvList) serialize.KVListFormat(buffer, merged...) l.log(LogError, msg, 0, buffer, err, kvList) } func (l tlogger) log(what LogType, msg string, level int, buffer *bytes.Buffer, err error, kvList []interface{}) { l.shared.t.Helper() args := []interface{}{what} if l.prefix != "" { args = append(args, l.prefix+":") } args = append(args, msg) if buffer.Len() > 0 { // Skip leading space inserted by serialize.KVListFormat. args = append(args, string(buffer.Bytes()[1:])) } l.shared.t.Log(args...) l.shared.buffer.mutex.Lock() defer l.shared.buffer.mutex.Unlock() // Store as text. l.shared.buffer.text.WriteString(string(what)) for i := 1; i < len(args); i++ { l.shared.buffer.text.WriteByte(' ') l.shared.buffer.text.WriteString(args[i].(string)) } lastArg := args[len(args)-1].(string) if lastArg[len(lastArg)-1] != '\n' { l.shared.buffer.text.WriteByte('\n') } // Store as raw data. l.shared.buffer.log = append(l.shared.buffer.log, LogEntry{ Timestamp: time.Now(), Type: what, Prefix: l.prefix, Message: msg, Verbosity: level, Err: err, WithKVList: l.values, ParameterKVList: kvList, }, ) } // WithName returns a new logr.Logger with the specified name appended. klogr // uses '/' characters to separate name elements. Callers should not pass '/' // in the provided name string, but this library does not actually enforce that. func (l tlogger) WithName(name string) logr.LogSink { if len(l.prefix) > 0 { l.prefix = l.prefix + "/" } l.prefix += name return l } func (l tlogger) WithValues(kvList ...interface{}) logr.LogSink { l.values = serialize.WithValues(l.values, kvList) return l } func (l tlogger) GetUnderlying() TL { return l.shared.t } func (l tlogger) GetBuffer() Buffer { return &l.shared.buffer } var _ logr.LogSink = &tlogger{} var _ logr.CallStackHelperLogSink = &tlogger{} var _ Underlier = &tlogger{} golang-k8s-klog-2.80.1/ktesting/testinglogger_test.go000066400000000000000000000176241432555450600226240ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Copyright 2020 Intel Coporation. SPDX-License-Identifier: Apache-2.0 */ package ktesting_test import ( "bytes" "errors" "flag" "fmt" "regexp" "runtime" "sync" "testing" "k8s.io/klog/v2" "k8s.io/klog/v2/ktesting" ) func TestInfo(t *testing.T) { tests := map[string]struct { text string withValues []interface{} keysAndValues []interface{} names []string err error expectedOutput string }{ "should log with values passed to keysAndValues": { text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `INFO test akey="avalue" `, }, "should support single name": { names: []string{"hello"}, text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `INFO hello: test akey="avalue" `, }, "should support multiple names": { names: []string{"hello", "world"}, text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `INFO hello/world: test akey="avalue" `, }, "should not print duplicate keys with the same value": { text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue"}, expectedOutput: `INFO test akey="avalue" akey="avalue" `, }, "should only print the last duplicate key when the values are passed to Info": { text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey", "avalue2"}, expectedOutput: `INFO test akey="avalue" akey="avalue2" `, }, "should only print the duplicate key that is passed to Info if one was passed to the logger": { withValues: []interface{}{"akey", "avalue"}, text: "test", keysAndValues: []interface{}{"akey", "avalue"}, expectedOutput: `INFO test akey="avalue" `, }, "should only print the key passed to Info when one is already set on the logger": { withValues: []interface{}{"akey", "avalue"}, text: "test", keysAndValues: []interface{}{"akey", "avalue2"}, expectedOutput: `INFO test akey="avalue2" `, }, "should correctly handle odd-numbers of KVs": { text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: `INFO test akey="avalue" akey2="(MISSING)" `, }, "should correctly html characters": { text: "test", keysAndValues: []interface{}{"akey", "<&>"}, expectedOutput: `INFO test akey="<&>" `, }, "should correctly handle odd-numbers of KVs in both log values and Info args": { withValues: []interface{}{"basekey1", "basevar1", "basekey2"}, text: "test", keysAndValues: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: `INFO test basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `, }, "should correctly print regular error types": { text: "test", keysAndValues: []interface{}{"err", errors.New("whoops")}, expectedOutput: `INFO test err="whoops" `, }, "should correctly print regular error types when using logr.Error": { text: "test", err: errors.New("whoops"), expectedOutput: `ERROR test err="whoops" `, }, } for n, test := range tests { t.Run(n, func(t *testing.T) { var buffer logToBuf klogr := ktesting.NewLogger(&buffer, ktesting.NewConfig()) for _, name := range test.names { klogr = klogr.WithName(name) } klogr = klogr.WithValues(test.withValues...) if test.err != nil { klogr.Error(test.err, test.text, test.keysAndValues...) } else { klogr.Info(test.text, test.keysAndValues...) } actual := buffer.String() if actual != test.expectedOutput { t.Errorf("Expected:\n%sActual:\n%s\n", test.expectedOutput, actual) } }) } } func TestCallDepth(t *testing.T) { logger := ktesting.NewLogger(t, ktesting.NewConfig()) logger.Info("hello world") } type logToBuf struct { ktesting.NopTL bytes.Buffer } func (l *logToBuf) Helper() { } func (l *logToBuf) Log(args ...interface{}) { for i, arg := range args { if i > 0 { l.Write([]byte(" ")) } l.Write([]byte(fmt.Sprintf("%s", arg))) } l.Write([]byte("\n")) } func TestStop(t *testing.T) { // This test is set up so that a subtest spawns a goroutine and that // goroutine logs through ktesting *after* the subtest has // completed. This is not supported by testing.T.Log and normally // leads to: // panic: Log in goroutine after TestGoroutines/Sub has completed: INFO hello world // // It works with ktesting if (and only if) logging gets redirected to klog // before returning from the test. // Capture output for testing. state := klog.CaptureState() defer state.Restore() var output bytes.Buffer var fs flag.FlagSet klog.InitFlags(&fs) fs.Set("alsologtostderr", "false") fs.Set("logtostderr", "false") fs.Set("stderrthreshold", "FATAL") fs.Set("one_output", "true") klog.SetOutput(&output) var logger klog.Logger var line int var wg1, wg2 sync.WaitGroup wg1.Add(1) wg2.Add(1) t.Run("Sub", func(t *testing.T) { logger, _ = ktesting.NewTestContext(t) go func() { defer wg2.Done() // Wait for test to have returned. wg1.Wait() // This output must go to klog because the test has // completed. _, _, line, _ = runtime.Caller(0) logger.Info("simple info message") logger.Error(nil, "error message") logger.WithName("me").WithValues("completed", true).Info("complex info message", "anotherValue", 1) }() }) // Allow goroutine above to proceed. wg1.Done() // Ensure that goroutine has completed. wg2.Wait() actual := output.String() // Strip time and pid prefix. actual = regexp.MustCompile(`(?m)^.* testinglogger_test.go:`).ReplaceAllString(actual, `testinglogger_test.go:`) // All lines from the callstack get stripped. We can be sure that it was non-empty because otherwise we wouldn't // have the < > markers. // // Full output: // testinglogger_test.go:194] "TestStop/Sub leaked goroutine: WARNING: test kept at least one goroutine running after test completion" callstack=< // goroutine 23 [running]: // k8s.io/klog/v2/internal/dbg.Stacks(0x0) // /nvme/gopath/src/k8s.io/klog/internal/dbg/dbg.go:34 +0x8a // k8s.io/klog/v2/ktesting.tlogger.fallbackLogger({0xc0000f2780, {0x0, 0x0}, {0x0, 0x0, 0x0}}) // /nvme/gopath/src/k8s.io/klog/ktesting/testinglogger.go:292 +0x232 // k8s.io/klog/v2/ktesting.tlogger.Info({0xc0000f2780, {0x0, 0x0}, {0x0, 0x0, 0x0}}, 0x0, {0x5444a5, 0x13}, {0x0, ...}) // /nvme/gopath/src/k8s.io/klog/ktesting/testinglogger.go:316 +0x28a // github.com/go-logr/logr.Logger.Info({{0x572438?, 0xc0000c0ff0?}, 0x0?}, {0x5444a5, 0x13}, {0x0, 0x0, 0x0}) // /nvme/gopath/pkg/mod/github.com/go-logr/logr@v1.2.0/logr.go:249 +0xd0 // k8s.io/klog/v2/ktesting_test.TestStop.func1.1() // /nvme/gopath/src/k8s.io/klog/ktesting/testinglogger_test.go:194 +0xe5 // created by k8s.io/klog/v2/ktesting_test.TestStop.func1 // /nvme/gopath/src/k8s.io/klog/ktesting/testinglogger_test.go:185 +0x105 // > actual = regexp.MustCompile(`(?m)^\t.*?\n`).ReplaceAllString(actual, ``) expected := fmt.Sprintf(`testinglogger_test.go:%d] "TestStop/Sub leaked goroutine: WARNING: test kept at least one goroutine running after test completion" callstack=< > testinglogger_test.go:%d] "TestStop/Sub leaked goroutine: simple info message" testinglogger_test.go:%d] "TestStop/Sub leaked goroutine: error message" testinglogger_test.go:%d] "TestStop/Sub leaked goroutine/me: complex info message" completed=true anotherValue=1 `, line+1, line+1, line+2, line+3) if actual != expected { t.Errorf("Output does not match. Expected:\n%s\nActual:\n%s\n", expected, actual) } testingLogger, ok := logger.GetSink().(ktesting.Underlier) if !ok { t.Fatal("should have had a ktesting logger") } captured := testingLogger.GetBuffer().String() if captured != "" { t.Errorf("testing logger should not have captured any output, got instead:\n%s", captured) } } golang-k8s-klog-2.80.1/test/000077500000000000000000000000001432555450600154765ustar00rootroot00000000000000golang-k8s-klog-2.80.1/test/output.go000066400000000000000000000634561432555450600174030ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 test contains a reusable unit test for logging output and behavior. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. package test import ( "bytes" "encoding/json" "errors" "flag" "fmt" "io" "regexp" "runtime" "strconv" "strings" "testing" "time" "github.com/go-logr/logr" "k8s.io/klog/v2" ) // InitKlog must be called once in an init function of a test package to // configure klog for testing with Output. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func InitKlog() { // klog gets configured so that it writes to a single output file that // will be set during tests with SetOutput. klog.InitFlags(nil) flag.Set("v", "10") flag.Set("log_file", "/dev/null") flag.Set("logtostderr", "false") flag.Set("alsologtostderr", "false") flag.Set("stderrthreshold", "10") } // OutputConfig contains optional settings for Output. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type OutputConfig struct { // NewLogger is called to create a new logger. If nil, output via klog // is tested. Support for -vmodule is optional. ClearLogger is called // after each test, therefore it is okay to user SetLogger without // undoing that in the callback. NewLogger func(out io.Writer, v int, vmodule string) logr.Logger // AsBackend enables testing through klog and the logger set there with // SetLogger. AsBackend bool // ExpectedOutputMapping replaces the builtin expected output for test // cases with something else. If nil or a certain case is not present, // the original text is used. // // The expected output uses as a placeholder for the line of the // log call. The source code is always the output.go file itself. When // testing a logger directly, is used for the first // WithValues call, for a second and // for a third. ExpectedOutputMapping map[string]string // SupportsVModule indicates that the logger supports the vmodule // parameter. Ignored when logging through klog. SupportsVModule bool } // Output covers various special cases of emitting log output. // It can be used for arbitrary logr.Logger implementations. // // The expected output is what klog would print. When testing loggers // that emit different output, a mapping from klog output to the // corresponding logger output must be provided, otherwise the // test will compare against the expected klog output. // // Loggers will be tested with direct calls to Info or // as backend for klog. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. The test cases and thus the expected output also may // change. func Output(t *testing.T, config OutputConfig) { tests := map[string]struct { withHelper bool // use wrappers that get skipped during stack unwinding withNames []string // For a first WithValues call: logger1 := logger.WithValues() withValues []interface{} // For another WithValues call: logger2 := logger1.WithValues() moreValues []interface{} // For another WithValues call on the same logger as before: logger3 := logger1.WithValues() evenMoreValues []interface{} v int vmodule string text string values []interface{} err error expectedOutput string }{ "log with values": { text: "test", values: []interface{}{"akey", "avalue"}, expectedOutput: `I output.go:] "test" akey="avalue" `, }, "call depth": { text: "helper", withHelper: true, values: []interface{}{"akey", "avalue"}, expectedOutput: `I output.go:] "helper" akey="avalue" `, }, "verbosity enabled": { text: "you see me", v: 9, expectedOutput: `I output.go:] "you see me" `, }, "verbosity disabled": { text: "you don't see me", v: 11, }, "vmodule": { text: "v=11: you see me because of -vmodule output=11", v: 11, vmodule: "output=11", }, "other vmodule": { text: "v=11: you still don't see me because of -vmodule output_helper=11", v: 11, vmodule: "output_helper=11", }, "log with name and values": { withNames: []string{"me"}, text: "test", values: []interface{}{"akey", "avalue"}, expectedOutput: `I output.go:] "me: test" akey="avalue" `, }, "log with multiple names and values": { withNames: []string{"hello", "world"}, text: "test", values: []interface{}{"akey", "avalue"}, expectedOutput: `I output.go:] "hello/world: test" akey="avalue" `, }, "override single value": { withValues: []interface{}{"akey", "avalue"}, text: "test", values: []interface{}{"akey", "avalue2"}, expectedOutput: `I output.go:] "test" akey="avalue2" `, }, "override WithValues": { withValues: []interface{}{"duration", time.Hour, "X", "y"}, text: "test", values: []interface{}{"duration", time.Minute, "A", "b"}, expectedOutput: `I output.go:] "test" X="y" duration="1m0s" A="b" `, }, "odd WithValues": { withValues: []interface{}{"keyWithoutValue"}, moreValues: []interface{}{"anotherKeyWithoutValue"}, text: "odd WithValues", expectedOutput: `I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" anotherKeyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" `, }, "multiple WithValues": { withValues: []interface{}{"firstKey", 1}, moreValues: []interface{}{"secondKey", 2}, evenMoreValues: []interface{}{"secondKey", 3}, text: "test", expectedOutput: `I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=2 I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=3 `, }, "empty WithValues": { withValues: []interface{}{}, text: "test", expectedOutput: `I output.go:] "test" `, }, "print duplicate keys in arguments": { text: "test", values: []interface{}{"akey", "avalue", "akey", "avalue2"}, expectedOutput: `I output.go:] "test" akey="avalue" akey="avalue2" `, }, "preserve order of key/value pairs": { withValues: []interface{}{"akey9", "avalue9", "akey8", "avalue8", "akey1", "avalue1"}, text: "test", values: []interface{}{"akey5", "avalue5", "akey4", "avalue4"}, expectedOutput: `I output.go:] "test" akey9="avalue9" akey8="avalue8" akey1="avalue1" akey5="avalue5" akey4="avalue4" `, }, "handle odd-numbers of KVs": { text: "odd arguments", values: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: `I output.go:] "odd arguments" akey="avalue" akey2="(MISSING)" `, }, "html characters": { text: "test", values: []interface{}{"akey", "<&>"}, expectedOutput: `I output.go:] "test" akey="<&>" `, }, "quotation": { text: `"quoted"`, values: []interface{}{"key", `"quoted value"`}, expectedOutput: `I output.go:] "\"quoted\"" key="\"quoted value\"" `, }, "handle odd-numbers of KVs in both log values and Info args": { withValues: []interface{}{"basekey1", "basevar1", "basekey2"}, text: "both odd", values: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: `I output.go:] "both odd" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `, }, "KObj": { text: "test", values: []interface{}{"pod", klog.KObj(&kmeta{Name: "pod-1", Namespace: "kube-system"})}, expectedOutput: `I output.go:] "test" pod="kube-system/pod-1" `, }, "KObjs": { text: "test", values: []interface{}{"pods", klog.KObjs([]interface{}{ &kmeta{Name: "pod-1", Namespace: "kube-system"}, &kmeta{Name: "pod-2", Namespace: "kube-system"}, })}, expectedOutput: `I output.go:] "test" pods=[kube-system/pod-1 kube-system/pod-2] `, }, "KObjSlice okay": { text: "test", values: []interface{}{"pods", klog.KObjSlice([]interface{}{ &kmeta{Name: "pod-1", Namespace: "kube-system"}, &kmeta{Name: "pod-2", Namespace: "kube-system"}, })}, expectedOutput: `I output.go:] "test" pods="[kube-system/pod-1 kube-system/pod-2]" `, }, "KObjSlice nil arg": { text: "test", values: []interface{}{"pods", klog.KObjSlice(nil)}, expectedOutput: `I output.go:] "test" pods="[]" `, }, "KObjSlice int arg": { text: "test", values: []interface{}{"pods", klog.KObjSlice(1)}, expectedOutput: `I output.go:] "test" pods="" `, }, "KObjSlice nil entry": { text: "test", values: []interface{}{"pods", klog.KObjSlice([]interface{}{ &kmeta{Name: "pod-1", Namespace: "kube-system"}, nil, })}, expectedOutput: `I output.go:] "test" pods="[kube-system/pod-1 ]" `, }, "KObjSlice ints": { text: "test", values: []interface{}{"ints", klog.KObjSlice([]int{1, 2, 3})}, expectedOutput: `I output.go:] "test" ints="" `, }, "regular error types as value": { text: "test", values: []interface{}{"err", errors.New("whoops")}, expectedOutput: `I output.go:] "test" err="whoops" `, }, "ignore MarshalJSON": { text: "test", values: []interface{}{"err", &customErrorJSON{"whoops"}}, expectedOutput: `I output.go:] "test" err="whoops" `, }, "regular error types when using logr.Error": { text: "test", err: errors.New("whoops"), expectedOutput: `E output.go:] "test" err="whoops" `, }, "Error() for nil": { text: "error nil", err: (*customErrorJSON)(nil), expectedOutput: `E output.go:] "error nil" err="" `, }, "String() for nil": { text: "stringer nil", values: []interface{}{"stringer", (*stringer)(nil)}, expectedOutput: `I output.go:] "stringer nil" stringer="" `, }, "MarshalLog() for nil": { text: "marshaler nil", values: []interface{}{"obj", (*klog.ObjectRef)(nil)}, expectedOutput: `I output.go:] "marshaler nil" obj="" `, }, "Error() that panics": { text: "error panic", err: faultyError{}, expectedOutput: `E output.go:] "error panic" err="" `, }, "String() that panics": { text: "stringer panic", values: []interface{}{"stringer", faultyStringer{}}, expectedOutput: `I output.go:] "stringer panic" stringer="" `, }, "MarshalLog() that panics": { text: "marshaler panic", values: []interface{}{"obj", faultyMarshaler{}}, expectedOutput: `I output.go:] "marshaler panic" obj="" `, }, "MarshalLog() that returns itself": { text: "marshaler recursion", values: []interface{}{"obj", recursiveMarshaler{}}, expectedOutput: `I output.go:] "marshaler recursion" obj={} `, }, "handle integer keys": { withValues: []interface{}{1, "value", 2, "value2"}, text: "integer keys", values: []interface{}{"akey", "avalue", "akey2"}, expectedOutput: `I output.go:] "integer keys" %!s(int=1)="value" %!s(int=2)="value2" akey="avalue" akey2="(MISSING)" `, }, "struct keys": { withValues: []interface{}{struct{ name string }{"name"}, "value", "test", "other value"}, text: "struct keys", values: []interface{}{"key", "val"}, expectedOutput: `I output.go:] "struct keys" {name}="value" test="other value" key="val" `, }, "map keys": { withValues: []interface{}{}, text: "map keys", values: []interface{}{map[string]bool{"test": true}, "test"}, expectedOutput: `I output.go:] "map keys" map[test:%!s(bool=true)]="test" `, }, } for n, test := range tests { t.Run(n, func(t *testing.T) { defer klog.ClearLogger() printWithLogger := func(logger logr.Logger) { for _, name := range test.withNames { logger = logger.WithName(name) } // When we have multiple WithValues calls, we test // first with the initial set of additional values, then // the combination, then again the original logger. // It must not have been modified. This produces // three log entries. logger = logger.WithValues(test.withValues...) // loggers := []logr.Logger{logger} if test.moreValues != nil { loggers = append(loggers, logger.WithValues(test.moreValues...), logger) // } if test.evenMoreValues != nil { loggers = append(loggers, logger.WithValues(test.evenMoreValues...)) // } for _, logger := range loggers { if test.withHelper { loggerHelper(logger, test.text, test.values) // } else if test.err != nil { logger.Error(test.err, test.text, test.values...) // } else { logger.V(test.v).Info(test.text, test.values...) // } } } _, _, printWithLoggerLine, _ := runtime.Caller(0) printWithKlog := func() { kv := []interface{}{} haveKeyInValues := func(key interface{}) bool { for i := 0; i < len(test.values); i += 2 { if key == test.values[i] { return true } } return false } appendKV := func(withValues []interface{}) { if len(withValues)%2 != 0 { withValues = append(withValues, "(MISSING)") } for i := 0; i < len(withValues); i += 2 { if !haveKeyInValues(withValues[i]) { kv = append(kv, withValues[i], withValues[i+1]) } } } // Here we need to emulate the handling of WithValues above. appendKV(test.withValues) kvs := [][]interface{}{copySlice(kv)} if test.moreValues != nil { appendKV(test.moreValues) kvs = append(kvs, copySlice(kv), copySlice(kvs[0])) } if test.evenMoreValues != nil { kv = copySlice(kvs[0]) appendKV(test.evenMoreValues) kvs = append(kvs, copySlice(kv)) } for _, kv := range kvs { if len(test.values) > 0 { kv = append(kv, test.values...) } text := test.text if len(test.withNames) > 0 { text = strings.Join(test.withNames, "/") + ": " + text } if test.withHelper { klogHelper(text, kv) } else if test.err != nil { klog.ErrorS(test.err, text, kv...) } else { klog.V(klog.Level(test.v)).InfoS(text, kv...) } } } _, _, printWithKlogLine, _ := runtime.Caller(0) testOutput := func(t *testing.T, expectedLine int, print func(buffer *bytes.Buffer)) { var tmpWriteBuffer bytes.Buffer klog.SetOutput(&tmpWriteBuffer) print(&tmpWriteBuffer) klog.Flush() actual := tmpWriteBuffer.String() // Strip varying header. re := `(?m)^(.).... ..:..:......... ....... output.go` actual = regexp.MustCompile(re).ReplaceAllString(actual, `${1} output.go`) // Inject expected line. This matches the if checks above, which are // the same for both printWithKlog and printWithLogger. callLine := expectedLine if test.withHelper { callLine -= 8 } else if test.err != nil { callLine -= 6 } else { callLine -= 4 } expected := test.expectedOutput if repl, ok := config.ExpectedOutputMapping[expected]; ok { expected = repl } expectedWithPlaceholder := expected expected = strings.ReplaceAll(expected, "", fmt.Sprintf("%d", callLine)) expected = strings.ReplaceAll(expected, "", fmt.Sprintf("%d", expectedLine-18)) expected = strings.ReplaceAll(expected, "", fmt.Sprintf("%d", expectedLine-15)) expected = strings.ReplaceAll(expected, "", fmt.Sprintf("%d", expectedLine-12)) if actual != expected { if expectedWithPlaceholder == test.expectedOutput { t.Errorf("Output mismatch. Expected:\n%s\nActual:\n%s\n", expectedWithPlaceholder, actual) } else { t.Errorf("Output mismatch. klog:\n%s\nExpected:\n%s\nActual:\n%s\n", test.expectedOutput, expectedWithPlaceholder, actual) } } } if config.NewLogger == nil { // Test klog. testOutput(t, printWithKlogLine, func(buffer *bytes.Buffer) { printWithKlog() }) return } if config.AsBackend { testOutput(t, printWithKlogLine, func(buffer *bytes.Buffer) { klog.SetLogger(config.NewLogger(buffer, 10, "")) printWithKlog() }) return } if test.vmodule != "" && !config.SupportsVModule { t.Skip("vmodule not supported") } testOutput(t, printWithLoggerLine, func(buffer *bytes.Buffer) { printWithLogger(config.NewLogger(buffer, 10, test.vmodule)) }) }) } if config.NewLogger == nil || config.AsBackend { // Test all klog output functions. // // Each test case must be defined with the same number of // lines, then the source code location of the call itself // can be computed below. tests := []struct { name string logFunc func() output string }{ { name: "Info", logFunc: func() { klog.Info("hello", "world") }, output: "I output.go:] helloworld\n", // This looks odd, but simply is how klog works. }, { name: "InfoDepth", logFunc: func() { klog.InfoDepth(0, "hello", "world") }, output: "I output.go:] helloworld\n", }, { name: "Infoln", logFunc: func() { klog.Infoln("hello", "world") }, output: "I output.go:] hello world\n", }, { name: "InfolnDepth", logFunc: func() { klog.InfolnDepth(0, "hello", "world") }, output: "I output.go:] hello world\n", }, { name: "Infof", logFunc: func() { klog.Infof("hello %s", "world") }, output: "I output.go:] hello world\n", }, { name: "InfofDepth", logFunc: func() { klog.InfofDepth(0, "hello %s", "world") }, output: "I output.go:] hello world\n", }, { name: "InfoS", logFunc: func() { klog.InfoS("hello", "what", "world") }, output: "I output.go:] \"hello\" what=\"world\"\n", }, { name: "InfoSDepth", logFunc: func() { klog.InfoSDepth(0, "hello", "what", "world") }, output: "I output.go:] \"hello\" what=\"world\"\n", }, { name: "Warning", logFunc: func() { klog.Warning("hello", "world") }, output: "W output.go:] helloworld\n", }, { name: "WarningDepth", logFunc: func() { klog.WarningDepth(0, "hello", "world") }, output: "W output.go:] helloworld\n", }, { name: "Warningln", logFunc: func() { klog.Warningln("hello", "world") }, output: "W output.go:] hello world\n", }, { name: "WarninglnDepth", logFunc: func() { klog.WarninglnDepth(0, "hello", "world") }, output: "W output.go:] hello world\n", }, { name: "Warningf", logFunc: func() { klog.Warningf("hello %s", "world") }, output: "W output.go:] hello world\n", }, { name: "WarningfDepth", logFunc: func() { klog.WarningfDepth(0, "hello %s", "world") }, output: "W output.go:] hello world\n", }, { name: "Error", logFunc: func() { klog.Error("hello", "world") }, output: "E output.go:] helloworld\n", }, { name: "ErrorDepth", logFunc: func() { klog.ErrorDepth(0, "hello", "world") }, output: "E output.go:] helloworld\n", }, { name: "Errorln", logFunc: func() { klog.Errorln("hello", "world") }, output: "E output.go:] hello world\n", }, { name: "ErrorlnDepth", logFunc: func() { klog.ErrorlnDepth(0, "hello", "world") }, output: "E output.go:] hello world\n", }, { name: "Errorf", logFunc: func() { klog.Errorf("hello %s", "world") }, output: "E output.go:] hello world\n", }, { name: "ErrorfDepth", logFunc: func() { klog.ErrorfDepth(0, "hello %s", "world") }, output: "E output.go:] hello world\n", }, { name: "ErrorS", logFunc: func() { klog.ErrorS(errors.New("hello"), "world") }, output: "E output.go:] \"world\" err=\"hello\"\n", }, { name: "ErrorSDepth", logFunc: func() { klog.ErrorSDepth(0, errors.New("hello"), "world") }, output: "E output.go:] \"world\" err=\"hello\"\n", }, { name: "V().Info", logFunc: func() { klog.V(1).Info("hello", "one", "world") }, output: "I output.go:] hellooneworld\n", }, { name: "V().InfoDepth", logFunc: func() { klog.V(1).InfoDepth(0, "hello", "one", "world") }, output: "I output.go:] hellooneworld\n", }, { name: "V().Infoln", logFunc: func() { klog.V(1).Infoln("hello", "one", "world") }, output: "I output.go:] hello one world\n", }, { name: "V().InfolnDepth", logFunc: func() { klog.V(1).InfolnDepth(0, "hello", "one", "world") }, output: "I output.go:] hello one world\n", }, { name: "V().Infof", logFunc: func() { klog.V(1).Infof("hello %s %s", "one", "world") }, output: "I output.go:] hello one world\n", }, { name: "V().InfofDepth", logFunc: func() { klog.V(1).InfofDepth(0, "hello %s %s", "one", "world") }, output: "I output.go:] hello one world\n", }, { name: "V().InfoS", logFunc: func() { klog.V(1).InfoS("hello", "what", "one world") }, output: "I output.go:] \"hello\" what=\"one world\"\n", }, { name: "V().InfoSDepth", logFunc: func() { klog.V(1).InfoSDepth(0, "hello", "what", "one world") }, output: "I output.go:] \"hello\" what=\"one world\"\n", }, { name: "V().ErrorS", logFunc: func() { klog.V(1).ErrorS(errors.New("hello"), "one world") }, output: "E output.go:] \"one world\" err=\"hello\"\n", }, } _, _, line, _ := runtime.Caller(0) for i, test := range tests { t.Run(test.name, func(t *testing.T) { var buffer bytes.Buffer if config.NewLogger == nil { klog.SetOutput(&buffer) } else { klog.SetLogger(config.NewLogger(&buffer, 10, "")) defer klog.ClearLogger() } test.logFunc() klog.Flush() actual := buffer.String() // Strip varying header. re := `(?m)^(.).... ..:..:......... ....... output.go` actual = regexp.MustCompile(re).ReplaceAllString(actual, `${1} output.go`) // Inject expected line. This matches the if checks above, which are // the same for both printWithKlog and printWithLogger. callLine := line + 1 - (len(tests)-i)*5 expected := test.output // When klog does string formating for // non-structured calls, it passes the entire // result, including a trailing newline, to // Logger.Info. if config.NewLogger != nil && !strings.HasSuffix(test.name, "S") && !strings.HasSuffix(test.name, "SDepth") { // klog: I output.go:] hello world // with logger: I output.go:] "hello world\n" index := strings.Index(expected, "] ") if index == -1 { t.Fatalf("did not find ] separator: %s", expected) } expected = expected[0:index+2] + strconv.Quote(expected[index+2:]) + "\n" // Warnings become info messages. if strings.HasPrefix(expected, "W") { expected = "I" + expected[1:] } } if repl, ok := config.ExpectedOutputMapping[expected]; ok { expected = repl } expectedWithPlaceholder := expected expected = strings.ReplaceAll(expected, "", fmt.Sprintf("%d", callLine)) if actual != expected { if expectedWithPlaceholder == test.output { t.Errorf("Output mismatch. Expected:\n%s\nActual:\n%s\n", expectedWithPlaceholder, actual) } else { t.Errorf("Output mismatch. klog:\n%s\nExpected:\n%s\nActual:\n%s\n", test.output, expectedWithPlaceholder, actual) } } }) } } } func copySlice(in []interface{}) []interface{} { return append([]interface{}{}, in...) } type kmeta struct { Name, Namespace string } func (k kmeta) GetName() string { return k.Name } func (k kmeta) GetNamespace() string { return k.Namespace } var _ klog.KMetadata = kmeta{} type customErrorJSON struct { s string } var _ error = &customErrorJSON{} var _ json.Marshaler = &customErrorJSON{} func (e *customErrorJSON) Error() string { return e.s } func (e *customErrorJSON) MarshalJSON() ([]byte, error) { return json.Marshal(strings.ToUpper(e.s)) } type stringer struct { s string } // String crashes when called for nil. func (s *stringer) String() string { return s.s } var _ fmt.Stringer = &stringer{} type faultyStringer struct{} // String always panics. func (f faultyStringer) String() string { panic("fake String panic") } var _ fmt.Stringer = faultyStringer{} type faultyMarshaler struct{} // MarshalLog always panics. func (f faultyMarshaler) MarshalLog() interface{} { panic("fake MarshalLog panic") } var _ logr.Marshaler = faultyMarshaler{} type recursiveMarshaler struct{} // MarshalLog returns itself, which could cause the logger to recurse infinitely. func (r recursiveMarshaler) MarshalLog() interface{} { return r } var _ logr.Marshaler = recursiveMarshaler{} type faultyError struct{} // Error always panics. func (f faultyError) Error() string { panic("fake Error panic") } var _ error = faultyError{} golang-k8s-klog-2.80.1/test/output_helper.go000066400000000000000000000015311432555450600207240ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 test import ( "github.com/go-logr/logr" "k8s.io/klog/v2" ) func loggerHelper(logger logr.Logger, msg string, kv []interface{}) { logger = logger.WithCallDepth(1) logger.Info(msg, kv...) } func klogHelper(msg string, kv []interface{}) { klog.InfoSDepth(1, msg, kv...) } golang-k8s-klog-2.80.1/test/zapr.go000066400000000000000000000343561432555450600170140ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes 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 test // ZaprOutputMappingDirect provides a mapping from klog output to the // corresponding zapr output when zapr is called directly. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. func ZaprOutputMappingDirect() map[string]string { return map[string]string{ `I output.go:] "test" akey="<&>" `: `{"caller":"test/output.go:","msg":"test","v":0,"akey":"<&>"} `, `E output.go:] "test" err="whoops" `: `{"caller":"test/output.go:","msg":"test","err":"whoops"} `, `I output.go:] "helper" akey="avalue" `: `{"caller":"test/output.go:","msg":"helper","v":0,"akey":"avalue"} `, `I output.go:] "test" akey="avalue" akey="avalue2" `: `{"caller":"test/output.go:","msg":"test","v":0,"akey":"avalue","akey":"avalue2"} `, `I output.go:] "hello/world: test" akey="avalue" `: `{"logger":"hello.world","caller":"test/output.go:","msg":"test","v":0,"akey":"avalue"} `, `I output.go:] "test" X="y" duration="1m0s" A="b" `: `{"caller":"test/output.go:","msg":"test","duration":"1h0m0s","X":"y","v":0,"duration":"1m0s","A":"b"} `, `I output.go:] "test" akey9="avalue9" akey8="avalue8" akey1="avalue1" akey5="avalue5" akey4="avalue4" `: `{"caller":"test/output.go:","msg":"test","akey9":"avalue9","akey8":"avalue8","akey1":"avalue1","v":0,"akey5":"avalue5","akey4":"avalue4"} `, `I output.go:] "test" `: `{"caller":"test/output.go:","msg":"test","v":0} `, `I output.go:] "\"quoted\"" key="\"quoted value\"" `: `{"caller":"test/output.go:","msg":"\"quoted\"","v":0,"key":"\"quoted value\""} `, `I output.go:] "test" err="whoops" `: `{"caller":"test/output.go:","msg":"test","v":0,"err":"whoops"} `, `I output.go:] "test" pod="kube-system/pod-1" `: `{"caller":"test/output.go:","msg":"test","v":0,"pod":{"name":"pod-1","namespace":"kube-system"}} `, `I output.go:] "test" pods=[kube-system/pod-1 kube-system/pod-2] `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},{"name":"pod-2","namespace":"kube-system"}]} `, `I output.go:] "test" pods="[kube-system/pod-1 kube-system/pod-2]" `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},{"name":"pod-2","namespace":"kube-system"}]} `, `I output.go:] "test" pods="[]" `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":null} `, `I output.go:] "test" pods="" `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":""} `, `I output.go:] "test" ints="" `: `{"caller":"test/output.go:","msg":"test","v":0,"ints":""} `, `I output.go:] "test" pods="[kube-system/pod-1 ]" `: `{"caller":"test/output.go:","msg":"test","v":0,"pods":[{"name":"pod-1","namespace":"kube-system"},null]} `, `I output.go:] "test" akey="avalue" `: `{"caller":"test/output.go:","msg":"test","v":0,"akey":"avalue"} `, `I output.go:] "me: test" akey="avalue" `: `{"logger":"me","caller":"test/output.go:","msg":"test","v":0,"akey":"avalue"} `, `I output.go:] "test" akey="avalue2" `: `{"caller":"test/output.go:","msg":"test","akey":"avalue","v":0,"akey":"avalue2"} `, `I output.go:] "you see me" `: `{"caller":"test/output.go:","msg":"you see me","v":9} `, `I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=2 I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=3 `: `{"caller":"test/output.go:","msg":"test","firstKey":1,"v":0} {"caller":"test/output.go:","msg":"test","firstKey":1,"secondKey":2,"v":0} {"caller":"test/output.go:","msg":"test","firstKey":1,"v":0} {"caller":"test/output.go:","msg":"test","firstKey":1,"secondKey":3,"v":0} `, `I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" anotherKeyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"keyWithoutValue"} {"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"anotherKeyWithoutValue"} {"caller":"test/output.go:","msg":"odd WithValues","v":0} {"caller":"test/output.go:","msg":"odd WithValues","v":0} {"caller":"test/output.go:","msg":"odd WithValues","v":0} `, `I output.go:] "odd arguments" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"akey2"} {"caller":"test/output.go:","msg":"odd arguments","v":0,"akey":"avalue"} `, `I output.go:] "both odd" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"basekey2"} {"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","basekey1":"basevar1","ignored key":"akey2"} {"caller":"test/output.go:","msg":"both odd","basekey1":"basevar1","v":0,"akey":"avalue"} `, `I output.go:] "marshaler nil" obj="" `: `{"caller":"test/output.go:","msg":"marshaler nil","v":0,"objError":"PANIC=value method k8s.io/klog/v2.ObjectRef.MarshalLog called using nil *ObjectRef pointer"} `, // zap replaces a panic for a nil object with . `E output.go:] "error nil" err="" `: `{"caller":"test/output.go:","msg":"error nil","err":""} `, `I output.go:] "stringer nil" stringer="" `: `{"caller":"test/output.go:","msg":"stringer nil","v":0,"stringer":""} `, `I output.go:] "stringer panic" stringer="" `: `{"caller":"test/output.go:","msg":"stringer panic","v":0,"stringerError":"PANIC=fake String panic"} `, `E output.go:] "error panic" err="" `: `{"caller":"test/output.go:","msg":"error panic","errError":"PANIC=fake Error panic"} `, `I output.go:] "marshaler panic" obj="" `: `{"caller":"test/output.go:","msg":"marshaler panic","v":0,"objError":"PANIC=fake MarshalLog panic"} `, `I output.go:] "marshaler recursion" obj={} `: `{"caller":"test/output.go:","msg":"marshaler recursion","v":0,"obj":{}} `, // klog.Info `I output.go:] "helloworld\n" `: `{"caller":"test/output.go:","msg":"helloworld\n","v":0} `, // klog.Infoln `I output.go:] "hello world\n" `: `{"caller":"test/output.go:","msg":"hello world\n","v":0} `, // klog.Error `E output.go:] "helloworld\n" `: `{"caller":"test/output.go:","msg":"helloworld\n"} `, // klog.Errorln `E output.go:] "hello world\n" `: `{"caller":"test/output.go:","msg":"hello world\n"} `, // klog.ErrorS `E output.go:] "world" err="hello" `: `{"caller":"test/output.go:","msg":"world","err":"hello"} `, // klog.InfoS `I output.go:] "hello" what="world" `: `{"caller":"test/output.go:","msg":"hello","v":0,"what":"world"} `, // klog.V(1).Info `I output.go:] "hellooneworld\n" `: `{"caller":"test/output.go:","msg":"hellooneworld\n","v":1} `, // klog.V(1).Infoln `I output.go:] "hello one world\n" `: `{"caller":"test/output.go:","msg":"hello one world\n","v":1} `, // klog.V(1).ErrorS `E output.go:] "one world" err="hello" `: `{"caller":"test/output.go:","msg":"one world","err":"hello"} `, // klog.V(1).InfoS `I output.go:] "hello" what="one world" `: `{"caller":"test/output.go:","msg":"hello","v":1,"what":"one world"} `, `I output.go:] "integer keys" %!s(int=1)="value" %!s(int=2)="value2" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":1} {"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"akey2"} {"caller":"test/output.go:","msg":"integer keys","v":0,"akey":"avalue"} `, `I output.go:] "struct keys" {name}="value" test="other value" key="val" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{}} {"caller":"test/output.go:","msg":"struct keys","v":0,"key":"val"} `, `I output.go:] "map keys" map[test:%!s(bool=true)]="test" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{"test":true}} {"caller":"test/output.go:","msg":"map keys","v":0} `, } } // ZaprOutputMappingIndirect provides a mapping from klog output to the // corresponding zapr output when zapr is called indirectly through // klog. // // This is different from ZaprOutputMappingDirect because: // - WithName gets added to the message by Output. // - zap uses . as separator instead of / between WithName values, // here we get slashes because Output concatenates these values. // - WithValues are added to the normal key/value parameters by // Output, which puts them after "v". // - Output does that without emitting the warning that we get // from zapr. // - zap drops keys with missing values, here we get "(MISSING)". // - zap does not de-duplicate key/value pairs, here klog does that // for it. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. func ZaprOutputMappingIndirect() map[string]string { mapping := ZaprOutputMappingDirect() for key, value := range map[string]string{ `I output.go:] "hello/world: test" akey="avalue" `: `{"caller":"test/output.go:","msg":"hello/world: test","v":0,"akey":"avalue"} `, `I output.go:] "me: test" akey="avalue" `: `{"caller":"test/output.go:","msg":"me: test","v":0,"akey":"avalue"} `, `I output.go:] "odd parameters" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"akey2"} {"caller":"test/output.go:","msg":"odd parameters","v":0,"basekey1":"basevar1","basekey2":"(MISSING)","akey":"avalue"} `, `I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" anotherKeyWithoutValue="(MISSING)" I output.go:] "odd WithValues" keyWithoutValue="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd WithValues","v":0,"keyWithoutValue":"(MISSING)"} {"caller":"test/output.go:","msg":"odd WithValues","v":0,"keyWithoutValue":"(MISSING)","anotherKeyWithoutValue":"(MISSING)"} {"caller":"test/output.go:","msg":"odd WithValues","v":0,"keyWithoutValue":"(MISSING)"} `, `I output.go:] "both odd" basekey1="basevar1" basekey2="(MISSING)" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"odd number of arguments passed as key-value pairs for logging","ignored key":"akey2"} {"caller":"test/output.go:","msg":"both odd","v":0,"basekey1":"basevar1","basekey2":"(MISSING)","akey":"avalue"} `, `I output.go:] "test" akey9="avalue9" akey8="avalue8" akey1="avalue1" akey5="avalue5" akey4="avalue4" `: `{"caller":"test/output.go:","msg":"test","v":0,"akey9":"avalue9","akey8":"avalue8","akey1":"avalue1","akey5":"avalue5","akey4":"avalue4"} `, `I output.go:] "test" akey="avalue2" `: `{"caller":"test/output.go:","msg":"test","v":0,"akey":"avalue2"} `, `I output.go:] "test" X="y" duration="1m0s" A="b" `: `{"caller":"test/output.go:","msg":"test","v":0,"X":"y","duration":"1m0s","A":"b"} `, `I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=2 I output.go:] "test" firstKey=1 I output.go:] "test" firstKey=1 secondKey=3 `: `{"caller":"test/output.go:","msg":"test","v":0,"firstKey":1} {"caller":"test/output.go:","msg":"test","v":0,"firstKey":1,"secondKey":2} {"caller":"test/output.go:","msg":"test","v":0,"firstKey":1} {"caller":"test/output.go:","msg":"test","v":0,"firstKey":1,"secondKey":3} `, `I output.go:] "integer keys" %!s(int=1)="value" %!s(int=2)="value2" akey="avalue" akey2="(MISSING)" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":1} {"caller":"test/output.go:","msg":"integer keys","v":0} `, `I output.go:] "struct keys" {name}="value" test="other value" key="val" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{}} {"caller":"test/output.go:","msg":"struct keys","v":0} `, `I output.go:] "map keys" map[test:%!s(bool=true)]="test" `: `{"caller":"test/output.go:","msg":"non-string key argument passed to logging, ignoring all later arguments","invalid key":{"test":true}} {"caller":"test/output.go:","msg":"map keys","v":0} `, } { mapping[key] = value } return mapping } golang-k8s-klog-2.80.1/textlogger/000077500000000000000000000000001432555450600167035ustar00rootroot00000000000000golang-k8s-klog-2.80.1/textlogger/options.go000066400000000000000000000074221432555450600207320ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes 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 textlogger import ( "flag" "io" "os" "strconv" "k8s.io/klog/v2/internal/verbosity" ) // Config influences logging in a text logger. To make this configurable via // command line flags, instantiate this once per program and use AddFlags to // bind command line flags to the instance before passing it to NewTestContext. // // Must be constructed with NewConfig. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type Config struct { *verbosity.VState co configOptions } // ConfigOption implements functional parameters for NewConfig. // // # Experimental // // Notice: This type is EXPERIMENTAL and may be changed or removed in a // later release. type ConfigOption func(co *configOptions) type configOptions struct { verbosityFlagName string vmoduleFlagName string verbosityDefault int output io.Writer } // VerbosityFlagName overrides the default -v for the verbosity level. func VerbosityFlagName(name string) ConfigOption { return func(co *configOptions) { co.verbosityFlagName = name } } // VModulFlagName overrides the default -vmodule for the per-module // verbosity levels. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func VModuleFlagName(name string) ConfigOption { return func(co *configOptions) { co.vmoduleFlagName = name } } // Verbosity overrides the default verbosity level of 0. // See https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions // for log level conventions in Kubernetes. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func Verbosity(level int) ConfigOption { return func(co *configOptions) { co.verbosityDefault = level } } // Output overrides stderr as the output stream. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func Output(output io.Writer) ConfigOption { return func(co *configOptions) { co.output = output } } // NewConfig returns a configuration with recommended defaults and optional // modifications. Command line flags are not bound to any FlagSet yet. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func NewConfig(opts ...ConfigOption) *Config { c := &Config{ VState: verbosity.New(), co: configOptions{ verbosityFlagName: "v", vmoduleFlagName: "vmodule", verbosityDefault: 0, output: os.Stderr, }, } for _, opt := range opts { opt(&c.co) } c.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10)) return c } // AddFlags registers the command line flags that control the configuration. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. func (c *Config) AddFlags(fs *flag.FlagSet) { fs.Var(c.V(), c.co.verbosityFlagName, "number for the log level verbosity of the testing logger") fs.Var(c.VModule(), c.co.vmoduleFlagName, "comma-separated list of pattern=N log level settings for files matching the patterns") } golang-k8s-klog-2.80.1/textlogger/textlogger.go000066400000000000000000000102151432555450600214150ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Copyright 2020 Intel Coporation. 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 textlogger contains an implementation of the logr interface // which is producing the exact same output as klog. // // # Experimental // // Notice: This package is EXPERIMENTAL and may be changed or removed in a // later release. package textlogger import ( "runtime" "strconv" "strings" "time" "github.com/go-logr/logr" "k8s.io/klog/v2/internal/buffer" "k8s.io/klog/v2/internal/serialize" "k8s.io/klog/v2/internal/severity" "k8s.io/klog/v2/internal/verbosity" ) var ( // TimeNow is used to retrieve the current time. May be changed for testing. // // Experimental // // Notice: This variable is EXPERIMENTAL and may be changed or removed in a // later release. TimeNow = time.Now ) // NewLogger constructs a new logger. // // # Experimental // // Notice: This function is EXPERIMENTAL and may be changed or removed in a // later release. The behavior of the returned Logger may change. func NewLogger(c *Config) logr.Logger { return logr.New(&tlogger{ prefix: "", values: nil, config: c, bufferCache: &buffer.Buffers{}, }) } type tlogger struct { callDepth int prefix string values []interface{} config *Config bufferCache *buffer.Buffers } func copySlice(in []interface{}) []interface{} { out := make([]interface{}, len(in)) copy(out, in) return out } func (l *tlogger) Init(info logr.RuntimeInfo) { l.callDepth = info.CallDepth } func (l *tlogger) WithCallDepth(depth int) logr.LogSink { newLogger := *l newLogger.callDepth += depth return &newLogger } func (l *tlogger) Enabled(level int) bool { return l.config.Enabled(verbosity.Level(level), 1) } func (l *tlogger) Info(level int, msg string, kvList ...interface{}) { l.print(nil, severity.InfoLog, msg, kvList) } func (l *tlogger) Error(err error, msg string, kvList ...interface{}) { l.print(err, severity.ErrorLog, msg, kvList) } func (l *tlogger) print(err error, s severity.Severity, msg string, kvList []interface{}) { // Only create a new buffer if we don't have one cached. b := l.bufferCache.GetBuffer() // Determine caller. // +1 for this frame, +1 for Info/Error. _, file, line, ok := runtime.Caller(l.callDepth + 2) if !ok { file = "???" line = 1 } else { if slash := strings.LastIndex(file, "/"); slash >= 0 { path := file file = path[slash+1:] } } // Format header. now := TimeNow() b.FormatHeader(s, file, line, now) // Inject WithName names into message. if l.prefix != "" { msg = l.prefix + ": " + msg } // The message is always quoted, even if it contains line breaks. // If developers want multi-line output, they should use a small, fixed // message and put the multi-line output into a value. b.WriteString(strconv.Quote(msg)) if err != nil { serialize.KVListFormat(&b.Buffer, "err", err) } merged := serialize.MergeKVs(l.values, kvList) serialize.KVListFormat(&b.Buffer, merged...) if b.Len() == 0 || b.Bytes()[b.Len()-1] != '\n' { b.WriteByte('\n') } l.config.co.output.Write(b.Bytes()) } // WithName returns a new logr.Logger with the specified name appended. klogr // uses '/' characters to separate name elements. Callers should not pass '/' // in the provided name string, but this library does not actually enforce that. func (l *tlogger) WithName(name string) logr.LogSink { new := *l if len(l.prefix) > 0 { new.prefix = l.prefix + "/" } new.prefix += name return &new } func (l *tlogger) WithValues(kvList ...interface{}) logr.LogSink { new := *l new.values = serialize.WithValues(l.values, kvList) return &new } var _ logr.LogSink = &tlogger{} var _ logr.CallDepthLogSink = &tlogger{}