[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Tell us about a problem you are experiencing\n\n---\n\n/kind bug\n\n**What steps did you take and what happened:**\n[A clear and concise description of what the bug is.]\n\n\n**What did you expect to happen:**\n\n\n**Anything else you would like to add:**\n[Miscellaneous information that will assist in solving the issue.]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature enhancement request\nabout: Suggest an idea for this project\n\n---\n\n/kind feature\n\n**Describe the solution you'd like**\n[A clear and concise description of what you want to happen.]\n\n\n**Anything else you would like to add:**\n[Miscellaneous information that will assist in solving the issue.]\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for sending a pull request!  Here are some tips for you:\n1. If this is your first time, read our contributor guidelines https://git.k8s.io/community/contributors/guide/pull-requests.md#the-pull-request-submit-process and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide\n2. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews\n3. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md\n4. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests\n5. If this PR changes image versions, please title this PR \"Bump <image name> from x.x.x to y.y.y.\"\n-->\n\n**What this PR does / why we need it**:\n\n**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:\nFixes #\n\n**Special notes for your reviewer**:\n\n_Please confirm that if this PR changes any image versions, then that's the sole change this PR makes._\n\n**Release note**:\n<!--  Write your release note:\n1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string \"action required\".\n2. If no release note is required, just write \"NONE\".\n-->\n```release-note\n\n```"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: gomod\n    directories:\n      - \"**/*\"\n    schedule:\n      interval: daily\n\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "name: Run lint\n\non: [ push, pull_request ]\n\npermissions:\n  contents: read\n\njobs:\n  lint:\n    strategy:\n      matrix:\n        path:\n          - .\n          - examples\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Lint\n        uses: golangci/golangci-lint-action@v6\n        with:\n          # version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version\n          version: latest\n          working-directory: ${{ matrix.path }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non: [push, pull_request]\njobs:\n  test:\n    strategy:\n      matrix:\n        go-version: [\"1.21\", \"1.22\", \"1.23\", \"1.24\", \"1.25\"]\n        platform: [ubuntu-latest, macos-latest, windows-latest]\n    runs-on: ${{ matrix.platform }}\n    steps:\n    - name: Install Go\n      uses: actions/setup-go@v5\n      with:\n        go-version: ${{ matrix.go-version }}\n    - name: Checkout code\n      uses: actions/checkout@v4\n    - name: Test klog\n      run: |\n        go get -t -v ./...\n        go test -v -race ./...\n    - name: Test examples\n      run: cd examples && go test -v -race ./...\n  apidiff:\n    runs-on: ubuntu-latest\n    if: github.base_ref\n    steps:\n      - name: Install Go\n        uses: actions/setup-go@v5\n        with:\n          go-version: 1.23\n      - name: Add GOBIN to PATH\n        run: echo \"PATH=$(go env GOPATH)/bin:$PATH\" >>$GITHUB_ENV\n      - name: Install dependencies\n        run: go install golang.org/x/exp/cmd/apidiff@latest\n      - name: Checkout old code\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.base_ref }}\n          path: \"old\"\n      - name: Checkout new code\n        uses: actions/checkout@v4\n        with:\n          path: \"new\"\n      - name: APIDiff\n        run: ./hack/verify-apidiff.sh -d ../old\n        working-directory: \"new\"\n"
  },
  {
    "path": ".gitignore",
    "content": "# OSX leaves these everywhere on SMB shares\n._*\n\n# OSX trash\n.DS_Store\n\n# Eclipse files\n.classpath\n.project\n.settings/**\n\n# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA\n.idea/\n*.iml\n\n# Vscode files\n.vscode\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "linters:\n  disable-all: true\n  enable: # sorted alphabetical\n    - gofmt\n    - misspell\n    - revive\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing Guidelines\n\nWelcome 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:\n\n_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._\n\n## Getting Started\n\nWe have full documentation on how to get started contributing here:\n\n- [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\n- [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)\n- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers\n\n## Mentorship\n\n- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!\n\n## Contact Information\n\n- [Slack](https://kubernetes.slack.com/messages/sig-architecture)\n- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction, and\ndistribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by the copyright\nowner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all other entities\nthat control, are controlled by, or are under common control with that entity.\nFor the purposes of this definition, \"control\" means (i) the power, direct or\nindirect, to cause the direction or management of such entity, whether by\ncontract or otherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising\npermissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications, including\nbut not limited to software source code, documentation source, and configuration\nfiles.\n\n\"Object\" form shall mean any form resulting from mechanical transformation or\ntranslation of a Source form, including but not limited to compiled object code,\ngenerated documentation, and conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or Object form, made\navailable under the License, as indicated by a copyright notice that is included\nin or attached to the work (an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object form, that\nis based on (or derived from) the Work and for which the editorial revisions,\nannotations, elaborations, or other modifications represent, as a whole, an\noriginal work of authorship. For the purposes of this License, Derivative Works\nshall not include works that remain separable from, or merely link (or bind by\nname) to the interfaces of, the Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including the original version\nof the Work and any modifications or additions to that Work or Derivative Works\nthereof, that is intentionally submitted to Licensor for inclusion in the Work\nby the copyright owner or by an individual or Legal Entity authorized to submit\non behalf of the copyright owner. For the purposes of this definition,\n\"submitted\" means any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems, and\nissue tracking systems that are managed by, or on behalf of, the Licensor for\nthe purpose of discussing and improving the Work, but excluding communication\nthat is conspicuously marked or otherwise designated in writing by the copyright\nowner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf\nof whom a Contribution has been received by Licensor and subsequently\nincorporated within the Work.\n\n2. Grant of Copyright License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable copyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the Work and such\nDerivative Works in Source or Object form.\n\n3. Grant of Patent License.\n\nSubject to the terms and conditions of this License, each Contributor hereby\ngrants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,\nirrevocable (except as stated in this section) patent license to make, have\nmade, use, offer to sell, sell, import, and otherwise transfer the Work, where\nsuch license applies only to those patent claims licensable by such Contributor\nthat are necessarily infringed by their Contribution(s) alone or by combination\nof their Contribution(s) with the Work to which such Contribution(s) was\nsubmitted. If You institute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work or a\nContribution incorporated within the Work constitutes direct or contributory\npatent infringement, then any patent licenses granted to You under this License\nfor that Work shall terminate as of the date such litigation is filed.\n\n4. Redistribution.\n\nYou may reproduce and distribute copies of the Work or Derivative Works thereof\nin any medium, with or without modifications, and in Source or Object form,\nprovided that You meet the following conditions:\n\nYou must give any other recipients of the Work or Derivative Works a copy of\nthis License; and\nYou must cause any modified files to carry prominent notices stating that You\nchanged the files; and\nYou must retain, in the Source form of any Derivative Works that You distribute,\nall copyright, patent, trademark, and attribution notices from the Source form\nof the Work, excluding those notices that do not pertain to any part of the\nDerivative Works; and\nIf the Work includes a \"NOTICE\" text file as part of its distribution, then any\nDerivative Works that You distribute must include a readable copy of the\nattribution notices contained within such NOTICE file, excluding those notices\nthat do not pertain to any part of the Derivative Works, in at least one of the\nfollowing places: within a NOTICE text file distributed as part of the\nDerivative Works; within the Source form or documentation, if provided along\nwith the Derivative Works; or, within a display generated by the Derivative\nWorks, if and wherever such third-party notices normally appear. The contents of\nthe NOTICE file are for informational purposes only and do not modify the\nLicense. You may add Your own attribution notices within Derivative Works that\nYou distribute, alongside or as an addendum to the NOTICE text from the Work,\nprovided that such additional attribution notices cannot be construed as\nmodifying the License.\nYou may add Your own copyright statement to Your modifications and may provide\nadditional or different license terms and conditions for use, reproduction, or\ndistribution of Your modifications, or for any such Derivative Works as a whole,\nprovided Your use, reproduction, and distribution of the Work otherwise complies\nwith the conditions stated in this License.\n\n5. Submission of Contributions.\n\nUnless You explicitly state otherwise, any Contribution intentionally submitted\nfor inclusion in the Work by You to the Licensor shall be under the terms and\nconditions of this License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify the terms of\nany separate license agreement you may have executed with Licensor regarding\nsuch Contributions.\n\n6. Trademarks.\n\nThis License does not grant permission to use the trade names, trademarks,\nservice marks, or product names of the Licensor, except as required for\nreasonable and customary use in describing the origin of the Work and\nreproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty.\n\nUnless required by applicable law or agreed to in writing, Licensor provides the\nWork (and each Contributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,\nincluding, without limitation, any warranties or conditions of TITLE,\nNON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are\nsolely responsible for determining the appropriateness of using or\nredistributing the Work and assume any risks associated with Your exercise of\npermissions under this License.\n\n8. Limitation of Liability.\n\nIn no event and under no legal theory, whether in tort (including negligence),\ncontract, or otherwise, unless required by applicable law (such as deliberate\nand grossly negligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special, incidental,\nor consequential damages of any character arising as a result of this License or\nout of the use or inability to use the Work (including but not limited to\ndamages for loss of goodwill, work stoppage, computer failure or malfunction, or\nany and all other commercial damages or losses), even if such Contributor has\nbeen advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability.\n\nWhile redistributing the Work or Derivative Works thereof, You may choose to\noffer, and charge a fee for, acceptance of support, warranty, indemnity, or\nother liability obligations and/or rights consistent with this License. However,\nin accepting such obligations, You may act only on Your own behalf and on Your\nsole responsibility, not on behalf of any other Contributor, and only if You\nagree to indemnify, defend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason of your\naccepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nAPPENDIX: How to apply the Apache License to your work\n\nTo apply the Apache License to your work, attach the following boilerplate\nnotice, with the fields enclosed by brackets \"[]\" replaced with your own\nidentifying information. (Don't include the brackets!) The text should be\nenclosed in the appropriate comment syntax for the file format. We also\nrecommend that a file or class name and description of purpose be included on\nthe same \"printed page\" as the copyright notice for easier identification within\nthird-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n     http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "OWNERS",
    "content": "# See the OWNERS docs at https://go.k8s.io/owners\nreviewers:\n  - harshanarayana\n  - mengjiao-liu\n  - pohly\napprovers:\n  - dims\n  - pohly\n  - thockin\nemeritus_approvers:\n  - brancz\n  - justinsb\n  - lavalamp\n  - piosz\n  - serathius\n  - tallclair\n"
  },
  {
    "path": "README.md",
    "content": "klog\n====\n\nklog is a permanent fork of https://github.com/golang/glog.\n\n## Why was klog created?\n\nThe decision to create klog was one that wasn't made lightly, but it was necessary due to some\ndrawbacks 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:\n\n> The code in this repo [...] is not itself under development\n\nThis makes us unable to solve many use cases without a fork. The factors that contributed to needing feature development are listed below:\n\n * `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.\n * `glog` doesn't provide an easy way to test logs, which detracts from the stability of software using it\n * A long term goal is to implement a logging interface that allows us to add context, change output format, etc.\n \nHistorical context is available here:\n\n * https://github.com/kubernetes/kubernetes/issues/61006\n * https://github.com/kubernetes/kubernetes/issues/70264\n * https://groups.google.com/forum/#!msg/kubernetes-sig-architecture/wCWiWf3Juzs/hXRVBH90CgAJ\n * https://groups.google.com/forum/#!msg/kubernetes-dev/7vnijOMhLS0/1oRiNtigBgAJ\n\n## Release versioning\n\nSemantic versioning is used in this repository. It contains several Go modules\nwith different levels of stability:\n- `k8s.io/klog/v2` - stable API, `vX.Y.Z` tags\n- `examples` - no stable API, no tags, no intention to ever stabilize\n\nExempt from the API stability guarantee are items (packages, functions, etc.)\nwhich are marked explicitly as `EXPERIMENTAL` in their docs comment. Those\nmay still change in incompatible ways or get removed entirely. This can only\nbe used for code that is used in tests to avoid situations where non-test\ncode from two different Kubernetes dependencies depends on incompatible\nreleases of klog because an experimental API was changed.\n\n----\n\nHow to use klog\n===============\n- Replace imports for `\"github.com/golang/glog\"` with `\"k8s.io/klog/v2\"`\n- Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags\n- You can now use `log_file` instead of `log_dir` for logging to a single file (See `examples/log_file/usage_log_file.go`)\n- 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`)\n- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md))\n- See our documentation on [pkg.go.dev/k8s.io](https://pkg.go.dev/k8s.io/klog).\n\n### Coexisting with klog/v2\n\nSee [this example](examples/coexist_klog_v1_and_v2/) to see how to coexist with both klog/v1 and klog/v2.\n\n### Coexisting with glog\nThis 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`.\n\n## Community, discussion, contribution, and support\n\nLearn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).\n\nYou can reach the maintainers of this project at:\n\n- [Slack](https://kubernetes.slack.com/messages/klog)\n- [Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-architecture)\n\n### Code of conduct\n\nParticipation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).\n\n----\n\nglog\n====\n\nLeveled execution logs for Go.\n\nThis is an efficient pure Go implementation of leveled logs in the\nmanner of the open source C++ package\n\thttps://github.com/google/glog\n\nBy binding methods to booleans it is possible to use the log package\nwithout paying the expense of evaluating the arguments to the log.\nThrough the -vmodule flag, the package also provides fine-grained\ncontrol over logging at the file level.\n\nThe comment from glog.go introduces the ideas:\n\n\tPackage glog implements logging analogous to the Google-internal\n\tC++ INFO/ERROR/V setup.  It provides functions Info, Warning,\n\tError, Fatal, plus formatting variants such as Infof. It\n\talso provides V-style logging controlled by the -v and\n\t-vmodule=file=2 flags.\n\n\tBasic examples:\n\n\t\tglog.Info(\"Prepare to repel boarders\")\n\n\t\tglog.Fatalf(\"Initialization failed: %s\", err)\n\n\tSee the documentation of the V function for an explanation\n\tof these examples:\n\n\t\tif glog.V(2) {\n\t\t\tglog.Info(\"Starting transaction...\")\n\t\t}\n\n\t\tglog.V(2).Infoln(\"Processed\", nItems, \"elements\")\n\n\nThe repository contains an open source version of the log package\nused inside Google. The master copy of the source lives inside\nGoogle, not here. The code in this repo is for export only and is not itself\nunder development. Feature requests will be ignored.\n\nSend bug reports to golang-nuts@googlegroups.com.\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Release Process\n\nThe `klog` is released on an as-needed basis. The process is as follows:\n\n1. An issue is proposing a new release with a changelog since the last release\n1. All [OWNERS](OWNERS) must LGTM this release\n1. An OWNER runs `git tag -s $VERSION` and inserts the changelog and pushes the tag with `git push $VERSION`\n1. The release issue is closed\n1. An announcement email is sent to `kubernetes-dev@googlegroups.com` with the subject `[ANNOUNCE] kubernetes-template-project $VERSION is released`\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Security Announcements\n\nJoin the [kubernetes-security-announce] group for security and vulnerability announcements.\n\nYou can also subscribe to an RSS feed of the above using [this link][kubernetes-security-announce-rss].\n\n## Reporting a Vulnerability\n\nInstructions for reporting a vulnerability can be found on the\n[Kubernetes Security and Disclosure Information] page.\n\n## Supported Versions\n\nInformation about supported Kubernetes versions can be found on the\n[Kubernetes version and version skew support policy] page on the Kubernetes website.\n\n[kubernetes-security-announce]: https://groups.google.com/forum/#!forum/kubernetes-security-announce\n[kubernetes-security-announce-rss]: https://groups.google.com/forum/feed/kubernetes-security-announce/msgs/rss_v2_0.xml?num=50\n[Kubernetes version and version skew support policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-versions\n[Kubernetes Security and Disclosure Information]: https://kubernetes.io/docs/reference/issues-security/security/#report-a-vulnerability\n"
  },
  {
    "path": "SECURITY_CONTACTS",
    "content": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Committee to reach out\n# to for triaging and handling of incoming issues.\n#\n# The below names agree to abide by the\n# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)\n# and will be removed and replaced if they violate that agreement.\n#\n# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE\n# INSTRUCTIONS AT https://kubernetes.io/security/\n\ndims\nthockin\njustinsb\ntallclair\npiosz\nbrancz\nDirectXMan12\nlavalamp\n"
  },
  {
    "path": "code-of-conduct.md",
    "content": "# Kubernetes Community Code of Conduct\n\nPlease refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)\n"
  },
  {
    "path": "contextual.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// This file provides the implementation of\n// https://github.com/kubernetes/enhancements/tree/master/keps/sig-instrumentation/1602-structured-logging\n//\n// SetLogger and ClearLogger were originally added to klog.go and got moved\n// here. Contextual logging adds a way to retrieve a Logger for direct logging\n// without the logging calls in klog.go.\n//\n// The global variables are expected to be modified only during sequential\n// parts of a program (init, serial tests) and therefore are not protected by\n// mutex locking.\n\nvar (\n\t// klogLogger is used as fallback for logging through the normal klog code\n\t// when no Logger is set.\n\tklogLogger logr.Logger = logr.New(&klogger{})\n)\n\n// SetLogger sets a Logger implementation that will be used as backing\n// implementation of the traditional klog log calls. klog will do its own\n// verbosity checks before calling logger.V().Info. logger.Error is always\n// called, regardless of the klog verbosity settings.\n//\n// If set, all log lines will be suppressed from the regular output, and\n// redirected to the logr implementation.\n// Use as:\n//\n//\t...\n//\tklog.SetLogger(zapr.NewLogger(zapLog))\n//\n// To remove a backing logr implemention, use ClearLogger. Setting an\n// empty logger with SetLogger(logr.Logger{}) does not work.\n//\n// Modifying the logger is not thread-safe and should be done while no other\n// goroutines invoke log calls, usually during program initialization.\nfunc SetLogger(logger logr.Logger) {\n\tSetLoggerWithOptions(logger)\n}\n\n// SetLoggerWithOptions is a more flexible version of SetLogger. Without\n// additional options, it behaves exactly like SetLogger. By passing\n// ContextualLogger(true) as option, it can be used to set a logger that then\n// will also get called directly by applications which retrieve it via\n// FromContext, Background, or TODO.\n//\n// Supporting direct calls is recommended because it avoids the overhead of\n// routing log entries through klogr into klog and then into the actual Logger\n// backend.\nfunc SetLoggerWithOptions(logger logr.Logger, opts ...LoggerOption) {\n\tlogging.loggerOptions = loggerOptions{}\n\tfor _, opt := range opts {\n\t\topt(&logging.loggerOptions)\n\t}\n\tlogging.logger = &logWriter{\n\t\tLogger:          logger,\n\t\twriteKlogBuffer: logging.loggerOptions.writeKlogBuffer,\n\t}\n}\n\n// ContextualLogger determines whether the logger passed to\n// SetLoggerWithOptions may also get called directly. Such a logger cannot rely\n// on verbosity checking in klog.\nfunc ContextualLogger(enabled bool) LoggerOption {\n\treturn func(o *loggerOptions) {\n\t\to.contextualLogger = enabled\n\t}\n}\n\n// FlushLogger provides a callback for flushing data buffered by the logger.\nfunc FlushLogger(flush func()) LoggerOption {\n\treturn func(o *loggerOptions) {\n\t\to.flush = flush\n\t}\n}\n\n// WriteKlogBuffer sets a callback that will be invoked by klog to write output\n// produced by non-structured log calls like Infof.\n//\n// The buffer will contain exactly the same data that klog normally would write\n// into its own output stream(s). In particular this includes the header, if\n// klog is configured to write one. The callback then can divert that data into\n// its own output streams. The buffer may or may not end in a line break.\n//\n// Without such a callback, klog will call the logger's Info or Error method\n// with just the message string (i.e. no header).\nfunc WriteKlogBuffer(write func([]byte)) LoggerOption {\n\treturn func(o *loggerOptions) {\n\t\to.writeKlogBuffer = write\n\t}\n}\n\n// LoggerOption implements the functional parameter paradigm for\n// SetLoggerWithOptions.\ntype LoggerOption func(o *loggerOptions)\n\ntype loggerOptions struct {\n\tcontextualLogger bool\n\tflush            func()\n\twriteKlogBuffer  func([]byte)\n}\n\n// logWriter combines a logger (always set) with a write callback (optional).\ntype logWriter struct {\n\tLogger\n\twriteKlogBuffer func([]byte)\n}\n\n// ClearLogger removes a backing Logger implementation if one was set earlier\n// with SetLogger.\n//\n// Modifying the logger is not thread-safe and should be done while no other\n// goroutines invoke log calls, usually during program initialization.\nfunc ClearLogger() {\n\tlogging.logger = nil\n\tlogging.loggerOptions = loggerOptions{}\n}\n\n// EnableContextualLogging controls whether contextual logging is enabled.\n// By default it is enabled. When disabled, FromContext avoids looking up\n// the logger in the context and always returns the global logger.\n// LoggerWithValues, LoggerWithName, and NewContext become no-ops\n// and return their input logger respectively context. This may be useful\n// to avoid the additional overhead for contextual logging.\n//\n// This must be called during initialization before goroutines are started.\nfunc EnableContextualLogging(enabled bool) {\n\tlogging.contextualLoggingEnabled = enabled\n}\n\n// FromContext retrieves a logger set by the caller or, if not set,\n// falls back to the program's global logger (a Logger instance or klog\n// itself).\nfunc FromContext(ctx context.Context) Logger {\n\tif logging.contextualLoggingEnabled {\n\t\tif logger, err := logr.FromContext(ctx); err == nil {\n\t\t\treturn logger\n\t\t}\n\t}\n\n\treturn Background()\n}\n\n// TODO can be used as a last resort by code that has no means of\n// receiving a logger from its caller. FromContext or an explicit logger\n// parameter should be used instead.\nfunc TODO() Logger {\n\treturn Background()\n}\n\n// Background retrieves the fallback logger. It should not be called before\n// that logger was initialized by the program and not by code that should\n// better receive a logger via its parameters. TODO can be used as a temporary\n// solution for such code.\nfunc Background() Logger {\n\tif logging.loggerOptions.contextualLogger {\n\t\t// Is non-nil because logging.loggerOptions.contextualLogger is\n\t\t// only true if a logger was set.\n\t\treturn logging.logger.Logger\n\t}\n\n\treturn klogLogger\n}\n\n// LoggerWithValues returns logger.WithValues(...kv) when\n// contextual logging is enabled, otherwise the logger.\nfunc LoggerWithValues(logger Logger, kv ...interface{}) Logger {\n\tif logging.contextualLoggingEnabled {\n\t\treturn logger.WithValues(kv...)\n\t}\n\treturn logger\n}\n\n// LoggerWithName returns logger.WithName(name) when contextual logging is\n// enabled, otherwise the logger.\nfunc LoggerWithName(logger Logger, name string) Logger {\n\tif logging.contextualLoggingEnabled {\n\t\treturn logger.WithName(name)\n\t}\n\treturn logger\n}\n\n// NewContext returns logr.NewContext(ctx, logger) when\n// contextual logging is enabled, otherwise ctx.\nfunc NewContext(ctx context.Context, logger Logger) context.Context {\n\tif logging.contextualLoggingEnabled {\n\t\treturn logr.NewContext(ctx, logger)\n\t}\n\treturn ctx\n}\n"
  },
  {
    "path": "contextual_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"log/slog\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// SetSlogLogger reconfigures klog to log through the slog logger. The logger must not be nil.\nfunc SetSlogLogger(logger *slog.Logger) {\n\tSetLoggerWithOptions(logr.FromSlogHandler(logger.Handler()), ContextualLogger(true))\n}\n"
  },
  {
    "path": "contextual_slog_example_test.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc ExampleSetSlogLogger() {\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\n\thandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{\n\t\tReplaceAttr: func(_ /* groups */ []string, a slog.Attr) slog.Attr {\n\t\t\tif a.Key == slog.TimeKey {\n\t\t\t\t// Avoid non-deterministic output.\n\t\t\t\treturn slog.Attr{}\n\t\t\t}\n\t\t\treturn a\n\t\t},\n\t})\n\tlogger := slog.New(handler)\n\tklog.SetSlogLogger(logger)\n\tklog.Info(\"hello world\")\n\n\t// Output:\n\t// level=INFO msg=\"hello world\"\n}\n"
  },
  {
    "path": "contextual_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc ExampleSetLogger() {\n\tdefer klog.ClearLogger()\n\n\t// Logger is only used as backend, Background() returns klogr.\n\tklog.SetLogger(logr.Discard())\n\tfmt.Printf(\"logger after SetLogger: %T\\n\", klog.Background().GetSink())\n\n\t// Logger is only used as backend, Background() returns klogr.\n\tklog.SetLoggerWithOptions(logr.Discard(), klog.ContextualLogger(false))\n\tfmt.Printf(\"logger after SetLoggerWithOptions with ContextualLogger(false): %T\\n\", klog.Background().GetSink())\n\n\t// Logger is used as backend and directly.\n\tklog.SetLoggerWithOptions(logr.Discard(), klog.ContextualLogger(true))\n\tfmt.Printf(\"logger after SetLoggerWithOptions with ContextualLogger(true): %T\\n\", klog.Background().GetSink())\n\n\t// Output:\n\t// logger after SetLogger: *klog.klogger\n\t// logger after SetLoggerWithOptions with ContextualLogger(false): *klog.klogger\n\t// logger after SetLoggerWithOptions with ContextualLogger(true): <nil>\n}\n\nfunc ExampleFlushLogger() {\n\tdefer klog.ClearLogger()\n\n\t// This simple logger doesn't need flushing, but others might.\n\tklog.SetLoggerWithOptions(logr.Discard(), klog.FlushLogger(func() {\n\t\tfmt.Print(\"flushing...\")\n\t}))\n\tklog.Flush()\n\n\t// Output:\n\t// flushing...\n}\n\nfunc BenchmarkPassingLogger(b *testing.B) {\n\tb.Run(\"with context\", func(b *testing.B) {\n\t\tctx := klog.NewContext(context.Background(), klog.Background())\n\t\tvar finalCtx context.Context\n\t\tfor n := b.N; n > 0; n-- {\n\t\t\tfinalCtx = passCtx(ctx)\n\t\t}\n\t\truntime.KeepAlive(finalCtx)\n\t})\n\n\tb.Run(\"without context\", func(b *testing.B) {\n\t\tlogger := klog.Background()\n\t\tvar finalLogger klog.Logger\n\t\tfor n := b.N; n > 0; n-- {\n\t\t\tfinalLogger = passLogger(logger)\n\t\t}\n\t\truntime.KeepAlive(finalLogger)\n\t})\n}\n\nfunc BenchmarkExtractLogger(b *testing.B) {\n\tb.Run(\"from context\", func(b *testing.B) {\n\t\tctx := klog.NewContext(context.Background(), klog.Background())\n\t\tvar finalLogger klog.Logger\n\t\tfor n := b.N; n > 0; n-- {\n\t\t\tfinalLogger = extractCtx(ctx)\n\t\t}\n\t\truntime.KeepAlive(finalLogger)\n\t})\n}\n\n//go:noinline\nfunc passCtx(ctx context.Context) context.Context { return ctx }\n\n//go:noinline\nfunc extractCtx(ctx context.Context) klog.Logger { return klog.FromContext(ctx) }\n\n//go:noinline\nfunc passLogger(logger klog.Logger) klog.Logger { return logger }\n"
  },
  {
    "path": "examples/benchmarks/benchmarks_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage benchmarks\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/go-logr/zapr\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n)\n\nconst (\n\tverbosityThreshold = 10\n)\n\nfunc init() {\n\t// klog gets configured so that it writes to a single output file that\n\t// will be set during tests with SetOutput.\n\tklog.InitFlags(nil)\n\trequire.NoError(flag.Set(\"v\", fmt.Sprintf(\"%d\", verbosityThreshold)))\n\trequire.NoError(flag.Set(\"log_file\", \"/dev/null\"))\n\trequire.NoError(flag.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(flag.Set(\"alsologtostderr\", \"false\"))\n\trequire.NoError(flag.Set(\"stderrthreshold\", \"10\"))\n}\n\ntype testcase struct {\n\tname     string\n\tgenerate func() interface{}\n}\n\nfunc BenchmarkOutput(b *testing.B) {\n\t// We'll run each benchmark for different output formatting.\n\tconfigs := map[string]struct {\n\t\tinit, cleanup func()\n\t}{\n\t\t\"klog\": {\n\t\t\tinit: func() { klog.SetOutput(discard{}) },\n\t\t},\n\t\t\"zapr\": {\n\t\t\tinit:    func() { klog.SetLogger(newZaprLogger()) },\n\t\t\tcleanup: func() { klog.ClearLogger() },\n\t\t},\n\t}\n\n\t// Each benchmark tests formatting of one key/value pair, with\n\t// different values. The order is relevant here.\n\tvar tests []testcase\n\tfor length := 0; length <= 100; length += 10 {\n\t\targ := make([]interface{}, length)\n\t\tfor i := 0; i < length; i++ {\n\t\t\targ[i] = KMetadataMock{Name: \"a\", NS: \"a\"}\n\t\t}\n\t\ttests = append(tests, testcase{\n\t\t\tname: fmt.Sprintf(\"objects/%d\", length),\n\t\t\tgenerate: func() interface{} {\n\t\t\t\treturn klog.KObjSlice(arg)\n\t\t\t},\n\t\t})\n\t}\n\n\t// Verbosity checks may influence the result.\n\tverbosity := map[string]func(value interface{}){\n\t\t\"no-verbosity-check\": func(value interface{}) {\n\t\t\tklog.InfoS(\"test\", \"key\", value)\n\t\t},\n\t\t\"pass-verbosity-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold).InfoS(\"test\", \"key\", value)\n\t\t},\n\t\t\"fail-verbosity-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold+1).InfoS(\"test\", \"key\", value)\n\t\t},\n\t\t\"non-standard-int-key-check\": func(value interface{}) {\n\t\t\tklog.InfoS(\"test\", 1, value)\n\t\t},\n\t\t\"non-standard-struct-key-check\": func(value interface{}) {\n\t\t\tklog.InfoS(\"test\", struct{ key string }{\"test\"}, value)\n\t\t},\n\t\t\"non-standard-map-key-check\": func(value interface{}) {\n\t\t\tklog.InfoS(\"test\", map[string]bool{\"key\": true}, value)\n\t\t},\n\t\t\"pass-verbosity-non-standard-int-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold).InfoS(\"test\", 1, value)\n\t\t},\n\t\t\"pass-verbosity-non-standard-struct-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold).InfoS(\"test\", struct{ key string }{\"test\"}, value)\n\t\t},\n\t\t\"pass-verbosity-non-standard-map-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold).InfoS(\"test\", map[string]bool{\"key\": true}, value)\n\t\t},\n\t\t\"fail-verbosity-non-standard-int-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold+1).InfoS(\"test\", 1, value)\n\t\t},\n\t\t\"fail-verbosity-non-standard-struct-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold+1).InfoS(\"test\", struct{ key string }{\"test\"}, value)\n\t\t},\n\t\t\"fail-verbosity-non-standard-map-key-check\": func(value interface{}) {\n\t\t\tklog.V(verbosityThreshold+1).InfoS(\"test\", map[string]bool{\"key\": true}, value)\n\t\t},\n\t}\n\n\tfor name, config := range configs {\n\t\tb.Run(name, func(b *testing.B) {\n\t\t\tif config.cleanup != nil {\n\t\t\t\tdefer config.cleanup()\n\t\t\t}\n\t\t\tconfig.init()\n\n\t\t\tfor name, logCall := range verbosity {\n\t\t\t\tb.Run(name, func(b *testing.B) {\n\t\t\t\t\tfor _, testcase := range tests {\n\t\t\t\t\t\tb.Run(testcase.name, func(b *testing.B) {\n\t\t\t\t\t\t\tb.ResetTimer()\n\t\t\t\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\t\t\t\tlogCall(testcase.generate())\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc newZaprLogger() logr.Logger {\n\tencoderConfig := &zapcore.EncoderConfig{\n\t\tMessageKey:     \"msg\",\n\t\tCallerKey:      \"caller\",\n\t\tNameKey:        \"logger\",\n\t\tEncodeDuration: zapcore.StringDurationEncoder,\n\t\tEncodeCaller:   zapcore.ShortCallerEncoder,\n\t}\n\tencoder := zapcore.NewJSONEncoder(*encoderConfig)\n\tzapV := -zapcore.Level(verbosityThreshold)\n\tcore := zapcore.NewCore(encoder, zapcore.AddSync(discard{}), zapV)\n\tl := zap.New(core, zap.WithCaller(true))\n\tlogger := zapr.NewLoggerWithOptions(l, zapr.LogInfoLevel(\"v\"), zapr.ErrorKey(\"err\"))\n\treturn logger\n}\n\ntype KMetadataMock struct {\n\tName, NS string\n}\n\nfunc (m KMetadataMock) GetName() string {\n\treturn m.Name\n}\nfunc (m KMetadataMock) GetNamespace() string {\n\treturn m.NS\n}\n\ntype discard struct{}\n\nvar _ io.Writer = discard{}\n\nfunc (discard) Write(p []byte) (int, error) {\n\treturn len(p), nil\n}\n"
  },
  {
    "path": "examples/coexist_glog/coexist_glog.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t\"github.com/golang/glog\"\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\trequire.NoError(flag.Set(\"alsologtostderr\", \"true\"))\n\tflag.Parse()\n\n\tklogFlags := flag.NewFlagSet(\"klog\", flag.ExitOnError)\n\tklog.InitFlags(klogFlags)\n\n\t// Sync the glog and klog flags.\n\tflag.CommandLine.VisitAll(func(f1 *flag.Flag) {\n\t\tf2 := klogFlags.Lookup(f1.Name)\n\t\tif f2 != nil {\n\t\t\tvalue := f1.Value.String()\n\t\t\trequire.NoError(f2.Value.Set(value))\n\t\t}\n\t})\n\n\tglog.Info(\"hello from glog!\")\n\tklog.Info(\"nice to meet you, I'm klog\")\n\tglog.Flush()\n\tklog.Flush()\n}\n"
  },
  {
    "path": "examples/coexist_klog_v1_and_v2/coexist_klog_v1_and_v2.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\tklogv1 \"k8s.io/klog\"\n\tklogv2 \"k8s.io/klog/v2\"\n)\n\n// OutputCallDepth is the stack depth where we can find the origin of this call\nconst OutputCallDepth = 6\n\n// DefaultPrefixLength is the length of the log prefix that we have to strip out\nconst DefaultPrefixLength = 53\n\n// klogWriter is used in SetOutputBySeverity call below to redirect\n// any calls to klogv1 to end up in klogv2\ntype klogWriter struct{}\n\nfunc (kw klogWriter) Write(p []byte) (n int, err error) {\n\tif len(p) < DefaultPrefixLength {\n\t\tklogv2.InfoDepth(OutputCallDepth, string(p))\n\t\treturn len(p), nil\n\t}\n\tif p[0] == 'I' {\n\t\tklogv2.InfoDepth(OutputCallDepth, string(p[DefaultPrefixLength:]))\n\t} else if p[0] == 'W' {\n\t\tklogv2.WarningDepth(OutputCallDepth, string(p[DefaultPrefixLength:]))\n\t} else if p[0] == 'E' {\n\t\tklogv2.ErrorDepth(OutputCallDepth, string(p[DefaultPrefixLength:]))\n\t} else if p[0] == 'F' {\n\t\tklogv2.FatalDepth(OutputCallDepth, string(p[DefaultPrefixLength:]))\n\t} else {\n\t\tklogv2.InfoDepth(OutputCallDepth, string(p[DefaultPrefixLength:]))\n\t}\n\treturn len(p), nil\n}\n\nfunc main() {\n\t// initialize klog/v2, can also bind to a local flagset if desired\n\tklogv2.InitFlags(nil)\n\n\t// In this example, we want to show you that all the lines logged\n\t// end up in the myfile.log. You do NOT need them in your application\n\t// as all these flags are set up from the command line typically\n\tflag.Set(\"logtostderr\", \"false\")     // By default klog logs to stderr, switch that off\n\tflag.Set(\"alsologtostderr\", \"false\") // false is default, but this is informative\n\tflag.Set(\"stderrthreshold\", \"FATAL\") // stderrthreshold defaults to ERROR, we don't want anything in stderr\n\tflag.Set(\"log_file\", \"myfile.log\")   // log to a file\n\n\t// parse klog/v2 flags\n\tflag.Parse()\n\t// make sure we flush before exiting\n\tdefer klogv2.Flush()\n\n\t// BEGIN : hack to redirect klogv1 calls to klog v2\n\t// Tell klog NOT to log into STDERR. Otherwise, we risk\n\t// certain kinds of API errors getting logged into a directory not\n\t// available in a `FROM scratch` Docker container, causing us to abort\n\tvar klogv1Flags flag.FlagSet\n\tklogv1.InitFlags(&klogv1Flags)\n\tklogv1Flags.Set(\"logtostderr\", \"false\")     // By default klog v1 logs to stderr, switch that off\n\tklogv1Flags.Set(\"stderrthreshold\", \"FATAL\") // stderrthreshold defaults to ERROR, use this if you\n\t// don't want anything in your stderr\n\n\tklogv1.SetOutputBySeverity(\"INFO\", klogWriter{}) // tell klog v1 to use the writer\n\t// END : hack to redirect klogv1 calls to klog v2\n\n\t// Now you can mix klogv1 and v2 in the same code base\n\tklogv2.Info(\"hello from klog (v2)!\")\n\tklogv1.Info(\"hello from klog (v1)!\")\n\tklogv1.Warning(\"beware from klog (v1)!\")\n\tklogv1.Error(\"error from klog (v1)!\")\n\tklogv2.Info(\"nice to meet you (v2)\")\n}\n"
  },
  {
    "path": "examples/coexist_klog_v1_and_v2/go.mod",
    "content": "module k8s.io/klog/examples/coexist_klog_v1_and_v2\n\ngo 1.13\n\nrequire (\n\tk8s.io/klog v1.0.0\n\tk8s.io/klog/v2 v2.0.0\n)\n"
  },
  {
    "path": "examples/flushing/.gitignore",
    "content": "myfile.log\n"
  },
  {
    "path": "examples/flushing/flushing_test.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t\"go.uber.org/goleak\"\n\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\tklog.InitFlags(nil)\n\n\t// By default klog writes to stderr. Setting logtostderr to false makes klog\n\t// write to a log file.\n\trequire.NoError(flag.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(flag.Set(\"log_file\", \"myfile.log\"))\n\tflag.Parse()\n\n\t// Info writes the first log message. When the first log file is created,\n\t// a flushDaemon is started to frequently flush bytes to the file.\n\tklog.Info(\"nice to meet you\")\n\n\t// klog won't ever stop this flushDaemon. To exit without leaking a goroutine,\n\t// the daemon can be stopped manually.\n\tklog.StopFlushDaemon()\n\n\t// After you stopped the flushDaemon, you can still manually flush.\n\tklog.Info(\"bye\")\n\tklog.Flush()\n}\n\nfunc TestLeakingFlushDaemon(t *testing.T) {\n\t// goleak detects leaking goroutines.\n\tdefer goleak.VerifyNone(t)\n\n\t// Without calling StopFlushDaemon in main, this test will fail.\n\tmain()\n}\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module k8s.io/klog/examples\n\ngo 1.22.0\n\ntoolchain go1.23.0\n\nrequire (\n\tgithub.com/go-logr/logr v1.4.1\n\tgithub.com/go-logr/zapr v1.2.3\n\tgithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b\n\tgo.uber.org/goleak v1.1.12\n\tgo.uber.org/zap v1.19.0\n\tgolang.org/x/tools v0.27.0\n\tk8s.io/klog/v2 v2.30.0\n)\n\nrequire (\n\tgo.uber.org/atomic v1.7.0 // indirect\n\tgo.uber.org/multierr v1.6.0 // indirect\n\tgolang.org/x/mod v0.22.0 // indirect\n\tgolang.org/x/sync v0.9.0 // indirect\n)\n\nreplace k8s.io/klog/v2 => ../\n"
  },
  {
    "path": "examples/go.sum",
    "content": "github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=\ngithub.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=\ngithub.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngo.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=\ngo.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=\ngolang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=\ngolang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=\ngolang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "examples/go_vet/go_vet_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"os\"\n\t\"os/exec\"\n\t\"path\"\n\t\"testing\"\n\n\t\"golang.org/x/tools/go/analysis/analysistest\"\n\t\"golang.org/x/tools/go/analysis/passes/printf\"\n)\n\n// TestGoVet checks that \"go vet\" detects incorrect klog calls like\n// mismatched format specifiers and arguments.\nfunc TestGoVet(t *testing.T) {\n\ttestdata := analysistest.TestData()\n\tsrc := path.Join(testdata, \"src\")\n\tt.Cleanup(func() {\n\t\tos.RemoveAll(src)\n\t})\n\n\t// analysistest doesn't support using existing code\n\t// via modules (https://github.com/golang/go/issues/37054).\n\t// Populating the \"testdata/src\" directory with the\n\t// result of \"go mod vendor\" is a workaround.\n\tcmd := exec.Command(\"go\", \"mod\", \"vendor\", \"-o\", src)\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"%s failed: %v\\nOutput: %s\", cmd, err, string(out))\n\t}\n\n\tanalyzer := printf.Analyzer\n\tanalysistest.Run(t, testdata, analyzer, \"\")\n}\n"
  },
  {
    "path": "examples/go_vet/testdata/calls.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage testdata\n\nimport (\n\t\"k8s.io/klog/v2\"\n)\n\nfunc calls() {\n\tklog.Infof(\"%s\") // want `k8s.io/klog/v2.Infof format %s reads arg #1, but call has 0 args`\n\tklog.Infof(\"%s\", \"world\")\n\tklog.Info(\"%s\", \"world\") // want `k8s.io/klog/v2.Info call has possible Printf formatting directive %s`\n\tklog.Info(\"world\")\n\tklog.Infoln(\"%s\", \"world\") // want `k8s.io/klog/v2.Infoln call has possible Printf formatting directive %s`\n\tklog.Infoln(\"world\")\n\n\tklog.InfofDepth(1, \"%s\") // want `k8s.io/klog/v2.InfofDepth format %s reads arg #1, but call has 0 args`\n\tklog.InfofDepth(1, \"%s\", \"world\")\n\tklog.InfoDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.InfoDepth call has possible Printf formatting directive %s`\n\tklog.InfoDepth(1, \"world\")\n\tklog.InfolnDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.InfolnDepth call has possible Printf formatting directive %s`\n\tklog.InfolnDepth(1, \"world\")\n\n\tklog.Warningf(\"%s\") // want `k8s.io/klog/v2.Warningf format %s reads arg #1, but call has 0 args`\n\tklog.Warningf(\"%s\", \"world\")\n\tklog.Warning(\"%s\", \"world\") // want `k8s.io/klog/v2.Warning call has possible Printf formatting directive %s`\n\tklog.Warning(\"world\")\n\tklog.Warningln(\"%s\", \"world\") // want `k8s.io/klog/v2.Warningln call has possible Printf formatting directive %s`\n\tklog.Warningln(\"world\")\n\n\tklog.WarningfDepth(1, \"%s\") // want `k8s.io/klog/v2.WarningfDepth format %s reads arg #1, but call has 0 args`\n\tklog.WarningfDepth(1, \"%s\", \"world\")\n\tklog.WarningDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.WarningDepth call has possible Printf formatting directive %s`\n\tklog.WarningDepth(1, \"world\")\n\tklog.WarninglnDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.WarninglnDepth call has possible Printf formatting directive %s`\n\tklog.WarninglnDepth(1, \"world\")\n\n\tklog.Errorf(\"%s\") // want `k8s.io/klog/v2.Errorf format %s reads arg #1, but call has 0 args`\n\tklog.Errorf(\"%s\", \"world\")\n\tklog.Error(\"%s\", \"world\") // want `k8s.io/klog/v2.Error call has possible Printf formatting directive %s`\n\tklog.Error(\"world\")\n\tklog.Errorln(\"%s\", \"world\") // want `k8s.io/klog/v2.Errorln call has possible Printf formatting directive %s`\n\tklog.Errorln(\"world\")\n\n\tklog.ErrorfDepth(1, \"%s\") // want `k8s.io/klog/v2.ErrorfDepth format %s reads arg #1, but call has 0 args`\n\tklog.ErrorfDepth(1, \"%s\", \"world\")\n\tklog.ErrorDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.ErrorDepth call has possible Printf formatting directive %s`\n\tklog.ErrorDepth(1, \"world\")\n\tklog.ErrorlnDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.ErrorlnDepth call has possible Printf formatting directive %s`\n\tklog.ErrorlnDepth(1, \"world\")\n\n\tklog.Fatalf(\"%s\") // want `k8s.io/klog/v2.Fatalf format %s reads arg #1, but call has 0 args`\n\tklog.Fatalf(\"%s\", \"world\")\n\tklog.Fatal(\"%s\", \"world\") // want `k8s.io/klog/v2.Fatal call has possible Printf formatting directive %s`\n\tklog.Fatal(\"world\")\n\tklog.Fatalln(\"%s\", \"world\") // want `k8s.io/klog/v2.Fatalln call has possible Printf formatting directive %s`\n\tklog.Fatalln(\"world\")\n\n\tklog.FatalfDepth(1, \"%s\") // want `k8s.io/klog/v2.FatalfDepth format %s reads arg #1, but call has 0 args`\n\tklog.FatalfDepth(1, \"%s\", \"world\")\n\tklog.FatalDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.FatalDepth call has possible Printf formatting directive %s`\n\tklog.FatalDepth(1, \"world\")\n\tklog.FatallnDepth(1, \"%s\", \"world\") // want `k8s.io/klog/v2.FatallnDepth call has possible Printf formatting directive %s`\n\tklog.FatallnDepth(1, \"world\")\n\n\tklog.V(1).Infof(\"%s\") // want `\\(k8s.io/klog/v2.Verbose\\).Infof format %s reads arg #1, but call has 0 args`\n\tklog.V(1).Infof(\"%s\", \"world\")\n\tklog.V(1).Info(\"%s\", \"world\") // want `\\(k8s.io/klog/v2.Verbose\\).Info call has possible Printf formatting directive %s`\n\tklog.V(1).Info(\"world\")\n\tklog.V(1).Infoln(\"%s\", \"world\") // want `\\(k8s.io/klog/v2.Verbose\\).Infoln call has possible Printf formatting directive %s`\n\tklog.V(1).Infoln(\"world\")\n\n\tklog.V(1).InfofDepth(1, \"%s\") // want `\\(k8s.io/klog/v2.Verbose\\).InfofDepth format %s reads arg #1, but call has 0 args`\n\tklog.V(1).InfofDepth(1, \"%s\", \"world\")\n\tklog.V(1).InfoDepth(1, \"%s\", \"world\") // want `\\(k8s.io/klog/v2.Verbose\\).InfoDepth call has possible Printf formatting directive %s`\n\tklog.V(1).InfoDepth(1, \"world\")\n\tklog.V(1).InfolnDepth(1, \"%s\", \"world\") // want `\\(k8s.io/klog/v2.Verbose\\).InfolnDepth call has possible Printf formatting directive %s`\n\tklog.V(1).InfolnDepth(1, \"world\")\n\n\t// Detecting format specifiers for klog.InfoS and other structured logging calls would be nice,\n\t// but doesn't work the same way because of the extra \"msg\" string parameter. logcheck\n\t// can be used instead of \"go vet\".\n\tklog.InfoS(\"%s\", \"world\")\n}\n"
  },
  {
    "path": "examples/klogr/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/klogr\"\n)\n\ntype myError struct {\n\tstr string\n}\n\nfunc (e myError) Error() string {\n\treturn e.str\n}\n\nfunc main() {\n\tklog.InitFlags(nil)\n\trequire.NoError(flag.Set(\"v\", \"3\"))\n\tflag.Parse()\n\tlog := klogr.New().WithName(\"MyName\").WithValues(\"user\", \"you\")\n\tlog.Info(\"hello\", \"val1\", 1, \"val2\", map[string]int{\"k\": 1})\n\tlog.V(3).Info(\"nice to meet you\")\n\tlog.Error(nil, \"uh oh\", \"trouble\", true, \"reasons\", []float64{0.1, 0.11, 3.14})\n\tlog.Error(myError{\"an error occurred\"}, \"goodbye\", \"code\", -1)\n\tklog.Flush()\n}\n"
  },
  {
    "path": "examples/log_file/usage_log_file.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\tklog.InitFlags(nil)\n\t// By default klog writes to stderr. Setting logtostderr to false makes klog\n\t// write to a log file.\n\trequire.NoError(flag.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(flag.Set(\"log_file\", \"myfile.log\"))\n\tflag.Parse()\n\tklog.Info(\"nice to meet you\")\n\tklog.Flush()\n}\n"
  },
  {
    "path": "examples/output_test/output_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package output_test shows how to use k8s.io/klog/v2/test\n// and provides unit testing with dependencies that wouldn't\n// be acceptable for the main module.\npackage output_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/go-logr/zapr\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/klogr\"\n\t\"k8s.io/klog/v2/test\"\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\n// newLogger is a test.OutputConfig.NewLogger callback which creates a zapr\n// logger. The vmodule parameter is ignored because zapr does not support that.\nfunc newLogger(out io.Writer, v int, _ string) logr.Logger {\n\treturn newZaprLogger(out, v)\n}\n\n// TestZaprOutput tests the zapr, directly and as backend.\nfunc TestZaprOutput(t *testing.T) {\n\ttest.InitKlog(t)\n\tt.Run(\"direct\", func(t *testing.T) {\n\t\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: test.ZaprOutputMappingDirect()})\n\t})\n\tt.Run(\"klog-backend\", func(t *testing.T) {\n\t\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, AsBackend: true, ExpectedOutputMapping: test.ZaprOutputMappingIndirect()})\n\t})\n}\n\n// Benchmark direct zapr output.\nfunc BenchmarkZaprOutput(b *testing.B) {\n\ttest.InitKlog(b)\n\ttest.Benchmark(b, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: test.ZaprOutputMappingDirect()})\n}\n\n// TestKlogrStackText tests klogr.klogr -> klog -> text logger.\nfunc TestKlogrStackText(t *testing.T) {\n\ttest.InitKlog(t)\n\tnewLogger := func(out io.Writer, v int, vmodule string) logr.Logger {\n\t\t// Backend: text output.\n\t\tconfig := textlogger.NewConfig(\n\t\t\ttextlogger.Verbosity(v),\n\t\t\ttextlogger.Output(out),\n\t\t)\n\t\tif err := config.VModule().Set(vmodule); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tklog.SetLogger(textlogger.NewLogger(config))\n\n\t\t// Frontend: klogr.\n\t\treturn klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog))\n\t}\n\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, SupportsVModule: true})\n}\n\n// TestKlogrStackKlogr tests klogr.klogr -> klog -> zapr.\n//\n// This exposes whether verbosity is passed through correctly\n// (https://github.com/kubernetes/klog/issues/294) because klogr logging\n// records that.\nfunc TestKlogrStackZapr(t *testing.T) {\n\ttest.InitKlog(t)\n\tmapping := test.ZaprOutputMappingIndirect()\n\n\t// klogr doesn't warn about invalid KVs and just inserts\n\t// \"(MISSING)\".\n\tfor key, value := range map[string]string{\n\t\t`I output.go:<LINE>] \"odd arguments\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd arguments\",\"v\":0,\"akey\":\"avalue\",\"akey2\":\"(MISSING)\"}\n`,\n\n\t\t`I output.go:<LINE>] \"both odd\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"both odd\",\"v\":0,\"basekey1\":\"basevar1\",\"basekey2\":\"(MISSING)\",\"akey\":\"avalue\",\"akey2\":\"(MISSING)\"}\n`,\n\t\t`I output.go:<LINE>] \"integer keys\" %!s(int=1)=\"value\" %!s(int=2)=\"value2\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"integer keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"struct keys\" {name}=\"value\" test=\"other value\" key=\"val\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"struct keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"map keys\" map[test:%!s(bool=true)]=\"test\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{\"test\":true}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"map keys\",\"v\":0}\n`,\n\t} {\n\t\tmapping[key] = value\n\t}\n\n\tnewLogger := func(out io.Writer, v int, _ string) logr.Logger {\n\t\t// Backend: zapr as configured in k8s.io/component-base/logs/json.\n\t\tklog.SetLogger(newZaprLogger(out, v))\n\n\t\t// Frontend: klogr.\n\t\treturn klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog))\n\t}\n\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: mapping})\n}\n\n// TestKlogrInternalStackText tests klog.klogr (the simplified version used for contextual logging) -> klog -> text logger.\nfunc TestKlogrInternalStackText(t *testing.T) {\n\ttest.InitKlog(t)\n\tnewLogger := func(out io.Writer, v int, vmodule string) logr.Logger {\n\t\t// Backend: text output.\n\t\tconfig := textlogger.NewConfig(\n\t\t\ttextlogger.Verbosity(v),\n\t\t\ttextlogger.Output(out),\n\t\t)\n\t\tif err := config.VModule().Set(vmodule); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tklog.SetLogger(textlogger.NewLogger(config))\n\n\t\t// Frontend: internal klogr.\n\t\treturn klog.NewKlogr()\n\t}\n\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, SupportsVModule: true})\n}\n\n// TestKlogrInternalStackKlogr tests klog.klogr (the simplified version used for contextual logging) -> klog -> zapr.\n//\n// This exposes whether verbosity is passed through correctly\n// (https://github.com/kubernetes/klog/issues/294) because klogr logging\n// records that.\nfunc TestKlogrInternalStackZapr(t *testing.T) {\n\ttest.InitKlog(t)\n\tmapping := test.ZaprOutputMappingIndirect()\n\n\t// klogr doesn't warn about invalid KVs and just inserts\n\t// \"(MISSING)\".\n\tfor key, value := range map[string]string{\n\t\t`I output.go:<LINE>] \"odd arguments\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd arguments\",\"v\":0,\"akey\":\"avalue\",\"akey2\":\"(MISSING)\"}\n`,\n\n\t\t`I output.go:<LINE>] \"both odd\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"both odd\",\"v\":0,\"basekey1\":\"basevar1\",\"basekey2\":\"(MISSING)\",\"akey\":\"avalue\",\"akey2\":\"(MISSING)\"}\n`,\n\t\t`I output.go:<LINE>] \"integer keys\" %!s(int=1)=\"value\" %!s(int=2)=\"value2\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"integer keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"struct keys\" {name}=\"value\" test=\"other value\" key=\"val\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"struct keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"map keys\" map[test:%!s(bool=true)]=\"test\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{\"test\":true}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"map keys\",\"v\":0}\n`,\n\t} {\n\t\tmapping[key] = value\n\t}\n\n\tnewLogger := func(out io.Writer, v int, _ string) logr.Logger {\n\t\t// Backend: zapr as configured in k8s.io/component-base/logs/json.\n\t\tklog.SetLogger(newZaprLogger(out, v))\n\n\t\t// Frontend: internal klogr.\n\t\treturn klog.NewKlogr()\n\t}\n\ttest.Output(t, test.OutputConfig{NewLogger: newLogger, ExpectedOutputMapping: mapping})\n}\n\nfunc newZaprLogger(out io.Writer, v int) logr.Logger {\n\tencoderConfig := &zapcore.EncoderConfig{\n\t\tMessageKey:     \"msg\",\n\t\tCallerKey:      \"caller\",\n\t\tNameKey:        \"logger\",\n\t\tEncodeDuration: zapcore.StringDurationEncoder,\n\t\tEncodeCaller:   zapcore.ShortCallerEncoder,\n\t}\n\tencoder := zapcore.NewJSONEncoder(*encoderConfig)\n\tzapV := -zapcore.Level(v)\n\tcore := zapcore.NewCore(encoder, zapcore.AddSync(out), zapV)\n\tl := zap.New(core, zap.WithCaller(true))\n\tlogger := zapr.NewLoggerWithOptions(l, zapr.LogInfoLevel(\"v\"), zapr.ErrorKey(\"err\"))\n\treturn logger\n}\n"
  },
  {
    "path": "examples/set_output/usage_set_output.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\n\t\"k8s.io/klog/examples/util/require\"\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\tklog.InitFlags(nil)\n\trequire.NoError(flag.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(flag.Set(\"alsologtostderr\", \"false\"))\n\tflag.Parse()\n\n\tbuf := new(bytes.Buffer)\n\tklog.SetOutput(buf)\n\tklog.Info(\"nice to meet you\")\n\tklog.Flush()\n\n\tfmt.Printf(\"LOGGED: %s\", buf.String())\n}\n"
  },
  {
    "path": "examples/stderr_threshold_fix/main.go",
    "content": "// Example demonstrating the new stderr threshold behavior\npackage main\n\nimport (\n\t\"flag\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\tklog.InitFlags(nil)\n\tflag.Parse()\n\n\tklog.Info(\"This is an INFO message\")\n\tklog.Warning(\"This is a WARNING message\")\n\tklog.Error(\"This is an ERROR message\")\n\n\tklog.Flush()\n}\n\n// Run examples:\n//\n// 1. Legacy behavior (default) - all logs to stderr:\n//    go run main.go -logtostderr=true -stderrthreshold=ERROR\n//    Result: All three messages appear\n//\n// 2. New behavior - filter by severity:\n//    go run main.go -logtostderr=true -legacy_stderr_threshold_behavior=false -stderrthreshold=ERROR\n//    Result: Only ERROR message appears\n//\n// 3. New behavior - show WARNING and above:\n//    go run main.go -logtostderr=true -legacy_stderr_threshold_behavior=false -stderrthreshold=WARNING\n//    Result: WARNING and ERROR messages appear\n//\n// 4. Using alsologtostderrthreshold with file logging:\n//    go run main.go -logtostderr=false -alsologtostderr=true -alsologtostderrthreshold=ERROR -log_dir=/tmp/logs\n//    Result: All logs in files, only ERROR to stderr\n"
  },
  {
    "path": "examples/structured_logging/structured_logging.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\n\t\"k8s.io/klog/v2\"\n)\n\n// MyStruct will be logged via %+v\ntype MyStruct struct {\n\tName     string\n\tData     string\n\tinternal int\n}\n\n// MyStringer will be logged as string, with String providing that string.\ntype MyString MyStruct\n\nfunc (m MyString) String() string {\n\treturn m.Name + \": \" + m.Data\n}\n\nfunc main() {\n\tklog.InitFlags(nil)\n\tflag.Parse()\n\n\tsomeData := MyStruct{\n\t\tName:     \"hello\",\n\t\tData:     \"world\",\n\t\tinternal: 42,\n\t}\n\n\tlongData := MyStruct{\n\t\tName: \"long\",\n\t\tData: `Multiple\nlines\nwith quite a bit\nof text.`,\n\t}\n\n\tlogData := MyStruct{\n\t\tName: \"log output from some program\",\n\t\tData: `I0000 12:00:00.000000  123456 main.go:42] Starting\nE0000 12:00:01.000000  123456 main.go:43] Failed for some reason\n`,\n\t}\n\n\tstringData := MyString(longData)\n\n\tklog.Infof(\"someData printed using InfoF: %v\", someData)\n\tklog.Infof(\"longData printed using InfoF: %v\", longData)\n\tklog.Infof(`stringData printed using InfoF,\nwith the message across multiple lines:\n%v`, stringData)\n\tklog.Infof(\"logData printed using InfoF:\\n%v\", logData)\n\n\tklog.Info(\"=============================================\")\n\n\tklog.InfoS(\"using InfoS\", \"someData\", someData)\n\tklog.InfoS(\"using InfoS\", \"longData\", longData)\n\tklog.InfoS(`using InfoS with\nthe message across multiple lines`,\n\t\t\"int\", 1,\n\t\t\"stringData\", stringData,\n\t\t\"str\", \"another value\")\n\tklog.InfoS(\"using InfoS\", \"logData\", logData)\n\tklog.InfoS(\"using InfoS\", \"boolean\", true, \"int\", 1, \"float\", 0.1)\n\n\t// The Kubernetes recommendation is to start the message with uppercase\n\t// and not end with punctuation. See\n\t// https://github.com/kubernetes/community/blob/HEAD/contributors/devel/sig-instrumentation/migration-to-structured-logging.md\n\tklog.InfoS(\"Did something\", \"item\", \"foobar\")\n\t// Not recommended, but also works.\n\tklog.InfoS(\"This is a full sentence.\", \"item\", \"foobar\")\n}\n"
  },
  {
    "path": "examples/util/require/require.go",
    "content": "package require\n\nfunc NoError(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "exit.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2013 Google Inc. All Rights Reserved.\n// Copyright 2022 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage klog\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n)\n\nvar (\n\n\t// ExitFlushTimeout is the timeout that klog has traditionally used during\n\t// calls like Fatal or Exit when flushing log data right before exiting.\n\t// Applications that replace those calls and do not have some specific\n\t// requirements like \"exit immediately\" can use this value as parameter\n\t// for FlushAndExit.\n\t//\n\t// Can be set for testing purpose or to change the application's\n\t// default.\n\tExitFlushTimeout = 10 * time.Second\n\n\t// OsExit is the function called by FlushAndExit to terminate the program.\n\t//\n\t// Can be set for testing purpose or to change the application's\n\t// default behavior. Note that the function should not simply return\n\t// because callers of functions like Fatal will not expect that.\n\tOsExit = os.Exit\n)\n\n// FlushAndExit flushes log data for a certain amount of time and then calls\n// os.Exit. Combined with some logging call it provides a replacement for\n// traditional calls like Fatal or Exit.\nfunc FlushAndExit(flushTimeout time.Duration, exitCode int) {\n\ttimeoutFlush(flushTimeout)\n\tOsExit(exitCode)\n}\n\n// timeoutFlush calls Flush and returns when it completes or after timeout\n// elapses, whichever happens first.  This is needed because the hooks invoked\n// by Flush may deadlock when klog.Fatal is called from a hook that holds\n// a lock. Flushing also might take too long.\nfunc timeoutFlush(timeout time.Duration) {\n\tdone := make(chan bool, 1)\n\tgo func() {\n\t\tFlush() // calls logging.lockAndFlushAll()\n\t\tdone <- true\n\t}()\n\tselect {\n\tcase <-done:\n\tcase <-time.After(timeout):\n\t\tfmt.Fprintln(os.Stderr, \"klog: Flush took longer than\", timeout)\n\t}\n}\n"
  },
  {
    "path": "exit_test.go",
    "content": "// Copyright 2022 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage klog_test\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc ExampleFlushAndExit() {\n\t// Set up klog so that we can test it below.\n\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\tif err := fs.Set(\"skip_headers\", \"true\"); err != nil {\n\t\tpanic(err)\n\t}\n\tif err := fs.Set(\"logtostderr\", \"false\"); err != nil {\n\t\tpanic(err)\n\t}\n\tklog.SetOutput(os.Stdout)\n\tklog.OsExit = func(exitCode int) {\n\t\tfmt.Printf(\"os.Exit(%d)\\n\", exitCode)\n\t}\n\n\t// If we were to return or exit without flushing, this message would\n\t// get lost because it is buffered in memory by klog when writing to\n\t// files. Output to stderr is not buffered.\n\tklog.InfoS(\"exiting...\")\n\texitCode := 10\n\tklog.FlushAndExit(klog.ExitFlushTimeout, exitCode)\n\n\t// Output:\n\t// \"exiting...\"\n\t// os.Exit(10)\n}\n"
  },
  {
    "path": "format.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// Format wraps a value of an arbitrary type and implement fmt.Stringer and\n// logr.Marshaler for them. Stringer returns pretty-printed JSON. MarshalLog\n// returns the original value with a type that has no special methods, in\n// particular no MarshalLog or MarshalJSON.\n//\n// Wrapping values like that is useful when the value has a broken\n// implementation of these special functions (for example, a type which\n// inherits String from TypeMeta, but then doesn't re-implement String) or the\n// implementation produces output that is less readable or unstructured (for\n// example, the generated String functions for Kubernetes API types).\nfunc Format(obj interface{}) interface{} {\n\treturn formatAny{Object: obj}\n}\n\ntype formatAny struct {\n\tObject interface{}\n}\n\nfunc (f formatAny) String() string {\n\tvar buffer strings.Builder\n\tencoder := json.NewEncoder(&buffer)\n\tencoder.SetIndent(\"\", \"  \")\n\tif err := encoder.Encode(&f.Object); err != nil {\n\t\treturn fmt.Sprintf(\"error marshaling %T to JSON: %v\", f, err)\n\t}\n\treturn buffer.String()\n}\n\nfunc (f formatAny) MarshalLog() interface{} {\n\t// Returning a pointer to a pointer ensures that zapr doesn't find a\n\t// fmt.Stringer or logr.Marshaler when it checks the type of the\n\t// value. It then falls back to reflection, which dumps the value being\n\t// pointed to (JSON doesn't have pointers).\n\tptr := &f.Object\n\treturn &ptr\n}\n\nvar _ fmt.Stringer = formatAny{}\nvar _ logr.Marshaler = formatAny{}\n"
  },
  {
    "path": "format_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n\n\t\"github.com/go-logr/logr\"\n)\n\nfunc TestFormat(t *testing.T) {\n\tobj := config{\n\t\tTypeMeta: TypeMeta{\n\t\t\tKind: \"config\",\n\t\t},\n\t\tRealField: 42,\n\t}\n\n\tassertEqual(t, \"kind is config\", obj.String(), \"config.String()\")\n\tassertEqual(t, `{\n  \"Kind\": \"config\",\n  \"RealField\": 42\n}\n`, klog.Format(obj).(fmt.Stringer).String(), \"Format(config).String()\")\n\t// fmt.Sprintf would call String if it was available.\n\tstr := fmt.Sprintf(\"%s\", klog.Format(obj).(logr.Marshaler).MarshalLog())\n\tif strings.Contains(str, \"kind is config\") {\n\t\tt.Errorf(\"fmt.Sprintf called TypeMeta.String for klog.Format(obj).MarshalLog():\\n%s\", str)\n\t}\n\n\tstructured, err := json.Marshal(klog.Format(obj).(logr.Marshaler).MarshalLog())\n\tif err != nil {\n\t\tt.Errorf(\"JSON Marshal: %v\", err)\n\t} else {\n\t\tassertEqual(t, `{\"Kind\":\"config\",\"RealField\":42}`, string(structured), \"json.Marshal(klog.Format(obj).MarshalLog())\")\n\t}\n}\n\nfunc assertEqual(t *testing.T, expected, actual, what string) {\n\tif expected != actual {\n\t\tt.Errorf(\"%s:\\nExpected\\n%s\\nActual\\n%s\\n\", what, expected, actual)\n\t}\n}\n\ntype TypeMeta struct {\n\tKind string\n}\n\nfunc (t TypeMeta) String() string {\n\treturn \"kind is \" + t.Kind\n}\n\nfunc (t TypeMeta) MarshalLog() interface{} {\n\treturn t.Kind\n}\n\ntype config struct {\n\tTypeMeta\n\n\tRealField int\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module k8s.io/klog/v2\n\ngo 1.21\n\nrequire github.com/go-logr/logr v1.4.1\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=\ngithub.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\n"
  },
  {
    "path": "hack/verify-apidiff.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright 2020 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nfunction usage {\n  local script=\"$(basename $0)\"\n\n  echo >&2 \"Usage: ${script} [-r <branch|tag> | -d <dir>]\n\nThis script should be run at the root of a module.\n\n-r <branch|tag>\n  Compare the exported API of the local working copy with the \n  exported API of the local repo at the specified branch or tag.\n\n-d <dir>\n  Compare the exported API of the local working copy with the \n  exported API of the specified directory, which should point\n  to the root of a different version of the same module.\n\nExamples:\n  ${script} -r master\n  ${script} -r v1.10.0\n  ${script} -r release-1.10\n  ${script} -d /path/to/historical/version\n\"\n  exit 1\n}\n\nref=\"\"\ndir=\"\"\nwhile getopts r:d: o\ndo case \"$o\" in\n  r)    ref=\"$OPTARG\";;\n  d)    dir=\"$OPTARG\";;\n  [?])  usage;;\n  esac\ndone\n\n# If REF and DIR are empty, print usage and error\nif [[ -z \"${ref}\" && -z \"${dir}\" ]]; then\n  usage;\nfi\n# If REF and DIR are both set, print usage and error\nif [[ -n \"${ref}\" && -n \"${dir}\" ]]; then\n  usage;\nfi\n\nif ! which apidiff > /dev/null; then\n  echo \"Installing golang.org/x/exp/cmd/apidiff...\"\n  pushd \"${TMPDIR:-/tmp}\" > /dev/null\n    GO111MODULE=off go get golang.org/x/exp/cmd/apidiff\n  popd > /dev/null\nfi\n\noutput=$(mktemp -d -t \"apidiff.output.XXXX\")\ncleanup_output () { rm -fr \"${output}\"; }\ntrap cleanup_output EXIT\n\n# If ref is set, clone . to temp dir at $ref, and set $dir to the temp dir\nclone=\"\"\nbase=\"${dir}\"\nif [[ -n \"${ref}\" ]]; then\n  base=\"${ref}\"\n  clone=$(mktemp -d -t \"apidiff.clone.XXXX\")\n  cleanup_clone_and_output () { rm -fr \"${clone}\"; cleanup_output; }\n  trap cleanup_clone_and_output EXIT\n  git clone . -q --no-tags -b \"${ref}\" \"${clone}\"\n  dir=\"${clone}\"\nfi\n\npushd \"${dir}\" >/dev/null\n  echo \"Inspecting API of ${base}...\"\n  go list ./... > packages.txt\n  for pkg in $(cat packages.txt); do\n    mkdir -p \"${output}/${pkg}\"\n    apidiff -w \"${output}/${pkg}/apidiff.output\" \"${pkg}\"\n  done\npopd >/dev/null\n\nretval=0\n\necho \"Comparing with ${base}...\"\nfor pkg in $(go list ./...); do\n  # New packages are ok\n  if [ ! -f \"${output}/${pkg}/apidiff.output\" ]; then\n    continue\n  fi\n\n  # Check for incompatible changes to previous packages\n  incompatible=$(apidiff -incompatible \"${output}/${pkg}/apidiff.output\" \"${pkg}\")\n  if [[ -n \"${incompatible}\" ]]; then\n    echo >&2 \"FAIL: ${pkg} contains incompatible changes:\n${incompatible}\n\"\n    retval=1\n  fi\ndone\n\n# Check for removed packages\nremoved=$(comm -23 \"${dir}/packages.txt\" <(go list ./...))\nif [[ -n \"${removed}\" ]]; then\n  echo >&2 \"FAIL: removed packages:\n${removed}\n\"\n  retval=1\nfi\n\nexit $retval\n"
  },
  {
    "path": "imports.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"github.com/go-logr/logr\"\n)\n\n// The reason for providing these aliases is to allow code to work with logr\n// without directly importing it.\n\n// Logger in this package is exactly the same as logr.Logger.\ntype Logger = logr.Logger\n\n// LogSink in this package is exactly the same as logr.LogSink.\ntype LogSink = logr.LogSink\n\n// Runtimeinfo in this package is exactly the same as logr.RuntimeInfo.\ntype RuntimeInfo = logr.RuntimeInfo\n\nvar (\n\t// New is an alias for logr.New.\n\tNew = logr.New\n)\n"
  },
  {
    "path": "integration_tests/internal/main.go",
    "content": "/*\n\nThis file is intended to be used as a standin for a klog'ed executable.\n\nIt is called by the integration test via `go run` and with different klog\nflags to assert on klog behaviour, especially where klog logs its output\nwhen different combinations of the klog flags are at play.\n\nThis file is not intended to be used outside of the integration tests and\nis not supposed to be a (good) example on how to use klog.\n\n*/\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc main() {\n\tinfoLogLine := getEnvOrDie(\"KLOG_INFO_LOG\")\n\twarningLogLine := getEnvOrDie(\"KLOG_WARNING_LOG\")\n\terrorLogLine := getEnvOrDie(\"KLOG_ERROR_LOG\")\n\tfatalLogLine := getEnvOrDie(\"KLOG_FATAL_LOG\")\n\n\tklog.InitFlags(nil)\n\tflag.Parse()\n\tklog.Info(infoLogLine)\n\tklog.Warning(warningLogLine)\n\tklog.Error(errorLogLine)\n\tklog.Flush()\n\tklog.Fatal(fatalLogLine)\n}\n\nfunc getEnvOrDie(name string) string {\n\tval, ok := os.LookupEnv(name)\n\tif !ok {\n\t\tfmt.Fprintln(os.Stderr, name, \"could not be found in environment\")\n\t\tos.Exit(1)\n\t}\n\treturn val\n}\n"
  },
  {
    "path": "integration_tests/klog_test.go",
    "content": "package integration_tests_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n)\n\nconst (\n\tinfoLog    = \"this is a info log line\"\n\twarningLog = \"this is a warning log line\"\n\terrorLog   = \"this is a error log line\"\n\tfatalLog   = \"this is a fatal log line\"\n)\n\n// res is a type alias to a slice of pointers to regular expressions.\ntype res = []*regexp.Regexp\n\nvar (\n\tinfoLogRE    = regexp.MustCompile(regexp.QuoteMeta(infoLog))\n\twarningLogRE = regexp.MustCompile(regexp.QuoteMeta(warningLog))\n\terrorLogRE   = regexp.MustCompile(regexp.QuoteMeta(errorLog))\n\tfatalLogRE   = regexp.MustCompile(regexp.QuoteMeta(fatalLog))\n\n\tstackTraceRE = regexp.MustCompile(`\\ngoroutine \\d+ \\[[^]]+\\]:\\n`)\n\n\tallLogREs = res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE, stackTraceRE}\n\n\tdefaultExpectedInDirREs = map[int]res{\n\t\t0: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE, infoLogRE},\n\t\t1: {stackTraceRE, fatalLogRE, errorLogRE, warningLogRE},\n\t\t2: {stackTraceRE, fatalLogRE, errorLogRE},\n\t\t3: {stackTraceRE, fatalLogRE},\n\t}\n\texpectedOneOutputInDirREs = map[int]res{\n\t\t0: {infoLogRE},\n\t\t1: {warningLogRE},\n\t\t2: {errorLogRE},\n\t\t3: {fatalLogRE},\n\t}\n\n\tdefaultNotExpectedInDirREs = map[int]res{\n\t\t0: {},\n\t\t1: {infoLogRE},\n\t\t2: {infoLogRE, warningLogRE},\n\t\t3: {infoLogRE, warningLogRE, errorLogRE},\n\t}\n)\n\nfunc TestDestinationsWithDifferentFlags(t *testing.T) {\n\ttests := map[string]struct {\n\t\t// logfile states if the flag -log_file should be set\n\t\tlogfile bool\n\t\t// logdir states if the flag -log_dir should be set\n\t\tlogdir bool\n\t\t// flags is for additional flags to pass to the klog'ed executable\n\t\tflags []string\n\n\t\t// expectedLogFile states if we generally expect the log file to exist.\n\t\t// If this is not set, we expect the file not to exist and will error if it\n\t\t// does.\n\t\texpectedLogFile bool\n\t\t// expectedLogDir states if we generally expect the log files in the log\n\t\t// dir to exist.\n\t\t// If this is not set, we expect the log files in the log dir not to exist and\n\t\t// will error if they do.\n\t\texpectedLogDir bool\n\n\t\t// expectedOnStderr is a list of REs we expect to find on stderr\n\t\texpectedOnStderr res\n\t\t// notExpectedOnStderr is a list of REs that we must not find on stderr\n\t\tnotExpectedOnStderr res\n\t\t// expectedInFile is a list of REs we expect to find in the log file\n\t\texpectedInFile res\n\t\t// notExpectedInFile is a list of REs we must not find in the log file\n\t\tnotExpectedInFile res\n\n\t\t// expectedInDir is a list of REs we expect to find in the log files in the\n\t\t// log dir, specified by log severity (0 = warning, 1 = info, ...)\n\t\texpectedInDir map[int]res\n\t\t// notExpectedInDir is a list of REs we must not find in the log files in\n\t\t// the log dir, specified by log severity (0 = warning, 1 = info, ...)\n\t\tnotExpectedInDir map[int]res\n\t}{\n\t\t\"default flags\": {\n\t\t\t// Everything, EXCEPT the trace on fatal, goes to stderr\n\n\t\t\texpectedOnStderr:    res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE},\n\t\t\tnotExpectedOnStderr: res{stackTraceRE},\n\t\t},\n\t\t\"everything disabled\": {\n\t\t\t// Nothing, including the trace on fatal, is showing anywhere\n\n\t\t\tflags: []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\tnotExpectedOnStderr: allLogREs,\n\t\t},\n\t\t\"everything disabled but low stderrthreshold\": {\n\t\t\t// Everything above -stderrthreshold, including the trace on fatal, will\n\t\t\t// be logged to stderr, even if we set -logtostderr to false.\n\n\t\t\tflags: []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1\"},\n\n\t\t\texpectedOnStderr:    res{warningLogRE, errorLogRE, stackTraceRE},\n\t\t\tnotExpectedOnStderr: res{infoLogRE},\n\t\t},\n\t\t\"with logtostderr only\": {\n\t\t\t// Everything, EXCEPT the trace on fatal, goes to stderr\n\n\t\t\tflags: []string{\"-logtostderr=true\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedOnStderr:    res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE},\n\t\t\tnotExpectedOnStderr: res{stackTraceRE},\n\t\t},\n\t\t\"with log file only\": {\n\t\t\t// Everything, including the trace on fatal, goes to the single log file\n\n\t\t\tlogfile: true,\n\t\t\tflags:   []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedLogFile: true,\n\n\t\t\tnotExpectedOnStderr: allLogREs,\n\t\t\texpectedInFile:      allLogREs,\n\t\t},\n\t\t\"with log dir only\": {\n\t\t\t// Everything, including the trace on fatal, goes to the log files in the log dir\n\n\t\t\tlogdir: true,\n\t\t\tflags:  []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedLogDir: true,\n\n\t\t\tnotExpectedOnStderr: allLogREs,\n\t\t\texpectedInDir:       defaultExpectedInDirREs,\n\t\t\tnotExpectedInDir:    defaultNotExpectedInDirREs,\n\t\t},\n\t\t\"with log dir only and one_output\": {\n\t\t\t// Everything, including the trace on fatal, goes to the log files in the log dir\n\n\t\t\tlogdir: true,\n\t\t\tflags:  []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\", \"-one_output=true\"},\n\n\t\t\texpectedLogDir: true,\n\n\t\t\tnotExpectedOnStderr: allLogREs,\n\t\t\texpectedInDir:       expectedOneOutputInDirREs,\n\t\t\tnotExpectedInDir:    defaultNotExpectedInDirREs,\n\t\t},\n\t\t\"with log dir and logtostderr\": {\n\t\t\t// Everything, EXCEPT the trace on fatal, goes to stderr. The -log_dir is\n\t\t\t// ignored, nothing goes to the log files in the log dir.\n\n\t\t\tlogdir: true,\n\t\t\tflags:  []string{\"-logtostderr=true\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedOnStderr:    res{infoLogRE, warningLogRE, errorLogRE, fatalLogRE},\n\t\t\tnotExpectedOnStderr: res{stackTraceRE},\n\t\t},\n\t\t\"with log file and log dir\": {\n\t\t\t// Everything, including the trace on fatal, goes to the single log file.\n\t\t\t// The -log_dir is ignored, nothing goes to the log file in the log dir.\n\n\t\t\tlogdir:  true,\n\t\t\tlogfile: true,\n\t\t\tflags:   []string{\"-logtostderr=false\", \"-alsologtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedLogFile: true,\n\n\t\t\tnotExpectedOnStderr: allLogREs,\n\t\t\texpectedInFile:      allLogREs,\n\t\t},\n\t\t\"with log file and alsologtostderr\": {\n\t\t\t// Everything, including the trace on fatal, goes to the single log file\n\t\t\t// AND to stderr.\n\n\t\t\tflags:   []string{\"-alsologtostderr=true\", \"-logtostderr=false\", \"-stderrthreshold=1000\"},\n\t\t\tlogfile: true,\n\n\t\t\texpectedLogFile: true,\n\n\t\t\texpectedOnStderr: allLogREs,\n\t\t\texpectedInFile:   allLogREs,\n\t\t},\n\t\t\"with log dir and alsologtostderr\": {\n\t\t\t// Everything, including the trace on fatal, goes to the log file in the\n\t\t\t// log dir AND to stderr.\n\n\t\t\tlogdir: true,\n\t\t\tflags:  []string{\"-alsologtostderr=true\", \"-logtostderr=false\", \"-stderrthreshold=1000\"},\n\n\t\t\texpectedLogDir: true,\n\n\t\t\texpectedOnStderr: allLogREs,\n\t\t\texpectedInDir:    defaultExpectedInDirREs,\n\t\t\tnotExpectedInDir: defaultNotExpectedInDirREs,\n\t\t},\n\t\t\"with log dir, alsologtostderr and one_output\": {\n\t\t\t// Everything, including the trace on fatal, goes to the log file in the\n\t\t\t// log dir AND to stderr.\n\n\t\t\tlogdir: true,\n\t\t\tflags:  []string{\"-alsologtostderr=true\", \"-logtostderr=false\", \"-stderrthreshold=1000\", \"-one_output=true\"},\n\n\t\t\texpectedLogDir: true,\n\n\t\t\texpectedOnStderr: allLogREs,\n\t\t\texpectedInDir:    expectedOneOutputInDirREs,\n\t\t\tnotExpectedInDir: defaultNotExpectedInDirREs,\n\t\t},\n\t}\n\n\tbinaryFileExtention := \"\"\n\tif runtime.GOOS == \"windows\" {\n\t\tbinaryFileExtention = \".exe\"\n\t}\n\n\tfor tcName, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\twithTmpDir(t, func(logdir string) {\n\t\t\t\t// :: Setup\n\t\t\t\tflags := tc.flags\n\t\t\t\tstderr := &bytes.Buffer{}\n\t\t\t\tlogfile := filepath.Join(logdir, \"the_single_log_file\") // /some/tmp/dir/the_single_log_file\n\n\t\t\t\tif tc.logfile {\n\t\t\t\t\tflags = append(flags, \"-log_file=\"+logfile)\n\t\t\t\t}\n\t\t\t\tif tc.logdir {\n\t\t\t\t\tflags = append(flags, \"-log_dir=\"+logdir)\n\t\t\t\t}\n\n\t\t\t\t// :: Execute\n\t\t\t\tklogRun(t, flags, stderr)\n\n\t\t\t\t// :: Assert\n\t\t\t\t// check stderr\n\t\t\t\tcheckForLogs(t, tc.expectedOnStderr, tc.notExpectedOnStderr, stderr.String(), \"stderr\")\n\n\t\t\t\t// check log_file\n\t\t\t\tif tc.expectedLogFile {\n\t\t\t\t\tcontent := getFileContent(t, logfile)\n\t\t\t\t\tcheckForLogs(t, tc.expectedInFile, tc.notExpectedInFile, content, \"logfile\")\n\t\t\t\t} else {\n\t\t\t\t\tassertFileIsAbsent(t, logfile)\n\t\t\t\t}\n\n\t\t\t\t// check files in log_dir\n\t\t\t\tfor level, levelName := range logFileLevels {\n\t\t\t\t\tbinaryName := \"main\" + binaryFileExtention\n\t\t\t\t\tlogfile, err := getLogFilePath(logdir, binaryName, levelName)\n\t\t\t\t\tif tc.expectedLogDir {\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Errorf(\"Unable to find log file: %v\", err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontent := getFileContent(t, logfile)\n\t\t\t\t\t\tcheckForLogs(t, tc.expectedInDir[level], tc.notExpectedInDir[level], content, \"logfile[\"+logfile+\"]\")\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif err == nil {\n\t\t\t\t\t\t\tt.Errorf(\"Unexpectedly found log file %s\", logfile)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\nconst klogExampleGoFile = \"./internal/main.go\"\n\n// klogRun spawns a simple executable that uses klog, to later inspect its\n// stderr and potentially created log files\nfunc klogRun(t *testing.T, flags []string, stderr io.Writer) {\n\tcallFlags := []string{\"run\", klogExampleGoFile}\n\tcallFlags = append(callFlags, flags...)\n\n\tcmd := exec.Command(\"go\", callFlags...)\n\tcmd.Stderr = stderr\n\tcmd.Env = append(os.Environ(),\n\t\t\"KLOG_INFO_LOG=\"+infoLog,\n\t\t\"KLOG_WARNING_LOG=\"+warningLog,\n\t\t\"KLOG_ERROR_LOG=\"+errorLog,\n\t\t\"KLOG_FATAL_LOG=\"+fatalLog,\n\t)\n\n\terr := cmd.Run()\n\n\tif _, ok := err.(*exec.ExitError); !ok {\n\t\tt.Fatalf(\"Run failed: %v\", err)\n\t}\n}\n\nvar logFileLevels = map[int]string{\n\t0: \"INFO\",\n\t1: \"WARNING\",\n\t2: \"ERROR\",\n\t3: \"FATAL\",\n}\n\nfunc getFileContent(t *testing.T, filePath string) string {\n\tcontent, err := ioutil.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Errorf(\"Could not read file '%s': %v\", filePath, err)\n\t}\n\treturn string(content)\n}\n\nfunc assertFileIsAbsent(t *testing.T, filePath string) {\n\tif _, err := os.Stat(filePath); !os.IsNotExist(err) {\n\t\tt.Errorf(\"Expected file '%s' not to exist\", filePath)\n\t}\n}\n\nfunc checkForLogs(t *testing.T, expected, disallowed res, content, name string) {\n\tfor _, re := range expected {\n\t\tcheckExpected(t, true, name, content, re)\n\t}\n\tfor _, re := range disallowed {\n\t\tcheckExpected(t, false, name, content, re)\n\t}\n}\n\nfunc checkExpected(t *testing.T, expected bool, where string, haystack string, needle *regexp.Regexp) {\n\tfound := needle.MatchString(haystack)\n\n\tif expected && !found {\n\t\tt.Errorf(\"Expected to find '%s' in %s\", needle, where)\n\t}\n\tif !expected && found {\n\t\tt.Errorf(\"Expected not to find '%s' in %s\", needle, where)\n\t}\n}\n\nfunc withTmpDir(t *testing.T, f func(string)) {\n\ttmpDir, err := ioutil.TempDir(\"\", \"klog_e2e_\")\n\tif err != nil {\n\t\tt.Fatalf(\"Could not create temp directory: %v\", err)\n\t}\n\tdefer func() {\n\t\tif err := os.RemoveAll(tmpDir); err != nil {\n\t\t\tt.Fatalf(\"Could not remove temp directory '%s': %v\", tmpDir, err)\n\t\t}\n\t}()\n\n\tf(tmpDir)\n}\n\n// getLogFileFromDir returns the path of either the symbolic link to the logfile, or the the logfile itself. This must\n// be done as the creation of a symlink is not guaranteed on any platform. On Windows, only users with administration\n// privileges can create a symlink.\nfunc getLogFilePath(dir, binaryName, levelName string) (string, error) {\n\tsymlink := filepath.Join(dir, binaryName+\".\"+levelName)\n\tif _, err := os.Stat(symlink); err == nil {\n\t\treturn symlink, nil\n\t}\n\tfiles, err := ioutil.ReadDir(dir)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"could not read directory %s: %v\", dir, err)\n\t}\n\tvar foundFile string\n\tfor _, file := range files {\n\t\tif strings.HasPrefix(file.Name(), binaryName) && strings.Contains(file.Name(), levelName) {\n\t\t\tif foundFile != \"\" {\n\t\t\t\treturn \"\", fmt.Errorf(\"found multiple matching files\")\n\t\t\t}\n\t\t\tfoundFile = file.Name()\n\t\t}\n\t}\n\tif foundFile != \"\" {\n\t\treturn filepath.Join(dir, foundFile), nil\n\t}\n\treturn \"\", fmt.Errorf(\"file missing from directory\")\n}\n"
  },
  {
    "path": "internal/buffer/buffer.go",
    "content": "// Copyright 2013 Google Inc. All Rights Reserved.\n// Copyright 2022 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package buffer provides a cache for byte.Buffer instances that can be reused\n// to avoid frequent allocation and deallocation. It also has utility code\n// for log header formatting that use these buffers.\npackage buffer\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/severity\"\n)\n\nvar (\n\t// Pid is inserted into log headers. Can be overridden for tests.\n\tPid = os.Getpid()\n\n\t// Time, if set, will be used instead of the actual current time.\n\tTime *time.Time\n)\n\n// Buffer holds a single byte.Buffer for reuse. The zero value is ready for\n// use. It also provides some helper methods for output formatting.\ntype Buffer struct {\n\tbytes.Buffer\n\tTmp [64]byte // temporary byte array for creating headers.\n}\n\nvar buffers = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn new(Buffer)\n\t},\n}\n\n// GetBuffer returns a new, ready-to-use buffer.\nfunc GetBuffer() *Buffer {\n\tb := buffers.Get().(*Buffer)\n\tb.Reset()\n\treturn b\n}\n\n// PutBuffer returns a buffer to the free list.\nfunc PutBuffer(b *Buffer) {\n\tif b.Len() >= 256 {\n\t\t// Let big buffers die a natural death, without relying on\n\t\t// sync.Pool behavior. The documentation implies that items may\n\t\t// get deallocated while stored there (\"If the Pool holds the\n\t\t// only reference when this [= be removed automatically]\n\t\t// happens, the item might be deallocated.\"), but\n\t\t// https://github.com/golang/go/issues/23199 leans more towards\n\t\t// having such a size limit.\n\t\treturn\n\t}\n\n\tbuffers.Put(b)\n}\n\n// Some custom tiny helper functions to print the log header efficiently.\n\nconst digits = \"0123456789\"\n\n// twoDigits formats a zero-prefixed two-digit integer at buf.Tmp[i].\nfunc (buf *Buffer) twoDigits(i, d int) {\n\tbuf.Tmp[i+1] = digits[d%10]\n\td /= 10\n\tbuf.Tmp[i] = digits[d%10]\n}\n\n// nDigits formats an n-digit integer at buf.Tmp[i],\n// padding with pad on the left.\n// It assumes d >= 0.\nfunc (buf *Buffer) nDigits(n, i, d int, pad byte) {\n\tj := n - 1\n\tfor ; j >= 0 && d > 0; j-- {\n\t\tbuf.Tmp[i+j] = digits[d%10]\n\t\td /= 10\n\t}\n\tfor ; j >= 0; j-- {\n\t\tbuf.Tmp[i+j] = pad\n\t}\n}\n\n// someDigits formats a zero-prefixed variable-width integer at buf.Tmp[i].\nfunc (buf *Buffer) someDigits(i, d int) int {\n\t// Print into the top, then copy down. We know there's space for at least\n\t// a 10-digit number.\n\tj := len(buf.Tmp)\n\tfor {\n\t\tj--\n\t\tbuf.Tmp[j] = digits[d%10]\n\t\td /= 10\n\t\tif d == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn copy(buf.Tmp[i:], buf.Tmp[j:])\n}\n\n// FormatHeader formats a log header using the provided file name and line number\n// and writes it into the buffer.\nfunc (buf *Buffer) FormatHeader(s severity.Severity, file string, line int, now time.Time) {\n\tif line < 0 {\n\t\tline = 0 // not a real line number, but acceptable to someDigits\n\t}\n\tif s > severity.FatalLog {\n\t\ts = severity.InfoLog // for safety.\n\t}\n\n\t// Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.\n\t// It's worth about 3X. Fprintf is hard.\n\tif Time != nil {\n\t\tnow = *Time\n\t}\n\t_, month, day := now.Date()\n\thour, minute, second := now.Clock()\n\t// Lmmdd hh:mm:ss.uuuuuu threadid file:line]\n\tbuf.Tmp[0] = severity.Char[s]\n\tbuf.twoDigits(1, int(month))\n\tbuf.twoDigits(3, day)\n\tbuf.Tmp[5] = ' '\n\tbuf.twoDigits(6, hour)\n\tbuf.Tmp[8] = ':'\n\tbuf.twoDigits(9, minute)\n\tbuf.Tmp[11] = ':'\n\tbuf.twoDigits(12, second)\n\tbuf.Tmp[14] = '.'\n\tbuf.nDigits(6, 15, now.Nanosecond()/1000, '0')\n\tbuf.Tmp[21] = ' '\n\tbuf.nDigits(7, 22, Pid, ' ') // TODO: should be TID\n\tbuf.Tmp[29] = ' '\n\tbuf.Write(buf.Tmp[:30])\n\tbuf.WriteString(file)\n\tbuf.Tmp[0] = ':'\n\tn := buf.someDigits(1, line)\n\tbuf.Tmp[n+1] = ']'\n\tbuf.Tmp[n+2] = ' '\n\tbuf.Write(buf.Tmp[:n+3])\n}\n\n// SprintHeader formats a log header and returns a string. This is a simpler\n// version of FormatHeader for use in ktesting.\nfunc (buf *Buffer) SprintHeader(s severity.Severity, now time.Time) string {\n\tif s > severity.FatalLog {\n\t\ts = severity.InfoLog // for safety.\n\t}\n\n\t// Avoid Fprintf, for speed. The format is so simple that we can do it quickly by hand.\n\t// It's worth about 3X. Fprintf is hard.\n\tif Time != nil {\n\t\tnow = *Time\n\t}\n\t_, month, day := now.Date()\n\thour, minute, second := now.Clock()\n\t// Lmmdd hh:mm:ss.uuuuuu threadid file:line]\n\tbuf.Tmp[0] = severity.Char[s]\n\tbuf.twoDigits(1, int(month))\n\tbuf.twoDigits(3, day)\n\tbuf.Tmp[5] = ' '\n\tbuf.twoDigits(6, hour)\n\tbuf.Tmp[8] = ':'\n\tbuf.twoDigits(9, minute)\n\tbuf.Tmp[11] = ':'\n\tbuf.twoDigits(12, second)\n\tbuf.Tmp[14] = '.'\n\tbuf.nDigits(6, 15, now.Nanosecond()/1000, '0')\n\tbuf.Tmp[21] = ']'\n\treturn string(buf.Tmp[:22])\n}\n"
  },
  {
    "path": "internal/clock/README.md",
    "content": "# Clock\n\nThis package provides an interface for time-based operations.  It allows\nmocking time for testing.\n\nThis is a copy of k8s.io/utils/clock. We have to copy it to avoid a circular\ndependency (k8s.io/klog -> k8s.io/utils -> k8s.io/klog).\n"
  },
  {
    "path": "internal/clock/clock.go",
    "content": "/*\nCopyright 2014 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage clock\n\nimport \"time\"\n\n// PassiveClock allows for injecting fake or real clocks into code\n// that needs to read the current time but does not support scheduling\n// activity in the future.\ntype PassiveClock interface {\n\tNow() time.Time\n\tSince(time.Time) time.Duration\n}\n\n// Clock allows for injecting fake or real clocks into code that\n// needs to do arbitrary things based on time.\ntype Clock interface {\n\tPassiveClock\n\t// After returns the channel of a new Timer.\n\t// This method does not allow to free/GC the backing timer before it fires. Use\n\t// NewTimer instead.\n\tAfter(d time.Duration) <-chan time.Time\n\t// NewTimer returns a new Timer.\n\tNewTimer(d time.Duration) Timer\n\t// Sleep sleeps for the provided duration d.\n\t// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.\n\tSleep(d time.Duration)\n\t// NewTicker returns a new Ticker.\n\tNewTicker(time.Duration) Ticker\n}\n\n// WithDelayedExecution allows for injecting fake or real clocks into\n// code that needs to make use of AfterFunc functionality.\ntype WithDelayedExecution interface {\n\tClock\n\t// AfterFunc executes f in its own goroutine after waiting\n\t// for d duration and returns a Timer whose channel can be\n\t// closed by calling Stop() on the Timer.\n\tAfterFunc(d time.Duration, f func()) Timer\n}\n\n// WithTickerAndDelayedExecution allows for injecting fake or real clocks\n// into code that needs Ticker and AfterFunc functionality\ntype WithTickerAndDelayedExecution interface {\n\tClock\n\t// AfterFunc executes f in its own goroutine after waiting\n\t// for d duration and returns a Timer whose channel can be\n\t// closed by calling Stop() on the Timer.\n\tAfterFunc(d time.Duration, f func()) Timer\n}\n\n// Ticker defines the Ticker interface.\ntype Ticker interface {\n\tC() <-chan time.Time\n\tStop()\n}\n\nvar _ Clock = RealClock{}\n\n// RealClock really calls time.Now()\ntype RealClock struct{}\n\n// Now returns the current time.\nfunc (RealClock) Now() time.Time {\n\treturn time.Now()\n}\n\n// Since returns time since the specified timestamp.\nfunc (RealClock) Since(ts time.Time) time.Duration {\n\treturn time.Since(ts)\n}\n\n// After is the same as time.After(d).\n// This method does not allow to free/GC the backing timer before it fires. Use\n// NewTimer instead.\nfunc (RealClock) After(d time.Duration) <-chan time.Time {\n\treturn time.After(d)\n}\n\n// NewTimer is the same as time.NewTimer(d)\nfunc (RealClock) NewTimer(d time.Duration) Timer {\n\treturn &realTimer{\n\t\ttimer: time.NewTimer(d),\n\t}\n}\n\n// AfterFunc is the same as time.AfterFunc(d, f).\nfunc (RealClock) AfterFunc(d time.Duration, f func()) Timer {\n\treturn &realTimer{\n\t\ttimer: time.AfterFunc(d, f),\n\t}\n}\n\n// NewTicker returns a new Ticker.\nfunc (RealClock) NewTicker(d time.Duration) Ticker {\n\treturn &realTicker{\n\t\tticker: time.NewTicker(d),\n\t}\n}\n\n// Sleep is the same as time.Sleep(d)\n// Consider making the sleep interruptible by using 'select' on a context channel and a timer channel.\nfunc (RealClock) Sleep(d time.Duration) {\n\ttime.Sleep(d)\n}\n\n// Timer allows for injecting fake or real timers into code that\n// needs to do arbitrary things based on time.\ntype Timer interface {\n\tC() <-chan time.Time\n\tStop() bool\n\tReset(d time.Duration) bool\n}\n\nvar _ = Timer(&realTimer{})\n\n// realTimer is backed by an actual time.Timer.\ntype realTimer struct {\n\ttimer *time.Timer\n}\n\n// C returns the underlying timer's channel.\nfunc (r *realTimer) C() <-chan time.Time {\n\treturn r.timer.C\n}\n\n// Stop calls Stop() on the underlying timer.\nfunc (r *realTimer) Stop() bool {\n\treturn r.timer.Stop()\n}\n\n// Reset calls Reset() on the underlying timer.\nfunc (r *realTimer) Reset(d time.Duration) bool {\n\treturn r.timer.Reset(d)\n}\n\ntype realTicker struct {\n\tticker *time.Ticker\n}\n\nfunc (r *realTicker) C() <-chan time.Time {\n\treturn r.ticker.C\n}\n\nfunc (r *realTicker) Stop() {\n\tr.ticker.Stop()\n}\n"
  },
  {
    "path": "internal/clock/testing/fake_clock.go",
    "content": "/*\nCopyright 2014 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage testing\n\nimport (\n\t\"sync\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/clock\"\n)\n\nvar (\n\t_ = clock.PassiveClock(&FakePassiveClock{})\n\t_ = clock.Clock(&IntervalClock{})\n)\n\n// FakePassiveClock implements PassiveClock, but returns an arbitrary time.\ntype FakePassiveClock struct {\n\tlock sync.RWMutex\n\ttime time.Time\n}\n\n// FakeClock implements clock.Clock, but returns an arbitrary time.\ntype FakeClock struct {\n\tFakePassiveClock\n\n\t// waiters are waiting for the fake time to pass their specified time\n\twaiters []*fakeClockWaiter\n}\n\ntype fakeClockWaiter struct {\n\ttargetTime    time.Time\n\tstepInterval  time.Duration\n\tskipIfBlocked bool\n\tdestChan      chan time.Time\n\tfired         bool\n\tafterFunc     func()\n}\n\n// NewFakePassiveClock returns a new FakePassiveClock.\nfunc NewFakePassiveClock(t time.Time) *FakePassiveClock {\n\treturn &FakePassiveClock{\n\t\ttime: t,\n\t}\n}\n\n// NewFakeClock constructs a fake clock set to the provided time.\nfunc NewFakeClock(t time.Time) *FakeClock {\n\treturn &FakeClock{\n\t\tFakePassiveClock: *NewFakePassiveClock(t),\n\t}\n}\n\n// Now returns f's time.\nfunc (f *FakePassiveClock) Now() time.Time {\n\tf.lock.RLock()\n\tdefer f.lock.RUnlock()\n\treturn f.time\n}\n\n// Since returns time since the time in f.\nfunc (f *FakePassiveClock) Since(ts time.Time) time.Duration {\n\tf.lock.RLock()\n\tdefer f.lock.RUnlock()\n\treturn f.time.Sub(ts)\n}\n\n// SetTime sets the time on the FakePassiveClock.\nfunc (f *FakePassiveClock) SetTime(t time.Time) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tf.time = t\n}\n\n// After is the fake version of time.After(d).\nfunc (f *FakeClock) After(d time.Duration) <-chan time.Time {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tstopTime := f.time.Add(d)\n\tch := make(chan time.Time, 1) // Don't block!\n\tf.waiters = append(f.waiters, &fakeClockWaiter{\n\t\ttargetTime: stopTime,\n\t\tdestChan:   ch,\n\t})\n\treturn ch\n}\n\n// NewTimer constructs a fake timer, akin to time.NewTimer(d).\nfunc (f *FakeClock) NewTimer(d time.Duration) clock.Timer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tstopTime := f.time.Add(d)\n\tch := make(chan time.Time, 1) // Don't block!\n\ttimer := &fakeTimer{\n\t\tfakeClock: f,\n\t\twaiter: fakeClockWaiter{\n\t\t\ttargetTime: stopTime,\n\t\t\tdestChan:   ch,\n\t\t},\n\t}\n\tf.waiters = append(f.waiters, &timer.waiter)\n\treturn timer\n}\n\n// AfterFunc is the Fake version of time.AfterFunc(d, cb).\nfunc (f *FakeClock) AfterFunc(d time.Duration, cb func()) clock.Timer {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tstopTime := f.time.Add(d)\n\tch := make(chan time.Time, 1) // Don't block!\n\n\ttimer := &fakeTimer{\n\t\tfakeClock: f,\n\t\twaiter: fakeClockWaiter{\n\t\t\ttargetTime: stopTime,\n\t\t\tdestChan:   ch,\n\t\t\tafterFunc:  cb,\n\t\t},\n\t}\n\tf.waiters = append(f.waiters, &timer.waiter)\n\treturn timer\n}\n\n// Tick constructs a fake ticker, akin to time.Tick\nfunc (f *FakeClock) Tick(d time.Duration) <-chan time.Time {\n\tif d <= 0 {\n\t\treturn nil\n\t}\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\ttickTime := f.time.Add(d)\n\tch := make(chan time.Time, 1) // hold one tick\n\tf.waiters = append(f.waiters, &fakeClockWaiter{\n\t\ttargetTime:    tickTime,\n\t\tstepInterval:  d,\n\t\tskipIfBlocked: true,\n\t\tdestChan:      ch,\n\t})\n\n\treturn ch\n}\n\n// NewTicker returns a new Ticker.\nfunc (f *FakeClock) NewTicker(d time.Duration) clock.Ticker {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\ttickTime := f.time.Add(d)\n\tch := make(chan time.Time, 1) // hold one tick\n\tf.waiters = append(f.waiters, &fakeClockWaiter{\n\t\ttargetTime:    tickTime,\n\t\tstepInterval:  d,\n\t\tskipIfBlocked: true,\n\t\tdestChan:      ch,\n\t})\n\n\treturn &fakeTicker{\n\t\tc: ch,\n\t}\n}\n\n// Step moves the clock by Duration and notifies anyone that's called After,\n// Tick, or NewTimer.\nfunc (f *FakeClock) Step(d time.Duration) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tf.setTimeLocked(f.time.Add(d))\n}\n\n// SetTime sets the time.\nfunc (f *FakeClock) SetTime(t time.Time) {\n\tf.lock.Lock()\n\tdefer f.lock.Unlock()\n\tf.setTimeLocked(t)\n}\n\n// Actually changes the time and checks any waiters. f must be write-locked.\nfunc (f *FakeClock) setTimeLocked(t time.Time) {\n\tf.time = t\n\tnewWaiters := make([]*fakeClockWaiter, 0, len(f.waiters))\n\tfor i := range f.waiters {\n\t\tw := f.waiters[i]\n\t\tif !w.targetTime.After(t) {\n\t\t\tif w.skipIfBlocked {\n\t\t\t\tselect {\n\t\t\t\tcase w.destChan <- t:\n\t\t\t\t\tw.fired = true\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tw.destChan <- t\n\t\t\t\tw.fired = true\n\t\t\t}\n\n\t\t\tif w.afterFunc != nil {\n\t\t\t\tw.afterFunc()\n\t\t\t}\n\n\t\t\tif w.stepInterval > 0 {\n\t\t\t\tfor !w.targetTime.After(t) {\n\t\t\t\t\tw.targetTime = w.targetTime.Add(w.stepInterval)\n\t\t\t\t}\n\t\t\t\tnewWaiters = append(newWaiters, w)\n\t\t\t}\n\n\t\t} else {\n\t\t\tnewWaiters = append(newWaiters, f.waiters[i])\n\t\t}\n\t}\n\tf.waiters = newWaiters\n}\n\n// HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can\n// write race-free tests).\nfunc (f *FakeClock) HasWaiters() bool {\n\tf.lock.RLock()\n\tdefer f.lock.RUnlock()\n\treturn len(f.waiters) > 0\n}\n\n// Sleep is akin to time.Sleep\nfunc (f *FakeClock) Sleep(d time.Duration) {\n\tf.Step(d)\n}\n\n// IntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration.\n// IntervalClock technically implements the other methods of clock.Clock, but each implementation is just a panic.\n//\n// Deprecated: See SimpleIntervalClock for an alternative that only has the methods of PassiveClock.\ntype IntervalClock struct {\n\tTime     time.Time\n\tDuration time.Duration\n}\n\n// Now returns i's time.\nfunc (i *IntervalClock) Now() time.Time {\n\ti.Time = i.Time.Add(i.Duration)\n\treturn i.Time\n}\n\n// Since returns time since the time in i.\nfunc (i *IntervalClock) Since(ts time.Time) time.Duration {\n\treturn i.Time.Sub(ts)\n}\n\n// After is unimplemented, will panic.\n// TODO: make interval clock use FakeClock so this can be implemented.\nfunc (*IntervalClock) After(time.Duration) <-chan time.Time {\n\tpanic(\"IntervalClock doesn't implement After\")\n}\n\n// NewTimer is unimplemented, will panic.\n// TODO: make interval clock use FakeClock so this can be implemented.\nfunc (*IntervalClock) NewTimer(time.Duration) clock.Timer {\n\tpanic(\"IntervalClock doesn't implement NewTimer\")\n}\n\n// AfterFunc is unimplemented, will panic.\n// TODO: make interval clock use FakeClock so this can be implemented.\nfunc (*IntervalClock) AfterFunc(time.Duration, func()) clock.Timer {\n\tpanic(\"IntervalClock doesn't implement AfterFunc\")\n}\n\n// NewTicker has no implementation yet and is omitted.\n// TODO: make interval clock use FakeClock so this can be implemented.\nfunc (*IntervalClock) NewTicker(time.Duration) clock.Ticker {\n\tpanic(\"IntervalClock doesn't implement NewTicker\")\n}\n\n// Sleep is unimplemented, will panic.\nfunc (*IntervalClock) Sleep(time.Duration) {\n\tpanic(\"IntervalClock doesn't implement Sleep\")\n}\n\nvar _ = clock.Timer(&fakeTimer{})\n\n// fakeTimer implements clock.Timer based on a FakeClock.\ntype fakeTimer struct {\n\tfakeClock *FakeClock\n\twaiter    fakeClockWaiter\n}\n\n// C returns the channel that notifies when this timer has fired.\nfunc (f *fakeTimer) C() <-chan time.Time {\n\treturn f.waiter.destChan\n}\n\n// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.\nfunc (f *fakeTimer) Stop() bool {\n\tf.fakeClock.lock.Lock()\n\tdefer f.fakeClock.lock.Unlock()\n\n\tnewWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters))\n\tfor i := range f.fakeClock.waiters {\n\t\tw := f.fakeClock.waiters[i]\n\t\tif w != &f.waiter {\n\t\t\tnewWaiters = append(newWaiters, w)\n\t\t}\n\t}\n\n\tf.fakeClock.waiters = newWaiters\n\n\treturn !f.waiter.fired\n}\n\n// Reset resets the timer to the fake clock's \"now\" + d. It returns true if the timer has not yet\n// fired, or false otherwise.\nfunc (f *fakeTimer) Reset(d time.Duration) bool {\n\tf.fakeClock.lock.Lock()\n\tdefer f.fakeClock.lock.Unlock()\n\n\tactive := !f.waiter.fired\n\n\tf.waiter.fired = false\n\tf.waiter.targetTime = f.fakeClock.time.Add(d)\n\n\tvar isWaiting bool\n\tfor i := range f.fakeClock.waiters {\n\t\tw := f.fakeClock.waiters[i]\n\t\tif w == &f.waiter {\n\t\t\tisWaiting = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !isWaiting {\n\t\tf.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)\n\t}\n\n\treturn active\n}\n\ntype fakeTicker struct {\n\tc <-chan time.Time\n}\n\nfunc (t *fakeTicker) C() <-chan time.Time {\n\treturn t.c\n}\n\nfunc (t *fakeTicker) Stop() {\n}\n"
  },
  {
    "path": "internal/clock/testing/simple_interval_clock.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage testing\n\nimport (\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/clock\"\n)\n\nvar (\n\t_ = clock.PassiveClock(&SimpleIntervalClock{})\n)\n\n// SimpleIntervalClock implements clock.PassiveClock, but each invocation of Now steps the clock forward the specified duration\ntype SimpleIntervalClock struct {\n\tTime     time.Time\n\tDuration time.Duration\n}\n\n// Now returns i's time.\nfunc (i *SimpleIntervalClock) Now() time.Time {\n\ti.Time = i.Time.Add(i.Duration)\n\treturn i.Time\n}\n\n// Since returns time since the time in i.\nfunc (i *SimpleIntervalClock) Since(ts time.Time) time.Duration {\n\treturn i.Time.Sub(ts)\n}\n"
  },
  {
    "path": "internal/dbg/dbg.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2013 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package dbg provides some helper code for call traces.\npackage dbg\n\nimport (\n\t\"runtime\"\n)\n\n// Stacks is a wrapper for runtime.Stack that attempts to recover the data for\n// all goroutines or the calling one.\nfunc Stacks(all bool) []byte {\n\t// We don't know how big the traces are, so grow a few times if they don't fit. Start large, though.\n\tn := 10000\n\tif all {\n\t\tn = 100000\n\t}\n\tvar trace []byte\n\tfor i := 0; i < 5; i++ {\n\t\ttrace = make([]byte, n)\n\t\tnbytes := runtime.Stack(trace, all)\n\t\tif nbytes < len(trace) {\n\t\t\treturn trace[:nbytes]\n\t\t}\n\t\tn *= 2\n\t}\n\treturn trace\n}\n"
  },
  {
    "path": "internal/serialize/keyvalues.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage serialize\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n)\n\ntype textWriter interface {\n\tWriteText(*bytes.Buffer)\n}\n\n// WithValues implements LogSink.WithValues. The old key/value pairs are\n// assumed to be well-formed, the new ones are checked and padded if\n// necessary. It returns a new slice.\nfunc WithValues(oldKV, newKV []interface{}) []interface{} {\n\tif len(newKV) == 0 {\n\t\treturn oldKV\n\t}\n\tnewLen := len(oldKV) + len(newKV)\n\thasMissingValue := newLen%2 != 0\n\tif hasMissingValue {\n\t\tnewLen++\n\t}\n\t// The new LogSink must have its own slice.\n\tkv := make([]interface{}, 0, newLen)\n\tkv = append(kv, oldKV...)\n\tkv = append(kv, newKV...)\n\tif hasMissingValue {\n\t\tkv = append(kv, missingValue)\n\t}\n\treturn kv\n}\n\ntype Formatter struct {\n\tAnyToStringHook AnyToStringFunc\n}\n\ntype AnyToStringFunc func(v interface{}) string\n\nconst missingValue = \"(MISSING)\"\n\nfunc FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {\n\tFormatter{}.FormatKVs(b, kvs...)\n}\n\n// FormatKVs formats all key/value pairs such that the output contains no\n// duplicates (\"last one wins\").\nfunc (f Formatter) FormatKVs(b *bytes.Buffer, kvs ...[]interface{}) {\n\t// De-duplication is done by optimistically formatting all key value\n\t// pairs and then cutting out the output of those key/value pairs which\n\t// got overwritten later.\n\t//\n\t// In the common case of no duplicates, the only overhead is tracking\n\t// previous keys. This uses a slice with a simple linear search because\n\t// the number of entries is typically so low that allocating a map or\n\t// keeping a sorted slice with binary search aren't justified.\n\t//\n\t// Using a fixed size here makes the Go compiler use the stack as\n\t// initial backing store for the slice, which is crucial for\n\t// performance.\n\texisting := make([]obsoleteKV, 0, 32)\n\tobsolete := make([]interval, 0, 32) // Sorted by start index.\n\tfor _, keysAndValues := range kvs {\n\t\tfor i := 0; i < len(keysAndValues); i += 2 {\n\t\t\tvar v interface{}\n\t\t\tk := keysAndValues[i]\n\t\t\tif i+1 < len(keysAndValues) {\n\t\t\t\tv = keysAndValues[i+1]\n\t\t\t} else {\n\t\t\t\tv = missingValue\n\t\t\t}\n\t\t\tvar e obsoleteKV\n\t\t\te.start = b.Len()\n\t\t\te.key = f.KVFormat(b, k, v)\n\t\t\te.end = b.Len()\n\t\t\ti := findObsoleteEntry(existing, e.key)\n\t\t\tif i >= 0 {\n\t\t\t\tdata := b.Bytes()\n\t\t\t\tif bytes.Compare(data[existing[i].start:existing[i].end], data[e.start:e.end]) == 0 {\n\t\t\t\t\t// The new entry gets obsoleted because it's identical.\n\t\t\t\t\t// This has the advantage that key/value pairs from\n\t\t\t\t\t// a WithValues call always come first, even if the same\n\t\t\t\t\t// pair gets added again later. This makes different log\n\t\t\t\t\t// entries more consistent.\n\t\t\t\t\t//\n\t\t\t\t\t// The new entry has a higher start index and thus can be appended.\n\t\t\t\t\tobsolete = append(obsolete, e.interval)\n\t\t\t\t} else {\n\t\t\t\t\t// The old entry gets obsoleted because it's value is different.\n\t\t\t\t\t//\n\t\t\t\t\t// Sort order is not guaranteed, we have to insert at the right place.\n\t\t\t\t\tindex, _ := slices.BinarySearchFunc(obsolete, existing[i].interval, func(a, b interval) int { return a.start - b.start })\n\t\t\t\t\tobsolete = slices.Insert(obsolete, index, existing[i].interval)\n\t\t\t\t\texisting[i].interval = e.interval\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Instead of appending at the end and doing a\n\t\t\t\t// linear search in findEntry, we could keep\n\t\t\t\t// the slice sorted by key and do a binary search.\n\t\t\t\t//\n\t\t\t\t// Above:\n\t\t\t\t//    i, ok := slices.BinarySearchFunc(existing, e, func(a, b entry) int { return strings.Compare(a.key, b.key) })\n\t\t\t\t// Here:\n\t\t\t\t//    existing = slices.Insert(existing, i, e)\n\t\t\t\t//\n\t\t\t\t// But that adds a dependency on the slices package\n\t\t\t\t// and made performance slightly worse, presumably\n\t\t\t\t// because the cost of shifting entries around\n\t\t\t\t// did not pay of with faster lookups.\n\t\t\t\texisting = append(existing, e)\n\t\t\t}\n\t\t}\n\t}\n\n\t// If we need to remove some obsolete key/value pairs then move the memory.\n\tif len(obsolete) > 0 {\n\t\t// Potentially the next remaining output (might itself be obsolete).\n\t\tfrom := obsolete[0].end\n\t\t// Next obsolete entry.\n\t\tnextObsolete := 1\n\t\t// This is the source buffer, before truncation.\n\t\tall := b.Bytes()\n\t\tb.Truncate(obsolete[0].start)\n\n\t\tfor nextObsolete < len(obsolete) {\n\t\t\tif from == obsolete[nextObsolete].start {\n\t\t\t\t// Skip also the next obsolete key/value.\n\t\t\t\tfrom = obsolete[nextObsolete].end\n\t\t\t\tnextObsolete++\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Preserve some output. Write uses copy, which\n\t\t\t// explicitly allows source and destination to overlap.\n\t\t\t// That could happen here.\n\t\t\tvalid := all[from:obsolete[nextObsolete].start]\n\t\t\tb.Write(valid)\n\t\t\tfrom = obsolete[nextObsolete].end\n\t\t\tnextObsolete++\n\t\t}\n\t\t// Copy end of buffer.\n\t\tvalid := all[from:]\n\t\tb.Write(valid)\n\t}\n}\n\ntype obsoleteKV struct {\n\tkey string\n\tinterval\n}\n\n// interval includes the start and excludes the end.\ntype interval struct {\n\tstart int\n\tend   int\n}\n\nfunc findObsoleteEntry(entries []obsoleteKV, key string) int {\n\tfor i, entry := range entries {\n\t\tif entry.key == key {\n\t\t\treturn i\n\t\t}\n\t}\n\treturn -1\n}\n\n// formatAny is the fallback formatter for a value. It supports a hook (for\n// example, for YAML encoding) and itself uses JSON encoding.\nfunc (f Formatter) formatAny(b *bytes.Buffer, v interface{}) {\n\tif f.AnyToStringHook != nil {\n\t\tstr := f.AnyToStringHook(v)\n\t\tif strings.Contains(str, \"\\n\") {\n\t\t\t// If it's multi-line, then pass it through writeStringValue to get start/end delimiters,\n\t\t\t// which separates it better from any following key/value pair.\n\t\t\twriteStringValue(b, str)\n\t\t\treturn\n\t\t}\n\t\t// Otherwise put it directly after the separator, on the same lime,\n\t\t// The assumption is that the hook returns something where start/end are obvious.\n\t\tb.WriteRune('=')\n\t\tb.WriteString(str)\n\t\treturn\n\t}\n\tb.WriteRune('=')\n\tformatAsJSON(b, v)\n}\n\nfunc formatAsJSON(b *bytes.Buffer, v interface{}) {\n\tencoder := json.NewEncoder(b)\n\tl := b.Len()\n\tif err := encoder.Encode(v); err != nil {\n\t\t// This shouldn't happen. We discard whatever the encoder\n\t\t// wrote and instead dump an error string.\n\t\tb.Truncate(l)\n\t\tb.WriteString(fmt.Sprintf(`\"<internal error: %v>\"`, err))\n\t\treturn\n\t}\n\t// Remove trailing newline.\n\tb.Truncate(b.Len() - 1)\n}\n\n// StringerToString converts a Stringer to a string,\n// handling panics if they occur.\nfunc StringerToString(s fmt.Stringer) (ret string) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tret = fmt.Sprintf(\"<panic: %s>\", err)\n\t\t}\n\t}()\n\tret = s.String()\n\treturn\n}\n\n// MarshalerToValue invokes a marshaler and catches\n// panics.\nfunc MarshalerToValue(m logr.Marshaler) (ret interface{}) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tret = fmt.Sprintf(\"<panic: %s>\", err)\n\t\t}\n\t}()\n\tret = m.MarshalLog()\n\treturn\n}\n\n// ErrorToString converts an error to a string,\n// handling panics if they occur.\nfunc ErrorToString(err error) (ret string) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tret = fmt.Sprintf(\"<panic: %s>\", err)\n\t\t}\n\t}()\n\tret = err.Error()\n\treturn\n}\n\nfunc writeTextWriterValue(b *bytes.Buffer, v textWriter) {\n\tb.WriteByte('=')\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tfmt.Fprintf(b, `\"<panic: %s>\"`, err)\n\t\t}\n\t}()\n\tv.WriteText(b)\n}\n\nfunc writeStringValue(b *bytes.Buffer, v string) {\n\tdata := []byte(v)\n\tindex := bytes.IndexByte(data, '\\n')\n\tif index == -1 {\n\t\tb.WriteByte('=')\n\t\t// Simple string, quote quotation marks and non-printable characters.\n\t\tb.WriteString(strconv.Quote(v))\n\t\treturn\n\t}\n\n\t// Complex multi-line string, show as-is with indention like this:\n\t// I... \"hello world\" key=<\n\t// <tab>line 1\n\t// <tab>line 2\n\t//  >\n\t//\n\t// Tabs indent the lines of the value while the end of string delimiter\n\t// is indented with a space. That has two purposes:\n\t// - visual difference between the two for a human reader because indention\n\t//   will be different\n\t// - no ambiguity when some value line starts with the end delimiter\n\t//\n\t// One downside is that the output cannot distinguish between strings that\n\t// end with a line break and those that don't because the end delimiter\n\t// will always be on the next line.\n\tb.WriteString(\"=<\\n\")\n\tfor index != -1 {\n\t\tb.WriteByte('\\t')\n\t\tb.Write(data[0 : index+1])\n\t\tdata = data[index+1:]\n\t\tindex = bytes.IndexByte(data, '\\n')\n\t}\n\tif len(data) == 0 {\n\t\t// String ended with line break, don't add another.\n\t\tb.WriteString(\" >\")\n\t} else {\n\t\t// No line break at end of last line, write rest of string and\n\t\t// add one.\n\t\tb.WriteByte('\\t')\n\t\tb.Write(data)\n\t\tb.WriteString(\"\\n >\")\n\t}\n}\n"
  },
  {
    "path": "internal/serialize/keyvalues_no_slog.go",
    "content": "//go:build !go1.21\n// +build !go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage serialize\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// KVFormat serializes one key/value pair into the provided buffer.\n// A space gets inserted before the pair.\nfunc (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {\n\t// This is the version without slog support. Must be kept in sync with\n\t// the version in keyvalues_slog.go.\n\n\tb.WriteByte(' ')\n\t// Keys are assumed to be well-formed according to\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments\n\t// for the sake of performance. Keys with spaces,\n\t// special characters, etc. will break parsing.\n\tvar key string\n\tif sK, ok := k.(string); ok {\n\t\t// Avoid one allocation when the key is a string, which\n\t\t// normally it should be.\n\t\tkey = sK\n\t} else {\n\t\tkey = fmt.Sprintf(\"%s\", k)\n\t}\n\tb.WriteString(key)\n\n\t// The type checks are sorted so that more frequently used ones\n\t// come first because that is then faster in the common\n\t// cases. In Kubernetes, ObjectRef (a Stringer) is more common\n\t// than plain strings\n\t// (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).\n\tswitch v := v.(type) {\n\tcase textWriter:\n\t\twriteTextWriterValue(b, v)\n\tcase fmt.Stringer:\n\t\twriteStringValue(b, StringerToString(v))\n\tcase string:\n\t\twriteStringValue(b, v)\n\tcase error:\n\t\twriteStringValue(b, ErrorToString(v))\n\tcase logr.Marshaler:\n\t\tvalue := MarshalerToValue(v)\n\t\t// A marshaler that returns a string is useful for\n\t\t// delayed formatting of complex values. We treat this\n\t\t// case like a normal string. This is useful for\n\t\t// multi-line support.\n\t\t//\n\t\t// We could do this by recursively formatting a value,\n\t\t// but that comes with the risk of infinite recursion\n\t\t// if a marshaler returns itself. Instead we call it\n\t\t// only once and rely on it returning the intended\n\t\t// value directly.\n\t\tswitch value := value.(type) {\n\t\tcase string:\n\t\t\twriteStringValue(b, value)\n\t\tdefault:\n\t\t\tf.formatAny(b, value)\n\t\t}\n\tcase []byte:\n\t\t// In https://github.com/kubernetes/klog/pull/237 it was decided\n\t\t// to format byte slices with \"%+q\". The advantages of that are:\n\t\t// - readable output if the bytes happen to be printable\n\t\t// - non-printable bytes get represented as unicode escape\n\t\t//   sequences (\\uxxxx)\n\t\t//\n\t\t// The downsides are that we cannot use the faster\n\t\t// strconv.Quote here and that multi-line output is not\n\t\t// supported. If developers know that a byte array is\n\t\t// printable and they want multi-line output, they can\n\t\t// convert the value to string before logging it.\n\t\tb.WriteByte('=')\n\t\tb.WriteString(fmt.Sprintf(\"%+q\", v))\n\tdefault:\n\t\tf.formatAny(b, v)\n\t}\n\n\treturn key\n}\n"
  },
  {
    "path": "internal/serialize/keyvalues_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage serialize\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strconv\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// KVFormat serializes one key/value pair into the provided buffer.\n// A space gets inserted before the pair. It returns the key.\nfunc (f Formatter) KVFormat(b *bytes.Buffer, k, v interface{}) string {\n\t// This is the version without slog support. Must be kept in sync with\n\t// the version in keyvalues_slog.go.\n\n\tb.WriteByte(' ')\n\t// Keys are assumed to be well-formed according to\n\t// https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/migration-to-structured-logging.md#name-arguments\n\t// for the sake of performance. Keys with spaces,\n\t// special characters, etc. will break parsing.\n\tvar key string\n\tif sK, ok := k.(string); ok {\n\t\t// Avoid one allocation when the key is a string, which\n\t\t// normally it should be.\n\t\tkey = sK\n\t} else {\n\t\tkey = fmt.Sprintf(\"%s\", k)\n\t}\n\tb.WriteString(key)\n\n\t// The type checks are sorted so that more frequently used ones\n\t// come first because that is then faster in the common\n\t// cases. In Kubernetes, ObjectRef (a Stringer) is more common\n\t// than plain strings\n\t// (https://github.com/kubernetes/kubernetes/pull/106594#issuecomment-975526235).\n\t//\n\t// slog.LogValuer does not need to be handled here because the handler will\n\t// already have resolved such special values to the final value for logging.\n\tswitch v := v.(type) {\n\tcase textWriter:\n\t\twriteTextWriterValue(b, v)\n\tcase slog.Value:\n\t\t// This must come before fmt.Stringer because slog.Value implements\n\t\t// fmt.Stringer, but does not produce the output that we want.\n\t\tb.WriteByte('=')\n\t\tgenerateJSON(b, v)\n\tcase fmt.Stringer:\n\t\twriteStringValue(b, StringerToString(v))\n\tcase string:\n\t\twriteStringValue(b, v)\n\tcase error:\n\t\twriteStringValue(b, ErrorToString(v))\n\tcase logr.Marshaler:\n\t\tvalue := MarshalerToValue(v)\n\t\t// A marshaler that returns a string is useful for\n\t\t// delayed formatting of complex values. We treat this\n\t\t// case like a normal string. This is useful for\n\t\t// multi-line support.\n\t\t//\n\t\t// We could do this by recursively formatting a value,\n\t\t// but that comes with the risk of infinite recursion\n\t\t// if a marshaler returns itself. Instead we call it\n\t\t// only once and rely on it returning the intended\n\t\t// value directly.\n\t\tswitch value := value.(type) {\n\t\tcase string:\n\t\t\twriteStringValue(b, value)\n\t\tdefault:\n\t\t\tf.formatAny(b, value)\n\t\t}\n\tcase slog.LogValuer:\n\t\tvalue := slog.AnyValue(v).Resolve()\n\t\tif value.Kind() == slog.KindString {\n\t\t\twriteStringValue(b, value.String())\n\t\t} else {\n\t\t\tb.WriteByte('=')\n\t\t\tgenerateJSON(b, value)\n\t\t}\n\tcase []byte:\n\t\t// In https://github.com/kubernetes/klog/pull/237 it was decided\n\t\t// to format byte slices with \"%+q\". The advantages of that are:\n\t\t// - readable output if the bytes happen to be printable\n\t\t// - non-printable bytes get represented as unicode escape\n\t\t//   sequences (\\uxxxx)\n\t\t//\n\t\t// The downsides are that we cannot use the faster\n\t\t// strconv.Quote here and that multi-line output is not\n\t\t// supported. If developers know that a byte array is\n\t\t// printable and they want multi-line output, they can\n\t\t// convert the value to string before logging it.\n\t\tb.WriteByte('=')\n\t\tb.WriteString(fmt.Sprintf(\"%+q\", v))\n\tdefault:\n\t\tf.formatAny(b, v)\n\t}\n\n\treturn key\n}\n\n// generateJSON has the same preference for plain strings as KVFormat.\n// In contrast to KVFormat it always produces valid JSON with no line breaks.\nfunc generateJSON(b *bytes.Buffer, v interface{}) {\n\tswitch v := v.(type) {\n\tcase slog.Value:\n\t\tswitch v.Kind() {\n\t\tcase slog.KindGroup:\n\t\t\t// Format as a JSON group. We must not involve f.AnyToStringHook (if there is any),\n\t\t\t// because there is no guarantee that it produces valid JSON.\n\t\t\tb.WriteByte('{')\n\t\t\tfor i, attr := range v.Group() {\n\t\t\t\tif i > 0 {\n\t\t\t\t\tb.WriteByte(',')\n\t\t\t\t}\n\t\t\t\tb.WriteString(strconv.Quote(attr.Key))\n\t\t\t\tb.WriteByte(':')\n\t\t\t\tgenerateJSON(b, attr.Value)\n\t\t\t}\n\t\t\tb.WriteByte('}')\n\t\tcase slog.KindLogValuer:\n\t\t\tgenerateJSON(b, v.Resolve())\n\t\tdefault:\n\t\t\t// Peel off the slog.Value wrapper and format the actual value.\n\t\t\tgenerateJSON(b, v.Any())\n\t\t}\n\tcase fmt.Stringer:\n\t\tb.WriteString(strconv.Quote(StringerToString(v)))\n\tcase logr.Marshaler:\n\t\tgenerateJSON(b, MarshalerToValue(v))\n\tcase slog.LogValuer:\n\t\tgenerateJSON(b, slog.AnyValue(v).Resolve().Any())\n\tcase string:\n\t\tb.WriteString(strconv.Quote(v))\n\tcase error:\n\t\tb.WriteString(strconv.Quote(v.Error()))\n\tdefault:\n\t\tformatAsJSON(b, v)\n\t}\n}\n"
  },
  {
    "path": "internal/serialize/keyvalues_test.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage serialize_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/test\"\n)\n\n// point conforms to fmt.Stringer interface as it implements the String() method\ntype point struct {\n\tx int\n\ty int\n}\n\n// we now have a value receiver\nfunc (p point) String() string {\n\treturn fmt.Sprintf(\"x=%d, y=%d\", p.x, p.y)\n}\n\ntype dummyStruct struct {\n\tkey   string\n\tvalue string\n}\n\nfunc (d *dummyStruct) MarshalLog() interface{} {\n\treturn map[string]string{\n\t\t\"key-data\":   d.key,\n\t\t\"value-data\": d.value,\n\t}\n}\n\ntype dummyStructWithStringMarshal struct {\n\tkey   string\n\tvalue string\n}\n\nfunc (d *dummyStructWithStringMarshal) MarshalLog() interface{} {\n\treturn fmt.Sprintf(\"%s=%s\", d.key, d.value)\n}\n\n// Test that kvListFormat works as advertised.\nfunc TestKvListFormat(t *testing.T) {\n\tvar emptyPoint *point\n\tvar testKVList = []struct {\n\t\tkeysValues []interface{}\n\t\twant       string\n\t}{\n\t\t{\n\t\t\tkeysValues: []interface{}{\"data\", &dummyStruct{key: \"test\", value: \"info\"}},\n\t\t\twant:       ` data={\"key-data\":\"test\",\"value-data\":\"info\"}`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"data\", &dummyStructWithStringMarshal{key: \"test\", value: \"info\"}},\n\t\t\twant:       ` data=\"test=info\"`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\"},\n\t\t\twant:       \" pod=\\\"kubedns\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"update\", true},\n\t\t\twant:       \" pod=\\\"kubedns\\\" update=true\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"spec\", struct {\n\t\t\t\tX int\n\t\t\t\tY string\n\t\t\t\tN time.Time\n\t\t\t}{X: 76, Y: \"strval\", N: time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.UTC)}},\n\t\t\twant: ` pod=\"kubedns\" spec={\"X\":76,\"Y\":\"strval\",\"N\":\"2006-01-02T15:04:05.06789Z\"}`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"values\", []int{8, 6, 7, 5, 3, 0, 9}},\n\t\t\twant:       \" pod=\\\"kubedns\\\" values=[8,6,7,5,3,0,9]\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"values\", []string{\"deployment\", \"svc\", \"configmap\"}},\n\t\t\twant:       ` pod=\"kubedns\" values=[\"deployment\",\"svc\",\"configmap\"]`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"bytes\", []byte(\"test case for byte array\")},\n\t\t\twant:       \" pod=\\\"kubedns\\\" bytes=\\\"test case for byte array\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"bytes\", []byte(\"��=� ⌘\")},\n\t\t\twant:       \" pod=\\\"kubedns\\\" bytes=\\\"\\\\ufffd\\\\ufffd=\\\\ufffd \\\\u2318\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"multiLineString\", `Hello world!\n\tStarts with tab.\n  Starts with spaces.\nNo whitespace.`,\n\t\t\t\t\"pod\", \"kubedns\",\n\t\t\t},\n\t\t\twant: ` multiLineString=<\n\tHello world!\n\t\tStarts with tab.\n\t  Starts with spaces.\n\tNo whitespace.\n > pod=\"kubedns\"`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\", \"maps\", map[string]int{\"three\": 4}},\n\t\t\twant:       ` pod=\"kubedns\" maps={\"three\":4}`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KRef(\"kube-system\", \"kubedns\"), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"kube-system/kubedns\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KRef(\"\", \"kubedns\"), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"kubedns\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KObj(test.KMetadataMock{Name: \"test-name\", NS: \"test-ns\"}), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"test-ns/test-name\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KObj(test.KMetadataMock{Name: \"test-name\", NS: \"\"}), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"test-name\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KObj(nil), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KObj((*test.PtrKMetadataMock)(nil)), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pod\", klog.KObj((*test.KMetadataMock)(nil)), \"status\", \"ready\"},\n\t\t\twant:       \" pod=\\\"\\\" status=\\\"ready\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"pods\", klog.KObjs([]test.KMetadataMock{\n\t\t\t\t{\n\t\t\t\t\tName: \"kube-dns\",\n\t\t\t\t\tNS:   \"kube-system\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"mi-conf\",\n\t\t\t\t},\n\t\t\t})},\n\t\t\twant: ` pods=[{\"name\":\"kube-dns\",\"namespace\":\"kube-system\"},{\"name\":\"mi-conf\"}]`,\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{\"point-1\", point{100, 200}, \"point-2\", emptyPoint},\n\t\t\twant:       \" point-1=\\\"x=100, y=200\\\" point-2=\\\"<panic: value method k8s.io/klog/v2/internal/serialize_test.point.String called using nil *point pointer>\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{struct{ key string }{key: \"k1\"}, \"value\"},\n\t\t\twant:       \" {k1}=\\\"value\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{1, \"test\"},\n\t\t\twant:       \" %!s(int=1)=\\\"test\\\"\",\n\t\t},\n\t\t{\n\t\t\tkeysValues: []interface{}{map[string]string{\"k\": \"key\"}, \"value\"},\n\t\t\twant:       \" map[k:key]=\\\"value\\\"\",\n\t\t},\n\t}\n\n\tfor _, d := range testKVList {\n\t\tb := &bytes.Buffer{}\n\t\tserialize.FormatKVs(b, d.keysValues)\n\t\tif b.String() != d.want {\n\t\t\tt.Errorf(\"KVListFormat error:\\n got:\\n\\t%s\\nwant:\\t%s\", b.String(), d.want)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/severity/severity.go",
    "content": "// Copyright 2013 Google Inc. All Rights Reserved.\n// Copyright 2022 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package severity provides definitions for klog severity (info, warning, ...)\npackage severity\n\nimport (\n\t\"strings\"\n)\n\n// severity identifies the sort of log: info, warning etc. The binding to flag.Value\n// is handled in klog.go\ntype Severity int32 // sync/atomic int32\n\n// These constants identify the log levels in order of increasing severity.\n// A message written to a high-severity log file is also written to each\n// lower-severity log file.\nconst (\n\tInfoLog Severity = iota\n\tWarningLog\n\tErrorLog\n\tFatalLog\n\tNumSeverity = 4\n)\n\n// Char contains one shortcut letter per severity level.\nconst Char = \"IWEF\"\n\n// Name contains one name per severity level.\nvar Name = []string{\n\tInfoLog:    \"INFO\",\n\tWarningLog: \"WARNING\",\n\tErrorLog:   \"ERROR\",\n\tFatalLog:   \"FATAL\",\n}\n\n// ByName looks up a severity level by name.\nfunc ByName(s string) (Severity, bool) {\n\ts = strings.ToUpper(s)\n\tfor i, name := range Name {\n\t\tif name == s {\n\t\t\treturn Severity(i), true\n\t\t}\n\t}\n\treturn 0, false\n}\n"
  },
  {
    "path": "internal/sloghandler/sloghandler_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage sloghandler\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/severity\"\n)\n\nfunc Handle(_ context.Context, record slog.Record, groups string, printWithInfos func(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{})) error {\n\tnow := record.Time\n\tif now.IsZero() {\n\t\t// This format doesn't support printing entries without a time.\n\t\tnow = time.Now()\n\t}\n\n\t// slog has numeric severity levels, with 0 as default \"info\", negative for debugging, and\n\t// positive with some pre-defined levels for more important. Those ranges get mapped to\n\t// the corresponding klog levels where possible, with \"info\" the default that is used\n\t// also for negative debug levels.\n\tlevel := record.Level\n\ts := severity.InfoLog\n\tswitch {\n\tcase level >= slog.LevelError:\n\t\ts = severity.ErrorLog\n\tcase level >= slog.LevelWarn:\n\t\ts = severity.WarningLog\n\t}\n\n\tvar file string\n\tvar line int\n\tif record.PC != 0 {\n\t\t// Same as https://cs.opensource.google/go/x/exp/+/642cacee:slog/record.go;drc=642cacee5cc05231f45555a333d07f1005ffc287;l=70\n\t\tfs := runtime.CallersFrames([]uintptr{record.PC})\n\t\tf, _ := fs.Next()\n\t\tif f.File != \"\" {\n\t\t\tfile = f.File\n\t\t\tif slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\t\t\tfile = file[slash+1:]\n\t\t\t}\n\t\t\tline = f.Line\n\t\t}\n\t} else {\n\t\tfile = \"???\"\n\t\tline = 1\n\t}\n\n\tkvList := make([]interface{}, 0, 2*record.NumAttrs())\n\trecord.Attrs(func(attr slog.Attr) bool {\n\t\tkvList = appendAttr(groups, kvList, attr)\n\t\treturn true\n\t})\n\n\tprintWithInfos(file, line, now, nil, s, record.Message, kvList)\n\treturn nil\n}\n\nfunc Attrs2KVList(groups string, attrs []slog.Attr) []interface{} {\n\tkvList := make([]interface{}, 0, 2*len(attrs))\n\tfor _, attr := range attrs {\n\t\tkvList = appendAttr(groups, kvList, attr)\n\t}\n\treturn kvList\n}\n\nfunc appendAttr(groups string, kvList []interface{}, attr slog.Attr) []interface{} {\n\tvar key string\n\tif groups != \"\" {\n\t\tkey = groups + \".\" + attr.Key\n\t} else {\n\t\tkey = attr.Key\n\t}\n\treturn append(kvList, key, attr.Value)\n}\n"
  },
  {
    "path": "internal/test/mock.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package test contains common code for klog tests.\npackage test\n\ntype KMetadataMock struct {\n\tName, NS string\n}\n\nfunc (m KMetadataMock) GetName() string {\n\treturn m.Name\n}\nfunc (m KMetadataMock) GetNamespace() string {\n\treturn m.NS\n}\n\ntype PtrKMetadataMock struct {\n\tName, NS string\n}\n\nfunc (m *PtrKMetadataMock) GetName() string {\n\treturn m.Name\n}\nfunc (m *PtrKMetadataMock) GetNamespace() string {\n\treturn m.NS\n}\n"
  },
  {
    "path": "internal/test/require/require.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage require\n\nimport \"testing\"\n\nfunc NoError(tb testing.TB, err error) {\n\tif err != nil {\n\t\ttb.Helper()\n\t\ttb.Fatalf(\"Unexpected error: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "internal/verbosity/helper_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage verbosity\n\nfunc enabledInHelper(vs *VState, l Level) bool {\n\treturn vs.Enabled(l, 0)\n}\n"
  },
  {
    "path": "internal/verbosity/verbosity.go",
    "content": "/*\nCopyright 2013 Google Inc. All Rights Reserved.\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage verbosity\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\n// New returns a struct that implements -v and -vmodule support. Changing and\n// checking these settings is thread-safe, with all concurrency issues handled\n// internally.\nfunc New() *VState {\n\tvs := new(VState)\n\n\t// The two fields must have a pointer to the overal struct for their\n\t// implementation of Set.\n\tvs.vmodule.vs = vs\n\tvs.verbosity.vs = vs\n\n\treturn vs\n}\n\n// Value is an extension that makes it possible to use the values in pflag.\ntype Value interface {\n\tflag.Value\n\tType() string\n}\n\nfunc (vs *VState) V() Value {\n\treturn &vs.verbosity\n}\n\nfunc (vs *VState) VModule() Value {\n\treturn &vs.vmodule\n}\n\n// VState contains settings and state. Some of its fields can be accessed\n// through atomic read/writes, in other cases a mutex must be held.\ntype VState struct {\n\tmu sync.Mutex\n\n\t// These flags are modified only under lock, although verbosity may be fetched\n\t// safely using atomic.LoadInt32.\n\tvmodule   moduleSpec // The state of the -vmodule flag.\n\tverbosity levelSpec  // V logging level, the value of the -v flag/\n\n\t// pcs is used in V to avoid an allocation when computing the caller's PC.\n\tpcs [1]uintptr\n\t// vmap is a cache of the V Level for each V() call site, identified by PC.\n\t// It is wiped whenever the vmodule flag changes state.\n\tvmap map[uintptr]Level\n\t// filterLength stores the length of the vmodule filter chain. If greater\n\t// than zero, it means vmodule is enabled. It may be read safely\n\t// using sync.LoadInt32, but is only modified under mu.\n\tfilterLength int32\n}\n\n// Level must be an int32 to support atomic read/writes.\ntype Level int32\n\ntype levelSpec struct {\n\tvs *VState\n\tl  Level\n}\n\n// get returns the value of the level.\nfunc (l *levelSpec) get() Level {\n\treturn Level(atomic.LoadInt32((*int32)(&l.l)))\n}\n\n// set sets the value of the level.\nfunc (l *levelSpec) set(val Level) {\n\tatomic.StoreInt32((*int32)(&l.l), int32(val))\n}\n\n// String is part of the flag.Value interface.\nfunc (l *levelSpec) String() string {\n\treturn strconv.FormatInt(int64(l.l), 10)\n}\n\n// Get is part of the flag.Getter interface. It returns the\n// verbosity level as int32.\nfunc (l *levelSpec) Get() interface{} {\n\treturn int32(l.l)\n}\n\n// Type is part of pflag.Value.\nfunc (l *levelSpec) Type() string {\n\treturn \"Level\"\n}\n\n// Set is part of the flag.Value interface.\nfunc (l *levelSpec) Set(value string) error {\n\tv, err := strconv.ParseInt(value, 10, 32)\n\tif err != nil {\n\t\treturn err\n\t}\n\tl.vs.mu.Lock()\n\tdefer l.vs.mu.Unlock()\n\tl.vs.set(Level(v), l.vs.vmodule.filter, false)\n\treturn nil\n}\n\n// moduleSpec represents the setting of the -vmodule flag.\ntype moduleSpec struct {\n\tvs     *VState\n\tfilter []modulePat\n}\n\n// modulePat contains a filter for the -vmodule flag.\n// It holds a verbosity level and a file pattern to match.\ntype modulePat struct {\n\tpattern string\n\tliteral bool // The pattern is a literal string\n\tlevel   Level\n}\n\n// match reports whether the file matches the pattern. It uses a string\n// comparison if the pattern contains no metacharacters.\nfunc (m *modulePat) match(file string) bool {\n\tif m.literal {\n\t\treturn file == m.pattern\n\t}\n\tmatch, _ := filepath.Match(m.pattern, file)\n\treturn match\n}\n\nfunc (m *moduleSpec) String() string {\n\t// Lock because the type is not atomic. TODO: clean this up.\n\t// Empty instances don't have and don't need a lock (can\n\t// happen when flag uses introspection).\n\tif m.vs != nil {\n\t\tm.vs.mu.Lock()\n\t\tdefer m.vs.mu.Unlock()\n\t}\n\tvar b bytes.Buffer\n\tfor i, f := range m.filter {\n\t\tif i > 0 {\n\t\t\tb.WriteRune(',')\n\t\t}\n\t\tfmt.Fprintf(&b, \"%s=%d\", f.pattern, f.level)\n\t}\n\treturn b.String()\n}\n\n// Get is part of the (Go 1.2)  flag.Getter interface. It always returns nil for this flag type since the\n// struct is not exported.\nfunc (m *moduleSpec) Get() interface{} {\n\treturn nil\n}\n\n// Type is part of pflag.Value\nfunc (m *moduleSpec) Type() string {\n\treturn \"pattern=N,...\"\n}\n\nvar errVmoduleSyntax = errors.New(\"syntax error: expect comma-separated list of filename=N\")\n\n// Set will sets module value\n// Syntax: -vmodule=recordio=2,file=1,gfs*=3\nfunc (m *moduleSpec) Set(value string) error {\n\tvar filter []modulePat\n\tfor _, pat := range strings.Split(value, \",\") {\n\t\tif len(pat) == 0 {\n\t\t\t// Empty strings such as from a trailing comma can be ignored.\n\t\t\tcontinue\n\t\t}\n\t\tpatLev := strings.Split(pat, \"=\")\n\t\tif len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {\n\t\t\treturn errVmoduleSyntax\n\t\t}\n\t\tpattern := patLev[0]\n\t\tv, err := strconv.ParseInt(patLev[1], 10, 32)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"syntax error: expect comma-separated list of filename=N\")\n\t\t}\n\t\tif v < 0 {\n\t\t\treturn errors.New(\"negative value for vmodule level\")\n\t\t}\n\t\tif v == 0 {\n\t\t\tcontinue // Ignore. It's harmless but no point in paying the overhead.\n\t\t}\n\t\t// TODO: check syntax of filter?\n\t\tfilter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)})\n\t}\n\tm.vs.mu.Lock()\n\tdefer m.vs.mu.Unlock()\n\tm.vs.set(m.vs.verbosity.l, filter, true)\n\treturn nil\n}\n\n// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters\n// that require filepath.Match to be called to match the pattern.\nfunc isLiteral(pattern string) bool {\n\treturn !strings.ContainsAny(pattern, `\\*?[]`)\n}\n\n// set sets a consistent state for V logging.\n// The mutex must be held.\nfunc (vs *VState) set(l Level, filter []modulePat, setFilter bool) {\n\t// Turn verbosity off so V will not fire while we are in transition.\n\tvs.verbosity.set(0)\n\t// Ditto for filter length.\n\tatomic.StoreInt32(&vs.filterLength, 0)\n\n\t// Set the new filters and wipe the pc->Level map if the filter has changed.\n\tif setFilter {\n\t\tvs.vmodule.filter = filter\n\t\tvs.vmap = make(map[uintptr]Level)\n\t}\n\n\t// Things are consistent now, so enable filtering and verbosity.\n\t// They are enabled in order opposite to that in V.\n\tatomic.StoreInt32(&vs.filterLength, int32(len(filter)))\n\tvs.verbosity.set(l)\n}\n\n// Enabled checks whether logging is enabled at the given level. This must be\n// called with depth=0 when the caller of enabled will do the logging and\n// higher values when more stack levels need to be skipped.\n//\n// The mutex will be locked only if needed.\nfunc (vs *VState) Enabled(level Level, depth int) bool {\n\t// This function tries hard to be cheap unless there's work to do.\n\t// The fast path is two atomic loads and compares.\n\n\t// Here is a cheap but safe test to see if V logging is enabled globally.\n\tif vs.verbosity.get() >= level {\n\t\treturn true\n\t}\n\n\t// It's off globally but vmodule may still be set.\n\t// Here is another cheap but safe test to see if vmodule is enabled.\n\tif atomic.LoadInt32(&vs.filterLength) > 0 {\n\t\t// Now we need a proper lock to use the logging structure. The pcs field\n\t\t// is shared so we must lock before accessing it. This is fairly expensive,\n\t\t// but if V logging is enabled we're slow anyway.\n\t\tvs.mu.Lock()\n\t\tdefer vs.mu.Unlock()\n\t\tif runtime.Callers(depth+2, vs.pcs[:]) == 0 {\n\t\t\treturn false\n\t\t}\n\t\t// runtime.Callers returns \"return PCs\", but we want\n\t\t// to look up the symbolic information for the call,\n\t\t// so subtract 1 from the PC. runtime.CallersFrames\n\t\t// would be cleaner, but allocates.\n\t\tpc := vs.pcs[0] - 1\n\t\tv, ok := vs.vmap[pc]\n\t\tif !ok {\n\t\t\tv = vs.setV(pc)\n\t\t}\n\t\treturn v >= level\n\t}\n\treturn false\n}\n\n// setV computes and remembers the V level for a given PC\n// when vmodule is enabled.\n// File pattern matching takes the basename of the file, stripped\n// of its .go suffix, and uses filepath.Match, which is a little more\n// general than the *? matching used in C++.\n// Mutex is held.\nfunc (vs *VState) setV(pc uintptr) Level {\n\tfn := runtime.FuncForPC(pc)\n\tfile, _ := fn.FileLine(pc)\n\t// The file is something like /a/b/c/d.go. We want just the d.\n\tfile = strings.TrimSuffix(file, \".go\")\n\tif slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\tfile = file[slash+1:]\n\t}\n\tfor _, filter := range vs.vmodule.filter {\n\t\tif filter.match(file) {\n\t\t\tvs.vmap[pc] = filter.level\n\t\t\treturn filter.level\n\t\t}\n\t}\n\tvs.vmap[pc] = 0\n\treturn 0\n}\n"
  },
  {
    "path": "internal/verbosity/verbosity_test.go",
    "content": "/*\nCopyright 2013 Google Inc. All Rights Reserved.\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage verbosity\n\nimport (\n\t\"testing\"\n\n\t\"k8s.io/klog/v2/internal/test/require\"\n)\n\nfunc TestV(t *testing.T) {\n\tvs := New()\n\trequire.NoError(t, vs.verbosity.Set(\"2\"))\n\tdepth := 0\n\tif !vs.Enabled(1, depth) {\n\t\tt.Error(\"not enabled for 1\")\n\t}\n\tif !vs.Enabled(2, depth) {\n\t\tt.Error(\"not enabled for 2\")\n\t}\n\tif vs.Enabled(3, depth) {\n\t\tt.Error(\"enabled for 3\")\n\t}\n}\n\nfunc TestVmoduleOn(t *testing.T) {\n\tvs := New()\n\trequire.NoError(t, vs.vmodule.Set(\"verbosity_test=2\"))\n\tdepth := 0\n\tif !vs.Enabled(1, depth) {\n\t\tt.Error(\"not enabled for 1\")\n\t}\n\tif !vs.Enabled(2, depth) {\n\t\tt.Error(\"not enabled for 2\")\n\t}\n\tif vs.Enabled(3, depth) {\n\t\tt.Error(\"enabled for 3\")\n\t}\n\tif enabledInHelper(vs, 1) {\n\t\tt.Error(\"enabled for helper at 1\")\n\t}\n\tif enabledInHelper(vs, 2) {\n\t\tt.Error(\"enabled for helper at 2\")\n\t}\n\tif enabledInHelper(vs, 3) {\n\t\tt.Error(\"enabled for helper at 3\")\n\t}\n}\n\n// vGlobs are patterns that match/don't match this file at V=2.\nvar vGlobs = map[string]bool{\n\t// Easy to test the numeric match here.\n\t\"verbosity_test=1\": false, // If -vmodule sets V to 1, V(2) will fail.\n\t\"verbosity_test=2\": true,\n\t\"verbosity_test=3\": true, // If -vmodule sets V to 1, V(3) will succeed.\n\t// These all use 2 and check the patterns. All are true.\n\t\"*=2\":                true,\n\t\"?e*=2\":              true,\n\t\"?????????_*=2\":      true,\n\t\"??[arx]??????_*t=2\": true,\n\t// These all use 2 and check the patterns. All are false.\n\t\"*x=2\":         false,\n\t\"m*=2\":         false,\n\t\"??_*=2\":       false,\n\t\"?[abc]?_*t=2\": false,\n}\n\n// Test that vmodule globbing works as advertised.\nfunc testVmoduleGlob(pat string, match bool, t *testing.T) {\n\tvs := New()\n\trequire.NoError(t, vs.vmodule.Set(pat))\n\tdepth := 0\n\tactual := vs.Enabled(2, depth)\n\tif actual != match {\n\t\tt.Errorf(\"incorrect match for %q: got %#v expected %#v\", pat, actual, match)\n\t}\n}\n\n// Test that a vmodule globbing works as advertised.\nfunc TestVmoduleGlob(t *testing.T) {\n\tfor glob, match := range vGlobs {\n\t\ttestVmoduleGlob(glob, match, t)\n\t}\n}\n"
  },
  {
    "path": "k8s_references.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// ObjectRef references a kubernetes object\ntype ObjectRef struct {\n\tName      string `json:\"name\"`\n\tNamespace string `json:\"namespace,omitempty\"`\n}\n\nfunc (ref ObjectRef) String() string {\n\tif ref.Namespace != \"\" {\n\t\tvar builder strings.Builder\n\t\tbuilder.Grow(len(ref.Namespace) + len(ref.Name) + 1)\n\t\tbuilder.WriteString(ref.Namespace)\n\t\tbuilder.WriteRune('/')\n\t\tbuilder.WriteString(ref.Name)\n\t\treturn builder.String()\n\t}\n\treturn ref.Name\n}\n\nfunc (ref ObjectRef) WriteText(out *bytes.Buffer) {\n\tout.WriteRune('\"')\n\tref.writeUnquoted(out)\n\tout.WriteRune('\"')\n}\n\nfunc (ref ObjectRef) writeUnquoted(out *bytes.Buffer) {\n\tif ref.Namespace != \"\" {\n\t\tout.WriteString(ref.Namespace)\n\t\tout.WriteRune('/')\n\t}\n\tout.WriteString(ref.Name)\n}\n\n// MarshalLog ensures that loggers with support for structured output will log\n// as a struct by removing the String method via a custom type.\nfunc (ref ObjectRef) MarshalLog() interface{} {\n\ttype or ObjectRef\n\treturn or(ref)\n}\n\nvar _ logr.Marshaler = ObjectRef{}\n\n// KMetadata is a subset of the kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface\n// this interface may expand in the future, but will always be a subset of the\n// kubernetes k8s.io/apimachinery/pkg/apis/meta/v1.Object interface\ntype KMetadata interface {\n\tGetName() string\n\tGetNamespace() string\n}\n\n// KObj returns ObjectRef from ObjectMeta\nfunc KObj(obj KMetadata) ObjectRef {\n\tif obj == nil {\n\t\treturn ObjectRef{}\n\t}\n\tif val := reflect.ValueOf(obj); val.Kind() == reflect.Ptr && val.IsNil() {\n\t\treturn ObjectRef{}\n\t}\n\n\treturn ObjectRef{\n\t\tName:      obj.GetName(),\n\t\tNamespace: obj.GetNamespace(),\n\t}\n}\n\n// KRef returns ObjectRef from name and namespace\nfunc KRef(namespace, name string) ObjectRef {\n\treturn ObjectRef{\n\t\tName:      name,\n\t\tNamespace: namespace,\n\t}\n}\n\n// KObjs returns slice of ObjectRef from an slice of ObjectMeta\n//\n// DEPRECATED: Use KObjSlice instead, it has better performance.\nfunc KObjs(arg interface{}) []ObjectRef {\n\ts := reflect.ValueOf(arg)\n\tif s.Kind() != reflect.Slice {\n\t\treturn nil\n\t}\n\tobjectRefs := make([]ObjectRef, 0, s.Len())\n\tfor i := 0; i < s.Len(); i++ {\n\t\tif v, ok := s.Index(i).Interface().(KMetadata); ok {\n\t\t\tobjectRefs = append(objectRefs, KObj(v))\n\t\t} else {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn objectRefs\n}\n\n// KObjSlice takes a slice of objects that implement the KMetadata interface\n// and returns an object that gets logged as a slice of ObjectRef values or a\n// string containing those values, depending on whether the logger prefers text\n// output or structured output.\n//\n// An error string is logged when KObjSlice is not passed a suitable slice.\n//\n// Processing of the argument is delayed until the value actually gets logged,\n// in contrast to KObjs where that overhead is incurred regardless of whether\n// the result is needed.\nfunc KObjSlice(arg interface{}) interface{} {\n\treturn kobjSlice{arg: arg}\n}\n\ntype kobjSlice struct {\n\targ interface{}\n}\n\nvar _ fmt.Stringer = kobjSlice{}\nvar _ logr.Marshaler = kobjSlice{}\n\nfunc (ks kobjSlice) String() string {\n\tobjectRefs, errStr := ks.process()\n\tif errStr != \"\" {\n\t\treturn errStr\n\t}\n\treturn fmt.Sprintf(\"%v\", objectRefs)\n}\n\nfunc (ks kobjSlice) MarshalLog() interface{} {\n\tobjectRefs, errStr := ks.process()\n\tif errStr != \"\" {\n\t\treturn errStr\n\t}\n\treturn objectRefs\n}\n\nfunc (ks kobjSlice) process() (objs []interface{}, err string) {\n\ts := reflect.ValueOf(ks.arg)\n\tswitch s.Kind() {\n\tcase reflect.Invalid:\n\t\t// nil parameter, print as nil.\n\t\treturn nil, \"\"\n\tcase reflect.Slice:\n\t\t// Okay, handle below.\n\tdefault:\n\t\treturn nil, fmt.Sprintf(\"<KObjSlice needs a slice, got type %T>\", ks.arg)\n\t}\n\tobjectRefs := make([]interface{}, 0, s.Len())\n\tfor i := 0; i < s.Len(); i++ {\n\t\titem := s.Index(i).Interface()\n\t\tif item == nil {\n\t\t\tobjectRefs = append(objectRefs, nil)\n\t\t} else if v, ok := item.(KMetadata); ok {\n\t\t\tobjectRefs = append(objectRefs, KObj(v))\n\t\t} else {\n\t\t\treturn nil, fmt.Sprintf(\"<KObjSlice needs a slice of values implementing KMetadata, got type %T>\", item)\n\t\t}\n\t}\n\treturn objectRefs, \"\"\n}\n\nvar nilToken = []byte(\"null\")\n\nfunc (ks kobjSlice) WriteText(out *bytes.Buffer) {\n\ts := reflect.ValueOf(ks.arg)\n\tswitch s.Kind() {\n\tcase reflect.Invalid:\n\t\t// nil parameter, print as null.\n\t\tout.Write(nilToken)\n\t\treturn\n\tcase reflect.Slice:\n\t\t// Okay, handle below.\n\tdefault:\n\t\tfmt.Fprintf(out, `\"<KObjSlice needs a slice, got type %T>\"`, ks.arg)\n\t\treturn\n\t}\n\tout.Write([]byte{'['})\n\tdefer out.Write([]byte{']'})\n\tfor i := 0; i < s.Len(); i++ {\n\t\tif i > 0 {\n\t\t\tout.Write([]byte{','})\n\t\t}\n\t\titem := s.Index(i).Interface()\n\t\tif item == nil {\n\t\t\tout.Write(nilToken)\n\t\t} else if v, ok := item.(KMetadata); ok {\n\t\t\tKObj(v).WriteText(out)\n\t\t} else {\n\t\t\tfmt.Fprintf(out, `\"<KObjSlice needs a slice of values implementing KMetadata, got type %T>\"`, item)\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "k8s_references_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"log/slog\"\n)\n\nfunc (ref ObjectRef) LogValue() slog.Value {\n\tif ref.Namespace != \"\" {\n\t\treturn slog.GroupValue(slog.String(\"name\", ref.Name), slog.String(\"namespace\", ref.Namespace))\n\t}\n\treturn slog.GroupValue(slog.String(\"name\", ref.Name))\n}\n\nvar _ slog.LogValuer = ObjectRef{}\n\nfunc (ks kobjSlice) LogValue() slog.Value {\n\treturn slog.AnyValue(ks.MarshalLog())\n}\n\nvar _ slog.LogValuer = kobjSlice{}\n"
  },
  {
    "path": "klog.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2013 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Package klog contains the following functionality:\n//\n//   - output routing as defined via command line flags ([InitFlags])\n//   - log formatting as text, either with a single, unstructured string ([Info], [Infof], etc.)\n//     or as a structured log entry with message and key/value pairs ([InfoS], etc.)\n//   - management of a go-logr [Logger] ([SetLogger], [Background], [TODO])\n//   - helper functions for logging values ([Format]) and managing the state of klog ([CaptureState], [State.Restore])\n//   - wrappers for [logr] APIs for contextual logging where the wrappers can\n//     be turned into no-ops ([EnableContextualLogging], [NewContext], [FromContext],\n//     [LoggerWithValues], [LoggerWithName]); if the ability to turn off\n//     contextual logging is not needed, then go-logr can also be used directly\n//   - type aliases for go-logr types to simplify imports in code which uses both (e.g. [Logger])\n//   - [k8s.io/klog/v2/textlogger]: a logger which uses the same formatting as klog log with\n//     simpler output routing; beware that it comes with its own command line flags\n//     and does not use the ones from klog\n//   - [k8s.io/klog/v2/ktesting]: per-test output in Go unit tests\n//   - [k8s.io/klog/v2/klogr]: a deprecated, standalone [logr.Logger] on top of the main klog package;\n//     use [Background] instead if klog output routing is needed, [k8s.io/klog/v2/textlogger] if not\n//   - [k8s.io/klog/v2/examples]: demos of this functionality\n//   - [k8s.io/klog/v2/test]: reusable tests for [logr.Logger] implementations\n//\n// Basic examples:\n//\n//\tklog.Info(\"Prepare to repel boarders\")\n//\n//\tklog.Fatalf(\"Initialization failed: %s\", err)\n//\n// See the documentation for the V function for an explanation of these examples:\n//\n//\tif klog.V(2) {\n//\t\tklog.Info(\"Starting transaction...\")\n//\t}\n//\n//\tklog.V(2).Infoln(\"Processed\", nItems, \"elements\")\n//\n// Log output is buffered and written periodically using Flush. Programs\n// should call Flush before exiting to guarantee all log output is written.\n//\n// By default, all log statements write to standard error.\n// This package provides several flags that modify this behavior.\n// As a result, flag.Parse must be called before any logging is done.\n//\n//\t-logtostderr=true\n//\t\tLogs are written to standard error instead of to files.\n//\t\tBy default, all logs are written regardless of severity\n//\t\t(legacy behavior). To filter logs by severity when\n//\t\t-logtostderr=true, set -legacy_stderr_threshold_behavior=false\n//\t\tand use -stderrthreshold.\n//\t\tWith -legacy_stderr_threshold_behavior=true,\n//\t\t-stderrthreshold has no effect.\n//\n//\t\tThe following flags always have no effect:\n//\t\t-alsologtostderr, -alsologtostderrthreshold, and -log_dir.\n//\t\tOutput redirection at runtime with SetOutput is also ignored.\n//\t-alsologtostderr=false\n//\t\tLogs are written to standard error as well as to files.\n//\t-alsologtostderrthreshold=INFO\n//\t\tLog events at or above this severity are logged to standard\n//\t\terror when -alsologtostderr=true (no effect when -logtostderr=true).\n//\t\tDefault is INFO to maintain backward compatibility.\n//\t-stderrthreshold=ERROR\n//\t\tLog events at or above this severity are logged to standard\n//\t\terror as well as to files. When -logtostderr=true, this flag\n//\t\thas no effect unless -legacy_stderr_threshold_behavior=false.\n//\t-legacy_stderr_threshold_behavior=true\n//\t\tIf true, -stderrthreshold is ignored when -logtostderr=true\n//\t\t(legacy behavior). If false, -stderrthreshold is honored even\n//\t\twhen -logtostderr=true, allowing severity-based filtering.\n//\t-log_dir=\"\"\n//\t\tLog files will be written to this directory instead of the\n//\t\tdefault temporary directory.\n//\n//\tOther flags provide aids to debugging.\n//\n//\t-log_backtrace_at=\"\"\n//\t\tWhen set to a file and line number holding a logging statement,\n//\t\tsuch as\n//\t\t\t-log_backtrace_at=gopherflakes.go:234\n//\t\ta stack trace will be written to the Info log whenever execution\n//\t\thits that statement. (Unlike with -vmodule, the \".go\" must be\n//\t\tpresent.)\n//\t-v=0\n//\t\tEnable V-leveled logging at the specified level.\n//\t-vmodule=\"\"\n//\t\tThe syntax of the argument is a comma-separated list of pattern=N,\n//\t\twhere pattern is a literal file name (minus the \".go\" suffix) or\n//\t\t\"glob\" pattern and N is a V level. For instance,\n//\t\t\t-vmodule=gopher*=3\n//\t\tsets the V level to 3 in all Go files whose names begin \"gopher\".\npackage klog\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\tstdLog \"log\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/internal/clock\"\n\t\"k8s.io/klog/v2/internal/dbg\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/severity\"\n)\n\n// severityValue identifies the sort of log: info, warning etc. It also implements\n// the flag.Value interface. The -stderrthreshold flag is of type severity and\n// should be modified only through the flag.Value interface. The values match\n// the corresponding constants in C++.\ntype severityValue struct {\n\tseverity.Severity\n}\n\n// get returns the value of the severity.\nfunc (s *severityValue) get() severity.Severity {\n\treturn severity.Severity(atomic.LoadInt32((*int32)(&s.Severity)))\n}\n\n// set sets the value of the severity.\nfunc (s *severityValue) set(val severity.Severity) {\n\tatomic.StoreInt32((*int32)(&s.Severity), int32(val))\n}\n\n// String is part of the flag.Value interface.\nfunc (s *severityValue) String() string {\n\treturn strconv.FormatInt(int64(s.Severity), 10)\n}\n\n// Get is part of the flag.Getter interface.\nfunc (s *severityValue) Get() interface{} {\n\treturn s.Severity\n}\n\n// Set is part of the flag.Value interface.\nfunc (s *severityValue) Set(value string) error {\n\tvar threshold severity.Severity\n\t// Is it a known name?\n\tif v, ok := severity.ByName(value); ok {\n\t\tthreshold = v\n\t} else {\n\t\tv, err := strconv.ParseInt(value, 10, 32)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tthreshold = severity.Severity(v)\n\t}\n\ts.set(threshold)\n\treturn nil\n}\n\n// OutputStats tracks the number of output lines and bytes written.\ntype OutputStats struct {\n\tlines int64\n\tbytes int64\n}\n\n// Lines returns the number of lines written.\nfunc (s *OutputStats) Lines() int64 {\n\treturn atomic.LoadInt64(&s.lines)\n}\n\n// Bytes returns the number of bytes written.\nfunc (s *OutputStats) Bytes() int64 {\n\treturn atomic.LoadInt64(&s.bytes)\n}\n\n// Stats tracks the number of lines of output and number of bytes\n// per severity level. Values must be read with atomic.LoadInt64.\nvar Stats struct {\n\tInfo, Warning, Error OutputStats\n}\n\nvar severityStats = [severity.NumSeverity]*OutputStats{\n\tseverity.InfoLog:    &Stats.Info,\n\tseverity.WarningLog: &Stats.Warning,\n\tseverity.ErrorLog:   &Stats.Error,\n}\n\n// Level is exported because it appears in the arguments to V and is\n// the type of the v flag, which can be set programmatically.\n// It's a distinct type because we want to discriminate it from logType.\n// Variables of type level are only changed under logging.mu.\n// The -v flag is read only with atomic ops, so the state of the logging\n// module is consistent.\n\n// Level is treated as a sync/atomic int32.\n\n// Level specifies a level of verbosity for V logs. *Level implements\n// flag.Value; the -v flag is of type Level and should be modified\n// only through the flag.Value interface.\ntype Level int32\n\n// get returns the value of the Level.\nfunc (l *Level) get() Level {\n\treturn Level(atomic.LoadInt32((*int32)(l)))\n}\n\n// set sets the value of the Level.\nfunc (l *Level) set(val Level) {\n\tatomic.StoreInt32((*int32)(l), int32(val))\n}\n\n// String is part of the flag.Value interface.\nfunc (l *Level) String() string {\n\treturn strconv.FormatInt(int64(*l), 10)\n}\n\n// Get is part of the flag.Getter interface.\nfunc (l *Level) Get() interface{} {\n\treturn *l\n}\n\n// Set is part of the flag.Value interface.\nfunc (l *Level) Set(value string) error {\n\tv, err := strconv.ParseInt(value, 10, 32)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\tlogging.setVState(Level(v), logging.vmodule.filter, false)\n\treturn nil\n}\n\n// moduleSpec represents the setting of the -vmodule flag.\ntype moduleSpec struct {\n\tfilter []modulePat\n}\n\n// modulePat contains a filter for the -vmodule flag.\n// It holds a verbosity level and a file pattern to match.\ntype modulePat struct {\n\tpattern string\n\tliteral bool // The pattern is a literal string\n\tlevel   Level\n}\n\n// match reports whether the file matches the pattern. It uses a string\n// comparison if the pattern contains no metacharacters.\nfunc (m *modulePat) match(file string) bool {\n\tif m.literal {\n\t\treturn file == m.pattern\n\t}\n\tmatch, _ := filepath.Match(m.pattern, file)\n\treturn match\n}\n\nfunc (m *moduleSpec) String() string {\n\t// Lock because the type is not atomic. TODO: clean this up.\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\treturn m.serialize()\n}\n\nfunc (m *moduleSpec) serialize() string {\n\tvar b bytes.Buffer\n\tfor i, f := range m.filter {\n\t\tif i > 0 {\n\t\t\tb.WriteRune(',')\n\t\t}\n\t\tfmt.Fprintf(&b, \"%s=%d\", f.pattern, f.level)\n\t}\n\treturn b.String()\n}\n\n// Get is part of the (Go 1.2)  flag.Getter interface. It always returns nil for this flag type since the\n// struct is not exported.\nfunc (m *moduleSpec) Get() interface{} {\n\treturn nil\n}\n\nvar errVmoduleSyntax = errors.New(\"syntax error: expect comma-separated list of filename=N\")\n\n// Set will sets module value\n// Syntax: -vmodule=recordio=2,file=1,gfs*=3\nfunc (m *moduleSpec) Set(value string) error {\n\tfilter, err := parseModuleSpec(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\tlogging.setVState(logging.verbosity, filter, true)\n\treturn nil\n}\n\nfunc parseModuleSpec(value string) ([]modulePat, error) {\n\tvar filter []modulePat\n\tfor _, pat := range strings.Split(value, \",\") {\n\t\tif len(pat) == 0 {\n\t\t\t// Empty strings such as from a trailing comma can be ignored.\n\t\t\tcontinue\n\t\t}\n\t\tpatLev := strings.Split(pat, \"=\")\n\t\tif len(patLev) != 2 || len(patLev[0]) == 0 || len(patLev[1]) == 0 {\n\t\t\treturn nil, errVmoduleSyntax\n\t\t}\n\t\tpattern := patLev[0]\n\t\tv, err := strconv.ParseInt(patLev[1], 10, 32)\n\t\tif err != nil {\n\t\t\treturn nil, errors.New(\"syntax error: expect comma-separated list of filename=N\")\n\t\t}\n\t\tif v < 0 {\n\t\t\treturn nil, errors.New(\"negative value for vmodule level\")\n\t\t}\n\t\tif v == 0 {\n\t\t\tcontinue // Ignore. It's harmless but no point in paying the overhead.\n\t\t}\n\t\t// TODO: check syntax of filter?\n\t\tfilter = append(filter, modulePat{pattern, isLiteral(pattern), Level(v)})\n\t}\n\treturn filter, nil\n}\n\n// isLiteral reports whether the pattern is a literal string, that is, has no metacharacters\n// that require filepath.Match to be called to match the pattern.\nfunc isLiteral(pattern string) bool {\n\treturn !strings.ContainsAny(pattern, `\\*?[]`)\n}\n\n// traceLocation represents the setting of the -log_backtrace_at flag.\ntype traceLocation struct {\n\tfile string\n\tline int\n}\n\n// isSet reports whether the trace location has been specified.\n// logging.mu is held.\nfunc (t *traceLocation) isSet() bool {\n\treturn t.line > 0\n}\n\n// match reports whether the specified file and line matches the trace location.\n// The argument file name is the full path, not the basename specified in the flag.\n// logging.mu is held.\nfunc (t *traceLocation) match(file string, line int) bool {\n\tif t.line != line {\n\t\treturn false\n\t}\n\tif i := strings.LastIndex(file, \"/\"); i >= 0 {\n\t\tfile = file[i+1:]\n\t}\n\treturn t.file == file\n}\n\nfunc (t *traceLocation) String() string {\n\t// Lock because the type is not atomic. TODO: clean this up.\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\treturn fmt.Sprintf(\"%s:%d\", t.file, t.line)\n}\n\n// Get is part of the (Go 1.2) flag.Getter interface. It always returns nil for this flag type since the\n// struct is not exported\nfunc (t *traceLocation) Get() interface{} {\n\treturn nil\n}\n\nvar errTraceSyntax = errors.New(\"syntax error: expect file.go:234\")\n\n// Set will sets backtrace value\n// Syntax: -log_backtrace_at=gopherflakes.go:234\n// Note that unlike vmodule the file extension is included here.\nfunc (t *traceLocation) Set(value string) error {\n\tif value == \"\" {\n\t\t// Unset.\n\t\tlogging.mu.Lock()\n\t\tdefer logging.mu.Unlock()\n\t\tt.line = 0\n\t\tt.file = \"\"\n\t\treturn nil\n\t}\n\tfields := strings.Split(value, \":\")\n\tif len(fields) != 2 {\n\t\treturn errTraceSyntax\n\t}\n\tfile, line := fields[0], fields[1]\n\tif !strings.Contains(file, \".\") {\n\t\treturn errTraceSyntax\n\t}\n\tv, err := strconv.Atoi(line)\n\tif err != nil {\n\t\treturn errTraceSyntax\n\t}\n\tif v <= 0 {\n\t\treturn errors.New(\"negative or zero value for level\")\n\t}\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\tt.line = v\n\tt.file = file\n\treturn nil\n}\n\nvar logging loggingT\nvar commandLine flag.FlagSet\n\n// init sets up the defaults and creates command line flags.\nfunc init() {\n\t// Initialize severity thresholds\n\tlogging.stderrThreshold = severityValue{\n\t\tSeverity: severity.ErrorLog, // Default stderrThreshold is ERROR.\n\t}\n\tlogging.alsologtostderrthreshold = severityValue{\n\t\tSeverity: severity.InfoLog, // Default alsologtostderrthreshold is INFO (to maintain backward compatibility).\n\t}\n\tlogging.setVState(0, nil, false)\n\n\tcommandLine.StringVar(&logging.logDir, \"log_dir\", \"\", \"If non-empty, write log files in this directory (no effect when -logtostderr=true)\")\n\tcommandLine.StringVar(&logging.logFile, \"log_file\", \"\", \"If non-empty, use this log file (no effect when -logtostderr=true)\")\n\tcommandLine.Uint64Var(&logging.logFileMaxSizeMB, \"log_file_max_size\", 1800,\n\t\t\"Defines the maximum size a log file can grow to (no effect when -logtostderr=true). Unit is megabytes. \"+\n\t\t\t\"If the value is 0, the maximum file size is unlimited.\")\n\tcommandLine.BoolVar(&logging.toStderr, \"logtostderr\", true, \"log to standard error instead of files\")\n\tcommandLine.BoolVar(&logging.alsoToStderr, \"alsologtostderr\", false, \"log to standard error as well as files (no effect when -logtostderr=true)\")\n\tcommandLine.BoolVar(&logging.legacyStderrThresholdBehavior, \"legacy_stderr_threshold_behavior\", true, \"If true, stderrthreshold is ignored when logtostderr=true (legacy behavior). If false, stderrthreshold is honored even when logtostderr=true\")\n\tcommandLine.Var(&logging.verbosity, \"v\", \"number for the log level verbosity\")\n\tcommandLine.BoolVar(&logging.addDirHeader, \"add_dir_header\", false, \"If true, adds the file directory to the header of the log messages\")\n\tcommandLine.BoolVar(&logging.skipHeaders, \"skip_headers\", false, \"If true, avoid header prefixes in the log messages\")\n\tcommandLine.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)\")\n\tcommandLine.BoolVar(&logging.skipLogHeaders, \"skip_log_headers\", false, \"If true, avoid headers when opening log files (no effect when -logtostderr=true)\")\n\tcommandLine.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=true unless -legacy_stderr_threshold_behavior=false)\")\n\tcommandLine.Var(&logging.alsologtostderrthreshold, \"alsologtostderrthreshold\", \"logs at or above this threshold go to stderr when -alsologtostderr=true (no effect when -logtostderr=true)\")\n\tcommandLine.Var(&logging.vmodule, \"vmodule\", \"comma-separated list of pattern=N settings for file-filtered logging\")\n\tcommandLine.Var(&logging.traceLocation, \"log_backtrace_at\", \"when logging hits line file:N, emit a stack trace\")\n\n\tlogging.settings.contextualLoggingEnabled = true\n\tlogging.flushD = newFlushDaemon(logging.lockAndFlushAll, nil)\n}\n\n// InitFlags is for explicitly initializing the flags.\n// It may get called repeatedly for different flagsets, but not\n// twice for the same one. May get called concurrently\n// to other goroutines using klog. However, only some flags\n// may get set concurrently (see implementation).\nfunc InitFlags(flagset *flag.FlagSet) {\n\tif flagset == nil {\n\t\tflagset = flag.CommandLine\n\t}\n\n\tcommandLine.VisitAll(func(f *flag.Flag) {\n\t\tflagset.Var(f.Value, f.Name, f.Usage)\n\t})\n}\n\n// Flush flushes all pending log I/O.\nfunc Flush() {\n\tlogging.lockAndFlushAll()\n}\n\n// settings collects global settings.\ntype settings struct {\n\t// contextualLoggingEnabled controls whether contextual logging is\n\t// active. Disabling it may have some small performance benefit.\n\tcontextualLoggingEnabled bool\n\n\t// logger is the global Logger chosen by users of klog, nil if\n\t// none is available.\n\tlogger *logWriter\n\n\t// loggerOptions contains the options that were supplied for\n\t// globalLogger.\n\tloggerOptions loggerOptions\n\n\t// Boolean flags. Not handled atomically because the flag.Value interface\n\t// does not let us avoid the =true, and that shorthand is necessary for\n\t// compatibility. TODO: does this matter enough to fix? Seems unlikely.\n\ttoStderr                      bool // The -logtostderr flag.\n\talsoToStderr                  bool // The -alsologtostderr flag.\n\tlegacyStderrThresholdBehavior bool // The -legacy_stderr_threshold_behavior flag.\n\n\t// Level flag. Handled atomically.\n\tstderrThreshold          severityValue // The -stderrthreshold flag.\n\talsologtostderrthreshold severityValue // The -alsologtostderrthreshold flag.\n\n\t// Access to all of the following fields must be protected via a mutex.\n\n\t// file holds writer for each of the log types.\n\tfile [severity.NumSeverity]io.Writer\n\t// flushInterval is the interval for periodic flushing. If zero,\n\t// the global default will be used.\n\tflushInterval time.Duration\n\n\t// filterLength stores the length of the vmodule filter chain. If greater\n\t// than zero, it means vmodule is enabled. It may be read safely\n\t// using sync.LoadInt32, but is only modified under mu.\n\tfilterLength int32\n\t// traceLocation is the state of the -log_backtrace_at flag.\n\ttraceLocation traceLocation\n\t// These flags are modified only under lock, although verbosity may be fetched\n\t// safely using atomic.LoadInt32.\n\tvmodule   moduleSpec // The state of the -vmodule flag.\n\tverbosity Level      // V logging level, the value of the -v flag/\n\n\t// If non-empty, overrides the choice of directory in which to write logs.\n\t// See createLogDirs for the full list of possible destinations.\n\tlogDir string\n\n\t// If non-empty, specifies the path of the file to write logs. mutually exclusive\n\t// with the log_dir option.\n\tlogFile string\n\n\t// When logFile is specified, this limiter makes sure the logFile won't exceeds a certain size. When exceeds, the\n\t// logFile will be cleaned up. If this value is 0, no size limitation will be applied to logFile.\n\tlogFileMaxSizeMB uint64\n\n\t// If true, do not add the prefix headers, useful when used with SetOutput\n\tskipHeaders bool\n\n\t// If true, do not add the headers to log files\n\tskipLogHeaders bool\n\n\t// If true, add the file directory to the header\n\taddDirHeader bool\n\n\t// If true, messages will not be propagated to lower severity log levels\n\toneOutput bool\n\n\t// If set, all output will be filtered through the filter.\n\tfilter LogFilter\n}\n\n// deepCopy creates a copy that doesn't share anything with the original\n// instance.\nfunc (s settings) deepCopy() settings {\n\t// vmodule is a slice and would be shared, so we have copy it.\n\tfilter := make([]modulePat, len(s.vmodule.filter))\n\tcopy(filter, s.vmodule.filter)\n\ts.vmodule.filter = filter\n\n\tif s.logger != nil {\n\t\tlogger := *s.logger\n\t\ts.logger = &logger\n\t}\n\n\treturn s\n}\n\n// loggingT collects all the global state of the logging setup.\ntype loggingT struct {\n\tsettings\n\n\t// flushD holds a flushDaemon that frequently flushes log file buffers.\n\t// Uses its own mutex.\n\tflushD *flushDaemon\n\n\t// mu protects the remaining elements of this structure and the fields\n\t// in settingsT which need a mutex lock.\n\tmu sync.Mutex\n\n\t// pcs is used in V to avoid an allocation when computing the caller's PC.\n\tpcs [1]uintptr\n\t// vmap is a cache of the V Level for each V() call site, identified by PC.\n\t// It is wiped whenever the vmodule flag changes state.\n\tvmap map[uintptr]Level\n}\n\n// setVState sets a consistent state for V logging.\n// l.mu is held.\nfunc (l *loggingT) setVState(verbosity Level, filter []modulePat, setFilter bool) {\n\t// Turn verbosity off so V will not fire while we are in transition.\n\tl.verbosity.set(0)\n\t// Ditto for filter length.\n\tatomic.StoreInt32(&l.filterLength, 0)\n\n\t// Set the new filters and wipe the pc->Level map if the filter has changed.\n\tif setFilter {\n\t\tl.vmodule.filter = filter\n\t\tl.vmap = make(map[uintptr]Level)\n\t}\n\n\t// Things are consistent now, so enable filtering and verbosity.\n\t// They are enabled in order opposite to that in V.\n\tatomic.StoreInt32(&l.filterLength, int32(len(filter)))\n\tl.verbosity.set(verbosity)\n}\n\nvar timeNow = time.Now // Stubbed out for testing.\n\n// CaptureState gathers information about all current klog settings.\n// The result can be used to restore those settings.\nfunc CaptureState() State {\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\treturn &state{\n\t\tsettings:      logging.settings.deepCopy(),\n\t\tflushDRunning: logging.flushD.isRunning(),\n\t\tmaxSize:       MaxSize,\n\t}\n}\n\n// State stores a snapshot of klog settings. It gets created with CaptureState\n// and can be used to restore the entire state. Modifying individual settings\n// is supported via the command line flags.\ntype State interface {\n\t// Restore restore the entire state. It may get called more than once.\n\tRestore()\n}\n\ntype state struct {\n\tsettings\n\n\tflushDRunning bool\n\tmaxSize       uint64\n}\n\nfunc (s *state) Restore() {\n\t// This needs to be done before mutex locking.\n\tif s.flushDRunning && !logging.flushD.isRunning() {\n\t\t// This is not quite accurate: StartFlushDaemon might\n\t\t// have been called with some different interval.\n\t\tinterval := s.flushInterval\n\t\tif interval == 0 {\n\t\t\tinterval = flushInterval\n\t\t}\n\t\tlogging.flushD.run(interval)\n\t} else if !s.flushDRunning && logging.flushD.isRunning() {\n\t\tlogging.flushD.stop()\n\t}\n\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\n\tlogging.settings = s.settings\n\tlogging.setVState(s.verbosity, s.vmodule.filter, true)\n\tMaxSize = s.maxSize\n}\n\n/*\nheader formats a log header as defined by the C++ implementation.\nIt returns a buffer containing the formatted header and the user's file and line number.\nThe depth specifies how many stack frames above lives the source line to be identified in the log message.\n\nLog lines have this form:\n\n\tLmmdd hh:mm:ss.uuuuuu threadid file:line] msg...\n\nwhere the fields are defined as follows:\n\n\tL                A single character, representing the log level (eg 'I' for INFO)\n\tmm               The month (zero padded; ie May is '05')\n\tdd               The day (zero padded)\n\thh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds\n\tthreadid         The space-padded thread ID as returned by GetTID()\n\tfile             The file name\n\tline             The line number\n\tmsg              The user-supplied message\n*/\nfunc (l *loggingT) header(s severity.Severity, depth int) (*buffer.Buffer, string, int) {\n\t_, file, line, ok := runtime.Caller(3 + depth)\n\tif !ok {\n\t\tfile = \"???\"\n\t\tline = 1\n\t} else {\n\t\tif slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\t\tpath := file\n\t\t\tfile = path[slash+1:]\n\t\t\tif l.addDirHeader {\n\t\t\t\tif dirsep := strings.LastIndex(path[:slash], \"/\"); dirsep >= 0 {\n\t\t\t\t\tfile = path[dirsep+1:]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn l.formatHeader(s, file, line, timeNow()), file, line\n}\n\n// formatHeader formats a log header using the provided file name and line number.\nfunc (l *loggingT) formatHeader(s severity.Severity, file string, line int, now time.Time) *buffer.Buffer {\n\tbuf := buffer.GetBuffer()\n\tif l.skipHeaders {\n\t\treturn buf\n\t}\n\tbuf.FormatHeader(s, file, line, now)\n\treturn buf\n}\n\nfunc (l *loggingT) println(s severity.Severity, logger *logWriter, filter LogFilter, args ...interface{}) {\n\tl.printlnDepth(s, logger, filter, 1, args...)\n}\n\nfunc (l *loggingT) printlnDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {\n\tif false {\n\t\t_ = fmt.Sprintln(args...) // cause vet to treat this function like fmt.Println\n\t}\n\n\tbuf, file, line := l.header(s, depth)\n\t// If a logger is set and doesn't support writing a formatted buffer,\n\t// we clear the generated header as we rely on the backing\n\t// logger implementation to print headers.\n\tif logger != nil && logger.writeKlogBuffer == nil {\n\t\tbuffer.PutBuffer(buf)\n\t\tbuf = buffer.GetBuffer()\n\t}\n\tif filter != nil {\n\t\targs = filter.Filter(args)\n\t}\n\tfmt.Fprintln(buf, args...)\n\tl.output(s, logger, buf, depth, file, line, false)\n}\n\nfunc (l *loggingT) print(s severity.Severity, logger *logWriter, filter LogFilter, args ...interface{}) {\n\tl.printDepth(s, logger, filter, 1, args...)\n}\n\nfunc (l *loggingT) printDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {\n\tif false {\n\t\t_ = fmt.Sprint(args...) //  // cause vet to treat this function like fmt.Print\n\t}\n\n\tbuf, file, line := l.header(s, depth)\n\tl.printWithInfos(buf, file, line, s, logger, filter, depth+1, args...)\n}\n\nfunc (l *loggingT) printWithInfos(buf *buffer.Buffer, file string, line int, s severity.Severity, logger *logWriter, filter LogFilter, depth int, args ...interface{}) {\n\t// If a logger is set and doesn't support writing a formatted buffer,\n\t// we clear the generated header as we rely on the backing\n\t// logger implementation to print headers.\n\tif logger != nil && logger.writeKlogBuffer == nil {\n\t\tbuffer.PutBuffer(buf)\n\t\tbuf = buffer.GetBuffer()\n\t}\n\tif filter != nil {\n\t\targs = filter.Filter(args)\n\t}\n\tfmt.Fprint(buf, args...)\n\tif buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\\n' {\n\t\tbuf.WriteByte('\\n')\n\t}\n\tl.output(s, logger, buf, depth, file, line, false)\n}\n\nfunc (l *loggingT) printf(s severity.Severity, logger *logWriter, filter LogFilter, format string, args ...interface{}) {\n\tl.printfDepth(s, logger, filter, 1, format, args...)\n}\n\nfunc (l *loggingT) printfDepth(s severity.Severity, logger *logWriter, filter LogFilter, depth int, format string, args ...interface{}) {\n\tif false {\n\t\t_ = fmt.Sprintf(format, args...) // cause vet to treat this function like fmt.Printf\n\t}\n\n\tbuf, file, line := l.header(s, depth)\n\t// If a logger is set and doesn't support writing a formatted buffer,\n\t// we clear the generated header as we rely on the backing\n\t// logger implementation to print headers.\n\tif logger != nil && logger.writeKlogBuffer == nil {\n\t\tbuffer.PutBuffer(buf)\n\t\tbuf = buffer.GetBuffer()\n\t}\n\tif filter != nil {\n\t\tformat, args = filter.FilterF(format, args)\n\t}\n\tfmt.Fprintf(buf, format, args...)\n\tif buf.Bytes()[buf.Len()-1] != '\\n' {\n\t\tbuf.WriteByte('\\n')\n\t}\n\tl.output(s, logger, buf, depth, file, line, false)\n}\n\n// printWithFileLine behaves like print but uses the provided file and line number.  If\n// alsoLogToStderr is true, the log message always appears on standard error; it\n// will also appear in the log file unless --logtostderr is set.\nfunc (l *loggingT) printWithFileLine(s severity.Severity, logger *logWriter, filter LogFilter, file string, line int, alsoToStderr bool, args ...interface{}) {\n\tbuf := l.formatHeader(s, file, line, timeNow())\n\t// If a logger is set and doesn't support writing a formatted buffer,\n\t// we clear the generated header as we rely on the backing\n\t// logger implementation to print headers.\n\tif logger != nil && logger.writeKlogBuffer == nil {\n\t\tbuffer.PutBuffer(buf)\n\t\tbuf = buffer.GetBuffer()\n\t}\n\tif filter != nil {\n\t\targs = filter.Filter(args)\n\t}\n\tfmt.Fprint(buf, args...)\n\tif buf.Bytes()[buf.Len()-1] != '\\n' {\n\t\tbuf.WriteByte('\\n')\n\t}\n\tl.output(s, logger, buf, 2 /* depth */, file, line, alsoToStderr)\n}\n\n// if logger is specified, will call logger.Error, otherwise output with logging module.\nfunc (l *loggingT) errorS(err error, logger *logWriter, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) {\n\tif filter != nil {\n\t\tmsg, keysAndValues = filter.FilterS(msg, keysAndValues)\n\t}\n\tif logger != nil {\n\t\tlogger.WithCallDepth(depth+2).Error(err, msg, keysAndValues...)\n\t\treturn\n\t}\n\tl.printS(err, severity.ErrorLog, depth+1, msg, keysAndValues...)\n}\n\n// if logger is specified, will call logger.Info, otherwise output with logging module.\nfunc (l *loggingT) infoS(logger *logWriter, filter LogFilter, depth int, msg string, keysAndValues ...interface{}) {\n\tif filter != nil {\n\t\tmsg, keysAndValues = filter.FilterS(msg, keysAndValues)\n\t}\n\tif logger != nil {\n\t\tlogger.WithCallDepth(depth+2).Info(msg, keysAndValues...)\n\t\treturn\n\t}\n\tl.printS(nil, severity.InfoLog, depth+1, msg, keysAndValues...)\n}\n\n// printS is called from infoS and errorS if logger is not specified.\n// set log severity by s\nfunc (l *loggingT) printS(err error, s severity.Severity, depth int, msg string, keysAndValues ...interface{}) {\n\t// The message is always quoted, even if it contains line breaks.\n\t// If developers want multi-line output, they should use a small, fixed\n\t// message and put the multi-line output into a value.\n\tqMsg := make([]byte, 0, 1024)\n\tqMsg = strconv.AppendQuote(qMsg, msg)\n\n\t// Only create a new buffer if we don't have one cached.\n\tb := buffer.GetBuffer()\n\tb.Write(qMsg)\n\n\tvar errKV []interface{}\n\tif err != nil {\n\t\terrKV = []interface{}{\"err\", err}\n\t}\n\tserialize.FormatKVs(&b.Buffer, errKV, keysAndValues)\n\tl.printDepth(s, nil, nil, depth+1, &b.Buffer)\n\t// Make the buffer available for reuse.\n\tbuffer.PutBuffer(b)\n}\n\n// SetOutput sets the output destination for all severities\nfunc SetOutput(w io.Writer) {\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\tfor s := severity.FatalLog; s >= severity.InfoLog; s-- {\n\t\tlogging.file[s] = w\n\t}\n}\n\n// SetOutputBySeverity sets the output destination for specific severity\nfunc SetOutputBySeverity(name string, w io.Writer) {\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\tsev, ok := severity.ByName(name)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"SetOutputBySeverity(%q): unrecognized severity name\", name))\n\t}\n\tlogging.file[sev] = w\n}\n\n// LogToStderr sets whether to log exclusively to stderr, bypassing outputs\nfunc LogToStderr(stderr bool) {\n\tlogging.mu.Lock()\n\tdefer logging.mu.Unlock()\n\n\tlogging.toStderr = stderr\n}\n\n// output writes the data to the log files and releases the buffer.\nfunc (l *loggingT) output(s severity.Severity, logger *logWriter, buf *buffer.Buffer, depth int, file string, line int, alsoToStderr bool) {\n\tvar isLocked = true\n\tl.mu.Lock()\n\tdefer func() {\n\t\tif isLocked {\n\t\t\t// Unlock before returning in case that it wasn't done already.\n\t\t\tl.mu.Unlock()\n\t\t}\n\t}()\n\n\tif l.traceLocation.isSet() {\n\t\tif l.traceLocation.match(file, line) {\n\t\t\tbuf.Write(dbg.Stacks(false))\n\t\t}\n\t}\n\tdata := buf.Bytes()\n\tif logger != nil {\n\t\tif logger.writeKlogBuffer != nil {\n\t\t\tlogger.writeKlogBuffer(data)\n\t\t} else {\n\t\t\tif len(data) > 0 && data[len(data)-1] == '\\n' {\n\t\t\t\tdata = data[:len(data)-1]\n\t\t\t}\n\t\t\t// TODO: set 'severity' and caller information as structured log info\n\t\t\t// keysAndValues := []interface{}{\"severity\", severityName[s], \"file\", file, \"line\", line}\n\t\t\tif s == severity.ErrorLog {\n\t\t\t\tlogger.WithCallDepth(depth+3).Error(nil, string(data))\n\t\t\t} else {\n\t\t\t\tlogger.WithCallDepth(depth + 3).Info(string(data))\n\t\t\t}\n\t\t}\n\t} else if l.toStderr {\n\t\t// When logging to stderr only, check if we should filter by severity.\n\t\t// This is controlled by the legacy_stderr_threshold_behavior flag.\n\t\tif l.legacyStderrThresholdBehavior {\n\t\t\t// Legacy behavior: always write to stderr, ignore stderrthreshold\n\t\t\tos.Stderr.Write(data)\n\t\t} else {\n\t\t\t// New behavior: honor stderrthreshold even when logtostderr=true\n\t\t\tif s >= l.stderrThreshold.get() {\n\t\t\t\tos.Stderr.Write(data)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Write to stderr if any of these conditions are met:\n\t\t// - alsoToStderr is set (legacy behavior)\n\t\t// - alsologtostderr is set and severity meets alsologtostderrthreshold\n\t\t// - alsologtostderr is not set and severity meets stderrThreshold\n\t\tif alsoToStderr ||\n\t\t\t(l.alsoToStderr && s >= l.alsologtostderrthreshold.get()) ||\n\t\t\t(!l.alsoToStderr && s >= l.stderrThreshold.get()) {\n\t\t\tos.Stderr.Write(data)\n\t\t}\n\n\t\tif logging.logFile != \"\" {\n\t\t\t// Since we are using a single log file, all of the items in l.file array\n\t\t\t// will point to the same file, so just use one of them to write data.\n\t\t\tif l.file[severity.InfoLog] == nil {\n\t\t\t\tif err := l.createFiles(severity.InfoLog); err != nil {\n\t\t\t\t\tos.Stderr.Write(data) // Make sure the message appears somewhere.\n\t\t\t\t\tl.exit(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_, _ = l.file[severity.InfoLog].Write(data)\n\t\t} else {\n\t\t\tif l.file[s] == nil {\n\t\t\t\tif err := l.createFiles(s); err != nil {\n\t\t\t\t\tos.Stderr.Write(data) // Make sure the message appears somewhere.\n\t\t\t\t\tl.exit(err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif l.oneOutput {\n\t\t\t\t_, _ = l.file[s].Write(data)\n\t\t\t} else {\n\t\t\t\tswitch s {\n\t\t\t\tcase severity.FatalLog:\n\t\t\t\t\t_, _ = l.file[severity.FatalLog].Write(data)\n\t\t\t\t\tfallthrough\n\t\t\t\tcase severity.ErrorLog:\n\t\t\t\t\t_, _ = l.file[severity.ErrorLog].Write(data)\n\t\t\t\t\tfallthrough\n\t\t\t\tcase severity.WarningLog:\n\t\t\t\t\t_, _ = l.file[severity.WarningLog].Write(data)\n\t\t\t\t\tfallthrough\n\t\t\t\tcase severity.InfoLog:\n\t\t\t\t\t_, _ = l.file[severity.InfoLog].Write(data)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif s == severity.FatalLog {\n\t\t// If we got here via Exit rather than Fatal, print no stacks.\n\t\tif atomic.LoadUint32(&fatalNoStacks) > 0 {\n\t\t\tl.mu.Unlock()\n\t\t\tisLocked = false\n\t\t\ttimeoutFlush(ExitFlushTimeout)\n\t\t\tOsExit(1)\n\t\t}\n\t\t// Dump all goroutine stacks before exiting.\n\t\t// First, make sure we see the trace for the current goroutine on standard error.\n\t\t// If -logtostderr has been specified, the loop below will do that anyway\n\t\t// as the first stack in the full dump.\n\t\tif !l.toStderr {\n\t\t\tos.Stderr.Write(dbg.Stacks(false))\n\t\t}\n\n\t\t// Write the stack trace for all goroutines to the files.\n\t\ttrace := dbg.Stacks(true)\n\t\tlogExitFunc = func(error) {} // If we get a write error, we'll still exit below.\n\t\tfor log := severity.FatalLog; log >= severity.InfoLog; log-- {\n\t\t\tif f := l.file[log]; f != nil { // Can be nil if -logtostderr is set.\n\t\t\t\t_, _ = f.Write(trace)\n\t\t\t}\n\t\t}\n\t\tl.mu.Unlock()\n\t\tisLocked = false\n\t\ttimeoutFlush(ExitFlushTimeout)\n\t\tOsExit(255) // C++ uses -1, which is silly because it's anded with 255 anyway.\n\t}\n\tbuffer.PutBuffer(buf)\n\n\tif stats := severityStats[s]; stats != nil {\n\t\tatomic.AddInt64(&stats.lines, 1)\n\t\tatomic.AddInt64(&stats.bytes, int64(len(data)))\n\t}\n}\n\n// logExitFunc provides a simple mechanism to override the default behavior\n// of exiting on error. Used in testing and to guarantee we reach a required exit\n// for fatal logs. Instead, exit could be a function rather than a method but that\n// would make its use clumsier.\nvar logExitFunc func(error)\n\n// exit is called if there is trouble creating or writing log files.\n// It flushes the logs and exits the program; there's no point in hanging around.\n// l.mu is held.\nfunc (l *loggingT) exit(err error) {\n\tfmt.Fprintf(os.Stderr, \"log: exiting because of error: %s\\n\", err)\n\t// If logExitFunc is set, we do that instead of exiting.\n\tif logExitFunc != nil {\n\t\tlogExitFunc(err)\n\t\treturn\n\t}\n\tneedToSync := l.flushAll()\n\tl.syncAll(needToSync)\n\tOsExit(2)\n}\n\n// syncBuffer joins a bufio.Writer to its underlying file, providing access to the\n// file's Sync method and providing a wrapper for the Write method that provides log\n// file rotation. There are conflicting methods, so the file cannot be embedded.\n// l.mu is held for all its methods.\ntype syncBuffer struct {\n\tlogger *loggingT\n\t*bufio.Writer\n\tfile     *os.File\n\tsev      severity.Severity\n\tnbytes   uint64 // The number of bytes written to this file\n\tmaxbytes uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up.\n}\n\n// CalculateMaxSize returns the real max size in bytes after considering the default max size and the flag options.\nfunc CalculateMaxSize() uint64 {\n\tif logging.logFile != \"\" {\n\t\tif logging.logFileMaxSizeMB == 0 {\n\t\t\t// If logFileMaxSizeMB is zero, we don't have limitations on the log size.\n\t\t\treturn math.MaxUint64\n\t\t}\n\t\t// Flag logFileMaxSizeMB is in MB for user convenience.\n\t\treturn logging.logFileMaxSizeMB * 1024 * 1024\n\t}\n\t// If \"log_file\" flag is not specified, the target file (sb.file) will be cleaned up when reaches a fixed size.\n\treturn MaxSize\n}\n\nfunc (sb *syncBuffer) Write(p []byte) (n int, err error) {\n\tif sb.nbytes+uint64(len(p)) >= sb.maxbytes {\n\t\tif err := sb.rotateFile(time.Now(), false); err != nil {\n\t\t\tsb.logger.exit(err)\n\t\t}\n\t}\n\tn, err = sb.Writer.Write(p)\n\tsb.nbytes += uint64(n)\n\tif err != nil {\n\t\tsb.logger.exit(err)\n\t}\n\treturn\n}\n\n// rotateFile closes the syncBuffer's file and starts a new one.\n// The startup argument indicates whether this is the initial startup of klog.\n// If startup is true, existing files are opened for appending instead of truncated.\nfunc (sb *syncBuffer) rotateFile(now time.Time, startup bool) error {\n\tif sb.file != nil {\n\t\tsb.Flush()\n\t\tsb.file.Close()\n\t}\n\tvar err error\n\tsb.file, _, err = create(severity.Name[sb.sev], now, startup)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif startup {\n\t\tfileInfo, err := sb.file.Stat()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"file stat could not get fileinfo: %v\", err)\n\t\t}\n\t\t// init file size\n\t\tsb.nbytes = uint64(fileInfo.Size())\n\t} else {\n\t\tsb.nbytes = 0\n\t}\n\tsb.Writer = bufio.NewWriterSize(sb.file, bufferSize)\n\n\tif sb.logger.skipLogHeaders {\n\t\treturn nil\n\t}\n\n\t// Write header.\n\tvar buf bytes.Buffer\n\tfmt.Fprintf(&buf, \"Log file created at: %s\\n\", now.Format(\"2006/01/02 15:04:05\"))\n\tfmt.Fprintf(&buf, \"Running on machine: %s\\n\", host)\n\tfmt.Fprintf(&buf, \"Binary: Built with %s %s for %s/%s\\n\", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH)\n\tfmt.Fprintf(&buf, \"Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\\n\")\n\tn, err := sb.file.Write(buf.Bytes())\n\tsb.nbytes += uint64(n)\n\treturn err\n}\n\n// bufferSize sizes the buffer associated with each log file. It's large\n// so that log records can accumulate without the logging thread blocking\n// on disk I/O. The flushDaemon will block instead.\nconst bufferSize = 256 * 1024\n\n// createFiles creates all the log files for severity from sev down to infoLog.\n// l.mu is held.\nfunc (l *loggingT) createFiles(sev severity.Severity) error {\n\tinterval := l.flushInterval\n\tif interval == 0 {\n\t\tinterval = flushInterval\n\t}\n\tl.flushD.run(interval)\n\tnow := time.Now()\n\t// Files are created in decreasing severity order, so as soon as we find one\n\t// has already been created, we can stop.\n\tfor s := sev; s >= severity.InfoLog && l.file[s] == nil; s-- {\n\t\tsb := &syncBuffer{\n\t\t\tlogger:   l,\n\t\t\tsev:      s,\n\t\t\tmaxbytes: CalculateMaxSize(),\n\t\t}\n\t\tif err := sb.rotateFile(now, true); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tl.file[s] = sb\n\t}\n\treturn nil\n}\n\nconst flushInterval = 5 * time.Second\n\n// flushDaemon periodically flushes the log file buffers.\ntype flushDaemon struct {\n\tmu       sync.Mutex\n\tclock    clock.Clock\n\tflush    func()\n\tstopC    chan struct{}\n\tstopDone chan struct{}\n}\n\n// newFlushDaemon returns a new flushDaemon. If the passed clock is nil, a\n// clock.RealClock is used.\nfunc newFlushDaemon(flush func(), tickClock clock.Clock) *flushDaemon {\n\tif tickClock == nil {\n\t\ttickClock = clock.RealClock{}\n\t}\n\treturn &flushDaemon{\n\t\tflush: flush,\n\t\tclock: tickClock,\n\t}\n}\n\n// run starts a goroutine that periodically calls the daemons flush function.\n// Calling run on an already running daemon will have no effect.\nfunc (f *flushDaemon) run(interval time.Duration) {\n\tf.mu.Lock()\n\tdefer f.mu.Unlock()\n\n\tif f.stopC != nil { // daemon already running\n\t\treturn\n\t}\n\n\tf.stopC = make(chan struct{}, 1)\n\tf.stopDone = make(chan struct{}, 1)\n\n\tticker := f.clock.NewTicker(interval)\n\tgo func() {\n\t\tdefer ticker.Stop()\n\t\tdefer func() { f.stopDone <- struct{}{} }()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C():\n\t\t\t\tf.flush()\n\t\t\tcase <-f.stopC:\n\t\t\t\tf.flush()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// stop stops the running flushDaemon and waits until the daemon has shut down.\n// Calling stop on a daemon that isn't running will have no effect.\nfunc (f *flushDaemon) stop() {\n\tf.mu.Lock()\n\tdefer f.mu.Unlock()\n\n\tif f.stopC == nil { // daemon not running\n\t\treturn\n\t}\n\n\tf.stopC <- struct{}{}\n\t<-f.stopDone\n\n\tf.stopC = nil\n\tf.stopDone = nil\n}\n\n// isRunning returns true if the flush daemon is running.\nfunc (f *flushDaemon) isRunning() bool {\n\tf.mu.Lock()\n\tdefer f.mu.Unlock()\n\treturn f.stopC != nil\n}\n\n// StopFlushDaemon stops the flush daemon, if running, and flushes once.\n// This prevents klog from leaking goroutines on shutdown. After stopping\n// the daemon, you can still manually flush buffers again by calling Flush().\nfunc StopFlushDaemon() {\n\tlogging.flushD.stop()\n}\n\n// StartFlushDaemon ensures that the flush daemon runs with the given delay\n// between flush calls. If it is already running, it gets restarted.\nfunc StartFlushDaemon(interval time.Duration) {\n\tStopFlushDaemon()\n\tlogging.flushD.run(interval)\n}\n\n// lockAndFlushAll is like flushAll but locks l.mu first.\nfunc (l *loggingT) lockAndFlushAll() {\n\tl.mu.Lock()\n\tneedToSync := l.flushAll()\n\tl.mu.Unlock()\n\t// Some environments are slow when syncing and holding the lock might cause contention.\n\tl.syncAll(needToSync)\n}\n\n// flushAll flushes all the logs\n// l.mu is held.\n//\n// The result is the number of files which need to be synced and the pointers to them.\nfunc (l *loggingT) flushAll() fileArray {\n\tvar needToSync fileArray\n\n\t// Flush from fatal down, in case there's trouble flushing.\n\tfor s := severity.FatalLog; s >= severity.InfoLog; s-- {\n\t\tfile := l.file[s]\n\t\tif sb, ok := file.(*syncBuffer); ok && sb.file != nil {\n\t\t\t_ = sb.Flush() // ignore error\n\t\t\tneedToSync.files[needToSync.num] = sb.file\n\t\t\tneedToSync.num++\n\t\t}\n\t}\n\tif logging.loggerOptions.flush != nil {\n\t\tlogging.loggerOptions.flush()\n\t}\n\treturn needToSync\n}\n\ntype fileArray struct {\n\tnum   int\n\tfiles [severity.NumSeverity]*os.File\n}\n\n// syncAll attempts to \"sync\" their data to disk.\nfunc (l *loggingT) syncAll(needToSync fileArray) {\n\t// Flush from fatal down, in case there's trouble flushing.\n\tfor i := 0; i < needToSync.num; i++ {\n\t\t_ = needToSync.files[i].Sync() // ignore error\n\t}\n}\n\n// CopyStandardLogTo arranges for messages written to the Go \"log\" package's\n// default logs to also appear in the Google logs for the named and lower\n// severities.  Subsequent changes to the standard log's default output location\n// or format may break this behavior.\n//\n// Valid names are \"INFO\", \"WARNING\", \"ERROR\", and \"FATAL\".  If the name is not\n// recognized, CopyStandardLogTo panics.\nfunc CopyStandardLogTo(name string) {\n\tsev, ok := severity.ByName(name)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"log.CopyStandardLogTo(%q): unrecognized severity name\", name))\n\t}\n\t// Set a log format that captures the user's file and line:\n\t//   d.go:23: message\n\tstdLog.SetFlags(stdLog.Lshortfile)\n\tstdLog.SetOutput(logBridge(sev))\n}\n\n// NewStandardLogger returns a Logger that writes to the klog logs for the\n// named and lower severities.\n//\n// Valid names are \"INFO\", \"WARNING\", \"ERROR\", and \"FATAL\". If the name is not\n// recognized, NewStandardLogger panics.\nfunc NewStandardLogger(name string) *stdLog.Logger {\n\tsev, ok := severity.ByName(name)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"klog.NewStandardLogger(%q): unknown severity\", name))\n\t}\n\treturn stdLog.New(logBridge(sev), \"\", stdLog.Lshortfile)\n}\n\n// logBridge provides the Write method that enables CopyStandardLogTo to connect\n// Go's standard logs to the logs provided by this package.\ntype logBridge severity.Severity\n\n// Write parses the standard logging line and passes its components to the\n// logger for severity(lb).\nfunc (lb logBridge) Write(b []byte) (n int, err error) {\n\tvar (\n\t\tfile = \"???\"\n\t\tline = 1\n\t\ttext string\n\t)\n\t// Split \"d.go:23: message\" into \"d.go\", \"23\", and \"message\".\n\tif parts := bytes.SplitN(b, []byte{':'}, 3); len(parts) != 3 || len(parts[0]) < 1 || len(parts[2]) < 1 {\n\t\ttext = fmt.Sprintf(\"bad log format: %s\", b)\n\t} else {\n\t\tfile = string(parts[0])\n\t\ttext = string(parts[2][1:]) // skip leading space\n\t\tline, err = strconv.Atoi(string(parts[1]))\n\t\tif err != nil {\n\t\t\ttext = fmt.Sprintf(\"bad line number: %s\", b)\n\t\t\tline = 1\n\t\t}\n\t}\n\t// printWithFileLine with alsoToStderr=true, so standard log messages\n\t// always appear on standard error.\n\tlogging.printWithFileLine(severity.Severity(lb), logging.logger, logging.filter, file, line, true, text)\n\treturn len(b), nil\n}\n\n// setV computes and remembers the V level for a given PC\n// when vmodule is enabled.\n// File pattern matching takes the basename of the file, stripped\n// of its .go suffix, and uses filepath.Match, which is a little more\n// general than the *? matching used in C++.\n// l.mu is held.\nfunc (l *loggingT) setV(pc uintptr) Level {\n\tfn := runtime.FuncForPC(pc)\n\tfile, _ := fn.FileLine(pc)\n\t// The file is something like /a/b/c/d.go. We want just the d.\n\tfile = strings.TrimSuffix(file, \".go\")\n\tif slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\tfile = file[slash+1:]\n\t}\n\tfor _, filter := range l.vmodule.filter {\n\t\tif filter.match(file) {\n\t\t\tl.vmap[pc] = filter.level\n\t\t\treturn filter.level\n\t\t}\n\t}\n\tl.vmap[pc] = 0\n\treturn 0\n}\n\n// Verbose is a boolean type that implements Infof (like Printf) etc.\n// See the documentation of V for more information.\ntype Verbose struct {\n\tenabled bool\n\tlogger  *logWriter\n}\n\nfunc newVerbose(level Level, b bool) Verbose {\n\tif logging.logger == nil {\n\t\treturn Verbose{b, nil}\n\t}\n\tv := logging.logger.V(int(level))\n\treturn Verbose{b, &logWriter{Logger: v, writeKlogBuffer: logging.loggerOptions.writeKlogBuffer}}\n}\n\n// V reports whether verbosity at the call site is at least the requested level.\n// The returned value is a struct of type Verbose, which implements Info, Infoln\n// and Infof. These methods will write to the Info log if called.\n// Thus, one may write either\n//\n//\tif klog.V(2).Enabled() { klog.Info(\"log this\") }\n//\n// or\n//\n//\tklog.V(2).Info(\"log this\")\n//\n// The second form is shorter but the first is cheaper if logging is off because it does\n// not evaluate its arguments.\n//\n// Whether an individual call to V generates a log record depends on the setting of\n// the -v and -vmodule flags; both are off by default. The V call will log if its level\n// is less than or equal to the value of the -v flag, or alternatively if its level is\n// less than or equal to the value of the -vmodule pattern matching the source file\n// containing the call.\nfunc V(level Level) Verbose {\n\treturn VDepth(1, level)\n}\n\n// VDepth is a variant of V that accepts a number of stack frames that will be\n// skipped when checking the -vmodule patterns. VDepth(0) is equivalent to\n// V().\nfunc VDepth(depth int, level Level) Verbose {\n\t// This function tries hard to be cheap unless there's work to do.\n\t// The fast path is two atomic loads and compares.\n\n\t// Here is a cheap but safe test to see if V logging is enabled globally.\n\tif logging.verbosity.get() >= level {\n\t\treturn newVerbose(level, true)\n\t}\n\n\t// It's off globally but vmodule may still be set.\n\t// Here is another cheap but safe test to see if vmodule is enabled.\n\tif atomic.LoadInt32(&logging.filterLength) > 0 {\n\t\t// Now we need a proper lock to use the logging structure. The pcs field\n\t\t// is shared so we must lock before accessing it. This is fairly expensive,\n\t\t// but if V logging is enabled we're slow anyway.\n\t\tlogging.mu.Lock()\n\t\tdefer logging.mu.Unlock()\n\t\tif runtime.Callers(2+depth, logging.pcs[:]) == 0 {\n\t\t\treturn newVerbose(level, false)\n\t\t}\n\t\t// runtime.Callers returns \"return PCs\", but we want\n\t\t// to look up the symbolic information for the call,\n\t\t// so subtract 1 from the PC. runtime.CallersFrames\n\t\t// would be cleaner, but allocates.\n\t\tpc := logging.pcs[0] - 1\n\t\tv, ok := logging.vmap[pc]\n\t\tif !ok {\n\t\t\tv = logging.setV(pc)\n\t\t}\n\t\treturn newVerbose(level, v >= level)\n\t}\n\treturn newVerbose(level, false)\n}\n\n// Enabled will return true if this log level is enabled, guarded by the value\n// of v.\n// See the documentation of V for usage.\nfunc (v Verbose) Enabled() bool {\n\treturn v.enabled\n}\n\n// Info is equivalent to the global Info function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) Info(args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.print(severity.InfoLog, v.logger, logging.filter, args...)\n\t}\n}\n\n// InfoDepth is equivalent to the global InfoDepth function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) InfoDepth(depth int, args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.printDepth(severity.InfoLog, v.logger, logging.filter, depth, args...)\n\t}\n}\n\n// Infoln is equivalent to the global Infoln function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) Infoln(args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.println(severity.InfoLog, v.logger, logging.filter, args...)\n\t}\n}\n\n// InfolnDepth is equivalent to the global InfolnDepth function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) InfolnDepth(depth int, args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.printlnDepth(severity.InfoLog, v.logger, logging.filter, depth, args...)\n\t}\n}\n\n// Infof is equivalent to the global Infof function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) Infof(format string, args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.printf(severity.InfoLog, v.logger, logging.filter, format, args...)\n\t}\n}\n\n// InfofDepth is equivalent to the global InfofDepth function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) InfofDepth(depth int, format string, args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.printfDepth(severity.InfoLog, v.logger, logging.filter, depth, format, args...)\n\t}\n}\n\n// InfoS is equivalent to the global InfoS function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) InfoS(msg string, keysAndValues ...interface{}) {\n\tif v.enabled {\n\t\tlogging.infoS(v.logger, logging.filter, 0, msg, keysAndValues...)\n\t}\n}\n\n// InfoSDepth acts as InfoS but uses depth to determine which call frame to log.\n// InfoSDepth(0, \"msg\") is the same as InfoS(\"msg\").\nfunc InfoSDepth(depth int, msg string, keysAndValues ...interface{}) {\n\tlogging.infoS(logging.logger, logging.filter, depth, msg, keysAndValues...)\n}\n\n// InfoSDepth is equivalent to the global InfoSDepth function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) InfoSDepth(depth int, msg string, keysAndValues ...interface{}) {\n\tif v.enabled {\n\t\tlogging.infoS(v.logger, logging.filter, depth, msg, keysAndValues...)\n\t}\n}\n\n// Deprecated: Use ErrorS instead.\nfunc (v Verbose) Error(err error, msg string, args ...interface{}) {\n\tif v.enabled {\n\t\tlogging.errorS(err, v.logger, logging.filter, 0, msg, args...)\n\t}\n}\n\n// ErrorS is equivalent to the global Error function, guarded by the value of v.\n// See the documentation of V for usage.\nfunc (v Verbose) ErrorS(err error, msg string, keysAndValues ...interface{}) {\n\tif v.enabled {\n\t\tlogging.errorS(err, v.logger, logging.filter, 0, msg, keysAndValues...)\n\t}\n}\n\n// Info logs to the INFO log.\n// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.\nfunc Info(args ...interface{}) {\n\tlogging.print(severity.InfoLog, logging.logger, logging.filter, args...)\n}\n\n// InfoDepth acts as Info but uses depth to determine which call frame to log.\n// InfoDepth(0, \"msg\") is the same as Info(\"msg\").\nfunc InfoDepth(depth int, args ...interface{}) {\n\tlogging.printDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Infoln logs to the INFO log.\n// Arguments are handled in the manner of fmt.Println; a newline is always appended.\nfunc Infoln(args ...interface{}) {\n\tlogging.println(severity.InfoLog, logging.logger, logging.filter, args...)\n}\n\n// InfolnDepth acts as Infoln but uses depth to determine which call frame to log.\n// InfolnDepth(0, \"msg\") is the same as Infoln(\"msg\").\nfunc InfolnDepth(depth int, args ...interface{}) {\n\tlogging.printlnDepth(severity.InfoLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Infof logs to the INFO log.\n// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.\nfunc Infof(format string, args ...interface{}) {\n\tlogging.printf(severity.InfoLog, logging.logger, logging.filter, format, args...)\n}\n\n// InfofDepth acts as Infof but uses depth to determine which call frame to log.\n// InfofDepth(0, \"msg\", args...) is the same as Infof(\"msg\", args...).\nfunc InfofDepth(depth int, format string, args ...interface{}) {\n\tlogging.printfDepth(severity.InfoLog, logging.logger, logging.filter, depth, format, args...)\n}\n\n// InfoS structured logs to the INFO log.\n// The msg argument used to add constant description to the log line.\n// The key/value pairs would be join by \"=\" ; a newline is always appended.\n//\n// Basic examples:\n// >> klog.InfoS(\"Pod status updated\", \"pod\", \"kubedns\", \"status\", \"ready\")\n// output:\n// >> I1025 00:15:15.525108       1 controller_utils.go:116] \"Pod status updated\" pod=\"kubedns\" status=\"ready\"\nfunc InfoS(msg string, keysAndValues ...interface{}) {\n\tlogging.infoS(logging.logger, logging.filter, 0, msg, keysAndValues...)\n}\n\n// Warning logs to the WARNING and INFO logs.\n// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.\nfunc Warning(args ...interface{}) {\n\tlogging.print(severity.WarningLog, logging.logger, logging.filter, args...)\n}\n\n// WarningDepth acts as Warning but uses depth to determine which call frame to log.\n// WarningDepth(0, \"msg\") is the same as Warning(\"msg\").\nfunc WarningDepth(depth int, args ...interface{}) {\n\tlogging.printDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Warningln logs to the WARNING and INFO logs.\n// Arguments are handled in the manner of fmt.Println; a newline is always appended.\nfunc Warningln(args ...interface{}) {\n\tlogging.println(severity.WarningLog, logging.logger, logging.filter, args...)\n}\n\n// WarninglnDepth acts as Warningln but uses depth to determine which call frame to log.\n// WarninglnDepth(0, \"msg\") is the same as Warningln(\"msg\").\nfunc WarninglnDepth(depth int, args ...interface{}) {\n\tlogging.printlnDepth(severity.WarningLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Warningf logs to the WARNING and INFO logs.\n// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.\nfunc Warningf(format string, args ...interface{}) {\n\tlogging.printf(severity.WarningLog, logging.logger, logging.filter, format, args...)\n}\n\n// WarningfDepth acts as Warningf but uses depth to determine which call frame to log.\n// WarningfDepth(0, \"msg\", args...) is the same as Warningf(\"msg\", args...).\nfunc WarningfDepth(depth int, format string, args ...interface{}) {\n\tlogging.printfDepth(severity.WarningLog, logging.logger, logging.filter, depth, format, args...)\n}\n\n// Error logs to the ERROR, WARNING, and INFO logs.\n// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.\nfunc Error(args ...interface{}) {\n\tlogging.print(severity.ErrorLog, logging.logger, logging.filter, args...)\n}\n\n// ErrorDepth acts as Error but uses depth to determine which call frame to log.\n// ErrorDepth(0, \"msg\") is the same as Error(\"msg\").\nfunc ErrorDepth(depth int, args ...interface{}) {\n\tlogging.printDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Errorln logs to the ERROR, WARNING, and INFO logs.\n// Arguments are handled in the manner of fmt.Println; a newline is always appended.\nfunc Errorln(args ...interface{}) {\n\tlogging.println(severity.ErrorLog, logging.logger, logging.filter, args...)\n}\n\n// ErrorlnDepth acts as Errorln but uses depth to determine which call frame to log.\n// ErrorlnDepth(0, \"msg\") is the same as Errorln(\"msg\").\nfunc ErrorlnDepth(depth int, args ...interface{}) {\n\tlogging.printlnDepth(severity.ErrorLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Errorf logs to the ERROR, WARNING, and INFO logs.\n// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.\nfunc Errorf(format string, args ...interface{}) {\n\tlogging.printf(severity.ErrorLog, logging.logger, logging.filter, format, args...)\n}\n\n// ErrorfDepth acts as Errorf but uses depth to determine which call frame to log.\n// ErrorfDepth(0, \"msg\", args...) is the same as Errorf(\"msg\", args...).\nfunc ErrorfDepth(depth int, format string, args ...interface{}) {\n\tlogging.printfDepth(severity.ErrorLog, logging.logger, logging.filter, depth, format, args...)\n}\n\n// ErrorS structured logs to the ERROR, WARNING, and INFO logs.\n// the err argument used as \"err\" field of log line.\n// The msg argument used to add constant description to the log line.\n// The key/value pairs would be join by \"=\" ; a newline is always appended.\n//\n// Basic examples:\n// >> klog.ErrorS(err, \"Failed to update pod status\")\n// output:\n// >> E1025 00:15:15.525108       1 controller_utils.go:114] \"Failed to update pod status\" err=\"timeout\"\nfunc ErrorS(err error, msg string, keysAndValues ...interface{}) {\n\tlogging.errorS(err, logging.logger, logging.filter, 0, msg, keysAndValues...)\n}\n\n// ErrorSDepth acts as ErrorS but uses depth to determine which call frame to log.\n// ErrorSDepth(0, \"msg\") is the same as ErrorS(\"msg\").\nfunc ErrorSDepth(depth int, err error, msg string, keysAndValues ...interface{}) {\n\tlogging.errorS(err, logging.logger, logging.filter, depth, msg, keysAndValues...)\n}\n\n// Fatal logs to the FATAL, ERROR, WARNING, and INFO logs,\n// prints stack trace(s), then calls OsExit(255).\n//\n// Stderr only receives a dump of the current goroutine's stack trace. Log files,\n// if there are any, receive a dump of the stack traces in all goroutines.\n//\n// Callers who want more control over handling of fatal events may instead use a\n// combination of different functions:\n//   - some info or error logging function, optionally with a stack trace\n//     value generated by github.com/go-logr/lib/dbg.Backtrace\n//   - Flush to flush pending log data\n//   - panic, os.Exit or returning to the caller with an error\n//\n// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.\nfunc Fatal(args ...interface{}) {\n\tlogging.print(severity.FatalLog, logging.logger, logging.filter, args...)\n}\n\n// FatalDepth acts as Fatal but uses depth to determine which call frame to log.\n// FatalDepth(0, \"msg\") is the same as Fatal(\"msg\").\nfunc FatalDepth(depth int, args ...interface{}) {\n\tlogging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs,\n// including a stack trace of all running goroutines, then calls OsExit(255).\n// Arguments are handled in the manner of fmt.Println; a newline is always appended.\nfunc Fatalln(args ...interface{}) {\n\tlogging.println(severity.FatalLog, logging.logger, logging.filter, args...)\n}\n\n// FatallnDepth acts as Fatalln but uses depth to determine which call frame to log.\n// FatallnDepth(0, \"msg\") is the same as Fatalln(\"msg\").\nfunc FatallnDepth(depth int, args ...interface{}) {\n\tlogging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Fatalf logs to the FATAL, ERROR, WARNING, and INFO logs,\n// including a stack trace of all running goroutines, then calls OsExit(255).\n// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.\nfunc Fatalf(format string, args ...interface{}) {\n\tlogging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...)\n}\n\n// FatalfDepth acts as Fatalf but uses depth to determine which call frame to log.\n// FatalfDepth(0, \"msg\", args...) is the same as Fatalf(\"msg\", args...).\nfunc FatalfDepth(depth int, format string, args ...interface{}) {\n\tlogging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...)\n}\n\n// fatalNoStacks is non-zero if we are to exit without dumping goroutine stacks.\n// It allows Exit and relatives to use the Fatal logs.\nvar fatalNoStacks uint32\n\n// Exit logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).\n// Arguments are handled in the manner of fmt.Print; a newline is appended if missing.\nfunc Exit(args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.print(severity.FatalLog, logging.logger, logging.filter, args...)\n}\n\n// ExitDepth acts as Exit but uses depth to determine which call frame to log.\n// ExitDepth(0, \"msg\") is the same as Exit(\"msg\").\nfunc ExitDepth(depth int, args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.printDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Exitln logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).\nfunc Exitln(args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.println(severity.FatalLog, logging.logger, logging.filter, args...)\n}\n\n// ExitlnDepth acts as Exitln but uses depth to determine which call frame to log.\n// ExitlnDepth(0, \"msg\") is the same as Exitln(\"msg\").\nfunc ExitlnDepth(depth int, args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.printlnDepth(severity.FatalLog, logging.logger, logging.filter, depth, args...)\n}\n\n// Exitf logs to the FATAL, ERROR, WARNING, and INFO logs, then calls OsExit(1).\n// Arguments are handled in the manner of fmt.Printf; a newline is appended if missing.\nfunc Exitf(format string, args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.printf(severity.FatalLog, logging.logger, logging.filter, format, args...)\n}\n\n// ExitfDepth acts as Exitf but uses depth to determine which call frame to log.\n// ExitfDepth(0, \"msg\", args...) is the same as Exitf(\"msg\", args...).\nfunc ExitfDepth(depth int, format string, args ...interface{}) {\n\tatomic.StoreUint32(&fatalNoStacks, 1)\n\tlogging.printfDepth(severity.FatalLog, logging.logger, logging.filter, depth, format, args...)\n}\n\n// LogFilter is a collection of functions that can filter all logging calls,\n// e.g. for sanitization of arguments and prevent accidental leaking of secrets.\ntype LogFilter interface {\n\tFilter(args []interface{}) []interface{}\n\tFilterF(format string, args []interface{}) (string, []interface{})\n\tFilterS(msg string, keysAndValues []interface{}) (string, []interface{})\n}\n\n// SetLogFilter installs a filter that is used for all log calls.\n//\n// Modifying the filter is not thread-safe and should be done while no other\n// goroutines invoke log calls, usually during program initialization.\nfunc SetLogFilter(filter LogFilter) {\n\tlogging.filter = filter\n}\n"
  },
  {
    "path": "klog_file.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2013 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// File I/O for logs.\n\npackage klog\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\n// MaxSize is the maximum size of a log file in bytes.\nvar MaxSize uint64 = 1024 * 1024 * 1800\n\n// logDirs lists the candidate directories for new log files.\nvar logDirs []string\n\nfunc createLogDirs() {\n\tif logging.logDir != \"\" {\n\t\tlogDirs = append(logDirs, logging.logDir)\n\t}\n\tlogDirs = append(logDirs, os.TempDir())\n}\n\nvar (\n\tpid          = os.Getpid()\n\tprogram      = filepath.Base(os.Args[0])\n\thost         = \"unknownhost\"\n\tuserName     = \"unknownuser\"\n\tuserNameOnce sync.Once\n)\n\nfunc init() {\n\tif h, err := os.Hostname(); err == nil {\n\t\thost = shortHostname(h)\n\t}\n}\n\n// shortHostname returns its argument, truncating at the first period.\n// For instance, given \"www.google.com\" it returns \"www\".\nfunc shortHostname(hostname string) string {\n\tif i := strings.Index(hostname, \".\"); i >= 0 {\n\t\treturn hostname[:i]\n\t}\n\treturn hostname\n}\n\n// logName returns a new log file name containing tag, with start time t, and\n// the name for the symlink for tag.\nfunc logName(tag string, t time.Time) (name, link string) {\n\tname = fmt.Sprintf(\"%s.%s.%s.log.%s.%04d%02d%02d-%02d%02d%02d.%d\",\n\t\tprogram,\n\t\thost,\n\t\tgetUserName(),\n\t\ttag,\n\t\tt.Year(),\n\t\tt.Month(),\n\t\tt.Day(),\n\t\tt.Hour(),\n\t\tt.Minute(),\n\t\tt.Second(),\n\t\tpid)\n\treturn name, program + \".\" + tag\n}\n\nvar onceLogDirs sync.Once\n\n// create creates a new log file and returns the file and its filename, which\n// contains tag (\"INFO\", \"FATAL\", etc.) and t.  If the file is created\n// successfully, create also attempts to update the symlink for that tag, ignoring\n// errors.\n// The startup argument indicates whether this is the initial startup of klog.\n// If startup is true, existing files are opened for appending instead of truncated.\nfunc create(tag string, t time.Time, startup bool) (f *os.File, filename string, err error) {\n\tif logging.logFile != \"\" {\n\t\tf, err := openOrCreate(logging.logFile, startup)\n\t\tif err == nil {\n\t\t\treturn f, logging.logFile, nil\n\t\t}\n\t\treturn nil, \"\", fmt.Errorf(\"log: unable to create log: %v\", err)\n\t}\n\tonceLogDirs.Do(createLogDirs)\n\tif len(logDirs) == 0 {\n\t\treturn nil, \"\", errors.New(\"log: no log dirs\")\n\t}\n\tname, link := logName(tag, t)\n\tvar lastErr error\n\tfor _, dir := range logDirs {\n\t\tfname := filepath.Join(dir, name)\n\t\tf, err := openOrCreate(fname, startup)\n\t\tif err == nil {\n\t\t\tsymlink := filepath.Join(dir, link)\n\t\t\t_ = os.Remove(symlink)        // ignore err\n\t\t\t_ = os.Symlink(name, symlink) // ignore err\n\t\t\treturn f, fname, nil\n\t\t}\n\t\tlastErr = err\n\t}\n\treturn nil, \"\", fmt.Errorf(\"log: cannot create log: %v\", lastErr)\n}\n\n// The startup argument indicates whether this is the initial startup of klog.\n// If startup is true, existing files are opened for appending instead of truncated.\nfunc openOrCreate(name string, startup bool) (*os.File, error) {\n\tif startup {\n\t\tf, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\t\treturn f, err\n\t}\n\tf, err := os.Create(name)\n\treturn f, err\n}\n"
  },
  {
    "path": "klog_file_others.go",
    "content": "//go:build !windows\n// +build !windows\n\npackage klog\n\nimport (\n\t\"os/user\"\n)\n\nfunc getUserName() string {\n\tuserNameOnce.Do(func() {\n\t\tcurrent, err := user.Current()\n\t\tif err == nil {\n\t\t\tuserName = current.Username\n\t\t}\n\t})\n\n\treturn userName\n}\n"
  },
  {
    "path": "klog_file_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage klog\n\nimport (\n\t\"os\"\n\t\"strings\"\n)\n\nfunc getUserName() string {\n\tuserNameOnce.Do(func() {\n\t\t// On Windows, the Go 'user' package requires netapi32.dll.\n\t\t// This affects Windows Nano Server:\n\t\t//   https://github.com/golang/go/issues/21867\n\t\t// Fallback to using environment variables.\n\t\tu := os.Getenv(\"USERNAME\")\n\t\tif len(u) == 0 {\n\t\t\treturn\n\t\t}\n\t\t// Sanitize the USERNAME since it may contain filepath separators.\n\t\tu = strings.Replace(u, `\\`, \"_\", -1)\n\n\t\t// user.Current().Username normally produces something like 'USERDOMAIN\\USERNAME'\n\t\td := os.Getenv(\"USERDOMAIN\")\n\t\tif len(d) != 0 {\n\t\t\tuserName = d + \"_\" + u\n\t\t} else {\n\t\t\tuserName = u\n\t\t}\n\t})\n\n\treturn userName\n}\n"
  },
  {
    "path": "klog_test.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2013 Google Inc. All Rights Reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage klog\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\tstdLog \"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/internal/buffer\"\n\ttestingclock \"k8s.io/klog/v2/internal/clock/testing\"\n\t\"k8s.io/klog/v2/internal/severity\"\n\t\"k8s.io/klog/v2/internal/test\"\n\t\"k8s.io/klog/v2/internal/test/require\"\n)\n\n// TODO: This test package should be refactored so that tests cannot\n// interfere with each-other.\n\n// Test that shortHostname works as advertised.\nfunc TestShortHostname(t *testing.T) {\n\tfor hostname, expect := range map[string]string{\n\t\t\"\":                \"\",\n\t\t\"host\":            \"host\",\n\t\t\"host.google.com\": \"host\",\n\t} {\n\t\tif got := shortHostname(hostname); expect != got {\n\t\t\tt.Errorf(\"shortHostname(%q): expected %q, got %q\", hostname, expect, got)\n\t\t}\n\t}\n}\n\n// flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.\ntype flushBuffer struct {\n\tbytes.Buffer\n}\n\nfunc (f *flushBuffer) Flush() error {\n\treturn nil\n}\n\nfunc (f *flushBuffer) Sync() error {\n\treturn nil\n}\n\n// swap sets the log writers and returns the old array.\nfunc (l *loggingT) swap(writers [severity.NumSeverity]io.Writer) (old [severity.NumSeverity]io.Writer) {\n\tl.mu.Lock()\n\tdefer l.mu.Unlock()\n\told = l.file\n\tlogging.file = writers\n\treturn\n}\n\n// newBuffers sets the log writers to all new byte buffers and returns the old array.\nfunc (l *loggingT) newBuffers() [severity.NumSeverity]io.Writer {\n\treturn l.swap([severity.NumSeverity]io.Writer{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})\n}\n\n// contents returns the specified log value as a string.\nfunc contents(s severity.Severity) string {\n\treturn logging.file[s].(*flushBuffer).String()\n}\n\n// contains reports whether the string is contained in the log.\nfunc contains(s severity.Severity, str string) bool {\n\treturn strings.Contains(contents(s), str)\n}\n\n// setFlags configures the logging flags how the test expects them.\nfunc setFlags() {\n\tlogging.toStderr = false\n\tlogging.addDirHeader = false\n}\n\n// Test that Info works as advertised.\nfunc TestInfo(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tInfo(\"test\")\n\tif !contains(severity.InfoLog, \"I\") {\n\t\tt.Errorf(\"Info has wrong character: %q\", contents(severity.InfoLog))\n\t}\n\tif !contains(severity.InfoLog, \"test\") {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\nfunc TestInfoDepth(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\n\tf := func() { InfoDepth(1, \"depth-test1\") }\n\n\t// The next three lines must stay together\n\t_, _, wantLine, _ := runtime.Caller(0)\n\tInfoDepth(0, \"depth-test0\")\n\tf()\n\n\tmsgs := strings.Split(strings.TrimSuffix(contents(severity.InfoLog), \"\\n\"), \"\\n\")\n\tif len(msgs) != 2 {\n\t\tt.Fatalf(\"Got %d lines, expected 2\", len(msgs))\n\t}\n\n\tfor i, m := range msgs {\n\t\tif !strings.HasPrefix(m, \"I\") {\n\t\t\tt.Errorf(\"InfoDepth[%d] has wrong character: %q\", i, m)\n\t\t}\n\t\tw := fmt.Sprintf(\"depth-test%d\", i)\n\t\tif !strings.Contains(m, w) {\n\t\t\tt.Errorf(\"InfoDepth[%d] missing %q: %q\", i, w, m)\n\t\t}\n\n\t\t// pull out the line number (between : and ])\n\t\tmsg := m[strings.LastIndex(m, \":\")+1:]\n\t\tx := strings.Index(msg, \"]\")\n\t\tif x < 0 {\n\t\t\tt.Errorf(\"InfoDepth[%d]: missing ']': %q\", i, m)\n\t\t\tcontinue\n\t\t}\n\t\tline, err := strconv.Atoi(msg[:x])\n\t\tif err != nil {\n\t\t\tt.Errorf(\"InfoDepth[%d]: bad line number: %q\", i, m)\n\t\t\tcontinue\n\t\t}\n\t\twantLine++\n\t\tif wantLine != line {\n\t\t\tt.Errorf(\"InfoDepth[%d]: got line %d, want %d\", i, line, wantLine)\n\t\t}\n\t}\n}\n\nfunc init() {\n\tCopyStandardLogTo(\"INFO\")\n}\n\n// Test that CopyStandardLogTo panics on bad input.\nfunc TestCopyStandardLogToPanic(t *testing.T) {\n\tdefer func() {\n\t\tif s, ok := recover().(string); !ok || !strings.Contains(s, \"LOG\") {\n\t\t\tt.Errorf(`CopyStandardLogTo(\"LOG\") should have panicked: %v`, s)\n\t\t}\n\t}()\n\tCopyStandardLogTo(\"LOG\")\n}\n\n// Test that using the standard log package logs to INFO.\nfunc TestStandardLog(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tstdLog.Print(\"test\")\n\tif !contains(severity.InfoLog, \"I\") {\n\t\tt.Errorf(\"Info has wrong character: %q\", contents(severity.InfoLog))\n\t}\n\tif !contains(severity.InfoLog, \"test\") {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that the header has the correct format.\nfunc TestHeader(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tdefer func(previous func() time.Time) { timeNow = previous }(timeNow)\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)\n\t}\n\tbuffer.Pid = 1234\n\tInfo(\"test\")\n\tvar line int\n\tformat := \"I0102 15:04:05.067890    1234 klog_test.go:%d] test\\n\"\n\tn, err := fmt.Sscanf(contents(severity.InfoLog), format, &line)\n\tif n != 1 || err != nil {\n\t\tt.Errorf(\"log format error: %d elements, error %s:\\n%s\", n, err, contents(severity.InfoLog))\n\t}\n\t// Scanf treats multiple spaces as equivalent to a single space,\n\t// so check for correct space-padding also.\n\twant := fmt.Sprintf(format, line)\n\tif contents(severity.InfoLog) != want {\n\t\tt.Errorf(\"log format error: got:\\n\\t%q\\nwant:\\t%q\", contents(severity.InfoLog), want)\n\t}\n}\n\nfunc TestHeaderWithDir(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tlogging.addDirHeader = true\n\tdefer logging.swap(logging.newBuffers())\n\tdefer func(previous func() time.Time) { timeNow = previous }(timeNow)\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)\n\t}\n\tpid = 1234\n\tInfo(\"test\")\n\tre := regexp.MustCompile(`I0102 15:04:05.067890    1234 (klog|v2)/klog_test.go:(\\d+)] test\\n`)\n\tif !re.MatchString(contents(severity.InfoLog)) {\n\t\tt.Errorf(\"log format error: line does not match regex:\\n\\t%q\\n\", contents(severity.InfoLog))\n\t}\n}\n\n// Test that an Error log goes to Warning and Info.\n// Even in the Info log, the source character will be E, so the data should\n// all be identical.\nfunc TestError(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tError(\"test\")\n\tif !contains(severity.ErrorLog, \"E\") {\n\t\tt.Errorf(\"Error has wrong character: %q\", contents(severity.ErrorLog))\n\t}\n\tif !contains(severity.ErrorLog, \"test\") {\n\t\tt.Error(\"Error failed\")\n\t}\n\tstr := contents(severity.ErrorLog)\n\tif !contains(severity.WarningLog, str) {\n\t\tt.Error(\"Warning failed\")\n\t}\n\tif !contains(severity.InfoLog, str) {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that an Error log does not goes to Warning and Info.\n// Even in the Info log, the source character will be E, so the data should\n// all be identical.\nfunc TestErrorWithOneOutput(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tlogging.oneOutput = true\n\tdefer logging.swap(logging.newBuffers())\n\tError(\"test\")\n\tif !contains(severity.ErrorLog, \"E\") {\n\t\tt.Errorf(\"Error has wrong character: %q\", contents(severity.ErrorLog))\n\t}\n\tif !contains(severity.ErrorLog, \"test\") {\n\t\tt.Error(\"Error failed\")\n\t}\n\tstr := contents(severity.ErrorLog)\n\tif contains(severity.WarningLog, str) {\n\t\tt.Error(\"Warning failed\")\n\t}\n\tif contains(severity.InfoLog, str) {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that a Warning log goes to Info.\n// Even in the Info log, the source character will be W, so the data should\n// all be identical.\nfunc TestWarning(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tWarning(\"test\")\n\tif !contains(severity.WarningLog, \"W\") {\n\t\tt.Errorf(\"Warning has wrong character: %q\", contents(severity.WarningLog))\n\t}\n\tif !contains(severity.WarningLog, \"test\") {\n\t\tt.Error(\"Warning failed\")\n\t}\n\tstr := contents(severity.WarningLog)\n\tif !contains(severity.InfoLog, str) {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that a Warning log does not goes to Info.\n// Even in the Info log, the source character will be W, so the data should\n// all be identical.\nfunc TestWarningWithOneOutput(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tlogging.oneOutput = true\n\tdefer logging.swap(logging.newBuffers())\n\tWarning(\"test\")\n\tif !contains(severity.WarningLog, \"W\") {\n\t\tt.Errorf(\"Warning has wrong character: %q\", contents(severity.WarningLog))\n\t}\n\tif !contains(severity.WarningLog, \"test\") {\n\t\tt.Error(\"Warning failed\")\n\t}\n\tstr := contents(severity.WarningLog)\n\tif contains(severity.InfoLog, str) {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that a V log goes to Info.\nfunc TestV(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\trequire.NoError(t, logging.verbosity.Set(\"2\"))\n\tV(2).Info(\"test\")\n\tif !contains(severity.InfoLog, \"I\") {\n\t\tt.Errorf(\"Info has wrong character: %q\", contents(severity.InfoLog))\n\t}\n\tif !contains(severity.InfoLog, \"test\") {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that a vmodule enables a log in this file.\nfunc TestVmoduleOn(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\trequire.NoError(t, logging.vmodule.Set(\"klog_test=2\"))\n\tif !V(1).Enabled() {\n\t\tt.Error(\"V not enabled for 1\")\n\t}\n\tif !V(2).Enabled() {\n\t\tt.Error(\"V not enabled for 2\")\n\t}\n\tif V(3).Enabled() {\n\t\tt.Error(\"V enabled for 3\")\n\t}\n\tV(2).Info(\"test\")\n\tif !contains(severity.InfoLog, \"I\") {\n\t\tt.Errorf(\"Info has wrong character: %q\", contents(severity.InfoLog))\n\t}\n\tif !contains(severity.InfoLog, \"test\") {\n\t\tt.Error(\"Info failed\")\n\t}\n}\n\n// Test that a vmodule of another file does not enable a log in this file.\nfunc TestVmoduleOff(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\trequire.NoError(t, logging.vmodule.Set(\"notthisfile=2\"))\n\tfor i := 1; i <= 3; i++ {\n\t\tif V(Level(i)).Enabled() {\n\t\t\tt.Errorf(\"V enabled for %d\", i)\n\t\t}\n\t}\n\tV(2).Info(\"test\")\n\tif contents(severity.InfoLog) != \"\" {\n\t\tt.Error(\"V logged incorrectly\")\n\t}\n}\n\nfunc TestSetOutputDataRace(*testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tvar wg sync.WaitGroup\n\tvar daemons []*flushDaemon\n\tfor i := 1; i <= 50; i++ {\n\t\tdaemon := newFlushDaemon(logging.lockAndFlushAll, nil)\n\t\tdaemon.run(time.Second)\n\t\tdaemons = append(daemons, daemon)\n\t}\n\tfor i := 1; i <= 50; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tSetOutput(ioutil.Discard)\n\t\t}()\n\t}\n\tfor i := 1; i <= 50; i++ {\n\t\tdaemon := newFlushDaemon(logging.lockAndFlushAll, nil)\n\t\tdaemon.run(time.Second)\n\t\tdaemons = append(daemons, daemon)\n\t}\n\tfor i := 1; i <= 50; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tSetOutputBySeverity(\"INFO\", ioutil.Discard)\n\t\t}()\n\t}\n\tfor i := 1; i <= 50; i++ {\n\t\tdaemon := newFlushDaemon(logging.lockAndFlushAll, nil)\n\t\tdaemon.run(time.Second)\n\t\tdaemons = append(daemons, daemon)\n\t}\n\twg.Wait()\n\tfor _, d := range daemons {\n\t\td.stop()\n\t}\n}\n\nfunc TestLogToOutput(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tlogging.toStderr = true\n\tdefer logging.swap(logging.newBuffers())\n\tbuf := new(bytes.Buffer)\n\tSetOutput(buf)\n\tLogToStderr(false)\n\n\tInfo(\"Does logging to an output work?\")\n\n\tstr := buf.String()\n\tif !strings.Contains(str, \"Does logging to an output work?\") {\n\t\tt.Fatalf(\"Expected %q to contain \\\"Does logging to an output work?\\\"\", str)\n\t}\n}\n\n// vGlobs are patterns that match/don't match this file at V=2.\nvar vGlobs = map[string]bool{\n\t// Easy to test the numeric match here.\n\t\"klog_test=1\": false, // If -vmodule sets V to 1, V(2) will fail.\n\t\"klog_test=2\": true,\n\t\"klog_test=3\": true, // If -vmodule sets V to 1, V(3) will succeed.\n\t// These all use 2 and check the patterns. All are true.\n\t\"*=2\":           true,\n\t\"?l*=2\":         true,\n\t\"????_*=2\":      true,\n\t\"??[mno]?_*t=2\": true,\n\t// These all use 2 and check the patterns. All are false.\n\t\"*x=2\":         false,\n\t\"m*=2\":         false,\n\t\"??_*=2\":       false,\n\t\"?[abc]?_*t=2\": false,\n}\n\n// Test that vmodule globbing works as advertised.\nfunc testVmoduleGlob(pat string, match bool, t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\trequire.NoError(t, logging.vmodule.Set(pat))\n\tif V(2).Enabled() != match {\n\t\tt.Errorf(\"incorrect match for %q: got %#v expected %#v\", pat, V(2), match)\n\t}\n}\n\n// Test that a vmodule globbing works as advertised.\nfunc TestVmoduleGlob(t *testing.T) {\n\tfor glob, match := range vGlobs {\n\t\ttestVmoduleGlob(glob, match, t)\n\t}\n}\n\nfunc TestRollover(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tvar err error\n\tdefer func(previous func(error)) { logExitFunc = previous }(logExitFunc)\n\tlogExitFunc = func(e error) {\n\t\terr = e\n\t}\n\tMaxSize = 512\n\tInfo(\"x\") // Be sure we have a file.\n\tinfo, ok := logging.file[severity.InfoLog].(*syncBuffer)\n\tif !ok {\n\t\tt.Fatal(\"info wasn't created\")\n\t}\n\tif err != nil {\n\t\tt.Fatalf(\"info has initial error: %v\", err)\n\t}\n\tfname0 := info.file.Name()\n\tInfo(strings.Repeat(\"x\", int(MaxSize))) // force a rollover\n\tif err != nil {\n\t\tt.Fatalf(\"info has error after big write: %v\", err)\n\t}\n\n\t// Make sure the next log file gets a file name with a different\n\t// time stamp.\n\t//\n\t// TODO: determine whether we need to support subsecond log\n\t// rotation.  C++ does not appear to handle this case (nor does it\n\t// handle Daylight Savings Time properly).\n\ttime.Sleep(1 * time.Second)\n\n\tInfo(\"x\") // create a new file\n\tif err != nil {\n\t\tt.Fatalf(\"error after rotation: %v\", err)\n\t}\n\tfname1 := info.file.Name()\n\tif fname0 == fname1 {\n\t\tt.Errorf(\"info.f.Name did not change: %v\", fname0)\n\t}\n\tif info.nbytes >= info.maxbytes {\n\t\tt.Errorf(\"file size was not reset: %d\", info.nbytes)\n\t}\n}\n\nfunc TestOpenAppendOnStart(t *testing.T) {\n\tconst (\n\t\tx string = \"xxxxxxxxxx\"\n\t\ty string = \"yyyyyyyyyy\"\n\t)\n\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tvar err error\n\tdefer func(previous func(error)) { logExitFunc = previous }(logExitFunc)\n\tlogExitFunc = func(e error) {\n\t\terr = e\n\t}\n\n\tf, err := ioutil.TempFile(\"\", \"test_klog_OpenAppendOnStart\")\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tdefer os.Remove(f.Name())\n\tlogging.logFile = f.Name()\n\n\t// Erase files created by prior tests,\n\tfor i := range logging.file {\n\t\tlogging.file[i] = nil\n\t}\n\n\t// Logging creates the file\n\tInfo(x)\n\tsb, ok := logging.file[severity.InfoLog].(*syncBuffer)\n\tif !ok {\n\t\tt.Fatal(\"info wasn't created\")\n\t}\n\n\t// ensure we wrote what we expected\n\tneedToSync := logging.flushAll()\n\tif needToSync.num != 1 || needToSync.files[0] != sb.file {\n\t\tt.Errorf(\"Should have received exactly the file from severity.InfoLog for syncing, got instead: %+v\", needToSync)\n\t}\n\tlogging.syncAll(needToSync)\n\tb, err := ioutil.ReadFile(logging.logFile)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !strings.Contains(string(b), x) {\n\t\tt.Fatalf(\"got %s, missing expected Info log: %s\", string(b), x)\n\t}\n\n\t// Set the file to nil so it gets \"created\" (opened) again on the next write.\n\tfor i := range logging.file {\n\t\tlogging.file[i] = nil\n\t}\n\n\t// Logging again should open the file again with O_APPEND instead of O_TRUNC\n\tInfo(y)\n\t// ensure we wrote what we expected\n\tlogging.lockAndFlushAll()\n\tb, err = ioutil.ReadFile(logging.logFile)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !strings.Contains(string(b), y) {\n\t\tt.Fatalf(\"got %s, missing expected Info log: %s\", string(b), y)\n\t}\n\t// The initial log message should be preserved across create calls.\n\tlogging.lockAndFlushAll()\n\tb, err = ioutil.ReadFile(logging.logFile)\n\tif err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif !strings.Contains(string(b), x) {\n\t\tt.Fatalf(\"got %s, missing expected Info log: %s\", string(b), x)\n\t}\n}\n\nfunc TestLogBacktraceAt(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\t// The peculiar style of this code simplifies line counting and maintenance of the\n\t// tracing block below.\n\tvar infoLine string\n\tsetTraceLocation := func(file string, line int, ok bool, delta int) {\n\t\tif !ok {\n\t\t\tt.Fatal(\"could not get file:line\")\n\t\t}\n\t\t_, file = filepath.Split(file)\n\t\tinfoLine = fmt.Sprintf(\"%s:%d\", file, line+delta)\n\t\terr := logging.traceLocation.Set(infoLine)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"error setting log_backtrace_at: \", err)\n\t\t}\n\t}\n\t{\n\t\t// Start of tracing block. These lines know about each other's relative position.\n\t\t_, file, line, ok := runtime.Caller(0)\n\t\tsetTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.\n\t\tInfo(\"we want a stack trace here\")\n\t}\n\tnumAppearances := strings.Count(contents(severity.InfoLog), infoLine)\n\tif numAppearances < 2 {\n\t\t// Need 2 appearances, one in the log header and one in the trace:\n\t\t//   log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here\n\t\t//   ...\n\t\t//   k8s.io/klog/klog_test.go:280 (0x41ba91)\n\t\t//   ...\n\t\t// We could be more precise but that would require knowing the details\n\t\t// of the traceback format, which may not be dependable.\n\t\tt.Fatal(\"got no trace back; log is \", contents(severity.InfoLog))\n\t}\n}\n\nfunc BenchmarkHeader(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tbuf, _, _ := logging.header(severity.InfoLog, 0)\n\t\tbuffer.PutBuffer(buf)\n\t}\n}\n\nfunc BenchmarkHeaderWithDir(b *testing.B) {\n\tlogging.addDirHeader = true\n\tfor i := 0; i < b.N; i++ {\n\t\tbuf, _, _ := logging.header(severity.InfoLog, 0)\n\t\tbuffer.PutBuffer(buf)\n\t}\n}\n\n// Ensure that benchmarks have side effects to avoid compiler optimization\nvar result interface{}\nvar enabled bool\n\nfunc BenchmarkV(b *testing.B) {\n\tvar v Verbose\n\tfor i := 0; i < b.N; i++ {\n\t\tv = V(10)\n\t}\n\tenabled = v.Enabled()\n}\n\nfunc BenchmarkKRef(b *testing.B) {\n\tvar r ObjectRef\n\tfor i := 0; i < b.N; i++ {\n\t\tr = KRef(\"namespace\", \"name\")\n\t}\n\tresult = r\n}\n\nfunc BenchmarkKObj(b *testing.B) {\n\ta := test.KMetadataMock{Name: \"a\", NS: \"a\"}\n\tvar r ObjectRef\n\tfor i := 0; i < b.N; i++ {\n\t\tr = KObj(&a)\n\t}\n\tresult = r\n}\n\n// BenchmarkKObjs measures the (pretty typical) case\n// where KObjs is used in a V(5).InfoS call that never\n// emits a log entry because verbosity is lower than 5.\n// For performance when the result of KObjs gets formatted,\n// see examples/benchmarks.\n//\n// This uses two different patterns:\n// - directly calling klog.V(5).Info\n// - guarding the call with Enabled\nfunc BenchmarkKObjs(b *testing.B) {\n\tfor length := 0; length <= 100; length += 10 {\n\t\tb.Run(fmt.Sprintf(\"%d\", length), func(b *testing.B) {\n\t\t\targ := make([]interface{}, length)\n\t\t\tfor i := 0; i < length; i++ {\n\t\t\t\targ[i] = test.KMetadataMock{Name: \"a\", NS: \"a\"}\n\t\t\t}\n\n\t\t\tb.Run(\"simple\", func(b *testing.B) {\n\t\t\t\tb.ResetTimer()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tV(5).InfoS(\"benchmark\", \"objs\", KObjs(arg))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tb.Run(\"conditional\", func(b *testing.B) {\n\t\t\t\tb.ResetTimer()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tif klogV := V(5); klogV.Enabled() {\n\t\t\t\t\t\tklogV.InfoS(\"benchmark\", \"objs\", KObjs(arg))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\n// BenchmarkKObjSlice corresponds to BenchmarkKObjs except that it uses KObjSlice\nfunc BenchmarkKObjSlice(b *testing.B) {\n\tfor length := 0; length <= 100; length += 10 {\n\t\tb.Run(fmt.Sprintf(\"%d\", length), func(b *testing.B) {\n\t\t\targ := make([]interface{}, length)\n\t\t\tfor i := 0; i < length; i++ {\n\t\t\t\targ[i] = test.KMetadataMock{Name: \"a\", NS: \"a\"}\n\t\t\t}\n\n\t\t\tb.Run(\"simple\", func(b *testing.B) {\n\t\t\t\tb.ResetTimer()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tV(5).InfoS(\"benchmark\", \"objs\", KObjSlice(arg))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tb.Run(\"conditional\", func(b *testing.B) {\n\t\t\t\tb.ResetTimer()\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tif klogV := V(5); klogV.Enabled() {\n\t\t\t\t\t\tklogV.InfoS(\"benchmark\", \"objs\", KObjSlice(arg))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n}\n\n// BenchmarkScalars corresponds to BenchmarkKObjs except that it avoids function\n// calls for the parameters.\nfunc BenchmarkScalars(b *testing.B) {\n\tb.Run(\"simple\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tV(5).InfoS(\"benchmark\", \"str\", \"hello world\", \"int\", 42)\n\t\t}\n\t})\n\n\tb.Run(\"conditional\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif klogV := V(5); klogV.Enabled() {\n\t\t\t\tklogV.InfoS(\"benchmark\", \"str\", \"hello world\", \"int\", 42)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// BenchmarkScalarsWithLogger is the same as BenchmarkScalars except that it uses\n// a go-logr instance.\nfunc BenchmarkScalarsWithLogger(b *testing.B) {\n\tlogger := Background()\n\tb.Run(\"simple\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.V(5).Info(\"benchmark\", \"str\", \"hello world\", \"int\", 42)\n\t\t}\n\t})\n\n\tb.Run(\"conditional\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif loggerV := logger.V(5); loggerV.Enabled() {\n\t\t\t\tloggerV.Info(\"benchmark\", \"str\", \"hello world\", \"int\", 42)\n\t\t\t}\n\t\t}\n\t})\n}\n\n// BenchmarKObjSliceWithLogger is the same as BenchmarkKObjSlice except that it\n// uses a go-logr instance and a slice with a single entry. BenchmarkKObjSlice\n// shows that the overhead for KObjSlice is constant and doesn't depend on the\n// slice length when logging is off.\nfunc BenchmarkKObjSliceWithLogger(b *testing.B) {\n\tlogger := Background()\n\targ := []interface{}{test.KMetadataMock{Name: \"a\", NS: \"a\"}}\n\tb.Run(\"simple\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tlogger.V(5).Info(\"benchmark\", \"objs\", KObjSlice(arg))\n\t\t}\n\t})\n\n\tb.Run(\"conditional\", func(b *testing.B) {\n\t\tb.ResetTimer()\n\t\tfor i := 0; i < b.N; i++ {\n\t\t\tif loggerV := logger.V(5); loggerV.Enabled() {\n\t\t\t\tloggerV.Info(\"benchmark\", \"objs\", KObjSlice(arg))\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc BenchmarkLogs(b *testing.B) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\n\ttestFile, err := ioutil.TempFile(\"\", \"test.log\")\n\tif err != nil {\n\t\tb.Fatal(\"unable to create temporary file\")\n\t}\n\tdefer os.Remove(testFile.Name())\n\n\trequire.NoError(b, logging.verbosity.Set(\"0\"))\n\tlogging.toStderr = false\n\tlogging.alsoToStderr = false\n\tlogging.stderrThreshold = severityValue{\n\t\tSeverity: severity.FatalLog,\n\t}\n\tlogging.logFile = testFile.Name()\n\tlogging.swap([severity.NumSeverity]io.Writer{nil, nil, nil, nil})\n\n\tfor i := 0; i < b.N; i++ {\n\t\tError(\"error\")\n\t\tWarning(\"warning\")\n\t\tInfo(\"info\")\n\t}\n\tneedToSync := logging.flushAll()\n\tsb, ok := logging.file[severity.InfoLog].(*syncBuffer)\n\tif !ok {\n\t\tb.Fatal(\"info wasn't created\")\n\t}\n\tif needToSync.num != 1 || needToSync.files[0] != sb.file {\n\t\tb.Fatalf(\"Should have received exactly the file from severity.InfoLog for syncing, got instead: %+v\", needToSync)\n\t}\n\tlogging.syncAll(needToSync)\n}\n\nfunc BenchmarkFlush(b *testing.B) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\n\ttestFile, err := ioutil.TempFile(\"\", \"test.log\")\n\tif err != nil {\n\t\tb.Fatal(\"unable to create temporary file\")\n\t}\n\tdefer os.Remove(testFile.Name())\n\n\trequire.NoError(b, logging.verbosity.Set(\"0\"))\n\tlogging.toStderr = false\n\tlogging.alsoToStderr = false\n\tlogging.stderrThreshold = severityValue{\n\t\tSeverity: severity.FatalLog,\n\t}\n\tlogging.logFile = testFile.Name()\n\tlogging.swap([severity.NumSeverity]io.Writer{nil, nil, nil, nil})\n\n\t// Create output file.\n\tInfo(\"info\")\n\tneedToSync := logging.flushAll()\n\n\tif needToSync.num != 1 {\n\t\tb.Fatalf(\"expected exactly one file to sync, got: %+v\", needToSync)\n\t}\n\n\tb.ResetTimer()\n\n\tfor i := 0; i < b.N; i++ {\n\t\tneedToSync := logging.flushAll()\n\t\tlogging.syncAll(needToSync)\n\t}\n}\n\n// Test the logic on checking log size limitation.\nfunc TestFileSizeCheck(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\ttestData := map[string]struct {\n\t\ttestLogFile          string\n\t\ttestLogFileMaxSizeMB uint64\n\t\ttestCurrentSize      uint64\n\t\texpectedResult       bool\n\t}{\n\t\t\"logFile not specified, exceeds max size\": {\n\t\t\ttestLogFile:          \"\",\n\t\t\ttestLogFileMaxSizeMB: 1,\n\t\t\ttestCurrentSize:      1024 * 1024 * 2000, //exceeds the maxSize\n\t\t\texpectedResult:       true,\n\t\t},\n\n\t\t\"logFile not specified, not exceeds max size\": {\n\t\t\ttestLogFile:          \"\",\n\t\t\ttestLogFileMaxSizeMB: 1,\n\t\t\ttestCurrentSize:      1024 * 1024 * 1000, //smaller than the maxSize\n\t\t\texpectedResult:       false,\n\t\t},\n\t\t\"logFile specified, exceeds max size\": {\n\t\t\ttestLogFile:          \"/tmp/test.log\",\n\t\t\ttestLogFileMaxSizeMB: 500,                // 500MB\n\t\t\ttestCurrentSize:      1024 * 1024 * 1000, //exceeds the logFileMaxSizeMB\n\t\t\texpectedResult:       true,\n\t\t},\n\t\t\"logFile specified, not exceeds max size\": {\n\t\t\ttestLogFile:          \"/tmp/test.log\",\n\t\t\ttestLogFileMaxSizeMB: 500,               // 500MB\n\t\t\ttestCurrentSize:      1024 * 1024 * 300, //smaller than the logFileMaxSizeMB\n\t\t\texpectedResult:       false,\n\t\t},\n\t}\n\n\tfor name, test := range testData {\n\t\tlogging.logFile = test.testLogFile\n\t\tlogging.logFileMaxSizeMB = test.testLogFileMaxSizeMB\n\t\tactualResult := test.testCurrentSize >= CalculateMaxSize()\n\t\tif test.expectedResult != actualResult {\n\t\t\tt.Fatalf(\"Error on test case '%v': Was expecting result equals %v, got %v\",\n\t\t\t\tname, test.expectedResult, actualResult)\n\t\t}\n\t}\n}\n\nfunc TestInitFlags(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\n\tfs1 := flag.NewFlagSet(\"test1\", flag.PanicOnError)\n\tInitFlags(fs1)\n\trequire.NoError(t, fs1.Set(\"log_dir\", \"/test1\"))\n\trequire.NoError(t, fs1.Set(\"log_file_max_size\", \"1\"))\n\tfs2 := flag.NewFlagSet(\"test2\", flag.PanicOnError)\n\tInitFlags(fs2)\n\tif logging.logDir != \"/test1\" {\n\t\tt.Fatalf(\"Expected log_dir to be %q, got %q\", \"/test1\", logging.logDir)\n\t}\n\trequire.NoError(t, fs2.Set(\"log_file_max_size\", \"2048\"))\n\tif logging.logFileMaxSizeMB != 2048 {\n\t\tt.Fatal(\"Expected log_file_max_size to be 2048\")\n\t}\n}\n\nfunc TestCommandLine(t *testing.T) {\n\tvar fs flag.FlagSet\n\tInitFlags(&fs)\n\n\texpectedFlags := `  -add_dir_header\n    \tIf true, adds the file directory to the header of the log messages\n  -alsologtostderr\n    \tlog to standard error as well as files (no effect when -logtostderr=true)\n  -alsologtostderrthreshold value\n    \tlogs at or above this threshold go to stderr when -alsologtostderr=true (no effect when -logtostderr=true)\n  -legacy_stderr_threshold_behavior\n    \tIf true, stderrthreshold is ignored when logtostderr=true (legacy behavior). If false, stderrthreshold is honored even when logtostderr=true (default true)\n  -log_backtrace_at value\n    \twhen logging hits line file:N, emit a stack trace\n  -log_dir string\n    \tIf non-empty, write log files in this directory (no effect when -logtostderr=true)\n  -log_file string\n    \tIf non-empty, use this log file (no effect when -logtostderr=true)\n  -log_file_max_size uint\n    \tDefines 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)\n  -logtostderr\n    \tlog to standard error instead of files (default true)\n  -one_output\n    \tIf true, only write logs to their native severity level (vs also writing to each lower severity level; no effect when -logtostderr=true)\n  -skip_headers\n    \tIf true, avoid header prefixes in the log messages\n  -skip_log_headers\n    \tIf true, avoid headers when opening log files (no effect when -logtostderr=true)\n  -stderrthreshold value\n    \tlogs at or above this threshold go to stderr when writing to files and stderr (no effect when -logtostderr=true or -alsologtostderr=true unless -legacy_stderr_threshold_behavior=false) (default 2)\n  -v value\n    \tnumber for the log level verbosity\n  -vmodule value\n    \tcomma-separated list of pattern=N settings for file-filtered logging\n`\n\n\tvar output bytes.Buffer\n\tfs.SetOutput(&output)\n\tfs.PrintDefaults()\n\tactualFlags := output.String()\n\n\tif expectedFlags != actualFlags {\n\t\tt.Fatalf(\"Command line changed.\\nExpected:\\n%q\\nActual:\\n%q\\n\", expectedFlags, actualFlags)\n\t}\n}\n\nfunc TestInfoObjectRef(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\n\ttests := []struct {\n\t\tname string\n\t\tref  ObjectRef\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"with ns\",\n\t\t\tref: ObjectRef{\n\t\t\t\tName:      \"test-name\",\n\t\t\t\tNamespace: \"test-ns\",\n\t\t\t},\n\t\t\twant: \"test-ns/test-name\",\n\t\t},\n\t\t{\n\t\t\tname: \"without ns\",\n\t\t\tref: ObjectRef{\n\t\t\t\tName:      \"test-name\",\n\t\t\t\tNamespace: \"\",\n\t\t\t},\n\t\t\twant: \"test-name\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\tref:  ObjectRef{},\n\t\t\twant: \"\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tInfo(tt.ref)\n\t\t\tif !contains(severity.InfoLog, tt.want) {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tt.want, contents(severity.InfoLog))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestKObj(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tobj  KMetadata\n\t\twant ObjectRef\n\t}{\n\t\t{\n\t\t\tname: \"nil passed as pointer KMetadata implementation\",\n\t\t\tobj:  (*test.PtrKMetadataMock)(nil),\n\t\t\twant: ObjectRef{},\n\t\t},\n\t\t{\n\t\t\tname: \"empty struct passed as non-pointer KMetadata implementation\",\n\t\t\tobj:  test.KMetadataMock{},\n\t\t\twant: ObjectRef{},\n\t\t},\n\t\t{\n\t\t\tname: \"nil pointer passed to non-pointer KMetadata implementation\",\n\t\t\tobj:  (*test.KMetadataMock)(nil),\n\t\t\twant: ObjectRef{},\n\t\t},\n\t\t{\n\t\t\tname: \"nil\",\n\t\t\tobj:  nil,\n\t\t\twant: ObjectRef{},\n\t\t},\n\t\t{\n\t\t\tname: \"with ns\",\n\t\t\tobj:  &test.KMetadataMock{Name: \"test-name\", NS: \"test-ns\"},\n\t\t\twant: ObjectRef{\n\t\t\t\tName:      \"test-name\",\n\t\t\t\tNamespace: \"test-ns\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"without ns\",\n\t\t\tobj:  &test.KMetadataMock{Name: \"test-name\", NS: \"\"},\n\t\t\twant: ObjectRef{\n\t\t\t\tName: \"test-name\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif KObj(tt.obj) != tt.want {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tt.want, KObj(tt.obj))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestKRef(t *testing.T) {\n\ttests := []struct {\n\t\ttestname  string\n\t\tname      string\n\t\tnamespace string\n\t\twant      ObjectRef\n\t}{\n\t\t{\n\t\t\ttestname:  \"with ns\",\n\t\t\tname:      \"test-name\",\n\t\t\tnamespace: \"test-ns\",\n\t\t\twant: ObjectRef{\n\t\t\t\tName:      \"test-name\",\n\t\t\t\tNamespace: \"test-ns\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\ttestname: \"without ns\",\n\t\t\tname:     \"test-name\",\n\t\t\twant: ObjectRef{\n\t\t\t\tName: \"test-name\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.testname, func(t *testing.T) {\n\t\t\tif KRef(tt.namespace, tt.name) != tt.want {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", tt.want, KRef(tt.namespace, tt.name))\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Test that InfoS and InfoSDepth work as advertised.\nfunc TestInfoS(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)\n\t}\n\tpid = 1234\n\tvar testDataInfo = []struct {\n\t\tmsg        string\n\t\tformat     string\n\t\tkeysValues []interface{}\n\t}{\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" pod=\\\"kubedns\\\"\\n\",\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\"},\n\t\t},\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" replicaNum=20\\n\",\n\t\t\tkeysValues: []interface{}{\"replicaNum\", 20},\n\t\t},\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" err=\\\"test error\\\"\\n\",\n\t\t\tkeysValues: []interface{}{\"err\", errors.New(\"test error\")},\n\t\t},\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" err=\\\"test error\\\"\\n\",\n\t\t\tkeysValues: []interface{}{\"err\", errors.New(\"test error\")},\n\t\t},\n\t}\n\n\tfunctions := []func(msg string, keyAndValues ...interface{}){\n\t\tInfoS,\n\t\tmyInfoS,\n\t}\n\tfor _, f := range functions {\n\t\tfor _, data := range testDataInfo {\n\t\t\tlogging.file[severity.InfoLog] = &flushBuffer{}\n\t\t\tf(data.msg, data.keysValues...)\n\t\t\tvar line int\n\t\t\tn, err := fmt.Sscanf(contents(severity.InfoLog), data.format, &line)\n\t\t\tif n != 1 || err != nil {\n\t\t\t\tt.Errorf(\"log format error: %d elements, error %s:\\n%s\", n, err, contents(severity.InfoLog))\n\t\t\t}\n\t\t\twant := fmt.Sprintf(data.format, line)\n\t\t\tif contents(severity.InfoLog) != want {\n\t\t\t\tt.Errorf(\"InfoS has wrong format: \\n got:\\t%s\\nwant:\\t%s\", contents(severity.InfoLog), want)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Test that Verbose.InfoS works as advertised.\nfunc TestVInfoS(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)\n\t}\n\tpid = 1234\n\tmyData := struct {\n\t\tData string\n\t}{\n\t\tData: `This is some long text\nwith a line break.`,\n\t}\n\tvar testDataInfo = []struct {\n\t\tmsg        string\n\t\tformat     string\n\t\tkeysValues []interface{}\n\t}{\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" pod=\\\"kubedns\\\"\\n\",\n\t\t\tkeysValues: []interface{}{\"pod\", \"kubedns\"},\n\t\t},\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" replicaNum=20\\n\",\n\t\t\tkeysValues: []interface{}{\"replicaNum\", 20},\n\t\t},\n\t\t{\n\t\t\tmsg:        \"test\",\n\t\t\tformat:     \"I0102 15:04:05.067890    1234 klog_test.go:%d] \\\"test\\\" err=\\\"test error\\\"\\n\",\n\t\t\tkeysValues: []interface{}{\"err\", errors.New(\"test error\")},\n\t\t},\n\t\t{\n\t\t\tmsg: `first message line\nsecond message line`,\n\t\t\tformat: `I0102 15:04:05.067890    1234 klog_test.go:%d] \"first message line\\nsecond message line\" multiLine=<\n\tfirst value line\n\tsecond value line\n >\n`,\n\t\t\tkeysValues: []interface{}{\"multiLine\", `first value line\nsecond value line`},\n\t\t},\n\t\t{\n\t\t\tmsg: `message`,\n\t\t\tformat: `I0102 15:04:05.067890    1234 klog_test.go:%d] \"message\" myData={\"Data\":\"This is some long text\\nwith a line break.\"}\n`,\n\t\t\tkeysValues: []interface{}{\"myData\", myData},\n\t\t},\n\t}\n\n\trequire.NoError(t, logging.verbosity.Set(\"2\"))\n\n\tfor l := Level(0); l < Level(4); l++ {\n\t\tfor _, data := range testDataInfo {\n\t\t\tlogging.file[severity.InfoLog] = &flushBuffer{}\n\n\t\t\tV(l).InfoS(data.msg, data.keysValues...)\n\n\t\t\tvar want string\n\t\t\tvar line int\n\t\t\tif l <= 2 {\n\t\t\t\tn, err := fmt.Sscanf(contents(severity.InfoLog), data.format, &line)\n\t\t\t\tif n != 1 || err != nil {\n\t\t\t\t\tt.Errorf(\"log format error: %d elements, error %s:\\n%s\", n, err, contents(severity.InfoLog))\n\t\t\t\t}\n\n\t\t\t\twant = fmt.Sprintf(data.format, line)\n\t\t\t} else {\n\t\t\t\twant = \"\"\n\t\t\t}\n\t\t\tif contents(severity.InfoLog) != want {\n\t\t\t\tt.Errorf(\"V(%d).InfoS has unexpected output:\\ngot:\\n%s\\nwant:\\n%s\\n\", l, contents(severity.InfoLog), want)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Test that ErrorS and ErrorSDepth work as advertised.\nfunc TestErrorS(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\ttimeNow = func() time.Time {\n\t\treturn time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)\n\t}\n\tlogging.logFile = \"\"\n\tpid = 1234\n\n\tfunctions := []func(err error, msg string, keyAndValues ...interface{}){\n\t\tErrorS,\n\t\tmyErrorS,\n\t}\n\tfor _, f := range functions {\n\t\tvar errorList = []struct {\n\t\t\terr    error\n\t\t\tformat string\n\t\t}{\n\t\t\t{\n\t\t\t\terr:    fmt.Errorf(\"update status failed\"),\n\t\t\t\tformat: \"E0102 15:04:05.067890    1234 klog_test.go:%d] \\\"Failed to update pod status\\\" err=\\\"update status failed\\\" pod=\\\"kubedns\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\terr:    nil,\n\t\t\t\tformat: \"E0102 15:04:05.067890    1234 klog_test.go:%d] \\\"Failed to update pod status\\\" pod=\\\"kubedns\\\"\\n\",\n\t\t\t},\n\t\t}\n\t\tfor _, e := range errorList {\n\t\t\tlogging.file[severity.ErrorLog] = &flushBuffer{}\n\t\t\tf(e.err, \"Failed to update pod status\", \"pod\", \"kubedns\")\n\t\t\tvar line int\n\t\t\tn, err := fmt.Sscanf(contents(severity.ErrorLog), e.format, &line)\n\t\t\tif n != 1 || err != nil {\n\t\t\t\tt.Errorf(\"log format error: %d elements, error %s:\\n%s\", n, err, contents(severity.ErrorLog))\n\t\t\t}\n\t\t\twant := fmt.Sprintf(e.format, line)\n\t\t\tif contents(severity.ErrorLog) != want {\n\t\t\t\tt.Errorf(\"ErrorS has wrong format:\\ngot:\\n%s\\nwant:\\n%s\\n\", contents(severity.ErrorLog), want)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc createTestValueOfLoggingT() *loggingT {\n\tl := new(loggingT)\n\tl.toStderr = true\n\tl.alsoToStderr = false\n\tl.stderrThreshold = severityValue{\n\t\tSeverity: severity.ErrorLog,\n\t}\n\tl.verbosity = Level(0)\n\tl.skipHeaders = false\n\tl.skipLogHeaders = false\n\tl.addDirHeader = false\n\treturn l\n}\n\nfunc createTestValueOfModulePat(p string, li bool, le Level) modulePat {\n\tm := modulePat{}\n\tm.pattern = p\n\tm.literal = li\n\tm.level = le\n\treturn m\n}\n\nfunc compareModuleSpec(a, b moduleSpec) bool {\n\tif len(a.filter) != len(b.filter) {\n\t\treturn false\n\t}\n\n\tfor i := 0; i < len(a.filter); i++ {\n\t\tif a.filter[i] != b.filter[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc TestSetVState(t *testing.T) {\n\t//Target loggingT value\n\twant := createTestValueOfLoggingT()\n\twant.verbosity = Level(3)\n\twant.vmodule.filter = []modulePat{\n\t\tcreateTestValueOfModulePat(\"recordio\", true, Level(2)),\n\t\tcreateTestValueOfModulePat(\"file\", true, Level(1)),\n\t\tcreateTestValueOfModulePat(\"gfs*\", false, Level(3)),\n\t\tcreateTestValueOfModulePat(\"gopher*\", false, Level(3)),\n\t}\n\twant.filterLength = 4\n\n\t//loggingT value to which test is run\n\ttarget := createTestValueOfLoggingT()\n\n\ttf := []modulePat{\n\t\tcreateTestValueOfModulePat(\"recordio\", true, Level(2)),\n\t\tcreateTestValueOfModulePat(\"file\", true, Level(1)),\n\t\tcreateTestValueOfModulePat(\"gfs*\", false, Level(3)),\n\t\tcreateTestValueOfModulePat(\"gopher*\", false, Level(3)),\n\t}\n\n\ttarget.setVState(Level(3), tf, true)\n\n\tif want.verbosity != target.verbosity || !compareModuleSpec(want.vmodule, target.vmodule) || want.filterLength != target.filterLength {\n\t\tt.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)\n\t}\n}\n\ntype sampleLogFilter struct{}\n\nfunc (f *sampleLogFilter) Filter(args []interface{}) []interface{} {\n\tfor i, arg := range args {\n\t\tv, ok := arg.(string)\n\t\tif ok && strings.Contains(v, \"filter me\") {\n\t\t\targs[i] = \"[FILTERED]\"\n\t\t}\n\t}\n\treturn args\n}\n\nfunc (f *sampleLogFilter) FilterF(format string, args []interface{}) (string, []interface{}) {\n\treturn strings.Replace(format, \"filter me\", \"[FILTERED]\", 1), f.Filter(args)\n}\n\nfunc (f *sampleLogFilter) FilterS(msg string, keysAndValues []interface{}) (string, []interface{}) {\n\treturn strings.Replace(msg, \"filter me\", \"[FILTERED]\", 1), f.Filter(keysAndValues)\n}\n\nfunc TestLogFilter(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tsetFlags()\n\tdefer logging.swap(logging.newBuffers())\n\tSetLogFilter(&sampleLogFilter{})\n\tfuncs := []struct {\n\t\tname     string\n\t\tlogFunc  func(args ...interface{})\n\t\tseverity severity.Severity\n\t}{{\n\t\tname:     \"Info\",\n\t\tlogFunc:  Info,\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"InfoDepth\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tInfoDepth(1, args...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname:     \"Infoln\",\n\t\tlogFunc:  Infoln,\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"Infof\",\n\t\tlogFunc: func(args ...interface{}) {\n\n\t\t\tInfof(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"InfoS\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tInfoS(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname:     \"Warning\",\n\t\tlogFunc:  Warning,\n\t\tseverity: severity.WarningLog,\n\t}, {\n\t\tname: \"WarningDepth\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tWarningDepth(1, args...)\n\t\t},\n\t\tseverity: severity.WarningLog,\n\t}, {\n\t\tname:     \"Warningln\",\n\t\tlogFunc:  Warningln,\n\t\tseverity: severity.WarningLog,\n\t}, {\n\t\tname: \"Warningf\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tWarningf(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.WarningLog,\n\t}, {\n\t\tname:     \"Error\",\n\t\tlogFunc:  Error,\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname: \"ErrorDepth\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tErrorDepth(1, args...)\n\t\t},\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname:     \"Errorln\",\n\t\tlogFunc:  Errorln,\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname: \"Errorf\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tErrorf(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname: \"ErrorS\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tErrorS(errors.New(\"testerror\"), args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname: \"V().Info\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).Info(args...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"V().Infoln\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).Infoln(args...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"V().Infof\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).Infof(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"V().InfoS\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).InfoS(args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.InfoLog,\n\t}, {\n\t\tname: \"V().Error\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).Error(errors.New(\"test error\"), args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.ErrorLog,\n\t}, {\n\t\tname: \"V().ErrorS\",\n\t\tlogFunc: func(args ...interface{}) {\n\t\t\tV(0).ErrorS(errors.New(\"test error\"), args[0].(string), args[1:]...)\n\t\t},\n\t\tseverity: severity.ErrorLog,\n\t}}\n\n\ttestcases := []struct {\n\t\tname           string\n\t\targs           []interface{}\n\t\texpectFiltered bool\n\t}{{\n\t\targs:           []interface{}{\"%s:%s\", \"foo\", \"bar\"},\n\t\texpectFiltered: false,\n\t}, {\n\t\targs:           []interface{}{\"%s:%s\", \"foo\", \"filter me\"},\n\t\texpectFiltered: true,\n\t}, {\n\t\targs:           []interface{}{\"filter me %s:%s\", \"foo\", \"bar\"},\n\t\texpectFiltered: true,\n\t}}\n\n\tfor _, f := range funcs {\n\t\tfor _, tc := range testcases {\n\t\t\tlogging.newBuffers()\n\t\t\tf.logFunc(tc.args...)\n\t\t\tgot := contains(f.severity, \"[FILTERED]\")\n\t\t\tif got != tc.expectFiltered {\n\t\t\t\tt.Errorf(\"%s filter application failed, got %v, want %v\", f.name, got, tc.expectFiltered)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestInfoWithLogr(t *testing.T) {\n\tlogger := new(testLogr)\n\n\ttestDataInfo := []struct {\n\t\tmsg      string\n\t\texpected testLogrEntry\n\t}{{\n\t\tmsg: \"foo\",\n\t\texpected: testLogrEntry{\n\t\t\tseverity: severity.InfoLog,\n\t\t\tmsg:      \"foo\",\n\t\t},\n\t}, {\n\t\tmsg: \"\",\n\t\texpected: testLogrEntry{\n\t\t\tseverity: severity.InfoLog,\n\t\t\tmsg:      \"\",\n\t\t},\n\t}}\n\n\tfor _, data := range testDataInfo {\n\t\tt.Run(data.msg, func(t *testing.T) {\n\t\t\tl := logr.New(logger)\n\t\t\tdefer CaptureState().Restore()\n\t\t\tSetLogger(l)\n\t\t\tdefer logger.reset()\n\n\t\t\tInfo(data.msg)\n\n\t\t\tif !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) {\n\t\t\t\tt.Errorf(\"expected: %+v; but got: %+v\", []testLogrEntry{data.expected}, logger.entries)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInfoSWithLogr(t *testing.T) {\n\tlogger := new(testLogr)\n\n\ttestDataInfo := []struct {\n\t\tmsg        string\n\t\tkeysValues []interface{}\n\t\texpected   testLogrEntry\n\t}{{\n\t\tmsg:        \"foo\",\n\t\tkeysValues: []interface{}{},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.InfoLog,\n\t\t\tmsg:           \"foo\",\n\t\t\tkeysAndValues: []interface{}{},\n\t\t},\n\t}, {\n\t\tmsg:        \"bar\",\n\t\tkeysValues: []interface{}{\"a\", 1},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.InfoLog,\n\t\t\tmsg:           \"bar\",\n\t\t\tkeysAndValues: []interface{}{\"a\", 1},\n\t\t},\n\t}}\n\n\tfor _, data := range testDataInfo {\n\t\tt.Run(data.msg, func(t *testing.T) {\n\t\t\tdefer CaptureState().Restore()\n\t\t\tl := logr.New(logger)\n\t\t\tSetLogger(l)\n\t\t\tdefer logger.reset()\n\n\t\t\tInfoS(data.msg, data.keysValues...)\n\n\t\t\tif !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) {\n\t\t\t\tt.Errorf(\"expected: %+v; but got: %+v\", []testLogrEntry{data.expected}, logger.entries)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestErrorSWithLogr(t *testing.T) {\n\tlogger := new(testLogr)\n\n\ttestError := errors.New(\"testError\")\n\n\ttestDataInfo := []struct {\n\t\terr        error\n\t\tmsg        string\n\t\tkeysValues []interface{}\n\t\texpected   testLogrEntry\n\t}{{\n\t\terr:        testError,\n\t\tmsg:        \"foo1\",\n\t\tkeysValues: []interface{}{},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.ErrorLog,\n\t\t\tmsg:           \"foo1\",\n\t\t\tkeysAndValues: []interface{}{},\n\t\t\terr:           testError,\n\t\t},\n\t}, {\n\t\terr:        testError,\n\t\tmsg:        \"bar1\",\n\t\tkeysValues: []interface{}{\"a\", 1},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.ErrorLog,\n\t\t\tmsg:           \"bar1\",\n\t\t\tkeysAndValues: []interface{}{\"a\", 1},\n\t\t\terr:           testError,\n\t\t},\n\t}, {\n\t\terr:        nil,\n\t\tmsg:        \"foo2\",\n\t\tkeysValues: []interface{}{},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.ErrorLog,\n\t\t\tmsg:           \"foo2\",\n\t\t\tkeysAndValues: []interface{}{},\n\t\t\terr:           nil,\n\t\t},\n\t}, {\n\t\terr:        nil,\n\t\tmsg:        \"bar2\",\n\t\tkeysValues: []interface{}{\"a\", 1},\n\t\texpected: testLogrEntry{\n\t\t\tseverity:      severity.ErrorLog,\n\t\t\tmsg:           \"bar2\",\n\t\t\tkeysAndValues: []interface{}{\"a\", 1},\n\t\t\terr:           nil,\n\t\t},\n\t}}\n\n\tfor _, data := range testDataInfo {\n\t\tt.Run(data.msg, func(t *testing.T) {\n\t\t\tdefer CaptureState().Restore()\n\t\t\tl := logr.New(logger)\n\t\t\tSetLogger(l)\n\t\t\tdefer logger.reset()\n\n\t\t\tErrorS(data.err, data.msg, data.keysValues...)\n\n\t\t\tif !reflect.DeepEqual(logger.entries, []testLogrEntry{data.expected}) {\n\t\t\t\tt.Errorf(\"expected: %+v; but got: %+v\", []testLogrEntry{data.expected}, logger.entries)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCallDepthLogr(t *testing.T) {\n\tlogger := &callDepthTestLogr{}\n\tlogger.resetCallDepth()\n\n\ttestCases := []struct {\n\t\tname  string\n\t\tlogFn func()\n\t}{\n\t\t{\n\t\t\tname:  \"Info log\",\n\t\t\tlogFn: func() { Info(\"info log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"InfoDepth log\",\n\t\t\tlogFn: func() { InfoDepth(0, \"infodepth log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"InfoSDepth log\",\n\t\t\tlogFn: func() { InfoSDepth(0, \"infoSDepth log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"Warning log\",\n\t\t\tlogFn: func() { Warning(\"warning log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"WarningDepth log\",\n\t\t\tlogFn: func() { WarningDepth(0, \"warningdepth log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"Error log\",\n\t\t\tlogFn: func() { Error(\"error log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"ErrorDepth log\",\n\t\t\tlogFn: func() { ErrorDepth(0, \"errordepth log\") },\n\t\t},\n\t\t{\n\t\t\tname:  \"ErrorSDepth log\",\n\t\t\tlogFn: func() { ErrorSDepth(0, errors.New(\"some error\"), \"errorSDepth log\") },\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tl := logr.New(logger)\n\t\t\tdefer ClearLogger()\n\t\t\tSetLogger(l)\n\t\t\tdefer logger.reset()\n\t\t\tdefer logger.resetCallDepth()\n\n\t\t\t// Keep these lines together.\n\t\t\t_, wantFile, wantLine, _ := runtime.Caller(0)\n\t\t\ttc.logFn()\n\t\t\twantLine++\n\n\t\t\tif len(logger.entries) != 1 {\n\t\t\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t\t\t}\n\t\t\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n\t\t})\n\t}\n}\n\nfunc TestCallDepthLogrInfoS(t *testing.T) {\n\tlogger := &callDepthTestLogr{}\n\tlogger.resetCallDepth()\n\tl := logr.New(logger)\n\tdefer CaptureState().Restore()\n\tSetLogger(l)\n\n\t// Add wrapper to ensure callDepthTestLogr +2 offset is correct.\n\tlogFunc := func() {\n\t\tInfoS(\"infoS log\")\n\t}\n\n\t// Keep these lines together.\n\t_, wantFile, wantLine, _ := runtime.Caller(0)\n\tlogFunc()\n\twantLine++\n\n\tif len(logger.entries) != 1 {\n\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t}\n\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n}\n\nfunc TestCallDepthLogrErrorS(t *testing.T) {\n\tlogger := &callDepthTestLogr{}\n\tlogger.resetCallDepth()\n\tl := logr.New(logger)\n\tdefer CaptureState().Restore()\n\tSetLogger(l)\n\n\t// Add wrapper to ensure callDepthTestLogr +2 offset is correct.\n\tlogFunc := func() {\n\t\tErrorS(errors.New(\"some error\"), \"errorS log\")\n\t}\n\n\t// Keep these lines together.\n\t_, wantFile, wantLine, _ := runtime.Caller(0)\n\tlogFunc()\n\twantLine++\n\n\tif len(logger.entries) != 1 {\n\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t}\n\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n}\n\nfunc TestCallDepthLogrGoLog(t *testing.T) {\n\tdefer CaptureState().Restore()\n\tlogger := &callDepthTestLogr{}\n\tlogger.resetCallDepth()\n\tl := logr.New(logger)\n\tSetLogger(l)\n\tCopyStandardLogTo(\"INFO\")\n\n\t// Add wrapper to ensure callDepthTestLogr +2 offset is correct.\n\tlogFunc := func() {\n\t\tstdLog.Print(\"some log\")\n\t}\n\n\t// Keep these lines together.\n\t_, wantFile, wantLine, _ := runtime.Caller(0)\n\tlogFunc()\n\twantLine++\n\n\tif len(logger.entries) != 1 {\n\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t}\n\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n\tfmt.Println(logger.entries[0])\n}\n\n// Test callDepthTestLogr logs the expected offsets.\nfunc TestCallDepthTestLogr(t *testing.T) {\n\tlogger := &callDepthTestLogr{}\n\tlogger.resetCallDepth()\n\n\tlogFunc := func() {\n\t\tlogger.Info(0, \"some info log\")\n\t}\n\t// Keep these lines together.\n\t_, wantFile, wantLine, _ := runtime.Caller(0)\n\tlogFunc()\n\twantLine++\n\n\tif len(logger.entries) != 1 {\n\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t}\n\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n\n\tlogger.reset()\n\n\tlogFunc = func() {\n\t\tlogger.Error(errors.New(\"error\"), \"some error log\")\n\t}\n\t// Keep these lines together.\n\t_, wantFile, wantLine, _ = runtime.Caller(0)\n\tlogFunc()\n\twantLine++\n\n\tif len(logger.entries) != 1 {\n\t\tt.Errorf(\"expected a single log entry to be generated, got %d\", len(logger.entries))\n\t}\n\tcheckLogrEntryCorrectCaller(t, wantFile, wantLine, logger.entries[0])\n}\n\ntype testLogr struct {\n\tentries []testLogrEntry\n\tmutex   sync.Mutex\n}\n\ntype testLogrEntry struct {\n\tseverity      severity.Severity\n\tmsg           string\n\tkeysAndValues []interface{}\n\terr           error\n}\n\nfunc (l *testLogr) reset() {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\tl.entries = []testLogrEntry{}\n}\n\nfunc (l *testLogr) Info(_ int, msg string, keysAndValues ...interface{}) {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\tl.entries = append(l.entries, testLogrEntry{\n\t\tseverity:      severity.InfoLog,\n\t\tmsg:           msg,\n\t\tkeysAndValues: keysAndValues,\n\t})\n}\n\nfunc (l *testLogr) Error(err error, msg string, keysAndValues ...interface{}) {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\tl.entries = append(l.entries, testLogrEntry{\n\t\tseverity:      severity.ErrorLog,\n\t\tmsg:           msg,\n\t\tkeysAndValues: keysAndValues,\n\t\terr:           err,\n\t})\n}\n\nfunc (l *testLogr) Init(logr.RuntimeInfo)                  {}\nfunc (l *testLogr) Enabled(int) bool                       { return true }\nfunc (l *testLogr) V(int) logr.Logger                      { panic(\"not implemented\") }\nfunc (l *testLogr) WithName(string) logr.LogSink           { panic(\"not implemented\") }\nfunc (l *testLogr) WithValues(...interface{}) logr.LogSink { panic(\"not implemented\") }\nfunc (l *testLogr) WithCallDepth(int) logr.LogSink         { return l }\n\nvar _ logr.LogSink = &testLogr{}\nvar _ logr.CallDepthLogSink = &testLogr{}\n\ntype callDepthTestLogr struct {\n\ttestLogr\n\tcallDepth int\n}\n\nfunc (l *callDepthTestLogr) resetCallDepth() {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\tl.callDepth = 0\n}\n\nfunc (l *callDepthTestLogr) WithCallDepth(depth int) logr.LogSink {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\t// Note: Usually WithCallDepth would be implemented by cloning l\n\t// and setting the call depth on the clone. We modify l instead in\n\t// this test helper for simplicity.\n\tl.callDepth = depth + 1\n\treturn l\n}\n\nfunc (l *callDepthTestLogr) Info(_ int, msg string, keysAndValues ...interface{}) {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\t// Add 2 to depth for the wrapper function caller and for invocation in\n\t// test case.\n\t_, file, line, _ := runtime.Caller(l.callDepth + 2)\n\tl.entries = append(l.entries, testLogrEntry{\n\t\tseverity:      severity.InfoLog,\n\t\tmsg:           msg,\n\t\tkeysAndValues: append([]interface{}{file, line}, keysAndValues...),\n\t})\n}\n\nfunc (l *callDepthTestLogr) Error(err error, msg string, keysAndValues ...interface{}) {\n\tl.mutex.Lock()\n\tdefer l.mutex.Unlock()\n\t// Add 2 to depth for the wrapper function caller and for invocation in\n\t// test case.\n\t_, file, line, _ := runtime.Caller(l.callDepth + 2)\n\tl.entries = append(l.entries, testLogrEntry{\n\t\tseverity:      severity.ErrorLog,\n\t\tmsg:           msg,\n\t\tkeysAndValues: append([]interface{}{file, line}, keysAndValues...),\n\t\terr:           err,\n\t})\n}\n\nvar _ logr.LogSink = &callDepthTestLogr{}\nvar _ logr.CallDepthLogSink = &callDepthTestLogr{}\n\nfunc checkLogrEntryCorrectCaller(t *testing.T, wantFile string, wantLine int, entry testLogrEntry) {\n\tt.Helper()\n\n\twant := fmt.Sprintf(\"%s:%d\", wantFile, wantLine)\n\t// Log fields contain file and line number as first elements.\n\tgot := fmt.Sprintf(\"%s:%d\", entry.keysAndValues[0], entry.keysAndValues[1])\n\n\tif want != got {\n\t\tt.Errorf(\"expected file and line %q but got %q\", want, got)\n\t}\n}\n\n// existedFlag contains all existed flag, without KlogPrefix\nvar existedFlag = map[string]struct{}{\n\t\"log_dir\":                          {},\n\t\"add_dir_header\":                   {},\n\t\"alsologtostderr\":                  {},\n\t\"alsologtostderrthreshold\":         {},\n\t\"legacy_stderr_threshold_behavior\": {},\n\t\"log_backtrace_at\":                 {},\n\t\"log_file\":                         {},\n\t\"log_file_max_size\":                {},\n\t\"logtostderr\":                      {},\n\t\"one_output\":                       {},\n\t\"skip_headers\":                     {},\n\t\"skip_log_headers\":                 {},\n\t\"stderrthreshold\":                  {},\n\t\"v\":                                {},\n\t\"vmodule\":                          {},\n}\n\n// KlogPrefix define new flag prefix\nconst KlogPrefix string = \"klog\"\n\n// TestKlogFlagPrefix check every klog flag's prefix, exclude flag in existedFlag\nfunc TestKlogFlagPrefix(t *testing.T) {\n\tfs := &flag.FlagSet{}\n\tInitFlags(fs)\n\tfs.VisitAll(func(f *flag.Flag) {\n\t\tif _, found := existedFlag[f.Name]; !found {\n\t\t\tif !strings.HasPrefix(f.Name, KlogPrefix) {\n\t\t\t\tt.Errorf(\"flag %s not have klog prefix: %s\", f.Name, KlogPrefix)\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc TestKObjs(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tobj  interface{}\n\t\twant []ObjectRef\n\t}{\n\t\t{\n\t\t\tname: \"test for KObjs function with KMetadata slice\",\n\t\t\tobj: []test.KMetadataMock{\n\t\t\t\t{\n\t\t\t\t\tName: \"kube-dns\",\n\t\t\t\t\tNS:   \"kube-system\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"mi-conf\",\n\t\t\t\t},\n\t\t\t\t{},\n\t\t\t},\n\t\t\twant: []ObjectRef{\n\t\t\t\t{\n\t\t\t\t\tName:      \"kube-dns\",\n\t\t\t\t\tNamespace: \"kube-system\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"mi-conf\",\n\t\t\t\t},\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test for KObjs function with KMetadata pointer slice\",\n\t\t\tobj: []*test.KMetadataMock{\n\t\t\t\t{\n\t\t\t\t\tName: \"kube-dns\",\n\t\t\t\t\tNS:   \"kube-system\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"mi-conf\",\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t},\n\t\t\twant: []ObjectRef{\n\t\t\t\t{\n\t\t\t\t\tName:      \"kube-dns\",\n\t\t\t\t\tNamespace: \"kube-system\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName: \"mi-conf\",\n\t\t\t\t},\n\t\t\t\t{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test for KObjs function with slice does not implement KMetadata\",\n\t\t\tobj:  []int{1, 2, 3, 4, 6},\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"test for KObjs function with interface\",\n\t\t\tobj:  \"test case\",\n\t\t\twant: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"test for KObjs function with nil\",\n\t\t\tobj:  nil,\n\t\t\twant: nil,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif !reflect.DeepEqual(KObjs(tt.obj), tt.want) {\n\t\t\t\tt.Errorf(\"\\nwant:\\t %v\\n got:\\t %v\", tt.want, KObjs(tt.obj))\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Benchmark test for lock with/without defer\ntype structWithLock struct {\n\tm sync.Mutex\n\tn int64\n}\n\nfunc BenchmarkWithoutDeferUnLock(b *testing.B) {\n\ts := structWithLock{}\n\tfor i := 0; i < b.N; i++ {\n\t\ts.addWithoutDefer()\n\t}\n}\n\nfunc BenchmarkWithDeferUnLock(b *testing.B) {\n\ts := structWithLock{}\n\tfor i := 0; i < b.N; i++ {\n\t\ts.addWithDefer()\n\t}\n}\n\nfunc (s *structWithLock) addWithoutDefer() {\n\ts.m.Lock()\n\ts.n++\n\ts.m.Unlock()\n}\n\nfunc (s *structWithLock) addWithDefer() {\n\ts.m.Lock()\n\tdefer s.m.Unlock()\n\ts.n++\n}\n\nfunc TestFlushDaemon(t *testing.T) {\n\tfor sev := severity.InfoLog; sev < severity.FatalLog; sev++ {\n\t\tflushed := make(chan struct{}, 1)\n\t\tspyFunc := func() {\n\t\t\tflushed <- struct{}{}\n\t\t}\n\t\ttestClock := testingclock.NewFakeClock(time.Now())\n\t\ttestLog := loggingT{\n\t\t\tsettings: settings{\n\t\t\t\tflushInterval: time.Second,\n\t\t\t},\n\t\t\tflushD: newFlushDaemon(spyFunc, testClock),\n\t\t}\n\n\t\t// Calling testLog will call createFile, which should start the daemon.\n\t\ttestLog.print(sev, nil, nil, \"x\")\n\n\t\tif !testLog.flushD.isRunning() {\n\t\t\tt.Error(\"expected flushD to be running\")\n\t\t}\n\n\t\ttimer := time.NewTimer(10 * time.Second)\n\t\tdefer timer.Stop()\n\t\ttestClock.Step(time.Second)\n\t\tselect {\n\t\tcase <-flushed:\n\t\tcase <-timer.C:\n\t\t\tt.Fatal(\"flushDaemon didn't call flush function on tick\")\n\t\t}\n\n\t\ttimer = time.NewTimer(10 * time.Second)\n\t\tdefer timer.Stop()\n\t\ttestClock.Step(time.Second)\n\t\tselect {\n\t\tcase <-flushed:\n\t\tcase <-timer.C:\n\t\t\tt.Fatal(\"flushDaemon didn't call flush function on second tick\")\n\t\t}\n\n\t\ttimer = time.NewTimer(10 * time.Second)\n\t\tdefer timer.Stop()\n\t\ttestLog.flushD.stop()\n\t\tselect {\n\t\tcase <-flushed:\n\t\tcase <-timer.C:\n\t\t\tt.Fatal(\"flushDaemon didn't call flush function one last time on stop\")\n\t\t}\n\t}\n}\n\nfunc TestStopFlushDaemon(t *testing.T) {\n\tlogging.flushD.stop()\n\tlogging.flushD = newFlushDaemon(func() {}, nil)\n\tlogging.flushD.run(time.Second)\n\tif !logging.flushD.isRunning() {\n\t\tt.Error(\"expected flushD to be running\")\n\t}\n\tStopFlushDaemon()\n\tif logging.flushD.isRunning() {\n\t\tt.Error(\"expected flushD to be stopped\")\n\t}\n}\n\nfunc TestCaptureState(t *testing.T) {\n\tvar fs flag.FlagSet\n\tInitFlags(&fs)\n\n\t// Capture state.\n\toldState := map[string]string{}\n\tfs.VisitAll(func(f *flag.Flag) {\n\t\toldState[f.Name] = f.Value.String()\n\t})\n\toriginalLogger := Background()\n\tfile := logging.file\n\n\t// And through dedicated API.\n\t// Ensure we always restore.\n\tstate := CaptureState()\n\tdefer state.Restore()\n\n\t// Change state.\n\tfor name, value := range map[string]string{\n\t\t// All of these are non-standard values.\n\t\t\"v\":                 \"10\",\n\t\t\"vmodule\":           \"abc=2\",\n\t\t\"log_dir\":           \"/tmp\",\n\t\t\"log_file_max_size\": \"10\",\n\t\t\"logtostderr\":       \"false\",\n\t\t\"alsologtostderr\":   \"true\",\n\t\t\"add_dir_header\":    \"true\",\n\t\t\"skip_headers\":      \"true\",\n\t\t\"one_output\":        \"true\",\n\t\t\"skip_log_headers\":  \"true\",\n\t\t\"stderrthreshold\":   \"1\",\n\t\t\"log_backtrace_at\":  \"foobar.go:100\",\n\t} {\n\t\tf := fs.Lookup(name)\n\t\tif f == nil {\n\t\t\tt.Fatalf(\"could not look up %q\", name)\n\t\t}\n\t\tcurrentValue := f.Value.String()\n\t\tif currentValue == value {\n\t\t\tt.Fatalf(\"%q is already set to non-default %q?!\", name, value)\n\t\t}\n\t\tif err := f.Value.Set(value); err != nil {\n\t\t\tt.Fatalf(\"setting %q to %q: %v\", name, value, err)\n\t\t}\n\t}\n\tStartFlushDaemon(time.Minute)\n\tif !logging.flushD.isRunning() {\n\t\tt.Error(\"Flush daemon should have been started.\")\n\t}\n\tlogger := logr.Discard()\n\tSetLoggerWithOptions(logger, ContextualLogger(true))\n\tactualLogger := Background()\n\tif logger != actualLogger {\n\t\tt.Errorf(\"Background logger should be %v, got %v\", logger, actualLogger)\n\t}\n\tbuffer := bytes.Buffer{}\n\tSetOutput(&buffer)\n\tif file == logging.file {\n\t\tt.Error(\"Output files should have been modified.\")\n\t}\n\n\t// Let klog restore the state.\n\tstate.Restore()\n\n\t// Verify that the original state is back.\n\tfs.VisitAll(func(f *flag.Flag) {\n\t\toldValue := oldState[f.Name]\n\t\tcurrentValue := f.Value.String()\n\t\tif oldValue != currentValue {\n\t\t\tt.Errorf(\"%q should have been restored to %q, is %q instead\", f.Name, oldValue, currentValue)\n\t\t}\n\t})\n\tif logging.flushD.isRunning() {\n\t\tt.Error(\"Flush daemon should have been stopped.\")\n\t}\n\tactualLogger = Background()\n\tif originalLogger != actualLogger {\n\t\tt.Errorf(\"Background logger should be %v, got %v\", originalLogger, actualLogger)\n\t}\n\tif file != logging.file {\n\t\tt.Errorf(\"Output files should have been restored to %v, got %v\", file, logging.file)\n\t}\n}\n\nfunc TestSettingsDeepCopy(t *testing.T) {\n\tlogger := logr.Discard()\n\n\tsettings := settings{\n\t\tlogger: &logWriter{Logger: logger},\n\t\tvmodule: moduleSpec{\n\t\t\tfilter: []modulePat{\n\t\t\t\t{pattern: \"a\"},\n\t\t\t\t{pattern: \"b\"},\n\t\t\t\t{pattern: \"c\"},\n\t\t\t},\n\t\t},\n\t}\n\tclone := settings.deepCopy()\n\tif !reflect.DeepEqual(settings, clone) {\n\t\tt.Fatalf(\"Copy not identical to original settings. Original:\\n    %+v\\nCopy:    %+v\", settings, clone)\n\t}\n\tsettings.vmodule.filter[1].pattern = \"x\"\n\tif clone.vmodule.filter[1].pattern == settings.vmodule.filter[1].pattern {\n\t\tt.Fatal(\"Copy should not have shared vmodule.filter.\")\n\t}\n}\n"
  },
  {
    "path": "klog_wrappers_test.go",
    "content": "// Copyright 2020 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage klog\n\n// These helper functions must be in a separate source file because the\n// tests in klog_test.go compare the logged source code file name against\n// \"klog_test.go\". \"klog_wrappers_test.go\" must *not* be logged.\n\nfunc myInfoS(msg string, keyAndValues ...interface{}) {\n\tInfoSDepth(1, msg, keyAndValues...)\n}\n\nfunc myErrorS(err error, msg string, keyAndValues ...interface{}) {\n\tErrorSDepth(1, err, msg, keyAndValues...)\n}\n"
  },
  {
    "path": "klogr/README.md",
    "content": "# Minimal Go logging using klog\n\nThis package implements the [logr interface](https://github.com/go-logr/logr)\nin terms of Kubernetes' [klog](https://github.com/kubernetes/klog).  This\nprovides a relatively minimalist API to logging in Go, backed by a well-proven\nimplementation.\n\nBecause klogr was implemented before klog itself added supported for\nstructured logging, the default in klogr is to serialize key/value\npairs with JSON and log the result as text messages via klog. This\ndoes not work well when klog itself forwards output to a structured\nlogger.\n\nTherefore the recommended approach is to let klogr pass all log\nmessages through to klog and deal with structured logging there. Just\nbeware that the output of klog without a structured logger is meant to\nbe human-readable, in contrast to the JSON-based traditional format.\n\nThis is a BETA grade implementation.\n"
  },
  {
    "path": "klogr/calldepth-test/call_depth_helper_test.go",
    "content": "package calldepth\n\nimport (\n\t\"github.com/go-logr/logr\"\n)\n\n// Putting these functions into a separate file makes it possible to validate that\n// their source code file is *not* logged because of WithCallDepth(1).\n\nfunc myInfo(l logr.Logger, msg string) {\n\tl.WithCallDepth(2).Info(msg)\n}\n\nfunc myInfo2(l logr.Logger, msg string) {\n\tmyInfo(l.WithCallDepth(2), msg)\n}\n"
  },
  {
    "path": "klogr/calldepth-test/call_depth_main_test.go",
    "content": "// Package calldepth does black-box testing.\n//\n// Another intentional effect is that \"go test\" compiles\n// this into a separate binary which we need because\n// we have to configure klog differently that TestOutput.\n\npackage calldepth\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/test/require\"\n\t\"k8s.io/klog/v2/klogr\"\n)\n\nfunc TestCallDepth(t *testing.T) {\n\tklog.InitFlags(nil)\n\trequire.NoError(t, flag.CommandLine.Set(\"v\", \"10\"))\n\trequire.NoError(t, flag.CommandLine.Set(\"skip_headers\", \"false\"))\n\trequire.NoError(t, flag.CommandLine.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(t, flag.CommandLine.Set(\"alsologtostderr\", \"false\"))\n\trequire.NoError(t, flag.CommandLine.Set(\"stderrthreshold\", \"10\"))\n\tflag.Parse()\n\n\tt.Run(\"call-depth\", func(t *testing.T) {\n\t\tlogr := klogr.New()\n\n\t\t// hijack the klog output\n\t\ttmpWriteBuffer := bytes.NewBuffer(nil)\n\t\tklog.SetOutput(tmpWriteBuffer)\n\n\t\tvalidate := func(t *testing.T) {\n\t\t\toutput := tmpWriteBuffer.String()\n\t\t\tif !strings.Contains(output, \"call_depth_main_test.go:\") {\n\t\t\t\tt.Fatalf(\"output should have contained call_depth_main_test.go, got instead: %s\", output)\n\t\t\t}\n\t\t}\n\n\t\tt.Run(\"direct\", func(t *testing.T) {\n\t\t\tlogr.Info(\"hello world\")\n\t\t\tvalidate(t)\n\t\t})\n\n\t\tt.Run(\"indirect\", func(t *testing.T) {\n\t\t\tmyInfo(logr, \"hello world\")\n\t\t\tvalidate(t)\n\t\t})\n\n\t\tt.Run(\"nested\", func(t *testing.T) {\n\t\t\tmyInfo2(logr, \"hello world\")\n\t\t\tvalidate(t)\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "klogr/klogr.go",
    "content": "// Package klogr implements github.com/go-logr/logr.Logger in terms of\n// k8s.io/klog.\npackage klogr\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n)\n\nconst (\n\t// nameKey is used to log the `WithName` values as an additional attribute.\n\tnameKey = \"logger\"\n)\n\n// Option is a functional option that reconfigures the logger created with New.\ntype Option func(*klogger)\n\n// Format defines how log output is produced.\ntype Format string\n\nconst (\n\t// FormatSerialize tells klogr to turn key/value pairs into text itself\n\t// before invoking klog. Key/value pairs are sorted by key.\n\tFormatSerialize Format = \"Serialize\"\n\n\t// FormatKlog tells klogr to pass all text messages and key/value pairs\n\t// directly to klog. Klog itself then serializes in a human-readable\n\t// format and optionally passes on to a structure logging backend.\n\tFormatKlog Format = \"Klog\"\n)\n\n// WithFormat selects the output format.\nfunc WithFormat(format Format) Option {\n\treturn func(l *klogger) {\n\t\tl.format = format\n\t}\n}\n\n// New returns a logr.Logger which serializes output itself\n// and writes it via klog.\n//\n// Deprecated: this uses a custom, out-dated output format. Use textlogger.NewLogger instead.\nfunc New() logr.Logger {\n\treturn NewWithOptions(WithFormat(FormatSerialize))\n}\n\n// NewWithOptions returns a logr.Logger which serializes as determined\n// by the WithFormat option and writes via klog. The default is\n// FormatKlog.\n//\n// Deprecated: FormatSerialize is out-dated. For FormatKlog, use textlogger.NewLogger instead.\nfunc NewWithOptions(options ...Option) logr.Logger {\n\tl := klogger{\n\t\tlevel:  0,\n\t\tvalues: nil,\n\t\tformat: FormatKlog,\n\t}\n\tfor _, option := range options {\n\t\toption(&l)\n\t}\n\treturn logr.New(&l)\n}\n\ntype klogger struct {\n\tlevel     int\n\tcallDepth int\n\n\t// hasPrefix is true if the first entry in values is the special\n\t// nameKey key/value. Such an entry gets added and later updated in\n\t// WithName.\n\thasPrefix bool\n\n\tvalues []interface{}\n\tformat Format\n}\n\nfunc (l *klogger) Init(info logr.RuntimeInfo) {\n\tl.callDepth += info.CallDepth\n}\n\nfunc flatten(kvList ...interface{}) string {\n\tkeys := make([]string, 0, len(kvList))\n\tvals := make(map[string]interface{}, len(kvList))\n\tfor i := 0; i < len(kvList); i += 2 {\n\t\tk, ok := kvList[i].(string)\n\t\tif !ok {\n\t\t\tpanic(fmt.Sprintf(\"key is not a string: %s\", pretty(kvList[i])))\n\t\t}\n\t\tvar v interface{}\n\t\tif i+1 < len(kvList) {\n\t\t\tv = kvList[i+1]\n\t\t}\n\t\t// Only print each key once...\n\t\tif _, seen := vals[k]; !seen {\n\t\t\tkeys = append(keys, k)\n\t\t}\n\t\t// ... with the latest value.\n\t\tvals[k] = v\n\t}\n\tsort.Strings(keys)\n\tbuf := bytes.Buffer{}\n\tfor i, k := range keys {\n\t\tv := vals[k]\n\t\tif i > 0 {\n\t\t\tbuf.WriteRune(' ')\n\t\t}\n\t\tbuf.WriteString(pretty(k))\n\t\tbuf.WriteString(\"=\")\n\t\tbuf.WriteString(pretty(v))\n\t}\n\treturn buf.String()\n}\n\nfunc pretty(value interface{}) string {\n\tif err, ok := value.(error); ok {\n\t\tif _, ok := value.(json.Marshaler); !ok {\n\t\t\tvalue = err.Error()\n\t\t}\n\t}\n\tbuffer := &bytes.Buffer{}\n\tencoder := json.NewEncoder(buffer)\n\tencoder.SetEscapeHTML(false)\n\tif err := encoder.Encode(value); err != nil {\n\t\treturn fmt.Sprintf(\"<<error: %v>>\", err)\n\t}\n\treturn strings.TrimSpace(buffer.String())\n}\n\nfunc (l *klogger) Info(level int, msg string, kvList ...interface{}) {\n\tswitch l.format {\n\tcase FormatSerialize:\n\t\tmsgStr := flatten(\"msg\", msg)\n\t\tmerged := serialize.WithValues(l.values, kvList)\n\t\tkvStr := flatten(merged...)\n\t\tklog.VDepth(l.callDepth+1, klog.Level(level)).InfoDepth(l.callDepth+1, msgStr, \" \", kvStr)\n\tcase FormatKlog:\n\t\tmerged := serialize.WithValues(l.values, kvList)\n\t\tklog.VDepth(l.callDepth+1, klog.Level(level)).InfoSDepth(l.callDepth+1, msg, merged...)\n\t}\n}\n\nfunc (l *klogger) Enabled(level int) bool {\n\treturn klog.VDepth(l.callDepth+1, klog.Level(level)).Enabled()\n}\n\nfunc (l *klogger) Error(err error, msg string, kvList ...interface{}) {\n\tmsgStr := flatten(\"msg\", msg)\n\tvar loggableErr interface{}\n\tif err != nil {\n\t\tloggableErr = serialize.ErrorToString(err)\n\t}\n\tswitch l.format {\n\tcase FormatSerialize:\n\t\terrStr := flatten(\"error\", loggableErr)\n\t\tmerged := serialize.WithValues(l.values, kvList)\n\t\tkvStr := flatten(merged...)\n\t\tklog.ErrorDepth(l.callDepth+1, msgStr, \" \", errStr, \" \", kvStr)\n\tcase FormatKlog:\n\t\tmerged := serialize.WithValues(l.values, kvList)\n\t\tklog.ErrorSDepth(l.callDepth+1, err, msg, merged...)\n\t}\n}\n\n// WithName returns a new logr.Logger with the specified name appended.  klogr\n// uses '.' characters to separate name elements.  Callers should not pass '.'\n// in the provided name string, but this library does not actually enforce that.\nfunc (l klogger) WithName(name string) logr.LogSink {\n\tif l.hasPrefix {\n\t\t// Copy slice and modify value. No length checks and type\n\t\t// assertions are needed because hasPrefix is only true if the\n\t\t// first two elements exist and are key/value strings.\n\t\tv := make([]interface{}, 0, len(l.values))\n\t\tv = append(v, l.values...)\n\t\tprefix, _ := v[1].(string)\n\t\tprefix = prefix + \".\" + name\n\t\tv[1] = prefix\n\t\tl.values = v\n\t} else {\n\t\t// Preprend new key/value pair.\n\t\tv := make([]interface{}, 0, 2+len(l.values))\n\t\tv = append(v, nameKey, name)\n\t\tv = append(v, l.values...)\n\t\tl.values = v\n\t\tl.hasPrefix = true\n\t}\n\treturn &l\n}\n\nfunc (l klogger) WithValues(kvList ...interface{}) logr.LogSink {\n\tl.values = serialize.WithValues(l.values, kvList)\n\treturn &l\n}\n\nfunc (l klogger) WithCallDepth(depth int) logr.LogSink {\n\tl.callDepth += depth\n\treturn &l\n}\n\nvar _ logr.LogSink = &klogger{}\nvar _ logr.CallDepthLogSink = &klogger{}\n"
  },
  {
    "path": "klogr/klogr_test.go",
    "content": "package klogr\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/test/require\"\n\t\"k8s.io/klog/v2/test\"\n\n\t\"github.com/go-logr/logr\"\n)\n\nconst (\n\tformatDefault = \"Default\"\n\tformatNew     = \"New\"\n)\n\nfunc testOutput(t *testing.T, format string) {\n\tcreateLogger := func() logr.Logger {\n\t\tswitch format {\n\t\tcase formatNew:\n\t\t\treturn New()\n\t\tcase formatDefault:\n\t\t\treturn NewWithOptions()\n\t\tdefault:\n\t\t\treturn NewWithOptions(WithFormat(Format(format)))\n\t\t}\n\t}\n\ttests := map[string]struct {\n\t\tklogr              logr.Logger\n\t\ttext               string\n\t\tkeysAndValues      []interface{}\n\t\terr                error\n\t\texpectedOutput     string\n\t\texpectedKlogOutput string\n\t}{\n\t\t\"should log with values passed to keysAndValues\": {\n\t\t\tklogr:         createLogger().V(0),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should log with name and values passed to keysAndValues\": {\n\t\t\tklogr:         createLogger().V(0).WithName(\"me\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\t// Sorted by keys.\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\" \"logger\"=\"me\"\n`,\n\t\t\t// Not sorted by keys.\n\t\t\texpectedKlogOutput: `\"test\" logger=\"me\" akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should log with multiple names and values passed to keysAndValues\": {\n\t\t\tklogr:         createLogger().V(0).WithName(\"hello\").WithName(\"world\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\t// Sorted by keys.\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\" \"logger\"=\"hello.world\"\n`,\n\t\t\t// Not sorted by keys.\n\t\t\texpectedKlogOutput: `\"test\" logger=\"hello.world\" akey=\"avalue\"\n`,\n\t\t},\n\t\t\"de-duplicate keys with the same value\": {\n\t\t\tklogr:         createLogger().V(0),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey\", \"avalue\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue\"\n`,\n\t\t},\n\t\t\"de-duplicate keys when the values are passed to Info\": {\n\t\t\tklogr:         createLogger().V(0),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey\", \"avalue2\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue2\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue2\"\n`,\n\t\t},\n\t\t\"should only print the duplicate key that is passed to Info if one was passed to the logger\": {\n\t\t\tklogr:         createLogger().WithValues(\"akey\", \"avalue\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should sort within logger and parameter key/value pairs in the default format and dump the logger pairs first\": {\n\t\t\tklogr:         createLogger().WithValues(\"akey9\", \"avalue9\", \"akey8\", \"avalue8\", \"akey1\", \"avalue1\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey5\", \"avalue5\", \"akey4\", \"avalue4\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey1\"=\"avalue1\" \"akey4\"=\"avalue4\" \"akey5\"=\"avalue5\" \"akey8\"=\"avalue8\" \"akey9\"=\"avalue9\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey9=\"avalue9\" akey8=\"avalue8\" akey1=\"avalue1\" akey5=\"avalue5\" akey4=\"avalue4\"\n`,\n\t\t},\n\t\t\"should only print the key passed to Info when one is already set on the logger\": {\n\t\t\tklogr:         createLogger().WithValues(\"akey\", \"avalue\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue2\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue2\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue2\"\n`,\n\t\t},\n\t\t\"should correctly handle odd-numbers of KVs\": {\n\t\t\tklogr:         createLogger(),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\" \"akey2\"=\"(MISSING)\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t\t},\n\t\t\"should correctly handle odd-numbers of KVs in WithValue\": {\n\t\t\tklogr:         createLogger().WithValues(\"keyWithoutValue\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\t\t// klogr format sorts all key/value pairs.\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\" \"akey2\"=\"(MISSING)\" \"keyWithoutValue\"=\"(MISSING)\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" keyWithoutValue=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t\t},\n\t\t\"should correctly html characters\": {\n\t\t\tklogr:         createLogger(),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"<&>\"},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"<&>\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" akey=\"<&>\"\n`,\n\t\t},\n\t\t\"should correctly handle odd-numbers of KVs in both log values and Info args\": {\n\t\t\tklogr:         createLogger().WithValues(\"basekey1\", \"basevar1\", \"basekey2\"),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\t\t// klogr format sorts all key/value pairs.\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"akey\"=\"avalue\" \"akey2\"=\"(MISSING)\" \"basekey1\"=\"basevar1\" \"basekey2\"=\"(MISSING)\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t\t},\n\t\t\"should correctly print regular error types\": {\n\t\t\tklogr:         createLogger().V(0),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"err\", errors.New(\"whoops\")},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"err\"=\"whoops\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" err=\"whoops\"\n`,\n\t\t},\n\t\t\"should use MarshalJSON in the default format if an error type implements it\": {\n\t\t\tklogr:         createLogger().V(0),\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"err\", &customErrorJSON{\"whoops\"}},\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"err\"=\"WHOOPS\"\n`,\n\t\t\texpectedKlogOutput: `\"test\" err=\"whoops\"\n`,\n\t\t},\n\t\t\"should correctly print regular error types when using logr.Error\": {\n\t\t\tklogr: createLogger().V(0),\n\t\t\ttext:  \"test\",\n\t\t\terr:   errors.New(\"whoops\"),\n\t\t\texpectedOutput: `\"msg\"=\"test\" \"error\"=\"whoops\" \n`,\n\t\t\texpectedKlogOutput: `\"test\" err=\"whoops\"\n`,\n\t\t},\n\t}\n\tfor n, test := range tests {\n\t\tt.Run(n, func(t *testing.T) {\n\n\t\t\t// hijack the klog output\n\t\t\ttmpWriteBuffer := bytes.NewBuffer(nil)\n\t\t\tklog.SetOutput(tmpWriteBuffer)\n\n\t\t\tif test.err != nil {\n\t\t\t\ttest.klogr.Error(test.err, test.text, test.keysAndValues...)\n\t\t\t} else {\n\t\t\t\ttest.klogr.Info(test.text, test.keysAndValues...)\n\t\t\t}\n\n\t\t\t// call Flush to ensure the text isn't still buffered\n\t\t\tklog.Flush()\n\n\t\t\tactual := tmpWriteBuffer.String()\n\t\t\texpectedOutput := test.expectedOutput\n\t\t\tif format == string(FormatKlog) || format == formatDefault {\n\t\t\t\texpectedOutput = test.expectedKlogOutput\n\t\t\t}\n\t\t\tif actual != expectedOutput {\n\t\t\t\tt.Errorf(\"Expected:\\n%s\\nActual:\\n%s\\n\", expectedOutput, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestOutput(t *testing.T) {\n\tfs := test.InitKlog(t)\n\trequire.NoError(t, fs.Set(\"skip_headers\", \"true\"))\n\n\tformats := []string{\n\t\tformatNew,\n\t\tformatDefault,\n\t\tstring(FormatSerialize),\n\t\tstring(FormatKlog),\n\t}\n\tfor _, format := range formats {\n\t\tt.Run(format, func(t *testing.T) {\n\t\t\ttestOutput(t, format)\n\t\t})\n\t}\n}\n\ntype customErrorJSON struct {\n\ts string\n}\n\nfunc (e *customErrorJSON) Error() string {\n\treturn e.s\n}\n\nfunc (e *customErrorJSON) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(strings.ToUpper(e.s))\n}\n"
  },
  {
    "path": "klogr/output_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klogr_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/klogr\"\n\t\"k8s.io/klog/v2/test\"\n)\n\n// TestKlogrOutput tests klogr output via klog.\nfunc TestKlogrOutput(t *testing.T) {\n\ttest.InitKlog(t)\n\ttest.Output(t, test.OutputConfig{\n\t\tNewLogger: func(_ io.Writer, _ int, _ string) logr.Logger {\n\t\t\treturn klogr.NewWithOptions(klogr.WithFormat(klogr.FormatKlog))\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "klogr.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/internal/serialize\"\n)\n\nconst (\n\t// nameKey is used to log the `WithName` values as an additional attribute.\n\tnameKey = \"logger\"\n)\n\n// NewKlogr returns a logger that is functionally identical to\n// klogr.NewWithOptions(klogr.FormatKlog), i.e. it passes through to klog. The\n// difference is that it uses a simpler implementation.\nfunc NewKlogr() Logger {\n\treturn New(&klogger{})\n}\n\n// klogger is a subset of klogr/klogr.go. It had to be copied to break an\n// import cycle (klogr wants to use klog, and klog wants to use klogr).\ntype klogger struct {\n\tcallDepth int\n\n\t// hasPrefix is true if the first entry in values is the special\n\t// nameKey key/value. Such an entry gets added and later updated in\n\t// WithName.\n\thasPrefix bool\n\n\tvalues []interface{}\n\tgroups string\n}\n\nfunc (l *klogger) Init(info logr.RuntimeInfo) {\n\tl.callDepth += info.CallDepth\n}\n\nfunc (l *klogger) Info(level int, msg string, kvList ...interface{}) {\n\tmerged := serialize.WithValues(l.values, kvList)\n\t// Skip this function.\n\tVDepth(l.callDepth+1, Level(level)).InfoSDepth(l.callDepth+1, msg, merged...)\n}\n\nfunc (l *klogger) Enabled(level int) bool {\n\treturn VDepth(l.callDepth+1, Level(level)).Enabled()\n}\n\nfunc (l *klogger) Error(err error, msg string, kvList ...interface{}) {\n\tmerged := serialize.WithValues(l.values, kvList)\n\tErrorSDepth(l.callDepth+1, err, msg, merged...)\n}\n\n// WithName returns a new logr.Logger with the specified name appended.  klogr\n// uses '.' characters to separate name elements.  Callers should not pass '.'\n// in the provided name string, but this library does not actually enforce that.\nfunc (l klogger) WithName(name string) logr.LogSink {\n\tif l.hasPrefix {\n\t\t// Copy slice and modify value. No length checks and type\n\t\t// assertions are needed because hasPrefix is only true if the\n\t\t// first two elements exist and are key/value strings.\n\t\tv := make([]interface{}, 0, len(l.values))\n\t\tv = append(v, l.values...)\n\t\tprefix, _ := v[1].(string)\n\t\tv[1] = prefix + \".\" + name\n\t\tl.values = v\n\t} else {\n\t\t// Preprend new key/value pair.\n\t\tv := make([]interface{}, 0, 2+len(l.values))\n\t\tv = append(v, nameKey, name)\n\t\tv = append(v, l.values...)\n\t\tl.values = v\n\t\tl.hasPrefix = true\n\t}\n\treturn &l\n}\n\nfunc (l klogger) WithValues(kvList ...interface{}) logr.LogSink {\n\tl.values = serialize.WithValues(l.values, kvList)\n\treturn &l\n}\n\nfunc (l klogger) WithCallDepth(depth int) logr.LogSink {\n\tl.callDepth += depth\n\treturn &l\n}\n\nvar _ logr.LogSink = &klogger{}\nvar _ logr.CallDepthLogSink = &klogger{}\n"
  },
  {
    "path": "klogr_helper_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc testVerbosity(t *testing.T, logger klog.Logger) {\n\t// This runs with -v=5 -vmodule=klog_helper_test=10.\n\tlogger.V(11).Info(\"v11 not visible from klogr_helper_test.go\")\n\tif logger.V(11).Enabled() {\n\t\tt.Error(\"V(11).Enabled() in klogr_helper_test.go should have returned false.\")\n\t}\n\tlogger.V(10).Info(\"v10 visible from klogr_helper_test.go\")\n\tif !logger.V(10).Enabled() {\n\t\tt.Error(\"V(10).Enabled() in klogr_helper_test.go should have returned true.\")\n\t}\n}\n"
  },
  {
    "path": "klogr_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/severity\"\n\t\"k8s.io/klog/v2/internal/sloghandler\"\n)\n\nfunc (l *klogger) Handle(ctx context.Context, record slog.Record) error {\n\tif logging.logger != nil {\n\t\tif slogSink, ok := logging.logger.GetSink().(logr.SlogSink); ok {\n\t\t\t// Let that logger do the work.\n\t\t\treturn slogSink.Handle(ctx, record)\n\t\t}\n\t}\n\n\treturn sloghandler.Handle(ctx, record, l.groups, slogOutput)\n}\n\n// slogOutput corresponds to several different functions in klog.go.\n// It goes through some of the same checks and formatting steps before\n// it ultimately converges by calling logging.printWithInfos.\nfunc slogOutput(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) {\n\t// See infoS.\n\tif logging.logger != nil {\n\t\t// Taking this path happens when klog has a logger installed\n\t\t// as backend which doesn't support slog. Not good, we have to\n\t\t// guess about the call depth and drop the actual location.\n\t\tlogger := logging.logger.WithCallDepth(2)\n\t\tif s > severity.ErrorLog {\n\t\t\tlogger.Error(err, msg, kvList...)\n\t\t} else {\n\t\t\tlogger.Info(msg, kvList...)\n\t\t}\n\t\treturn\n\t}\n\n\t// See printS.\n\tqMsg := make([]byte, 0, 1024)\n\tqMsg = strconv.AppendQuote(qMsg, msg)\n\n\tb := buffer.GetBuffer()\n\tb.Write(qMsg)\n\n\tvar errKV []interface{}\n\tif err != nil {\n\t\terrKV = []interface{}{\"err\", err}\n\t}\n\tserialize.FormatKVs(&b.Buffer, errKV, kvList)\n\n\t// See print + header.\n\tbuf := logging.formatHeader(s, file, line, now)\n\tlogging.printWithInfos(buf, file, line, s, nil, nil, 0, &b.Buffer)\n\n\tbuffer.PutBuffer(b)\n}\n\nfunc (l *klogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {\n\tclone := *l\n\tclone.values = serialize.WithValues(l.values, sloghandler.Attrs2KVList(l.groups, attrs))\n\treturn &clone\n}\n\nfunc (l *klogger) WithGroup(name string) logr.SlogSink {\n\tclone := *l\n\tif clone.groups != \"\" {\n\t\tclone.groups += \".\" + name\n\t} else {\n\t\tclone.groups = name\n\t}\n\treturn &clone\n}\n\nvar _ logr.SlogSink = &klogger{}\n"
  },
  {
    "path": "klogr_slog_test.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n\tinternal \"k8s.io/klog/v2/internal/buffer\"\n)\n\nvar _ slog.LogValuer = coordinates{}\n\ntype coordinates struct {\n\tx, y int\n}\n\nfunc (c coordinates) LogValue() slog.Value {\n\treturn slog.GroupValue(slog.Attr{Key: \"X\", Value: slog.IntValue(c.x)}, slog.Attr{Key: \"Y\", Value: slog.IntValue(c.y)})\n}\n\nfunc ExampleBackground_slog() {\n\t// Temporarily reconfigure for output to stdout, with -v=4.\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\tif err := fs.Set(\"logtostderr\", \"false\"); err != nil {\n\t\tfmt.Println(err)\n\t}\n\tif err := fs.Set(\"alsologtostderr\", \"false\"); err != nil {\n\t\tfmt.Println(err)\n\t}\n\tif err := fs.Set(\"v\", \"4\"); err != nil {\n\t\tfmt.Println(err)\n\t}\n\tif err := fs.Set(\"one_output\", \"true\"); err != nil {\n\t\tfmt.Println(err)\n\t}\n\tif err := fs.Set(\"skip_headers\", \"false\"); err != nil {\n\t\tfmt.Println(err)\n\t}\n\tklog.SetOutput(os.Stdout)\n\n\t// To get consistent output for each run.\n\tts, _ := time.Parse(time.RFC3339, \"2000-12-24T12:30:40Z\")\n\tinternal.Time = &ts\n\tinternal.Pid = 123\n\n\tlogrLogger := klog.Background()\n\tslogHandler := logr.ToSlogHandler(logrLogger)\n\tslogLogger := slog.New(slogHandler)\n\n\t// Note that -vmodule does not work when using the slog API because\n\t// stack unwinding during the Enabled check leads to the wrong source\n\t// code.\n\tslogLogger.Debug(\"A debug message\")\n\tslogLogger.Log(nil, slog.LevelDebug-1, \"A debug message with even lower level, not printed.\")\n\tslogLogger.Info(\"An info message\")\n\tslogLogger.Warn(\"A warning\")\n\tslogLogger.Error(\"An error\", \"err\", errors.New(\"fake error\"))\n\n\t// The slog API supports grouping, in contrast to the logr API.\n\tslogLogger.WithGroup(\"top\").With(\"int\", 42, slog.Group(\"variables\", \"a\", 1, \"b\", 2)).Info(\"Grouping\",\n\t\t\"sub\", slog.GroupValue(\n\t\t\tslog.Attr{Key: \"str\", Value: slog.StringValue(\"abc\")},\n\t\t\tslog.Attr{Key: \"bool\", Value: slog.BoolValue(true)},\n\t\t\tslog.Attr{Key: \"bottom\", Value: slog.GroupValue(slog.Attr{Key: \"coordinates\", Value: slog.AnyValue(coordinates{x: -1, y: -2})})},\n\t\t),\n\t\t\"duration\", slog.DurationValue(time.Second),\n\t\tslog.Float64(\"pi\", 3.12),\n\t\t\"e\", 2.71,\n\t\t\"moreCoordinates\", coordinates{x: 100, y: 200},\n\t)\n\n\t// slog special values are also supported when passed through the logr API.\n\t// This works with the textlogger, but might not work with other implementations\n\t// and thus isn't portable. Passing attributes (= key and value in a single\n\t// parameter) is not supported.\n\tlogrLogger.Info(\"slog values\",\n\t\t\"variables\", slog.GroupValue(slog.Int(\"a\", 1), slog.Int(\"b\", 2)),\n\t\t\"duration\", slog.DurationValue(time.Second),\n\t\t\"coordinates\", coordinates{x: 100, y: 200},\n\t)\n\n\t// Output:\n\t// I1224 12:30:40.000000     123 klogr_slog_test.go:81] \"A debug message\"\n\t// I1224 12:30:40.000000     123 klogr_slog_test.go:83] \"An info message\"\n\t// W1224 12:30:40.000000     123 klogr_slog_test.go:84] \"A warning\"\n\t// E1224 12:30:40.000000     123 klogr_slog_test.go:85] \"An error\" err=\"fake error\"\n\t// I1224 12:30:40.000000     123 klogr_slog_test.go:88] \"Grouping\" top.sub={\"str\":\"abc\",\"bool\":true,\"bottom\":{\"coordinates\":{\"X\":-1,\"Y\":-2}}} top.duration=\"1s\" top.pi=3.12 top.e=2.71 top.moreCoordinates={\"X\":100,\"Y\":200}\n\t// I1224 12:30:40.000000     123 klogr_slog_test.go:104] \"slog values\" variables={\"a\":1,\"b\":2} duration=\"1s\" coordinates={\"X\":100,\"Y\":200}\n}\n"
  },
  {
    "path": "klogr_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc TestVerbosity(t *testing.T) {\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\tif err := fs.Set(\"v\", \"5\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := fs.Set(\"vmodule\", \"klogr_helper_test=10\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := fs.Set(\"logtostderr\", \"false\"); err != nil {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tvar buffer bytes.Buffer\n\tklog.SetOutput(&buffer)\n\tlogger := klog.Background()\n\n\t// -v=5 is in effect here.\n\tlogger.V(6).Info(\"v6 not visible from klogr_test.go\")\n\tif logger.V(6).Enabled() {\n\t\tt.Error(\"V(6).Enabled() in klogr_test.go should have returned false.\")\n\t}\n\tlogger.V(5).Info(\"v5 visible from klogr_test.go\")\n\tif !logger.V(5).Enabled() {\n\t\tt.Error(\"V(5).Enabled() in klogr_test.go should have returned true.\")\n\t}\n\n\t// Now test with -v=5 -vmodule=klogr_helper_test=10.\n\ttestVerbosity(t, logger)\n\n\tklog.Flush()\n\texpected := `^.*v5 visible from klogr_test.go.*\n.*v10 visible from klogr_helper_test.go.*\n`\n\tif !regexp.MustCompile(expected).Match(buffer.Bytes()) {\n\t\tt.Errorf(\"Output did not match regular expression.\\nOutput:\\n%s\\n\\nRegular expression:\\n%s\\n\", buffer.String(), expected)\n\t}\n}\n"
  },
  {
    "path": "ktesting/contextual_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nCopyright 2020 Intel Corporation.\n\nSPDX-License-Identifier: Apache-2.0\n*/\n\npackage ktesting_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/ktesting\"\n)\n\nfunc TestContextual(t *testing.T) {\n\tvar buffer ktesting.BufferTL\n\tlogger, ctx := ktesting.NewTestContext(&buffer)\n\n\tdoSomething(ctx)\n\n\t// When contextual logging is disabled, the output goes to klog\n\t// instead of the testing logger.\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\tklog.EnableContextualLogging(false)\n\tdoSomething(ctx)\n\n\ttestingLogger, ok := logger.GetSink().(ktesting.Underlier)\n\tif !ok {\n\t\tt.Fatal(\"Should have had a ktesting LogSink!?\")\n\t}\n\n\tactual := testingLogger.GetBuffer().String()\n\tif actual != \"\" {\n\t\tt.Errorf(\"testinglogger should not have buffered, got:\\n%s\", actual)\n\t}\n\n\tactual = buffer.String()\n\tactual = headerRe.ReplaceAllString(actual, \"${1}xxx] \")\n\texpected := `Ixxx] hello world\nIxxx] foo: hello also from me\n`\n\tif actual != expected {\n\t\tt.Errorf(\"mismatch in captured output, expected:\\n%s\\ngot:\\n%s\\n\", expected, actual)\n\t}\n}\n\nfunc doSomething(ctx context.Context) {\n\tlogger := klog.FromContext(ctx)\n\tlogger.Info(\"hello world\")\n\n\tlogger = logger.WithName(\"foo\")\n\tctx = klog.NewContext(ctx, logger)\n\tdoSomeMore(ctx)\n}\n\nfunc doSomeMore(ctx context.Context) {\n\tlogger := klog.FromContext(ctx)\n\tlogger.Info(\"hello also from me\")\n}\n"
  },
  {
    "path": "ktesting/example/example_test.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nSPDX-License-Identifier: Apache-2.0\n*/\n\npackage example\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/test\"\n\t\"k8s.io/klog/v2/ktesting\"\n\t_ \"k8s.io/klog/v2/ktesting/init\" // add command line flags\n)\n\nfunc TestKlogr(t *testing.T) {\n\tlogger, _ := ktesting.NewTestContext(t)\n\texampleOutput(logger)\n}\n\ntype pair struct {\n\ta, b int\n}\n\nfunc (p pair) String() string {\n\treturn fmt.Sprintf(\"(%d, %d)\", p.a, p.b)\n}\n\nvar _ fmt.Stringer = pair{}\n\ntype err struct {\n\tmsg string\n}\n\nfunc (e err) Error() string {\n\treturn \"failed: \" + e.msg\n}\n\nvar _ error = err{}\n\nfunc exampleOutput(logger klog.Logger) {\n\tlogger.Info(\"hello world\")\n\tlogger.Error(err{msg: \"some error\"}, \"failed\")\n\tlogger.V(1).Info(\"verbosity 1\")\n\tlogger.WithName(\"main\").WithName(\"helper\").Info(\"with prefix\")\n\tobj := test.KMetadataMock{Name: \"joe\", NS: \"kube-system\"}\n\tlogger.Info(\"key/value pairs\",\n\t\t\"int\", 1,\n\t\t\"float\", 2.0,\n\t\t\"pair\", pair{a: 1, b: 2},\n\t\t\"raw\", obj,\n\t\t\"kobj\", klog.KObj(obj),\n\t)\n\tlogger.V(4).Info(\"info message level 4\")\n\tlogger.V(5).Info(\"info message level 5\")\n\tlogger.V(6).Info(\"info message level 6\")\n}\n"
  },
  {
    "path": "ktesting/example_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ktesting_test\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/ktesting\"\n)\n\nfunc ExampleUnderlier() {\n\tlogger := ktesting.NewLogger(ktesting.NopTL{},\n\t\tktesting.NewConfig(\n\t\t\tktesting.Verbosity(4),\n\t\t\tktesting.BufferLogs(true),\n\t\t\tktesting.AnyToString(func(value interface{}) string {\n\t\t\t\t// This simple demo formats with %+v,\n\t\t\t\t// splits into multiple lines at spaces (much too simplistic for unknown data, but works here),\n\t\t\t\t// then adds ### as delimiters.\n\t\t\t\tstr := fmt.Sprintf(\"%+v\", value)\n\t\t\t\tstr = strings.ReplaceAll(str, \" \", \"\\n\")\n\t\t\t\treturn fmt.Sprintf(\"### %s ###\", str)\n\t\t\t}),\n\t\t),\n\t)\n\n\tlogger.Error(errors.New(\"failure\"), \"I failed\", \"what\", \"something\",\n\t\t// The AnyToString callback logs this with multiple lines.\n\t\t\"data\", struct{ X, Y int }{10, 20},\n\t\t// This with a single line.\n\t\t\"moreData\", struct{ Field int }{Field: 1},\n\t)\n\tlogger.WithValues(\"request\", 42).WithValues(\"anotherValue\", \"fish\").Info(\"hello world\")\n\tlogger.WithValues(\"request\", 42, \"anotherValue\", \"fish\").Info(\"hello world 2\", \"yetAnotherValue\", \"thanks\")\n\tlogger.WithName(\"example\").Info(\"with name\")\n\tlogger.V(4).Info(\"higher verbosity\")\n\tlogger.V(5).Info(\"Not captured because of ktesting.Verbosity(4) above. Normally it would be captured because default verbosity is 5.\")\n\n\ttestingLogger, ok := logger.GetSink().(ktesting.Underlier)\n\tif !ok {\n\t\tpanic(\"Should have had a ktesting LogSink!?\")\n\t}\n\n\tt := testingLogger.GetUnderlying()\n\tt.Log(\"This goes to /dev/null...\")\n\n\tbuffer := testingLogger.GetBuffer()\n\tfmt.Printf(\"%s\\n\", buffer.String())\n\n\tlog := buffer.Data()\n\tfor i, entry := range log {\n\t\tif i > 0 &&\n\t\t\tentry.Timestamp.Sub(log[i-1].Timestamp).Nanoseconds() < 0 {\n\t\t\tfmt.Printf(\"Unexpected timestamp order: #%d %s > #%d %s\", i-1, log[i-1].Timestamp, i, entry.Timestamp)\n\t\t}\n\t\t// Strip varying time stamp before dumping the struct.\n\t\tentry.Timestamp = time.Time{}\n\t\tfmt.Printf(\"log entry #%d: %+v\\n\", i, entry)\n\t}\n\n\t// Output:\n\t// ERROR I failed err=\"failure\" what=\"something\" data=<\n\t// \t### {X:10\n\t// \tY:20} ###\n\t//  > moreData=### {Field:1} ###\n\t// INFO hello world request=### 42 ### anotherValue=\"fish\"\n\t// INFO hello world 2 request=### 42 ### anotherValue=\"fish\" yetAnotherValue=\"thanks\"\n\t// INFO example: with name\n\t// INFO higher verbosity\n\t//\n\t// 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 data {X:10 Y:20} moreData {Field:1}]}\n\t// log entry #1: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:hello world Verbosity:0 Err:<nil> WithKVList:[request 42 anotherValue fish] ParameterKVList:[]}\n\t// log entry #2: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:hello world 2 Verbosity:0 Err:<nil> WithKVList:[request 42 anotherValue fish] ParameterKVList:[yetAnotherValue thanks]}\n\t// log entry #3: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix:example Message:with name Verbosity:0 Err:<nil> WithKVList:[] ParameterKVList:[]}\n\t// log entry #4: {Timestamp:0001-01-01 00:00:00 +0000 UTC Type:INFO Prefix: Message:higher verbosity Verbosity:4 Err:<nil> WithKVList:[] ParameterKVList:[]}\n}\n\nfunc ExampleNewLogger() {\n\tvar buffer ktesting.BufferTL\n\tlogger := ktesting.NewLogger(&buffer, ktesting.NewConfig())\n\n\tlogger.Error(errors.New(\"failure\"), \"I failed\", \"what\", \"something\", \"data\", struct{ Field int }{Field: 1})\n\tlogger.V(5).Info(\"Logged at level 5.\")\n\tlogger.V(6).Info(\"Not logged at level 6.\")\n\n\ttestingLogger, ok := logger.GetSink().(ktesting.Underlier)\n\tif !ok {\n\t\tpanic(\"Should have had a ktesting LogSink!?\")\n\t}\n\tfmt.Printf(\">> %s <<\\n\", testingLogger.GetBuffer().String())       // Should be empty.\n\tfmt.Print(headerRe.ReplaceAllString(buffer.String(), \"${1}...] \")) // Should not be empty.\n\n\t// Output:\n\t// >>  <<\n\t// E...] I failed err=\"failure\" what=\"something\" data={\"Field\":1}\n\t// I...] Logged at level 5.\n}\n\nfunc ExampleConfig_Verbosity() {\n\tvar buffer ktesting.BufferTL\n\tconfig := ktesting.NewConfig(ktesting.Verbosity(1))\n\tlogger := ktesting.NewLogger(&buffer, config)\n\n\tlogger.Info(\"initial verbosity\", \"v\", config.Verbosity().String())\n\tlogger.V(2).Info(\"now you don't see me\")\n\tif err := config.Verbosity().Set(\"2\"); err != nil {\n\t\tlogger.Error(err, \"setting verbosity to 2\")\n\t}\n\tlogger.V(2).Info(\"now you see me\")\n\tif err := config.Verbosity().Set(\"1\"); err != nil {\n\t\tlogger.Error(err, \"setting verbosity to 1\")\n\t}\n\tlogger.V(2).Info(\"now I'm gone again\")\n\n\tfmt.Print(headerRe.ReplaceAllString(buffer.String(), \"${1}...] \"))\n\n\t// Output:\n\t// I...] initial verbosity v=\"1\"\n\t// I...] now you see me\n}\n"
  },
  {
    "path": "ktesting/helper_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nSPDX-License-Identifier: Apache-2.0\n*/\n\npackage ktesting_test\n\nimport (\n\t\"k8s.io/klog/v2\"\n)\n\nfunc callDepthHelper(logger klog.Logger, msg string) {\n\thelper, logger := logger.WithCallStackHelper()\n\thelper()\n\tlogger.Info(msg)\n}\n"
  },
  {
    "path": "ktesting/init/init.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package init registers the command line flags for k8s.io/klogr/testing in\n// the flag.CommandLine. This is done during initialization, so merely\n// importing it is enough.\npackage init\n\nimport (\n\t\"flag\"\n\n\t\"k8s.io/klog/v2/ktesting\"\n)\n\nfunc init() {\n\tktesting.DefaultConfig.AddFlags(flag.CommandLine)\n}\n"
  },
  {
    "path": "ktesting/main_test.go",
    "content": "/*\nCopyright 2025 The Kubernetes Authors.\n\nSPDX-License-Identifier: Apache-2.0\n*/\n\npackage ktesting_test\n\nimport (\n\t\"flag\"\n\t\"testing\"\n\n\t_ \"k8s.io/klog/v2/ktesting/init\"\n)\n\nfunc TestMain(m *testing.M) {\n\tflag.Parse()\n\tm.Run()\n}\n"
  },
  {
    "path": "ktesting/options.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ktesting\n\nimport (\n\t\"flag\"\n\t\"strconv\"\n\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/verbosity\"\n)\n\n// Config influences logging in a test logger. To make this configurable via\n// command line flags, instantiate this once per program and use AddFlags to\n// bind command line flags to the instance before passing it to NewTestContext.\n//\n// Must be constructed with NewConfig.\ntype Config struct {\n\tvstate *verbosity.VState\n\tco     configOptions\n}\n\n// Verbosity returns a value instance that can be used to query (via String) or\n// modify (via Set) the verbosity threshold. This is thread-safe and can be\n// done at runtime.\nfunc (c *Config) Verbosity() flag.Value {\n\treturn c.vstate.V()\n}\n\n// VModule returns a value instance that can be used to query (via String) or\n// modify (via Set) the vmodule settings. This is thread-safe and can be done\n// at runtime.\nfunc (c *Config) VModule() flag.Value {\n\treturn c.vstate.VModule()\n}\n\n// ConfigOption implements functional parameters for NewConfig.\ntype ConfigOption func(co *configOptions)\n\ntype configOptions struct {\n\tanyToString       serialize.AnyToStringFunc\n\tverbosityFlagName string\n\tvmoduleFlagName   string\n\tverbosityDefault  int\n\tbufferLogs        bool\n}\n\n// AnyToString overrides the default formatter for values that are not\n// supported directly by klog. The default is `fmt.Sprintf(\"%+v\")`.\n// The formatter must not panic.\nfunc AnyToString(anyToString func(value interface{}) string) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.anyToString = anyToString\n\t}\n}\n\n// VerbosityFlagName overrides the default -testing.v for the verbosity level.\nfunc VerbosityFlagName(name string) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.verbosityFlagName = name\n\t}\n}\n\n// VModulFlagName overrides the default -testing.vmodule for the per-module\n// verbosity levels.\nfunc VModuleFlagName(name string) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.vmoduleFlagName = name\n\t}\n}\n\n// Verbosity overrides the default verbosity level of 5. That default is higher\n// than in klog itself because it enables logging entries for \"the steps\n// leading up to errors and warnings\" and \"troubleshooting\" (see\n// https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions),\n// which is useful when debugging a failed test. `go test` only shows the log\n// output for failed tests. To see all output, use `go test -v`.\nfunc Verbosity(level int) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.verbosityDefault = level\n\t}\n}\n\n// BufferLogs controls whether log entries are captured in memory in addition\n// to being printed. Off by default. Unit tests that want to verify that\n// log entries are emitted as expected can turn this on and then retrieve\n// the captured log through the Underlier LogSink interface.\nfunc BufferLogs(enabled bool) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.bufferLogs = enabled\n\t}\n}\n\n// NewConfig returns a configuration with recommended defaults and optional\n// modifications. Command line flags are not bound to any FlagSet yet.\nfunc NewConfig(opts ...ConfigOption) *Config {\n\tc := &Config{\n\t\tco: configOptions{\n\t\t\tverbosityFlagName: \"testing.v\",\n\t\t\tvmoduleFlagName:   \"testing.vmodule\",\n\t\t\tverbosityDefault:  5,\n\t\t},\n\t}\n\tfor _, opt := range opts {\n\t\topt(&c.co)\n\t}\n\n\tc.vstate = verbosity.New()\n\t// Cannot fail for this input.\n\t_ = c.vstate.V().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))\n\treturn c\n}\n\n// AddFlags registers the command line flags that control the configuration.\nfunc (c *Config) AddFlags(fs *flag.FlagSet) {\n\tfs.Var(c.vstate.V(), c.co.verbosityFlagName, \"number for the log level verbosity of the testing logger\")\n\tfs.Var(c.vstate.VModule(), c.co.vmoduleFlagName, \"comma-separated list of pattern=N log level settings for files matching the patterns\")\n}\n"
  },
  {
    "path": "ktesting/setup.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage ktesting\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-logr/logr\"\n)\n\n// DefaultConfig is the global default logging configuration for a unit\n// test. It is used by NewTestContext and k8s.io/klogr/testing/init.\nvar DefaultConfig = NewConfig()\n\n// NewTestContext returns a logger and context for use in a unit test case or\n// benchmark. The tl parameter can be a testing.T or testing.B pointer that\n// will receive all log output. Importing k8s.io/klogr/testing/init will add\n// command line flags that modify the configuration of that log output.\nfunc NewTestContext(tl TL) (logr.Logger, context.Context) {\n\tlogger := NewLogger(tl, DefaultConfig)\n\tctx := logr.NewContext(context.Background(), logger)\n\treturn logger, ctx\n\n}\n"
  },
  {
    "path": "ktesting/testinglogger.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nCopyright 2020 Intel Corporation.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package testinglogger contains an implementation of the logr interface\n// which is logging through a function like testing.TB.Log function.\n// Therefore it can be used in standard Go tests and Gingko test suites\n// to ensure that output is associated with the currently running test.\n//\n// In addition, the log data is captured in a buffer and can be used by the\n// test to verify that the code under test is logging as expected. To get\n// access to that data, cast the LogSink into the Underlier type and retrieve\n// it:\n//\n//\tlogger := ktesting.NewLogger(...)\n//\tif testingLogger, ok := logger.GetSink().(ktesting.Underlier); ok {\n//\t    t := testingLogger.GetUnderlying()\n//\t    buffer := testingLogger.GetBuffer()\n//\t    text := buffer.String()\n//\t    log := buffer.Data()\n//\n// Serialization of the structured log parameters is done in the same way\n// as for klog.InfoS.\npackage ktesting\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/internal/dbg\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/severity\"\n\t\"k8s.io/klog/v2/internal/verbosity\"\n)\n\n// TL is the relevant subset of testing.TB.\ntype TL interface {\n\tHelper()\n\tLog(args ...interface{})\n}\n\n// NopTL implements TL with empty stubs. It can be used when only capturing\n// output in memory is relevant.\ntype NopTL struct{}\n\nfunc (n NopTL) Helper()            {}\nfunc (n NopTL) Log(...interface{}) {}\n\nvar _ TL = NopTL{}\n\n// BufferTL implements TL with an in-memory buffer.\ntype BufferTL struct {\n\tstrings.Builder\n}\n\nfunc (n *BufferTL) Helper() {}\nfunc (n *BufferTL) Log(args ...interface{}) {\n\tn.Builder.WriteString(fmt.Sprintln(args...))\n}\n\nvar _ TL = &BufferTL{}\n\n// NewLogger constructs a new logger for the given test interface.\n//\n// Beware that testing.T does not support logging after the test that\n// it was created for has completed. If a test leaks goroutines\n// and those goroutines log something after test completion,\n// that output will be printed via the global klog logger with\n// `<test name> leaked goroutine` as prefix.\n//\n// Verbosity can be modified at any time through the Config.V and\n// Config.VModule API.\nfunc NewLogger(t TL, c *Config) logr.Logger {\n\tl := tlogger{\n\t\tshared: &tloggerShared{\n\t\t\tt:      t,\n\t\t\tconfig: c,\n\t\t},\n\t}\n\tif c.co.anyToString != nil {\n\t\tl.shared.formatter.AnyToStringHook = c.co.anyToString\n\t}\n\n\ttype testCleanup interface {\n\t\tCleanup(func())\n\t\tName() string\n\t}\n\n\t// Stopping the logging is optional and only done (and required)\n\t// for testing.T/B/F.\n\tif tb, ok := t.(testCleanup); ok {\n\t\ttb.Cleanup(l.shared.stop)\n\t\tl.shared.testName = tb.Name()\n\t}\n\treturn logr.New(l)\n}\n\n// Buffer stores log entries as formatted text and structured data.\n// It is safe to use this concurrently.\ntype Buffer interface {\n\t// String returns the log entries in a format that is similar to the\n\t// klog text output.\n\tString() string\n\n\t// Data returns the log entries as structs.\n\tData() Log\n}\n\n// Log contains log entries in the order in which they were generated.\ntype Log []LogEntry\n\n// DeepCopy returns a copy of the log. The error instance and key/value\n// pairs remain shared.\nfunc (l Log) DeepCopy() Log {\n\tlog := make(Log, 0, len(l))\n\tlog = append(log, l...)\n\treturn log\n}\n\n// LogEntry represents all information captured for a log entry.\ntype LogEntry struct {\n\t// Timestamp stores the time when the log entry was created.\n\tTimestamp time.Time\n\n\t// Type is either LogInfo or LogError.\n\tType LogType\n\n\t// Prefix contains the WithName strings concatenated with a slash.\n\tPrefix string\n\n\t// Message is the fixed log message string.\n\tMessage string\n\n\t// Verbosity is always 0 for LogError.\n\tVerbosity int\n\n\t// Err is always nil for LogInfo. It may or may not be\n\t// nil for LogError.\n\tErr error\n\n\t// WithKVList are the concatenated key/value pairs from WithValues\n\t// calls. It's guaranteed to have an even number of entries because\n\t// the logger ensures that when WithValues is called.\n\tWithKVList []interface{}\n\n\t// ParameterKVList are the key/value pairs passed into the call,\n\t// without any validation.\n\tParameterKVList []interface{}\n}\n\n// LogType determines whether a log entry was created with an Error or Info\n// call.\ntype LogType string\n\nconst (\n\t// LogError is the special value used for Error log entries.\n\tLogError = LogType(\"ERROR\")\n\n\t// LogInfo is the special value used for Info log entries.\n\tLogInfo = LogType(\"INFO\")\n)\n\n// Underlier is implemented by the LogSink of this logger. It provides access\n// to additional APIs that are normally hidden behind the Logger API.\ntype Underlier interface {\n\t// GetUnderlying returns the testing instance that logging goes to.\n\t// It returns nil when the test has completed already.\n\tGetUnderlying() TL\n\n\t// GetBuffer grants access to the in-memory copy of the log entries.\n\tGetBuffer() Buffer\n}\n\ntype logBuffer struct {\n\tmutex sync.Mutex\n\ttext  strings.Builder\n\tlog   Log\n}\n\nfunc (b *logBuffer) String() string {\n\tb.mutex.Lock()\n\tdefer b.mutex.Unlock()\n\treturn b.text.String()\n}\n\nfunc (b *logBuffer) Data() Log {\n\tb.mutex.Lock()\n\tdefer b.mutex.Unlock()\n\treturn b.log.DeepCopy()\n}\n\n// tloggerShared holds values that are the same for all LogSink instances. It\n// gets referenced by pointer in the tlogger struct.\ntype tloggerShared struct {\n\t// mutex protects access to t.\n\tmutex sync.Mutex\n\n\t// t gets cleared when the test is completed.\n\tt TL\n\n\t// The time when the test completed.\n\tstopTime time.Time\n\n\t// We warn once when a leaked goroutine logs after test completion.\n\t//\n\t// Not terminating immediately is fairly normal: many controllers\n\t// log \"terminating\" messages while they shut down, which often is\n\t// right after test completion, if that is when the test cancels the\n\t// context and then doesn't wait for goroutines (which is often\n\t// not possible).\n\t//\n\t// Therefore there is the [stopGracePeriod] during which messages get\n\t// logged to the global logger without the warning.\n\tgoroutineWarningDone bool\n\n\tformatter serialize.Formatter\n\ttestName  string\n\tconfig    *Config\n\tbuffer    logBuffer\n\tcallDepth int\n}\n\n// Log output of a leaked goroutine during this period after test completion\n// does not trigger the warning.\nconst stopGracePeriod = 10 * time.Second\n\nfunc (ls *tloggerShared) stop() {\n\tls.mutex.Lock()\n\tdefer ls.mutex.Unlock()\n\tls.t = nil\n\tls.stopTime = time.Now()\n}\n\n// tlogger is the actual LogSink implementation.\ntype tlogger struct {\n\tshared *tloggerShared\n\tprefix string\n\tvalues []interface{}\n}\n\n// fallbackLogger is called while l.shared.mutex is locked and after it has\n// been determined that the original testing.TB is no longer usable.\nfunc (l tlogger) fallbackLogger() logr.Logger {\n\tlogger := klog.Background().WithValues(l.values...).WithName(l.shared.testName + \" leaked goroutine\")\n\tif l.prefix != \"\" {\n\t\tlogger = logger.WithName(l.prefix)\n\t}\n\t// Skip direct caller (= Error or Info) plus the logr wrapper.\n\tlogger = logger.WithCallDepth(l.shared.callDepth + 1)\n\n\tif !l.shared.goroutineWarningDone {\n\t\tduration := time.Since(l.shared.stopTime)\n\t\tif duration > stopGracePeriod {\n\n\t\t\tlogger.WithCallDepth(1).Info(\"WARNING: test kept at least one goroutine running after test completion\", \"timeSinceCompletion\", duration.Round(time.Second), \"callstack\", string(dbg.Stacks(false)))\n\t\t\tl.shared.goroutineWarningDone = true\n\t\t}\n\t}\n\treturn logger\n}\n\nfunc (l tlogger) Init(info logr.RuntimeInfo) {\n\tl.shared.callDepth = info.CallDepth\n}\n\nfunc (l tlogger) GetCallStackHelper() func() {\n\tl.shared.mutex.Lock()\n\tdefer l.shared.mutex.Unlock()\n\tif l.shared.t == nil {\n\t\treturn func() {}\n\t}\n\n\treturn l.shared.t.Helper\n}\n\nfunc (l tlogger) Info(level int, msg string, kvList ...interface{}) {\n\tl.shared.mutex.Lock()\n\tdefer l.shared.mutex.Unlock()\n\tif l.shared.t == nil {\n\t\tl.fallbackLogger().V(level).Info(msg, kvList...)\n\t\treturn\n\t}\n\n\tl.shared.t.Helper()\n\tbuf := buffer.GetBuffer()\n\tl.shared.formatter.FormatKVs(&buf.Buffer, l.values, kvList)\n\tl.log(LogInfo, msg, level, buf, nil, kvList)\n}\n\nfunc (l tlogger) Enabled(level int) bool {\n\treturn l.shared.config.vstate.Enabled(verbosity.Level(level), l.shared.callDepth+1)\n}\n\nfunc (l tlogger) Error(err error, msg string, kvList ...interface{}) {\n\tl.shared.mutex.Lock()\n\tdefer l.shared.mutex.Unlock()\n\tif l.shared.t == nil {\n\t\tl.fallbackLogger().Error(err, msg, kvList...)\n\t\treturn\n\t}\n\n\tl.shared.t.Helper()\n\tbuf := buffer.GetBuffer()\n\tvar errKV []interface{}\n\tif err != nil {\n\t\terrKV = []interface{}{\"err\", err}\n\t}\n\tl.shared.formatter.FormatKVs(&buf.Buffer, errKV, l.values, kvList)\n\tl.log(LogError, msg, 0, buf, err, kvList)\n}\n\nfunc (l tlogger) log(what LogType, msg string, level int, buf *buffer.Buffer, err error, kvList []interface{}) {\n\tl.shared.t.Helper()\n\ts := severity.InfoLog\n\tif what == LogError {\n\t\ts = severity.ErrorLog\n\t}\n\targs := []interface{}{buf.SprintHeader(s, time.Now())}\n\tif l.prefix != \"\" {\n\t\targs = append(args, l.prefix+\":\")\n\t}\n\targs = append(args, msg)\n\tif buf.Len() > 0 {\n\t\t// Skip leading space inserted by serialize.KVListFormat.\n\t\targs = append(args, string(buf.Bytes()[1:]))\n\t}\n\tl.shared.t.Log(args...)\n\n\tif !l.shared.config.co.bufferLogs {\n\t\treturn\n\t}\n\n\tl.shared.buffer.mutex.Lock()\n\tdefer l.shared.buffer.mutex.Unlock()\n\n\t// Store as text.\n\tl.shared.buffer.text.WriteString(string(what))\n\tfor i := 1; i < len(args); i++ {\n\t\tl.shared.buffer.text.WriteByte(' ')\n\t\tl.shared.buffer.text.WriteString(args[i].(string))\n\t}\n\tlastArg := args[len(args)-1].(string)\n\tif lastArg[len(lastArg)-1] != '\\n' {\n\t\tl.shared.buffer.text.WriteByte('\\n')\n\t}\n\n\t// Store as raw data.\n\tl.shared.buffer.log = append(l.shared.buffer.log,\n\t\tLogEntry{\n\t\t\tTimestamp:       time.Now(),\n\t\t\tType:            what,\n\t\t\tPrefix:          l.prefix,\n\t\t\tMessage:         msg,\n\t\t\tVerbosity:       level,\n\t\t\tErr:             err,\n\t\t\tWithKVList:      l.values,\n\t\t\tParameterKVList: kvList,\n\t\t},\n\t)\n}\n\n// WithName returns a new logr.Logger with the specified name appended.  klogr\n// uses '/' characters to separate name elements.  Callers should not pass '/'\n// in the provided name string, but this library does not actually enforce that.\nfunc (l tlogger) WithName(name string) logr.LogSink {\n\tif len(l.prefix) > 0 {\n\t\tl.prefix = l.prefix + \"/\"\n\t}\n\tl.prefix += name\n\treturn l\n}\n\nfunc (l tlogger) WithValues(kvList ...interface{}) logr.LogSink {\n\tl.values = serialize.WithValues(l.values, kvList)\n\treturn l\n}\n\nfunc (l tlogger) GetUnderlying() TL {\n\treturn l.shared.t\n}\n\nfunc (l tlogger) GetBuffer() Buffer {\n\treturn &l.shared.buffer\n}\n\nvar _ logr.LogSink = &tlogger{}\nvar _ logr.CallStackHelperLogSink = &tlogger{}\nvar _ Underlier = &tlogger{}\n"
  },
  {
    "path": "ktesting/testinglogger_test.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nCopyright 2020 Intel Corporation.\n\nSPDX-License-Identifier: Apache-2.0\n*/\n\npackage ktesting_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/internal/test/require\"\n\t\"k8s.io/klog/v2/ktesting\"\n)\n\nvar headerRe = regexp.MustCompile(`([IE])[[:digit:]]{4} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]{6}\\] `)\n\nfunc TestInfo(t *testing.T) {\n\ttests := map[string]struct {\n\t\ttext           string\n\t\twithValues     []interface{}\n\t\tkeysAndValues  []interface{}\n\t\tnames          []string\n\t\terr            error\n\t\texpectedOutput string\n\t}{\n\t\t\"should log with values passed to keysAndValues\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should support single name\": {\n\t\t\tnames:         []string{\"hello\"},\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `Ixxx hello: test akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should support multiple names\": {\n\t\t\tnames:         []string{\"hello\", \"world\"},\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `Ixxx hello/world: test akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should not print duplicate keys with the same value\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey\", \"avalue\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should print no duplicate keys when the values are passed to Info\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey\", \"avalue2\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue2\"\n`,\n\t\t},\n\t\t\"should only print the duplicate key that is passed to Info if one was passed to the logger\": {\n\t\t\twithValues:    []interface{}{\"akey\", \"avalue\"},\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue\"\n`,\n\t\t},\n\t\t\"should only print the key passed to Info when one is already set on the logger\": {\n\t\t\twithValues:    []interface{}{\"akey\", \"avalue\"},\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue2\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue2\"\n`,\n\t\t},\n\t\t\"should correctly handle odd-numbers of KVs\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t\t},\n\t\t\"should correctly html characters\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"<&>\"},\n\t\t\texpectedOutput: `Ixxx test akey=\"<&>\"\n`,\n\t\t},\n\t\t\"should correctly handle odd-numbers of KVs in both log values and Info args\": {\n\t\t\twithValues:    []interface{}{\"basekey1\", \"basevar1\", \"basekey2\"},\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\t\texpectedOutput: `Ixxx test basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t\t},\n\t\t\"should correctly print regular error types\": {\n\t\t\ttext:          \"test\",\n\t\t\tkeysAndValues: []interface{}{\"err\", errors.New(\"whoops\")},\n\t\t\texpectedOutput: `Ixxx test err=\"whoops\"\n`,\n\t\t},\n\t\t\"should correctly print regular error types when using logr.Error\": {\n\t\t\ttext: \"test\",\n\t\t\terr:  errors.New(\"whoops\"),\n\t\t\texpectedOutput: `Exxx test err=\"whoops\"\n`,\n\t\t},\n\t}\n\tfor n, test := range tests {\n\t\tt.Run(n, func(t *testing.T) {\n\t\t\tvar buffer logToBuf\n\t\t\tklogr := ktesting.NewLogger(&buffer, ktesting.NewConfig())\n\t\t\tfor _, name := range test.names {\n\t\t\t\tklogr = klogr.WithName(name)\n\t\t\t}\n\t\t\tklogr = klogr.WithValues(test.withValues...)\n\n\t\t\tif test.err != nil {\n\t\t\t\tklogr.Error(test.err, test.text, test.keysAndValues...)\n\t\t\t} else {\n\t\t\t\tklogr.Info(test.text, test.keysAndValues...)\n\t\t\t}\n\n\t\t\tactual := buffer.String()\n\t\t\tactual = headerRe.ReplaceAllString(actual, `${1}xxx `)\n\t\t\tif actual != test.expectedOutput {\n\t\t\t\tt.Errorf(\"Expected:\\n%sActual:\\n%s\\n\", test.expectedOutput, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestCallDepthOutput produces output which gets checked by TestCallDepthVerify.\nfunc TestCallDepthOutput(t *testing.T) {\n\tlogger := ktesting.NewLogger(t, ktesting.DefaultConfig)\n\tlogger.Info(\"hello world\")\n\tlogger.V(1).Info(\"you shouldn't see this with -testing.v=0\")\n\tcallDepthHelper(logger.V(1), \"you should see this with -testing.v=0 -testing.vmodule=helper_test=1\")\n}\n\n// TestCallDepthVerify runs TestCallDepth with appropriate flags and checks the output.\n//\n// Invoking `go test` is necessary because we want to verify that it correctly unwinds the stack,\n// which means that we have to let it print output. We cannot intercept that log output from\n// within a test.\nfunc TestCallDepthVerify(t *testing.T) {\n\tcmd, args := \"go\", \"test -v -run=TestCallDepthOutput k8s.io/klog/v2/ktesting -args -testing.v=0 -testing.vmodule=helper_test=1\"\n\toutput, err := exec.Command(\"go\", strings.Split(args, \" \")...).CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"failed to %s %s: %v\", cmd, args, err)\n\t}\n\tt.Log(\"Got output:\\n\", string(output))\n\n\texpect := `(?m)^[[:space:]]*testinglogger_test.go:.*hello world$\n^[[:space:]]*testinglogger_test.go:.*you should see this`\n\tmatched, err := regexp.MatchString(expect, string(output))\n\tif err != nil {\n\t\tt.Fatalf(\"failed to parse regexp: %v\\n\\n%s\", err, expect)\n\t}\n\tif !matched {\n\t\tt.Fatalf(\"expected output\\n%s\\n\\nto match regexp\\n%s\", string(output), expect)\n\t}\n}\n\ntype logToBuf struct {\n\tktesting.NopTL\n\tbytes.Buffer\n}\n\nfunc (l *logToBuf) Helper() {\n}\n\nfunc (l *logToBuf) Log(args ...interface{}) {\n\tfor i, arg := range args {\n\t\tif i > 0 {\n\t\t\tl.Write([]byte(\" \"))\n\t\t}\n\t\tl.Write([]byte(fmt.Sprintf(\"%s\", arg)))\n\t}\n\tl.Write([]byte(\"\\n\"))\n}\n\nfunc TestStop(t *testing.T) {\n\t// This test is set up so that a subtest spawns a goroutine and that\n\t// goroutine logs through ktesting *after* the subtest has\n\t// completed. This is not supported by testing.T.Log and normally\n\t// leads to:\n\t//   panic: Log in goroutine after TestGoroutines/Sub has completed: INFO hello world\n\t//\n\t// It works with ktesting if (and only if) logging gets redirected to klog\n\t// before returning from the test.\n\n\t// Capture output for testing.\n\tstate := klog.CaptureState()\n\tdefer state.Restore()\n\tvar output bytes.Buffer\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\trequire.NoError(t, fs.Set(\"alsologtostderr\", \"false\"))\n\trequire.NoError(t, fs.Set(\"logtostderr\", \"false\"))\n\trequire.NoError(t, fs.Set(\"stderrthreshold\", \"FATAL\"))\n\trequire.NoError(t, fs.Set(\"one_output\", \"true\"))\n\tklog.SetOutput(&output)\n\n\tvar logger klog.Logger\n\tvar line int\n\tvar wg1, wg2 sync.WaitGroup\n\twg1.Add(1)\n\twg2.Add(1)\n\tt.Run(\"Sub\", func(t *testing.T) {\n\t\tlogger, _ = ktesting.NewTestContext(t)\n\t\tgo func() {\n\t\t\tdefer wg2.Done()\n\n\t\t\t// Wait for test to have returned.\n\t\t\twg1.Wait()\n\n\t\t\t// This output must go to klog because the test has\n\t\t\t// completed.\n\t\t\t_, _, line, _ = runtime.Caller(0)\n\t\t\tlogger.Info(\"simple info message\")\n\t\t\tlogger.Error(nil, \"error message\")\n\t\t\ttime.Sleep(15 * time.Second)\n\t\t\tlogger.WithName(\"me\").WithValues(\"completed\", true).Info(\"complex info message\", \"anotherValue\", 1)\n\t\t}()\n\t})\n\t// Allow goroutine above to proceed.\n\twg1.Done()\n\n\t// Ensure that goroutine has completed.\n\twg2.Wait()\n\n\tactual := output.String()\n\n\t// Strip time and pid prefix.\n\tactual = regexp.MustCompile(`(?m)^.* testinglogger_test.go:`).ReplaceAllString(actual, `testinglogger_test.go:`)\n\n\t// Strip duration.\n\tactual = regexp.MustCompile(`timeSinceCompletion=\"\\d+s\"`).ReplaceAllString(actual, `timeSinceCompletion=\"<...>s\"`)\n\n\t// All lines from the callstack get stripped. We can be sure that it was non-empty because otherwise we wouldn't\n\t// have the < > markers.\n\t//\n\t// Full output:\n\t// \ttestinglogger_test.go:194] \"WARNING: test kept at least one goroutine running after test completion\" logger=\"TestStop/Sub leaked goroutine.me\" completed=true timeSinceCompletion=\"15s\" callstack=<\n\t//        \tgoroutine 23 [running]:\n\t//        \tk8s.io/klog/v2/internal/dbg.Stacks(0x0)\n\t//        \t\t/nvme/gopath/src/k8s.io/klog/internal/dbg/dbg.go:34 +0x8a\n\t//        \tk8s.io/klog/v2/ktesting.tlogger.fallbackLogger({0xc0000f2780, {0x0, 0x0}, {0x0, 0x0, 0x0}})\n\t//        \t\t/nvme/gopath/src/k8s.io/klog/ktesting/testinglogger.go:292 +0x232\n\t//        \tk8s.io/klog/v2/ktesting.tlogger.Info({0xc0000f2780, {0x0, 0x0}, {0x0, 0x0, 0x0}}, 0x0, {0x5444a5, 0x13}, {0x0, ...})\n\t//        \t\t/nvme/gopath/src/k8s.io/klog/ktesting/testinglogger.go:316 +0x28a\n\t//        \tgithub.com/go-logr/logr.Logger.Info({{0x572438?, 0xc0000c0ff0?}, 0x0?}, {0x5444a5, 0x13}, {0x0, 0x0, 0x0})\n\t//        \t\t/nvme/gopath/pkg/mod/github.com/go-logr/logr@v1.2.0/logr.go:249 +0xd0\n\t//        \tk8s.io/klog/v2/ktesting_test.TestStop.func1.1()\n\t//        \t\t/nvme/gopath/src/k8s.io/klog/ktesting/testinglogger_test.go:194 +0xe5\n\t//        \tcreated by k8s.io/klog/v2/ktesting_test.TestStop.func1\n\t//        \t\t/nvme/gopath/src/k8s.io/klog/ktesting/testinglogger_test.go:185 +0x105\n\t//         >\n\tactual = regexp.MustCompile(`(?m)^\\t.*?\\n`).ReplaceAllString(actual, ``)\n\n\texpected := fmt.Sprintf(`testinglogger_test.go:%d] \"simple info message\" logger=\"TestStop/Sub leaked goroutine\"\ntestinglogger_test.go:%d] \"error message\" logger=\"TestStop/Sub leaked goroutine\"\ntestinglogger_test.go:%d] \"WARNING: test kept at least one goroutine running after test completion\" logger=\"TestStop/Sub leaked goroutine.me\" completed=true timeSinceCompletion=\"<...>s\" callstack=<\n >\ntestinglogger_test.go:%d] \"complex info message\" logger=\"TestStop/Sub leaked goroutine.me\" completed=true anotherValue=1\n`,\n\t\tline+1, line+2, line+4, line+4)\n\tif actual != expected {\n\t\tt.Errorf(\"Output does not match. Expected:\\n%s\\nActual:\\n%s\\n\", expected, actual)\n\t}\n\n\ttestingLogger, ok := logger.GetSink().(ktesting.Underlier)\n\tif !ok {\n\t\tt.Fatal(\"should have had a ktesting logger\")\n\t}\n\tcaptured := testingLogger.GetBuffer().String()\n\tif captured != \"\" {\n\t\tt.Errorf(\"testing logger should not have captured any output, got instead:\\n%s\", captured)\n\t}\n}\n"
  },
  {
    "path": "output_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/test\"\n)\n\n// klogConfig tests klog output without a logger.\nvar klogConfig = test.OutputConfig{}\n\nfunc TestKlogOutput(t *testing.T) {\n\ttest.InitKlog(t)\n\ttest.Output(t, klogConfig)\n}\n\nfunc BenchmarkKlogOutput(b *testing.B) {\n\ttest.InitKlog(b)\n\ttest.Benchmark(b, klogConfig)\n}\n\n// klogKlogrConfig tests klogr output via klog, using the klog/v2 klogr.\nvar klogKLogrConfig = test.OutputConfig{\n\tNewLogger: func(_ io.Writer, _ int, _ string) logr.Logger {\n\t\treturn klog.NewKlogr()\n\t},\n}\n\nfunc TestKlogrOutput(t *testing.T) {\n\ttest.InitKlog(t)\n\ttest.Output(t, klogKLogrConfig)\n}\n"
  },
  {
    "path": "safeptr.go",
    "content": "//go:build go1.18\n// +build go1.18\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog\n\n// SafePtr is a function that takes a pointer of any type (T) as an argument.\n// If the provided pointer is not nil, it returns the same pointer. If it is nil, it returns nil instead.\n//\n// This function is particularly useful to prevent nil pointer dereferencing when:\n//\n//   - The type implements interfaces that are called by the logger, such as `fmt.Stringer`.\n//   - And these interface implementations do not perform nil checks themselves.\nfunc SafePtr[T any](p *T) any {\n\tif p == nil {\n\t\treturn nil\n\t}\n\treturn p\n}\n"
  },
  {
    "path": "safeptr_test.go",
    "content": "//go:build go1.18\n// +build go1.18\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage klog_test\n\nimport (\n\t\"testing\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc TestSafePtr(t *testing.T) {\n\t// Test with nil pointer\n\tvar stringPtr *string\n\tif result := klog.SafePtr(stringPtr); result != nil {\n\t\tt.Errorf(\"Expected nil, got %p\", result)\n\t}\n\n\t// Test with non-nil pointer\n\texpected := \"foo\"\n\tstringPtr = &expected\n\tif result := klog.SafePtr(stringPtr); result != stringPtr {\n\t\tt.Errorf(\"Expected %v, got %v\", stringPtr, result)\n\t}\n}\n"
  },
  {
    "path": "stderr_threshold_test.go",
    "content": "// Go support for leveled logs, analogous to https://code.google.com/p/google-glog/\n//\n// Copyright 2026 The Kubernetes Authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage klog\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/internal/severity\"\n)\n\n// TestStderrThresholdWithLogToStderr tests the new behavior where stderrthreshold\n// can be honored even when logtostderr=true, when legacy_stderr_threshold_behavior=false\nfunc TestStderrThresholdWithLogToStderr(t *testing.T) {\n\tdefer CaptureState().Restore()\n\n\ttests := []struct {\n\t\tname            string\n\t\tlogtostderr     bool\n\t\tlegacyBehavior  bool\n\t\tstderrthreshold string\n\t\tlogLevel        severity.Severity\n\t\texpectInStderr  bool\n\t\tdescription     string\n\t}{\n\t\t{\n\t\t\tname:            \"legacy behavior - logtostderr=true, all logs to stderr\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  true,\n\t\t\tstderrthreshold: \"ERROR\",\n\t\t\tlogLevel:        severity.InfoLog,\n\t\t\texpectInStderr:  true,\n\t\t\tdescription:     \"Legacy: INFO should appear in stderr even with stderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:            \"legacy behavior - logtostderr=true, ERROR threshold ignored\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  true,\n\t\t\tstderrthreshold: \"ERROR\",\n\t\t\tlogLevel:        severity.ErrorLog,\n\t\t\texpectInStderr:  true,\n\t\t\tdescription:     \"Legacy: ERROR should appear in stderr\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold honored, INFO filtered\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"ERROR\",\n\t\t\tlogLevel:        severity.InfoLog,\n\t\t\texpectInStderr:  false,\n\t\t\tdescription:     \"New: INFO should NOT appear in stderr with stderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold honored, WARNING filtered\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"ERROR\",\n\t\t\tlogLevel:        severity.WarningLog,\n\t\t\texpectInStderr:  false,\n\t\t\tdescription:     \"New: WARNING should NOT appear in stderr with stderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold honored, ERROR passes\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"ERROR\",\n\t\t\tlogLevel:        severity.ErrorLog,\n\t\t\texpectInStderr:  true,\n\t\t\tdescription:     \"New: ERROR should appear in stderr with stderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold=WARNING, INFO filtered\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"WARNING\",\n\t\t\tlogLevel:        severity.InfoLog,\n\t\t\texpectInStderr:  false,\n\t\t\tdescription:     \"New: INFO should NOT appear in stderr with stderrthreshold=WARNING\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold=WARNING, WARNING passes\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"WARNING\",\n\t\t\tlogLevel:        severity.WarningLog,\n\t\t\texpectInStderr:  true,\n\t\t\tdescription:     \"New: WARNING should appear in stderr with stderrthreshold=WARNING\",\n\t\t},\n\t\t{\n\t\t\tname:            \"new behavior - logtostderr=true, stderrthreshold=INFO, all pass\",\n\t\t\tlogtostderr:     true,\n\t\t\tlegacyBehavior:  false,\n\t\t\tstderrthreshold: \"INFO\",\n\t\t\tlogLevel:        severity.InfoLog,\n\t\t\texpectInStderr:  true,\n\t\t\tdescription:     \"New: INFO should appear in stderr with stderrthreshold=INFO\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Reset state for each test\n\t\t\tstate := CaptureState()\n\t\t\tdefer state.Restore()\n\n\t\t\t// Capture stderr\n\t\t\toldStderr := os.Stderr\n\t\t\tr, w, _ := os.Pipe()\n\t\t\tos.Stderr = w\n\n\t\t\t// Configure flags\n\t\t\tlogging.mu.Lock()\n\t\t\tlogging.toStderr = tt.logtostderr\n\t\t\tlogging.legacyStderrThresholdBehavior = tt.legacyBehavior\n\t\t\tlogging.stderrThreshold.Set(tt.stderrthreshold)\n\t\t\tlogging.mu.Unlock()\n\n\t\t\t// Log message based on level\n\t\t\ttestMsg := fmt.Sprintf(\"test message %s\", tt.name)\n\t\t\tbuf := buffer.GetBuffer()\n\t\t\tbuf.WriteString(testMsg)\n\t\t\tbuf.WriteString(\"\\n\")\n\n\t\t\t// Call output directly to test the logic\n\t\t\tlogging.output(tt.logLevel, nil, buf, 0, \"test.go\", 123, false)\n\n\t\t\t// Close writer and read stderr\n\t\t\tw.Close()\n\t\t\tvar stderrBuf bytes.Buffer\n\t\t\tio.Copy(&stderrBuf, r)\n\t\t\tos.Stderr = oldStderr\n\n\t\t\tstderrContent := stderrBuf.String()\n\t\t\tcontainsMsg := strings.Contains(stderrContent, testMsg)\n\n\t\t\tif tt.expectInStderr && !containsMsg {\n\t\t\t\tt.Errorf(\"%s: expected message in stderr but not found.\\nStderr: %q\", tt.description, stderrContent)\n\t\t\t}\n\t\t\tif !tt.expectInStderr && containsMsg {\n\t\t\t\tt.Errorf(\"%s: did not expect message in stderr but found it.\\nStderr: %q\", tt.description, stderrContent)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestAlsologtostderrthreshold tests the new alsologtostderrthreshold flag\nfunc TestAlsologtostderrthreshold(t *testing.T) {\n\tdefer CaptureState().Restore()\n\n\ttests := []struct {\n\t\tname                     string\n\t\talsologtostderr          bool\n\t\talsologtostderrthreshold string\n\t\tlogLevel                 severity.Severity\n\t\texpectInStderr           bool\n\t\tdescription              string\n\t}{\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, threshold=ERROR, INFO filtered\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"ERROR\",\n\t\t\tlogLevel:                 severity.InfoLog,\n\t\t\texpectInStderr:           false,\n\t\t\tdescription:              \"INFO should NOT appear in stderr with alsologtostderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, threshold=ERROR, WARNING filtered\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"ERROR\",\n\t\t\tlogLevel:                 severity.WarningLog,\n\t\t\texpectInStderr:           false,\n\t\t\tdescription:              \"WARNING should NOT appear in stderr with alsologtostderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, threshold=ERROR, ERROR passes\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"ERROR\",\n\t\t\tlogLevel:                 severity.ErrorLog,\n\t\t\texpectInStderr:           true,\n\t\t\tdescription:              \"ERROR should appear in stderr with alsologtostderrthreshold=ERROR\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, threshold=WARNING, INFO filtered\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"WARNING\",\n\t\t\tlogLevel:                 severity.InfoLog,\n\t\t\texpectInStderr:           false,\n\t\t\tdescription:              \"INFO should NOT appear in stderr with alsologtostderrthreshold=WARNING\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, threshold=WARNING, WARNING passes\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"WARNING\",\n\t\t\tlogLevel:                 severity.WarningLog,\n\t\t\texpectInStderr:           true,\n\t\t\tdescription:              \"WARNING should appear in stderr with alsologtostderrthreshold=WARNING\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=true, default threshold (INFO), all pass\",\n\t\t\talsologtostderr:          true,\n\t\t\talsologtostderrthreshold: \"INFO\",\n\t\t\tlogLevel:                 severity.InfoLog,\n\t\t\texpectInStderr:           true,\n\t\t\tdescription:              \"INFO should appear in stderr with alsologtostderrthreshold=INFO (default)\",\n\t\t},\n\t\t{\n\t\t\tname:                     \"alsologtostderr=false, threshold=ERROR, no stderr\",\n\t\t\talsologtostderr:          false,\n\t\t\talsologtostderrthreshold: \"ERROR\",\n\t\t\tlogLevel:                 severity.ErrorLog,\n\t\t\texpectInStderr:           false,\n\t\t\tdescription:              \"ERROR should NOT appear in stderr when alsologtostderr=false\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Reset state for each test\n\t\t\tstate := CaptureState()\n\t\t\tdefer state.Restore()\n\n\t\t\t// Capture stderr\n\t\t\toldStderr := os.Stderr\n\t\t\tr, w, _ := os.Pipe()\n\t\t\tos.Stderr = w\n\n\t\t\t// Configure flags - disable logtostderr and enable file logging\n\t\t\tlogging.mu.Lock()\n\t\t\tlogging.toStderr = false\n\t\t\tlogging.alsoToStderr = tt.alsologtostderr\n\t\t\tlogging.alsologtostderrthreshold.Set(tt.alsologtostderrthreshold)\n\t\t\tlogging.stderrThreshold.Set(\"FATAL\") // Set high to avoid interference\n\t\t\t// Use buffer writers instead of files\n\t\t\tlogging.file = [severity.NumSeverity]io.Writer{\n\t\t\t\tnew(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer),\n\t\t\t}\n\t\t\tlogging.mu.Unlock()\n\n\t\t\t// Log message based on level\n\t\t\ttestMsg := fmt.Sprintf(\"test message %s\", tt.name)\n\t\t\tbuf := buffer.GetBuffer()\n\t\t\tbuf.WriteString(testMsg)\n\t\t\tbuf.WriteString(\"\\n\")\n\n\t\t\t// Call output directly to test the logic\n\t\t\tlogging.output(tt.logLevel, nil, buf, 0, \"test.go\", 123, false)\n\n\t\t\t// Close writer and read stderr\n\t\t\tw.Close()\n\t\t\tvar stderrBuf bytes.Buffer\n\t\t\tio.Copy(&stderrBuf, r)\n\t\t\tos.Stderr = oldStderr\n\n\t\t\tstderrContent := stderrBuf.String()\n\t\t\tcontainsMsg := strings.Contains(stderrContent, testMsg)\n\n\t\t\tif tt.expectInStderr && !containsMsg {\n\t\t\t\tt.Errorf(\"%s: expected message in stderr but not found.\\nStderr: %q\", tt.description, stderrContent)\n\t\t\t}\n\t\t\tif !tt.expectInStderr && containsMsg {\n\t\t\t\tt.Errorf(\"%s: did not expect message in stderr but found it.\\nStderr: %q\", tt.description, stderrContent)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestFlagParsing tests that the new flags can be parsed correctly\nfunc TestNewFlagParsing(t *testing.T) {\n\tdefer CaptureState().Restore()\n\n\t// Create a new flag set for testing\n\tfs := flag.NewFlagSet(\"test\", flag.ContinueOnError)\n\tInitFlags(fs)\n\n\ttests := []struct {\n\t\tname        string\n\t\targs        []string\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname: \"valid legacy_stderr_threshold_behavior=true\",\n\t\t\targs: []string{\"-legacy_stderr_threshold_behavior=true\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid legacy_stderr_threshold_behavior=false\",\n\t\t\targs: []string{\"-legacy_stderr_threshold_behavior=false\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid alsologtostderrthreshold=ERROR\",\n\t\t\targs: []string{\"-alsologtostderrthreshold=ERROR\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid alsologtostderrthreshold=WARNING\",\n\t\t\targs: []string{\"-alsologtostderrthreshold=WARNING\"},\n\t\t},\n\t\t{\n\t\t\tname: \"valid alsologtostderrthreshold=INFO\",\n\t\t\targs: []string{\"-alsologtostderrthreshold=INFO\"},\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid alsologtostderrthreshold\",\n\t\t\targs:        []string{\"-alsologtostderrthreshold=INVALID\"},\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname: \"combined flags\",\n\t\t\targs: []string{\n\t\t\t\t\"-logtostderr=true\",\n\t\t\t\t\"-legacy_stderr_threshold_behavior=false\",\n\t\t\t\t\"-stderrthreshold=ERROR\",\n\t\t\t\t\"-alsologtostderrthreshold=WARNING\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Reset flags\n\t\t\tfs = flag.NewFlagSet(\"test\", flag.ContinueOnError)\n\t\t\tInitFlags(fs)\n\n\t\t\terr := fs.Parse(tt.args)\n\t\t\tif tt.expectError && err == nil {\n\t\t\t\tt.Errorf(\"expected error but got none\")\n\t\t\t}\n\t\t\tif !tt.expectError && err != nil {\n\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "test/output.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package test contains a reusable unit test for logging output and behavior.\npackage test\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\n// InitKlog must be called in a test to configure klog for testing.\n// The previous klog configuration will be restored automatically\n// after the test.\n//\n// The returned flag set has the klog flags registered. It can\n// be used to make further changes to the klog configuration.\nfunc InitKlog(tb testing.TB) *flag.FlagSet {\n\tstate := klog.CaptureState()\n\ttb.Cleanup(state.Restore)\n\n\texpectNoError := func(err error) {\n\t\tif err != nil {\n\t\t\ttb.Fatalf(\"unexpected error: %v\", err)\n\t\t}\n\t}\n\n\t// klog gets configured so that it writes to a single output file that\n\t// will be set during tests with SetOutput.\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\texpectNoError(fs.Set(\"v\", \"10\"))\n\texpectNoError(fs.Set(\"log_file\", \"/dev/null\"))\n\texpectNoError(fs.Set(\"logtostderr\", \"false\"))\n\texpectNoError(fs.Set(\"alsologtostderr\", \"false\"))\n\texpectNoError(fs.Set(\"stderrthreshold\", \"10\"))\n\n\treturn &fs\n}\n\n// OutputConfig contains optional settings for Output.\ntype OutputConfig struct {\n\t// NewLogger is called to create a new logger. If nil, output via klog\n\t// is tested. Support for -vmodule is optional.  ClearLogger is called\n\t// after each test, therefore it is okay to user SetLogger without\n\t// undoing that in the callback.\n\tNewLogger func(out io.Writer, v int, vmodule string) logr.Logger\n\n\t// AsBackend enables testing through klog and the logger set there with\n\t// SetLogger.\n\tAsBackend bool\n\n\t// ExpectedOutputMapping replaces the builtin expected output for test\n\t// cases with something else. If nil or a certain case is not present,\n\t// the original text is used.\n\t//\n\t// The expected output uses <LINE> as a placeholder for the line of the\n\t// log call. The source code is always the output.go file itself. When\n\t// testing a logger directly, <WITH-VALUES-LINE> is used for the first\n\t// WithValues call, <WITH-VALUES-LINE-2> for a second and\n\t// <WITH-VALUES-LINE-3> for a third.\n\tExpectedOutputMapping map[string]string\n\n\t// SupportsVModule indicates that the logger supports the vmodule\n\t// parameter. Ignored when logging through klog.\n\tSupportsVModule bool\n}\n\ntype testcase struct {\n\twithHelper bool // use wrappers that get skipped during stack unwinding\n\twithNames  []string\n\t// For a first WithValues call: logger1 := logger.WithValues()\n\twithValues []interface{}\n\t// For another WithValues call: logger2 := logger1.WithValues()\n\tmoreValues []interface{}\n\t// For another WithValues call on the same logger as before: logger3 := logger1.WithValues()\n\tevenMoreValues []interface{}\n\tv              int\n\tvmodule        string\n\ttext           string\n\tvalues         []interface{}\n\terr            error\n\texpectedOutput string\n}\n\nvar tests = map[string]testcase{\n\t\"log with values\": {\n\t\ttext:   \"test\",\n\t\tvalues: []interface{}{\"akey\", \"avalue\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" akey=\"avalue\"\n`,\n\t},\n\t\"call depth\": {\n\t\ttext:       \"helper\",\n\t\twithHelper: true,\n\t\tvalues:     []interface{}{\"akey\", \"avalue\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"helper\" akey=\"avalue\"\n`,\n\t},\n\t\"verbosity enabled\": {\n\t\ttext: \"you see me\",\n\t\tv:    9,\n\t\texpectedOutput: `I output.go:<LINE>] \"you see me\"\n`,\n\t},\n\t\"verbosity disabled\": {\n\t\ttext: \"you don't see me\",\n\t\tv:    11,\n\t},\n\t\"vmodule\": {\n\t\ttext:    \"v=11: you see me because of -vmodule output=11\",\n\t\tv:       11,\n\t\tvmodule: \"output=11\",\n\t\texpectedOutput: `I output.go:<LINE>] \"v=11: you see me because of -vmodule output=11\"\n`,\n\t},\n\t\"other vmodule\": {\n\t\ttext:    \"v=11: you still don't see me because of -vmodule output_helper=11\",\n\t\tv:       11,\n\t\tvmodule: \"output_helper=11\",\n\t},\n\t\"vmodule with helper\": {\n\t\ttext:       \"v=11: you see me because of -vmodule output=11\",\n\t\twithHelper: true,\n\t\tv:          11,\n\t\tvmodule:    \"output=11\",\n\t\texpectedOutput: `I output.go:<LINE>] \"v=11: you see me because of -vmodule output=11\"\n`,\n\t},\n\t\"other vmodule with helper\": {\n\t\ttext:       \"v=11: you still don't see me because of -vmodule output_helper=11\",\n\t\twithHelper: true,\n\t\tv:          11,\n\t\tvmodule:    \"output_helper=11\",\n\t},\n\t\"log with name and values\": {\n\t\twithNames: []string{\"me\"},\n\t\ttext:      \"test\",\n\t\tvalues:    []interface{}{\"akey\", \"avalue\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" logger=\"me\" akey=\"avalue\"\n`,\n\t},\n\t\"log with multiple names and values\": {\n\t\twithNames: []string{\"hello\", \"world\"},\n\t\ttext:      \"test\",\n\t\tvalues:    []interface{}{\"akey\", \"avalue\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" logger=\"hello.world\" akey=\"avalue\"\n`,\n\t},\n\t\"override single value\": {\n\t\twithValues: []interface{}{\"akey\", \"avalue\"},\n\t\ttext:       \"test-with-values\",\n\t\tvalues:     []interface{}{\"akey\", \"avalue2\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test-with-values\" akey=\"avalue2\"\n`,\n\t},\n\t\"override WithValues\": {\n\t\twithValues: []interface{}{\"duration\", time.Hour, \"X\", \"y\"},\n\t\ttext:       \"test\",\n\t\tvalues:     []interface{}{\"duration\", time.Minute, \"A\", \"b\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" X=\"y\" duration=\"1m0s\" A=\"b\"\n`,\n\t},\n\t\"odd WithValues\": {\n\t\twithValues: []interface{}{\"keyWithoutValue\"},\n\t\tmoreValues: []interface{}{\"anotherKeyWithoutValue\"},\n\t\ttext:       \"odd WithValues\",\n\t\texpectedOutput: `I output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\" anotherKeyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\n`,\n\t},\n\t\"multiple WithValues\": {\n\t\twithValues:     []interface{}{\"firstKey\", 1},\n\t\tmoreValues:     []interface{}{\"secondKey\", 2},\n\t\tevenMoreValues: []interface{}{\"secondKey\", 3},\n\t\ttext:           \"test\",\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=2\nI output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=3\n`,\n\t},\n\t\"empty WithValues\": {\n\t\twithValues: []interface{}{},\n\t\ttext:       \"test\",\n\t\texpectedOutput: `I output.go:<LINE>] \"test\"\n`,\n\t},\n\t\"print duplicate keys in arguments\": {\n\t\ttext:   \"test-arguments\",\n\t\tvalues: []interface{}{\"akey\", \"avalue\", \"akey\", \"avalue2\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test-arguments\" akey=\"avalue2\"\n`,\n\t},\n\t\"preserve order of key/value pairs\": {\n\t\twithValues: []interface{}{\"akey9\", \"avalue9\", \"akey8\", \"avalue8\", \"akey1\", \"avalue1\"},\n\t\ttext:       \"test\",\n\t\tvalues:     []interface{}{\"akey5\", \"avalue5\", \"akey4\", \"avalue4\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" akey9=\"avalue9\" akey8=\"avalue8\" akey1=\"avalue1\" akey5=\"avalue5\" akey4=\"avalue4\"\n`,\n\t},\n\t\"handle odd-numbers of KVs\": {\n\t\ttext:   \"odd arguments\",\n\t\tvalues: []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"odd arguments\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t},\n\t\"html characters\": {\n\t\ttext:   \"test\",\n\t\tvalues: []interface{}{\"akey\", \"<&>\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" akey=\"<&>\"\n`,\n\t},\n\t\"quotation\": {\n\t\ttext:   `\"quoted\"`,\n\t\tvalues: []interface{}{\"key\", `\"quoted value\"`},\n\t\texpectedOutput: `I output.go:<LINE>] \"\\\"quoted\\\"\" key=\"\\\"quoted value\\\"\"\n`,\n\t},\n\t\"handle odd-numbers of KVs in both log values and Info args\": {\n\t\twithValues: []interface{}{\"basekey1\", \"basevar1\", \"basekey2\"},\n\t\ttext:       \"both odd\",\n\t\tvalues:     []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"both odd\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t},\n\t\"KObj\": {\n\t\ttext:   \"test\",\n\t\tvalues: []interface{}{\"pod\", klog.KObj(&kmeta{Name: \"pod-1\", Namespace: \"kube-system\"})},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" pod=\"kube-system/pod-1\"\n`,\n\t},\n\t\"KObjs\": {\n\t\ttext: \"KObjs\",\n\t\tvalues: []interface{}{\"pods\",\n\t\t\tklog.KObjs([]interface{}{\n\t\t\t\t&kmeta{Name: \"pod-1\", Namespace: \"kube-system\"},\n\t\t\t\t&kmeta{Name: \"pod-2\", Namespace: \"kube-system\"},\n\t\t\t})},\n\t\texpectedOutput: `I output.go:<LINE>] \"KObjs\" pods=[{\"name\":\"pod-1\",\"namespace\":\"kube-system\"},{\"name\":\"pod-2\",\"namespace\":\"kube-system\"}]\n`,\n\t},\n\t\"KObjSlice okay\": {\n\t\ttext: \"KObjSlice\",\n\t\tvalues: []interface{}{\"pods\",\n\t\t\tklog.KObjSlice([]interface{}{\n\t\t\t\t&kmeta{Name: \"pod-1\", Namespace: \"kube-system\"},\n\t\t\t\t&kmeta{Name: \"pod-2\", Namespace: \"kube-system\"},\n\t\t\t})},\n\t\texpectedOutput: `I output.go:<LINE>] \"KObjSlice\" pods=[\"kube-system/pod-1\",\"kube-system/pod-2\"]\n`,\n\t},\n\t\"KObjSlice nil arg\": {\n\t\ttext: \"test\",\n\t\tvalues: []interface{}{\"pods\",\n\t\t\tklog.KObjSlice(nil)},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" pods=null\n`,\n\t},\n\t\"KObjSlice int arg\": {\n\t\ttext: \"test\",\n\t\tvalues: []interface{}{\"pods\",\n\t\t\tklog.KObjSlice(1)},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" pods=\"<KObjSlice needs a slice, got type int>\"\n`,\n\t},\n\t\"KObjSlice nil entry\": {\n\t\ttext: \"test\",\n\t\tvalues: []interface{}{\"pods\",\n\t\t\tklog.KObjSlice([]interface{}{\n\t\t\t\t&kmeta{Name: \"pod-1\", Namespace: \"kube-system\"},\n\t\t\t\tnil,\n\t\t\t})},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" pods=[\"kube-system/pod-1\",null]\n`,\n\t},\n\t\"KObjSlice ints\": {\n\t\ttext: \"test\",\n\t\tvalues: []interface{}{\"ints\",\n\t\t\tklog.KObjSlice([]int{1, 2, 3})},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" ints=[\"<KObjSlice needs a slice of values implementing KMetadata, got type int>\"]\n`,\n\t},\n\t\"regular error types as value\": {\n\t\ttext:   \"test\",\n\t\tvalues: []interface{}{\"err\", errors.New(\"whoops\")},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" err=\"whoops\"\n`,\n\t},\n\t\"ignore MarshalJSON\": {\n\t\ttext:   \"test\",\n\t\tvalues: []interface{}{\"err\", &customErrorJSON{\"whoops\"}},\n\t\texpectedOutput: `I output.go:<LINE>] \"test\" err=\"whoops\"\n`,\n\t},\n\t\"regular error types when using logr.Error\": {\n\t\ttext: \"test\",\n\t\terr:  errors.New(\"whoops\"),\n\t\texpectedOutput: `E output.go:<LINE>] \"test\" err=\"whoops\"\n`,\n\t},\n\t\"Error() for nil\": {\n\t\ttext: \"error nil\",\n\t\terr:  (*customErrorJSON)(nil),\n\t\texpectedOutput: `E output.go:<LINE>] \"error nil\" err=\"<panic: runtime error: invalid memory address or nil pointer dereference>\"\n`,\n\t},\n\t\"String() for nil\": {\n\t\ttext:   \"stringer nil\",\n\t\tvalues: []interface{}{\"stringer\", (*stringer)(nil)},\n\t\texpectedOutput: `I output.go:<LINE>] \"stringer nil\" stringer=\"<panic: runtime error: invalid memory address or nil pointer dereference>\"\n`,\n\t},\n\t\"MarshalLog() for nil\": {\n\t\ttext:   \"marshaler nil\",\n\t\tvalues: []interface{}{\"obj\", (*klog.ObjectRef)(nil)},\n\t\texpectedOutput: `I output.go:<LINE>] \"marshaler nil\" obj=\"<panic: value method k8s.io/klog/v2.ObjectRef.WriteText called using nil *ObjectRef pointer>\"\n`,\n\t},\n\t\"Error() that panics\": {\n\t\ttext: \"error panic\",\n\t\terr:  faultyError{},\n\t\texpectedOutput: `E output.go:<LINE>] \"error panic\" err=\"<panic: fake Error panic>\"\n`,\n\t},\n\t\"String() that panics\": {\n\t\ttext:   \"stringer panic\",\n\t\tvalues: []interface{}{\"stringer\", faultyStringer{}},\n\t\texpectedOutput: `I output.go:<LINE>] \"stringer panic\" stringer=\"<panic: fake String panic>\"\n`,\n\t},\n\t\"MarshalLog() that panics\": {\n\t\ttext:   \"marshaler panic\",\n\t\tvalues: []interface{}{\"obj\", faultyMarshaler{}},\n\t\texpectedOutput: `I output.go:<LINE>] \"marshaler panic\" obj=\"<panic: fake MarshalLog panic>\"\n`,\n\t},\n\t\"MarshalLog() that returns itself\": {\n\t\ttext:   \"marshaler recursion\",\n\t\tvalues: []interface{}{\"obj\", recursiveMarshaler{}},\n\t\texpectedOutput: `I output.go:<LINE>] \"marshaler recursion\" obj={}\n`,\n\t},\n\t\"handle integer keys\": {\n\t\twithValues: []interface{}{1, \"value\", 2, \"value2\"},\n\t\ttext:       \"integer keys\",\n\t\tvalues:     []interface{}{\"akey\", \"avalue\", \"akey2\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"integer keys\" %!s(int=1)=\"value\" %!s(int=2)=\"value2\" akey=\"avalue\" akey2=\"(MISSING)\"\n`,\n\t},\n\t\"struct keys\": {\n\t\twithValues: []interface{}{struct{ name string }{\"name\"}, \"value\", \"test\", \"other value\"},\n\t\ttext:       \"struct keys\",\n\t\tvalues:     []interface{}{\"key\", \"val\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"struct keys\" {name}=\"value\" test=\"other value\" key=\"val\"\n`,\n\t},\n\t\"map keys\": {\n\t\twithValues: []interface{}{},\n\t\ttext:       \"map keys\",\n\t\tvalues:     []interface{}{map[string]bool{\"test\": true}, \"test\"},\n\t\texpectedOutput: `I output.go:<LINE>] \"map keys\" map[test:%!s(bool=true)]=\"test\"\n`,\n\t},\n\t\"map values\": {\n\t\ttext:   \"maps\",\n\t\tvalues: []interface{}{\"s\", map[string]string{\"hello\": \"world\"}, \"i\", map[int]int{1: 2, 3: 4}},\n\t\texpectedOutput: `I output.go:<LINE>] \"maps\" s={\"hello\":\"world\"} i={\"1\":2,\"3\":4}\n`,\n\t},\n\t\"slice values\": {\n\t\ttext:   \"slices\",\n\t\tvalues: []interface{}{\"s\", []string{\"hello\", \"world\"}, \"i\", []int{1, 2, 3}},\n\t\texpectedOutput: `I output.go:<LINE>] \"slices\" s=[\"hello\",\"world\"] i=[1,2,3]\n`,\n\t},\n\t\"struct values\": {\n\t\ttext:   \"structs\",\n\t\tvalues: []interface{}{\"s\", struct{ Name, Kind, hidden string }{Name: \"worker\", Kind: \"pod\", hidden: \"ignore\"}},\n\t\texpectedOutput: `I output.go:<LINE>] \"structs\" s={\"Name\":\"worker\",\"Kind\":\"pod\"}\n`,\n\t},\n\t\"klog.Format\": {\n\t\ttext:   \"klog.Format\",\n\t\tvalues: []interface{}{\"s\", klog.Format(struct{ Name, Kind, hidden string }{Name: \"worker\", Kind: \"pod\", hidden: \"ignore\"})},\n\t\texpectedOutput: `I output.go:<LINE>] \"klog.Format\" s=<\n\t{\n\t  \"Name\": \"worker\",\n\t  \"Kind\": \"pod\"\n\t}\n >\n`,\n\t},\n\t\"cyclic list\": {\n\t\ttext:   \"cycle\",\n\t\tvalues: []interface{}{\"list\", newCyclicList()},\n\t\texpectedOutput: `I output.go:<LINE>] \"cycle\" list=\"<internal error: json: unsupported value: encountered a cycle via *test.myList>\"\n`,\n\t},\n\t\"duplicates\": {\n\t\twithValues:     []interface{}{\"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"0102030405060708\")},\n\t\tmoreValues:     []interface{}{\"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"1112131415161718\")},\n\t\tevenMoreValues: []interface{}{\"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"2122232425262728\")},\n\t\ttext:           \"duplicates\",\n\n\t\texpectedOutput: `I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"2122232425262728\"\n`,\n\t},\n\t\"mixed duplicates\": {\n\t\twithValues:     []interface{}{\"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"0102030405060708\"), \"a\", 1},\n\t\tmoreValues:     []interface{}{\"b\", 2, \"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"1112131415161718\")},\n\t\tevenMoreValues: []interface{}{\"c\", 3, \"trace\", traceIDFromHex(\"101112131415161718191A1B1C1D1E1F\"), \"span\", spanIDFromHex(\"2122232425262728\"), \"d\", 4},\n\t\ttext:           \"duplicates\",\n\n\t\texpectedOutput: `I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 b=2 span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 c=3 span=\"2122232425262728\" d=4\n`,\n\t},\n}\n\nfunc printWithLogger(logger logr.Logger, test testcase) {\n\tfor _, name := range test.withNames {\n\t\tlogger = logger.WithName(name)\n\t}\n\t// When we have multiple WithValues calls, we test\n\t// first with the initial set of additional values, then\n\t// the combination, then again the original logger.\n\t// It must not have been modified. This produces\n\t// three log entries.\n\tlogger = logger.WithValues(test.withValues...) // <WITH-VALUES>\n\tloggers := []logr.Logger{logger}\n\tif test.moreValues != nil {\n\t\t// Intentionally append the logger again: WithValues must not change what it prints.\n\t\tloggers = append(loggers, logger.WithValues(test.moreValues...), logger) // <WITH-VALUES-2>\n\t}\n\tif test.evenMoreValues != nil {\n\t\tloggers = append(loggers, logger.WithValues(test.evenMoreValues...)) // <WITH-VALUES-3>\n\t}\n\tfor _, logger := range loggers {\n\t\tif test.withHelper {\n\t\t\tloggerHelper(logger.V(test.v), test.text, test.values) // <LINE>\n\t\t} else if test.err != nil {\n\t\t\tlogger.Error(test.err, test.text, test.values...) // <LINE>\n\t\t} else {\n\t\t\tlogger.V(test.v).Info(test.text, test.values...) // <LINE>\n\t\t}\n\t}\n}\n\nvar _, _, printWithLoggerLine, _ = runtime.Caller(0) // anchor for finding the line numbers above\n\nfunc initPrintWithKlog(tb testing.TB, test testcase) {\n\tif test.withHelper && test.vmodule != \"\" {\n\t\ttb.Skip(\"klog does not support -vmodule properly when using helper functions\")\n\t}\n\n\tstate := klog.CaptureState()\n\ttb.Cleanup(state.Restore)\n\n\tvar fs flag.FlagSet\n\tklog.InitFlags(&fs)\n\tif err := fs.Set(\"v\", \"10\"); err != nil {\n\t\ttb.Fatalf(\"unexpected error: %v\", err)\n\t}\n\tif err := fs.Set(\"vmodule\", test.vmodule); err != nil {\n\t\ttb.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc printWithKlog(test testcase) {\n\tkv := []interface{}{}\n\tappendKV := func(withValues ...interface{}) {\n\t\tif len(withValues)%2 != 0 {\n\t\t\twithValues = append(withValues, \"(MISSING)\")\n\t\t}\n\t\tfor i := 0; i < len(withValues); i += 2 {\n\t\t\tkv = append(kv, withValues[i], withValues[i+1])\n\t\t}\n\t}\n\t// Here we need to emulate the handling of WithValues above.\n\tif len(test.withNames) > 0 {\n\t\tappendKV(\"logger\", strings.Join(test.withNames, \".\"))\n\t}\n\tappendKV(test.withValues...)\n\tkvs := [][]interface{}{copySlice(kv)}\n\tif test.moreValues != nil {\n\t\tappendKV(test.moreValues...)\n\t\tkvs = append(kvs, copySlice(kv), copySlice(kvs[0]))\n\t}\n\tif test.evenMoreValues != nil {\n\t\tkv = copySlice(kvs[0])\n\t\tappendKV(test.evenMoreValues...)\n\t\tkvs = append(kvs, copySlice(kv))\n\t}\n\tfor _, kv := range kvs {\n\t\tif len(test.values) > 0 {\n\t\t\tkv = append(kv, test.values...)\n\t\t}\n\t\ttext := test.text\n\t\tif test.withHelper {\n\t\t\tklogHelper(klog.Level(test.v), text, kv)\n\t\t} else if test.err != nil {\n\t\t\tklog.ErrorS(test.err, text, kv...)\n\t\t} else {\n\t\t\tklog.V(klog.Level(test.v)).InfoS(text, kv...)\n\t\t}\n\t}\n}\n\nvar _, _, printWithKlogLine, _ = runtime.Caller(0) // anchor for finding the line numbers above\n\n// Output covers various special cases of emitting log output.\n// It can be used for arbitrary logr.Logger implementations.\n//\n// The expected output is what klog would print. When testing loggers\n// that emit different output, a mapping from klog output to the\n// corresponding logger output must be provided, otherwise the\n// test will compare against the expected klog output.\n//\n// Loggers will be tested with direct calls to Info or\n// as backend for klog.\nfunc Output(t *testing.T, config OutputConfig) {\n\tfor n, test := range tests {\n\t\tt.Run(n, func(t *testing.T) {\n\t\t\tinitPrintWithKlog(t, test)\n\n\t\t\ttestOutput := func(t *testing.T, expectedLine int, logToBuffer func(buffer *bytes.Buffer)) {\n\t\t\t\tvar tmpWriteBuffer bytes.Buffer\n\t\t\t\tklog.SetOutput(&tmpWriteBuffer)\n\t\t\t\tlogToBuffer(&tmpWriteBuffer)\n\t\t\t\tklog.Flush()\n\n\t\t\t\tactual := tmpWriteBuffer.String()\n\t\t\t\t// Strip varying header.\n\t\t\t\tre := `(?m)^(.).... ..:..:......... ....... output.go`\n\t\t\t\tactual = regexp.MustCompile(re).ReplaceAllString(actual, `${1} output.go`)\n\n\t\t\t\t// Inject expected line. This matches the if checks above, which are\n\t\t\t\t// the same for both printWithKlog and printWithLogger.\n\t\t\t\tcallLine := expectedLine\n\t\t\t\tif test.withHelper {\n\t\t\t\t\tcallLine -= 8\n\t\t\t\t} else if test.err != nil {\n\t\t\t\t\tcallLine -= 6\n\t\t\t\t} else {\n\t\t\t\t\tcallLine -= 4\n\t\t\t\t}\n\t\t\t\texpected := test.expectedOutput\n\t\t\t\tif repl, ok := config.ExpectedOutputMapping[expected]; ok {\n\t\t\t\t\texpected = repl\n\t\t\t\t}\n\t\t\t\texpectedWithPlaceholder := expected\n\t\t\t\texpected = strings.ReplaceAll(expected, \"<LINE>\", fmt.Sprintf(\"%d\", callLine))\n\t\t\t\texpected = strings.ReplaceAll(expected, \"<WITH-VALUES>\", fmt.Sprintf(\"%d\", expectedLine-19))\n\t\t\t\texpected = strings.ReplaceAll(expected, \"<WITH-VALUES-2>\", fmt.Sprintf(\"%d\", expectedLine-15))\n\t\t\t\texpected = strings.ReplaceAll(expected, \"<WITH-VALUES-3>\", fmt.Sprintf(\"%d\", expectedLine-12))\n\t\t\t\tif actual != expected {\n\t\t\t\t\tif expectedWithPlaceholder == test.expectedOutput {\n\t\t\t\t\t\tt.Errorf(\"Output mismatch.\\n\\nExpected with placeholders:\\n%s\\nExpected without placeholders:\\n%s\\nActual:\\n%s\\n\", expectedWithPlaceholder, expected, actual)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"Output mismatch. klog:\\n%s\\n\\nExpected with placeholders:\\n%s\\nExpected without placeholders:\\n%s\\nActual:\\n%s\\n\", test.expectedOutput, expectedWithPlaceholder, expected, actual)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif config.NewLogger == nil {\n\t\t\t\t// Test klog.\n\t\t\t\ttestOutput(t, printWithKlogLine-1, func(_ *bytes.Buffer) {\n\t\t\t\t\tprintWithKlog(test)\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif config.AsBackend {\n\t\t\t\ttestOutput(t, printWithKlogLine-1, func(buffer *bytes.Buffer) {\n\t\t\t\t\tsetLogger(config.NewLogger(buffer, 10, test.vmodule))\n\t\t\t\t\tprintWithKlog(test)\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.vmodule != \"\" && !config.SupportsVModule {\n\t\t\t\tt.Skip(\"vmodule not supported\")\n\t\t\t}\n\n\t\t\ttestOutput(t, printWithLoggerLine-1, func(buffer *bytes.Buffer) {\n\t\t\t\tprintWithLogger(config.NewLogger(buffer, 10, test.vmodule), test)\n\t\t\t})\n\t\t})\n\t}\n\n\tif config.NewLogger == nil || config.AsBackend {\n\t\tconfigStruct := klog.Format(myConfig{typeMeta: typeMeta{Kind: \"config\"}, RealField: 42})\n\t\tconfigStructOutput := `I output.go:<LINE>] \"Format\" config=<\n\t{\n\t  \"Kind\": \"config\",\n\t  \"RealField\": 42\n\t}\n >\n`\n\n\t\t// Test all klog output functions.\n\t\t//\n\t\t// Each test case must be defined with the same number of\n\t\t// lines, then the source code location of the call itself\n\t\t// can be computed below.\n\t\ttests := []struct {\n\t\t\tname    string\n\t\t\tlogFunc func()\n\t\t\toutput  string\n\t\t}{\n\t\t\t{\n\t\t\t\tname:    \"Info\",\n\t\t\t\tlogFunc: func() { klog.Info(\"hello\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] helloworld\\n\", // This looks odd, but simply is how klog works.\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"InfoDepth\",\n\t\t\t\tlogFunc: func() { klog.InfoDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] helloworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Infoln\",\n\t\t\t\tlogFunc: func() { klog.Infoln(\"hello\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"InfolnDepth\",\n\t\t\t\tlogFunc: func() { klog.InfolnDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Infof\",\n\t\t\t\tlogFunc: func() { klog.Infof(\"hello %s\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"InfofDepth\",\n\t\t\t\tlogFunc: func() { klog.InfofDepth(0, \"hello %s\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"InfoS\",\n\t\t\t\tlogFunc: func() { klog.InfoS(\"hello\", \"what\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] \\\"hello\\\" what=\\\"world\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"InfoSDepth\",\n\t\t\t\tlogFunc: func() { klog.InfoSDepth(0, \"hello\", \"what\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] \\\"hello\\\" what=\\\"world\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Warning\",\n\t\t\t\tlogFunc: func() { klog.Warning(\"hello\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] helloworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"WarningDepth\",\n\t\t\t\tlogFunc: func() { klog.WarningDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] helloworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Warningln\",\n\t\t\t\tlogFunc: func() { klog.Warningln(\"hello\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"WarninglnDepth\",\n\t\t\t\tlogFunc: func() { klog.WarninglnDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Warningf\",\n\t\t\t\tlogFunc: func() { klog.Warningf(\"hello %s\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"WarningfDepth\",\n\t\t\t\tlogFunc: func() { klog.WarningfDepth(0, \"hello %s\", \"world\") },\n\t\t\t\toutput:  \"W output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Error\",\n\t\t\t\tlogFunc: func() { klog.Error(\"hello\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] helloworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"ErrorDepth\",\n\t\t\t\tlogFunc: func() { klog.ErrorDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] helloworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Errorln\",\n\t\t\t\tlogFunc: func() { klog.Errorln(\"hello\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"ErrorlnDepth\",\n\t\t\t\tlogFunc: func() { klog.ErrorlnDepth(0, \"hello\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Errorf\",\n\t\t\t\tlogFunc: func() { klog.Errorf(\"hello %s\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"ErrorfDepth\",\n\t\t\t\tlogFunc: func() { klog.ErrorfDepth(0, \"hello %s\", \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] hello world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"ErrorS\",\n\t\t\t\tlogFunc: func() { klog.ErrorS(errors.New(\"hello\"), \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] \\\"world\\\" err=\\\"hello\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"ErrorSDepth\",\n\t\t\t\tlogFunc: func() { klog.ErrorSDepth(0, errors.New(\"hello\"), \"world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] \\\"world\\\" err=\\\"hello\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().Info\",\n\t\t\t\tlogFunc: func() { klog.V(1).Info(\"hello\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hellooneworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().InfoDepth\",\n\t\t\t\tlogFunc: func() { klog.V(1).InfoDepth(0, \"hello\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hellooneworld\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().Infoln\",\n\t\t\t\tlogFunc: func() { klog.V(1).Infoln(\"hello\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello one world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().InfolnDepth\",\n\t\t\t\tlogFunc: func() { klog.V(1).InfolnDepth(0, \"hello\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello one world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().Infof\",\n\t\t\t\tlogFunc: func() { klog.V(1).Infof(\"hello %s %s\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello one world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().InfofDepth\",\n\t\t\t\tlogFunc: func() { klog.V(1).InfofDepth(0, \"hello %s %s\", \"one\", \"world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] hello one world\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().InfoS\",\n\t\t\t\tlogFunc: func() { klog.V(1).InfoS(\"hello\", \"what\", \"one world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] \\\"hello\\\" what=\\\"one world\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().InfoSDepth\",\n\t\t\t\tlogFunc: func() { klog.V(1).InfoSDepth(0, \"hello\", \"what\", \"one world\") },\n\t\t\t\toutput:  \"I output.go:<LINE>] \\\"hello\\\" what=\\\"one world\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"V().ErrorS\",\n\t\t\t\tlogFunc: func() { klog.V(1).ErrorS(errors.New(\"hello\"), \"one world\") },\n\t\t\t\toutput:  \"E output.go:<LINE>] \\\"one world\\\" err=\\\"hello\\\"\\n\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:    \"Format InfoS\",\n\t\t\t\tlogFunc: func() { klog.InfoS(\"Format\", \"config\", configStruct) },\n\t\t\t\toutput:  configStructOutput,\n\t\t\t},\n\t\t}\n\t\t_, _, line, _ := runtime.Caller(0)\n\n\t\tfor i, test := range tests {\n\t\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\t\tvar buffer bytes.Buffer\n\t\t\t\thaveWriteKlogBuffer := false\n\t\t\t\tif config.NewLogger == nil {\n\t\t\t\t\tklog.SetOutput(&buffer)\n\t\t\t\t} else {\n\t\t\t\t\thaveWriteKlogBuffer = setLogger(config.NewLogger(&buffer, 10, \"\"))\n\t\t\t\t\tdefer klog.ClearLogger()\n\t\t\t\t}\n\t\t\t\ttest.logFunc()\n\t\t\t\tklog.Flush()\n\n\t\t\t\tactual := buffer.String()\n\t\t\t\t// Strip varying header.\n\t\t\t\tre := `(?m)^(.).... ..:..:......... ....... output.go`\n\t\t\t\tactual = regexp.MustCompile(re).ReplaceAllString(actual, `${1} output.go`)\n\n\t\t\t\t// Inject expected line. This matches the if checks above, which are\n\t\t\t\t// the same for both printWithKlog and printWithLogger.\n\t\t\t\tcallLine := line + 1 - (len(tests)-i)*5\n\t\t\t\texpected := test.output\n\n\t\t\t\t// When klog does string formating for\n\t\t\t\t// non-structured calls, it passes the entire\n\t\t\t\t// result, including a trailing newline, to\n\t\t\t\t// Logger.Info.\n\t\t\t\tif config.NewLogger != nil &&\n\t\t\t\t\t!haveWriteKlogBuffer &&\n\t\t\t\t\t!strings.HasSuffix(test.name, \"S\") &&\n\t\t\t\t\t!strings.HasSuffix(test.name, \"SDepth\") {\n\t\t\t\t\t// klog: I output.go:<LINE>] hello world\n\t\t\t\t\t// with logger: I output.go:<LINE>] \"hello world\\n\"\n\t\t\t\t\tindex := strings.Index(expected, \"] \")\n\t\t\t\t\tif index == -1 {\n\t\t\t\t\t\tt.Fatalf(\"did not find ] separator: %s\", expected)\n\t\t\t\t\t}\n\t\t\t\t\texpected = expected[0:index+2] + strconv.Quote(expected[index+2:]) + \"\\n\"\n\n\t\t\t\t\t// Warnings become info messages.\n\t\t\t\t\tif strings.HasPrefix(expected, \"W\") {\n\t\t\t\t\t\texpected = \"I\" + expected[1:]\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif repl, ok := config.ExpectedOutputMapping[expected]; ok {\n\t\t\t\t\texpected = repl\n\t\t\t\t}\n\t\t\t\texpectedWithPlaceholder := expected\n\t\t\t\texpected = strings.ReplaceAll(expected, \"<LINE>\", fmt.Sprintf(\"%d\", callLine))\n\t\t\t\tif actual != expected {\n\t\t\t\t\tif expectedWithPlaceholder == test.output {\n\t\t\t\t\t\tt.Errorf(\"Output mismatch. Expected with placeholders:\\n%s\\nExpected without placeholders:\\n%s\\nActual:\\n%s\\n\", expectedWithPlaceholder, expected, actual)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tt.Errorf(\"Output mismatch. klog:\\n%s\\nExpected with placeholders:\\n%s\\nExpected without placeholders:\\n%s\\nActual:\\n%s\\n\", test.output, expectedWithPlaceholder, expected, actual)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\n// Benchmark covers various special cases of emitting log output.\n// It can be used for arbitrary logr.Logger implementations.\n//\n// Loggers will be tested with direct calls to Info or\n// as backend for klog.\nfunc Benchmark(b *testing.B, config OutputConfig) {\n\tfor n, test := range tests {\n\t\tb.Run(n, func(b *testing.B) {\n\t\t\tstate := klog.CaptureState()\n\t\t\tdefer state.Restore()\n\t\t\tklog.SetOutput(io.Discard)\n\t\t\tinitPrintWithKlog(b, test)\n\t\t\tb.ResetTimer()\n\n\t\t\tif config.NewLogger == nil {\n\t\t\t\t// Test klog.\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tprintWithKlog(test)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif config.AsBackend {\n\t\t\t\tsetLogger(config.NewLogger(io.Discard, 10, \"\"))\n\t\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\t\tprintWithKlog(test)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.vmodule != \"\" && !config.SupportsVModule {\n\t\t\t\tb.Skip(\"vmodule not supported\")\n\t\t\t}\n\n\t\t\tlogger := config.NewLogger(io.Discard, 10, test.vmodule)\n\t\t\tb.ResetTimer()\n\t\t\tfor i := 0; i < b.N; i++ {\n\t\t\t\tprintWithLogger(logger, test)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc setLogger(logger logr.Logger) bool {\n\thaveWriteKlogBuffer := false\n\tvar opts []klog.LoggerOption\n\tif writer, ok := logger.GetSink().(textlogger.KlogBufferWriter); ok {\n\t\topts = append(opts, klog.WriteKlogBuffer(writer.WriteKlogBuffer))\n\t\thaveWriteKlogBuffer = true\n\t}\n\tklog.SetLoggerWithOptions(logger, opts...)\n\treturn haveWriteKlogBuffer\n}\n\nfunc copySlice(in []interface{}) []interface{} {\n\treturn append([]interface{}{}, in...)\n}\n\ntype kmeta struct {\n\tName, Namespace string\n}\n\nfunc (k kmeta) GetName() string {\n\treturn k.Name\n}\n\nfunc (k kmeta) GetNamespace() string {\n\treturn k.Namespace\n}\n\nvar _ klog.KMetadata = kmeta{}\n\ntype customErrorJSON struct {\n\ts string\n}\n\nvar _ error = &customErrorJSON{}\nvar _ json.Marshaler = &customErrorJSON{}\n\nfunc (e *customErrorJSON) Error() string {\n\treturn e.s\n}\n\nfunc (e *customErrorJSON) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(strings.ToUpper(e.s))\n}\n\ntype stringer struct {\n\ts string\n}\n\n// String crashes when called for nil.\nfunc (s *stringer) String() string {\n\treturn s.s\n}\n\nvar _ fmt.Stringer = &stringer{}\n\ntype faultyStringer struct{}\n\n// String always panics.\nfunc (f faultyStringer) String() string {\n\tpanic(\"fake String panic\")\n}\n\nvar _ fmt.Stringer = faultyStringer{}\n\ntype faultyMarshaler struct{}\n\n// MarshalLog always panics.\nfunc (f faultyMarshaler) MarshalLog() interface{} {\n\tpanic(\"fake MarshalLog panic\")\n}\n\nvar _ logr.Marshaler = faultyMarshaler{}\n\ntype recursiveMarshaler struct{}\n\n// MarshalLog returns itself, which could cause the logger to recurse infinitely.\nfunc (r recursiveMarshaler) MarshalLog() interface{} {\n\treturn r\n}\n\nvar _ logr.Marshaler = recursiveMarshaler{}\n\ntype faultyError struct{}\n\n// Error always panics.\nfunc (f faultyError) Error() string {\n\tpanic(\"fake Error panic\")\n}\n\nvar _ error = faultyError{}\n\n// typeMeta implements fmt.Stringer and logr.Marshaler. config below\n// inherits those (incomplete!) implementations.\ntype typeMeta struct {\n\tKind string\n}\n\nfunc (t typeMeta) String() string {\n\treturn \"kind is \" + t.Kind\n}\n\nfunc (t typeMeta) MarshalLog() interface{} {\n\treturn t.Kind\n}\n\ntype myConfig struct {\n\ttypeMeta\n\n\tRealField int\n}\n\nvar _ logr.Marshaler = myConfig{}\nvar _ fmt.Stringer = myConfig{}\n\n// This is a linked list. It can contain a cycle, which cannot be expressed in JSON.\ntype myList struct {\n\tValue int\n\tNext  *myList\n}\n\nfunc newCyclicList() *myList {\n\ta := &myList{Value: 1}\n\tb := &myList{Value: 2, Next: a}\n\ta.Next = b\n\treturn a\n}\n\n// SpanID mimicks https://pkg.go.dev/go.opentelemetry.io/otel/trace#SpanID.\ntype SpanID [8]byte\n\nvar (\n\t_ json.Marshaler = SpanID{}\n\t_ fmt.Stringer   = SpanID{}\n)\n\nfunc (s SpanID) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(s.String())\n}\n\nfunc (s SpanID) String() string {\n\treturn hex.EncodeToString(s[:])\n}\n\nfunc spanIDFromHex(str string) SpanID {\n\tdecoded, err := hex.DecodeString(str)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"invalid hex string %q: %v\", str, err))\n\t}\n\tif len(decoded) != len(SpanID{}) {\n\t\tpanic(fmt.Sprintf(\"invalid length of hex string %q: need %d bytes\", str, len(SpanID{})))\n\t}\n\tvar result SpanID\n\tfor i := range result {\n\t\tresult[i] = decoded[i]\n\t}\n\treturn result\n}\n\n// TraceID mimicks https://pkg.go.dev/go.opentelemetry.io/otel/trace#TraceID.\ntype TraceID [16]byte\n\nvar (\n\t_ json.Marshaler = TraceID{}\n\t_ fmt.Stringer   = TraceID{}\n)\n\nfunc (s TraceID) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(s.String())\n}\n\nfunc (s TraceID) String() string {\n\treturn hex.EncodeToString(s[:])\n}\n\nfunc traceIDFromHex(str string) TraceID {\n\tdecoded, err := hex.DecodeString(str)\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"invalid hex string %q: %v\", str, err))\n\t}\n\tif len(decoded) != len(TraceID{}) {\n\t\tpanic(fmt.Sprintf(\"invalid length of hex string %q: need %d bytes\", str, len(TraceID{})))\n\t}\n\tvar result TraceID\n\tfor i := range result {\n\t\tresult[i] = decoded[i]\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "test/output_helper.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage test\n\nimport (\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2\"\n)\n\nfunc loggerHelper(logger logr.Logger, msg string, kv []interface{}) {\n\tlogger = logger.WithCallDepth(1)\n\tlogger.Info(msg, kv...)\n}\n\nfunc klogHelper(level klog.Level, msg string, kv []interface{}) {\n\tklog.V(level).InfoSDepth(1, msg, kv...)\n}\n"
  },
  {
    "path": "test/zapr.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage test\n\n// ZaprOutputMappingDirect provides a mapping from klog output to the\n// corresponding zapr output when zapr is called directly.\nfunc ZaprOutputMappingDirect() map[string]string {\n\treturn map[string]string{\n\t\t`I output.go:<LINE>] \"test\" akey=\"<&>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"akey\":\"<&>\"}\n`,\n\n\t\t`E output.go:<LINE>] \"test\" err=\"whoops\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"err\":\"whoops\"}\n`,\n\n\t\t`I output.go:<LINE>] \"helper\" akey=\"avalue\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"helper\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test-arguments\" akey=\"avalue2\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test-arguments\",\"v\":0,\"akey\":\"avalue\",\"akey\":\"avalue2\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test-with-values\" akey=\"avalue2\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test-with-values\",\"akey\":\"avalue\",\"v\":0,\"akey\":\"avalue2\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" logger=\"hello.world\" akey=\"avalue\"\n`: `{\"logger\":\"hello.world\",\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" X=\"y\" duration=\"1m0s\" A=\"b\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"duration\":\"1h0m0s\",\"X\":\"y\",\"v\":0,\"duration\":\"1m0s\",\"A\":\"b\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" akey9=\"avalue9\" akey8=\"avalue8\" akey1=\"avalue1\" akey5=\"avalue5\" akey4=\"avalue4\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"akey9\":\"avalue9\",\"akey8\":\"avalue8\",\"akey1\":\"avalue1\",\"v\":0,\"akey5\":\"avalue5\",\"akey4\":\"avalue4\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0}\n`,\n\n\t\t`I output.go:<LINE>] \"\\\"quoted\\\"\" key=\"\\\"quoted value\\\"\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"\\\"quoted\\\"\",\"v\":0,\"key\":\"\\\"quoted value\\\"\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" err=\"whoops\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"err\":\"whoops\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" pod=\"kube-system/pod-1\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"pod\":{\"name\":\"pod-1\",\"namespace\":\"kube-system\"}}\n`,\n\n\t\t`I output.go:<LINE>] \"KObjs\" pods=[{\"name\":\"pod-1\",\"namespace\":\"kube-system\"},{\"name\":\"pod-2\",\"namespace\":\"kube-system\"}]\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"KObjs\",\"v\":0,\"pods\":[{\"name\":\"pod-1\",\"namespace\":\"kube-system\"},{\"name\":\"pod-2\",\"namespace\":\"kube-system\"}]}\n`,\n\n\t\t`I output.go:<LINE>] \"KObjSlice\" pods=[\"kube-system/pod-1\",\"kube-system/pod-2\"]\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"KObjSlice\",\"v\":0,\"pods\":[{\"name\":\"pod-1\",\"namespace\":\"kube-system\"},{\"name\":\"pod-2\",\"namespace\":\"kube-system\"}]}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" pods=null\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"pods\":null}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" pods=\"<KObjSlice needs a slice, got type int>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"pods\":\"<KObjSlice needs a slice, got type int>\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" ints=[\"<KObjSlice needs a slice of values implementing KMetadata, got type int>\"]\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"ints\":\"<KObjSlice needs a slice of values implementing KMetadata, got type int>\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" pods=[\"kube-system/pod-1\",null]\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"pods\":[{\"name\":\"pod-1\",\"namespace\":\"kube-system\"},null]}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" akey=\"avalue\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" logger=\"me\" akey=\"avalue\"\n`: `{\"logger\":\"me\",\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" akey=\"avalue2\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"akey\":\"avalue\",\"v\":0,\"akey\":\"avalue2\"}\n`,\n\n\t\t`I output.go:<LINE>] \"you see me\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"you see me\",\"v\":9}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=2\nI output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=3\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"firstKey\":1,\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"firstKey\":1,\"secondKey\":2,\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"firstKey\":1,\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"firstKey\":1,\"secondKey\":3,\"v\":0}\n`,\n\n\t\t`I output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\" anotherKeyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<WITH-VALUES>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"keyWithoutValue\"}\n{\"caller\":\"test/output.go:<WITH-VALUES-2>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"anotherKeyWithoutValue\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0}\n`,\n\n\t\t`I output.go:<LINE>] \"odd arguments\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"akey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd arguments\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"both odd\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<WITH-VALUES>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"basekey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"basekey1\":\"basevar1\",\"ignored key\":\"akey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"both odd\",\"basekey1\":\"basevar1\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"marshaler nil\" obj=\"<panic: value method k8s.io/klog/v2.ObjectRef.WriteText called using nil *ObjectRef pointer>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"marshaler nil\",\"v\":0,\"objError\":\"PANIC=value method k8s.io/klog/v2.ObjectRef.MarshalLog called using nil *ObjectRef pointer\"}\n`,\n\n\t\t// zap replaces a panic for a nil object with <nil>.\n\t\t`E output.go:<LINE>] \"error nil\" err=\"<panic: runtime error: invalid memory address or nil pointer dereference>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"error nil\",\"err\":\"<nil>\"}\n`,\n\n\t\t`I output.go:<LINE>] \"stringer nil\" stringer=\"<panic: runtime error: invalid memory address or nil pointer dereference>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"stringer nil\",\"v\":0,\"stringer\":\"<nil>\"}\n`,\n\n\t\t`I output.go:<LINE>] \"stringer panic\" stringer=\"<panic: fake String panic>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"stringer panic\",\"v\":0,\"stringerError\":\"PANIC=fake String panic\"}\n`,\n\n\t\t`E output.go:<LINE>] \"error panic\" err=\"<panic: fake Error panic>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"error panic\",\"errError\":\"PANIC=fake Error panic\"}\n`,\n\n\t\t`I output.go:<LINE>] \"marshaler panic\" obj=\"<panic: fake MarshalLog panic>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"marshaler panic\",\"v\":0,\"objError\":\"PANIC=fake MarshalLog panic\"}\n`,\n\n\t\t`I output.go:<LINE>] \"marshaler recursion\" obj={}\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"marshaler recursion\",\"v\":0,\"obj\":{}}\n`,\n\n\t\t// klog.Info\n\t\t`I output.go:<LINE>] \"helloworld\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"helloworld\",\"v\":0}\n`,\n\n\t\t// klog.Infoln\n\t\t`I output.go:<LINE>] \"hello world\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hello world\",\"v\":0}\n`,\n\n\t\t// klog.Error\n\t\t`E output.go:<LINE>] \"helloworld\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"helloworld\"}\n`,\n\n\t\t// klog.Errorln\n\t\t`E output.go:<LINE>] \"hello world\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hello world\"}\n`,\n\n\t\t// klog.ErrorS\n\t\t`E output.go:<LINE>] \"world\" err=\"hello\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"world\",\"err\":\"hello\"}\n`,\n\n\t\t// klog.InfoS\n\t\t`I output.go:<LINE>] \"hello\" what=\"world\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hello\",\"v\":0,\"what\":\"world\"}\n`,\n\n\t\t// klog.V(1).Info\n\t\t`I output.go:<LINE>] \"hellooneworld\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hellooneworld\",\"v\":1}\n`,\n\n\t\t// klog.V(1).Infoln\n\t\t`I output.go:<LINE>] \"hello one world\\n\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hello one world\",\"v\":1}\n`,\n\n\t\t// klog.V(1).ErrorS\n\t\t`E output.go:<LINE>] \"one world\" err=\"hello\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"one world\",\"err\":\"hello\"}\n`,\n\n\t\t// klog.V(1).InfoS\n\t\t`I output.go:<LINE>] \"hello\" what=\"one world\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"hello\",\"v\":1,\"what\":\"one world\"}\n`,\n\n\t\t`I output.go:<LINE>] \"integer keys\" %!s(int=1)=\"value\" %!s(int=2)=\"value2\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<WITH-VALUES>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"akey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"integer keys\",\"v\":0,\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"struct keys\" {name}=\"value\" test=\"other value\" key=\"val\"\n`: `{\"caller\":\"test/output.go:<WITH-VALUES>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"struct keys\",\"v\":0,\"key\":\"val\"}\n`,\n\t\t`I output.go:<LINE>] \"map keys\" map[test:%!s(bool=true)]=\"test\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{\"test\":true}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"map keys\",\"v\":0}\n`,\n\n\t\t`I output.go:<LINE>] \"Format\" config=<\n\t{\n\t  \"Kind\": \"config\",\n\t  \"RealField\": 42\n\t}\n >\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"Format\",\"v\":0,\"config\":{\"Kind\":\"config\",\"RealField\":42}}\n`,\n\t\t`I output.go:<LINE>] \"maps\" s={\"hello\":\"world\"} i={\"1\":2,\"3\":4}\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"maps\",\"v\":0,\"s\":{\"hello\":\"world\"},\"i\":{\"1\":2,\"3\":4}}\n`,\n\n\t\t`I output.go:<LINE>] \"slices\" s=[\"hello\",\"world\"] i=[1,2,3]\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"slices\",\"v\":0,\"s\":[\"hello\",\"world\"],\"i\":[1,2,3]}\n`,\n\t\t`I output.go:<LINE>] \"structs\" s={\"Name\":\"worker\",\"Kind\":\"pod\"}\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"structs\",\"v\":0,\"s\":{\"Name\":\"worker\",\"Kind\":\"pod\"}}\n`,\n\t\t`I output.go:<LINE>] \"klog.Format\" s=<\n\t{\n\t  \"Name\": \"worker\",\n\t  \"Kind\": \"pod\"\n\t}\n >\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"klog.Format\",\"v\":0,\"s\":{\"Name\":\"worker\",\"Kind\":\"pod\"}}\n`,\n\n\t\t`I output.go:<LINE>] \"cycle\" list=\"<internal error: json: unsupported value: encountered a cycle via *test.myList>\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"cycle\",\"v\":0,\"listError\":\"json: unsupported value: encountered a cycle via *test.myList\"}\n`,\n\n\t\t`I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"2122232425262728\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"1112131415161718\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"2122232425262728\",\"v\":0}\n`,\n\n\t\t`I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 b=2 span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 c=3 span=\"2122232425262728\" d=4\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"b\":2,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"1112131415161718\",\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"v\":0}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"c\":3,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"2122232425262728\",\"d\":4,\"v\":0}\n`,\n\t}\n}\n\n// ZaprOutputMappingIndirect provides a mapping from klog output to the\n// corresponding zapr output when zapr is called indirectly through\n// klog.\n//\n// This is different from ZaprOutputMappingDirect because:\n//   - WithName gets added to the message by Output.\n//   - zap uses . as separator instead of / between WithName values,\n//     here we get slashes because Output concatenates these values.\n//   - WithValues are added to the normal key/value parameters by\n//     Output, which puts them after \"v\".\n//   - Output does that without emitting the warning that we get\n//     from zapr.\n//   - zap drops keys with missing values, here we get \"(MISSING)\".\nfunc ZaprOutputMappingIndirect() map[string]string {\n\tmapping := ZaprOutputMappingDirect()\n\n\tfor key, value := range map[string]string{\n\t\t`I output.go:<LINE>] \"test\" logger=\"hello.world\" akey=\"avalue\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"logger\":\"hello.world\",\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" logger=\"me\" akey=\"avalue\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"logger\":\"me\",\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"odd parameters\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"akey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd parameters\",\"v\":0,\"basekey1\":\"basevar1\",\"basekey2\":\"(MISSING)\",\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\" anotherKeyWithoutValue=\"(MISSING)\"\nI output.go:<LINE>] \"odd WithValues\" keyWithoutValue=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0,\"keyWithoutValue\":\"(MISSING)\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0,\"keyWithoutValue\":\"(MISSING)\",\"anotherKeyWithoutValue\":\"(MISSING)\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd WithValues\",\"v\":0,\"keyWithoutValue\":\"(MISSING)\"}\n`,\n\n\t\t`I output.go:<LINE>] \"both odd\" basekey1=\"basevar1\" basekey2=\"(MISSING)\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"odd number of arguments passed as key-value pairs for logging\",\"ignored key\":\"akey2\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"both odd\",\"v\":0,\"basekey1\":\"basevar1\",\"basekey2\":\"(MISSING)\",\"akey\":\"avalue\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" akey9=\"avalue9\" akey8=\"avalue8\" akey1=\"avalue1\" akey5=\"avalue5\" akey4=\"avalue4\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"akey9\":\"avalue9\",\"akey8\":\"avalue8\",\"akey1\":\"avalue1\",\"akey5\":\"avalue5\",\"akey4\":\"avalue4\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test-arguments\" akey=\"avalue2\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test-arguments\",\"v\":0,\"akey\":\"avalue\",\"akey\":\"avalue2\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test-with-values\" akey=\"avalue2\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test-with-values\",\"v\":0,\"akey\":\"avalue\",\"akey\":\"avalue2\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" X=\"y\" duration=\"1m0s\" A=\"b\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"duration\":\"1h0m0s\",\"X\":\"y\",\"duration\":\"1m0s\",\"A\":\"b\"}\n`,\n\n\t\t`I output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=2\nI output.go:<LINE>] \"test\" firstKey=1\nI output.go:<LINE>] \"test\" firstKey=1 secondKey=3\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"firstKey\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"firstKey\":1,\"secondKey\":2}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"firstKey\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"test\",\"v\":0,\"firstKey\":1,\"secondKey\":3}\n`,\n\t\t`I output.go:<LINE>] \"integer keys\" %!s(int=1)=\"value\" %!s(int=2)=\"value2\" akey=\"avalue\" akey2=\"(MISSING)\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"integer keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"struct keys\" {name}=\"value\" test=\"other value\" key=\"val\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"struct keys\",\"v\":0}\n`,\n\t\t`I output.go:<LINE>] \"map keys\" map[test:%!s(bool=true)]=\"test\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"non-string key argument passed to logging, ignoring all later arguments\",\"invalid key\":{\"test\":true}}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"map keys\",\"v\":0}\n`,\n\n\t\t// zapr does not support vmodule checks and thus always\n\t\t// discards these messages.\n\t\t`I output.go:<LINE>] \"v=11: you see me because of -vmodule output=11\"\n`: ``,\n\n\t\t`I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"2122232425262728\"\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"1112131415161718\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"2122232425262728\"}\n`,\n\n\t\t`I output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 b=2 span=\"1112131415161718\"\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" span=\"0102030405060708\" a=1\nI output.go:<LINE>] \"duplicates\" trace=\"101112131415161718191a1b1c1d1e1f\" a=1 c=3 span=\"2122232425262728\" d=4\n`: `{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"b\":2,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"1112131415161718\"}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1}\n{\"caller\":\"test/output.go:<LINE>\",\"msg\":\"duplicates\",\"v\":0,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"0102030405060708\",\"a\":1,\"c\":3,\"trace\":\"101112131415161718191a1b1c1d1e1f\",\"span\":\"2122232425262728\",\"d\":4}\n`,\n\t} {\n\t\tmapping[key] = value\n\t}\n\treturn mapping\n}\n"
  },
  {
    "path": "textlogger/example_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\nvar headerRe = regexp.MustCompile(`([IE])[[:digit:]]{4} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}\\.[[:digit:]]{6}[[:space:]]+[[:digit:]]+ example_test.go:[[:digit:]]+\\] `)\n\nfunc ExampleConfig_Verbosity() {\n\tvar buffer bytes.Buffer\n\tconfig := textlogger.NewConfig(textlogger.Verbosity(1), textlogger.Output(&buffer))\n\tlogger := textlogger.NewLogger(config)\n\n\tlogger.Info(\"initial verbosity\", \"v\", config.Verbosity().String())\n\tlogger.V(2).Info(\"now you don't see me\")\n\tif err := config.Verbosity().Set(\"2\"); err != nil {\n\t\tlogger.Error(err, \"setting verbosity to 2\")\n\t}\n\tlogger.V(2).Info(\"now you see me\")\n\tif err := config.Verbosity().Set(\"1\"); err != nil {\n\t\tlogger.Error(err, \"setting verbosity to 1\")\n\t}\n\tlogger.V(2).Info(\"now I'm gone again\")\n\n\tfmt.Print(headerRe.ReplaceAllString(buffer.String(), \"${1}...] \"))\n\n\t// Output:\n\t// I...] \"initial verbosity\" v=\"1\"\n\t// I...] \"now you see me\"\n}\n"
  },
  {
    "path": "textlogger/options.go",
    "content": "/*\nCopyright 2021 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger\n\nimport (\n\t\"flag\"\n\t\"io\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"k8s.io/klog/v2/internal/verbosity\"\n)\n\n// Config influences logging in a text logger. To make this configurable via\n// command line flags, instantiate this once per program and use AddFlags to\n// bind command line flags to the instance before passing it to NewTestContext.\n//\n// Must be constructed with NewConfig.\ntype Config struct {\n\tvstate *verbosity.VState\n\tco     configOptions\n}\n\n// Verbosity returns a value instance that can be used to query (via String) or\n// modify (via Set) the verbosity threshold. This is thread-safe and can be\n// done at runtime.\nfunc (c *Config) Verbosity() flag.Value {\n\treturn c.vstate.V()\n}\n\n// VModule returns a value instance that can be used to query (via String) or\n// modify (via Set) the vmodule settings. This is thread-safe and can be done\n// at runtime.\nfunc (c *Config) VModule() flag.Value {\n\treturn c.vstate.VModule()\n}\n\n// ConfigOption implements functional parameters for NewConfig.\ntype ConfigOption func(co *configOptions)\n\ntype configOptions struct {\n\tverbosityFlagName string\n\tvmoduleFlagName   string\n\tverbosityDefault  int\n\tfixedTime         *time.Time\n\tunwind            func(int) (string, int)\n\twithHeader        bool\n\toutput            io.Writer\n}\n\n// VerbosityFlagName overrides the default -v for the verbosity level.\nfunc VerbosityFlagName(name string) ConfigOption {\n\treturn func(co *configOptions) {\n\n\t\tco.verbosityFlagName = name\n\t}\n}\n\n// VModulFlagName overrides the default -vmodule for the per-module\n// verbosity levels.\nfunc VModuleFlagName(name string) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.vmoduleFlagName = name\n\t}\n}\n\n// Verbosity overrides the default verbosity level of 0.\n// See https://github.com/kubernetes/community/blob/9406b4352fe2d5810cb21cc3cb059ce5886de157/contributors/devel/sig-instrumentation/logging.md#logging-conventions\n// for log level conventions in Kubernetes.\nfunc Verbosity(level int) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.verbosityDefault = level\n\t}\n}\n\n// Output overrides stderr as the output stream.\nfunc Output(output io.Writer) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.output = output\n\t}\n}\n\n// FixedTime overrides the actual time with a fixed time. Useful only for testing.\n//\n// # Experimental\n//\n// Notice: This function is EXPERIMENTAL and may be changed or removed in a\n// later release.\nfunc FixedTime(ts time.Time) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.fixedTime = &ts\n\t}\n}\n\n// WithHeader controls whether the header (time, source code location, etc.)\n// is included in the output. The default is to include it.\n//\n// This can be useful in combination with redirection to a buffer to\n// turn structured log parameters into a string (see example).\n//\n// # Experimental\n//\n// Notice: This function is EXPERIMENTAL and may be changed or removed in a\n// later release.\nfunc WithHeader(enabled bool) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.withHeader = enabled\n\t}\n}\n\n// Backtrace overrides the default mechanism for determining the call site.\n// The callback is invoked with the number of function calls between itself\n// and the call site. It must return the file name and line number. An empty\n// file name indicates that the information is unknown.\n//\n// # Experimental\n//\n// Notice: This function is EXPERIMENTAL and may be changed or removed in a\n// later release.\nfunc Backtrace(unwind func(skip int) (filename string, line int)) ConfigOption {\n\treturn func(co *configOptions) {\n\t\tco.unwind = unwind\n\t}\n}\n\n// NewConfig returns a configuration with recommended defaults and optional\n// modifications. Command line flags are not bound to any FlagSet yet.\nfunc NewConfig(opts ...ConfigOption) *Config {\n\tc := &Config{\n\t\tvstate: verbosity.New(),\n\t\tco: configOptions{\n\t\t\tverbosityFlagName: \"v\",\n\t\t\tvmoduleFlagName:   \"vmodule\",\n\t\t\tverbosityDefault:  0,\n\t\t\tunwind:            runtimeBacktrace,\n\t\t\twithHeader:        true,\n\t\t\toutput:            os.Stderr,\n\t\t},\n\t}\n\tfor _, opt := range opts {\n\t\topt(&c.co)\n\t}\n\n\t// Cannot fail for this input.\n\t_ = c.Verbosity().Set(strconv.FormatInt(int64(c.co.verbosityDefault), 10))\n\treturn c\n}\n\n// AddFlags registers the command line flags that control the configuration.\n//\n// The default flag names are the same as in klog, so unless those defaults\n// are changed, either klog.InitFlags or Config.AddFlags can be used for the\n// same flag set, but not both.\nfunc (c *Config) AddFlags(fs *flag.FlagSet) {\n\tfs.Var(c.Verbosity(), c.co.verbosityFlagName, \"number for the log level verbosity of the testing logger\")\n\tfs.Var(c.VModule(), c.co.vmoduleFlagName, \"comma-separated list of pattern=N log level settings for files matching the patterns\")\n}\n"
  },
  {
    "path": "textlogger/output_test.go",
    "content": "/*\nCopyright 2022 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger_test\n\nimport (\n\t\"io\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/test\"\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\n// These test cover the textlogger, directly and as backend.\nfunc newLogger(out io.Writer, v int, vmodule string) logr.Logger {\n\tconfig := textlogger.NewConfig(\n\t\ttextlogger.Verbosity(v),\n\t\ttextlogger.Output(out),\n\t)\n\tif err := config.VModule().Set(vmodule); err != nil {\n\t\tpanic(err)\n\t}\n\treturn textlogger.NewLogger(config)\n}\n\nvar (\n\tdirectConfig   = test.OutputConfig{NewLogger: newLogger, SupportsVModule: true}\n\tindirectConfig = test.OutputConfig{NewLogger: newLogger, AsBackend: true}\n)\n\nfunc TestTextloggerOutput(t *testing.T) {\n\ttest.InitKlog(t)\n\tt.Run(\"direct\", func(t *testing.T) {\n\t\ttest.Output(t, directConfig)\n\t})\n\tt.Run(\"klog-backend\", func(t *testing.T) {\n\t\ttest.Output(t, indirectConfig)\n\t})\n}\n\nfunc BenchmarkTextloggerOutput(b *testing.B) {\n\ttest.InitKlog(b)\n\ttest.Benchmark(b, directConfig)\n}\n"
  },
  {
    "path": "textlogger/textlogger.go",
    "content": "/*\nCopyright 2019 The Kubernetes Authors.\nCopyright 2020 Intel Corporation.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package textlogger contains an implementation of the logr interface which is\n// producing the exact same output as klog. It does not route output through\n// klog (i.e. ignores [k8s.io/klog/v2.InitFlags]). Instead, all settings must be\n// configured through its own [NewConfig] and [Config.AddFlags].\npackage textlogger\n\nimport (\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/severity\"\n\t\"k8s.io/klog/v2/internal/verbosity\"\n)\n\nvar (\n\t// TimeNow is used to retrieve the current time. May be changed for testing.\n\tTimeNow = time.Now\n)\n\nconst (\n\t// nameKey is used to log the `WithName` values as an additional attribute.\n\tnameKey = \"logger\"\n)\n\n// NewLogger constructs a new logger.\n//\n// Verbosity can be modified at any time through the Config.V and\n// Config.VModule API.\nfunc NewLogger(c *Config) logr.Logger {\n\treturn logr.New(&tlogger{\n\t\tvalues: nil,\n\t\tconfig: c,\n\t})\n}\n\ntype tlogger struct {\n\tcallDepth int\n\n\t// hasPrefix is true if the first entry in values is the special\n\t// nameKey key/value. Such an entry gets added and later updated in\n\t// WithName.\n\thasPrefix bool\n\n\tvalues []interface{}\n\tgroups string\n\tconfig *Config\n}\n\nfunc (l *tlogger) Init(info logr.RuntimeInfo) {\n\tl.callDepth = info.CallDepth\n}\n\nfunc (l *tlogger) WithCallDepth(depth int) logr.LogSink {\n\tnewLogger := *l\n\tnewLogger.callDepth += depth\n\treturn &newLogger\n}\n\nfunc (l *tlogger) Enabled(level int) bool {\n\treturn l.config.vstate.Enabled(verbosity.Level(level), 1+l.callDepth)\n}\n\nfunc (l *tlogger) Info(_ int, msg string, kvList ...interface{}) {\n\tl.print(nil, severity.InfoLog, msg, kvList)\n}\n\nfunc (l *tlogger) Error(err error, msg string, kvList ...interface{}) {\n\tl.print(err, severity.ErrorLog, msg, kvList)\n}\n\nfunc (l *tlogger) print(err error, s severity.Severity, msg string, kvList []interface{}) {\n\tvar file string\n\tvar line int\n\tvar now time.Time\n\tif l.config.co.withHeader {\n\t\t// Determine caller.\n\t\t// +1 for this frame, +1 for Info/Error.\n\t\tskip := l.callDepth + 2\n\t\tfile, line = l.config.co.unwind(skip)\n\t\tif file == \"\" {\n\t\t\tfile = \"???\"\n\t\t\tline = 1\n\t\t} else if slash := strings.LastIndex(file, \"/\"); slash >= 0 {\n\t\t\tfile = file[slash+1:]\n\t\t}\n\t\tnow = time.Now()\n\t}\n\tl.printWithInfos(file, line, now, err, s, msg, kvList)\n}\n\nfunc runtimeBacktrace(skip int) (string, int) {\n\t_, file, line, ok := runtime.Caller(skip + 1)\n\tif !ok {\n\t\treturn \"\", 0\n\t}\n\treturn file, line\n}\n\nfunc (l *tlogger) printWithInfos(file string, line int, now time.Time, err error, s severity.Severity, msg string, kvList []interface{}) {\n\t// The message is always quoted, even if it contains line breaks.\n\t// If developers want multi-line output, they should use a small, fixed\n\t// message and put the multi-line output into a value.\n\tqMsg := make([]byte, 0, 1024)\n\tqMsg = strconv.AppendQuote(qMsg, msg)\n\n\t// Only create a new buffer if we don't have one cached.\n\tb := buffer.GetBuffer()\n\tdefer buffer.PutBuffer(b)\n\n\tif l.config.co.withHeader {\n\t\t// Format header.\n\t\tif l.config.co.fixedTime != nil {\n\t\t\tnow = *l.config.co.fixedTime\n\t\t}\n\t\tb.FormatHeader(s, file, line, now)\n\t}\n\n\tb.Write(qMsg)\n\n\tvar errKV []interface{}\n\tif err != nil {\n\t\terrKV = []interface{}{\"err\", err}\n\t}\n\tserialize.FormatKVs(&b.Buffer, errKV, l.values, kvList)\n\tif b.Len() == 0 || b.Bytes()[b.Len()-1] != '\\n' {\n\t\tb.WriteByte('\\n')\n\t}\n\t_, _ = l.config.co.output.Write(b.Bytes())\n}\n\nfunc (l *tlogger) WriteKlogBuffer(data []byte) {\n\t_, _ = l.config.co.output.Write(data)\n}\n\n// WithName returns a new logr.Logger with the specified name appended.  klogr\n// uses '/' characters to separate name elements.  Callers should not pass '/'\n// in the provided name string, but this library does not actually enforce that.\nfunc (l *tlogger) WithName(name string) logr.LogSink {\n\tclone := *l\n\tif l.hasPrefix {\n\t\t// Copy slice and modify value. No length checks and type\n\t\t// assertions are needed because hasPrefix is only true if the\n\t\t// first two elements exist and are key/value strings.\n\t\tv := make([]interface{}, 0, len(l.values))\n\t\tv = append(v, l.values...)\n\t\tprefix, _ := v[1].(string)\n\t\tv[1] = prefix + \".\" + name\n\t\tclone.values = v\n\t} else {\n\t\t// Preprend new key/value pair.\n\t\tv := make([]interface{}, 0, 2+len(l.values))\n\t\tv = append(v, nameKey, name)\n\t\tv = append(v, l.values...)\n\t\tclone.values = v\n\t\tclone.hasPrefix = true\n\t}\n\treturn &clone\n}\n\nfunc (l *tlogger) WithValues(kvList ...interface{}) logr.LogSink {\n\tclone := *l\n\tclone.values = serialize.WithValues(l.values, kvList)\n\treturn &clone\n}\n\n// KlogBufferWriter is implemented by the textlogger LogSink.\ntype KlogBufferWriter interface {\n\t// WriteKlogBuffer takes a pre-formatted buffer prepared by klog and\n\t// writes it unchanged to the output stream. Can be used with\n\t// klog.WriteKlogBuffer when setting a logger through\n\t// klog.SetLoggerWithOptions.\n\tWriteKlogBuffer([]byte)\n}\n\nvar _ logr.LogSink = &tlogger{}\nvar _ logr.CallDepthLogSink = &tlogger{}\nvar _ KlogBufferWriter = &tlogger{}\n"
  },
  {
    "path": "textlogger/textlogger_slog.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"k8s.io/klog/v2/internal/serialize\"\n\t\"k8s.io/klog/v2/internal/sloghandler\"\n)\n\nfunc (l *tlogger) Handle(ctx context.Context, record slog.Record) error {\n\treturn sloghandler.Handle(ctx, record, l.groups, l.printWithInfos)\n}\n\nfunc (l *tlogger) WithAttrs(attrs []slog.Attr) logr.SlogSink {\n\tclone := *l\n\tclone.values = serialize.WithValues(l.values, sloghandler.Attrs2KVList(l.groups, attrs))\n\treturn &clone\n}\n\nfunc (l *tlogger) WithGroup(name string) logr.SlogSink {\n\tclone := *l\n\tif clone.groups != \"\" {\n\t\tclone.groups += \".\" + name\n\t} else {\n\t\tclone.groups = name\n\t}\n\treturn &clone\n}\n\nvar _ logr.SlogSink = &tlogger{}\n"
  },
  {
    "path": "textlogger/textlogger_slog_test.go",
    "content": "//go:build go1.21\n// +build go1.21\n\n/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger_test\n\nimport (\n\t\"errors\"\n\t\"log/slog\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr/slogr\"\n\tinternal \"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\nvar _ slog.LogValuer = coordinates{}\n\ntype coordinates struct {\n\tx, y int\n}\n\nfunc (c coordinates) LogValue() slog.Value {\n\treturn slog.GroupValue(slog.Attr{Key: \"X\", Value: slog.IntValue(c.x)}, slog.Attr{Key: \"Y\", Value: slog.IntValue(c.y)})\n}\n\nfunc ExampleNewLogger_slog() {\n\tts, _ := time.Parse(time.RFC3339, \"2000-12-24T12:30:40Z\")\n\tinternal.Pid = 123 // To get consistent output for each run.\n\tconfig := textlogger.NewConfig(\n\t\ttextlogger.FixedTime(ts), // To get consistent output for each run.\n\t\ttextlogger.Verbosity(4),  // Matches slog.LevelDebug.\n\t\ttextlogger.Output(os.Stdout),\n\t)\n\tlogrLogger := textlogger.NewLogger(config)\n\tslogHandler := slogr.NewSlogHandler(logrLogger)\n\tslogLogger := slog.New(slogHandler)\n\n\tslogLogger.Debug(\"A debug message\")\n\tslogLogger.Log(nil, slog.LevelDebug-1, \"A debug message with even lower level, not printed.\")\n\tslogLogger.Info(\"An info message\")\n\tslogLogger.Warn(\"A warning\")\n\tslogLogger.Error(\"An error\", \"err\", errors.New(\"fake error\"))\n\n\t// The slog API supports grouping, in contrast to the logr API.\n\tslogLogger.WithGroup(\"top\").With(\"int\", 42, slog.Group(\"variables\", \"a\", 1, \"b\", 2)).Info(\"Grouping\",\n\t\t\"sub\", slog.GroupValue(\n\t\t\tslog.Attr{Key: \"str\", Value: slog.StringValue(\"abc\")},\n\t\t\tslog.Attr{Key: \"bool\", Value: slog.BoolValue(true)},\n\t\t\tslog.Attr{Key: \"bottom\", Value: slog.GroupValue(slog.Attr{Key: \"coordinates\", Value: slog.AnyValue(coordinates{x: -1, y: -2})})},\n\t\t),\n\t\t\"duration\", slog.DurationValue(time.Second),\n\t\tslog.Float64(\"pi\", 3.12),\n\t\t\"e\", 2.71,\n\t\t\"moreCoordinates\", coordinates{x: 100, y: 200},\n\t)\n\n\t// slog special values are also supported when passed through the logr API.\n\t// This works with the textlogger, but might not work with other implementations\n\t// and thus isn't portable. Passing attributes (= key and value in a single\n\t// parameter) is not supported.\n\tlogrLogger.Info(\"slog values\",\n\t\t\"variables\", slog.GroupValue(slog.Int(\"a\", 1), slog.Int(\"b\", 2)),\n\t\t\"duration\", slog.DurationValue(time.Second),\n\t\t\"coordinates\", coordinates{x: 100, y: 200},\n\t)\n\n\t// Output:\n\t// I1224 12:30:40.000000     123 textlogger_slog_test.go:55] \"A debug message\"\n\t// I1224 12:30:40.000000     123 textlogger_slog_test.go:57] \"An info message\"\n\t// W1224 12:30:40.000000     123 textlogger_slog_test.go:58] \"A warning\"\n\t// E1224 12:30:40.000000     123 textlogger_slog_test.go:59] \"An error\" err=\"fake error\"\n\t// I1224 12:30:40.000000     123 textlogger_slog_test.go:62] \"Grouping\" top.int=42 top.variables={\"a\":1,\"b\":2} top.sub={\"str\":\"abc\",\"bool\":true,\"bottom\":{\"coordinates\":{\"X\":-1,\"Y\":-2}}} top.duration=\"1s\" top.pi=3.12 top.e=2.71 top.moreCoordinates={\"X\":100,\"Y\":200}\n\t// I1224 12:30:40.000000     123 textlogger_slog_test.go:78] \"slog values\" variables={\"a\":1,\"b\":2} duration=\"1s\" coordinates={\"X\":100,\"Y\":200}\n}\n"
  },
  {
    "path": "textlogger/textlogger_test.go",
    "content": "/*\nCopyright 2023 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage textlogger_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/go-logr/logr\"\n\t\"k8s.io/klog/v2\"\n\tinternal \"k8s.io/klog/v2/internal/buffer\"\n\t\"k8s.io/klog/v2/textlogger\"\n)\n\nvar _ logr.Marshaler = coordinatesMarshaler{}\n\ntype coordinatesMarshaler struct {\n\tx, y int\n}\n\nfunc (c coordinatesMarshaler) MarshalLog() interface{} {\n\treturn map[string]int{\"X\": c.x, \"Y\": c.y}\n}\n\ntype variables struct {\n\tA, B int\n}\n\nfunc ExampleNewLogger() {\n\tts, _ := time.Parse(time.RFC3339, \"2000-12-24T12:30:40Z\")\n\tinternal.Pid = 123 // To get consistent output for each run.\n\tconfig := textlogger.NewConfig(\n\t\ttextlogger.FixedTime(ts), // To get consistent output for each run.\n\t\ttextlogger.Verbosity(4),  // Matches Kubernetes \"debug\" level.\n\t\ttextlogger.Output(os.Stdout),\n\t)\n\tlogger := textlogger.NewLogger(config)\n\n\tlogger.V(4).Info(\"A debug message\")\n\tlogger.V(5).Info(\"A debug message with even lower level, not printed.\")\n\tlogger.Info(\"An info message\")\n\tlogger.Error(errors.New(\"fake error\"), \"An error\")\n\tlogger.WithValues(\"int\", 42).Info(\"With values\",\n\t\t\"duration\", time.Second,\n\t\t\"float\", 3.12,\n\t\t\"coordinates\", coordinatesMarshaler{x: 100, y: 200},\n\t\t\"variables\", variables{A: 1, B: 2},\n\t)\n\t// The logr API supports skipping functions during stack unwinding, in contrast to slog.\n\tsomeHelper(logger, \"hello world\")\n\n\t// Output:\n\t// I1224 12:30:40.000000     123 textlogger_test.go:56] \"A debug message\"\n\t// I1224 12:30:40.000000     123 textlogger_test.go:58] \"An info message\"\n\t// E1224 12:30:40.000000     123 textlogger_test.go:59] \"An error\" err=\"fake error\"\n\t// I1224 12:30:40.000000     123 textlogger_test.go:60] \"With values\" int=42 duration=\"1s\" float=3.12 coordinates={\"X\":100,\"Y\":200} variables={\"A\":1,\"B\":2}\n\t// I1224 12:30:40.000000     123 textlogger_test.go:67] \"hello world\"\n}\n\nfunc someHelper(logger klog.Logger, msg string) {\n\tlogger.WithCallDepth(1).Info(msg)\n}\n\nfunc ExampleBacktrace() {\n\tts, _ := time.Parse(time.RFC3339, \"2000-12-24T12:30:40Z\")\n\tinternal.Pid = 123 // To get consistent output for each run.\n\tbacktraceCounter := 0\n\tconfig := textlogger.NewConfig(\n\t\ttextlogger.FixedTime(ts), // To get consistent output for each run.\n\t\ttextlogger.Backtrace(func(_ /* skip */ int) (filename string, line int) {\n\t\t\tbacktraceCounter++\n\t\t\tif backtraceCounter == 1 {\n\t\t\t\t// Simulate \"missing information\".\n\t\t\t\treturn \"\", 0\n\t\t\t}\n\t\t\treturn \"fake.go\", 42\n\n\t\t\t// A real implementation could use Ginkgo:\n\t\t\t//\n\t\t\t// import ginkgotypes \"github.com/onsi/ginkgo/v2/types\"\n\t\t\t//\n\t\t\t// location := ginkgotypes.NewCodeLocation(skip + 1)\n\t\t\t// return location.FileName, location.LineNumber\n\t\t}),\n\t\ttextlogger.Output(os.Stdout),\n\t)\n\tlogger := textlogger.NewLogger(config)\n\n\tlogger.Info(\"First message\")\n\tlogger.Info(\"Second message\")\n\n\t// Output:\n\t// I1224 12:30:40.000000     123 ???:1] \"First message\"\n\t// I1224 12:30:40.000000     123 fake.go:42] \"Second message\"\n}\n\nfunc ExampleWithHeader() {\n\tvar buffer bytes.Buffer\n\tconfig := textlogger.NewConfig(\n\t\ttextlogger.WithHeader(false),\n\t\ttextlogger.Output(&buffer),\n\t)\n\tlogger := textlogger.NewLogger(config)\n\n\tlogger.Error(errors.New(\"fake error\"), \"Something broke\", \"id\", 42)\n\tlogger.WithName(\"name\").WithValues(\"key\", \"value\").WithCallDepth(0).Info(\"Still no header\")\n\tfmt.Println(buffer.String())\n\n\t// Output:\n\t// \"Something broke\" err=\"fake error\" id=42\n\t// \"Still no header\" logger=\"name\" key=\"value\"\n}\n"
  }
]