diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:41:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:41:39 +0000 |
commit | fcfb5e62f95d625836328131cc5ca851182bcae4 (patch) | |
tree | 5309ef2284a82d61ece838d1dd1c97c09df152b8 /dependencies/pkg/mod/go.uber.org/zap@v1.27.0 | |
parent | Adding upstream version 1.1.1. (diff) | |
download | icingadb-upstream.tar.xz icingadb-upstream.zip |
Adding upstream version 1.2.0.upstream/1.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dependencies/pkg/mod/go.uber.org/zap@v1.27.0')
145 files changed, 23226 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.codecov.yml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.codecov.yml new file mode 100644 index 0000000..8e5ca7d --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.codecov.yml @@ -0,0 +1,17 @@ +coverage: + range: 80..100 + round: down + precision: 2 + + status: + project: # measuring the overall project coverage + default: # context, you can create multiple ones with custom titles + enabled: yes # must be yes|true to enable this status + target: 95% # specify the target coverage for each commit status + # option: "auto" (must increase from parent commit or pull request base) + # option: "X%" a static target percentage to hit + if_not_found: success # if parent is not found report status as success, error, or failure + if_ci_failed: error # if ci fails report status as success, error, or failure +ignore: + - internal/readme/readme.go + diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/bug_report.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..96fe902 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/config.yml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..917a641 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Questions + about: Please use our Discussions page + url: https://github.com/uber-go/zap/discussions diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/feature_request.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..8a1ef5c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Is this a breaking change?** +We do not accept breaking changes to the existing API. Please consider if your proposed solution is backwards compatible. If not, we can help you make it backwards compatible, but this must be considered when we consider new features. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/dependabot.yml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/dependabot.yml new file mode 100644 index 0000000..33ac821 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + # Since Zap is a library, we don't want to update its dependency requirements + # regularly--not until we need a newer version of a dependency for a feature + # or specific fix. + # This way, users of Zap aren't forced to upgrade all their transitive + # dependencies every time they upgrade Zap. + # + # However, we do want to regularly update dependencies used inside the tools + # submodule because that holds linters and other development tools. + - package-ecosystem: "gomod" + directory: "/tools" + schedule: + interval: "weekly" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/fossa.yaml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/fossa.yaml new file mode 100644 index 0000000..3da3e0d --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/fossa.yaml @@ -0,0 +1,20 @@ +name: FOSSA Analysis +on: push + +permissions: + contents: read + +jobs: + + build: + runs-on: ubuntu-latest + if: github.repository_owner == 'uber-go' + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: FOSSA analysis + uses: fossas/fossa-action@v1 + with: + api-key: ${{ secrets.FOSSA_API_KEY }} + diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/go.yml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/go.yml new file mode 100644 index 0000000..2fa9a69 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.github/workflows/go.yml @@ -0,0 +1,69 @@ +name: Go + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + branches: ['*'] + +permissions: + contents: read + +jobs: + + build: + runs-on: ubuntu-latest + strategy: + matrix: + go: ["1.21.x", "1.22.x"] + include: + - go: 1.22.x + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go }} + cache-dependency-path: '**/go.sum' + + - name: Download Dependencies + run: | + go mod download + (cd tools && go mod download) + (cd benchmarks && go mod download) + (cd zapgrpc/internal/test && go mod download) + + - name: Test + run: make cover + + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@v3 + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + name: Check out repository + - uses: actions/setup-go@v4 + name: Set up Go + with: + go-version: 1.22.x + cache: false # managed by golangci-lint + + - uses: golangci/golangci-lint-action@v4 + name: Install golangci-lint + with: + version: latest + args: --version # make lint will run the linter + + - run: make lint + name: Lint + + - name: vulncheck + run: make vulncheck diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.gitignore b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.gitignore new file mode 100644 index 0000000..da9d9d0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.gitignore @@ -0,0 +1,32 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +vendor + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.pprof +*.out +*.log + +/bin +cover.out +cover.html diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.golangci.yml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.golangci.yml new file mode 100644 index 0000000..2346df1 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.golangci.yml @@ -0,0 +1,77 @@ +output: + # Make output more digestible with quickfix in vim/emacs/etc. + sort-results: true + print-issued-lines: false + +linters: + # We'll track the golangci-lint default linters manually + # instead of letting them change without our control. + disable-all: true + enable: + # golangci-lint defaults: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + + # Our own extras: + - gofumpt + - nolintlint # lints nolint directives + - revive + +linters-settings: + govet: + # These govet checks are disabled by default, but they're useful. + enable: + - niliness + - reflectvaluecompare + - sortslice + - unusedwrite + + errcheck: + exclude-functions: + # These methods can not fail. + # They operate on an in-memory buffer. + - (*go.uber.org/zap/buffer.Buffer).Write + - (*go.uber.org/zap/buffer.Buffer).WriteByte + - (*go.uber.org/zap/buffer.Buffer).WriteString + + - (*go.uber.org/zap/zapio.Writer).Close + - (*go.uber.org/zap/zapio.Writer).Sync + - (*go.uber.org/zap/zapio.Writer).Write + # Write to zapio.Writer cannot fail, + # so io.WriteString on it cannot fail. + - io.WriteString(*go.uber.org/zap/zapio.Writer) + + # Writing a plain string to a fmt.State cannot fail. + - io.WriteString(fmt.State) + +issues: + # Print all issues reported by all linters. + max-issues-per-linter: 0 + max-same-issues: 0 + + # Don't ignore some of the issues that golangci-lint considers okay. + # This includes documenting all exported entities. + exclude-use-default: false + + exclude-rules: + # Don't warn on unused parameters. + # Parameter names are useful; replacing them with '_' is undesirable. + - linters: [revive] + text: 'unused-parameter: parameter \S+ seems to be unused, consider removing or renaming it as _' + + # staticcheck already has smarter checks for empty blocks. + # revive's empty-block linter has false positives. + # For example, as of writing this, the following is not allowed. + # for foo() { } + - linters: [revive] + text: 'empty-block: this block is empty, you can remove it' + + # Ignore logger.Sync() errcheck failures in example_test.go + # since those are intended to be uncomplicated examples. + - linters: [errcheck] + path: example_test.go + text: 'Error return value of `logger.Sync` is not checked' diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.readme.tmpl b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.readme.tmpl new file mode 100644 index 0000000..4fea302 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/.readme.tmpl @@ -0,0 +1,117 @@ +# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +<div align="center"> + +Blazing fast, structured, leveled logging in Go. + +![Zap logo](assets/logo.png) + +[![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +</div> + +## Installation + +`go get -u go.uber.org/zap` + +Note that zap only supports the two most recent minor versions of Go. + +## Quick Start + +In contexts where performance is nice, but not critical, use the +`SugaredLogger`. It's 4-10x faster than other structured logging +packages and includes both structured and `printf`-style APIs. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() // flushes buffer, if any +sugar := logger.Sugar() +sugar.Infow("failed to fetch URL", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, +) +sugar.Infof("Failed to fetch URL: %s", url) +``` + +When performance and type safety are critical, use the `Logger`. It's even +faster than the `SugaredLogger` and allocates far less, but it only supports +structured logging. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() +logger.Info("failed to fetch URL", + // Structured context as strongly typed Field values. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), +) +``` + +See the [documentation][doc] and [FAQ](FAQ.md) for more details. + +## Performance + +For applications that log in the hot path, reflection-based serialization and +string formatting are prohibitively expensive — they're CPU-intensive +and make many small allocations. Put differently, using `encoding/json` and +`fmt.Fprintf` to log tons of `interface{}`s makes your application slow. + +Zap takes a different approach. It includes a reflection-free, zero-allocation +JSON encoder, and the base `Logger` strives to avoid serialization overhead +and allocations wherever possible. By building the high-level `SugaredLogger` +on that foundation, zap lets users *choose* when they need to count every +allocation and when they'd prefer a more familiar, loosely typed API. + +As measured by its own [benchmarking suite][], not only is zap more performant +than comparable structured logging packages — it's also faster than the +standard library. Like all benchmarks, take these with a grain of salt.<sup +id="anchor-versions">[1](#footnote-versions)</sup> + +Log a message and 10 fields: + +{{.BenchmarkAddingFields}} + +Log a message with a logger that already has 10 fields of context: + +{{.BenchmarkAccumulatedContext}} + +Log a static string, without any context or `printf`-style templating: + +{{.BenchmarkWithoutFields}} + +## Development Status: Stable + +All APIs are finalized, and no breaking changes will be made in the 1.x series +of releases. Users of semver-aware dependency management systems should pin +zap to `^1`. + +## Contributing + +We encourage and support an active, healthy community of contributors — +including you! Details are in the [contribution guide](CONTRIBUTING.md) and +the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on +issues and pull requests, but you can also report any negative conduct to +oss-conduct@uber.com. That email list is a private, safe space; even the zap +maintainers don't have access, so don't hesitate to hold us to a high +standard. + +<hr> + +Released under the [MIT License](LICENSE). + +<sup id="footnote-versions">1</sup> In particular, keep in mind that we may be +benchmarking against slightly older versions of other packages. Versions are +pinned in the [benchmarks/go.mod][] file. [↩](#anchor-versions) + +[doc-img]: https://pkg.go.dev/badge/go.uber.org/zap +[doc]: https://pkg.go.dev/go.uber.org/zap +[ci-img]: https://github.com/uber-go/zap/actions/workflows/go.yml/badge.svg +[ci]: https://github.com/uber-go/zap/actions/workflows/go.yml +[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/zap +[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks +[benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod + diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CHANGELOG.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CHANGELOG.md new file mode 100644 index 0000000..6d6cd5f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CHANGELOG.md @@ -0,0 +1,687 @@ +# Changelog +All notable changes to this project will be documented in this file. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.27.0 (20 Feb 2024) +Enhancements: +* [#1378][]: Add `WithLazy` method for `SugaredLogger`. +* [#1399][]: zaptest: Add `NewTestingWriter` for customizing TestingWriter with more flexibility than `NewLogger`. +* [#1406][]: Add `Log`, `Logw`, `Logln` methods for `SugaredLogger`. +* [#1416][]: Add `WithPanicHook` option for testing panic logs. + +Thanks to @defval, @dimmo, @arxeiss, and @MKrupauskas for their contributions to this release. + +[#1378]: https://github.com/uber-go/zap/pull/1378 +[#1399]: https://github.com/uber-go/zap/pull/1399 +[#1406]: https://github.com/uber-go/zap/pull/1406 +[#1416]: https://github.com/uber-go/zap/pull/1416 + +## 1.26.0 (14 Sep 2023) +Enhancements: +* [#1297][]: Add Dict as a Field. +* [#1319][]: Add `WithLazy` method to `Logger` which lazily evaluates the structured +context. +* [#1350][]: String encoding is much (~50%) faster now. + +Thanks to @hhk7734, @jquirke, and @cdvr1993 for their contributions to this release. + +[#1297]: https://github.com/uber-go/zap/pull/1297 +[#1319]: https://github.com/uber-go/zap/pull/1319 +[#1350]: https://github.com/uber-go/zap/pull/1350 + +## 1.25.0 (1 Aug 2023) + +This release contains several improvements including performance, API additions, +and two new experimental packages whose APIs are unstable and may change in the +future. + +Enhancements: +* [#1246][]: Add `zap/exp/zapslog` package for integration with slog. +* [#1273][]: Add `Name` to `Logger` which returns the Logger's name if one is set. +* [#1281][]: Add `zap/exp/expfield` package which contains helper methods +`Str` and `Strs` for constructing String-like zap.Fields. +* [#1310][]: Reduce stack size on `Any`. + +Thanks to @knight42, @dzakaammar, @bcspragu, and @rexywork for their contributions +to this release. + +[#1246]: https://github.com/uber-go/zap/pull/1246 +[#1273]: https://github.com/uber-go/zap/pull/1273 +[#1281]: https://github.com/uber-go/zap/pull/1281 +[#1310]: https://github.com/uber-go/zap/pull/1310 + +## 1.24.0 (30 Nov 2022) + +Enhancements: +* [#1148][]: Add `Level` to both `Logger` and `SugaredLogger` that reports the + current minimum enabled log level. +* [#1185][]: `SugaredLogger` turns errors to zap.Error automatically. + +Thanks to @Abirdcfly, @craigpastro, @nnnkkk7, and @sashamelentyev for their +contributions to this release. + +[#1148]: https://github.coml/uber-go/zap/pull/1148 +[#1185]: https://github.coml/uber-go/zap/pull/1185 + +## 1.23.0 (24 Aug 2022) + +Enhancements: +* [#1147][]: Add a `zapcore.LevelOf` function to determine the level of a + `LevelEnabler` or `Core`. +* [#1155][]: Add `zap.Stringers` field constructor to log arrays of objects + that implement `String() string`. + +[#1147]: https://github.com/uber-go/zap/pull/1147 +[#1155]: https://github.com/uber-go/zap/pull/1155 + +## 1.22.0 (8 Aug 2022) + +Enhancements: +* [#1071][]: Add `zap.Objects` and `zap.ObjectValues` field constructors to log + arrays of objects. With these two constructors, you don't need to implement + `zapcore.ArrayMarshaler` for use with `zap.Array` if those objects implement + `zapcore.ObjectMarshaler`. +* [#1079][]: Add `SugaredLogger.WithOptions` to build a copy of an existing + `SugaredLogger` with the provided options applied. +* [#1080][]: Add `*ln` variants to `SugaredLogger` for each log level. + These functions provide a string joining behavior similar to `fmt.Println`. +* [#1088][]: Add `zap.WithFatalHook` option to control the behavior of the + logger for `Fatal`-level log entries. This defaults to exiting the program. +* [#1108][]: Add a `zap.Must` function that you can use with `NewProduction` or + `NewDevelopment` to panic if the system was unable to build the logger. +* [#1118][]: Add a `Logger.Log` method that allows specifying the log level for + a statement dynamically. + +Thanks to @cardil, @craigpastro, @sashamelentyev, @shota3506, and @zhupeijun +for their contributions to this release. + +[#1071]: https://github.com/uber-go/zap/pull/1071 +[#1079]: https://github.com/uber-go/zap/pull/1079 +[#1080]: https://github.com/uber-go/zap/pull/1080 +[#1088]: https://github.com/uber-go/zap/pull/1088 +[#1108]: https://github.com/uber-go/zap/pull/1108 +[#1118]: https://github.com/uber-go/zap/pull/1118 + +## 1.21.0 (7 Feb 2022) + +Enhancements: +* [#1047][]: Add `zapcore.ParseLevel` to parse a `Level` from a string. +* [#1048][]: Add `zap.ParseAtomicLevel` to parse an `AtomicLevel` from a + string. + +Bugfixes: +* [#1058][]: Fix panic in JSON encoder when `EncodeLevel` is unset. + +Other changes: +* [#1052][]: Improve encoding performance when the `AddCaller` and + `AddStacktrace` options are used together. + +[#1047]: https://github.com/uber-go/zap/pull/1047 +[#1048]: https://github.com/uber-go/zap/pull/1048 +[#1052]: https://github.com/uber-go/zap/pull/1052 +[#1058]: https://github.com/uber-go/zap/pull/1058 + +Thanks to @aerosol and @Techassi for their contributions to this release. + +## 1.20.0 (4 Jan 2022) + +Enhancements: +* [#989][]: Add `EncoderConfig.SkipLineEnding` flag to disable adding newline + characters between log statements. +* [#1039][]: Add `EncoderConfig.NewReflectedEncoder` field to customize JSON + encoding of reflected log fields. + +Bugfixes: +* [#1011][]: Fix inaccurate precision when encoding complex64 as JSON. +* [#554][], [#1017][]: Close JSON namespaces opened in `MarshalLogObject` + methods when the methods return. +* [#1033][]: Avoid panicking in Sampler core if `thereafter` is zero. + +Other changes: +* [#1028][]: Drop support for Go < 1.15. + +[#554]: https://github.com/uber-go/zap/pull/554 +[#989]: https://github.com/uber-go/zap/pull/989 +[#1011]: https://github.com/uber-go/zap/pull/1011 +[#1017]: https://github.com/uber-go/zap/pull/1017 +[#1028]: https://github.com/uber-go/zap/pull/1028 +[#1033]: https://github.com/uber-go/zap/pull/1033 +[#1039]: https://github.com/uber-go/zap/pull/1039 + +Thanks to @psrajat, @lruggieri, @sammyrnycreal for their contributions to this release. + +## 1.19.1 (8 Sep 2021) + +Bugfixes: +* [#1001][]: JSON: Fix complex number encoding with negative imaginary part. Thanks to @hemantjadon. +* [#1003][]: JSON: Fix inaccurate precision when encoding float32. + +[#1001]: https://github.com/uber-go/zap/pull/1001 +[#1003]: https://github.com/uber-go/zap/pull/1003 + +## 1.19.0 (9 Aug 2021) + +Enhancements: +* [#975][]: Avoid panicking in Sampler core if the level is out of bounds. +* [#984][]: Reduce the size of BufferedWriteSyncer by aligning the fields + better. + +[#975]: https://github.com/uber-go/zap/pull/975 +[#984]: https://github.com/uber-go/zap/pull/984 + +Thanks to @lancoLiu and @thockin for their contributions to this release. + +## 1.18.1 (28 Jun 2021) + +Bugfixes: +* [#974][]: Fix nil dereference in logger constructed by `zap.NewNop`. + +[#974]: https://github.com/uber-go/zap/pull/974 + +## 1.18.0 (28 Jun 2021) + +Enhancements: +* [#961][]: Add `zapcore.BufferedWriteSyncer`, a new `WriteSyncer` that buffers + messages in-memory and flushes them periodically. +* [#971][]: Add `zapio.Writer` to use a Zap logger as an `io.Writer`. +* [#897][]: Add `zap.WithClock` option to control the source of time via the + new `zapcore.Clock` interface. +* [#949][]: Avoid panicking in `zap.SugaredLogger` when arguments of `*w` + methods don't match expectations. +* [#943][]: Add support for filtering by level or arbitrary matcher function to + `zaptest/observer`. +* [#691][]: Comply with `io.StringWriter` and `io.ByteWriter` in Zap's + `buffer.Buffer`. + +Thanks to @atrn0, @ernado, @heyanfu, @hnlq715, @zchee +for their contributions to this release. + +[#691]: https://github.com/uber-go/zap/pull/691 +[#897]: https://github.com/uber-go/zap/pull/897 +[#943]: https://github.com/uber-go/zap/pull/943 +[#949]: https://github.com/uber-go/zap/pull/949 +[#961]: https://github.com/uber-go/zap/pull/961 +[#971]: https://github.com/uber-go/zap/pull/971 + +## 1.17.0 (25 May 2021) + +Bugfixes: +* [#867][]: Encode `<nil>` for nil `error` instead of a panic. +* [#931][], [#936][]: Update minimum version constraints to address + vulnerabilities in dependencies. + +Enhancements: +* [#865][]: Improve alignment of fields of the Logger struct, reducing its + size from 96 to 80 bytes. +* [#881][]: Support `grpclog.LoggerV2` in zapgrpc. +* [#903][]: Support URL-encoded POST requests to the AtomicLevel HTTP handler + with the `application/x-www-form-urlencoded` content type. +* [#912][]: Support multi-field encoding with `zap.Inline`. +* [#913][]: Speed up SugaredLogger for calls with a single string. +* [#928][]: Add support for filtering by field name to `zaptest/observer`. + +Thanks to @ash2k, @FMLS, @jimmystewpot, @Oncilla, @tsoslow, @tylitianrui, @withshubh, and @wziww for their contributions to this release. + +[#865]: https://github.com/uber-go/zap/pull/865 +[#867]: https://github.com/uber-go/zap/pull/867 +[#881]: https://github.com/uber-go/zap/pull/881 +[#903]: https://github.com/uber-go/zap/pull/903 +[#912]: https://github.com/uber-go/zap/pull/912 +[#913]: https://github.com/uber-go/zap/pull/913 +[#928]: https://github.com/uber-go/zap/pull/928 +[#931]: https://github.com/uber-go/zap/pull/931 +[#936]: https://github.com/uber-go/zap/pull/936 + +## 1.16.0 (1 Sep 2020) + +Bugfixes: +* [#828][]: Fix missing newline in IncreaseLevel error messages. +* [#835][]: Fix panic in JSON encoder when encoding times or durations + without specifying a time or duration encoder. +* [#843][]: Honor CallerSkip when taking stack traces. +* [#862][]: Fix the default file permissions to use `0666` and rely on the umask instead. +* [#854][]: Encode `<nil>` for nil `Stringer` instead of a panic error log. + +Enhancements: +* [#629][]: Added `zapcore.TimeEncoderOfLayout` to easily create time encoders + for custom layouts. +* [#697][]: Added support for a configurable delimiter in the console encoder. +* [#852][]: Optimize console encoder by pooling the underlying JSON encoder. +* [#844][]: Add ability to include the calling function as part of logs. +* [#843][]: Add `StackSkip` for including truncated stacks as a field. +* [#861][]: Add options to customize Fatal behaviour for better testability. + +Thanks to @SteelPhase, @tmshn, @lixingwang, @wyxloading, @moul, @segevfiner, @andy-retailnext and @jcorbin for their contributions to this release. + +[#629]: https://github.com/uber-go/zap/pull/629 +[#697]: https://github.com/uber-go/zap/pull/697 +[#828]: https://github.com/uber-go/zap/pull/828 +[#835]: https://github.com/uber-go/zap/pull/835 +[#843]: https://github.com/uber-go/zap/pull/843 +[#844]: https://github.com/uber-go/zap/pull/844 +[#852]: https://github.com/uber-go/zap/pull/852 +[#854]: https://github.com/uber-go/zap/pull/854 +[#861]: https://github.com/uber-go/zap/pull/861 +[#862]: https://github.com/uber-go/zap/pull/862 + +## 1.15.0 (23 Apr 2020) + +Bugfixes: +* [#804][]: Fix handling of `Time` values out of `UnixNano` range. +* [#812][]: Fix `IncreaseLevel` being reset after a call to `With`. + +Enhancements: +* [#806][]: Add `WithCaller` option to supersede the `AddCaller` option. This + allows disabling annotation of log entries with caller information if + previously enabled with `AddCaller`. +* [#813][]: Deprecate `NewSampler` constructor in favor of + `NewSamplerWithOptions` which supports a `SamplerHook` option. This option + adds support for monitoring sampling decisions through a hook. + +Thanks to @danielbprice for their contributions to this release. + +[#804]: https://github.com/uber-go/zap/pull/804 +[#812]: https://github.com/uber-go/zap/pull/812 +[#806]: https://github.com/uber-go/zap/pull/806 +[#813]: https://github.com/uber-go/zap/pull/813 + +## 1.14.1 (14 Mar 2020) + +Bugfixes: +* [#791][]: Fix panic on attempting to build a logger with an invalid Config. +* [#795][]: Vendoring Zap with `go mod vendor` no longer includes Zap's + development-time dependencies. +* [#799][]: Fix issue introduced in 1.14.0 that caused invalid JSON output to + be generated for arrays of `time.Time` objects when using string-based time + formats. + +Thanks to @YashishDua for their contributions to this release. + +[#791]: https://github.com/uber-go/zap/pull/791 +[#795]: https://github.com/uber-go/zap/pull/795 +[#799]: https://github.com/uber-go/zap/pull/799 + +## 1.14.0 (20 Feb 2020) + +Enhancements: +* [#771][]: Optimize calls for disabled log levels. +* [#773][]: Add millisecond duration encoder. +* [#775][]: Add option to increase the level of a logger. +* [#786][]: Optimize time formatters using `Time.AppendFormat` where possible. + +Thanks to @caibirdme for their contributions to this release. + +[#771]: https://github.com/uber-go/zap/pull/771 +[#773]: https://github.com/uber-go/zap/pull/773 +[#775]: https://github.com/uber-go/zap/pull/775 +[#786]: https://github.com/uber-go/zap/pull/786 + +## 1.13.0 (13 Nov 2019) + +Enhancements: +* [#758][]: Add `Intp`, `Stringp`, and other similar `*p` field constructors + to log pointers to primitives with support for `nil` values. + +Thanks to @jbizzle for their contributions to this release. + +[#758]: https://github.com/uber-go/zap/pull/758 + +## 1.12.0 (29 Oct 2019) + +Enhancements: +* [#751][]: Migrate to Go modules. + +[#751]: https://github.com/uber-go/zap/pull/751 + +## 1.11.0 (21 Oct 2019) + +Enhancements: +* [#725][]: Add `zapcore.OmitKey` to omit keys in an `EncoderConfig`. +* [#736][]: Add `RFC3339` and `RFC3339Nano` time encoders. + +Thanks to @juicemia, @uhthomas for their contributions to this release. + +[#725]: https://github.com/uber-go/zap/pull/725 +[#736]: https://github.com/uber-go/zap/pull/736 + +## 1.10.0 (29 Apr 2019) + +Bugfixes: +* [#657][]: Fix `MapObjectEncoder.AppendByteString` not adding value as a + string. +* [#706][]: Fix incorrect call depth to determine caller in Go 1.12. + +Enhancements: +* [#610][]: Add `zaptest.WrapOptions` to wrap `zap.Option` for creating test + loggers. +* [#675][]: Don't panic when encoding a String field. +* [#704][]: Disable HTML escaping for JSON objects encoded using the + reflect-based encoder. + +Thanks to @iaroslav-ciupin, @lelenanam, @joa, @NWilson for their contributions +to this release. + +[#657]: https://github.com/uber-go/zap/pull/657 +[#706]: https://github.com/uber-go/zap/pull/706 +[#610]: https://github.com/uber-go/zap/pull/610 +[#675]: https://github.com/uber-go/zap/pull/675 +[#704]: https://github.com/uber-go/zap/pull/704 + +## 1.9.1 (06 Aug 2018) + +Bugfixes: + +* [#614][]: MapObjectEncoder should not ignore empty slices. + +[#614]: https://github.com/uber-go/zap/pull/614 + +## 1.9.0 (19 Jul 2018) + +Enhancements: +* [#602][]: Reduce number of allocations when logging with reflection. +* [#572][], [#606][]: Expose a registry for third-party logging sinks. + +Thanks to @nfarah86, @AlekSi, @JeanMertz, @philippgille, @etsangsplk, and +@dimroc for their contributions to this release. + +[#602]: https://github.com/uber-go/zap/pull/602 +[#572]: https://github.com/uber-go/zap/pull/572 +[#606]: https://github.com/uber-go/zap/pull/606 + +## 1.8.0 (13 Apr 2018) + +Enhancements: +* [#508][]: Make log level configurable when redirecting the standard + library's logger. +* [#518][]: Add a logger that writes to a `*testing.TB`. +* [#577][]: Add a top-level alias for `zapcore.Field` to clean up GoDoc. + +Bugfixes: +* [#574][]: Add a missing import comment to `go.uber.org/zap/buffer`. + +Thanks to @DiSiqueira and @djui for their contributions to this release. + +[#508]: https://github.com/uber-go/zap/pull/508 +[#518]: https://github.com/uber-go/zap/pull/518 +[#577]: https://github.com/uber-go/zap/pull/577 +[#574]: https://github.com/uber-go/zap/pull/574 + +## 1.7.1 (25 Sep 2017) + +Bugfixes: +* [#504][]: Store strings when using AddByteString with the map encoder. + +[#504]: https://github.com/uber-go/zap/pull/504 + +## 1.7.0 (21 Sep 2017) + +Enhancements: + +* [#487][]: Add `NewStdLogAt`, which extends `NewStdLog` by allowing the user + to specify the level of the logged messages. + +[#487]: https://github.com/uber-go/zap/pull/487 + +## 1.6.0 (30 Aug 2017) + +Enhancements: + +* [#491][]: Omit zap stack frames from stacktraces. +* [#490][]: Add a `ContextMap` method to observer logs for simpler + field validation in tests. + +[#490]: https://github.com/uber-go/zap/pull/490 +[#491]: https://github.com/uber-go/zap/pull/491 + +## 1.5.0 (22 Jul 2017) + +Enhancements: + +* [#460][] and [#470][]: Support errors produced by `go.uber.org/multierr`. +* [#465][]: Support user-supplied encoders for logger names. + +Bugfixes: + +* [#477][]: Fix a bug that incorrectly truncated deep stacktraces. + +Thanks to @richard-tunein and @pavius for their contributions to this release. + +[#477]: https://github.com/uber-go/zap/pull/477 +[#465]: https://github.com/uber-go/zap/pull/465 +[#460]: https://github.com/uber-go/zap/pull/460 +[#470]: https://github.com/uber-go/zap/pull/470 + +## 1.4.1 (08 Jun 2017) + +This release fixes two bugs. + +Bugfixes: + +* [#435][]: Support a variety of case conventions when unmarshaling levels. +* [#444][]: Fix a panic in the observer. + +[#435]: https://github.com/uber-go/zap/pull/435 +[#444]: https://github.com/uber-go/zap/pull/444 + +## 1.4.0 (12 May 2017) + +This release adds a few small features and is fully backward-compatible. + +Enhancements: + +* [#424][]: Add a `LineEnding` field to `EncoderConfig`, allowing users to + override the Unix-style default. +* [#425][]: Preserve time zones when logging times. +* [#431][]: Make `zap.AtomicLevel` implement `fmt.Stringer`, which makes a + variety of operations a bit simpler. + +[#424]: https://github.com/uber-go/zap/pull/424 +[#425]: https://github.com/uber-go/zap/pull/425 +[#431]: https://github.com/uber-go/zap/pull/431 + +## 1.3.0 (25 Apr 2017) + +This release adds an enhancement to zap's testing helpers as well as the +ability to marshal an AtomicLevel. It is fully backward-compatible. + +Enhancements: + +* [#415][]: Add a substring-filtering helper to zap's observer. This is + particularly useful when testing the `SugaredLogger`. +* [#416][]: Make `AtomicLevel` implement `encoding.TextMarshaler`. + +[#415]: https://github.com/uber-go/zap/pull/415 +[#416]: https://github.com/uber-go/zap/pull/416 + +## 1.2.0 (13 Apr 2017) + +This release adds a gRPC compatibility wrapper. It is fully backward-compatible. + +Enhancements: + +* [#402][]: Add a `zapgrpc` package that wraps zap's Logger and implements + `grpclog.Logger`. + +[#402]: https://github.com/uber-go/zap/pull/402 + +## 1.1.0 (31 Mar 2017) + +This release fixes two bugs and adds some enhancements to zap's testing helpers. +It is fully backward-compatible. + +Bugfixes: + +* [#385][]: Fix caller path trimming on Windows. +* [#396][]: Fix a panic when attempting to use non-existent directories with + zap's configuration struct. + +Enhancements: + +* [#386][]: Add filtering helpers to zaptest's observing logger. + +Thanks to @moitias for contributing to this release. + +[#385]: https://github.com/uber-go/zap/pull/385 +[#396]: https://github.com/uber-go/zap/pull/396 +[#386]: https://github.com/uber-go/zap/pull/386 + +## 1.0.0 (14 Mar 2017) + +This is zap's first stable release. All exported APIs are now final, and no +further breaking changes will be made in the 1.x release series. Anyone using a +semver-aware dependency manager should now pin to `^1`. + +Breaking changes: + +* [#366][]: Add byte-oriented APIs to encoders to log UTF-8 encoded text without + casting from `[]byte` to `string`. +* [#364][]: To support buffering outputs, add `Sync` methods to `zapcore.Core`, + `zap.Logger`, and `zap.SugaredLogger`. +* [#371][]: Rename the `testutils` package to `zaptest`, which is less likely to + clash with other testing helpers. + +Bugfixes: + +* [#362][]: Make the ISO8601 time formatters fixed-width, which is friendlier + for tab-separated console output. +* [#369][]: Remove the automatic locks in `zapcore.NewCore`, which allows zap to + work with concurrency-safe `WriteSyncer` implementations. +* [#347][]: Stop reporting errors when trying to `fsync` standard out on Linux + systems. +* [#373][]: Report the correct caller from zap's standard library + interoperability wrappers. + +Enhancements: + +* [#348][]: Add a registry allowing third-party encodings to work with zap's + built-in `Config`. +* [#327][]: Make the representation of logger callers configurable (like times, + levels, and durations). +* [#376][]: Allow third-party encoders to use their own buffer pools, which + removes the last performance advantage that zap's encoders have over plugins. +* [#346][]: Add `CombineWriteSyncers`, a convenience function to tee multiple + `WriteSyncer`s and lock the result. +* [#365][]: Make zap's stacktraces compatible with mid-stack inlining (coming in + Go 1.9). +* [#372][]: Export zap's observing logger as `zaptest/observer`. This makes it + easier for particularly punctilious users to unit test their application's + logging. + +Thanks to @suyash, @htrendev, @flisky, @Ulexus, and @skipor for their +contributions to this release. + +[#366]: https://github.com/uber-go/zap/pull/366 +[#364]: https://github.com/uber-go/zap/pull/364 +[#371]: https://github.com/uber-go/zap/pull/371 +[#362]: https://github.com/uber-go/zap/pull/362 +[#369]: https://github.com/uber-go/zap/pull/369 +[#347]: https://github.com/uber-go/zap/pull/347 +[#373]: https://github.com/uber-go/zap/pull/373 +[#348]: https://github.com/uber-go/zap/pull/348 +[#327]: https://github.com/uber-go/zap/pull/327 +[#376]: https://github.com/uber-go/zap/pull/376 +[#346]: https://github.com/uber-go/zap/pull/346 +[#365]: https://github.com/uber-go/zap/pull/365 +[#372]: https://github.com/uber-go/zap/pull/372 + +## 1.0.0-rc.3 (7 Mar 2017) + +This is the third release candidate for zap's stable release. There are no +breaking changes. + +Bugfixes: + +* [#339][]: Byte slices passed to `zap.Any` are now correctly treated as binary blobs + rather than `[]uint8`. + +Enhancements: + +* [#307][]: Users can opt into colored output for log levels. +* [#353][]: In addition to hijacking the output of the standard library's + package-global logging functions, users can now construct a zap-backed + `log.Logger` instance. +* [#311][]: Frames from common runtime functions and some of zap's internal + machinery are now omitted from stacktraces. + +Thanks to @ansel1 and @suyash for their contributions to this release. + +[#339]: https://github.com/uber-go/zap/pull/339 +[#307]: https://github.com/uber-go/zap/pull/307 +[#353]: https://github.com/uber-go/zap/pull/353 +[#311]: https://github.com/uber-go/zap/pull/311 + +## 1.0.0-rc.2 (21 Feb 2017) + +This is the second release candidate for zap's stable release. It includes two +breaking changes. + +Breaking changes: + +* [#316][]: Zap's global loggers are now fully concurrency-safe + (previously, users had to ensure that `ReplaceGlobals` was called before the + loggers were in use). However, they must now be accessed via the `L()` and + `S()` functions. Users can update their projects with + + ``` + gofmt -r "zap.L -> zap.L()" -w . + gofmt -r "zap.S -> zap.S()" -w . + ``` +* [#309][] and [#317][]: RC1 was mistakenly shipped with invalid + JSON and YAML struct tags on all config structs. This release fixes the tags + and adds static analysis to prevent similar bugs in the future. + +Bugfixes: + +* [#321][]: Redirecting the standard library's `log` output now + correctly reports the logger's caller. + +Enhancements: + +* [#325][] and [#333][]: Zap now transparently supports non-standard, rich + errors like those produced by `github.com/pkg/errors`. +* [#326][]: Though `New(nil)` continues to return a no-op logger, `NewNop()` is + now preferred. Users can update their projects with `gofmt -r 'zap.New(nil) -> + zap.NewNop()' -w .`. +* [#300][]: Incorrectly importing zap as `github.com/uber-go/zap` now returns a + more informative error. + +Thanks to @skipor and @chapsuk for their contributions to this release. + +[#316]: https://github.com/uber-go/zap/pull/316 +[#309]: https://github.com/uber-go/zap/pull/309 +[#317]: https://github.com/uber-go/zap/pull/317 +[#321]: https://github.com/uber-go/zap/pull/321 +[#325]: https://github.com/uber-go/zap/pull/325 +[#333]: https://github.com/uber-go/zap/pull/333 +[#326]: https://github.com/uber-go/zap/pull/326 +[#300]: https://github.com/uber-go/zap/pull/300 + +## 1.0.0-rc.1 (14 Feb 2017) + +This is the first release candidate for zap's stable release. There are multiple +breaking changes and improvements from the pre-release version. Most notably: + +* **Zap's import path is now "go.uber.org/zap"** — all users will + need to update their code. +* User-facing types and functions remain in the `zap` package. Code relevant + largely to extension authors is now in the `zapcore` package. +* The `zapcore.Core` type makes it easy for third-party packages to use zap's + internals but provide a different user-facing API. +* `Logger` is now a concrete type instead of an interface. +* A less verbose (though slower) logging API is included by default. +* Package-global loggers `L` and `S` are included. +* A human-friendly console encoder is included. +* A declarative config struct allows common logger configurations to be managed + as configuration instead of code. +* Sampling is more accurate, and doesn't depend on the standard library's shared + timer heap. + +## 0.1.0-beta.1 (6 Feb 2017) + +This is a minor version, tagged to allow users to pin to the pre-1.0 APIs and +upgrade at their leisure. Since this is the first tagged release, there are no +backward compatibility concerns and all functionality is new. + +Early zap adopters should pin to the 0.1.x minor version until they're ready to +upgrade to the upcoming stable release. diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CODE_OF_CONDUCT.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e327d9a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at oss-conduct@uber.com. The project +team will review and investigate all complaints, and will respond in a way +that it deems appropriate to the circumstances. The project team is obligated +to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +[http://contributor-covenant.org/version/1/4][version]. + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CONTRIBUTING.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CONTRIBUTING.md new file mode 100644 index 0000000..ea02f3c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Contributing + +We'd love your help making zap the very best structured logging library in Go! + +If you'd like to add new exported APIs, please [open an issue][open-issue] +describing your proposal — discussing API changes ahead of time makes +pull request review much smoother. In your issue, pull request, and any other +communications, please remember to treat your fellow contributors with +respect! We take our [code of conduct](CODE_OF_CONDUCT.md) seriously. + +Note that you'll need to sign [Uber's Contributor License Agreement][cla] +before we can accept any of your contributions. If necessary, a bot will remind +you to accept the CLA when you open your pull request. + +## Setup + +[Fork][fork], then clone the repository: + +```bash +mkdir -p $GOPATH/src/go.uber.org +cd $GOPATH/src/go.uber.org +git clone git@github.com:your_github_username/zap.git +cd zap +git remote add upstream https://github.com/uber-go/zap.git +git fetch upstream +``` + +Make sure that the tests and the linters pass: + +```bash +make test +make lint +``` + +## Making Changes + +Start by creating a new branch for your changes: + +```bash +cd $GOPATH/src/go.uber.org/zap +git checkout master +git fetch upstream +git rebase upstream/master +git checkout -b cool_new_feature +``` + +Make your changes, then ensure that `make lint` and `make test` still pass. If +you're satisfied with your changes, push them to your fork. + +```bash +git push origin cool_new_feature +``` + +Then use the GitHub UI to open a pull request. + +At this point, you're waiting on us to review your changes. We _try_ to respond +to issues and pull requests within a few business days, and we may suggest some +improvements or alternatives. Once your changes are approved, one of the +project maintainers will merge them. + +We're much more likely to approve your changes if you: + +- Add tests for new functionality. +- Write a [good commit message][commit-message]. +- Maintain backward compatibility. + +[fork]: https://github.com/uber-go/zap/fork +[open-issue]: https://github.com/uber-go/zap/issues/new +[cla]: https://cla-assistant.io/uber-go/zap +[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/FAQ.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/FAQ.md new file mode 100644 index 0000000..b183b20 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/FAQ.md @@ -0,0 +1,164 @@ +# Frequently Asked Questions + +## Design + +### Why spend so much effort on logger performance? + +Of course, most applications won't notice the impact of a slow logger: they +already take tens or hundreds of milliseconds for each operation, so an extra +millisecond doesn't matter. + +On the other hand, why *not* make structured logging fast? The `SugaredLogger` +isn't any harder to use than other logging packages, and the `Logger` makes +structured logging possible in performance-sensitive contexts. Across a fleet +of Go microservices, making each application even slightly more efficient adds +up quickly. + +### Why aren't `Logger` and `SugaredLogger` interfaces? + +Unlike the familiar `io.Writer` and `http.Handler`, `Logger` and +`SugaredLogger` interfaces would include *many* methods. As [Rob Pike points +out][go-proverbs], "The bigger the interface, the weaker the abstraction." +Interfaces are also rigid — *any* change requires releasing a new major +version, since it breaks all third-party implementations. + +Making the `Logger` and `SugaredLogger` concrete types doesn't sacrifice much +abstraction, and it lets us add methods without introducing breaking changes. +Your applications should define and depend upon an interface that includes +just the methods you use. + +### Why are some of my logs missing? + +Logs are dropped intentionally by zap when sampling is enabled. The production +configuration (as returned by `NewProductionConfig()` enables sampling which will +cause repeated logs within a second to be sampled. See more details on why sampling +is enabled in [Why sample application logs](https://github.com/uber-go/zap/blob/master/FAQ.md#why-sample-application-logs). + +### Why sample application logs? + +Applications often experience runs of errors, either because of a bug or +because of a misbehaving user. Logging errors is usually a good idea, but it +can easily make this bad situation worse: not only is your application coping +with a flood of errors, it's also spending extra CPU cycles and I/O logging +those errors. Since writes are typically serialized, logging limits throughput +when you need it most. + +Sampling fixes this problem by dropping repetitive log entries. Under normal +conditions, your application writes out every entry. When similar entries are +logged hundreds or thousands of times each second, though, zap begins dropping +duplicates to preserve throughput. + +### Why do the structured logging APIs take a message in addition to fields? + +Subjectively, we find it helpful to accompany structured context with a brief +description. This isn't critical during development, but it makes debugging +and operating unfamiliar systems much easier. + +More concretely, zap's sampling algorithm uses the message to identify +duplicate entries. In our experience, this is a practical middle ground +between random sampling (which often drops the exact entry that you need while +debugging) and hashing the complete entry (which is prohibitively expensive). + +### Why include package-global loggers? + +Since so many other logging packages include a global logger, many +applications aren't designed to accept loggers as explicit parameters. +Changing function signatures is often a breaking change, so zap includes +global loggers to simplify migration. + +Avoid them where possible. + +### Why include dedicated Panic and Fatal log levels? + +In general, application code should handle errors gracefully instead of using +`panic` or `os.Exit`. However, every rule has exceptions, and it's common to +crash when an error is truly unrecoverable. To avoid losing any information +— especially the reason for the crash — the logger must flush any +buffered entries before the process exits. + +Zap makes this easy by offering `Panic` and `Fatal` logging methods that +automatically flush before exiting. Of course, this doesn't guarantee that +logs will never be lost, but it eliminates a common error. + +See the discussion in uber-go/zap#207 for more details. + +### What's `DPanic`? + +`DPanic` stands for "panic in development." In development, it logs at +`PanicLevel`; otherwise, it logs at `ErrorLevel`. `DPanic` makes it easier to +catch errors that are theoretically possible, but shouldn't actually happen, +*without* crashing in production. + +If you've ever written code like this, you need `DPanic`: + +```go +if err != nil { + panic(fmt.Sprintf("shouldn't ever get here: %v", err)) +} +``` + +## Installation + +### What does the error `expects import "go.uber.org/zap"` mean? + +Either zap was installed incorrectly or you're referencing the wrong package +name in your code. + +Zap's source code happens to be hosted on GitHub, but the [import +path][import-path] is `go.uber.org/zap`. This gives us, the project +maintainers, the freedom to move the source code if necessary. However, it +means that you need to take a little care when installing and using the +package. + +If you follow two simple rules, everything should work: install zap with `go +get -u go.uber.org/zap`, and always import it in your code with `import +"go.uber.org/zap"`. Your code shouldn't contain *any* references to +`github.com/uber-go/zap`. + +## Usage + +### Does zap support log rotation? + +Zap doesn't natively support rotating log files, since we prefer to leave this +to an external program like `logrotate`. + +However, it's easy to integrate a log rotation package like +[`gopkg.in/natefinch/lumberjack.v2`][lumberjack] as a `zapcore.WriteSyncer`. + +```go +// lumberjack.Logger is already safe for concurrent use, so we don't need to +// lock it. +w := zapcore.AddSync(&lumberjack.Logger{ + Filename: "/var/log/myapp/foo.log", + MaxSize: 500, // megabytes + MaxBackups: 3, + MaxAge: 28, // days +}) +core := zapcore.NewCore( + zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), + w, + zap.InfoLevel, +) +logger := zap.New(core) +``` + +## Extensions + +We'd love to support every logging need within zap itself, but we're only +familiar with a handful of log ingestion systems, flag-parsing packages, and +the like. Rather than merging code that we can't effectively debug and +support, we'd rather grow an ecosystem of zap extensions. + +We're aware of the following extensions, but haven't used them ourselves: + +| Package | Integration | +| --- | --- | +| `github.com/tchap/zapext` | Sentry, syslog | +| `github.com/fgrosse/zaptest` | Ginkgo | +| `github.com/blendle/zapdriver` | Stackdriver | +| `github.com/moul/zapgorm` | Gorm | +| `github.com/moul/zapfilter` | Advanced filtering rules | + +[go-proverbs]: https://go-proverbs.github.io/ +[import-path]: https://golang.org/cmd/go/#hdr-Remote_import_paths +[lumberjack]: https://godoc.org/gopkg.in/natefinch/lumberjack.v2 diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/LICENSE b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/LICENSE new file mode 100644 index 0000000..6652bed --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2016-2017 Uber Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/Makefile b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/Makefile new file mode 100644 index 0000000..eb1cee5 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/Makefile @@ -0,0 +1,76 @@ +# Directory containing the Makefile. +PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +export GOBIN ?= $(PROJECT_ROOT)/bin +export PATH := $(GOBIN):$(PATH) + +GOVULNCHECK = $(GOBIN)/govulncheck +BENCH_FLAGS ?= -cpuprofile=cpu.pprof -memprofile=mem.pprof -benchmem + +# Directories containing independent Go modules. +MODULE_DIRS = . ./exp ./benchmarks ./zapgrpc/internal/test + +# Directories that we want to track coverage for. +COVER_DIRS = . ./exp + +.PHONY: all +all: lint test + +.PHONY: lint +lint: golangci-lint tidy-lint license-lint + +.PHONY: golangci-lint +golangci-lint: + @$(foreach mod,$(MODULE_DIRS), \ + (cd $(mod) && \ + echo "[lint] golangci-lint: $(mod)" && \ + golangci-lint run --path-prefix $(mod)) &&) true + +.PHONY: tidy +tidy: + @$(foreach dir,$(MODULE_DIRS), \ + (cd $(dir) && go mod tidy) &&) true + +.PHONY: tidy-lint +tidy-lint: + @$(foreach mod,$(MODULE_DIRS), \ + (cd $(mod) && \ + echo "[lint] tidy: $(mod)" && \ + go mod tidy && \ + git diff --exit-code -- go.mod go.sum) &&) true + + +.PHONY: license-lint +license-lint: + ./checklicense.sh + +$(GOVULNCHECK): + cd tools && go install golang.org/x/vuln/cmd/govulncheck + +.PHONY: test +test: + @$(foreach dir,$(MODULE_DIRS),(cd $(dir) && go test -race ./...) &&) true + +.PHONY: cover +cover: + @$(foreach dir,$(COVER_DIRS), ( \ + cd $(dir) && \ + go test -race -coverprofile=cover.out -coverpkg=./... ./... \ + && go tool cover -html=cover.out -o cover.html) &&) true + +.PHONY: bench +BENCH ?= . +bench: + @$(foreach dir,$(MODULE_DIRS), ( \ + cd $(dir) && \ + go list ./... | xargs -n1 go test -bench=$(BENCH) -run="^$$" $(BENCH_FLAGS) \ + ) &&) true + +.PHONY: updatereadme +updatereadme: + rm -f README.md + cat .readme.tmpl | go run internal/readme/readme.go > README.md + +.PHONY: vulncheck +vulncheck: $(GOVULNCHECK) + $(GOVULNCHECK) ./... diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/README.md b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/README.md new file mode 100644 index 0000000..a17035c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/README.md @@ -0,0 +1,149 @@ +# :zap: zap + + +<div align="center"> + +Blazing fast, structured, leveled logging in Go. + +![Zap logo](assets/logo.png) + +[![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] + +</div> + +## Installation + +`go get -u go.uber.org/zap` + +Note that zap only supports the two most recent minor versions of Go. + +## Quick Start + +In contexts where performance is nice, but not critical, use the +`SugaredLogger`. It's 4-10x faster than other structured logging +packages and includes both structured and `printf`-style APIs. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() // flushes buffer, if any +sugar := logger.Sugar() +sugar.Infow("failed to fetch URL", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, +) +sugar.Infof("Failed to fetch URL: %s", url) +``` + +When performance and type safety are critical, use the `Logger`. It's even +faster than the `SugaredLogger` and allocates far less, but it only supports +structured logging. + +```go +logger, _ := zap.NewProduction() +defer logger.Sync() +logger.Info("failed to fetch URL", + // Structured context as strongly typed Field values. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), +) +``` + +See the [documentation][doc] and [FAQ](FAQ.md) for more details. + +## Performance + +For applications that log in the hot path, reflection-based serialization and +string formatting are prohibitively expensive — they're CPU-intensive +and make many small allocations. Put differently, using `encoding/json` and +`fmt.Fprintf` to log tons of `interface{}`s makes your application slow. + +Zap takes a different approach. It includes a reflection-free, zero-allocation +JSON encoder, and the base `Logger` strives to avoid serialization overhead +and allocations wherever possible. By building the high-level `SugaredLogger` +on that foundation, zap lets users *choose* when they need to count every +allocation and when they'd prefer a more familiar, loosely typed API. + +As measured by its own [benchmarking suite][], not only is zap more performant +than comparable structured logging packages — it's also faster than the +standard library. Like all benchmarks, take these with a grain of salt.<sup +id="anchor-versions">[1](#footnote-versions)</sup> + +Log a message and 10 fields: + +| Package | Time | Time % to zap | Objects Allocated | +| :------ | :--: | :-----------: | :---------------: | +| :zap: zap | 656 ns/op | +0% | 5 allocs/op +| :zap: zap (sugared) | 935 ns/op | +43% | 10 allocs/op +| zerolog | 380 ns/op | -42% | 1 allocs/op +| go-kit | 2249 ns/op | +243% | 57 allocs/op +| slog (LogAttrs) | 2479 ns/op | +278% | 40 allocs/op +| slog | 2481 ns/op | +278% | 42 allocs/op +| apex/log | 9591 ns/op | +1362% | 63 allocs/op +| log15 | 11393 ns/op | +1637% | 75 allocs/op +| logrus | 11654 ns/op | +1677% | 79 allocs/op + +Log a message with a logger that already has 10 fields of context: + +| Package | Time | Time % to zap | Objects Allocated | +| :------ | :--: | :-----------: | :---------------: | +| :zap: zap | 67 ns/op | +0% | 0 allocs/op +| :zap: zap (sugared) | 84 ns/op | +25% | 1 allocs/op +| zerolog | 35 ns/op | -48% | 0 allocs/op +| slog | 193 ns/op | +188% | 0 allocs/op +| slog (LogAttrs) | 200 ns/op | +199% | 0 allocs/op +| go-kit | 2460 ns/op | +3572% | 56 allocs/op +| log15 | 9038 ns/op | +13390% | 70 allocs/op +| apex/log | 9068 ns/op | +13434% | 53 allocs/op +| logrus | 10521 ns/op | +15603% | 68 allocs/op + +Log a static string, without any context or `printf`-style templating: + +| Package | Time | Time % to zap | Objects Allocated | +| :------ | :--: | :-----------: | :---------------: | +| :zap: zap | 63 ns/op | +0% | 0 allocs/op +| :zap: zap (sugared) | 81 ns/op | +29% | 1 allocs/op +| zerolog | 32 ns/op | -49% | 0 allocs/op +| standard library | 124 ns/op | +97% | 1 allocs/op +| slog | 196 ns/op | +211% | 0 allocs/op +| slog (LogAttrs) | 200 ns/op | +217% | 0 allocs/op +| go-kit | 213 ns/op | +238% | 9 allocs/op +| apex/log | 771 ns/op | +1124% | 5 allocs/op +| logrus | 1439 ns/op | +2184% | 23 allocs/op +| log15 | 2069 ns/op | +3184% | 20 allocs/op + +## Development Status: Stable + +All APIs are finalized, and no breaking changes will be made in the 1.x series +of releases. Users of semver-aware dependency management systems should pin +zap to `^1`. + +## Contributing + +We encourage and support an active, healthy community of contributors — +including you! Details are in the [contribution guide](CONTRIBUTING.md) and +the [code of conduct](CODE_OF_CONDUCT.md). The zap maintainers keep an eye on +issues and pull requests, but you can also report any negative conduct to +oss-conduct@uber.com. That email list is a private, safe space; even the zap +maintainers don't have access, so don't hesitate to hold us to a high +standard. + +<hr> + +Released under the [MIT License](LICENSE). + +<sup id="footnote-versions">1</sup> In particular, keep in mind that we may be +benchmarking against slightly older versions of other packages. Versions are +pinned in the [benchmarks/go.mod][] file. [↩](#anchor-versions) + +[doc-img]: https://pkg.go.dev/badge/go.uber.org/zap +[doc]: https://pkg.go.dev/go.uber.org/zap +[ci-img]: https://github.com/uber-go/zap/actions/workflows/go.yml/badge.svg +[ci]: https://github.com/uber-go/zap/actions/workflows/go.yml +[cov-img]: https://codecov.io/gh/uber-go/zap/branch/master/graph/badge.svg +[cov]: https://codecov.io/gh/uber-go/zap +[benchmarking suite]: https://github.com/uber-go/zap/tree/master/benchmarks +[benchmarks/go.mod]: https://github.com/uber-go/zap/blob/master/benchmarks/go.mod + diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array.go new file mode 100644 index 0000000..abfccb5 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array.go @@ -0,0 +1,447 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "time" + + "go.uber.org/zap/zapcore" +) + +// Array constructs a field with the given key and ArrayMarshaler. It provides +// a flexible, but still type-safe and efficient, way to add array-like types +// to the logging context. The struct's MarshalLogArray method is called lazily. +func Array(key string, val zapcore.ArrayMarshaler) Field { + return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val} +} + +// Bools constructs a field that carries a slice of bools. +func Bools(key string, bs []bool) Field { + return Array(key, bools(bs)) +} + +// ByteStrings constructs a field that carries a slice of []byte, each of which +// must be UTF-8 encoded text. +func ByteStrings(key string, bss [][]byte) Field { + return Array(key, byteStringsArray(bss)) +} + +// Complex128s constructs a field that carries a slice of complex numbers. +func Complex128s(key string, nums []complex128) Field { + return Array(key, complex128s(nums)) +} + +// Complex64s constructs a field that carries a slice of complex numbers. +func Complex64s(key string, nums []complex64) Field { + return Array(key, complex64s(nums)) +} + +// Durations constructs a field that carries a slice of time.Durations. +func Durations(key string, ds []time.Duration) Field { + return Array(key, durations(ds)) +} + +// Float64s constructs a field that carries a slice of floats. +func Float64s(key string, nums []float64) Field { + return Array(key, float64s(nums)) +} + +// Float32s constructs a field that carries a slice of floats. +func Float32s(key string, nums []float32) Field { + return Array(key, float32s(nums)) +} + +// Ints constructs a field that carries a slice of integers. +func Ints(key string, nums []int) Field { + return Array(key, ints(nums)) +} + +// Int64s constructs a field that carries a slice of integers. +func Int64s(key string, nums []int64) Field { + return Array(key, int64s(nums)) +} + +// Int32s constructs a field that carries a slice of integers. +func Int32s(key string, nums []int32) Field { + return Array(key, int32s(nums)) +} + +// Int16s constructs a field that carries a slice of integers. +func Int16s(key string, nums []int16) Field { + return Array(key, int16s(nums)) +} + +// Int8s constructs a field that carries a slice of integers. +func Int8s(key string, nums []int8) Field { + return Array(key, int8s(nums)) +} + +// Objects constructs a field with the given key, holding a list of the +// provided objects that can be marshaled by Zap. +// +// Note that these objects must implement zapcore.ObjectMarshaler directly. +// That is, if you're trying to marshal a []Request, the MarshalLogObject +// method must be declared on the Request type, not its pointer (*Request). +// If it's on the pointer, use ObjectValues. +// +// Given an object that implements MarshalLogObject on the value receiver, you +// can log a slice of those objects with Objects like so: +// +// type Author struct{ ... } +// func (a Author) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var authors []Author = ... +// logger.Info("loading article", zap.Objects("authors", authors)) +// +// Similarly, given a type that implements MarshalLogObject on its pointer +// receiver, you can log a slice of pointers to that object with Objects like +// so: +// +// type Request struct{ ... } +// func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var requests []*Request = ... +// logger.Info("sending requests", zap.Objects("requests", requests)) +// +// If instead, you have a slice of values of such an object, use the +// ObjectValues constructor. +// +// var requests []Request = ... +// logger.Info("sending requests", zap.ObjectValues("requests", requests)) +func Objects[T zapcore.ObjectMarshaler](key string, values []T) Field { + return Array(key, objects[T](values)) +} + +type objects[T zapcore.ObjectMarshaler] []T + +func (os objects[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for _, o := range os { + if err := arr.AppendObject(o); err != nil { + return err + } + } + return nil +} + +// ObjectMarshalerPtr is a constraint that specifies that the given type +// implements zapcore.ObjectMarshaler on a pointer receiver. +type ObjectMarshalerPtr[T any] interface { + *T + zapcore.ObjectMarshaler +} + +// ObjectValues constructs a field with the given key, holding a list of the +// provided objects, where pointers to these objects can be marshaled by Zap. +// +// Note that pointers to these objects must implement zapcore.ObjectMarshaler. +// That is, if you're trying to marshal a []Request, the MarshalLogObject +// method must be declared on the *Request type, not the value (Request). +// If it's on the value, use Objects. +// +// Given an object that implements MarshalLogObject on the pointer receiver, +// you can log a slice of those objects with ObjectValues like so: +// +// type Request struct{ ... } +// func (r *Request) MarshalLogObject(enc zapcore.ObjectEncoder) error +// +// var requests []Request = ... +// logger.Info("sending requests", zap.ObjectValues("requests", requests)) +// +// If instead, you have a slice of pointers of such an object, use the Objects +// field constructor. +// +// var requests []*Request = ... +// logger.Info("sending requests", zap.Objects("requests", requests)) +func ObjectValues[T any, P ObjectMarshalerPtr[T]](key string, values []T) Field { + return Array(key, objectValues[T, P](values)) +} + +type objectValues[T any, P ObjectMarshalerPtr[T]] []T + +func (os objectValues[T, P]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range os { + // It is necessary for us to explicitly reference the "P" type. + // We cannot simply pass "&os[i]" to AppendObject because its type + // is "*T", which the type system does not consider as + // implementing ObjectMarshaler. + // Only the type "P" satisfies ObjectMarshaler, which we have + // to convert "*T" to explicitly. + var p P = &os[i] + if err := arr.AppendObject(p); err != nil { + return err + } + } + return nil +} + +// Strings constructs a field that carries a slice of strings. +func Strings(key string, ss []string) Field { + return Array(key, stringArray(ss)) +} + +// Stringers constructs a field with the given key, holding a list of the +// output provided by the value's String method +// +// Given an object that implements String on the value receiver, you +// can log a slice of those objects with Objects like so: +// +// type Request struct{ ... } +// func (a Request) String() string +// +// var requests []Request = ... +// logger.Info("sending requests", zap.Stringers("requests", requests)) +// +// Note that these objects must implement fmt.Stringer directly. +// That is, if you're trying to marshal a []Request, the String method +// must be declared on the Request type, not its pointer (*Request). +func Stringers[T fmt.Stringer](key string, values []T) Field { + return Array(key, stringers[T](values)) +} + +type stringers[T fmt.Stringer] []T + +func (os stringers[T]) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for _, o := range os { + arr.AppendString(o.String()) + } + return nil +} + +// Times constructs a field that carries a slice of time.Times. +func Times(key string, ts []time.Time) Field { + return Array(key, times(ts)) +} + +// Uints constructs a field that carries a slice of unsigned integers. +func Uints(key string, nums []uint) Field { + return Array(key, uints(nums)) +} + +// Uint64s constructs a field that carries a slice of unsigned integers. +func Uint64s(key string, nums []uint64) Field { + return Array(key, uint64s(nums)) +} + +// Uint32s constructs a field that carries a slice of unsigned integers. +func Uint32s(key string, nums []uint32) Field { + return Array(key, uint32s(nums)) +} + +// Uint16s constructs a field that carries a slice of unsigned integers. +func Uint16s(key string, nums []uint16) Field { + return Array(key, uint16s(nums)) +} + +// Uint8s constructs a field that carries a slice of unsigned integers. +func Uint8s(key string, nums []uint8) Field { + return Array(key, uint8s(nums)) +} + +// Uintptrs constructs a field that carries a slice of pointer addresses. +func Uintptrs(key string, us []uintptr) Field { + return Array(key, uintptrs(us)) +} + +// Errors constructs a field that carries a slice of errors. +func Errors(key string, errs []error) Field { + return Array(key, errArray(errs)) +} + +type bools []bool + +func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bs { + arr.AppendBool(bs[i]) + } + return nil +} + +type byteStringsArray [][]byte + +func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range bss { + arr.AppendByteString(bss[i]) + } + return nil +} + +type complex128s []complex128 + +func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex128(nums[i]) + } + return nil +} + +type complex64s []complex64 + +func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendComplex64(nums[i]) + } + return nil +} + +type durations []time.Duration + +func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ds { + arr.AppendDuration(ds[i]) + } + return nil +} + +type float64s []float64 + +func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat64(nums[i]) + } + return nil +} + +type float32s []float32 + +func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendFloat32(nums[i]) + } + return nil +} + +type ints []int + +func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt(nums[i]) + } + return nil +} + +type int64s []int64 + +func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt64(nums[i]) + } + return nil +} + +type int32s []int32 + +func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt32(nums[i]) + } + return nil +} + +type int16s []int16 + +func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt16(nums[i]) + } + return nil +} + +type int8s []int8 + +func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendInt8(nums[i]) + } + return nil +} + +type stringArray []string + +func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ss { + arr.AppendString(ss[i]) + } + return nil +} + +type times []time.Time + +func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range ts { + arr.AppendTime(ts[i]) + } + return nil +} + +type uints []uint + +func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint(nums[i]) + } + return nil +} + +type uint64s []uint64 + +func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint64(nums[i]) + } + return nil +} + +type uint32s []uint32 + +func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint32(nums[i]) + } + return nil +} + +type uint16s []uint16 + +func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint16(nums[i]) + } + return nil +} + +type uint8s []uint8 + +func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUint8(nums[i]) + } + return nil +} + +type uintptrs []uintptr + +func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range nums { + arr.AppendUintptr(nums[i]) + } + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array_test.go new file mode 100644 index 0000000..97738c9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/array_test.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "testing" + "time" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkBoolsArrayMarshaler(b *testing.B) { + // Keep this benchmark here to capture the overhead of the ArrayMarshaler + // wrapper. + bs := make([]bool, 50) + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Bools("array", bs).AddTo(enc.Clone()) + } +} + +func BenchmarkBoolsReflect(b *testing.B) { + bs := make([]bool, 50) + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Reflect("array", bs).AddTo(enc.Clone()) + } +} + +func TestArrayWrappers(t *testing.T) { + tests := []struct { + desc string + field Field + expected []interface{} + }{ + {"empty bools", Bools("", []bool{}), []interface{}{}}, + {"empty byte strings", ByteStrings("", [][]byte{}), []interface{}{}}, + {"empty complex128s", Complex128s("", []complex128{}), []interface{}{}}, + {"empty complex64s", Complex64s("", []complex64{}), []interface{}{}}, + {"empty durations", Durations("", []time.Duration{}), []interface{}{}}, + {"empty float64s", Float64s("", []float64{}), []interface{}{}}, + {"empty float32s", Float32s("", []float32{}), []interface{}{}}, + {"empty ints", Ints("", []int{}), []interface{}{}}, + {"empty int64s", Int64s("", []int64{}), []interface{}{}}, + {"empty int32s", Int32s("", []int32{}), []interface{}{}}, + {"empty int16s", Int16s("", []int16{}), []interface{}{}}, + {"empty int8s", Int8s("", []int8{}), []interface{}{}}, + {"empty strings", Strings("", []string{}), []interface{}{}}, + {"empty times", Times("", []time.Time{}), []interface{}{}}, + {"empty uints", Uints("", []uint{}), []interface{}{}}, + {"empty uint64s", Uint64s("", []uint64{}), []interface{}{}}, + {"empty uint32s", Uint32s("", []uint32{}), []interface{}{}}, + {"empty uint16s", Uint16s("", []uint16{}), []interface{}{}}, + {"empty uint8s", Uint8s("", []uint8{}), []interface{}{}}, + {"empty uintptrs", Uintptrs("", []uintptr{}), []interface{}{}}, + {"bools", Bools("", []bool{true, false}), []interface{}{true, false}}, + {"byte strings", ByteStrings("", [][]byte{{1, 2}, {3, 4}}), []interface{}{"\x01\x02", "\x03\x04"}}, + {"complex128s", Complex128s("", []complex128{1 + 2i, 3 + 4i}), []interface{}{1 + 2i, 3 + 4i}}, + {"complex64s", Complex64s("", []complex64{1 + 2i, 3 + 4i}), []interface{}{complex64(1 + 2i), complex64(3 + 4i)}}, + {"durations", Durations("", []time.Duration{1, 2}), []interface{}{time.Nanosecond, 2 * time.Nanosecond}}, + {"float64s", Float64s("", []float64{1.2, 3.4}), []interface{}{1.2, 3.4}}, + {"float32s", Float32s("", []float32{1.2, 3.4}), []interface{}{float32(1.2), float32(3.4)}}, + {"ints", Ints("", []int{1, 2}), []interface{}{1, 2}}, + {"int64s", Int64s("", []int64{1, 2}), []interface{}{int64(1), int64(2)}}, + {"int32s", Int32s("", []int32{1, 2}), []interface{}{int32(1), int32(2)}}, + {"int16s", Int16s("", []int16{1, 2}), []interface{}{int16(1), int16(2)}}, + {"int8s", Int8s("", []int8{1, 2}), []interface{}{int8(1), int8(2)}}, + {"strings", Strings("", []string{"foo", "bar"}), []interface{}{"foo", "bar"}}, + {"times", Times("", []time.Time{time.Unix(0, 0), time.Unix(0, 0)}), []interface{}{time.Unix(0, 0), time.Unix(0, 0)}}, + {"uints", Uints("", []uint{1, 2}), []interface{}{uint(1), uint(2)}}, + {"uint64s", Uint64s("", []uint64{1, 2}), []interface{}{uint64(1), uint64(2)}}, + {"uint32s", Uint32s("", []uint32{1, 2}), []interface{}{uint32(1), uint32(2)}}, + {"uint16s", Uint16s("", []uint16{1, 2}), []interface{}{uint16(1), uint16(2)}}, + {"uint8s", Uint8s("", []uint8{1, 2}), []interface{}{uint8(1), uint8(2)}}, + {"uintptrs", Uintptrs("", []uintptr{1, 2}), []interface{}{uintptr(1), uintptr(2)}}, + } + + for _, tt := range tests { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc) + assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields) + } +} + +func TestObjectsAndObjectValues(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + }{ + { + desc: "Objects/nil slice", + give: Objects[*emptyObject]("", nil), + want: []any{}, + }, + { + desc: "ObjectValues/nil slice", + give: ObjectValues[emptyObject]("", nil), + want: []any{}, + }, + { + desc: "ObjectValues/empty slice", + give: ObjectValues("", []emptyObject{}), + want: []any{}, + }, + { + desc: "ObjectValues/single item", + give: ObjectValues("", []emptyObject{ + {}, + }), + want: []any{ + map[string]any{}, + }, + }, + { + desc: "Objects/multiple different objects", + give: Objects("", []*fakeObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + map[string]any{"value": "baz"}, + }, + }, + { + desc: "ObjectValues/multiple different objects", + give: ObjectValues("", []fakeObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + map[string]any{"value": "baz"}, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"]) + }) + } +} + +type emptyObject struct{} + +func (*emptyObject) MarshalLogObject(zapcore.ObjectEncoder) error { + return nil +} + +type fakeObject struct { + value string + err error // marshaling error, if any +} + +func (o *fakeObject) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("value", o.value) + return o.err +} + +func TestObjectsAndObjectValues_marshalError(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + wantErr string + }{ + { + desc: "Objects", + give: Objects("", []*fakeObject{ + {value: "foo"}, + {value: "bar", err: errors.New("great sadness")}, + {value: "baz"}, // does not get marshaled + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + }, + wantErr: "great sadness", + }, + { + desc: "ObjectValues", + give: ObjectValues("", []fakeObject{ + {value: "foo"}, + {value: "bar", err: errors.New("stuff failed")}, + {value: "baz"}, // does not get marshaled + }), + want: []any{ + map[string]any{"value": "foo"}, + map[string]any{"value": "bar"}, + }, + wantErr: "stuff failed", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + + require.Contains(t, enc.Fields, "k") + assert.Equal(t, tt.want, enc.Fields["k"]) + + // AddTo puts the error in a "%vError" field based on the name of the + // original field. + require.Contains(t, enc.Fields, "kError") + assert.Equal(t, tt.wantErr, enc.Fields["kError"]) + }) + } +} + +type stringerObject struct { + value string +} + +func (s stringerObject) String() string { + return s.value +} + +func TestStringers(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + give Field + want []any + }{ + { + desc: "Stringers", + give: Stringers("", []stringerObject{ + {value: "foo"}, + {value: "bar"}, + {value: "baz"}, + }), + want: []any{ + "foo", + "bar", + "baz", + }, + }, + { + desc: "Stringers with []fmt.Stringer", + give: Stringers("", []fmt.Stringer{ + stringerObject{value: "foo"}, + stringerObject{value: "bar"}, + stringerObject{value: "baz"}, + }), + want: []any{ + "foo", + "bar", + "baz", + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + tt.give.Key = "k" + + enc := zapcore.NewMapObjectEncoder() + tt.give.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"]) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer.go new file mode 100644 index 0000000..0b8540c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer.go @@ -0,0 +1,146 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package buffer provides a thin wrapper around a byte slice. Unlike the +// standard library's bytes.Buffer, it supports a portion of the strconv +// package's zero-allocation formatters. +package buffer // import "go.uber.org/zap/buffer" + +import ( + "strconv" + "time" +) + +const _size = 1024 // by default, create 1 KiB buffers + +// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so +// the only way to construct one is via a Pool. +type Buffer struct { + bs []byte + pool Pool +} + +// AppendByte writes a single byte to the Buffer. +func (b *Buffer) AppendByte(v byte) { + b.bs = append(b.bs, v) +} + +// AppendBytes writes the given slice of bytes to the Buffer. +func (b *Buffer) AppendBytes(v []byte) { + b.bs = append(b.bs, v...) +} + +// AppendString writes a string to the Buffer. +func (b *Buffer) AppendString(s string) { + b.bs = append(b.bs, s...) +} + +// AppendInt appends an integer to the underlying buffer (assuming base 10). +func (b *Buffer) AppendInt(i int64) { + b.bs = strconv.AppendInt(b.bs, i, 10) +} + +// AppendTime appends the time formatted using the specified layout. +func (b *Buffer) AppendTime(t time.Time, layout string) { + b.bs = t.AppendFormat(b.bs, layout) +} + +// AppendUint appends an unsigned integer to the underlying buffer (assuming +// base 10). +func (b *Buffer) AppendUint(i uint64) { + b.bs = strconv.AppendUint(b.bs, i, 10) +} + +// AppendBool appends a bool to the underlying buffer. +func (b *Buffer) AppendBool(v bool) { + b.bs = strconv.AppendBool(b.bs, v) +} + +// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN +// or +/- Inf. +func (b *Buffer) AppendFloat(f float64, bitSize int) { + b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize) +} + +// Len returns the length of the underlying byte slice. +func (b *Buffer) Len() int { + return len(b.bs) +} + +// Cap returns the capacity of the underlying byte slice. +func (b *Buffer) Cap() int { + return cap(b.bs) +} + +// Bytes returns a mutable reference to the underlying byte slice. +func (b *Buffer) Bytes() []byte { + return b.bs +} + +// String returns a string copy of the underlying byte slice. +func (b *Buffer) String() string { + return string(b.bs) +} + +// Reset resets the underlying byte slice. Subsequent writes re-use the slice's +// backing array. +func (b *Buffer) Reset() { + b.bs = b.bs[:0] +} + +// Write implements io.Writer. +func (b *Buffer) Write(bs []byte) (int, error) { + b.bs = append(b.bs, bs...) + return len(bs), nil +} + +// WriteByte writes a single byte to the Buffer. +// +// Error returned is always nil, function signature is compatible +// with bytes.Buffer and bufio.Writer +func (b *Buffer) WriteByte(v byte) error { + b.AppendByte(v) + return nil +} + +// WriteString writes a string to the Buffer. +// +// Error returned is always nil, function signature is compatible +// with bytes.Buffer and bufio.Writer +func (b *Buffer) WriteString(s string) (int, error) { + b.AppendString(s) + return len(s), nil +} + +// TrimNewline trims any final "\n" byte from the end of the buffer. +func (b *Buffer) TrimNewline() { + if i := len(b.bs) - 1; i >= 0 { + if b.bs[i] == '\n' { + b.bs = b.bs[:i] + } + } +} + +// Free returns the Buffer to its Pool. +// +// Callers must not retain references to the Buffer after calling Free. +func (b *Buffer) Free() { + b.pool.put(b) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer_test.go new file mode 100644 index 0000000..71ffac1 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/buffer_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "bytes" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestBufferWrites(t *testing.T) { + buf := NewPool().Get() + + tests := []struct { + desc string + f func() + want string + }{ + {"AppendByte", func() { buf.AppendByte('v') }, "v"}, + {"AppendString", func() { buf.AppendString("foo") }, "foo"}, + {"AppendIntPositive", func() { buf.AppendInt(42) }, "42"}, + {"AppendIntNegative", func() { buf.AppendInt(-42) }, "-42"}, + {"AppendUint", func() { buf.AppendUint(42) }, "42"}, + {"AppendBool", func() { buf.AppendBool(true) }, "true"}, + {"AppendFloat64", func() { buf.AppendFloat(3.14, 64) }, "3.14"}, + // Intentionally introduce some floating-point error. + {"AppendFloat32", func() { buf.AppendFloat(float64(float32(3.14)), 32) }, "3.14"}, + {"AppendWrite", func() { buf.Write([]byte("foo")) }, "foo"}, + {"AppendTime", func() { buf.AppendTime(time.Date(2000, 1, 2, 3, 4, 5, 6, time.UTC), time.RFC3339) }, "2000-01-02T03:04:05Z"}, + {"WriteByte", func() { buf.WriteByte('v') }, "v"}, + {"WriteString", func() { buf.WriteString("foo") }, "foo"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + buf.Reset() + tt.f() + assert.Equal(t, tt.want, buf.String(), "Unexpected buffer.String().") + assert.Equal(t, tt.want, string(buf.Bytes()), "Unexpected string(buffer.Bytes()).") + assert.Equal(t, len(tt.want), buf.Len(), "Unexpected buffer length.") + // We're not writing more than a kibibyte in tests. + assert.Equal(t, _size, buf.Cap(), "Expected buffer capacity to remain constant.") + }) + } +} + +func BenchmarkBuffers(b *testing.B) { + // Because we use the strconv.AppendFoo functions so liberally, we can't + // use the standard library's bytes.Buffer anyways (without incurring a + // bunch of extra allocations). Nevertheless, let's make sure that we're + // not losing any precious nanoseconds. + str := strings.Repeat("a", 1024) + slice := make([]byte, 1024) + buf := bytes.NewBuffer(slice) + custom := NewPool().Get() + b.Run("ByteSlice", func(b *testing.B) { + for i := 0; i < b.N; i++ { + slice = append(slice, str...) + slice = slice[:0] + } + }) + b.Run("BytesBuffer", func(b *testing.B) { + for i := 0; i < b.N; i++ { + buf.WriteString(str) + buf.Reset() + } + }) + b.Run("CustomBuffer", func(b *testing.B) { + for i := 0; i < b.N; i++ { + custom.AppendString(str) + custom.Reset() + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool.go new file mode 100644 index 0000000..8463233 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool.go @@ -0,0 +1,53 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "go.uber.org/zap/internal/pool" +) + +// A Pool is a type-safe wrapper around a sync.Pool. +type Pool struct { + p *pool.Pool[*Buffer] +} + +// NewPool constructs a new Pool. +func NewPool() Pool { + return Pool{ + p: pool.New(func() *Buffer { + return &Buffer{ + bs: make([]byte, 0, _size), + } + }), + } +} + +// Get retrieves a Buffer from the pool, creating one if necessary. +func (p Pool) Get() *Buffer { + buf := p.p.Get() + buf.Reset() + buf.pool = p + return buf +} + +func (p Pool) put(buf *Buffer) { + p.p.Put(buf) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool_test.go new file mode 100644 index 0000000..a219815 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/buffer/pool_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package buffer + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuffers(t *testing.T) { + const dummyData = "dummy data" + p := NewPool() + + var wg sync.WaitGroup + for g := 0; g < 10; g++ { + wg.Add(1) + go func() { + for i := 0; i < 100; i++ { + buf := p.Get() + assert.Zero(t, buf.Len(), "Expected truncated buffer") + assert.NotZero(t, buf.Cap(), "Expected non-zero capacity") + + buf.AppendString(dummyData) + assert.Equal(t, buf.Len(), len(dummyData), "Expected buffer to contain dummy data") + + buf.Free() + } + wg.Done() + }() + } + wg.Wait() +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/checklicense.sh b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/checklicense.sh new file mode 100644 index 0000000..345ac8b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/checklicense.sh @@ -0,0 +1,17 @@ +#!/bin/bash -e + +ERROR_COUNT=0 +while read -r file +do + case "$(head -1 "${file}")" in + *"Copyright (c) "*" Uber Technologies, Inc.") + # everything's cool + ;; + *) + echo "$file is missing license header." + (( ERROR_COUNT++ )) + ;; + esac +done < <(git ls-files "*\.go") + +exit $ERROR_COUNT diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/clock_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/clock_test.go new file mode 100644 index 0000000..29825fc --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/clock_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zaptest/observer" +) + +type constantClock time.Time + +func (c constantClock) Now() time.Time { return time.Time(c) } +func (c constantClock) NewTicker(d time.Duration) *time.Ticker { + return &time.Ticker{} +} + +func TestWithClock(t *testing.T) { + date := time.Date(2077, 1, 23, 10, 15, 13, 441, time.UTC) + clock := constantClock(date) + withLogger(t, DebugLevel, []Option{WithClock(clock)}, func(log *Logger, logs *observer.ObservedLogs) { + log.Info("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, date, logs.All()[0].Time, "Unexpected entry time.") + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/common_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/common_test.go new file mode 100644 index 0000000..b0a4a2e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/common_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync" + "testing" + + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func opts(opts ...Option) []Option { + return opts +} + +// Here specifically to introduce an easily-identifiable filename for testing +// stacktraces and caller skips. +func withLogger(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*Logger, *observer.ObservedLogs)) { + fac, logs := observer.New(e) + log := New(fac, opts...) + f(log, logs) +} + +func withSugar(t testing.TB, e zapcore.LevelEnabler, opts []Option, f func(*SugaredLogger, *observer.ObservedLogs)) { + withLogger(t, e, opts, func(logger *Logger, logs *observer.ObservedLogs) { f(logger.Sugar(), logs) }) +} + +func runConcurrently(goroutines, iterations int, wg *sync.WaitGroup, f func()) { + wg.Add(goroutines) + for g := 0; g < goroutines; g++ { + go func() { + defer wg.Done() + for i := 0; i < iterations; i++ { + f() + } + }() + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config.go new file mode 100644 index 0000000..e76e4e6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config.go @@ -0,0 +1,330 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "sort" + "time" + + "go.uber.org/zap/zapcore" +) + +// SamplingConfig sets a sampling strategy for the logger. Sampling caps the +// global CPU and I/O load that logging puts on your process while attempting +// to preserve a representative subset of your logs. +// +// If specified, the Sampler will invoke the Hook after each decision. +// +// Values configured here are per-second. See zapcore.NewSamplerWithOptions for +// details. +type SamplingConfig struct { + Initial int `json:"initial" yaml:"initial"` + Thereafter int `json:"thereafter" yaml:"thereafter"` + Hook func(zapcore.Entry, zapcore.SamplingDecision) `json:"-" yaml:"-"` +} + +// Config offers a declarative way to construct a logger. It doesn't do +// anything that can't be done with New, Options, and the various +// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to +// toggle common options. +// +// Note that Config intentionally supports only the most common options. More +// unusual logging setups (logging to network connections or message queues, +// splitting output between multiple files, etc.) are possible, but require +// direct use of the zapcore package. For sample code, see the package-level +// BasicConfiguration and AdvancedConfiguration examples. +// +// For an example showing runtime log level changes, see the documentation for +// AtomicLevel. +type Config struct { + // Level is the minimum enabled logging level. Note that this is a dynamic + // level, so calling Config.Level.SetLevel will atomically change the log + // level of all loggers descended from this config. + Level AtomicLevel `json:"level" yaml:"level"` + // Development puts the logger in development mode, which changes the + // behavior of DPanicLevel and takes stacktraces more liberally. + Development bool `json:"development" yaml:"development"` + // DisableCaller stops annotating logs with the calling function's file + // name and line number. By default, all logs are annotated. + DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` + // DisableStacktrace completely disables automatic stacktrace capturing. By + // default, stacktraces are captured for WarnLevel and above logs in + // development and ErrorLevel and above in production. + DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` + // Sampling sets a sampling policy. A nil SamplingConfig disables sampling. + Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` + // Encoding sets the logger's encoding. Valid values are "json" and + // "console", as well as any third-party encodings registered via + // RegisterEncoder. + Encoding string `json:"encoding" yaml:"encoding"` + // EncoderConfig sets options for the chosen encoder. See + // zapcore.EncoderConfig for details. + EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` + // OutputPaths is a list of URLs or file paths to write logging output to. + // See Open for details. + OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` + // ErrorOutputPaths is a list of URLs to write internal logger errors to. + // The default is standard error. + // + // Note that this setting only affects internal errors; for sample code that + // sends error-level logs to a different location from info- and debug-level + // logs, see the package-level AdvancedConfiguration example. + ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` + // InitialFields is a collection of fields to add to the root logger. + InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` +} + +// NewProductionEncoderConfig returns an opinionated EncoderConfig for +// production environments. +// +// Messages encoded with this configuration will be JSON-formatted +// and will have the following keys by default: +// +// - "level": The logging level (e.g. "info", "error"). +// - "ts": The current time in number of seconds since the Unix epoch. +// - "msg": The message passed to the log statement. +// - "caller": If available, a short path to the file and line number +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// - "stacktrace": If available, a stack trace from the line +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// +// By default, the following formats are used for different types: +// +// - Time is formatted as floating-point number of seconds since the Unix +// epoch. +// - Duration is formatted as floating-point number of seconds. +// +// You may change these by setting the appropriate fields in the returned +// object. +// For example, use the following to change the time encoding format: +// +// cfg := zap.NewProductionEncoderConfig() +// cfg.EncodeTime = zapcore.ISO8601TimeEncoder +func NewProductionEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + TimeKey: "ts", + LevelKey: "level", + NameKey: "logger", + CallerKey: "caller", + FunctionKey: zapcore.OmitKey, + MessageKey: "msg", + StacktraceKey: "stacktrace", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.EpochTimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewProductionConfig builds a reasonable default production logging +// configuration. +// Logging is enabled at InfoLevel and above, and uses a JSON encoder. +// Logs are written to standard error. +// Stacktraces are included on logs of ErrorLevel and above. +// DPanicLevel logs will not panic, but will write a stacktrace. +// +// Sampling is enabled at 100:100 by default, +// meaning that after the first 100 log entries +// with the same level and message in the same second, +// it will log every 100th entry +// with the same level and message in the same second. +// You may disable this behavior by setting Sampling to nil. +// +// See [NewProductionEncoderConfig] for information +// on the default encoder configuration. +func NewProductionConfig() Config { + return Config{ + Level: NewAtomicLevelAt(InfoLevel), + Development: false, + Sampling: &SamplingConfig{ + Initial: 100, + Thereafter: 100, + }, + Encoding: "json", + EncoderConfig: NewProductionEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for +// development environments. +// +// Messages encoded with this configuration will use Zap's console encoder +// intended to print human-readable output. +// It will print log messages with the following information: +// +// - The log level (e.g. "INFO", "ERROR"). +// - The time in ISO8601 format (e.g. "2017-01-01T12:00:00Z"). +// - The message passed to the log statement. +// - If available, a short path to the file and line number +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// - If available, a stacktrace from the line +// where the log statement was issued. +// The logger configuration determines whether this field is captured. +// +// By default, the following formats are used for different types: +// +// - Time is formatted in ISO8601 format (e.g. "2017-01-01T12:00:00Z"). +// - Duration is formatted as a string (e.g. "1.234s"). +// +// You may change these by setting the appropriate fields in the returned +// object. +// For example, use the following to change the time encoding format: +// +// cfg := zap.NewDevelopmentEncoderConfig() +// cfg.EncodeTime = zapcore.ISO8601TimeEncoder +func NewDevelopmentEncoderConfig() zapcore.EncoderConfig { + return zapcore.EncoderConfig{ + // Keys can be anything except the empty string. + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + FunctionKey: zapcore.OmitKey, + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + } +} + +// NewDevelopmentConfig builds a reasonable default development logging +// configuration. +// Logging is enabled at DebugLevel and above, and uses a console encoder. +// Logs are written to standard error. +// Stacktraces are included on logs of WarnLevel and above. +// DPanicLevel logs will panic. +// +// See [NewDevelopmentEncoderConfig] for information +// on the default encoder configuration. +func NewDevelopmentConfig() Config { + return Config{ + Level: NewAtomicLevelAt(DebugLevel), + Development: true, + Encoding: "console", + EncoderConfig: NewDevelopmentEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } +} + +// Build constructs a logger from the Config and Options. +func (cfg Config) Build(opts ...Option) (*Logger, error) { + enc, err := cfg.buildEncoder() + if err != nil { + return nil, err + } + + sink, errSink, err := cfg.openSinks() + if err != nil { + return nil, err + } + + if cfg.Level == (AtomicLevel{}) { + return nil, errors.New("missing Level") + } + + log := New( + zapcore.NewCore(enc, sink, cfg.Level), + cfg.buildOptions(errSink)..., + ) + if len(opts) > 0 { + log = log.WithOptions(opts...) + } + return log, nil +} + +func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option { + opts := []Option{ErrorOutput(errSink)} + + if cfg.Development { + opts = append(opts, Development()) + } + + if !cfg.DisableCaller { + opts = append(opts, AddCaller()) + } + + stackLevel := ErrorLevel + if cfg.Development { + stackLevel = WarnLevel + } + if !cfg.DisableStacktrace { + opts = append(opts, AddStacktrace(stackLevel)) + } + + if scfg := cfg.Sampling; scfg != nil { + opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core { + var samplerOpts []zapcore.SamplerOption + if scfg.Hook != nil { + samplerOpts = append(samplerOpts, zapcore.SamplerHook(scfg.Hook)) + } + return zapcore.NewSamplerWithOptions( + core, + time.Second, + cfg.Sampling.Initial, + cfg.Sampling.Thereafter, + samplerOpts..., + ) + })) + } + + if len(cfg.InitialFields) > 0 { + fs := make([]Field, 0, len(cfg.InitialFields)) + keys := make([]string, 0, len(cfg.InitialFields)) + for k := range cfg.InitialFields { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fs = append(fs, Any(k, cfg.InitialFields[k])) + } + opts = append(opts, Fields(fs...)) + } + + return opts +} + +func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) { + sink, closeOut, err := Open(cfg.OutputPaths...) + if err != nil { + return nil, nil, err + } + errSink, _, err := Open(cfg.ErrorOutputPaths...) + if err != nil { + closeOut() + return nil, nil, err + } + return sink, errSink, nil +} + +func (cfg Config) buildEncoder() (zapcore.Encoder, error) { + return newEncoder(cfg.Encoding, cfg.EncoderConfig) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config_test.go new file mode 100644 index 0000000..4badd1b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/config_test.go @@ -0,0 +1,203 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "os" + "path/filepath" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" +) + +func TestConfig(t *testing.T) { + tests := []struct { + desc string + cfg Config + expectN int64 + expectRe string + }{ + { + desc: "production", + cfg: NewProductionConfig(), + expectN: 2 + 100 + 1, // 2 from initial logs, 100 initial sampled logs, 1 from off-by-one in sampler + expectRe: `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" + + `{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n", + }, + { + desc: "development", + cfg: NewDevelopmentConfig(), + expectN: 3 + 200, // 3 initial logs, all 200 subsequent logs + expectRe: "DEBUG\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tdebug\t" + `{"k": "v", "z": "zz"}` + "\n" + + "INFO\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\tinfo\t" + `{"k": "v", "z": "zz"}` + "\n" + + "WARN\t[a-z0-9_-]+/config_test.go:" + `\d+` + "\twarn\t" + `{"k": "v", "z": "zz"}` + "\n" + + `go.uber.org/zap.TestConfig.\w+`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + logOut := filepath.Join(t.TempDir(), "test.log") + + tt.cfg.OutputPaths = []string{logOut} + tt.cfg.EncoderConfig.TimeKey = "" // no timestamps in tests + tt.cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"} + + hook, count := makeCountingHook() + logger, err := tt.cfg.Build(Hooks(hook)) + require.NoError(t, err, "Unexpected error constructing logger.") + + logger.Debug("debug") + logger.Info("info") + logger.Warn("warn") + + byteContents, err := os.ReadFile(logOut) + require.NoError(t, err, "Couldn't read log contents from temp file.") + logs := string(byteContents) + assert.Regexp(t, tt.expectRe, logs, "Unexpected log output.") + + for i := 0; i < 200; i++ { + logger.Info("sampling") + } + assert.Equal(t, tt.expectN, count.Load(), "Hook called an unexpected number of times.") + }) + } +} + +func TestConfigWithInvalidPaths(t *testing.T) { + tests := []struct { + desc string + output string + errOutput string + }{ + {"output directory doesn't exist", "/tmp/not-there/foo.log", "stderr"}, + {"error output directory doesn't exist", "stdout", "/tmp/not-there/foo-errors.log"}, + {"neither output directory exists", "/tmp/not-there/foo.log", "/tmp/not-there/foo-errors.log"}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := NewProductionConfig() + cfg.OutputPaths = []string{tt.output} + cfg.ErrorOutputPaths = []string{tt.errOutput} + _, err := cfg.Build() + assert.Error(t, err, "Expected an error opening a non-existent directory.") + }) + } +} + +func TestConfigWithMissingAttributes(t *testing.T) { + tests := []struct { + desc string + cfg Config + expectErr string + }{ + { + desc: "missing level", + cfg: Config{ + Encoding: "json", + }, + expectErr: "missing Level", + }, + { + desc: "missing encoder time in encoder config", + cfg: Config{ + Level: NewAtomicLevelAt(zapcore.InfoLevel), + Encoding: "json", + EncoderConfig: zapcore.EncoderConfig{ + MessageKey: "msg", + TimeKey: "ts", + }, + }, + expectErr: "missing EncodeTime in EncoderConfig", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := tt.cfg + _, err := cfg.Build() + assert.EqualError(t, err, tt.expectErr) + }) + } +} + +func makeSamplerCountingHook() (h func(zapcore.Entry, zapcore.SamplingDecision), + dropped, sampled *atomic.Int64, +) { + dropped = new(atomic.Int64) + sampled = new(atomic.Int64) + h = func(_ zapcore.Entry, dec zapcore.SamplingDecision) { + if dec&zapcore.LogDropped > 0 { + dropped.Add(1) + } else if dec&zapcore.LogSampled > 0 { + sampled.Add(1) + } + } + return h, dropped, sampled +} + +func TestConfigWithSamplingHook(t *testing.T) { + shook, dcount, scount := makeSamplerCountingHook() + cfg := Config{ + Level: NewAtomicLevelAt(InfoLevel), + Development: false, + Sampling: &SamplingConfig{ + Initial: 100, + Thereafter: 100, + Hook: shook, + }, + Encoding: "json", + EncoderConfig: NewProductionEncoderConfig(), + OutputPaths: []string{"stderr"}, + ErrorOutputPaths: []string{"stderr"}, + } + expectRe := `{"level":"info","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"info","k":"v","z":"zz"}` + "\n" + + `{"level":"warn","caller":"[a-z0-9_-]+/config_test.go:\d+","msg":"warn","k":"v","z":"zz"}` + "\n" + expectDropped := 99 // 200 - 100 initial - 1 thereafter + expectSampled := 103 // 2 from initial + 100 + 1 thereafter + + logOut := filepath.Join(t.TempDir(), "test.log") + cfg.OutputPaths = []string{logOut} + cfg.EncoderConfig.TimeKey = "" // no timestamps in tests + cfg.InitialFields = map[string]interface{}{"z": "zz", "k": "v"} + + logger, err := cfg.Build() + require.NoError(t, err, "Unexpected error constructing logger.") + + logger.Debug("debug") + logger.Info("info") + logger.Warn("warn") + + byteContents, err := os.ReadFile(logOut) + require.NoError(t, err, "Couldn't read log contents from temp file.") + logs := string(byteContents) + assert.Regexp(t, expectRe, logs, "Unexpected log output.") + + for i := 0; i < 200; i++ { + logger.Info("sampling") + } + assert.Equal(t, int64(expectDropped), dcount.Load()) + assert.Equal(t, int64(expectSampled), scount.Load()) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/doc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/doc.go new file mode 100644 index 0000000..3c50d7b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/doc.go @@ -0,0 +1,117 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zap provides fast, structured, leveled logging. +// +// For applications that log in the hot path, reflection-based serialization +// and string formatting are prohibitively expensive - they're CPU-intensive +// and make many small allocations. Put differently, using json.Marshal and +// fmt.Fprintf to log tons of interface{} makes your application slow. +// +// Zap takes a different approach. It includes a reflection-free, +// zero-allocation JSON encoder, and the base Logger strives to avoid +// serialization overhead and allocations wherever possible. By building the +// high-level SugaredLogger on that foundation, zap lets users choose when +// they need to count every allocation and when they'd prefer a more familiar, +// loosely typed API. +// +// # Choosing a Logger +// +// In contexts where performance is nice, but not critical, use the +// SugaredLogger. It's 4-10x faster than other structured logging packages and +// supports both structured and printf-style logging. Like log15 and go-kit, +// the SugaredLogger's structured logging APIs are loosely typed and accept a +// variadic number of key-value pairs. (For more advanced use cases, they also +// accept strongly typed fields - see the SugaredLogger.With documentation for +// details.) +// +// sugar := zap.NewExample().Sugar() +// defer sugar.Sync() +// sugar.Infow("failed to fetch URL", +// "url", "http://example.com", +// "attempt", 3, +// "backoff", time.Second, +// ) +// sugar.Infof("failed to fetch URL: %s", "http://example.com") +// +// By default, loggers are unbuffered. However, since zap's low-level APIs +// allow buffering, calling Sync before letting your process exit is a good +// habit. +// +// In the rare contexts where every microsecond and every allocation matter, +// use the Logger. It's even faster than the SugaredLogger and allocates far +// less, but it only supports strongly-typed, structured logging. +// +// logger := zap.NewExample() +// defer logger.Sync() +// logger.Info("failed to fetch URL", +// zap.String("url", "http://example.com"), +// zap.Int("attempt", 3), +// zap.Duration("backoff", time.Second), +// ) +// +// Choosing between the Logger and SugaredLogger doesn't need to be an +// application-wide decision: converting between the two is simple and +// inexpensive. +// +// logger := zap.NewExample() +// defer logger.Sync() +// sugar := logger.Sugar() +// plain := sugar.Desugar() +// +// # Configuring Zap +// +// The simplest way to build a Logger is to use zap's opinionated presets: +// NewExample, NewProduction, and NewDevelopment. These presets build a logger +// with a single function call: +// +// logger, err := zap.NewProduction() +// if err != nil { +// log.Fatalf("can't initialize zap logger: %v", err) +// } +// defer logger.Sync() +// +// Presets are fine for small projects, but larger projects and organizations +// naturally require a bit more customization. For most users, zap's Config +// struct strikes the right balance between flexibility and convenience. See +// the package-level BasicConfiguration example for sample code. +// +// More unusual configurations (splitting output between files, sending logs +// to a message queue, etc.) are possible, but require direct use of +// go.uber.org/zap/zapcore. See the package-level AdvancedConfiguration +// example for sample code. +// +// # Extending Zap +// +// The zap package itself is a relatively thin wrapper around the interfaces +// in go.uber.org/zap/zapcore. Extending zap to support a new encoding (e.g., +// BSON), a new log sink (e.g., Kafka), or something more exotic (perhaps an +// exception aggregation service, like Sentry or Rollbar) typically requires +// implementing the zapcore.Encoder, zapcore.WriteSyncer, or zapcore.Core +// interfaces. See the zapcore documentation for details. +// +// Similarly, package authors can use the high-performance Encoder and Core +// implementations in the zapcore package to build their own loggers. +// +// # Frequently Asked Questions +// +// An FAQ covering everything from installation errors to design decisions is +// available at https://github.com/uber-go/zap/blob/master/FAQ.md. +package zap // import "go.uber.org/zap" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder.go new file mode 100644 index 0000000..caa04ce --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder.go @@ -0,0 +1,79 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "sync" + + "go.uber.org/zap/zapcore" +) + +var ( + errNoEncoderNameSpecified = errors.New("no encoder name specified") + + _encoderNameToConstructor = map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error){ + "console": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewConsoleEncoder(encoderConfig), nil + }, + "json": func(encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + return zapcore.NewJSONEncoder(encoderConfig), nil + }, + } + _encoderMutex sync.RWMutex +) + +// RegisterEncoder registers an encoder constructor, which the Config struct +// can then reference. By default, the "json" and "console" encoders are +// registered. +// +// Attempting to register an encoder whose name is already taken returns an +// error. +func RegisterEncoder(name string, constructor func(zapcore.EncoderConfig) (zapcore.Encoder, error)) error { + _encoderMutex.Lock() + defer _encoderMutex.Unlock() + if name == "" { + return errNoEncoderNameSpecified + } + if _, ok := _encoderNameToConstructor[name]; ok { + return fmt.Errorf("encoder already registered for name %q", name) + } + _encoderNameToConstructor[name] = constructor + return nil +} + +func newEncoder(name string, encoderConfig zapcore.EncoderConfig) (zapcore.Encoder, error) { + if encoderConfig.TimeKey != "" && encoderConfig.EncodeTime == nil { + return nil, errors.New("missing EncodeTime in EncoderConfig") + } + + _encoderMutex.RLock() + defer _encoderMutex.RUnlock() + if name == "" { + return nil, errNoEncoderNameSpecified + } + constructor, ok := _encoderNameToConstructor[name] + if !ok { + return nil, fmt.Errorf("no encoder registered for name %q", name) + } + return constructor(encoderConfig) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder_test.go new file mode 100644 index 0000000..b71eb65 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/encoder_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestRegisterDefaultEncoders(t *testing.T) { + testEncodersRegistered(t, "console", "json") +} + +func TestRegisterEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + testEncodersRegistered(t, "foo") + }) +} + +func TestDuplicateRegisterEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + assert.Error(t, RegisterEncoder("foo", newNilEncoder), "expected an error when registering an encoder with the same name twice") + }) +} + +func TestRegisterEncoderNoName(t *testing.T) { + assert.Equal(t, errNoEncoderNameSpecified, RegisterEncoder("", newNilEncoder), "expected an error when registering an encoder with no name") +} + +func TestNewEncoder(t *testing.T) { + testEncoders(func() { + assert.NoError(t, RegisterEncoder("foo", newNilEncoder), "expected to be able to register the encoder foo") + encoder, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.NoError(t, err, "could not create an encoder for the registered name foo") + assert.Nil(t, encoder, "the encoder from newNilEncoder is not nil") + }) +} + +func TestNewEncoderNotRegistered(t *testing.T) { + _, err := newEncoder("foo", zapcore.EncoderConfig{}) + assert.Error(t, err, "expected an error when trying to create an encoder of an unregistered name") +} + +func TestNewEncoderNoName(t *testing.T) { + _, err := newEncoder("", zapcore.EncoderConfig{}) + assert.Equal(t, errNoEncoderNameSpecified, err, "expected an error when creating an encoder with no name") +} + +func testEncoders(f func()) { + existing := _encoderNameToConstructor + _encoderNameToConstructor = make(map[string]func(zapcore.EncoderConfig) (zapcore.Encoder, error)) + defer func() { _encoderNameToConstructor = existing }() + f() +} + +func testEncodersRegistered(t *testing.T, names ...string) { + assert.Len(t, _encoderNameToConstructor, len(names), "the expected number of registered encoders does not match the actual number") + for _, name := range names { + assert.NotNil(t, _encoderNameToConstructor[name], "no encoder is registered for name %s", name) + } +} + +func newNilEncoder(_ zapcore.EncoderConfig) (zapcore.Encoder, error) { + return nil, nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error.go new file mode 100644 index 0000000..45f7b83 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error.go @@ -0,0 +1,82 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "go.uber.org/zap/internal/pool" + "go.uber.org/zap/zapcore" +) + +var _errArrayElemPool = pool.New(func() *errArrayElem { + return &errArrayElem{} +}) + +// Error is shorthand for the common idiom NamedError("error", err). +func Error(err error) Field { + return NamedError("error", err) +} + +// NamedError constructs a field that lazily stores err.Error() under the +// provided key. Errors which also implement fmt.Formatter (like those produced +// by github.com/pkg/errors) will also have their verbose representation stored +// under key+"Verbose". If passed a nil error, the field is a no-op. +// +// For the common case in which the key is simply "error", the Error function +// is shorter and less repetitive. +func NamedError(key string, err error) Field { + if err == nil { + return Skip() + } + return Field{Key: key, Type: zapcore.ErrorType, Interface: err} +} + +type errArray []error + +func (errs errArray) MarshalLogArray(arr zapcore.ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + // To represent each error as an object with an "error" attribute and + // potentially an "errorVerbose" attribute, we need to wrap it in a + // type that implements LogObjectMarshaler. To prevent this from + // allocating, pool the wrapper type. + elem := _errArrayElemPool.Get() + elem.error = errs[i] + err := arr.AppendObject(elem) + elem.error = nil + _errArrayElemPool.Put(elem) + if err != nil { + return err + } + } + return nil +} + +type errArrayElem struct { + error +} + +func (e *errArrayElem) MarshalLogObject(enc zapcore.ObjectEncoder) error { + // Re-use the error field's logic, which supports non-standard error types. + Error(e.error).AddTo(enc) + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error_test.go new file mode 100644 index 0000000..4bfa370 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/error_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestErrorConstructors(t *testing.T) { + fail := errors.New("fail") + + tests := []struct { + name string + field Field + expect Field + }{ + {"Error", Skip(), Error(nil)}, + {"Error", Field{Key: "error", Type: zapcore.ErrorType, Interface: fail}, Error(fail)}, + {"NamedError", Skip(), NamedError("foo", nil)}, + {"NamedError", Field{Key: "foo", Type: zapcore.ErrorType, Interface: fail}, NamedError("foo", fail)}, + {"Any:Error", Any("k", errors.New("v")), NamedError("k", errors.New("v"))}, + {"Any:Errors", Any("k", []error{errors.New("v")}), Errors("k", []error{errors.New("v")})}, + } + + for _, tt := range tests { + if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor %s.", tt.name) { + t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface) + } + assertCanBeReused(t, tt.field) + } +} + +func TestErrorArrayConstructor(t *testing.T) { + tests := []struct { + desc string + field Field + expected []interface{} + }{ + {"empty errors", Errors("", []error{}), []interface{}{}}, + { + "errors", + Errors("", []error{nil, errors.New("foo"), nil, errors.New("bar")}), + []interface{}{map[string]interface{}{"error": "foo"}, map[string]interface{}{"error": "bar"}}, + }, + } + + for _, tt := range tests { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "%s: unexpected map contents.", tt.desc) + assert.Equal(t, 1, len(enc.Fields), "%s: found extra keys in map: %v", tt.desc, enc.Fields) + } +} + +func TestErrorsArraysHandleRichErrors(t *testing.T) { + errs := []error{fmt.Errorf("egad")} + + enc := zapcore.NewMapObjectEncoder() + Errors("k", errs).AddTo(enc) + assert.Equal(t, 1, len(enc.Fields), "Expected only top-level field.") + + val := enc.Fields["k"] + arr, ok := val.([]interface{}) + require.True(t, ok, "Expected top-level field to be an array.") + require.Equal(t, 1, len(arr), "Expected only one error object in array.") + + serialized := arr[0] + errMap, ok := serialized.(map[string]interface{}) + require.True(t, ok, "Expected serialized error to be a map, got %T.", serialized) + assert.Equal(t, "egad", errMap["error"], "Unexpected standard error string.") +} + +func TestErrArrayBrokenEncoder(t *testing.T) { + t.Parallel() + + failWith := errors.New("great sadness") + err := (brokenArrayObjectEncoder{ + Err: failWith, + ObjectEncoder: zapcore.NewMapObjectEncoder(), + }).AddArray("errors", errArray{ + errors.New("foo"), + errors.New("bar"), + }) + require.Error(t, err, "Expected error from broken encoder.") + assert.ErrorIs(t, err, failWith, "Unexpected error.") +} + +// brokenArrayObjectEncoder is an ObjectEncoder +// that builds a broken ArrayEncoder. +type brokenArrayObjectEncoder struct { + zapcore.ObjectEncoder + zapcore.ArrayEncoder + + Err error // error to return +} + +func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler zapcore.ArrayMarshaler) error { + return enc.ObjectEncoder.AddArray(key, + zapcore.ArrayMarshalerFunc(func(ae zapcore.ArrayEncoder) error { + enc.ArrayEncoder = ae + return marshaler.MarshalLogArray(enc) + })) +} + +func (enc brokenArrayObjectEncoder) AppendObject(zapcore.ObjectMarshaler) error { + return enc.Err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/example_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/example_test.go new file mode 100644 index 0000000..af7df0e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/example_test.go @@ -0,0 +1,413 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap_test + +import ( + "encoding/json" + "io" + "log" + "os" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func Example_presets() { + // Using zap's preset constructors is the simplest way to get a feel for the + // package, but they don't allow much customization. + logger := zap.NewExample() // or NewProduction, or NewDevelopment + defer logger.Sync() + + const url = "http://example.com" + + // In most circumstances, use the SugaredLogger. It's 4-10x faster than most + // other structured logging packages and has a familiar, loosely-typed API. + sugar := logger.Sugar() + sugar.Infow("Failed to fetch URL.", + // Structured context as loosely typed key-value pairs. + "url", url, + "attempt", 3, + "backoff", time.Second, + ) + sugar.Infof("Failed to fetch URL: %s", url) + + // In the unusual situations where every microsecond matters, use the + // Logger. It's even faster than the SugaredLogger, but only supports + // structured logging. + logger.Info("Failed to fetch URL.", + // Structured context as strongly typed fields. + zap.String("url", url), + zap.Int("attempt", 3), + zap.Duration("backoff", time.Second), + ) + // Output: + // {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"} + // {"level":"info","msg":"Failed to fetch URL: http://example.com"} + // {"level":"info","msg":"Failed to fetch URL.","url":"http://example.com","attempt":3,"backoff":"1s"} +} + +func Example_basicConfiguration() { + // For some users, the presets offered by the NewProduction, NewDevelopment, + // and NewExample constructors won't be appropriate. For most of those + // users, the bundled Config struct offers the right balance of flexibility + // and convenience. (For more complex needs, see the AdvancedConfiguration + // example.) + // + // See the documentation for Config and zapcore.EncoderConfig for all the + // available options. + rawJSON := []byte(`{ + "level": "debug", + "encoding": "json", + "outputPaths": ["stdout", "/tmp/logs"], + "errorOutputPaths": ["stderr"], + "initialFields": {"foo": "bar"}, + "encoderConfig": { + "messageKey": "message", + "levelKey": "level", + "levelEncoder": "lowercase" + } + }`) + + var cfg zap.Config + if err := json.Unmarshal(rawJSON, &cfg); err != nil { + panic(err) + } + logger := zap.Must(cfg.Build()) + defer logger.Sync() + + logger.Info("logger construction succeeded") + // Output: + // {"level":"info","message":"logger construction succeeded","foo":"bar"} +} + +func Example_advancedConfiguration() { + // The bundled Config struct only supports the most common configuration + // options. More complex needs, like splitting logs between multiple files + // or writing to non-file outputs, require use of the zapcore package. + // + // In this example, imagine we're both sending our logs to Kafka and writing + // them to the console. We'd like to encode the console output and the Kafka + // topics differently, and we'd also like special treatment for + // high-priority logs. + + // First, define our level-handling logic. + highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl >= zapcore.ErrorLevel + }) + lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { + return lvl < zapcore.ErrorLevel + }) + + // Assume that we have clients for two Kafka topics. The clients implement + // zapcore.WriteSyncer and are safe for concurrent use. (If they only + // implement io.Writer, we can use zapcore.AddSync to add a no-op Sync + // method. If they're not safe for concurrent use, we can add a protecting + // mutex with zapcore.Lock.) + topicDebugging := zapcore.AddSync(io.Discard) + topicErrors := zapcore.AddSync(io.Discard) + + // High-priority output should also go to standard error, and low-priority + // output should also go to standard out. + consoleDebugging := zapcore.Lock(os.Stdout) + consoleErrors := zapcore.Lock(os.Stderr) + + // Optimize the Kafka output for machine consumption and the console output + // for human operators. + kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) + consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) + + // Join the outputs, encoders, and level-handling functions into + // zapcore.Cores, then tee the four cores together. + core := zapcore.NewTee( + zapcore.NewCore(kafkaEncoder, topicErrors, highPriority), + zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), + zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority), + zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), + ) + + // From a zapcore.Core, it's easy to construct a Logger. + logger := zap.New(core) + defer logger.Sync() + logger.Info("constructed a logger") +} + +func ExampleNamespace() { + logger := zap.NewExample() + defer logger.Sync() + + logger.With( + zap.Namespace("metrics"), + zap.Int("counter", 1), + ).Info("tracked some metrics") + // Output: + // {"level":"info","msg":"tracked some metrics","metrics":{"counter":1}} +} + +type addr struct { + IP string + Port int +} + +type request struct { + URL string + Listen addr + Remote addr +} + +func (a addr) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("ip", a.IP) + enc.AddInt("port", a.Port) + return nil +} + +func (r *request) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("url", r.URL) + zap.Inline(r.Listen).AddTo(enc) + return enc.AddObject("remote", r.Remote) +} + +func ExampleObject() { + logger := zap.NewExample() + defer logger.Sync() + + req := &request{ + URL: "/test", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"127.0.0.1", 31200}, + } + logger.Info("new request, in nested object", zap.Object("req", req)) + logger.Info("new request, inline", zap.Inline(req)) + // Output: + // {"level":"info","msg":"new request, in nested object","req":{"url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}} + // {"level":"info","msg":"new request, inline","url":"/test","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}} +} + +func ExampleNewStdLog() { + logger := zap.NewExample() + defer logger.Sync() + + std := zap.NewStdLog(logger) + std.Print("standard logger wrapper") + // Output: + // {"level":"info","msg":"standard logger wrapper"} +} + +func ExampleRedirectStdLog() { + logger := zap.NewExample() + defer logger.Sync() + + undo := zap.RedirectStdLog(logger) + defer undo() + + log.Print("redirected standard library") + // Output: + // {"level":"info","msg":"redirected standard library"} +} + +func ExampleReplaceGlobals() { + logger := zap.NewExample() + defer logger.Sync() + + undo := zap.ReplaceGlobals(logger) + defer undo() + + zap.L().Info("replaced zap's global loggers") + // Output: + // {"level":"info","msg":"replaced zap's global loggers"} +} + +func ExampleAtomicLevel() { + atom := zap.NewAtomicLevel() + + // To keep the example deterministic, disable timestamps in the output. + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.TimeKey = "" + + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stdout), + atom, + )) + defer logger.Sync() + + logger.Info("info logging enabled") + + atom.SetLevel(zap.ErrorLevel) + logger.Info("info logging disabled") + // Output: + // {"level":"info","msg":"info logging enabled"} +} + +func ExampleAtomicLevel_config() { + // The zap.Config struct includes an AtomicLevel. To use it, keep a + // reference to the Config. + rawJSON := []byte(`{ + "level": "info", + "outputPaths": ["stdout"], + "errorOutputPaths": ["stderr"], + "encoding": "json", + "encoderConfig": { + "messageKey": "message", + "levelKey": "level", + "levelEncoder": "lowercase" + } + }`) + var cfg zap.Config + if err := json.Unmarshal(rawJSON, &cfg); err != nil { + panic(err) + } + logger := zap.Must(cfg.Build()) + defer logger.Sync() + + logger.Info("info logging enabled") + + cfg.Level.SetLevel(zap.ErrorLevel) + logger.Info("info logging disabled") + // Output: + // {"level":"info","message":"info logging enabled"} +} + +func ExampleLogger_Check() { + logger := zap.NewExample() + defer logger.Sync() + + if ce := logger.Check(zap.DebugLevel, "debugging"); ce != nil { + // If debug-level log output isn't enabled or if zap's sampling would have + // dropped this log entry, we don't allocate the slice that holds these + // fields. + ce.Write( + zap.String("foo", "bar"), + zap.String("baz", "quux"), + ) + } + + // Output: + // {"level":"debug","msg":"debugging","foo":"bar","baz":"quux"} +} + +func ExampleLogger_Named() { + logger := zap.NewExample() + defer logger.Sync() + + // By default, Loggers are unnamed. + logger.Info("no name") + + // The first call to Named sets the Logger name. + main := logger.Named("main") + main.Info("main logger") + + // Additional calls to Named create a period-separated path. + main.Named("subpackage").Info("sub-logger") + // Output: + // {"level":"info","msg":"no name"} + // {"level":"info","logger":"main","msg":"main logger"} + // {"level":"info","logger":"main.subpackage","msg":"sub-logger"} +} + +func ExampleWrapCore_replace() { + // Replacing a Logger's core can alter fundamental behaviors. + // For example, it can convert a Logger to a no-op. + nop := zap.WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewNopCore() + }) + + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("working") + logger.WithOptions(nop).Info("no-op") + logger.Info("original logger still works") + // Output: + // {"level":"info","msg":"working"} + // {"level":"info","msg":"original logger still works"} +} + +func ExampleWrapCore_wrap() { + // Wrapping a Logger's core can extend its functionality. As a trivial + // example, it can double-write all logs. + doubled := zap.WrapCore(func(c zapcore.Core) zapcore.Core { + return zapcore.NewTee(c, c) + }) + + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("single") + logger.WithOptions(doubled).Info("doubled") + // Output: + // {"level":"info","msg":"single"} + // {"level":"info","msg":"doubled"} + // {"level":"info","msg":"doubled"} +} + +func ExampleDict() { + logger := zap.NewExample() + defer logger.Sync() + + logger.Info("login event", + zap.Dict("event", + zap.Int("id", 123), + zap.String("name", "jane"), + zap.String("status", "pending"))) + // Output: + // {"level":"info","msg":"login event","event":{"id":123,"name":"jane","status":"pending"}} +} + +func ExampleObjects() { + logger := zap.NewExample() + defer logger.Sync() + + // Use the Objects field constructor when you have a list of objects, + // all of which implement zapcore.ObjectMarshaler. + logger.Debug("opening connections", + zap.Objects("addrs", []addr{ + {IP: "123.45.67.89", Port: 4040}, + {IP: "127.0.0.1", Port: 4041}, + {IP: "192.168.0.1", Port: 4042}, + })) + // Output: + // {"level":"debug","msg":"opening connections","addrs":[{"ip":"123.45.67.89","port":4040},{"ip":"127.0.0.1","port":4041},{"ip":"192.168.0.1","port":4042}]} +} + +func ExampleObjectValues() { + logger := zap.NewExample() + defer logger.Sync() + + // Use the ObjectValues field constructor when you have a list of + // objects that do not implement zapcore.ObjectMarshaler directly, + // but on their pointer receivers. + logger.Debug("starting tunnels", + zap.ObjectValues("addrs", []request{ + { + URL: "/foo", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"123.45.67.89", 4040}, + }, + { + URL: "/bar", + Listen: addr{"127.0.0.1", 8080}, + Remote: addr{"127.0.0.1", 31200}, + }, + })) + // Output: + // {"level":"debug","msg":"starting tunnels","addrs":[{"url":"/foo","ip":"127.0.0.1","port":8080,"remote":{"ip":"123.45.67.89","port":4040}},{"url":"/bar","ip":"127.0.0.1","port":8080,"remote":{"ip":"127.0.0.1","port":31200}}]} +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field.go new file mode 100644 index 0000000..6743930 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field.go @@ -0,0 +1,615 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "math" + "time" + + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +// Field is an alias for Field. Aliasing this type dramatically +// improves the navigability of this package's API documentation. +type Field = zapcore.Field + +var ( + _minTimeInt64 = time.Unix(0, math.MinInt64) + _maxTimeInt64 = time.Unix(0, math.MaxInt64) +) + +// Skip constructs a no-op field, which is often useful when handling invalid +// inputs in other Field constructors. +func Skip() Field { + return Field{Type: zapcore.SkipType} +} + +// nilField returns a field which will marshal explicitly as nil. See motivation +// in https://github.com/uber-go/zap/issues/753 . If we ever make breaking +// changes and add zapcore.NilType and zapcore.ObjectEncoder.AddNil, the +// implementation here should be changed to reflect that. +func nilField(key string) Field { return Reflect(key, nil) } + +// Binary constructs a field that carries an opaque binary blob. +// +// Binary data is serialized in an encoding-appropriate format. For example, +// zap's JSON encoder base64-encodes binary blobs. To log UTF-8 encoded text, +// use ByteString. +func Binary(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.BinaryType, Interface: val} +} + +// Bool constructs a field that carries a bool. +func Bool(key string, val bool) Field { + var ival int64 + if val { + ival = 1 + } + return Field{Key: key, Type: zapcore.BoolType, Integer: ival} +} + +// Boolp constructs a field that carries a *bool. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Boolp(key string, val *bool) Field { + if val == nil { + return nilField(key) + } + return Bool(key, *val) +} + +// ByteString constructs a field that carries UTF-8 encoded text as a []byte. +// To log opaque binary blobs (which aren't necessarily valid UTF-8), use +// Binary. +func ByteString(key string, val []byte) Field { + return Field{Key: key, Type: zapcore.ByteStringType, Interface: val} +} + +// Complex128 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex128 to +// interface{}). +func Complex128(key string, val complex128) Field { + return Field{Key: key, Type: zapcore.Complex128Type, Interface: val} +} + +// Complex128p constructs a field that carries a *complex128. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Complex128p(key string, val *complex128) Field { + if val == nil { + return nilField(key) + } + return Complex128(key, *val) +} + +// Complex64 constructs a field that carries a complex number. Unlike most +// numeric fields, this costs an allocation (to convert the complex64 to +// interface{}). +func Complex64(key string, val complex64) Field { + return Field{Key: key, Type: zapcore.Complex64Type, Interface: val} +} + +// Complex64p constructs a field that carries a *complex64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Complex64p(key string, val *complex64) Field { + if val == nil { + return nilField(key) + } + return Complex64(key, *val) +} + +// Float64 constructs a field that carries a float64. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float64(key string, val float64) Field { + return Field{Key: key, Type: zapcore.Float64Type, Integer: int64(math.Float64bits(val))} +} + +// Float64p constructs a field that carries a *float64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Float64p(key string, val *float64) Field { + if val == nil { + return nilField(key) + } + return Float64(key, *val) +} + +// Float32 constructs a field that carries a float32. The way the +// floating-point value is represented is encoder-dependent, so marshaling is +// necessarily lazy. +func Float32(key string, val float32) Field { + return Field{Key: key, Type: zapcore.Float32Type, Integer: int64(math.Float32bits(val))} +} + +// Float32p constructs a field that carries a *float32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Float32p(key string, val *float32) Field { + if val == nil { + return nilField(key) + } + return Float32(key, *val) +} + +// Int constructs a field with the given key and value. +func Int(key string, val int) Field { + return Int64(key, int64(val)) +} + +// Intp constructs a field that carries a *int. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Intp(key string, val *int) Field { + if val == nil { + return nilField(key) + } + return Int(key, *val) +} + +// Int64 constructs a field with the given key and value. +func Int64(key string, val int64) Field { + return Field{Key: key, Type: zapcore.Int64Type, Integer: val} +} + +// Int64p constructs a field that carries a *int64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int64p(key string, val *int64) Field { + if val == nil { + return nilField(key) + } + return Int64(key, *val) +} + +// Int32 constructs a field with the given key and value. +func Int32(key string, val int32) Field { + return Field{Key: key, Type: zapcore.Int32Type, Integer: int64(val)} +} + +// Int32p constructs a field that carries a *int32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int32p(key string, val *int32) Field { + if val == nil { + return nilField(key) + } + return Int32(key, *val) +} + +// Int16 constructs a field with the given key and value. +func Int16(key string, val int16) Field { + return Field{Key: key, Type: zapcore.Int16Type, Integer: int64(val)} +} + +// Int16p constructs a field that carries a *int16. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int16p(key string, val *int16) Field { + if val == nil { + return nilField(key) + } + return Int16(key, *val) +} + +// Int8 constructs a field with the given key and value. +func Int8(key string, val int8) Field { + return Field{Key: key, Type: zapcore.Int8Type, Integer: int64(val)} +} + +// Int8p constructs a field that carries a *int8. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Int8p(key string, val *int8) Field { + if val == nil { + return nilField(key) + } + return Int8(key, *val) +} + +// String constructs a field with the given key and value. +func String(key string, val string) Field { + return Field{Key: key, Type: zapcore.StringType, String: val} +} + +// Stringp constructs a field that carries a *string. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Stringp(key string, val *string) Field { + if val == nil { + return nilField(key) + } + return String(key, *val) +} + +// Uint constructs a field with the given key and value. +func Uint(key string, val uint) Field { + return Uint64(key, uint64(val)) +} + +// Uintp constructs a field that carries a *uint. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uintp(key string, val *uint) Field { + if val == nil { + return nilField(key) + } + return Uint(key, *val) +} + +// Uint64 constructs a field with the given key and value. +func Uint64(key string, val uint64) Field { + return Field{Key: key, Type: zapcore.Uint64Type, Integer: int64(val)} +} + +// Uint64p constructs a field that carries a *uint64. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint64p(key string, val *uint64) Field { + if val == nil { + return nilField(key) + } + return Uint64(key, *val) +} + +// Uint32 constructs a field with the given key and value. +func Uint32(key string, val uint32) Field { + return Field{Key: key, Type: zapcore.Uint32Type, Integer: int64(val)} +} + +// Uint32p constructs a field that carries a *uint32. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint32p(key string, val *uint32) Field { + if val == nil { + return nilField(key) + } + return Uint32(key, *val) +} + +// Uint16 constructs a field with the given key and value. +func Uint16(key string, val uint16) Field { + return Field{Key: key, Type: zapcore.Uint16Type, Integer: int64(val)} +} + +// Uint16p constructs a field that carries a *uint16. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint16p(key string, val *uint16) Field { + if val == nil { + return nilField(key) + } + return Uint16(key, *val) +} + +// Uint8 constructs a field with the given key and value. +func Uint8(key string, val uint8) Field { + return Field{Key: key, Type: zapcore.Uint8Type, Integer: int64(val)} +} + +// Uint8p constructs a field that carries a *uint8. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uint8p(key string, val *uint8) Field { + if val == nil { + return nilField(key) + } + return Uint8(key, *val) +} + +// Uintptr constructs a field with the given key and value. +func Uintptr(key string, val uintptr) Field { + return Field{Key: key, Type: zapcore.UintptrType, Integer: int64(val)} +} + +// Uintptrp constructs a field that carries a *uintptr. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Uintptrp(key string, val *uintptr) Field { + if val == nil { + return nilField(key) + } + return Uintptr(key, *val) +} + +// Reflect constructs a field with the given key and an arbitrary object. It uses +// an encoding-appropriate, reflection-based function to lazily serialize nearly +// any object into the logging context, but it's relatively slow and +// allocation-heavy. Outside tests, Any is always a better choice. +// +// If encoding fails (e.g., trying to serialize a map[int]string to JSON), Reflect +// includes the error message in the final log output. +func Reflect(key string, val interface{}) Field { + return Field{Key: key, Type: zapcore.ReflectType, Interface: val} +} + +// Namespace creates a named, isolated scope within the logger's context. All +// subsequent fields will be added to the new namespace. +// +// This helps prevent key collisions when injecting loggers into sub-components +// or third-party libraries. +func Namespace(key string) Field { + return Field{Key: key, Type: zapcore.NamespaceType} +} + +// Stringer constructs a field with the given key and the output of the value's +// String method. The Stringer's String method is called lazily. +func Stringer(key string, val fmt.Stringer) Field { + return Field{Key: key, Type: zapcore.StringerType, Interface: val} +} + +// Time constructs a Field with the given key and value. The encoder +// controls how the time is serialized. +func Time(key string, val time.Time) Field { + if val.Before(_minTimeInt64) || val.After(_maxTimeInt64) { + return Field{Key: key, Type: zapcore.TimeFullType, Interface: val} + } + return Field{Key: key, Type: zapcore.TimeType, Integer: val.UnixNano(), Interface: val.Location()} +} + +// Timep constructs a field that carries a *time.Time. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Timep(key string, val *time.Time) Field { + if val == nil { + return nilField(key) + } + return Time(key, *val) +} + +// Stack constructs a field that stores a stacktrace of the current goroutine +// under provided key. Keep in mind that taking a stacktrace is eager and +// expensive (relatively speaking); this function both makes an allocation and +// takes about two microseconds. +func Stack(key string) Field { + return StackSkip(key, 1) // skip Stack +} + +// StackSkip constructs a field similarly to Stack, but also skips the given +// number of frames from the top of the stacktrace. +func StackSkip(key string, skip int) Field { + // Returning the stacktrace as a string costs an allocation, but saves us + // from expanding the zapcore.Field union struct to include a byte slice. Since + // taking a stacktrace is already so expensive (~10us), the extra allocation + // is okay. + return String(key, stacktrace.Take(skip+1)) // skip StackSkip +} + +// Duration constructs a field with the given key and value. The encoder +// controls how the duration is serialized. +func Duration(key string, val time.Duration) Field { + return Field{Key: key, Type: zapcore.DurationType, Integer: int64(val)} +} + +// Durationp constructs a field that carries a *time.Duration. The returned Field will safely +// and explicitly represent `nil` when appropriate. +func Durationp(key string, val *time.Duration) Field { + if val == nil { + return nilField(key) + } + return Duration(key, *val) +} + +// Object constructs a field with the given key and ObjectMarshaler. It +// provides a flexible, but still type-safe and efficient, way to add map- or +// struct-like user-defined types to the logging context. The struct's +// MarshalLogObject method is called lazily. +func Object(key string, val zapcore.ObjectMarshaler) Field { + return Field{Key: key, Type: zapcore.ObjectMarshalerType, Interface: val} +} + +// Inline constructs a Field that is similar to Object, but it +// will add the elements of the provided ObjectMarshaler to the +// current namespace. +func Inline(val zapcore.ObjectMarshaler) Field { + return zapcore.Field{ + Type: zapcore.InlineMarshalerType, + Interface: val, + } +} + +// Dict constructs a field containing the provided key-value pairs. +// It acts similar to [Object], but with the fields specified as arguments. +func Dict(key string, val ...Field) Field { + return dictField(key, val) +} + +// We need a function with the signature (string, T) for zap.Any. +func dictField(key string, val []Field) Field { + return Object(key, dictObject(val)) +} + +type dictObject []Field + +func (d dictObject) MarshalLogObject(enc zapcore.ObjectEncoder) error { + for _, f := range d { + f.AddTo(enc) + } + return nil +} + +// We discovered an issue where zap.Any can cause a performance degradation +// when used in new goroutines. +// +// This happens because the compiler assigns 4.8kb (one zap.Field per arm of +// switch statement) of stack space for zap.Any when it takes the form: +// +// switch v := v.(type) { +// case string: +// return String(key, v) +// case int: +// return Int(key, v) +// // ... +// default: +// return Reflect(key, v) +// } +// +// To avoid this, we use the type switch to assign a value to a single local variable +// and then call a function on it. +// The local variable is just a function reference so it doesn't allocate +// when converted to an interface{}. +// +// A fair bit of experimentation went into this. +// See also: +// +// - https://github.com/uber-go/zap/pull/1301 +// - https://github.com/uber-go/zap/pull/1303 +// - https://github.com/uber-go/zap/pull/1304 +// - https://github.com/uber-go/zap/pull/1305 +// - https://github.com/uber-go/zap/pull/1308 +// +// See https://github.com/golang/go/issues/62077 for upstream issue. +type anyFieldC[T any] func(string, T) Field + +func (f anyFieldC[T]) Any(key string, val any) Field { + v, _ := val.(T) + // val is guaranteed to be a T, except when it's nil. + return f(key, v) +} + +// Any takes a key and an arbitrary value and chooses the best way to represent +// them as a field, falling back to a reflection-based approach only if +// necessary. +// +// Since byte/uint8 and rune/int32 are aliases, Any can't differentiate between +// them. To minimize surprises, []byte values are treated as binary blobs, byte +// values are treated as uint8, and runes are always treated as integers. +func Any(key string, value interface{}) Field { + var c interface{ Any(string, any) Field } + + switch value.(type) { + case zapcore.ObjectMarshaler: + c = anyFieldC[zapcore.ObjectMarshaler](Object) + case zapcore.ArrayMarshaler: + c = anyFieldC[zapcore.ArrayMarshaler](Array) + case []Field: + c = anyFieldC[[]Field](dictField) + case bool: + c = anyFieldC[bool](Bool) + case *bool: + c = anyFieldC[*bool](Boolp) + case []bool: + c = anyFieldC[[]bool](Bools) + case complex128: + c = anyFieldC[complex128](Complex128) + case *complex128: + c = anyFieldC[*complex128](Complex128p) + case []complex128: + c = anyFieldC[[]complex128](Complex128s) + case complex64: + c = anyFieldC[complex64](Complex64) + case *complex64: + c = anyFieldC[*complex64](Complex64p) + case []complex64: + c = anyFieldC[[]complex64](Complex64s) + case float64: + c = anyFieldC[float64](Float64) + case *float64: + c = anyFieldC[*float64](Float64p) + case []float64: + c = anyFieldC[[]float64](Float64s) + case float32: + c = anyFieldC[float32](Float32) + case *float32: + c = anyFieldC[*float32](Float32p) + case []float32: + c = anyFieldC[[]float32](Float32s) + case int: + c = anyFieldC[int](Int) + case *int: + c = anyFieldC[*int](Intp) + case []int: + c = anyFieldC[[]int](Ints) + case int64: + c = anyFieldC[int64](Int64) + case *int64: + c = anyFieldC[*int64](Int64p) + case []int64: + c = anyFieldC[[]int64](Int64s) + case int32: + c = anyFieldC[int32](Int32) + case *int32: + c = anyFieldC[*int32](Int32p) + case []int32: + c = anyFieldC[[]int32](Int32s) + case int16: + c = anyFieldC[int16](Int16) + case *int16: + c = anyFieldC[*int16](Int16p) + case []int16: + c = anyFieldC[[]int16](Int16s) + case int8: + c = anyFieldC[int8](Int8) + case *int8: + c = anyFieldC[*int8](Int8p) + case []int8: + c = anyFieldC[[]int8](Int8s) + case string: + c = anyFieldC[string](String) + case *string: + c = anyFieldC[*string](Stringp) + case []string: + c = anyFieldC[[]string](Strings) + case uint: + c = anyFieldC[uint](Uint) + case *uint: + c = anyFieldC[*uint](Uintp) + case []uint: + c = anyFieldC[[]uint](Uints) + case uint64: + c = anyFieldC[uint64](Uint64) + case *uint64: + c = anyFieldC[*uint64](Uint64p) + case []uint64: + c = anyFieldC[[]uint64](Uint64s) + case uint32: + c = anyFieldC[uint32](Uint32) + case *uint32: + c = anyFieldC[*uint32](Uint32p) + case []uint32: + c = anyFieldC[[]uint32](Uint32s) + case uint16: + c = anyFieldC[uint16](Uint16) + case *uint16: + c = anyFieldC[*uint16](Uint16p) + case []uint16: + c = anyFieldC[[]uint16](Uint16s) + case uint8: + c = anyFieldC[uint8](Uint8) + case *uint8: + c = anyFieldC[*uint8](Uint8p) + case []byte: + c = anyFieldC[[]byte](Binary) + case uintptr: + c = anyFieldC[uintptr](Uintptr) + case *uintptr: + c = anyFieldC[*uintptr](Uintptrp) + case []uintptr: + c = anyFieldC[[]uintptr](Uintptrs) + case time.Time: + c = anyFieldC[time.Time](Time) + case *time.Time: + c = anyFieldC[*time.Time](Timep) + case []time.Time: + c = anyFieldC[[]time.Time](Times) + case time.Duration: + c = anyFieldC[time.Duration](Duration) + case *time.Duration: + c = anyFieldC[*time.Duration](Durationp) + case []time.Duration: + c = anyFieldC[[]time.Duration](Durations) + case error: + c = anyFieldC[error](NamedError) + case []error: + c = anyFieldC[[]error](Errors) + case fmt.Stringer: + c = anyFieldC[fmt.Stringer](Stringer) + default: + c = anyFieldC[any](Reflect) + } + + return c.Any(key, value) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field_test.go new file mode 100644 index 0000000..f87f159 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/field_test.go @@ -0,0 +1,316 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "math" + "net" + "regexp" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +type username string + +func (n username) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("username", string(n)) + return nil +} + +func assertCanBeReused(t testing.TB, field Field) { + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + enc := zapcore.NewMapObjectEncoder() + + // Ensure using the field in multiple encoders in separate goroutines + // does not cause any races or panics. + wg.Add(1) + go func() { + defer wg.Done() + assert.NotPanics(t, func() { + field.AddTo(enc) + }, "Reusing a field should not cause issues") + }() + } + + wg.Wait() +} + +func TestFieldConstructors(t *testing.T) { + // Interface types. + addr := net.ParseIP("1.2.3.4") + name := username("phil") + ints := []int{5, 6} + + // Helpful values for use in constructing pointers to primitives below. + var ( + boolVal = bool(true) + complex128Val = complex128(complex(0, 0)) + complex64Val = complex64(complex(0, 0)) + durationVal = time.Duration(time.Second) + float64Val = float64(1.0) + float32Val = float32(1.0) + intVal = int(1) + int64Val = int64(1) + int32Val = int32(1) + int16Val = int16(1) + int8Val = int8(1) + stringVal = string("hello") + timeVal = time.Unix(100000, 0) + uintVal = uint(1) + uint64Val = uint64(1) + uint32Val = uint32(1) + uint16Val = uint16(1) + uint8Val = uint8(1) + uintptrVal = uintptr(1) + nilErr error + ) + + tests := []struct { + name string + field Field + expect Field + }{ + {"Skip", Field{Type: zapcore.SkipType}, Skip()}, + {"Binary", Field{Key: "k", Type: zapcore.BinaryType, Interface: []byte("ab12")}, Binary("k", []byte("ab12"))}, + {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, + {"Bool", Field{Key: "k", Type: zapcore.BoolType, Integer: 1}, Bool("k", true)}, + {"ByteString", Field{Key: "k", Type: zapcore.ByteStringType, Interface: []byte("ab12")}, ByteString("k", []byte("ab12"))}, + {"Complex128", Field{Key: "k", Type: zapcore.Complex128Type, Interface: 1 + 2i}, Complex128("k", 1+2i)}, + {"Complex64", Field{Key: "k", Type: zapcore.Complex64Type, Interface: complex64(1 + 2i)}, Complex64("k", 1+2i)}, + {"Duration", Field{Key: "k", Type: zapcore.DurationType, Integer: 1}, Duration("k", 1)}, + {"Int", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int("k", 1)}, + {"Int64", Field{Key: "k", Type: zapcore.Int64Type, Integer: 1}, Int64("k", 1)}, + {"Int32", Field{Key: "k", Type: zapcore.Int32Type, Integer: 1}, Int32("k", 1)}, + {"Int16", Field{Key: "k", Type: zapcore.Int16Type, Integer: 1}, Int16("k", 1)}, + {"Int8", Field{Key: "k", Type: zapcore.Int8Type, Integer: 1}, Int8("k", 1)}, + {"String", Field{Key: "k", Type: zapcore.StringType, String: "foo"}, String("k", "foo")}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 0, Interface: time.UTC}, Time("k", time.Unix(0, 0).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: 1000, Interface: time.UTC}, Time("k", time.Unix(0, 1000).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MinInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MinInt64).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeType, Integer: math.MaxInt64, Interface: time.UTC}, Time("k", time.Unix(0, math.MaxInt64).In(time.UTC))}, + {"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Time{}}, Time("k", time.Time{})}, + {"Time", Field{Key: "k", Type: zapcore.TimeFullType, Interface: time.Unix(math.MaxInt64, 0)}, Time("k", time.Unix(math.MaxInt64, 0))}, + {"Uint", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint("k", 1)}, + {"Uint64", Field{Key: "k", Type: zapcore.Uint64Type, Integer: 1}, Uint64("k", 1)}, + {"Uint32", Field{Key: "k", Type: zapcore.Uint32Type, Integer: 1}, Uint32("k", 1)}, + {"Uint16", Field{Key: "k", Type: zapcore.Uint16Type, Integer: 1}, Uint16("k", 1)}, + {"Uint8", Field{Key: "k", Type: zapcore.Uint8Type, Integer: 1}, Uint8("k", 1)}, + {"Uintptr", Field{Key: "k", Type: zapcore.UintptrType, Integer: 10}, Uintptr("k", 0xa)}, + {"Reflect", Field{Key: "k", Type: zapcore.ReflectType, Interface: ints}, Reflect("k", ints)}, + {"Reflect", Field{Key: "k", Type: zapcore.ReflectType}, Reflect("k", nil)}, + {"Stringer", Field{Key: "k", Type: zapcore.StringerType, Interface: addr}, Stringer("k", addr)}, + {"Object", Field{Key: "k", Type: zapcore.ObjectMarshalerType, Interface: name}, Object("k", name)}, + {"Inline", Field{Type: zapcore.InlineMarshalerType, Interface: name}, Inline(name)}, + {"Any:ObjectMarshaler", Any("k", name), Object("k", name)}, + {"Any:ArrayMarshaler", Any("k", bools([]bool{true})), Array("k", bools([]bool{true}))}, + {"Any:Dict", Any("k", []Field{String("k", "v")}), Dict("k", String("k", "v"))}, + {"Any:Stringer", Any("k", addr), Stringer("k", addr)}, + {"Any:Bool", Any("k", true), Bool("k", true)}, + {"Any:Bools", Any("k", []bool{true}), Bools("k", []bool{true})}, + {"Any:Byte", Any("k", byte(1)), Uint8("k", 1)}, + {"Any:Bytes", Any("k", []byte{1}), Binary("k", []byte{1})}, + {"Any:Complex128", Any("k", 1+2i), Complex128("k", 1+2i)}, + {"Any:Complex128s", Any("k", []complex128{1 + 2i}), Complex128s("k", []complex128{1 + 2i})}, + {"Any:Complex64", Any("k", complex64(1+2i)), Complex64("k", 1+2i)}, + {"Any:Complex64s", Any("k", []complex64{1 + 2i}), Complex64s("k", []complex64{1 + 2i})}, + {"Any:Float64", Any("k", 3.14), Float64("k", 3.14)}, + {"Any:Float64s", Any("k", []float64{3.14}), Float64s("k", []float64{3.14})}, + {"Any:Float32", Any("k", float32(3.14)), Float32("k", 3.14)}, + {"Any:Float32s", Any("k", []float32{3.14}), Float32s("k", []float32{3.14})}, + {"Any:Int", Any("k", 1), Int("k", 1)}, + {"Any:Ints", Any("k", []int{1}), Ints("k", []int{1})}, + {"Any:Int64", Any("k", int64(1)), Int64("k", 1)}, + {"Any:Int64s", Any("k", []int64{1}), Int64s("k", []int64{1})}, + {"Any:Int32", Any("k", int32(1)), Int32("k", 1)}, + {"Any:Int32s", Any("k", []int32{1}), Int32s("k", []int32{1})}, + {"Any:Int16", Any("k", int16(1)), Int16("k", 1)}, + {"Any:Int16s", Any("k", []int16{1}), Int16s("k", []int16{1})}, + {"Any:Int8", Any("k", int8(1)), Int8("k", 1)}, + {"Any:Int8s", Any("k", []int8{1}), Int8s("k", []int8{1})}, + {"Any:Rune", Any("k", rune(1)), Int32("k", 1)}, + {"Any:Runes", Any("k", []rune{1}), Int32s("k", []int32{1})}, + {"Any:String", Any("k", "v"), String("k", "v")}, + {"Any:Strings", Any("k", []string{"v"}), Strings("k", []string{"v"})}, + {"Any:Uint", Any("k", uint(1)), Uint("k", 1)}, + {"Any:Uints", Any("k", []uint{1}), Uints("k", []uint{1})}, + {"Any:Uint64", Any("k", uint64(1)), Uint64("k", 1)}, + {"Any:Uint64s", Any("k", []uint64{1}), Uint64s("k", []uint64{1})}, + {"Any:Uint32", Any("k", uint32(1)), Uint32("k", 1)}, + {"Any:Uint32s", Any("k", []uint32{1}), Uint32s("k", []uint32{1})}, + {"Any:Uint16", Any("k", uint16(1)), Uint16("k", 1)}, + {"Any:Uint16s", Any("k", []uint16{1}), Uint16s("k", []uint16{1})}, + {"Any:Uint8", Any("k", uint8(1)), Uint8("k", 1)}, + {"Any:Uint8s", Any("k", []uint8{1}), Binary("k", []uint8{1})}, + {"Any:Uintptr", Any("k", uintptr(1)), Uintptr("k", 1)}, + {"Any:Uintptrs", Any("k", []uintptr{1}), Uintptrs("k", []uintptr{1})}, + {"Any:Time", Any("k", time.Unix(0, 0)), Time("k", time.Unix(0, 0))}, + {"Any:TimeFullType", Any("k", time.Time{}), Time("k", time.Time{})}, + {"Any:Times", Any("k", []time.Time{time.Unix(0, 0)}), Times("k", []time.Time{time.Unix(0, 0)})}, + {"Any:Duration", Any("k", time.Second), Duration("k", time.Second)}, + {"Any:Durations", Any("k", []time.Duration{time.Second}), Durations("k", []time.Duration{time.Second})}, + {"Any:Fallback", Any("k", struct{}{}), Reflect("k", struct{}{})}, + {"Ptr:Bool", Boolp("k", nil), nilField("k")}, + {"Ptr:Bool", Boolp("k", &boolVal), Bool("k", boolVal)}, + {"Any:PtrBool", Any("k", (*bool)(nil)), nilField("k")}, + {"Any:PtrBool", Any("k", &boolVal), Bool("k", boolVal)}, + {"Ptr:Complex128", Complex128p("k", nil), nilField("k")}, + {"Ptr:Complex128", Complex128p("k", &complex128Val), Complex128("k", complex128Val)}, + {"Any:PtrComplex128", Any("k", (*complex128)(nil)), nilField("k")}, + {"Any:PtrComplex128", Any("k", &complex128Val), Complex128("k", complex128Val)}, + {"Ptr:Complex64", Complex64p("k", nil), nilField("k")}, + {"Ptr:Complex64", Complex64p("k", &complex64Val), Complex64("k", complex64Val)}, + {"Any:PtrComplex64", Any("k", (*complex64)(nil)), nilField("k")}, + {"Any:PtrComplex64", Any("k", &complex64Val), Complex64("k", complex64Val)}, + {"Ptr:Duration", Durationp("k", nil), nilField("k")}, + {"Ptr:Duration", Durationp("k", &durationVal), Duration("k", durationVal)}, + {"Any:PtrDuration", Any("k", (*time.Duration)(nil)), nilField("k")}, + {"Any:PtrDuration", Any("k", &durationVal), Duration("k", durationVal)}, + {"Ptr:Float64", Float64p("k", nil), nilField("k")}, + {"Ptr:Float64", Float64p("k", &float64Val), Float64("k", float64Val)}, + {"Any:PtrFloat64", Any("k", (*float64)(nil)), nilField("k")}, + {"Any:PtrFloat64", Any("k", &float64Val), Float64("k", float64Val)}, + {"Ptr:Float32", Float32p("k", nil), nilField("k")}, + {"Ptr:Float32", Float32p("k", &float32Val), Float32("k", float32Val)}, + {"Any:PtrFloat32", Any("k", (*float32)(nil)), nilField("k")}, + {"Any:PtrFloat32", Any("k", &float32Val), Float32("k", float32Val)}, + {"Ptr:Int", Intp("k", nil), nilField("k")}, + {"Ptr:Int", Intp("k", &intVal), Int("k", intVal)}, + {"Any:PtrInt", Any("k", (*int)(nil)), nilField("k")}, + {"Any:PtrInt", Any("k", &intVal), Int("k", intVal)}, + {"Ptr:Int64", Int64p("k", nil), nilField("k")}, + {"Ptr:Int64", Int64p("k", &int64Val), Int64("k", int64Val)}, + {"Any:PtrInt64", Any("k", (*int64)(nil)), nilField("k")}, + {"Any:PtrInt64", Any("k", &int64Val), Int64("k", int64Val)}, + {"Ptr:Int32", Int32p("k", nil), nilField("k")}, + {"Ptr:Int32", Int32p("k", &int32Val), Int32("k", int32Val)}, + {"Any:PtrInt32", Any("k", (*int32)(nil)), nilField("k")}, + {"Any:PtrInt32", Any("k", &int32Val), Int32("k", int32Val)}, + {"Ptr:Int16", Int16p("k", nil), nilField("k")}, + {"Ptr:Int16", Int16p("k", &int16Val), Int16("k", int16Val)}, + {"Any:PtrInt16", Any("k", (*int16)(nil)), nilField("k")}, + {"Any:PtrInt16", Any("k", &int16Val), Int16("k", int16Val)}, + {"Ptr:Int8", Int8p("k", nil), nilField("k")}, + {"Ptr:Int8", Int8p("k", &int8Val), Int8("k", int8Val)}, + {"Any:PtrInt8", Any("k", (*int8)(nil)), nilField("k")}, + {"Any:PtrInt8", Any("k", &int8Val), Int8("k", int8Val)}, + {"Ptr:String", Stringp("k", nil), nilField("k")}, + {"Ptr:String", Stringp("k", &stringVal), String("k", stringVal)}, + {"Any:PtrString", Any("k", (*string)(nil)), nilField("k")}, + {"Any:PtrString", Any("k", &stringVal), String("k", stringVal)}, + {"Ptr:Time", Timep("k", nil), nilField("k")}, + {"Ptr:Time", Timep("k", &timeVal), Time("k", timeVal)}, + {"Any:PtrTime", Any("k", (*time.Time)(nil)), nilField("k")}, + {"Any:PtrTime", Any("k", &timeVal), Time("k", timeVal)}, + {"Any:PtrTimeFullType", Any("k", &time.Time{}), Time("k", time.Time{})}, + {"Ptr:Uint", Uintp("k", nil), nilField("k")}, + {"Ptr:Uint", Uintp("k", &uintVal), Uint("k", uintVal)}, + {"Any:PtrUint", Any("k", (*uint)(nil)), nilField("k")}, + {"Any:PtrUint", Any("k", &uintVal), Uint("k", uintVal)}, + {"Ptr:Uint64", Uint64p("k", nil), nilField("k")}, + {"Ptr:Uint64", Uint64p("k", &uint64Val), Uint64("k", uint64Val)}, + {"Any:PtrUint64", Any("k", (*uint64)(nil)), nilField("k")}, + {"Any:PtrUint64", Any("k", &uint64Val), Uint64("k", uint64Val)}, + {"Ptr:Uint32", Uint32p("k", nil), nilField("k")}, + {"Ptr:Uint32", Uint32p("k", &uint32Val), Uint32("k", uint32Val)}, + {"Any:PtrUint32", Any("k", (*uint32)(nil)), nilField("k")}, + {"Any:PtrUint32", Any("k", &uint32Val), Uint32("k", uint32Val)}, + {"Ptr:Uint16", Uint16p("k", nil), nilField("k")}, + {"Ptr:Uint16", Uint16p("k", &uint16Val), Uint16("k", uint16Val)}, + {"Any:PtrUint16", Any("k", (*uint16)(nil)), nilField("k")}, + {"Any:PtrUint16", Any("k", &uint16Val), Uint16("k", uint16Val)}, + {"Ptr:Uint8", Uint8p("k", nil), nilField("k")}, + {"Ptr:Uint8", Uint8p("k", &uint8Val), Uint8("k", uint8Val)}, + {"Any:PtrUint8", Any("k", (*uint8)(nil)), nilField("k")}, + {"Any:PtrUint8", Any("k", &uint8Val), Uint8("k", uint8Val)}, + {"Ptr:Uintptr", Uintptrp("k", nil), nilField("k")}, + {"Ptr:Uintptr", Uintptrp("k", &uintptrVal), Uintptr("k", uintptrVal)}, + {"Any:PtrUintptr", Any("k", (*uintptr)(nil)), nilField("k")}, + {"Any:PtrUintptr", Any("k", &uintptrVal), Uintptr("k", uintptrVal)}, + {"Any:ErrorNil", Any("k", nilErr), nilField("k")}, + {"Namespace", Namespace("k"), Field{Key: "k", Type: zapcore.NamespaceType}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !assert.Equal(t, tt.expect, tt.field, "Unexpected output from convenience field constructor") { + t.Logf("type expected: %T\nGot: %T", tt.expect.Interface, tt.field.Interface) + } + assertCanBeReused(t, tt.field) + }) + } +} + +func TestStackField(t *testing.T) { + f := Stack("stacktrace") + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + r := regexp.MustCompile(`field_test.go:(\d+)`) + assert.Equal(t, r.ReplaceAllString(stacktrace.Take(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestStackSkipField(t *testing.T) { + f := StackSkip("stacktrace", 0) + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + r := regexp.MustCompile(`field_test.go:(\d+)`) + assert.Equal(t, r.ReplaceAllString(stacktrace.Take(0), "field_test.go"), r.ReplaceAllString(f.String, "field_test.go"), f.String, "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestStackSkipFieldWithSkip(t *testing.T) { + f := StackSkip("stacktrace", 1) + assert.Equal(t, "stacktrace", f.Key, "Unexpected field key.") + assert.Equal(t, zapcore.StringType, f.Type, "Unexpected field type.") + assert.Equal(t, stacktrace.Take(1), f.String, "Unexpected stack trace") + assertCanBeReused(t, f) +} + +func TestDict(t *testing.T) { + tests := []struct { + desc string + field Field + expected any + }{ + {"empty", Dict(""), map[string]any{}}, + {"single", Dict("", String("k", "v")), map[string]any{"k": "v"}}, + {"multiple", Dict("", String("k", "v"), String("k2", "v2")), map[string]any{"k": "v", "k2": "v2"}}, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := zapcore.NewMapObjectEncoder() + tt.field.Key = "k" + tt.field.AddTo(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "unexpected map contents") + assert.Len(t, enc.Fields, 1, "found extra keys in map: %v", enc.Fields) + + assertCanBeReused(t, tt.field) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag.go new file mode 100644 index 0000000..1312875 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "flag" + + "go.uber.org/zap/zapcore" +) + +// LevelFlag uses the standard library's flag.Var to declare a global flag +// with the specified name, default, and usage guidance. The returned value is +// a pointer to the value of the flag. +// +// If you don't want to use the flag package's global state, you can use any +// non-nil *Level as a flag.Value with your own *flag.FlagSet. +func LevelFlag(name string, defaultLevel zapcore.Level, usage string) *zapcore.Level { + lvl := defaultLevel + flag.Var(&lvl, name, usage) + return &lvl +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag_test.go new file mode 100644 index 0000000..9ff5444 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/flag_test.go @@ -0,0 +1,103 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "flag" + "io" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +type flagTestCase struct { + args []string + wantLevel zapcore.Level + wantErr bool +} + +func (tc flagTestCase) runImplicitSet(t testing.TB) { + origCommandLine := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + flag.CommandLine.SetOutput(io.Discard) + defer func() { flag.CommandLine = origCommandLine }() + + level := LevelFlag("level", InfoLevel, "") + tc.run(t, flag.CommandLine, level) +} + +func (tc flagTestCase) runExplicitSet(t testing.TB) { + var lvl zapcore.Level + set := flag.NewFlagSet("test", flag.ContinueOnError) + set.SetOutput(io.Discard) + set.Var(&lvl, "level", "minimum enabled logging level") + tc.run(t, set, &lvl) +} + +func (tc flagTestCase) run(t testing.TB, set *flag.FlagSet, actual *zapcore.Level) { + err := set.Parse(tc.args) + if tc.wantErr { + assert.Error(t, err, "Parse(%v) should fail.", tc.args) + return + } + if assert.NoError(t, err, "Parse(%v) should succeed.", tc.args) { + assert.Equal(t, tc.wantLevel, *actual, "Level mismatch.") + } +} + +func TestLevelFlag(t *testing.T) { + tests := []flagTestCase{ + { + args: nil, + wantLevel: zapcore.InfoLevel, + }, + { + args: []string{"--level", "unknown"}, + wantErr: true, + }, + { + args: []string{"--level", "error"}, + wantLevel: zapcore.ErrorLevel, + }, + } + + for _, tt := range tests { + tt.runExplicitSet(t) + tt.runImplicitSet(t) + } +} + +func TestLevelFlagsAreIndependent(t *testing.T) { + origCommandLine := flag.CommandLine + flag.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError) + flag.CommandLine.SetOutput(io.Discard) + defer func() { flag.CommandLine = origCommandLine }() + + // Make sure that these two flags are independent. + fileLevel := LevelFlag("file-level", InfoLevel, "") + consoleLevel := LevelFlag("console-level", InfoLevel, "") + + assert.NoError(t, flag.CommandLine.Parse([]string{"-file-level", "debug"}), "Unexpected flag-parsing error.") + assert.Equal(t, InfoLevel, *consoleLevel, "Expected file logging level to remain unchanged.") + assert.Equal(t, DebugLevel, *fileLevel, "Expected console logging level to have changed.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/glide.yaml b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/glide.yaml new file mode 100644 index 0000000..8e1d05e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/glide.yaml @@ -0,0 +1,34 @@ +package: go.uber.org/zap +license: MIT +import: +- package: go.uber.org/atomic + version: ^1 +- package: go.uber.org/multierr + version: ^1 +testImport: +- package: github.com/satori/go.uuid +- package: github.com/sirupsen/logrus +- package: github.com/apex/log + subpackages: + - handlers/json +- package: github.com/go-kit/kit + subpackages: + - log +- package: github.com/stretchr/testify + subpackages: + - assert + - require +- package: gopkg.in/inconshreveable/log15.v2 +- package: github.com/mattn/goveralls +- package: github.com/pborman/uuid +- package: github.com/pkg/errors +- package: github.com/rs/zerolog +- package: golang.org/x/tools + subpackages: + - cover +- package: golang.org/x/lint + subpackages: + - golint +- package: github.com/axw/gocov + subpackages: + - gocov diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global.go new file mode 100644 index 0000000..3cb46c9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global.go @@ -0,0 +1,169 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "fmt" + "log" + "os" + "sync" + + "go.uber.org/zap/zapcore" +) + +const ( + _stdLogDefaultDepth = 1 + _loggerWriterDepth = 2 + _programmerErrorTemplate = "You've found a bug in zap! Please file a bug at " + + "https://github.com/uber-go/zap/issues/new and reference this error: %v" +) + +var ( + _globalMu sync.RWMutex + _globalL = NewNop() + _globalS = _globalL.Sugar() +) + +// L returns the global Logger, which can be reconfigured with ReplaceGlobals. +// It's safe for concurrent use. +func L() *Logger { + _globalMu.RLock() + l := _globalL + _globalMu.RUnlock() + return l +} + +// S returns the global SugaredLogger, which can be reconfigured with +// ReplaceGlobals. It's safe for concurrent use. +func S() *SugaredLogger { + _globalMu.RLock() + s := _globalS + _globalMu.RUnlock() + return s +} + +// ReplaceGlobals replaces the global Logger and SugaredLogger, and returns a +// function to restore the original values. It's safe for concurrent use. +func ReplaceGlobals(logger *Logger) func() { + _globalMu.Lock() + prev := _globalL + _globalL = logger + _globalS = logger.Sugar() + _globalMu.Unlock() + return func() { ReplaceGlobals(prev) } +} + +// NewStdLog returns a *log.Logger which writes to the supplied zap Logger at +// InfoLevel. To redirect the standard library's package-global logging +// functions, use RedirectStdLog instead. +func NewStdLog(l *Logger) *log.Logger { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + f := logger.Info + return log.New(&loggerWriter{f}, "" /* prefix */, 0 /* flags */) +} + +// NewStdLogAt returns *log.Logger which writes to supplied zap logger at +// required level. +func NewStdLogAt(l *Logger, level zapcore.Level) (*log.Logger, error) { + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + return log.New(&loggerWriter{logFunc}, "" /* prefix */, 0 /* flags */), nil +} + +// RedirectStdLog redirects output from the standard library's package-global +// logger to the supplied logger at InfoLevel. Since zap already handles caller +// annotations, timestamps, etc., it automatically disables the standard +// library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLog(l *Logger) func() { + f, err := redirectStdLogAt(l, InfoLevel) + if err != nil { + // Can't get here, since passing InfoLevel to redirectStdLogAt always + // works. + panic(fmt.Sprintf(_programmerErrorTemplate, err)) + } + return f +} + +// RedirectStdLogAt redirects output from the standard library's package-global +// logger to the supplied logger at the specified level. Since zap already +// handles caller annotations, timestamps, etc., it automatically disables the +// standard library's annotations and prefixing. +// +// It returns a function to restore the original prefix and flags and reset the +// standard library's output to os.Stderr. +func RedirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + return redirectStdLogAt(l, level) +} + +func redirectStdLogAt(l *Logger, level zapcore.Level) (func(), error) { + flags := log.Flags() + prefix := log.Prefix() + log.SetFlags(0) + log.SetPrefix("") + logger := l.WithOptions(AddCallerSkip(_stdLogDefaultDepth + _loggerWriterDepth)) + logFunc, err := levelToFunc(logger, level) + if err != nil { + return nil, err + } + log.SetOutput(&loggerWriter{logFunc}) + return func() { + log.SetFlags(flags) + log.SetPrefix(prefix) + log.SetOutput(os.Stderr) + }, nil +} + +func levelToFunc(logger *Logger, lvl zapcore.Level) (func(string, ...Field), error) { + switch lvl { + case DebugLevel: + return logger.Debug, nil + case InfoLevel: + return logger.Info, nil + case WarnLevel: + return logger.Warn, nil + case ErrorLevel: + return logger.Error, nil + case DPanicLevel: + return logger.DPanic, nil + case PanicLevel: + return logger.Panic, nil + case FatalLevel: + return logger.Fatal, nil + } + return nil, fmt.Errorf("unrecognized level: %q", lvl) +} + +type loggerWriter struct { + logFunc func(msg string, fields ...Field) +} + +func (l *loggerWriter) Write(p []byte) (int, error) { + p = bytes.TrimSpace(p) + l.logFunc(string(p)) + return len(p), nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global_test.go new file mode 100644 index 0000000..17fa225 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/global_test.go @@ -0,0 +1,281 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "log" + "sync" + "sync/atomic" + "testing" + "time" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestReplaceGlobals(t *testing.T) { + initialL := *L() + initialS := *S() + + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + L().Info("no-op") + S().Info("no-op") + assert.Equal(t, 0, logs.Len(), "Expected initial logs to go to default no-op global.") + + defer ReplaceGlobals(l)() + + L().Info("captured") + S().Info("captured") + expected := observer.LoggedEntry{ + Entry: zapcore.Entry{Message: "captured"}, + Context: []Field{}, + } + assert.Equal( + t, + []observer.LoggedEntry{expected, expected}, + logs.AllUntimed(), + "Unexpected global log output.", + ) + }) + + assert.Equal(t, initialL, *L(), "Expected func returned from ReplaceGlobals to restore initial L.") + assert.Equal(t, initialS, *S(), "Expected func returned from ReplaceGlobals to restore initial S.") +} + +func TestGlobalsConcurrentUse(t *testing.T) { + var ( + stop atomic.Bool + wg sync.WaitGroup + ) + + for i := 0; i < 100; i++ { + wg.Add(2) + go func() { + for !stop.Load() { + ReplaceGlobals(NewNop()) + } + wg.Done() + }() + go func() { + for !stop.Load() { + L().With(Int("foo", 42)).Named("main").WithOptions(Development()).Info("") + S().Info("") + } + wg.Done() + }() + } + + ztest.Sleep(100 * time.Millisecond) + // CAS loop to toggle the current value. + for old := stop.Load(); !stop.CompareAndSwap(old, !old); { + old = stop.Load() + } + wg.Wait() +} + +func TestNewStdLog(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + std := NewStdLog(l) + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) +} + +func TestNewStdLogAt(t *testing.T) { + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtPanics(t *testing.T) { + // include DPanicLevel here and enable Development in options + levels := []zapcore.Level{DPanicLevel, PanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { + std, err := NewStdLogAt(l, level) + require.NoError(t, err, "Unexpected error") + assert.Panics(t, func() { std.Print("redirected") }, "Expected log to panic.") + checkStdLogMessage(t, "redirected", logs) + }) + } +} + +func TestNewStdLogAtFatal(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + std, err := NewStdLogAt(l, FatalLevel) + require.NoError(t, err, "Unexpected error.") + std.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") + stub.Unstub() + }) +} + +func TestNewStdLogAtInvalid(t *testing.T) { + _, err := NewStdLogAt(NewNop(), zapcore.Level(99)) + assert.ErrorContains(t, err, "99", "Expected level code in error message") +} + +func TestRedirectStdLog(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + defer RedirectStdLog(l)() + log.Print("redirected") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Message: "redirected"}, + Context: []Field{}, + }}, logs.AllUntimed(), "Unexpected global log output.") + }) + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogCaller(t *testing.T) { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + defer RedirectStdLog(l)() + log.Print("redirected") + entries := logs.All() + require.Len(t, entries, 1, "Unexpected number of logs.") + assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") + }) +} + +func TestRedirectStdLogAt(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, nil, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Level: level, Message: "redirected"}, + Context: []Field{}, + }}, logs.AllUntimed(), "Unexpected global log output.") + }) + } + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtCaller(t *testing.T) { + // include DPanicLevel here, but do not include Development in options + levels := []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + entries := logs.All() + require.Len(t, entries, 1, "Unexpected number of logs.") + assert.Contains(t, entries[0].Caller.File, "global_test.go", "Unexpected caller annotation.") + }) + } +} + +func TestRedirectStdLogAtPanics(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + // include DPanicLevel here and enable Development in options + levels := []zapcore.Level{DPanicLevel, PanicLevel} + for _, level := range levels { + withLogger(t, DebugLevel, []Option{AddCaller(), Development()}, func(l *Logger, logs *observer.ObservedLogs) { + restore, err := RedirectStdLogAt(l, level) + require.NoError(t, err, "Unexpected error.") + defer restore() + assert.Panics(t, func() { log.Print("redirected") }, "Expected log to panic.") + checkStdLogMessage(t, "redirected", logs) + }) + } + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtFatal(t *testing.T) { + initialFlags := log.Flags() + initialPrefix := log.Prefix() + + withLogger(t, DebugLevel, []Option{AddCaller()}, func(l *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + restore, err := RedirectStdLogAt(l, FatalLevel) + require.NoError(t, err, "Unexpected error.") + defer restore() + log.Print("redirected") + checkStdLogMessage(t, "redirected", logs) + }) + assert.True(t, true, stub.Exited, "Expected Fatal logger call to terminate process.") + stub.Unstub() + }) + + assert.Equal(t, initialFlags, log.Flags(), "Expected to reset initial flags.") + assert.Equal(t, initialPrefix, log.Prefix(), "Expected to reset initial prefix.") +} + +func TestRedirectStdLogAtInvalid(t *testing.T) { + restore, err := RedirectStdLogAt(NewNop(), zapcore.Level(99)) + defer func() { + if restore != nil { + restore() + } + }() + assert.ErrorContains(t, err, "99", "Expected level code in error message") +} + +func checkStdLogMessage(t *testing.T, msg string, logs *observer.ObservedLogs) { + require.Equal(t, 1, logs.Len(), "Expected exactly one entry to be logged") + entry := logs.AllUntimed()[0] + assert.Equal(t, []Field{}, entry.Context, "Unexpected entry context.") + assert.Equal(t, "redirected", entry.Message, "Unexpected entry message.") + assert.Regexp( + t, + `/global_test.go:\d+$`, + entry.Caller.String(), + "Unexpected caller annotation.", + ) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.mod b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.mod new file mode 100644 index 0000000..88575f4 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.mod @@ -0,0 +1,16 @@ +module go.uber.org/zap + +go 1.19 + +require ( + github.com/stretchr/testify v1.8.1 + go.uber.org/goleak v1.3.0 + go.uber.org/multierr v1.10.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect +) diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.sum b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.sum new file mode 100644 index 0000000..a725757 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/go.sum @@ -0,0 +1,25 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler.go new file mode 100644 index 0000000..2be8f65 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler.go @@ -0,0 +1,140 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "go.uber.org/zap/zapcore" +) + +// ServeHTTP is a simple JSON endpoint that can report on or change the current +// logging level. +// +// # GET +// +// The GET request returns a JSON description of the current logging level like: +// +// {"level":"info"} +// +// # PUT +// +// The PUT request changes the logging level. It is perfectly safe to change the +// logging level while a program is running. Two content types are supported: +// +// Content-Type: application/x-www-form-urlencoded +// +// With this content type, the level can be provided through the request body or +// a query parameter. The log level is URL encoded like: +// +// level=debug +// +// The request body takes precedence over the query parameter, if both are +// specified. +// +// This content type is the default for a curl PUT request. Following are two +// example curl requests that both set the logging level to debug. +// +// curl -X PUT localhost:8080/log/level?level=debug +// curl -X PUT localhost:8080/log/level -d level=debug +// +// For any other content type, the payload is expected to be JSON encoded and +// look like: +// +// {"level":"info"} +// +// An example curl request could look like this: +// +// curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}' +func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := lvl.serveHTTP(w, r); err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "internal error: %v", err) + } +} + +func (lvl AtomicLevel) serveHTTP(w http.ResponseWriter, r *http.Request) error { + type errorResponse struct { + Error string `json:"error"` + } + type payload struct { + Level zapcore.Level `json:"level"` + } + + enc := json.NewEncoder(w) + + switch r.Method { + case http.MethodGet: + return enc.Encode(payload{Level: lvl.Level()}) + + case http.MethodPut: + requestedLvl, err := decodePutRequest(r.Header.Get("Content-Type"), r) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return enc.Encode(errorResponse{Error: err.Error()}) + } + lvl.SetLevel(requestedLvl) + return enc.Encode(payload{Level: lvl.Level()}) + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + return enc.Encode(errorResponse{ + Error: "Only GET and PUT are supported.", + }) + } +} + +// Decodes incoming PUT requests and returns the requested logging level. +func decodePutRequest(contentType string, r *http.Request) (zapcore.Level, error) { + if contentType == "application/x-www-form-urlencoded" { + return decodePutURL(r) + } + return decodePutJSON(r.Body) +} + +func decodePutURL(r *http.Request) (zapcore.Level, error) { + lvl := r.FormValue("level") + if lvl == "" { + return 0, errors.New("must specify logging level") + } + var l zapcore.Level + if err := l.UnmarshalText([]byte(lvl)); err != nil { + return 0, err + } + return l, nil +} + +func decodePutJSON(body io.Reader) (zapcore.Level, error) { + var pld struct { + Level *zapcore.Level `json:"level"` + } + if err := json.NewDecoder(body).Decode(&pld); err != nil { + return 0, fmt.Errorf("malformed request body: %v", err) + } + if pld.Level == nil { + return 0, errors.New("must specify logging level") + } + return *pld.Level, nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler_test.go new file mode 100644 index 0000000..9da3dc7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/http_handler_test.go @@ -0,0 +1,217 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap_test + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAtomicLevelServeHTTP(t *testing.T) { + tests := []struct { + desc string + method string + query string + contentType string + body string + expectedCode int + expectedLevel zapcore.Level + }{ + { + desc: "GET", + method: http.MethodGet, + expectedCode: http.StatusOK, + expectedLevel: zap.InfoLevel, + }, + { + desc: "PUT JSON", + method: http.MethodPut, + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + body: `{"level":"warn"}`, + }, + { + desc: "PUT URL encoded", + method: http.MethodPut, + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + { + desc: "PUT query parameters", + method: http.MethodPut, + query: "?level=warn", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + }, + { + desc: "body takes precedence over query", + method: http.MethodPut, + query: "?level=info", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + { + desc: "JSON ignores query", + method: http.MethodPut, + query: "?level=info", + expectedCode: http.StatusOK, + expectedLevel: zap.WarnLevel, + body: `{"level":"warn"}`, + }, + { + desc: "PUT JSON unrecognized", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{"level":"unrecognized"}`, + }, + { + desc: "PUT URL encoded unrecognized", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "level=unrecognized", + }, + { + desc: "PUT JSON malformed", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{"level":"warn`, + }, + { + desc: "PUT URL encoded malformed", + method: http.MethodPut, + query: "?level=%", + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + }, + { + desc: "PUT Query parameters malformed", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "level=%", + }, + { + desc: "PUT JSON unspecified", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + body: `{}`, + }, + { + desc: "PUT URL encoded unspecified", + method: http.MethodPut, + expectedCode: http.StatusBadRequest, + contentType: "application/x-www-form-urlencoded", + body: "", + }, + { + desc: "POST JSON", + method: http.MethodPost, + expectedCode: http.StatusMethodNotAllowed, + body: `{"level":"warn"}`, + }, + { + desc: "POST URL", + method: http.MethodPost, + expectedCode: http.StatusMethodNotAllowed, + contentType: "application/x-www-form-urlencoded", + body: "level=warn", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + lvl := zap.NewAtomicLevel() + lvl.SetLevel(zapcore.InfoLevel) + + server := httptest.NewServer(lvl) + defer server.Close() + + req, err := http.NewRequest(tt.method, server.URL+tt.query, strings.NewReader(tt.body)) + require.NoError(t, err, "Error constructing %s request.", req.Method) + if tt.contentType != "" { + req.Header.Set("Content-Type", tt.contentType) + } + + res, err := http.DefaultClient.Do(req) + require.NoError(t, err, "Error making %s request.", req.Method) + defer func() { + assert.NoError(t, res.Body.Close(), "Error closing response body.") + }() + + require.Equal(t, tt.expectedCode, res.StatusCode, "Unexpected status code.") + if tt.expectedCode != http.StatusOK { + // Don't need to test exact error message, but one should be present. + var pld struct { + Error string `json:"error"` + } + require.NoError(t, json.NewDecoder(res.Body).Decode(&pld), "Decoding response body") + assert.NotEmpty(t, pld.Error, "Expected an error message") + return + } + + var pld struct { + Level zapcore.Level `json:"level"` + } + require.NoError(t, json.NewDecoder(res.Body).Decode(&pld), "Decoding response body") + assert.Equal(t, tt.expectedLevel, pld.Level, "Unexpected logging level returned") + }) + } +} + +func TestAtomicLevelServeHTTPBrokenWriter(t *testing.T) { + t.Parallel() + + lvl := zap.NewAtomicLevel() + + request, err := http.NewRequest(http.MethodGet, "http://localhost:1234/log/level", nil) + require.NoError(t, err, "Error constructing request.") + + recorder := httptest.NewRecorder() + lvl.ServeHTTP(&brokenHTTPResponseWriter{ + ResponseWriter: recorder, + }, request) + + assert.Equal(t, http.StatusInternalServerError, recorder.Code, "Unexpected status code.") +} + +type brokenHTTPResponseWriter struct { + http.ResponseWriter +} + +func (w *brokenHTTPResponseWriter) Write([]byte) (int, error) { + return 0, errors.New("great sadness") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/increase_level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/increase_level_test.go new file mode 100644 index 0000000..2d88380 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/increase_level_test.go @@ -0,0 +1,94 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func newLoggedEntry(level zapcore.Level, msg string, fields ...zapcore.Field) observer.LoggedEntry { + if len(fields) == 0 { + fields = []zapcore.Field{} + } + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: level, Message: msg}, + Context: fields, + } +} + +func TestIncreaseLevelTryDecrease(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + debugLogger := logger.WithOptions(IncreaseLevel(DebugLevel)) + debugLogger.Debug("ignored debug log") + debugLogger.Warn("increase level warn log") + debugLogger.Error("increase level error log") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(WarnLevel, "increase level warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + }, logs.AllUntimed(), "unexpected logs") + assert.Equal(t, + "failed to IncreaseLevel: invalid increase level, as level \"info\" is allowed by increased level, but not by existing core\n", + errorOut.String(), + "unexpected error output", + ) + }) +} + +func TestIncreaseLevel(t *testing.T) { + errorOut := &bytes.Buffer{} + opts := []Option{ + ErrorOutput(zapcore.AddSync(errorOut)), + } + withLogger(t, WarnLevel, opts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Warn("original warn log") + + errorLogger := logger.WithOptions(IncreaseLevel(ErrorLevel)) + errorLogger.Debug("ignored debug log") + errorLogger.Warn("ignored warn log") + errorLogger.Error("increase level error log") + + withFields := errorLogger.With(String("k", "v")) + withFields.Debug("ignored debug log with fields") + withFields.Warn("ignored warn log with fields") + withFields.Error("increase level error log with fields") + + assert.Equal(t, []observer.LoggedEntry{ + newLoggedEntry(WarnLevel, "original warn log"), + newLoggedEntry(ErrorLevel, "increase level error log"), + newLoggedEntry(ErrorLevel, "increase level error log with fields", String("k", "v")), + }, logs.AllUntimed(), "unexpected logs") + + assert.Empty(t, errorOut.String(), "expect no error output") + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/bufferpool/bufferpool.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/bufferpool/bufferpool.go new file mode 100644 index 0000000..dad583a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/bufferpool/bufferpool.go @@ -0,0 +1,31 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package bufferpool houses zap's shared internal buffer pool. Third-party +// packages can recreate the same functionality with buffers.NewPool. +package bufferpool + +import "go.uber.org/zap/buffer" + +var ( + _pool = buffer.NewPool() + // Get retrieves a buffer from the pool, creating one if necessary. + Get = _pool.Get +) diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color.go new file mode 100644 index 0000000..c4d5d02 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color.go @@ -0,0 +1,44 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package color adds coloring functionality for TTY output. +package color + +import "fmt" + +// Foreground colors. +const ( + Black Color = iota + 30 + Red + Green + Yellow + Blue + Magenta + Cyan + White +) + +// Color represents a text color. +type Color uint8 + +// Add adds the coloring to the given string. +func (c Color) Add(s string) string { + return fmt.Sprintf("\x1b[%dm%s\x1b[0m", uint8(c), s) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color_test.go new file mode 100644 index 0000000..4982903 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/color/color_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package color + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestColorFormatting(t *testing.T) { + assert.Equal( + t, + "\x1b[31mfoo\x1b[0m", + Red.Add("foo"), + "Unexpected colored output.", + ) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit.go new file mode 100644 index 0000000..f673f99 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit.go @@ -0,0 +1,66 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package exit provides stubs so that unit tests can exercise code that calls +// os.Exit(1). +package exit + +import "os" + +var _exit = os.Exit + +// With terminates the process by calling os.Exit(code). If the package is +// stubbed, it instead records a call in the testing spy. +func With(code int) { + _exit(code) +} + +// A StubbedExit is a testing fake for os.Exit. +type StubbedExit struct { + Exited bool + Code int + prev func(code int) +} + +// Stub substitutes a fake for the call to os.Exit(1). +func Stub() *StubbedExit { + s := &StubbedExit{prev: _exit} + _exit = s.exit + return s +} + +// WithStub runs the supplied function with Exit stubbed. It returns the stub +// used, so that users can test whether the process would have crashed. +func WithStub(f func()) *StubbedExit { + s := Stub() + defer s.Unstub() + f() + return s +} + +// Unstub restores the previous exit function. +func (se *StubbedExit) Unstub() { + _exit = se.prev +} + +func (se *StubbedExit) exit(code int) { + se.Exited = true + se.Code = code +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit_test.go new file mode 100644 index 0000000..2299584 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/exit/exit_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package exit_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/exit" +) + +func TestStub(t *testing.T) { + type want struct { + exit bool + code int + } + tests := []struct { + f func() + want want + }{ + {func() { exit.With(42) }, want{exit: true, code: 42}}, + {func() {}, want{}}, + } + + for _, tt := range tests { + s := exit.WithStub(tt.f) + assert.Equal(t, tt.want.exit, s.Exited, "Stub captured unexpected exit value.") + assert.Equal(t, tt.want.code, s.Code, "Stub captured unexpected exit value.") + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/level_enabler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/level_enabler.go new file mode 100644 index 0000000..40bfed8 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/level_enabler.go @@ -0,0 +1,37 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package internal and its subpackages hold types and functionality +// that are not part of Zap's public API. +package internal + +import "go.uber.org/zap/zapcore" + +// LeveledEnabler is an interface satisfied by LevelEnablers that are able to +// report their own level. +// +// This interface is defined to use more conveniently in tests and non-zapcore +// packages. +// This cannot be imported from zapcore because of the cyclic dependency. +type LeveledEnabler interface { + zapcore.LevelEnabler + + Level() zapcore.Level +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool.go new file mode 100644 index 0000000..60e9d2c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package pool provides internal pool utilities. +package pool + +import ( + "sync" +) + +// A Pool is a generic wrapper around [sync.Pool] to provide strongly-typed +// object pooling. +// +// Note that SA6002 (ref: https://staticcheck.io/docs/checks/#SA6002) will +// not be detected, so all internal pool use must take care to only store +// pointer types. +type Pool[T any] struct { + pool sync.Pool +} + +// New returns a new [Pool] for T, and will use fn to construct new Ts when +// the pool is empty. +func New[T any](fn func() T) *Pool[T] { + return &Pool[T]{ + pool: sync.Pool{ + New: func() any { + return fn() + }, + }, + } +} + +// Get gets a T from the pool, or creates a new one if the pool is empty. +func (p *Pool[T]) Get() T { + return p.pool.Get().(T) +} + +// Put returns x into the pool. +func (p *Pool[T]) Put(x T) { + p.pool.Put(x) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool_test.go new file mode 100644 index 0000000..094edf9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/pool/pool_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package pool_test + +import ( + "runtime/debug" + "sync" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/pool" +) + +type pooledValue[T any] struct { + value T +} + +func TestNew(t *testing.T) { + // Disable GC to avoid the victim cache during the test. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) + + p := pool.New(func() *pooledValue[string] { + return &pooledValue[string]{ + value: "new", + } + }) + + // Probabilistically, 75% of sync.Pool.Put calls will succeed when -race + // is enabled (see ref below); attempt to make this quasi-deterministic by + // brute force (i.e., put significantly more objects in the pool than we + // will need for the test) in order to avoid testing without race enabled. + // + // ref: https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/sync/pool.go;l=100-103 + for i := 0; i < 1_000; i++ { + p.Put(&pooledValue[string]{ + value: t.Name(), + }) + } + + // Ensure that we always get the expected value. Note that this must only + // run a fraction of the number of times that Put is called above. + for i := 0; i < 10; i++ { + func() { + x := p.Get() + defer p.Put(x) + require.Equal(t, t.Name(), x.value) + }() + } + + // Depool all objects that might be in the pool to ensure that it's empty. + for i := 0; i < 1_000; i++ { + p.Get() + } + + // Now that the pool is empty, it should use the value specified in the + // underlying sync.Pool.New func. + require.Equal(t, "new", p.Get().value) +} + +func TestNew_Race(t *testing.T) { + p := pool.New(func() *pooledValue[int] { + return &pooledValue[int]{ + value: -1, + } + }) + + var wg sync.WaitGroup + defer wg.Wait() + + // Run a number of goroutines that read and write pool object fields to + // tease out races. + for i := 0; i < 1_000; i++ { + i := i + + wg.Add(1) + go func() { + defer wg.Done() + + x := p.Get() + defer p.Put(x) + + // Must both read and write the field. + if n := x.value; n >= -1 { + x.value = i + } + }() + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/readme/readme.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/readme/readme.go new file mode 100644 index 0000000..a9c8ad7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/readme/readme.go @@ -0,0 +1,243 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// readme generates Zap's README from a template. +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "text/template" + "time" +) + +var libraryNameToMarkdownName = map[string]string{ + "Zap": ":zap: zap", + "Zap.Sugar": ":zap: zap (sugared)", + "stdlib.Println": "standard library", + "sirupsen/logrus": "logrus", + "go-kit/kit/log": "go-kit", + "inconshreveable/log15": "log15", + "apex/log": "apex/log", + "rs/zerolog": "zerolog", + "slog": "slog", + "slog.LogAttrs": "slog (LogAttrs)", +} + +func main() { + flag.Parse() + if err := do(); err != nil { + log.Fatal(err) + } +} + +func do() error { + tmplData, err := getTmplData() + if err != nil { + return err + } + data, err := io.ReadAll(os.Stdin) + if err != nil { + return err + } + t, err := template.New("tmpl").Parse(string(data)) + if err != nil { + return err + } + return t.Execute(os.Stdout, tmplData) +} + +func getTmplData() (*tmplData, error) { + tmplData := &tmplData{} + rows, err := getBenchmarkRows("BenchmarkAddingFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkAddingFields = rows + rows, err = getBenchmarkRows("BenchmarkAccumulatedContext") + if err != nil { + return nil, err + } + tmplData.BenchmarkAccumulatedContext = rows + rows, err = getBenchmarkRows("BenchmarkWithoutFields") + if err != nil { + return nil, err + } + tmplData.BenchmarkWithoutFields = rows + return tmplData, nil +} + +func getBenchmarkRows(benchmarkName string) (string, error) { + benchmarkOutput, err := getBenchmarkOutput(benchmarkName) + if err != nil { + return "", err + } + + // get the Zap time (unsugared) as baseline to compare with other loggers + baseline, err := getBenchmarkRow(benchmarkOutput, benchmarkName, "Zap", nil) + if err != nil { + return "", err + } + + var benchmarkRows []*benchmarkRow + for libraryName := range libraryNameToMarkdownName { + benchmarkRow, err := getBenchmarkRow( + benchmarkOutput, benchmarkName, libraryName, baseline, + ) + if err != nil { + return "", err + } + if benchmarkRow == nil { + continue + } + benchmarkRows = append(benchmarkRows, benchmarkRow) + } + sort.Sort(benchmarkRowsByTime(benchmarkRows)) + rows := []string{ + "| Package | Time | Time % to zap | Objects Allocated |", + "| :------ | :--: | :-----------: | :---------------: |", + } + for _, benchmarkRow := range benchmarkRows { + rows = append(rows, benchmarkRow.String()) + } + return strings.Join(rows, "\n"), nil +} + +func getBenchmarkRow( + input []string, benchmarkName string, libraryName string, baseline *benchmarkRow, +) (*benchmarkRow, error) { + line, err := findUniqueSubstring(input, fmt.Sprintf("%s/%s-", benchmarkName, libraryName)) + if err != nil { + return nil, err + } + if line == "" { + return nil, nil + } + split := strings.Split(line, "\t") + if len(split) < 5 { + return nil, fmt.Errorf("unknown benchmark line: %s", line) + } + duration, err := time.ParseDuration(strings.ReplaceAll(strings.TrimSuffix(strings.TrimSpace(split[2]), "/op"), " ", "")) + if err != nil { + return nil, err + } + allocatedBytes, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[3]), " B/op")) + if err != nil { + return nil, err + } + allocatedObjects, err := strconv.Atoi(strings.TrimSuffix(strings.TrimSpace(split[4]), " allocs/op")) + if err != nil { + return nil, err + } + r := &benchmarkRow{ + Name: libraryNameToMarkdownName[libraryName], + Time: duration, + AllocatedBytes: allocatedBytes, + AllocatedObjects: allocatedObjects, + } + + if baseline != nil { + r.ZapTime = baseline.Time + r.ZapAllocatedBytes = baseline.AllocatedBytes + r.ZapAllocatedObjects = baseline.AllocatedObjects + } + + return r, nil +} + +func findUniqueSubstring(input []string, substring string) (string, error) { + var output string + for _, line := range input { + if strings.Contains(line, substring) { + if output != "" { + return "", fmt.Errorf("input has duplicate substring %s", substring) + } + output = line + } + } + return output, nil +} + +func getBenchmarkOutput(benchmarkName string) ([]string, error) { + cmd := exec.Command("go", "test", fmt.Sprintf("-bench=%s", benchmarkName), "-benchmem") + cmd.Dir = "benchmarks" + output, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("error running 'go test -bench=%q': %v\n%s", benchmarkName, err, string(output)) + } + return strings.Split(string(output), "\n"), nil +} + +type tmplData struct { + BenchmarkAddingFields string + BenchmarkAccumulatedContext string + BenchmarkWithoutFields string +} + +type benchmarkRow struct { + Name string + + Time time.Duration + AllocatedBytes int + AllocatedObjects int + + ZapTime time.Duration + ZapAllocatedBytes int + ZapAllocatedObjects int +} + +func (b *benchmarkRow) String() string { + pct := func(val, baseline int64) string { + return fmt.Sprintf( + "%+0.f%%", + ((float64(val)/float64(baseline))*100)-100, + ) + } + t := b.Time.Nanoseconds() + tp := pct(t, b.ZapTime.Nanoseconds()) + + return fmt.Sprintf( + "| %s | %d ns/op | %s | %d allocs/op", b.Name, + t, tp, b.AllocatedObjects, + ) +} + +type benchmarkRowsByTime []*benchmarkRow + +func (b benchmarkRowsByTime) Len() int { return len(b) } +func (b benchmarkRowsByTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b benchmarkRowsByTime) Less(i, j int) bool { + left, right := b[i], b[j] + leftZap, rightZap := strings.Contains(left.Name, "zap"), strings.Contains(right.Name, "zap") + + // If neither benchmark is for zap or both are, sort by time. + if leftZap == rightZap { + return left.Time.Nanoseconds() < right.Time.Nanoseconds() + } + // Sort zap benchmark first. + return leftZap +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack.go new file mode 100644 index 0000000..82af755 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack.go @@ -0,0 +1,181 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package stacktrace provides support for gathering stack traces +// efficiently. +package stacktrace + +import ( + "runtime" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +var _stackPool = pool.New(func() *Stack { + return &Stack{ + storage: make([]uintptr, 64), + } +}) + +// Stack is a captured stack trace. +type Stack struct { + pcs []uintptr // program counters; always a subslice of storage + frames *runtime.Frames + + // The size of pcs varies depending on requirements: + // it will be one if the only the first frame was requested, + // and otherwise it will reflect the depth of the call stack. + // + // storage decouples the slice we need (pcs) from the slice we pool. + // We will always allocate a reasonably large storage, but we'll use + // only as much of it as we need. + storage []uintptr +} + +// Depth specifies how deep of a stack trace should be captured. +type Depth int + +const ( + // First captures only the first frame. + First Depth = iota + + // Full captures the entire call stack, allocating more + // storage for it if needed. + Full +) + +// Capture captures a stack trace of the specified depth, skipping +// the provided number of frames. skip=0 identifies the caller of +// Capture. +// +// The caller must call Free on the returned stacktrace after using it. +func Capture(skip int, depth Depth) *Stack { + stack := _stackPool.Get() + + switch depth { + case First: + stack.pcs = stack.storage[:1] + case Full: + stack.pcs = stack.storage + } + + // Unlike other "skip"-based APIs, skip=0 identifies runtime.Callers + // itself. +2 to skip captureStacktrace and runtime.Callers. + numFrames := runtime.Callers( + skip+2, + stack.pcs, + ) + + // runtime.Callers truncates the recorded stacktrace if there is no + // room in the provided slice. For the full stack trace, keep expanding + // storage until there are fewer frames than there is room. + if depth == Full { + pcs := stack.pcs + for numFrames == len(pcs) { + pcs = make([]uintptr, len(pcs)*2) + numFrames = runtime.Callers(skip+2, pcs) + } + + // Discard old storage instead of returning it to the pool. + // This will adjust the pool size over time if stack traces are + // consistently very deep. + stack.storage = pcs + stack.pcs = pcs[:numFrames] + } else { + stack.pcs = stack.pcs[:numFrames] + } + + stack.frames = runtime.CallersFrames(stack.pcs) + return stack +} + +// Free releases resources associated with this stacktrace +// and returns it back to the pool. +func (st *Stack) Free() { + st.frames = nil + st.pcs = nil + _stackPool.Put(st) +} + +// Count reports the total number of frames in this stacktrace. +// Count DOES NOT change as Next is called. +func (st *Stack) Count() int { + return len(st.pcs) +} + +// Next returns the next frame in the stack trace, +// and a boolean indicating whether there are more after it. +func (st *Stack) Next() (_ runtime.Frame, more bool) { + return st.frames.Next() +} + +// Take returns a string representation of the current stacktrace. +// +// skip is the number of frames to skip before recording the stack trace. +// skip=0 identifies the caller of Take. +func Take(skip int) string { + stack := Capture(skip+1, Full) + defer stack.Free() + + buffer := bufferpool.Get() + defer buffer.Free() + + stackfmt := NewFormatter(buffer) + stackfmt.FormatStack(stack) + return buffer.String() +} + +// Formatter formats a stack trace into a readable string representation. +type Formatter struct { + b *buffer.Buffer + nonEmpty bool // whehther we've written at least one frame already +} + +// NewFormatter builds a new Formatter. +func NewFormatter(b *buffer.Buffer) Formatter { + return Formatter{b: b} +} + +// FormatStack formats all remaining frames in the provided stacktrace -- minus +// the final runtime.main/runtime.goexit frame. +func (sf *Formatter) FormatStack(stack *Stack) { + // Note: On the last iteration, frames.Next() returns false, with a valid + // frame, but we ignore this frame. The last frame is a runtime frame which + // adds noise, since it's only either runtime.main or runtime.goexit. + for frame, more := stack.Next(); more; frame, more = stack.Next() { + sf.FormatFrame(frame) + } +} + +// FormatFrame formats the given frame. +func (sf *Formatter) FormatFrame(frame runtime.Frame) { + if sf.nonEmpty { + sf.b.AppendByte('\n') + } + sf.nonEmpty = true + sf.b.AppendString(frame.Function) + sf.b.AppendByte('\n') + sf.b.AppendByte('\t') + sf.b.AppendString(frame.File) + sf.b.AppendByte(':') + sf.b.AppendInt(int64(frame.Line)) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack_test.go new file mode 100644 index 0000000..195eeae --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/stacktrace/stack_test.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package stacktrace + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTake(t *testing.T) { + trace := Take(0) + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "go.uber.org/zap/internal/stacktrace.TestTake", + "Expected stacktrace to start with the test.", + ) +} + +func TestTakeWithSkip(t *testing.T) { + trace := Take(1) + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "testing.", + "Expected stacktrace to start with the test runner (skipping our own frame).", + ) +} + +func TestTakeWithSkipInnerFunc(t *testing.T) { + var trace string + func() { + trace = Take(2) + }() + lines := strings.Split(trace, "\n") + require.NotEmpty(t, lines, "Expected stacktrace to have at least one frame.") + assert.Contains( + t, + lines[0], + "testing.", + "Expected stacktrace to start with the test function (skipping the test function).", + ) +} + +func TestTakeDeepStack(t *testing.T) { + const ( + N = 500 + withStackDepthName = "go.uber.org/zap/internal/stacktrace.withStackDepth" + ) + withStackDepth(N, func() { + trace := Take(0) + for found := 0; found < N; found++ { + i := strings.Index(trace, withStackDepthName) + if i < 0 { + t.Fatalf(`expected %v occurrences of %q, found %d`, + N, withStackDepthName, found) + } + trace = trace[i+len(withStackDepthName):] + } + }) +} + +func BenchmarkTake(b *testing.B) { + for i := 0; i < b.N; i++ { + Take(0) + } +} + +func withStackDepth(depth int, f func()) { + var recurse func(rune) rune + recurse = func(r rune) rune { + if r > 0 { + bytes.Map(recurse, []byte(string([]rune{r - 1}))) + } else { + f() + } + return 0 + } + recurse(rune(depth)) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock.go new file mode 100644 index 0000000..47b0b7f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock.go @@ -0,0 +1,153 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "sort" + "sync" + "time" +) + +// MockClock is a fake source of time. +// It implements standard time operations, +// but allows the user to control the passage of time. +// +// Use the [Add] method to progress time. +type MockClock struct { + mu sync.RWMutex + now time.Time + + // The MockClock works by maintaining a list of waiters. + // Each waiter knows the time at which it should be resolved. + // When the clock advances, all waiters that are in range are resolved + // in chronological order. + waiters []waiter +} + +// NewMockClock builds a new mock clock +// using the current actual time as the initial time. +func NewMockClock() *MockClock { + return &MockClock{ + now: time.Now(), + } +} + +// Now reports the current time. +func (c *MockClock) Now() time.Time { + c.mu.RLock() + defer c.mu.RUnlock() + return c.now +} + +// NewTicker returns a time.Ticker that ticks at the specified frequency. +// +// As with [time.NewTicker], +// the ticker will drop ticks if the receiver is slow, +// and the channel is never closed. +// +// Calling Stop on the returned ticker is a no-op. +// The ticker only runs when the clock is advanced. +func (c *MockClock) NewTicker(d time.Duration) *time.Ticker { + ch := make(chan time.Time, 1) + + var tick func(time.Time) + tick = func(now time.Time) { + next := now.Add(d) + c.runAt(next, func() { + defer tick(next) + + select { + case ch <- next: + // ok + default: + // The receiver is slow. + // Drop the tick and continue. + } + }) + } + tick(c.Now()) + + return &time.Ticker{C: ch} +} + +// runAt schedules the given function to be run at the given time. +// The function runs without a lock held, so it may schedule more work. +func (c *MockClock) runAt(t time.Time, fn func()) { + c.mu.Lock() + defer c.mu.Unlock() + c.waiters = append(c.waiters, waiter{until: t, fn: fn}) +} + +type waiter struct { + until time.Time + fn func() +} + +// Add progresses time by the given duration. +// Other operations waiting for the time to advance +// will be resolved if they are within range. +// +// Side effects of operations waiting for the time to advance +// will take effect on a best-effort basis. +// Avoid racing with operations that have side effects. +// +// Panics if the duration is negative. +func (c *MockClock) Add(d time.Duration) { + if d < 0 { + panic("cannot add negative duration") + } + + c.mu.Lock() + defer c.mu.Unlock() + + sort.Slice(c.waiters, func(i, j int) bool { + return c.waiters[i].until.Before(c.waiters[j].until) + }) + + newTime := c.now.Add(d) + // newTime won't be recorded until the end of this method. + // This ensures that any waiters that are resolved + // are resolved at the time they were expecting. + + for len(c.waiters) > 0 { + w := c.waiters[0] + if w.until.After(newTime) { + break + } + c.waiters[0] = waiter{} // avoid memory leak + c.waiters = c.waiters[1:] + + // The waiter is within range. + // Travel to the time of the waiter and resolve it. + c.now = w.until + + // The waiter may schedule more work + // so we must release the lock. + c.mu.Unlock() + w.fn() + // Sleeping here is necessary to let the side effects of waiters + // take effect before we continue. + time.Sleep(1 * time.Millisecond) + c.mu.Lock() + } + + c.now = newTime +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock_test.go new file mode 100644 index 0000000..6db724b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/clock_test.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestMockClock_NewTicker(t *testing.T) { + var n atomic.Int32 + clock := NewMockClock() + + done := make(chan struct{}) + defer func() { <-done }() // wait for end + + quit := make(chan struct{}) + // Create a channel to increment every microsecond. + go func(ticker *time.Ticker) { + defer close(done) + for { + select { + case <-quit: + ticker.Stop() + return + case <-ticker.C: + n.Add(1) + } + } + }(clock.NewTicker(time.Microsecond)) + + // Move clock forward. + clock.Add(2 * time.Microsecond) + assert.Equal(t, int32(2), n.Load()) + close(quit) +} + +func TestMockClock_NewTicker_slowConsumer(t *testing.T) { + clock := NewMockClock() + + ticker := clock.NewTicker(time.Microsecond) + defer ticker.Stop() + + // Two ticks, only one consumed. + clock.Add(2 * time.Microsecond) + <-ticker.C + + select { + case <-ticker.C: + t.Fatal("unexpected tick") + default: + // ok + } +} + +func TestMockClock_Add_negative(t *testing.T) { + clock := NewMockClock() + assert.Panics(t, func() { clock.Add(-1) }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/doc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/doc.go new file mode 100644 index 0000000..cd4b98c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package ztest provides low-level helpers for testing log output. These +// utilities are helpful in zap's own unit tests, but any assertions using +// them are strongly coupled to a single encoding. +package ztest // import "go.uber.org/zap/internal/ztest" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/timeout.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/timeout.go new file mode 100644 index 0000000..e4222f9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/timeout.go @@ -0,0 +1,59 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "log" + "os" + "strconv" + "time" +) + +var _timeoutScale = 1.0 + +// Timeout scales the provided duration by $TEST_TIMEOUT_SCALE. +func Timeout(base time.Duration) time.Duration { + return time.Duration(float64(base) * _timeoutScale) +} + +// Sleep scales the sleep duration by $TEST_TIMEOUT_SCALE. +func Sleep(base time.Duration) { + time.Sleep(Timeout(base)) +} + +// Initialize checks the environment and alters the timeout scale accordingly. +// It returns a function to undo the scaling. +func Initialize(factor string) func() { + fv, err := strconv.ParseFloat(factor, 64) + if err != nil { + panic(err) + } + original := _timeoutScale + _timeoutScale = fv + return func() { _timeoutScale = original } +} + +func init() { + if v := os.Getenv("TEST_TIMEOUT_SCALE"); v != "" { + Initialize(v) + log.Printf("Scaling timeouts by %vx.\n", _timeoutScale) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/writer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/writer.go new file mode 100644 index 0000000..f54d856 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/internal/ztest/writer.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package ztest + +import ( + "bytes" + "errors" + "io" + "strings" +) + +// A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. +type Syncer struct { + err error + called bool +} + +// SetError sets the error that the Sync method will return. +func (s *Syncer) SetError(err error) { + s.err = err +} + +// Sync records that it was called, then returns the user-supplied error (if +// any). +func (s *Syncer) Sync() error { + s.called = true + return s.err +} + +// Called reports whether the Sync method was called. +func (s *Syncer) Called() bool { + return s.called +} + +// A Discarder sends all writes to io.Discard. +type Discarder struct{ Syncer } + +// Write implements io.Writer. +func (d *Discarder) Write(b []byte) (int, error) { + return io.Discard.Write(b) +} + +// FailWriter is a WriteSyncer that always returns an error on writes. +type FailWriter struct{ Syncer } + +// Write implements io.Writer. +func (w FailWriter) Write(b []byte) (int, error) { + return len(b), errors.New("failed") +} + +// ShortWriter is a WriteSyncer whose write method never fails, but +// nevertheless fails to the last byte of the input. +type ShortWriter struct{ Syncer } + +// Write implements io.Writer. +func (w ShortWriter) Write(b []byte) (int, error) { + return len(b) - 1, nil +} + +// Buffer is an implementation of zapcore.WriteSyncer that sends all writes to +// a bytes.Buffer. It has convenience methods to split the accumulated buffer +// on newlines. +type Buffer struct { + bytes.Buffer + Syncer +} + +// Lines returns the current buffer contents, split on newlines. +func (b *Buffer) Lines() []string { + output := strings.Split(b.String(), "\n") + return output[:len(output)-1] +} + +// Stripped returns the current buffer contents with the last trailing newline +// stripped. +func (b *Buffer) Stripped() string { + return strings.TrimRight(b.String(), "\n") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/leak_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/leak_test.go new file mode 100644 index 0000000..474ed2f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/leak_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level.go new file mode 100644 index 0000000..155b208 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level.go @@ -0,0 +1,153 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync/atomic" + + "go.uber.org/zap/internal" + "go.uber.org/zap/zapcore" +) + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel = zapcore.DebugLevel + // InfoLevel is the default logging priority. + InfoLevel = zapcore.InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel = zapcore.WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel = zapcore.ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel = zapcore.DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel = zapcore.PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel = zapcore.FatalLevel +) + +// LevelEnablerFunc is a convenient way to implement zapcore.LevelEnabler with +// an anonymous function. +// +// It's particularly useful when splitting log output between different +// outputs (e.g., standard error and standard out). For sample code, see the +// package-level AdvancedConfiguration example. +type LevelEnablerFunc func(zapcore.Level) bool + +// Enabled calls the wrapped function. +func (f LevelEnablerFunc) Enabled(lvl zapcore.Level) bool { return f(lvl) } + +// An AtomicLevel is an atomically changeable, dynamic logging level. It lets +// you safely change the log level of a tree of loggers (the root logger and +// any children created by adding context) at runtime. +// +// The AtomicLevel itself is an http.Handler that serves a JSON endpoint to +// alter its level. +// +// AtomicLevels must be created with the NewAtomicLevel constructor to allocate +// their internal atomic pointer. +type AtomicLevel struct { + l *atomic.Int32 +} + +var _ internal.LeveledEnabler = AtomicLevel{} + +// NewAtomicLevel creates an AtomicLevel with InfoLevel and above logging +// enabled. +func NewAtomicLevel() AtomicLevel { + lvl := AtomicLevel{l: new(atomic.Int32)} + lvl.l.Store(int32(InfoLevel)) + return lvl +} + +// NewAtomicLevelAt is a convenience function that creates an AtomicLevel +// and then calls SetLevel with the given level. +func NewAtomicLevelAt(l zapcore.Level) AtomicLevel { + a := NewAtomicLevel() + a.SetLevel(l) + return a +} + +// ParseAtomicLevel parses an AtomicLevel based on a lowercase or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +func ParseAtomicLevel(text string) (AtomicLevel, error) { + a := NewAtomicLevel() + l, err := zapcore.ParseLevel(text) + if err != nil { + return a, err + } + + a.SetLevel(l) + return a, nil +} + +// Enabled implements the zapcore.LevelEnabler interface, which allows the +// AtomicLevel to be used in place of traditional static levels. +func (lvl AtomicLevel) Enabled(l zapcore.Level) bool { + return lvl.Level().Enabled(l) +} + +// Level returns the minimum enabled log level. +func (lvl AtomicLevel) Level() zapcore.Level { + return zapcore.Level(int8(lvl.l.Load())) +} + +// SetLevel alters the logging level. +func (lvl AtomicLevel) SetLevel(l zapcore.Level) { + lvl.l.Store(int32(l)) +} + +// String returns the string representation of the underlying Level. +func (lvl AtomicLevel) String() string { + return lvl.Level().String() +} + +// UnmarshalText unmarshals the text to an AtomicLevel. It uses the same text +// representations as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl *AtomicLevel) UnmarshalText(text []byte) error { + if lvl.l == nil { + lvl.l = &atomic.Int32{} + } + + var l zapcore.Level + if err := l.UnmarshalText(text); err != nil { + return err + } + + lvl.SetLevel(l) + return nil +} + +// MarshalText marshals the AtomicLevel to a byte slice. It uses the same +// text representation as the static zapcore.Levels ("debug", "info", "warn", +// "error", "dpanic", "panic", and "fatal"). +func (lvl AtomicLevel) MarshalText() (text []byte, err error) { + return lvl.Level().MarshalText() +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level_test.go new file mode 100644 index 0000000..db7391d --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/level_test.go @@ -0,0 +1,140 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "sync" + "testing" + + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLevelEnablerFunc(t *testing.T) { + enab := LevelEnablerFunc(func(l zapcore.Level) bool { return l == zapcore.InfoLevel }) + tests := []struct { + level zapcore.Level + enabled bool + }{ + {DebugLevel, false}, + {InfoLevel, true}, + {WarnLevel, false}, + {ErrorLevel, false}, + {DPanicLevel, false}, + {PanicLevel, false}, + {FatalLevel, false}, + } + for _, tt := range tests { + assert.Equal(t, tt.enabled, enab.Enabled(tt.level), "Unexpected result applying LevelEnablerFunc to %s", tt.level) + } +} + +func TestNewAtomicLevel(t *testing.T) { + lvl := NewAtomicLevel() + assert.Equal(t, InfoLevel, lvl.Level(), "Unexpected initial level.") + lvl.SetLevel(ErrorLevel) + assert.Equal(t, ErrorLevel, lvl.Level(), "Unexpected level after SetLevel.") + lvl = NewAtomicLevelAt(WarnLevel) + assert.Equal(t, WarnLevel, lvl.Level(), "Unexpected level after SetLevel.") +} + +func TestParseAtomicLevel(t *testing.T) { + tests := []struct { + text string + level AtomicLevel + err string + }{ + {"info", NewAtomicLevel(), ""}, + {"DEBUG", NewAtomicLevelAt(DebugLevel), ""}, + {"FOO", NewAtomicLevel(), `unrecognized level: "FOO"`}, + } + + for _, tt := range tests { + parsedAtomicLevel, err := ParseAtomicLevel(tt.text) + if len(tt.err) == 0 { + require.NoError(t, err) + assert.Equal(t, tt.level, parsedAtomicLevel) + } else { + assert.ErrorContains(t, err, tt.err) + } + } +} + +func TestAtomicLevelMutation(t *testing.T) { + lvl := NewAtomicLevel() + lvl.SetLevel(WarnLevel) + // Trigger races for non-atomic level mutations. + proceed := make(chan struct{}) + wg := &sync.WaitGroup{} + runConcurrently(10, 100, wg, func() { + <-proceed + assert.Equal(t, WarnLevel, lvl.Level()) + }) + runConcurrently(10, 100, wg, func() { + <-proceed + lvl.SetLevel(WarnLevel) + }) + close(proceed) + wg.Wait() +} + +func TestAtomicLevelText(t *testing.T) { + tests := []struct { + text string + expect zapcore.Level + err bool + }{ + {"debug", DebugLevel, false}, + {"info", InfoLevel, false}, + {"", InfoLevel, false}, + {"warn", WarnLevel, false}, + {"error", ErrorLevel, false}, + {"dpanic", DPanicLevel, false}, + {"panic", PanicLevel, false}, + {"fatal", FatalLevel, false}, + {"foobar", InfoLevel, true}, + } + + for _, tt := range tests { + var lvl AtomicLevel + // Test both initial unmarshaling and overwriting existing value. + for i := 0; i < 2; i++ { + if tt.err { + assert.Error(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to fail.", tt.text) + } else { + assert.NoError(t, lvl.UnmarshalText([]byte(tt.text)), "Expected unmarshaling %q to succeed.", tt.text) + } + assert.Equal(t, tt.expect, lvl.Level(), "Unexpected level after unmarshaling.") + lvl.SetLevel(InfoLevel) + } + + // Test marshalling + if tt.text != "" && !tt.err { + lvl.SetLevel(tt.expect) + marshaled, err := lvl.MarshalText() + assert.NoError(t, err, `Unexpected error marshalling level "%v" to text.`, tt.expect) + assert.Equal(t, tt.text, string(marshaled), "Expected marshaled text to match") + assert.Equal(t, tt.text, lvl.String(), "Expected Stringer call to match") + } + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger.go new file mode 100644 index 0000000..c4d3003 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger.go @@ -0,0 +1,435 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "io" + "os" + "strings" + + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/stacktrace" + "go.uber.org/zap/zapcore" +) + +// A Logger provides fast, leveled, structured logging. All methods are safe +// for concurrent use. +// +// The Logger is designed for contexts in which every microsecond and every +// allocation matters, so its API intentionally favors performance and type +// safety over brevity. For most applications, the SugaredLogger strikes a +// better balance between performance and ergonomics. +type Logger struct { + core zapcore.Core + + development bool + addCaller bool + onPanic zapcore.CheckWriteHook // default is WriteThenPanic + onFatal zapcore.CheckWriteHook // default is WriteThenFatal + + name string + errorOutput zapcore.WriteSyncer + + addStack zapcore.LevelEnabler + + callerSkip int + + clock zapcore.Clock +} + +// New constructs a new Logger from the provided zapcore.Core and Options. If +// the passed zapcore.Core is nil, it falls back to using a no-op +// implementation. +// +// This is the most flexible way to construct a Logger, but also the most +// verbose. For typical use cases, the highly-opinionated presets +// (NewProduction, NewDevelopment, and NewExample) or the Config struct are +// more convenient. +// +// For sample code, see the package-level AdvancedConfiguration example. +func New(core zapcore.Core, options ...Option) *Logger { + if core == nil { + return NewNop() + } + log := &Logger{ + core: core, + errorOutput: zapcore.Lock(os.Stderr), + addStack: zapcore.FatalLevel + 1, + clock: zapcore.DefaultClock, + } + return log.WithOptions(options...) +} + +// NewNop returns a no-op Logger. It never writes out logs or internal errors, +// and it never runs user-defined hooks. +// +// Using WithOptions to replace the Core or error output of a no-op Logger can +// re-enable logging. +func NewNop() *Logger { + return &Logger{ + core: zapcore.NewNopCore(), + errorOutput: zapcore.AddSync(io.Discard), + addStack: zapcore.FatalLevel + 1, + clock: zapcore.DefaultClock, + } +} + +// NewProduction builds a sensible production Logger that writes InfoLevel and +// above logs to standard error as JSON. +// +// It's a shortcut for NewProductionConfig().Build(...Option). +func NewProduction(options ...Option) (*Logger, error) { + return NewProductionConfig().Build(options...) +} + +// NewDevelopment builds a development Logger that writes DebugLevel and above +// logs to standard error in a human-friendly format. +// +// It's a shortcut for NewDevelopmentConfig().Build(...Option). +func NewDevelopment(options ...Option) (*Logger, error) { + return NewDevelopmentConfig().Build(options...) +} + +// Must is a helper that wraps a call to a function returning (*Logger, error) +// and panics if the error is non-nil. It is intended for use in variable +// initialization such as: +// +// var logger = zap.Must(zap.NewProduction()) +func Must(logger *Logger, err error) *Logger { + if err != nil { + panic(err) + } + + return logger +} + +// NewExample builds a Logger that's designed for use in zap's testable +// examples. It writes DebugLevel and above logs to standard out as JSON, but +// omits the timestamp and calling function to keep example output +// short and deterministic. +func NewExample(options ...Option) *Logger { + encoderCfg := zapcore.EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "logger", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + } + core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel) + return New(core).WithOptions(options...) +} + +// Sugar wraps the Logger to provide a more ergonomic, but slightly slower, +// API. Sugaring a Logger is quite inexpensive, so it's reasonable for a +// single application to use both Loggers and SugaredLoggers, converting +// between them on the boundaries of performance-sensitive code. +func (log *Logger) Sugar() *SugaredLogger { + core := log.clone() + core.callerSkip += 2 + return &SugaredLogger{core} +} + +// Named adds a new path segment to the logger's name. Segments are joined by +// periods. By default, Loggers are unnamed. +func (log *Logger) Named(s string) *Logger { + if s == "" { + return log + } + l := log.clone() + if log.name == "" { + l.name = s + } else { + l.name = strings.Join([]string{l.name, s}, ".") + } + return l +} + +// WithOptions clones the current Logger, applies the supplied Options, and +// returns the resulting Logger. It's safe to use concurrently. +func (log *Logger) WithOptions(opts ...Option) *Logger { + c := log.clone() + for _, opt := range opts { + opt.apply(c) + } + return c +} + +// With creates a child logger and adds structured context to it. Fields added +// to the child don't affect the parent, and vice versa. Any fields that +// require evaluation (such as Objects) are evaluated upon invocation of With. +func (log *Logger) With(fields ...Field) *Logger { + if len(fields) == 0 { + return log + } + l := log.clone() + l.core = l.core.With(fields) + return l +} + +// WithLazy creates a child logger and adds structured context to it lazily. +// +// The fields are evaluated only if the logger is further chained with [With] +// or is written to with any of the log level methods. +// Until that occurs, the logger may retain references to objects inside the fields, +// and logging will reflect the state of an object at the time of logging, +// not the time of WithLazy(). +// +// WithLazy provides a worthwhile performance optimization for contextual loggers +// when the likelihood of using the child logger is low, +// such as error paths and rarely taken branches. +// +// Similar to [With], fields added to the child don't affect the parent, and vice versa. +func (log *Logger) WithLazy(fields ...Field) *Logger { + if len(fields) == 0 { + return log + } + return log.WithOptions(WrapCore(func(core zapcore.Core) zapcore.Core { + return zapcore.NewLazyWith(core, fields) + })) +} + +// Level reports the minimum enabled level for this logger. +// +// For NopLoggers, this is [zapcore.InvalidLevel]. +func (log *Logger) Level() zapcore.Level { + return zapcore.LevelOf(log.core) +} + +// Check returns a CheckedEntry if logging a message at the specified level +// is enabled. It's a completely optional optimization; in high-performance +// applications, Check can help avoid allocating a slice to hold fields. +func (log *Logger) Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + return log.check(lvl, msg) +} + +// Log logs a message at the specified level. The message includes any fields +// passed at the log site, as well as any fields accumulated on the logger. +// Any Fields that require evaluation (such as Objects) are evaluated upon +// invocation of Log. +func (log *Logger) Log(lvl zapcore.Level, msg string, fields ...Field) { + if ce := log.check(lvl, msg); ce != nil { + ce.Write(fields...) + } +} + +// Debug logs a message at DebugLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Debug(msg string, fields ...Field) { + if ce := log.check(DebugLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Info logs a message at InfoLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Info(msg string, fields ...Field) { + if ce := log.check(InfoLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Warn logs a message at WarnLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Warn(msg string, fields ...Field) { + if ce := log.check(WarnLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Error logs a message at ErrorLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +func (log *Logger) Error(msg string, fields ...Field) { + if ce := log.check(ErrorLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// DPanic logs a message at DPanicLevel. The message includes any fields +// passed at the log site, as well as any fields accumulated on the logger. +// +// If the logger is in development mode, it then panics (DPanic means +// "development panic"). This is useful for catching errors that are +// recoverable, but shouldn't ever happen. +func (log *Logger) DPanic(msg string, fields ...Field) { + if ce := log.check(DPanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Panic logs a message at PanicLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then panics, even if logging at PanicLevel is disabled. +func (log *Logger) Panic(msg string, fields ...Field) { + if ce := log.check(PanicLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Fatal logs a message at FatalLevel. The message includes any fields passed +// at the log site, as well as any fields accumulated on the logger. +// +// The logger then calls os.Exit(1), even if logging at FatalLevel is +// disabled. +func (log *Logger) Fatal(msg string, fields ...Field) { + if ce := log.check(FatalLevel, msg); ce != nil { + ce.Write(fields...) + } +} + +// Sync calls the underlying Core's Sync method, flushing any buffered log +// entries. Applications should take care to call Sync before exiting. +func (log *Logger) Sync() error { + return log.core.Sync() +} + +// Core returns the Logger's underlying zapcore.Core. +func (log *Logger) Core() zapcore.Core { + return log.core +} + +// Name returns the Logger's underlying name, +// or an empty string if the logger is unnamed. +func (log *Logger) Name() string { + return log.name +} + +func (log *Logger) clone() *Logger { + clone := *log + return &clone +} + +func (log *Logger) check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry { + // Logger.check must always be called directly by a method in the + // Logger interface (e.g., Check, Info, Fatal). + // This skips Logger.check and the Info/Fatal/Check/etc. method that + // called it. + const callerSkipOffset = 2 + + // Check the level first to reduce the cost of disabled log calls. + // Since Panic and higher may exit, we skip the optimization for those levels. + if lvl < zapcore.DPanicLevel && !log.core.Enabled(lvl) { + return nil + } + + // Create basic checked entry thru the core; this will be non-nil if the + // log message will actually be written somewhere. + ent := zapcore.Entry{ + LoggerName: log.name, + Time: log.clock.Now(), + Level: lvl, + Message: msg, + } + ce := log.core.Check(ent, nil) + willWrite := ce != nil + + // Set up any required terminal behavior. + switch ent.Level { + case zapcore.PanicLevel: + ce = ce.After(ent, terminalHookOverride(zapcore.WriteThenPanic, log.onPanic)) + case zapcore.FatalLevel: + ce = ce.After(ent, terminalHookOverride(zapcore.WriteThenFatal, log.onFatal)) + case zapcore.DPanicLevel: + if log.development { + ce = ce.After(ent, terminalHookOverride(zapcore.WriteThenPanic, log.onPanic)) + } + } + + // Only do further annotation if we're going to write this message; checked + // entries that exist only for terminal behavior don't benefit from + // annotation. + if !willWrite { + return ce + } + + // Thread the error output through to the CheckedEntry. + ce.ErrorOutput = log.errorOutput + + addStack := log.addStack.Enabled(ce.Level) + if !log.addCaller && !addStack { + return ce + } + + // Adding the caller or stack trace requires capturing the callers of + // this function. We'll share information between these two. + stackDepth := stacktrace.First + if addStack { + stackDepth = stacktrace.Full + } + stack := stacktrace.Capture(log.callerSkip+callerSkipOffset, stackDepth) + defer stack.Free() + + if stack.Count() == 0 { + if log.addCaller { + fmt.Fprintf(log.errorOutput, "%v Logger.check error: failed to get caller\n", ent.Time.UTC()) + _ = log.errorOutput.Sync() + } + return ce + } + + frame, more := stack.Next() + + if log.addCaller { + ce.Caller = zapcore.EntryCaller{ + Defined: frame.PC != 0, + PC: frame.PC, + File: frame.File, + Line: frame.Line, + Function: frame.Function, + } + } + + if addStack { + buffer := bufferpool.Get() + defer buffer.Free() + + stackfmt := stacktrace.NewFormatter(buffer) + + // We've already extracted the first frame, so format that + // separately and defer to stackfmt for the rest. + stackfmt.FormatFrame(frame) + if more { + stackfmt.FormatStack(stack) + } + ce.Stack = buffer.String() + } + + return ce +} + +func terminalHookOverride(defaultHook, override zapcore.CheckWriteHook) zapcore.CheckWriteHook { + // A nil or WriteThenNoop hook will lead to continued execution after + // a Panic or Fatal log entry, which is unexpected. For example, + // + // f, err := os.Open(..) + // if err != nil { + // log.Fatal("cannot open", zap.Error(err)) + // } + // fmt.Println(f.Name()) + // + // The f.Name() will panic if we continue execution after the log.Fatal. + if override == nil || override == zapcore.WriteThenNoop { + return defaultHook + } + return override +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_bench_test.go new file mode 100644 index 0000000..9d41298 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_bench_test.go @@ -0,0 +1,361 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "runtime" + "strconv" + "sync" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" +) + +type user struct { + Name string + Email string + CreatedAt time.Time +} + +func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddString("name", u.Name) + enc.AddString("email", u.Email) + enc.AddInt64("created_at", u.CreatedAt.UnixNano()) + return nil +} + +var _jane = &user{ + Name: "Jane Doe", + Email: "jane@test.com", + CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC), +} + +func withBenchedLogger(b *testing.B, f func(*Logger)) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + DebugLevel, + )) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + f(logger) + } + }) +} + +func BenchmarkNoContext(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("No context.") + }) +} + +func BenchmarkBoolField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Boolean.", Bool("foo", true)) + }) +} + +func BenchmarkByteStringField(b *testing.B) { + val := []byte("bar") + withBenchedLogger(b, func(log *Logger) { + log.Info("ByteString.", ByteString("foo", val)) + }) +} + +func BenchmarkFloat64Field(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Floating point.", Float64("foo", 3.14)) + }) +} + +func BenchmarkIntField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Integer.", Int("foo", 42)) + }) +} + +func BenchmarkInt64Field(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("64-bit integer.", Int64("foo", 42)) + }) +} + +func BenchmarkStringField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Strings.", String("foo", "bar")) + }) +} + +func BenchmarkStringerField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Level.", Stringer("foo", InfoLevel)) + }) +} + +func BenchmarkTimeField(b *testing.B) { + t := time.Unix(0, 0) + withBenchedLogger(b, func(log *Logger) { + log.Info("Time.", Time("foo", t)) + }) +} + +func BenchmarkDurationField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Duration", Duration("foo", time.Second)) + }) +} + +func BenchmarkErrorField(b *testing.B) { + err := errors.New("egad") + withBenchedLogger(b, func(log *Logger) { + log.Info("Error.", Error(err)) + }) +} + +func BenchmarkErrorsField(b *testing.B) { + errs := []error{ + errors.New("egad"), + errors.New("oh no"), + errors.New("dear me"), + errors.New("such fail"), + } + withBenchedLogger(b, func(log *Logger) { + log.Info("Errors.", Errors("errors", errs)) + }) +} + +func BenchmarkStackField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Error.", Stack("stacktrace")) + }) +} + +func BenchmarkObjectField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Arbitrary ObjectMarshaler.", Object("user", _jane)) + }) +} + +func BenchmarkReflectField(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Reflection-based serialization.", Reflect("user", _jane)) + }) +} + +func BenchmarkAddCallerHook(b *testing.B) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + InfoLevel, + ), + AddCaller(), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + logger.Info("Caller.") + } + }) +} + +func BenchmarkAddCallerAndStacktrace(b *testing.B) { + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + InfoLevel, + ), + AddCaller(), + AddStacktrace(WarnLevel), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + logger.Warn("Caller and stacktrace.") + } + }) +} + +func Benchmark5WithsUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).With, 5, true) +} + +// This benchmark will be used in future as a +// baseline for improving +func Benchmark5WithsNotUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).With, 5, false) +} + +func Benchmark5WithLazysUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).WithLazy, 5, true) +} + +// This benchmark will be used in future as a +// baseline for improving +func Benchmark5WithLazysNotUsed(b *testing.B) { + benchmarkWithUsed(b, (*Logger).WithLazy, 5, false) +} + +func benchmarkWithUsed(b *testing.B, withMethod func(*Logger, ...zapcore.Field) *Logger, N int, use bool) { + keys := make([]string, N) + values := make([]string, N) + for i := 0; i < N; i++ { + keys[i] = "k" + strconv.Itoa(i) + values[i] = "v" + strconv.Itoa(i) + } + + b.ResetTimer() + + withBenchedLogger(b, func(log *Logger) { + for i := 0; i < N; i++ { + log = withMethod(log, String(keys[i], values[i])) + } + if use { + log.Info("used") + return + } + runtime.KeepAlive(log) + }) +} + +func Benchmark10Fields(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("Ten fields, passed at the log site.", + Int("one", 1), + Int("two", 2), + Int("three", 3), + Int("four", 4), + Int("five", 5), + Int("six", 6), + Int("seven", 7), + Int("eight", 8), + Int("nine", 9), + Int("ten", 10), + ) + }) +} + +func Benchmark100Fields(b *testing.B) { + const batchSize = 50 + logger := New(zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + &ztest.Discarder{}, + DebugLevel, + )) + + // Don't include allocating these helper slices in the benchmark. Since + // access to them isn't synchronized, we can't run the benchmark in + // parallel. + first := make([]Field, batchSize) + second := make([]Field, batchSize) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for i := 0; i < batchSize; i++ { + // We're duplicating keys, but that doesn't affect performance. + first[i] = Int("foo", i) + second[i] = Int("foo", i+batchSize) + } + logger.With(first...).Info("Child loggers with lots of context.", second...) + } +} + +func BenchmarkAny(b *testing.B) { + key := "some-long-string-longer-than-16" + + tests := []struct { + name string + typed func() Field + anyArg any + }{ + { + name: "string", + typed: func() Field { return String(key, "yet-another-long-string") }, + anyArg: "yet-another-long-string", + }, + { + name: "stringer", + typed: func() Field { return Stringer(key, InfoLevel) }, + anyArg: InfoLevel, + }, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + b.Run("field-only", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + f := tt.typed() + runtime.KeepAlive(f) + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + f := Any(key, tt.anyArg) + runtime.KeepAlive(f) + }) + }) + }) + b.Run("log", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("", tt.typed()) + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + log.Info("", Any(key, tt.anyArg)) + }) + }) + }) + b.Run("log-go", func(b *testing.B) { + b.Run("typed", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + log.Info("", tt.typed()) + wg.Done() + }() + wg.Wait() + }) + }) + b.Run("any", func(b *testing.B) { + withBenchedLogger(b, func(log *Logger) { + var wg sync.WaitGroup + wg.Add(1) + go func() { + log.Info("", Any(key, tt.anyArg)) + wg.Done() + }() + wg.Wait() + }) + }) + }) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_test.go new file mode 100644 index 0000000..4a953b6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/logger_test.go @@ -0,0 +1,1061 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "strconv" + "sync" + "sync/atomic" + "testing" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeCountingHook() (func(zapcore.Entry) error, *atomic.Int64) { + count := &atomic.Int64{} + h := func(zapcore.Entry) error { + count.Add(1) + return nil + } + return h, count +} + +func TestLoggerAtomicLevel(t *testing.T) { + // Test that the dynamic level applies to all ancestors and descendants. + dl := NewAtomicLevel() + + withLogger(t, dl, nil, func(grandparent *Logger, _ *observer.ObservedLogs) { + parent := grandparent.With(Int("generation", 1)) + child := parent.With(Int("generation", 2)) + + tests := []struct { + setLevel zapcore.Level + testLevel zapcore.Level + enabled bool + }{ + {DebugLevel, DebugLevel, true}, + {InfoLevel, DebugLevel, false}, + {WarnLevel, PanicLevel, true}, + } + + for _, tt := range tests { + dl.SetLevel(tt.setLevel) + for _, logger := range []*Logger{grandparent, parent, child} { + if tt.enabled { + assert.NotNil( + t, + logger.Check(tt.testLevel, ""), + "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel, + ) + } else { + assert.Nil( + t, + logger.Check(tt.testLevel, ""), + "Expected level %s to be enabled after setting level %s.", tt.testLevel, tt.setLevel, + ) + } + } + } + }) +} + +func TestLoggerLevel(t *testing.T) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + core, _ := observer.New(lvl) + log := New(core) + assert.Equal(t, lvl, log.Level()) + }) + } + + t.Run("Nop", func(t *testing.T) { + assert.Equal(t, zapcore.InvalidLevel, NewNop().Level()) + }) +} + +func TestLoggerInitialFields(t *testing.T) { + fieldOpts := opts(Fields(Int("foo", 42), String("bar", "baz"))) + withLogger(t, DebugLevel, fieldOpts, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("") + assert.Equal( + t, + observer.LoggedEntry{Context: []Field{Int("foo", 42), String("bar", "baz")}}, + logs.AllUntimed()[0], + "Unexpected output with initial fields set.", + ) + }) +} + +func TestLoggerWith(t *testing.T) { + tests := []struct { + name string + initialFields []Field + withMethod func(*Logger, ...Field) *Logger + }{ + { + "regular non lazy logger", + []Field{Int("foo", 42)}, + (*Logger).With, + }, + { + "regular non lazy logger no initial fields", + []Field{}, + (*Logger).With, + }, + { + "lazy with logger", + []Field{Int("foo", 42)}, + (*Logger).WithLazy, + }, + { + "lazy with logger no initial fields", + []Field{}, + (*Logger).WithLazy, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withLogger(t, DebugLevel, opts(Fields(tt.initialFields...)), func(logger *Logger, logs *observer.ObservedLogs) { + // Child loggers should have copy-on-write semantics, so two children + // shouldn't stomp on each other's fields or affect the parent's fields. + tt.withMethod(logger).Info("") + tt.withMethod(logger, String("one", "two")).Info("") + tt.withMethod(logger, String("three", "four")).Info("") + tt.withMethod(logger, String("five", "six")).With(String("seven", "eight")).Info("") + logger.Info("") + + assert.Equal(t, []observer.LoggedEntry{ + {Context: tt.initialFields}, + {Context: append(tt.initialFields, String("one", "two"))}, + {Context: append(tt.initialFields, String("three", "four"))}, + {Context: append(tt.initialFields, String("five", "six"), String("seven", "eight"))}, + {Context: tt.initialFields}, + }, logs.AllUntimed(), "Unexpected cross-talk between child loggers.") + }) + }) + } +} + +func TestLoggerWithCaptures(t *testing.T) { + type withF func(*Logger, ...Field) *Logger + tests := []struct { + name string + withMethods []withF + wantJSON []string + }{ + { + name: "regular with captures arguments at time of With", + withMethods: []withF{(*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + }, + }, + { + name: "lazy with captures arguments at time of With or Logging", + withMethods: []withF{(*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + }, + }, + { + name: "2x With captures arguments at time of each With", + withMethods: []withF{(*Logger).With, (*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [0], + "c0": [2], + "a1": [10], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [0], + "c0": [2], + "a1": [10], + "c1": [12] + }`, + }, + }, + { + name: "2x WithLazy. Captures arguments only at logging time.", + withMethods: []withF{(*Logger).WithLazy, (*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [1], + "c0": [2], + "a1": [11], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [1], + "c0": [2], + "a1": [11], + "c1": [12] + }`, + }, + }, + { + name: "WithLazy then With", + withMethods: []withF{(*Logger).WithLazy, (*Logger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [1], + "c0": [2], + "a1": [10], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [1], + "c0": [2], + "a1": [10], + "c1": [12] + }`, + }, + }, + { + name: "With then WithLazy", + withMethods: []withF{(*Logger).With, (*Logger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + `{ + "m": "hello 1", + "a0": [0], + "c0": [2], + "a1": [11], + "b1": [11] + }`, + `{ + "m": "world 1", + "a0": [0], + "c0": [2], + "a1": [11], + "c1": [12] + }`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "m", + }) + + var bs ztest.Buffer + logger := New(zapcore.NewCore(enc, &bs, DebugLevel)) + + for i, withMethod := range tt.withMethods { + + iStr := strconv.Itoa(i) + x := 10 * i + arr := zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + enc.AppendInt(x) + return nil + }) + + // Demonstrate the arguments are captured when With() and Info() are invoked. + logger = withMethod(logger, Array("a"+iStr, arr)) + x++ + logger.Info(fmt.Sprintf("hello %d", i), Array("b"+iStr, arr)) + x++ + logger = withMethod(logger, Array("c"+iStr, arr)) + logger.Info(fmt.Sprintf("world %d", i)) + } + + if lines := bs.Lines(); assert.Len(t, lines, len(tt.wantJSON)) { + for i, want := range tt.wantJSON { + assert.JSONEq(t, want, lines[i], "Unexpected output from the %d'th log.", i) + } + } + }) + } +} + +func TestLoggerLogPanic(t *testing.T) { + for _, tt := range []struct { + do func(*Logger) + should bool + expected string + }{ + {func(logger *Logger) { logger.Check(PanicLevel, "foo").Write() }, true, "foo"}, + {func(logger *Logger) { logger.Log(PanicLevel, "bar") }, true, "bar"}, + {func(logger *Logger) { logger.Panic("baz") }, true, "baz"}, + } { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + if tt.should { + assert.Panics(t, func() { tt.do(logger) }, "Expected panic") + } else { + assert.NotPanics(t, func() { tt.do(logger) }, "Expected no panic") + } + + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Message: tt.expected, Level: PanicLevel}, + output[0].Entry, + "Unexpected output from panic-level Log.", + ) + }) + } +} + +func TestLoggerLogFatal(t *testing.T) { + for _, tt := range []struct { + do func(*Logger) + expected string + }{ + {func(logger *Logger) { logger.Check(FatalLevel, "foo").Write() }, "foo"}, + {func(logger *Logger) { logger.Log(FatalLevel, "bar") }, "bar"}, + {func(logger *Logger) { logger.Fatal("baz") }, "baz"}, + } { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { + tt.do(logger) + }) + assert.True(t, stub.Exited, "Expected Fatal logger call to terminate process.") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[0].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Message: tt.expected, Level: FatalLevel}, + output[0].Entry, + "Unexpected output from fatal-level Log.", + ) + }) + } +} + +func TestLoggerLeveledMethods(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + tests := []struct { + method func(string, ...Field) + expectedLevel zapcore.Level + }{ + {logger.Debug, DebugLevel}, + {logger.Info, InfoLevel}, + {logger.Warn, WarnLevel}, + {logger.Error, ErrorLevel}, + {logger.DPanic, DPanicLevel}, + } + for i, tt := range tests { + tt.method("") + output := logs.AllUntimed() + assert.Equal(t, i+1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Level: tt.expectedLevel}, + output[i].Entry, + "Unexpected output from %s-level logger method.", tt.expectedLevel) + } + }) +} + +func TestLoggerLogLevels(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + } + for i, level := range levels { + logger.Log(level, "") + output := logs.AllUntimed() + assert.Equal(t, i+1, len(output), "Unexpected number of logs.") + assert.Equal(t, 0, len(output[i].Context), "Unexpected context on first log.") + assert.Equal( + t, + zapcore.Entry{Level: level}, + output[i].Entry, + "Unexpected output from %s-level logger method.", level) + } + }) +} + +func TestLoggerAlwaysPanics(t *testing.T) { + // Users can disable writing out panic-level logs, but calls to logger.Panic() + // should still call panic(). + withLogger(t, FatalLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + msg := "Even if output is disabled, logger.Panic should always panic." + assert.Panics(t, func() { logger.Panic("foo") }, msg) + assert.Panics(t, func() { logger.Log(PanicLevel, "foo") }, msg) + assert.Panics(t, func() { + if ce := logger.Check(PanicLevel, "foo"); ce != nil { + ce.Write() + } + }, msg) + assert.Equal(t, 0, logs.Len(), "Panics shouldn't be written out if PanicLevel is disabled.") + }) +} + +func TestLoggerAlwaysFatals(t *testing.T) { + // Users can disable writing out fatal-level logs, but calls to logger.Fatal() + // should still terminate the process. + withLogger(t, FatalLevel+1, nil, func(logger *Logger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { logger.Fatal("") }) + assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.") + + stub = exit.WithStub(func() { logger.Log(FatalLevel, "") }) + assert.True(t, stub.Exited, "Expected calls to logger.Fatal to terminate process.") + + stub = exit.WithStub(func() { + if ce := logger.Check(FatalLevel, ""); ce != nil { + ce.Write() + } + }) + assert.True(t, stub.Exited, "Expected calls to logger.Check(FatalLevel, ...) to terminate process.") + + assert.Equal(t, 0, logs.Len(), "Shouldn't write out logs when fatal-level logging is disabled.") + }) +} + +func TestLoggerDPanic(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + assert.NotPanics(t, func() { logger.DPanic("") }) + assert.Equal( + t, + []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, + logs.AllUntimed(), + "Unexpected log output from DPanic in production mode.", + ) + }) + withLogger(t, DebugLevel, opts(Development()), func(logger *Logger, logs *observer.ObservedLogs) { + assert.Panics(t, func() { logger.DPanic("") }) + assert.Equal( + t, + []observer.LoggedEntry{{Entry: zapcore.Entry{Level: DPanicLevel}, Context: []Field{}}}, + logs.AllUntimed(), + "Unexpected log output from DPanic in development mode.", + ) + }) +} + +func TestLoggerNoOpsDisabledLevels(t *testing.T) { + withLogger(t, WarnLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("silence!") + assert.Equal( + t, + []observer.LoggedEntry{}, + logs.AllUntimed(), + "Expected logging at a disabled level to produce no output.", + ) + }) +} + +func TestLoggerNames(t *testing.T) { + tests := []struct { + names []string + expected string + }{ + {nil, ""}, + {[]string{""}, ""}, + {[]string{"foo"}, "foo"}, + {[]string{"foo", ""}, "foo"}, + {[]string{"foo", "bar"}, "foo.bar"}, + {[]string{"foo.bar", "baz"}, "foo.bar.baz"}, + // Garbage in, garbage out. + {[]string{"foo.", "bar"}, "foo..bar"}, + {[]string{"foo", ".bar"}, "foo..bar"}, + {[]string{"foo.", ".bar"}, "foo...bar"}, + } + + for _, tt := range tests { + withLogger(t, DebugLevel, nil, func(log *Logger, logs *observer.ObservedLogs) { + for _, n := range tt.names { + log = log.Named(n) + } + log.Info("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.") + assert.Equal(t, tt.expected, log.Name(), "Unexpected logger name.") + }) + withSugar(t, DebugLevel, nil, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for _, n := range tt.names { + log = log.Named(n) + } + log.Infow("") + require.Equal(t, 1, logs.Len(), "Expected only one log entry to be written.") + assert.Equal(t, tt.expected, logs.AllUntimed()[0].LoggerName, "Unexpected logger name from entry.") + assert.Equal(t, tt.expected, log.base.Name(), "Unexpected logger name.") + }) + } +} + +func TestLoggerWriteFailure(t *testing.T) { + errSink := &ztest.Buffer{} + logger := New( + zapcore.NewCore( + zapcore.NewJSONEncoder(NewProductionConfig().EncoderConfig), + zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})), + DebugLevel, + ), + ErrorOutput(errSink), + ) + + logger.Info("foo") + // Should log the error. + assert.Regexp(t, `write error: failed`, errSink.Stripped(), "Expected to log the error to the error output.") + assert.True(t, errSink.Called(), "Expected logging an internal error to call Sync the error sink.") +} + +func TestLoggerSync(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, _ *observer.ObservedLogs) { + assert.NoError(t, logger.Sync(), "Expected syncing a test logger to succeed.") + assert.NoError(t, logger.Sugar().Sync(), "Expected syncing a sugared logger to succeed.") + }) +} + +func TestLoggerSyncFail(t *testing.T) { + noSync := &ztest.Buffer{} + err := errors.New("fail") + noSync.SetError(err) + logger := New(zapcore.NewCore( + zapcore.NewJSONEncoder(zapcore.EncoderConfig{}), + noSync, + DebugLevel, + )) + assert.Equal(t, err, logger.Sync(), "Expected Logger.Sync to propagate errors.") + assert.Equal(t, err, logger.Sugar().Sync(), "Expected SugaredLogger.Sync to propagate errors.") +} + +func TestLoggerAddCaller(t *testing.T) { + tests := []struct { + options []Option + pat string + }{ + {opts(), `^undefined$`}, + {opts(WithCaller(false)), `^undefined$`}, + {opts(AddCaller()), `.+/logger_test.go:[\d]+$`}, + {opts(AddCaller(), WithCaller(false)), `^undefined$`}, + {opts(WithCaller(true)), `.+/logger_test.go:[\d]+$`}, + {opts(WithCaller(true), WithCaller(false)), `^undefined$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/logger_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(3)), `.+/src/runtime/.*:[\d]+$`}, + } + for _, tt := range tests { + withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) { + // Make sure that sugaring and desugaring resets caller skip properly. + logger = logger.Sugar().Desugar() + logger.Info("") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs written out.") + assert.Regexp( + t, + tt.pat, + output[0].Caller, + "Expected to find package name and file name in output.", + ) + }) + } +} + +func TestLoggerAddCallerFunction(t *testing.T) { + tests := []struct { + options []Option + loggerFunction string + sugaredFunction string + }{ + { + options: opts(), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(AddCaller()), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(AddCaller(), WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(WithCaller(true)), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(WithCaller(true), WithCaller(false)), + loggerFunction: "", + sugaredFunction: "", + }, + { + options: opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), + loggerFunction: "go.uber.org/zap.infoLog", + sugaredFunction: "go.uber.org/zap.infoLogSugared", + }, + { + options: opts(AddCaller(), AddCallerSkip(2)), + loggerFunction: "go.uber.org/zap.withLogger", + sugaredFunction: "go.uber.org/zap.withLogger", + }, + { + options: opts(AddCaller(), AddCallerSkip(2), AddCallerSkip(3)), + loggerFunction: "runtime.goexit", + sugaredFunction: "runtime.goexit", + }, + } + for _, tt := range tests { + withLogger(t, DebugLevel, tt.options, func(logger *Logger, logs *observer.ObservedLogs) { + // Make sure that sugaring and desugaring resets caller skip properly. + logger = logger.Sugar().Desugar() + infoLog(logger, "") + infoLogSugared(logger.Sugar(), "") + infoLog(logger.Sugar().Desugar(), "") + + entries := logs.AllUntimed() + assert.Equal(t, 3, len(entries), "Unexpected number of logs written out.") + for _, entry := range []observer.LoggedEntry{entries[0], entries[2]} { + assert.Regexp( + t, + tt.loggerFunction, + entry.Caller.Function, + "Expected to find function name in output.", + ) + } + assert.Regexp( + t, + tt.sugaredFunction, + entries[1].Caller.Function, + "Expected to find function name in output.", + ) + }) + } +} + +func TestLoggerAddCallerFail(t *testing.T) { + errBuf := &ztest.Buffer{} + withLogger(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *Logger, logs *observer.ObservedLogs) { + log.Info("Failure.") + assert.Regexp( + t, + `Logger.check error: failed to get caller`, + errBuf.String(), + "Didn't find expected failure message.", + ) + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "Failure.", + "Expected original message to survive failures in runtime.Caller.") + assert.Equal( + t, + logs.AllUntimed()[0].Caller.Function, + "", + "Expected function name to be empty string.") + }) +} + +func TestLoggerReplaceCore(t *testing.T) { + replace := WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewNopCore() + }) + withLogger(t, DebugLevel, opts(replace), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Debug("") + logger.Info("") + logger.Warn("") + assert.Equal(t, 0, logs.Len(), "Expected no-op core to write no logs.") + }) +} + +func TestLoggerIncreaseLevel(t *testing.T) { + withLogger(t, DebugLevel, opts(IncreaseLevel(WarnLevel)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Info("logger.Info") + logger.Warn("logger.Warn") + logger.Error("logger.Error") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warn", + "Expected first logged message to be warn level message", + ) + }) +} + +func TestLoggerHooks(t *testing.T) { + hook, seen := makeCountingHook() + withLogger(t, DebugLevel, opts(Hooks(hook)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Debug("") + logger.Info("") + }) + assert.Equal(t, int64(2), seen.Load(), "Hook saw an unexpected number of logs.") +} + +func TestLoggerConcurrent(t *testing.T) { + withLogger(t, DebugLevel, nil, func(logger *Logger, logs *observer.ObservedLogs) { + child := logger.With(String("foo", "bar")) + + wg := &sync.WaitGroup{} + runConcurrently(5, 10, wg, func() { + logger.Info("", String("foo", "bar")) + }) + runConcurrently(5, 10, wg, func() { + child.Info("") + }) + + wg.Wait() + + // Make sure the output doesn't contain interspersed entries. + assert.Equal(t, 100, logs.Len(), "Unexpected number of logs written out.") + for _, obs := range logs.AllUntimed() { + assert.Equal( + t, + observer.LoggedEntry{ + Entry: zapcore.Entry{Level: InfoLevel}, + Context: []Field{String("foo", "bar")}, + }, + obs, + "Unexpected log output.", + ) + } + }) +} + +func TestLoggerFatalOnNoop(t *testing.T) { + exitStub := exit.Stub() + defer exitStub.Unstub() + core, _ := observer.New(InfoLevel) + + // We don't allow a no-op fatal hook. + New(core, WithFatalHook(zapcore.WriteThenNoop)).Fatal("great sadness") + assert.True(t, exitStub.Exited, "must exit for WriteThenNoop") + assert.Equal(t, 1, exitStub.Code, "must exit with status 1 for WriteThenNoop") +} + +func TestLoggerCustomOnPanic(t *testing.T) { + tests := []struct { + msg string + level zapcore.Level + opts []Option + finished bool + want []observer.LoggedEntry + recoverValue any + }{ + { + msg: "panic with nil hook", + level: PanicLevel, + opts: opts(WithPanicHook(nil)), + finished: false, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: "foobar", + }, + { + msg: "panic with noop hook", + level: PanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenNoop)), + finished: false, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: "foobar", + }, + { + msg: "no panic with goexit hook", + level: PanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenGoexit)), + finished: false, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: PanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: nil, + }, + { + msg: "dpanic no panic in development mode with goexit hook", + level: DPanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenGoexit), Development()), + finished: false, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: nil, + }, + { + msg: "dpanic panic in development mode with noop hook", + level: DPanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenNoop), Development()), + finished: false, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: "foobar", + }, + { + msg: "dpanic no exit in production mode with goexit hook", + level: DPanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenPanic)), + finished: true, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: nil, + }, + { + msg: "dpanic no panic in production mode with panic hook", + level: DPanicLevel, + opts: opts(WithPanicHook(zapcore.WriteThenPanic)), + finished: true, + want: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{Level: DPanicLevel, Message: "foobar"}, + Context: []Field{}, + }, + }, + recoverValue: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + withLogger(t, InfoLevel, tt.opts, func(logger *Logger, logs *observer.ObservedLogs) { + var finished bool + recovered := make(chan any) + go func() { + defer func() { + recovered <- recover() + }() + + logger.Log(tt.level, "foobar") + finished = true + }() + + assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()") + assert.Equal(t, tt.finished, finished, "expect goroutine finished state doesn't match") + assert.Equal(t, tt.want, logs.AllUntimed(), "unexpected logs") + }) + }) + } +} + +func TestLoggerCustomOnFatal(t *testing.T) { + tests := []struct { + msg string + onFatal zapcore.CheckWriteAction + recoverValue interface{} + }{ + { + msg: "panic", + onFatal: zapcore.WriteThenPanic, + recoverValue: "fatal", + }, + { + msg: "goexit", + onFatal: zapcore.WriteThenGoexit, + recoverValue: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + withLogger(t, InfoLevel, opts(OnFatal(tt.onFatal)), func(logger *Logger, logs *observer.ObservedLogs) { + var finished bool + recovered := make(chan interface{}) + go func() { + defer func() { + recovered <- recover() + }() + + logger.Fatal("fatal") + finished = true + }() + + assert.Equal(t, tt.recoverValue, <-recovered, "unexpected value from recover()") + assert.False(t, finished, "expect goroutine to not finish after Fatal") + + assert.Equal(t, []observer.LoggedEntry{{ + Entry: zapcore.Entry{Level: FatalLevel, Message: "fatal"}, + Context: []Field{}, + }}, logs.AllUntimed(), "unexpected logs") + }) + }) + } +} + +type customWriteHook struct { + called bool +} + +func (h *customWriteHook) OnWrite(_ *zapcore.CheckedEntry, _ []Field) { + h.called = true +} + +func TestLoggerWithFatalHook(t *testing.T) { + var h customWriteHook + withLogger(t, InfoLevel, opts(WithFatalHook(&h)), func(logger *Logger, logs *observer.ObservedLogs) { + logger.Fatal("great sadness") + assert.True(t, h.called) + assert.Equal(t, 1, logs.FilterLevelExact(FatalLevel).Len()) + }) +} + +func TestNopLogger(t *testing.T) { + logger := NewNop() + + t.Run("basic levels", func(t *testing.T) { + logger.Debug("foo", String("k", "v")) + logger.Info("bar", Int("x", 42)) + logger.Warn("baz", Strings("ks", []string{"a", "b"})) + logger.Error("qux", Error(errors.New("great sadness"))) + }) + + t.Run("DPanic", func(t *testing.T) { + logger.With(String("component", "whatever")).DPanic("stuff") + }) + + t.Run("Panic", func(t *testing.T) { + assert.Panics(t, func() { + logger.Panic("great sadness") + }, "Nop logger should still cause panics.") + }) +} + +func TestMust(t *testing.T) { + t.Run("must without an error does not panic", func(t *testing.T) { + assert.NotPanics(t, func() { Must(NewNop(), nil) }, "must paniced with no error") + }) + + t.Run("must with an error panics", func(t *testing.T) { + assert.Panics(t, func() { Must(nil, errors.New("an error")) }, "must did not panic with an error") + }) +} + +func infoLog(logger *Logger, msg string, fields ...Field) { + logger.Info(msg, fields...) +} + +func infoLogSugared(logger *SugaredLogger, args ...interface{}) { + logger.Info(args...) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/options.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/options.go new file mode 100644 index 0000000..43d357a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/options.go @@ -0,0 +1,182 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + + "go.uber.org/zap/zapcore" +) + +// An Option configures a Logger. +type Option interface { + apply(*Logger) +} + +// optionFunc wraps a func so it satisfies the Option interface. +type optionFunc func(*Logger) + +func (f optionFunc) apply(log *Logger) { + f(log) +} + +// WrapCore wraps or replaces the Logger's underlying zapcore.Core. +func WrapCore(f func(zapcore.Core) zapcore.Core) Option { + return optionFunc(func(log *Logger) { + log.core = f(log.core) + }) +} + +// Hooks registers functions which will be called each time the Logger writes +// out an Entry. Repeated use of Hooks is additive. +// +// Hooks are useful for simple side effects, like capturing metrics for the +// number of emitted logs. More complex side effects, including anything that +// requires access to the Entry's structured fields, should be implemented as +// a zapcore.Core instead. See zapcore.RegisterHooks for details. +func Hooks(hooks ...func(zapcore.Entry) error) Option { + return optionFunc(func(log *Logger) { + log.core = zapcore.RegisterHooks(log.core, hooks...) + }) +} + +// Fields adds fields to the Logger. +func Fields(fs ...Field) Option { + return optionFunc(func(log *Logger) { + log.core = log.core.With(fs) + }) +} + +// ErrorOutput sets the destination for errors generated by the Logger. Note +// that this option only affects internal errors; for sample code that sends +// error-level logs to a different location from info- and debug-level logs, +// see the package-level AdvancedConfiguration example. +// +// The supplied WriteSyncer must be safe for concurrent use. The Open and +// zapcore.Lock functions are the simplest ways to protect files with a mutex. +func ErrorOutput(w zapcore.WriteSyncer) Option { + return optionFunc(func(log *Logger) { + log.errorOutput = w + }) +} + +// Development puts the logger in development mode, which makes DPanic-level +// logs panic instead of simply logging an error. +func Development() Option { + return optionFunc(func(log *Logger) { + log.development = true + }) +} + +// AddCaller configures the Logger to annotate each message with the filename, +// line number, and function name of zap's caller. See also WithCaller. +func AddCaller() Option { + return WithCaller(true) +} + +// WithCaller configures the Logger to annotate each message with the filename, +// line number, and function name of zap's caller, or not, depending on the +// value of enabled. This is a generalized form of AddCaller. +func WithCaller(enabled bool) Option { + return optionFunc(func(log *Logger) { + log.addCaller = enabled + }) +} + +// AddCallerSkip increases the number of callers skipped by caller annotation +// (as enabled by the AddCaller option). When building wrappers around the +// Logger and SugaredLogger, supplying this Option prevents zap from always +// reporting the wrapper code as the caller. +func AddCallerSkip(skip int) Option { + return optionFunc(func(log *Logger) { + log.callerSkip += skip + }) +} + +// AddStacktrace configures the Logger to record a stack trace for all messages at +// or above a given level. +func AddStacktrace(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + log.addStack = lvl + }) +} + +// IncreaseLevel increase the level of the logger. It has no effect if +// the passed in level tries to decrease the level of the logger. +func IncreaseLevel(lvl zapcore.LevelEnabler) Option { + return optionFunc(func(log *Logger) { + core, err := zapcore.NewIncreaseLevelCore(log.core, lvl) + if err != nil { + fmt.Fprintf(log.errorOutput, "failed to IncreaseLevel: %v\n", err) + } else { + log.core = core + } + }) +} + +// WithPanicHook sets a CheckWriteHook to run on Panic/DPanic logs. +// Zap will call this hook after writing a log statement with a Panic/DPanic level. +// +// For example, the following builds a logger that will exit the current +// goroutine after writing a Panic/DPanic log message, but it will not start a panic. +// +// zap.New(core, zap.WithPanicHook(zapcore.WriteThenGoexit)) +// +// This is useful for testing Panic/DPanic log output. +func WithPanicHook(hook zapcore.CheckWriteHook) Option { + return optionFunc(func(log *Logger) { + log.onPanic = hook + }) +} + +// OnFatal sets the action to take on fatal logs. +// +// Deprecated: Use [WithFatalHook] instead. +func OnFatal(action zapcore.CheckWriteAction) Option { + return WithFatalHook(action) +} + +// WithFatalHook sets a CheckWriteHook to run on fatal logs. +// Zap will call this hook after writing a log statement with a Fatal level. +// +// For example, the following builds a logger that will exit the current +// goroutine after writing a fatal log message, but it will not exit the +// program. +// +// zap.New(core, zap.WithFatalHook(zapcore.WriteThenGoexit)) +// +// It is important that the provided CheckWriteHook stops the control flow at +// the current statement to meet expectations of callers of the logger. +// We recommend calling os.Exit or runtime.Goexit inside custom hooks at +// minimum. +func WithFatalHook(hook zapcore.CheckWriteHook) Option { + return optionFunc(func(log *Logger) { + log.onFatal = hook + }) +} + +// WithClock specifies the clock used by the logger to determine the current +// time for logged entries. Defaults to the system clock with time.Now. +func WithClock(clock zapcore.Clock) Option { + return optionFunc(func(log *Logger) { + log.clock = clock + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink.go new file mode 100644 index 0000000..499772a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink.go @@ -0,0 +1,180 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "io" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + + "go.uber.org/zap/zapcore" +) + +const schemeFile = "file" + +var _sinkRegistry = newSinkRegistry() + +// Sink defines the interface to write to and close logger destinations. +type Sink interface { + zapcore.WriteSyncer + io.Closer +} + +type errSinkNotFound struct { + scheme string +} + +func (e *errSinkNotFound) Error() string { + return fmt.Sprintf("no sink found for scheme %q", e.scheme) +} + +type nopCloserSink struct{ zapcore.WriteSyncer } + +func (nopCloserSink) Close() error { return nil } + +type sinkRegistry struct { + mu sync.Mutex + factories map[string]func(*url.URL) (Sink, error) // keyed by scheme + openFile func(string, int, os.FileMode) (*os.File, error) // type matches os.OpenFile +} + +func newSinkRegistry() *sinkRegistry { + sr := &sinkRegistry{ + factories: make(map[string]func(*url.URL) (Sink, error)), + openFile: os.OpenFile, + } + // Infallible operation: the registry is empty, so we can't have a conflict. + _ = sr.RegisterSink(schemeFile, sr.newFileSinkFromURL) + return sr +} + +// RegisterScheme registers the given factory for the specific scheme. +func (sr *sinkRegistry) RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { + sr.mu.Lock() + defer sr.mu.Unlock() + + if scheme == "" { + return errors.New("can't register a sink factory for empty string") + } + normalized, err := normalizeScheme(scheme) + if err != nil { + return fmt.Errorf("%q is not a valid scheme: %v", scheme, err) + } + if _, ok := sr.factories[normalized]; ok { + return fmt.Errorf("sink factory already registered for scheme %q", normalized) + } + sr.factories[normalized] = factory + return nil +} + +func (sr *sinkRegistry) newSink(rawURL string) (Sink, error) { + // URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to + // the drive, and path is unset unless `c:/log.txt` is used. + // To avoid Windows-specific URL handling, we instead check IsAbs to open as a file. + // filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows. + if filepath.IsAbs(rawURL) { + return sr.newFileSinkFromPath(rawURL) + } + + u, err := url.Parse(rawURL) + if err != nil { + return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err) + } + if u.Scheme == "" { + u.Scheme = schemeFile + } + + sr.mu.Lock() + factory, ok := sr.factories[u.Scheme] + sr.mu.Unlock() + if !ok { + return nil, &errSinkNotFound{u.Scheme} + } + return factory(u) +} + +// RegisterSink registers a user-supplied factory for all sinks with a +// particular scheme. +// +// All schemes must be ASCII, valid under section 0.1 of RFC 3986 +// (https://tools.ietf.org/html/rfc3983#section-3.1), and must not already +// have a factory registered. Zap automatically registers a factory for the +// "file" scheme. +func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error { + return _sinkRegistry.RegisterSink(scheme, factory) +} + +func (sr *sinkRegistry) newFileSinkFromURL(u *url.URL) (Sink, error) { + if u.User != nil { + return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u) + } + if u.Fragment != "" { + return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u) + } + if u.RawQuery != "" { + return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u) + } + // Error messages are better if we check hostname and port separately. + if u.Port() != "" { + return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u) + } + if hn := u.Hostname(); hn != "" && hn != "localhost" { + return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u) + } + + return sr.newFileSinkFromPath(u.Path) +} + +func (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) { + switch path { + case "stdout": + return nopCloserSink{os.Stdout}, nil + case "stderr": + return nopCloserSink{os.Stderr}, nil + } + return sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666) +} + +func normalizeScheme(s string) (string, error) { + // https://tools.ietf.org/html/rfc3986#section-3.1 + s = strings.ToLower(s) + if first := s[0]; 'a' > first || 'z' < first { + return "", errors.New("must start with a letter") + } + for i := 1; i < len(s); i++ { // iterate over bytes, not runes + c := s[i] + switch { + case 'a' <= c && c <= 'z': + continue + case '0' <= c && c <= '9': + continue + case c == '.' || c == '+' || c == '-': + continue + } + return "", fmt.Errorf("may not contain %q", c) + } + return s, nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_test.go new file mode 100644 index 0000000..5fc37be --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_test.go @@ -0,0 +1,107 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "bytes" + "io" + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.uber.org/zap/zapcore" +) + +func stubSinkRegistry(t testing.TB) *sinkRegistry { + origSinkRegistry := _sinkRegistry + t.Cleanup(func() { + _sinkRegistry = origSinkRegistry + }) + + r := newSinkRegistry() + _sinkRegistry = r + return r +} + +func TestRegisterSink(t *testing.T) { + stubSinkRegistry(t) + + const ( + memScheme = "mem" + nopScheme = "no-op.1234" + ) + var memCalls, nopCalls int + + buf := bytes.NewBuffer(nil) + memFactory := func(u *url.URL) (Sink, error) { + assert.Equal(t, u.Scheme, memScheme, "Scheme didn't match registration.") + memCalls++ + return nopCloserSink{zapcore.AddSync(buf)}, nil + } + nopFactory := func(u *url.URL) (Sink, error) { + assert.Equal(t, u.Scheme, nopScheme, "Scheme didn't match registration.") + nopCalls++ + return nopCloserSink{zapcore.AddSync(io.Discard)}, nil + } + + require.NoError(t, RegisterSink(strings.ToUpper(memScheme), memFactory), "Failed to register scheme %q.", memScheme) + require.NoError(t, RegisterSink(nopScheme, nopFactory), "Failed to register scheme %q.", nopScheme) + + sink, closeSink, err := Open( + memScheme+"://somewhere", + nopScheme+"://somewhere-else", + ) + require.NoError(t, err, "Unexpected error opening URLs with registered schemes.") + defer closeSink() + + assert.Equal(t, 1, memCalls, "Unexpected number of calls to memory factory.") + assert.Equal(t, 1, nopCalls, "Unexpected number of calls to no-op factory.") + + _, err = sink.Write([]byte("foo")) + assert.NoError(t, err, "Failed to write to combined WriteSyncer.") + assert.Equal(t, "foo", buf.String(), "Unexpected buffer contents.") +} + +func TestRegisterSinkErrors(t *testing.T) { + nopFactory := func(_ *url.URL) (Sink, error) { + return nopCloserSink{zapcore.AddSync(io.Discard)}, nil + } + tests := []struct { + scheme string + err string + }{ + {"", "empty string"}, + {"FILE", "already registered"}, + {"42", "not a valid scheme"}, + {"http*", "not a valid scheme"}, + } + + for _, tt := range tests { + t.Run("scheme-"+tt.scheme, func(t *testing.T) { + r := newSinkRegistry() + err := r.RegisterSink(tt.scheme, nopFactory) + assert.ErrorContains(t, err, tt.err) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_windows_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_windows_test.go new file mode 100644 index 0000000..fd6a475 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sink_windows_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//go:build windows + +package zap + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWindowsPaths(t *testing.T) { + // See https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats + tests := []struct { + msg string + path string + }{ + { + msg: "local path with drive", + path: `c:\log.json`, + }, + { + msg: "local path with drive using forward slash", + path: `c:/log.json`, + }, + { + msg: "local path without drive", + path: `\Temp\log.json`, + }, + { + msg: "unc path", + path: `\\Server2\Logs\log.json`, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + sr := newSinkRegistry() + + openFilename := "<not called>" + sr.openFile = func(filename string, _ int, _ os.FileMode) (*os.File, error) { + openFilename = filename + return nil, assert.AnError + } + + _, err := sr.newSink(tt.path) + assert.Equal(t, assert.AnError, err, "expect stub error from OpenFile") + assert.Equal(t, tt.path, openFilename, "unexpected path opened") + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/stacktrace_ext_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/stacktrace_ext_test.go new file mode 100644 index 0000000..9f018aa --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/stacktrace_ext_test.go @@ -0,0 +1,210 @@ +// Copyright (c) 2016, 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap_test + +import ( + "bytes" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// _zapPackages are packages that we search for in the logging output to match a +// zap stack frame. It is different from _zapStacktracePrefixes which is only +// intended to match on the function name, while this is on the full output +// which includes filenames. +var _zapPackages = []string{ + "go.uber.org/zap.", + "go.uber.org/zap/zapcore.", +} + +func TestStacktraceFiltersZapLog(t *testing.T) { + withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) { + logger.Error("test log") + logger.Sugar().Error("sugar test log") + + require.Contains(t, out.String(), "TestStacktraceFiltersZapLog", "Should not strip out non-zap import") + verifyNoZap(t, out.String()) + }) +} + +func TestStacktraceFiltersZapMarshal(t *testing.T) { + withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) { + marshal := func(enc zapcore.ObjectEncoder) error { + logger.Warn("marshal caused warn") + enc.AddString("f", "v") + return nil + } + logger.Error("test log", zap.Object("obj", zapcore.ObjectMarshalerFunc(marshal))) + + logs := out.String() + + // The marshal function (which will be under the test function) should not be stripped. + const marshalFnPrefix = "TestStacktraceFiltersZapMarshal." + require.Contains(t, logs, marshalFnPrefix, "Should not strip out marshal call") + + // There should be no zap stack traces before that point. + marshalIndex := strings.Index(logs, marshalFnPrefix) + verifyNoZap(t, logs[:marshalIndex]) + + // After that point, there should be zap stack traces - we don't want to strip out + // the Marshal caller information. + for _, fnPrefix := range _zapPackages { + require.Contains(t, logs[marshalIndex:], fnPrefix, "Missing zap caller stack for Marshal") + } + }) +} + +func TestStacktraceFiltersVendorZap(t *testing.T) { + // We already have the dependencies downloaded so this should be + // instant. + deps := downloadDependencies(t) + + // We need to simulate a zap as a vendor library, so we're going to + // create a fake GOPATH and run the above test which will contain zap + // in the vendor directory. + withGoPath(t, func(goPath string) { + zapDir, err := os.Getwd() + require.NoError(t, err, "Failed to get current directory") + + testDir := filepath.Join(goPath, "src/go.uber.org/zap_test/") + vendorDir := filepath.Join(testDir, "vendor") + require.NoError(t, os.MkdirAll(testDir, 0o777), "Failed to create source director") + + curFile := getSelfFilename(t) + setupSymlink(t, curFile, filepath.Join(testDir, curFile)) + + // Set up symlinks for zap, and for any test dependencies. + setupSymlink(t, zapDir, filepath.Join(vendorDir, "go.uber.org/zap")) + for _, dep := range deps { + setupSymlink(t, dep.Dir, filepath.Join(vendorDir, dep.ImportPath)) + } + + // Now run the above test which ensures we filter out zap + // stacktraces, but this time zap is in a vendor + cmd := exec.Command("go", "test", "-v", "-run", "TestStacktraceFiltersZap") + cmd.Dir = testDir + cmd.Env = append(os.Environ(), "GO111MODULE=off") + out, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to run test in vendor directory, output: %s", out) + assert.Contains(t, string(out), "PASS") + }) +} + +func TestStacktraceWithoutCallerSkip(t *testing.T) { + withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) { + func() { + logger.Error("test log") + }() + + require.Contains(t, out.String(), "TestStacktraceWithoutCallerSkip.", "Should not skip too much") + verifyNoZap(t, out.String()) + }) +} + +func TestStacktraceWithCallerSkip(t *testing.T) { + withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) { + logger = logger.WithOptions(zap.AddCallerSkip(2)) + func() { + logger.Error("test log") + }() + + require.NotContains(t, out.String(), "TestStacktraceWithCallerSkip.", "Should skip as requested by caller skip") + require.Contains(t, out.String(), "TestStacktraceWithCallerSkip", "Should not skip too much") + verifyNoZap(t, out.String()) + }) +} + +// withLogger sets up a logger with a real encoder set up, so that any marshal functions are called. +// The inbuilt observer does not call Marshal for objects/arrays, which we need for some tests. +func withLogger(t *testing.T, fn func(logger *zap.Logger, out *bytes.Buffer)) { + buf := &bytes.Buffer{} + encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) + core := zapcore.NewCore(encoder, zapcore.AddSync(buf), zapcore.DebugLevel) + logger := zap.New(core, zap.AddStacktrace(zap.DebugLevel)) + fn(logger, buf) +} + +func verifyNoZap(t *testing.T, logs string) { + for _, fnPrefix := range _zapPackages { + require.NotContains(t, logs, fnPrefix, "Should not strip out marshal call") + } +} + +func withGoPath(t *testing.T, f func(goPath string)) { + goPath := filepath.Join(t.TempDir(), "gopath") + t.Setenv("GOPATH", goPath) + + f(goPath) +} + +func getSelfFilename(t *testing.T) string { + _, file, _, ok := runtime.Caller(0) + require.True(t, ok, "Failed to get caller information to identify local file") + + return filepath.Base(file) +} + +func setupSymlink(t *testing.T, src, dst string) { + // Make sure the destination directory exists. + require.NoError(t, os.MkdirAll(filepath.Dir(dst), 0o777)) + + // Get absolute path of the source for the symlink, otherwise we can create a symlink + // that uses relative paths. + srcAbs, err := filepath.Abs(src) + require.NoError(t, err, "Failed to get absolute path") + + require.NoError(t, os.Symlink(srcAbs, dst), "Failed to set up symlink") +} + +type dependency struct { + ImportPath string `json:"Path"` // import path of the dependency + Dir string `json:"Dir"` // location on disk +} + +// Downloads all dependencies for the current Go module and reports their +// module paths and locations on disk. +func downloadDependencies(t *testing.T) []dependency { + cmd := exec.Command("go", "mod", "download", "-json") + + stdout, err := cmd.Output() + require.NoError(t, err, "Failed to run 'go mod download'") + + var deps []dependency + dec := json.NewDecoder(bytes.NewBuffer(stdout)) + for dec.More() { + var d dependency + require.NoError(t, dec.Decode(&d), "Failed to decode dependency") + deps = append(deps, d) + } + + return deps +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar.go new file mode 100644 index 0000000..8904cd0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar.go @@ -0,0 +1,476 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +const ( + _oddNumberErrMsg = "Ignored key without a value." + _nonStringKeyErrMsg = "Ignored key-value pairs with non-string keys." + _multipleErrMsg = "Multiple errors without a key." +) + +// A SugaredLogger wraps the base Logger functionality in a slower, but less +// verbose, API. Any Logger can be converted to a SugaredLogger with its Sugar +// method. +// +// Unlike the Logger, the SugaredLogger doesn't insist on structured logging. +// For each log level, it exposes four methods: +// +// - methods named after the log level for log.Print-style logging +// - methods ending in "w" for loosely-typed structured logging +// - methods ending in "f" for log.Printf-style logging +// - methods ending in "ln" for log.Println-style logging +// +// For example, the methods for InfoLevel are: +// +// Info(...any) Print-style logging +// Infow(...any) Structured logging (read as "info with") +// Infof(string, ...any) Printf-style logging +// Infoln(...any) Println-style logging +type SugaredLogger struct { + base *Logger +} + +// Desugar unwraps a SugaredLogger, exposing the original Logger. Desugaring +// is quite inexpensive, so it's reasonable for a single application to use +// both Loggers and SugaredLoggers, converting between them on the boundaries +// of performance-sensitive code. +func (s *SugaredLogger) Desugar() *Logger { + base := s.base.clone() + base.callerSkip -= 2 + return base +} + +// Named adds a sub-scope to the logger's name. See Logger.Named for details. +func (s *SugaredLogger) Named(name string) *SugaredLogger { + return &SugaredLogger{base: s.base.Named(name)} +} + +// WithOptions clones the current SugaredLogger, applies the supplied Options, +// and returns the result. It's safe to use concurrently. +func (s *SugaredLogger) WithOptions(opts ...Option) *SugaredLogger { + base := s.base.clone() + for _, opt := range opts { + opt.apply(base) + } + return &SugaredLogger{base: base} +} + +// With adds a variadic number of fields to the logging context. It accepts a +// mix of strongly-typed Field objects and loosely-typed key-value pairs. When +// processing pairs, the first element of the pair is used as the field key +// and the second as the field value. +// +// For example, +// +// sugaredLogger.With( +// "hello", "world", +// "failure", errors.New("oh no"), +// Stack(), +// "count", 42, +// "user", User{Name: "alice"}, +// ) +// +// is the equivalent of +// +// unsugared.With( +// String("hello", "world"), +// String("failure", "oh no"), +// Stack(), +// Int("count", 42), +// Object("user", User{Name: "alice"}), +// ) +// +// Note that the keys in key-value pairs should be strings. In development, +// passing a non-string key panics. In production, the logger is more +// forgiving: a separate error is logged, but the key-value pair is skipped +// and execution continues. Passing an orphaned key triggers similar behavior: +// panics in development and errors in production. +func (s *SugaredLogger) With(args ...interface{}) *SugaredLogger { + return &SugaredLogger{base: s.base.With(s.sweetenFields(args)...)} +} + +// WithLazy adds a variadic number of fields to the logging context lazily. +// The fields are evaluated only if the logger is further chained with [With] +// or is written to with any of the log level methods. +// Until that occurs, the logger may retain references to objects inside the fields, +// and logging will reflect the state of an object at the time of logging, +// not the time of WithLazy(). +// +// Similar to [With], fields added to the child don't affect the parent, +// and vice versa. Also, the keys in key-value pairs should be strings. In development, +// passing a non-string key panics, while in production it logs an error and skips the pair. +// Passing an orphaned key has the same behavior. +func (s *SugaredLogger) WithLazy(args ...interface{}) *SugaredLogger { + return &SugaredLogger{base: s.base.WithLazy(s.sweetenFields(args)...)} +} + +// Level reports the minimum enabled level for this logger. +// +// For NopLoggers, this is [zapcore.InvalidLevel]. +func (s *SugaredLogger) Level() zapcore.Level { + return zapcore.LevelOf(s.base.core) +} + +// Log logs the provided arguments at provided level. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Log(lvl zapcore.Level, args ...interface{}) { + s.log(lvl, "", args, nil) +} + +// Debug logs the provided arguments at [DebugLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Debug(args ...interface{}) { + s.log(DebugLevel, "", args, nil) +} + +// Info logs the provided arguments at [InfoLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Info(args ...interface{}) { + s.log(InfoLevel, "", args, nil) +} + +// Warn logs the provided arguments at [WarnLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Warn(args ...interface{}) { + s.log(WarnLevel, "", args, nil) +} + +// Error logs the provided arguments at [ErrorLevel]. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Error(args ...interface{}) { + s.log(ErrorLevel, "", args, nil) +} + +// DPanic logs the provided arguments at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) DPanic(args ...interface{}) { + s.log(DPanicLevel, "", args, nil) +} + +// Panic constructs a message with the provided arguments and panics. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Panic(args ...interface{}) { + s.log(PanicLevel, "", args, nil) +} + +// Fatal constructs a message with the provided arguments and calls os.Exit. +// Spaces are added between arguments when neither is a string. +func (s *SugaredLogger) Fatal(args ...interface{}) { + s.log(FatalLevel, "", args, nil) +} + +// Logf formats the message according to the format specifier +// and logs it at provided level. +func (s *SugaredLogger) Logf(lvl zapcore.Level, template string, args ...interface{}) { + s.log(lvl, template, args, nil) +} + +// Debugf formats the message according to the format specifier +// and logs it at [DebugLevel]. +func (s *SugaredLogger) Debugf(template string, args ...interface{}) { + s.log(DebugLevel, template, args, nil) +} + +// Infof formats the message according to the format specifier +// and logs it at [InfoLevel]. +func (s *SugaredLogger) Infof(template string, args ...interface{}) { + s.log(InfoLevel, template, args, nil) +} + +// Warnf formats the message according to the format specifier +// and logs it at [WarnLevel]. +func (s *SugaredLogger) Warnf(template string, args ...interface{}) { + s.log(WarnLevel, template, args, nil) +} + +// Errorf formats the message according to the format specifier +// and logs it at [ErrorLevel]. +func (s *SugaredLogger) Errorf(template string, args ...interface{}) { + s.log(ErrorLevel, template, args, nil) +} + +// DPanicf formats the message according to the format specifier +// and logs it at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +func (s *SugaredLogger) DPanicf(template string, args ...interface{}) { + s.log(DPanicLevel, template, args, nil) +} + +// Panicf formats the message according to the format specifier +// and panics. +func (s *SugaredLogger) Panicf(template string, args ...interface{}) { + s.log(PanicLevel, template, args, nil) +} + +// Fatalf formats the message according to the format specifier +// and calls os.Exit. +func (s *SugaredLogger) Fatalf(template string, args ...interface{}) { + s.log(FatalLevel, template, args, nil) +} + +// Logw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Logw(lvl zapcore.Level, msg string, keysAndValues ...interface{}) { + s.log(lvl, msg, nil, keysAndValues) +} + +// Debugw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +// +// When debug-level logging is disabled, this is much faster than +// +// s.With(keysAndValues).Debug(msg) +func (s *SugaredLogger) Debugw(msg string, keysAndValues ...interface{}) { + s.log(DebugLevel, msg, nil, keysAndValues) +} + +// Infow logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Infow(msg string, keysAndValues ...interface{}) { + s.log(InfoLevel, msg, nil, keysAndValues) +} + +// Warnw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Warnw(msg string, keysAndValues ...interface{}) { + s.log(WarnLevel, msg, nil, keysAndValues) +} + +// Errorw logs a message with some additional context. The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) Errorw(msg string, keysAndValues ...interface{}) { + s.log(ErrorLevel, msg, nil, keysAndValues) +} + +// DPanicw logs a message with some additional context. In development, the +// logger then panics. (See DPanicLevel for details.) The variadic key-value +// pairs are treated as they are in With. +func (s *SugaredLogger) DPanicw(msg string, keysAndValues ...interface{}) { + s.log(DPanicLevel, msg, nil, keysAndValues) +} + +// Panicw logs a message with some additional context, then panics. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Panicw(msg string, keysAndValues ...interface{}) { + s.log(PanicLevel, msg, nil, keysAndValues) +} + +// Fatalw logs a message with some additional context, then calls os.Exit. The +// variadic key-value pairs are treated as they are in With. +func (s *SugaredLogger) Fatalw(msg string, keysAndValues ...interface{}) { + s.log(FatalLevel, msg, nil, keysAndValues) +} + +// Logln logs a message at provided level. +// Spaces are always added between arguments. +func (s *SugaredLogger) Logln(lvl zapcore.Level, args ...interface{}) { + s.logln(lvl, args, nil) +} + +// Debugln logs a message at [DebugLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Debugln(args ...interface{}) { + s.logln(DebugLevel, args, nil) +} + +// Infoln logs a message at [InfoLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Infoln(args ...interface{}) { + s.logln(InfoLevel, args, nil) +} + +// Warnln logs a message at [WarnLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Warnln(args ...interface{}) { + s.logln(WarnLevel, args, nil) +} + +// Errorln logs a message at [ErrorLevel]. +// Spaces are always added between arguments. +func (s *SugaredLogger) Errorln(args ...interface{}) { + s.logln(ErrorLevel, args, nil) +} + +// DPanicln logs a message at [DPanicLevel]. +// In development, the logger then panics. (See [DPanicLevel] for details.) +// Spaces are always added between arguments. +func (s *SugaredLogger) DPanicln(args ...interface{}) { + s.logln(DPanicLevel, args, nil) +} + +// Panicln logs a message at [PanicLevel] and panics. +// Spaces are always added between arguments. +func (s *SugaredLogger) Panicln(args ...interface{}) { + s.logln(PanicLevel, args, nil) +} + +// Fatalln logs a message at [FatalLevel] and calls os.Exit. +// Spaces are always added between arguments. +func (s *SugaredLogger) Fatalln(args ...interface{}) { + s.logln(FatalLevel, args, nil) +} + +// Sync flushes any buffered log entries. +func (s *SugaredLogger) Sync() error { + return s.base.Sync() +} + +// log message with Sprint, Sprintf, or neither. +func (s *SugaredLogger) log(lvl zapcore.Level, template string, fmtArgs []interface{}, context []interface{}) { + // If logging at this level is completely disabled, skip the overhead of + // string formatting. + if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { + return + } + + msg := getMessage(template, fmtArgs) + if ce := s.base.Check(lvl, msg); ce != nil { + ce.Write(s.sweetenFields(context)...) + } +} + +// logln message with Sprintln +func (s *SugaredLogger) logln(lvl zapcore.Level, fmtArgs []interface{}, context []interface{}) { + if lvl < DPanicLevel && !s.base.Core().Enabled(lvl) { + return + } + + msg := getMessageln(fmtArgs) + if ce := s.base.Check(lvl, msg); ce != nil { + ce.Write(s.sweetenFields(context)...) + } +} + +// getMessage format with Sprint, Sprintf, or neither. +func getMessage(template string, fmtArgs []interface{}) string { + if len(fmtArgs) == 0 { + return template + } + + if template != "" { + return fmt.Sprintf(template, fmtArgs...) + } + + if len(fmtArgs) == 1 { + if str, ok := fmtArgs[0].(string); ok { + return str + } + } + return fmt.Sprint(fmtArgs...) +} + +// getMessageln format with Sprintln. +func getMessageln(fmtArgs []interface{}) string { + msg := fmt.Sprintln(fmtArgs...) + return msg[:len(msg)-1] +} + +func (s *SugaredLogger) sweetenFields(args []interface{}) []Field { + if len(args) == 0 { + return nil + } + + var ( + // Allocate enough space for the worst case; if users pass only structured + // fields, we shouldn't penalize them with extra allocations. + fields = make([]Field, 0, len(args)) + invalid invalidPairs + seenError bool + ) + + for i := 0; i < len(args); { + // This is a strongly-typed field. Consume it and move on. + if f, ok := args[i].(Field); ok { + fields = append(fields, f) + i++ + continue + } + + // If it is an error, consume it and move on. + if err, ok := args[i].(error); ok { + if !seenError { + seenError = true + fields = append(fields, Error(err)) + } else { + s.base.Error(_multipleErrMsg, Error(err)) + } + i++ + continue + } + + // Make sure this element isn't a dangling key. + if i == len(args)-1 { + s.base.Error(_oddNumberErrMsg, Any("ignored", args[i])) + break + } + + // Consume this value and the next, treating them as a key-value pair. If the + // key isn't a string, add this pair to the slice of invalid pairs. + key, val := args[i], args[i+1] + if keyStr, ok := key.(string); !ok { + // Subsequent errors are likely, so allocate once up front. + if cap(invalid) == 0 { + invalid = make(invalidPairs, 0, len(args)/2) + } + invalid = append(invalid, invalidPair{i, key, val}) + } else { + fields = append(fields, Any(keyStr, val)) + } + i += 2 + } + + // If we encountered any invalid key-value pairs, log an error. + if len(invalid) > 0 { + s.base.Error(_nonStringKeyErrMsg, Array("invalid", invalid)) + } + return fields +} + +type invalidPair struct { + position int + key, value interface{} +} + +func (p invalidPair) MarshalLogObject(enc zapcore.ObjectEncoder) error { + enc.AddInt64("position", int64(p.position)) + Any("key", p.key).AddTo(enc) + Any("value", p.value).AddTo(enc) + return nil +} + +type invalidPairs []invalidPair + +func (ps invalidPairs) MarshalLogArray(enc zapcore.ArrayEncoder) error { + var err error + for i := range ps { + err = multierr.Append(err, enc.AppendObject(ps[i])) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar_test.go new file mode 100644 index 0000000..8ca2bdd --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/sugar_test.go @@ -0,0 +1,602 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSugarWith(t *testing.T) { + // Convenience functions to create expected error logs. + ignored := func(msg interface{}) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _oddNumberErrMsg}, + Context: []Field{Any("ignored", msg)}, + } + } + nonString := func(pairs ...invalidPair) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _nonStringKeyErrMsg}, + Context: []Field{Array("invalid", invalidPairs(pairs))}, + } + } + ignoredError := func(err error) observer.LoggedEntry { + return observer.LoggedEntry{ + Entry: zapcore.Entry{Level: ErrorLevel, Message: _multipleErrMsg}, + Context: []Field{Error(err)}, + } + } + + type withAny func(*SugaredLogger, ...interface{}) *SugaredLogger + withMethods := []withAny{(*SugaredLogger).With, (*SugaredLogger).WithLazy} + + tests := []struct { + desc string + args []interface{} + expected []Field + errLogs []observer.LoggedEntry + }{ + { + desc: "nil args", + args: nil, + expected: []Field{}, + errLogs: nil, + }, + { + desc: "empty slice of args", + args: []interface{}{}, + expected: []Field{}, + errLogs: nil, + }, + { + desc: "just a dangling key", + args: []interface{}{"should ignore"}, + expected: []Field{}, + errLogs: []observer.LoggedEntry{ignored("should ignore")}, + }, + { + desc: "well-formed key-value pairs", + args: []interface{}{"foo", 42, "true", "bar"}, + expected: []Field{Int("foo", 42), String("true", "bar")}, + errLogs: nil, + }, + { + desc: "just a structured field", + args: []interface{}{Int("foo", 42)}, + expected: []Field{Int("foo", 42)}, + errLogs: nil, + }, + { + desc: "structured field and a dangling key", + args: []interface{}{Int("foo", 42), "dangling"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "structured field and a dangling non-string key", + args: []interface{}{Int("foo", 42), 13}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored(13)}, + }, + { + desc: "key-value pair and a dangling key", + args: []interface{}{"foo", 42, "dangling"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "pairs, a structured field, and a dangling key", + args: []interface{}{"first", "field", Int("foo", 42), "baz", "quux", "dangling"}, + expected: []Field{String("first", "field"), Int("foo", 42), String("baz", "quux")}, + errLogs: []observer.LoggedEntry{ignored("dangling")}, + }, + { + desc: "one non-string key", + args: []interface{}{"foo", 42, true, "bar"}, + expected: []Field{Int("foo", 42)}, + errLogs: []observer.LoggedEntry{nonString(invalidPair{2, true, "bar"})}, + }, + { + desc: "pairs, structured fields, non-string keys, and a dangling key", + args: []interface{}{"foo", 42, true, "bar", Int("structure", 11), 42, "reversed", "baz", "quux", "dangling"}, + expected: []Field{Int("foo", 42), Int("structure", 11), String("baz", "quux")}, + errLogs: []observer.LoggedEntry{ + ignored("dangling"), + nonString(invalidPair{2, true, "bar"}, invalidPair{5, 42, "reversed"}), + }, + }, + { + desc: "multiple errors", + args: []interface{}{errors.New("first"), errors.New("second"), errors.New("third")}, + expected: []Field{Error(errors.New("first"))}, + errLogs: []observer.LoggedEntry{ + ignoredError(errors.New("second")), + ignoredError(errors.New("third")), + }, + }, + } + + for _, tt := range tests { + for _, withMethod := range withMethods { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + withMethod(logger, tt.args...).Info("") + output := logs.AllUntimed() + if len(tt.errLogs) > 0 { + for i := range tt.errLogs { + assert.Equal(t, tt.errLogs[i], output[i], "Unexpected error log at position %d for scenario %s.", i, tt.desc) + } + } + assert.Equal(t, len(tt.errLogs)+1, len(output), "Expected only one non-error message to be logged in scenario %s.", tt.desc) + assert.Equal(t, tt.expected, output[len(tt.errLogs)].Context, "Unexpected message context in scenario %s.", tt.desc) + }) + } + } +} + +func TestSugarWithCaptures(t *testing.T) { + type withAny func(*SugaredLogger, ...interface{}) *SugaredLogger + + tests := []struct { + name string + withMethods []withAny + wantJSON []string + }{ + { + name: "with captures arguments at time of With", + withMethods: []withAny{(*SugaredLogger).With}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [0], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [0], + "c0": [2] + }`, + }, + }, + { + name: "lazy with captures arguments at time of Logging", + withMethods: []withAny{(*SugaredLogger).WithLazy}, + wantJSON: []string{ + `{ + "m": "hello 0", + "a0": [1], + "b0": [1] + }`, + `{ + "m": "world 0", + "a0": [1], + "c0": [2] + }`, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "m", + }) + + var bs ztest.Buffer + logger := New(zapcore.NewCore(enc, &bs, DebugLevel)).Sugar() + + for i, withMethod := range tt.withMethods { + iStr := strconv.Itoa(i) + x := 10 * i + arr := zapcore.ArrayMarshalerFunc(func(enc zapcore.ArrayEncoder) error { + enc.AppendInt(x) + return nil + }) + + logger = withMethod(logger, Array("a"+iStr, arr)) + x++ + logger.Infow(fmt.Sprintf("hello %d", i), Array("b"+iStr, arr)) + x++ + logger = withMethod(logger, Array("c"+iStr, arr)) + logger.Infow(fmt.Sprintf("world %d", i)) + } + + if lines := bs.Lines(); assert.Len(t, lines, len(tt.wantJSON)) { + for i, want := range tt.wantJSON { + assert.JSONEq(t, want, lines[i], "Unexpected output from the %d'th log.", i) + } + } + }) + } +} + +func TestSugaredLoggerLevel(t *testing.T) { + levels := []zapcore.Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + core, _ := observer.New(lvl) + log := New(core).Sugar() + assert.Equal(t, lvl, log.Level()) + }) + } + + t.Run("Nop", func(t *testing.T) { + t.Parallel() + + assert.Equal(t, zapcore.InvalidLevel, NewNop().Sugar().Level()) + }) +} + +func TestSugarFieldsInvalidPairs(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(42, "foo", []string{"bar"}, "baz").Info("") + output := logs.AllUntimed() + + // Double-check that the actual message was logged. + require.Equal(t, 2, len(output), "Unexpected number of entries logged.") + require.Equal(t, observer.LoggedEntry{Context: []Field{}}, output[1], "Unexpected non-error log entry.") + + // Assert that the error message's structured fields serialize properly. + require.Equal(t, 1, len(output[0].Context), "Expected one field in error entry context.") + enc := zapcore.NewMapObjectEncoder() + output[0].Context[0].AddTo(enc) + assert.Equal(t, []interface{}{ + map[string]interface{}{"position": int64(0), "key": int64(42), "value": "foo"}, + map[string]interface{}{"position": int64(2), "key": []interface{}{"bar"}, "value": "baz"}, + }, enc.Fields["invalid"], "Unexpected output when logging invalid key-value pairs.") + }) +} + +func TestSugarStructuredLogging(t *testing.T) { + tests := []struct { + msg string + expectMsg string + }{ + {"foo", "foo"}, + {"", ""}, + } + + // Common to all test cases. + var ( + err = errors.New("qux") + context = []interface{}{"foo", "bar"} + extra = []interface{}{err, "baz", false} + expectedFields = []Field{String("foo", "bar"), Error(err), Bool("baz", false)} + ) + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugw(tt.msg, extra...) + logger.With(context...).Infow(tt.msg, extra...) + logger.With(context...).Warnw(tt.msg, extra...) + logger.With(context...).Errorw(tt.msg, extra...) + logger.With(context...).DPanicw(tt.msg, extra...) + logger.With(context...).Logw(WarnLevel, tt.msg, extra...) + + expected := make([]observer.LoggedEntry, 6) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, WarnLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expectMsg, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarConcatenatingLogging(t *testing.T) { + tests := []struct { + args []interface{} + expect string + }{ + {[]interface{}{nil}, "<nil>"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debug(tt.args...) + logger.With(context...).Info(tt.args...) + logger.With(context...).Warn(tt.args...) + logger.With(context...).Error(tt.args...) + logger.With(context...).DPanic(tt.args...) + logger.With(context...).Log(InfoLevel, tt.args...) + + expected := make([]observer.LoggedEntry, 6) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, InfoLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarTemplatedLogging(t *testing.T) { + tests := []struct { + format string + args []interface{} + expect string + }{ + {"", nil, ""}, + {"foo", nil, "foo"}, + // If the user fails to pass a template, degrade to fmt.Sprint. + {"", []interface{}{"foo"}, "foo"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugf(tt.format, tt.args...) + logger.With(context...).Infof(tt.format, tt.args...) + logger.With(context...).Warnf(tt.format, tt.args...) + logger.With(context...).Errorf(tt.format, tt.args...) + logger.With(context...).DPanicf(tt.format, tt.args...) + logger.With(context...).Logf(ErrorLevel, tt.format, tt.args...) + + expected := make([]observer.LoggedEntry, 6) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, ErrorLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarLnLogging(t *testing.T) { + tests := []struct { + args []interface{} + expect string + }{ + {nil, ""}, + {[]interface{}{}, ""}, + {[]interface{}{""}, ""}, + {[]interface{}{"foo"}, "foo"}, + {[]interface{}{"foo", "bar"}, "foo bar"}, + } + + // Common to all test cases. + context := []interface{}{"foo", "bar"} + expectedFields := []Field{String("foo", "bar")} + + for _, tt := range tests { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.With(context...).Debugln(tt.args...) + logger.With(context...).Infoln(tt.args...) + logger.With(context...).Warnln(tt.args...) + logger.With(context...).Errorln(tt.args...) + logger.With(context...).DPanicln(tt.args...) + logger.With(context...).Logln(InfoLevel, tt.args...) + + expected := make([]observer.LoggedEntry, 6) + for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, InfoLevel} { + expected[i] = observer.LoggedEntry{ + Entry: zapcore.Entry{Message: tt.expect, Level: lvl}, + Context: expectedFields, + } + } + assert.Equal(t, expected, logs.AllUntimed(), "Unexpected log output.") + }) + } +} + +func TestSugarLnLoggingIgnored(t *testing.T) { + withSugar(t, WarnLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.Infoln("hello") + assert.Zero(t, logs.Len(), "Expected zero log statements.") + }) +} + +func TestSugarPanicLogging(t *testing.T) { + tests := []struct { + loggerLevel zapcore.Level + f func(*SugaredLogger) + expectedMsg string + }{ + {FatalLevel, func(s *SugaredLogger) { s.Panic("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panic("foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicf("%s", "foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicw("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicw("foo") }, "foo"}, + {FatalLevel, func(s *SugaredLogger) { s.Panicln("foo") }, ""}, + {PanicLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Panicln("foo") }, "foo"}, + } + + for _, tt := range tests { + withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) { + assert.Panics(t, func() { tt.f(sugar) }, "Expected panic-level logger calls to panic.") + if tt.expectedMsg != "" { + assert.Equal(t, []observer.LoggedEntry{{ + Context: []Field{}, + Entry: zapcore.Entry{Message: tt.expectedMsg, Level: PanicLevel}, + }}, logs.AllUntimed(), "Unexpected log output.") + } else { + assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.") + } + }) + } +} + +func TestSugarFatalLogging(t *testing.T) { + tests := []struct { + loggerLevel zapcore.Level + f func(*SugaredLogger) + expectedMsg string + }{ + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatal("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatal("foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalf("%s", "foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalw("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalw("foo") }, "foo"}, + {FatalLevel + 1, func(s *SugaredLogger) { s.Fatalln("foo") }, ""}, + {FatalLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"}, + {DebugLevel, func(s *SugaredLogger) { s.Fatalln("foo") }, "foo"}, + } + + for _, tt := range tests { + withSugar(t, tt.loggerLevel, nil, func(sugar *SugaredLogger, logs *observer.ObservedLogs) { + stub := exit.WithStub(func() { tt.f(sugar) }) + assert.True(t, stub.Exited, "Expected all calls to fatal logger methods to exit process.") + if tt.expectedMsg != "" { + assert.Equal(t, []observer.LoggedEntry{{ + Context: []Field{}, + Entry: zapcore.Entry{Message: tt.expectedMsg, Level: FatalLevel}, + }}, logs.AllUntimed(), "Unexpected log output.") + } else { + assert.Equal(t, 0, logs.Len(), "Didn't expect any log output.") + } + }) + } +} + +func TestSugarAddCaller(t *testing.T) { + tests := []struct { + options []Option + pat string + }{ + {opts(AddCaller()), `.+/sugar_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(-1)), `.+/sugar_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1)), `.+/common_test.go:[\d]+$`}, + {opts(AddCaller(), AddCallerSkip(1), AddCallerSkip(5)), `.+/src/runtime/.*:[\d]+$`}, + } + for _, tt := range tests { + withSugar(t, DebugLevel, tt.options, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger.Info("") + output := logs.AllUntimed() + assert.Equal(t, 1, len(output), "Unexpected number of logs written out.") + assert.Regexp( + t, + tt.pat, + output[0].Caller, + "Expected to find package name and file name in output.", + ) + }) + } +} + +func TestSugarAddCallerFail(t *testing.T) { + errBuf := &ztest.Buffer{} + withSugar(t, DebugLevel, opts(AddCaller(), AddCallerSkip(1e3), ErrorOutput(errBuf)), func(log *SugaredLogger, logs *observer.ObservedLogs) { + log.Info("Failure.") + assert.Regexp( + t, + `Logger.check error: failed to get caller`, + errBuf.String(), + "Didn't find expected failure message.", + ) + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "Failure.", + "Expected original message to survive failures in runtime.Caller.") + }) +} + +func TestSugarWithOptionsIncreaseLevel(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger = logger.WithOptions(IncreaseLevel(WarnLevel)) + logger.Info("logger.Info") + logger.Warn("logger.Warn") + logger.Error("logger.Error") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warn", + "Expected first logged message to be warn level message", + ) + }) +} + +func TestSugarLnWithOptionsIncreaseLevel(t *testing.T) { + withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { + logger = logger.WithOptions(IncreaseLevel(WarnLevel)) + logger.Infoln("logger.Infoln") + logger.Warnln("logger.Warnln") + logger.Errorln("logger.Errorln") + require.Equal(t, 2, logs.Len(), "expected only warn + error logs due to IncreaseLevel.") + assert.Equal( + t, + logs.AllUntimed()[0].Message, + "logger.Warnln", + "Expected first logged message to be warn level message", + ) + }) +} + +func BenchmarkSugarSingleStrArg(b *testing.B) { + withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for i := 0; i < b.N; i++ { + log.Info("hello world") + } + }) +} + +func BenchmarkLnSugarSingleStrArg(b *testing.B) { + withSugar(b, InfoLevel, nil /* opts* */, func(log *SugaredLogger, logs *observer.ObservedLogs) { + for i := 0; i < b.N; i++ { + log.Infoln("hello world") + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time.go new file mode 100644 index 0000000..c5a1f16 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time.go @@ -0,0 +1,27 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import "time" + +func timeToMillis(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time_test.go new file mode 100644 index 0000000..cb993ab --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/time_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimeToMillis(t *testing.T) { + tests := []struct { + t time.Time + stamp int64 + }{ + {t: time.Unix(0, 0), stamp: 0}, + {t: time.Unix(1, 0), stamp: 1000}, + {t: time.Unix(1, int64(500*time.Millisecond)), stamp: 1500}, + } + for _, tt := range tests { + assert.Equal(t, tt.stamp, timeToMillis(tt.t), "Unexpected timestamp for time %v.", tt.t) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer.go new file mode 100644 index 0000000..06768c6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer.go @@ -0,0 +1,98 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "fmt" + "io" + + "go.uber.org/zap/zapcore" + + "go.uber.org/multierr" +) + +// Open is a high-level wrapper that takes a variadic number of URLs, opens or +// creates each of the specified resources, and combines them into a locked +// WriteSyncer. It also returns any error encountered and a function to close +// any opened files. +// +// Passing no URLs returns a no-op WriteSyncer. Zap handles URLs without a +// scheme and URLs with the "file" scheme. Third-party code may register +// factories for other schemes using RegisterSink. +// +// URLs with the "file" scheme must use absolute paths on the local +// filesystem. No user, password, port, fragments, or query parameters are +// allowed, and the hostname must be empty or "localhost". +// +// Since it's common to write logs to the local filesystem, URLs without a +// scheme (e.g., "/var/log/foo.log") are treated as local file paths. Without +// a scheme, the special paths "stdout" and "stderr" are interpreted as +// os.Stdout and os.Stderr. When specified without a scheme, relative file +// paths also work. +func Open(paths ...string) (zapcore.WriteSyncer, func(), error) { + writers, closeAll, err := open(paths) + if err != nil { + return nil, nil, err + } + + writer := CombineWriteSyncers(writers...) + return writer, closeAll, nil +} + +func open(paths []string) ([]zapcore.WriteSyncer, func(), error) { + writers := make([]zapcore.WriteSyncer, 0, len(paths)) + closers := make([]io.Closer, 0, len(paths)) + closeAll := func() { + for _, c := range closers { + _ = c.Close() + } + } + + var openErr error + for _, path := range paths { + sink, err := _sinkRegistry.newSink(path) + if err != nil { + openErr = multierr.Append(openErr, fmt.Errorf("open sink %q: %w", path, err)) + continue + } + writers = append(writers, sink) + closers = append(closers, sink) + } + if openErr != nil { + closeAll() + return nil, nil, openErr + } + + return writers, closeAll, nil +} + +// CombineWriteSyncers is a utility that combines multiple WriteSyncers into a +// single, locked WriteSyncer. If no inputs are supplied, it returns a no-op +// WriteSyncer. +// +// It's provided purely as a convenience; the result is no different from +// using zapcore.NewMultiWriteSyncer and zapcore.Lock individually. +func CombineWriteSyncers(writers ...zapcore.WriteSyncer) zapcore.WriteSyncer { + if len(writers) == 0 { + return zapcore.AddSync(io.Discard) + } + return zapcore.Lock(zapcore.NewMultiWriteSyncer(writers...)) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer_test.go new file mode 100644 index 0000000..20e00b7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/writer_test.go @@ -0,0 +1,266 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zap + +import ( + "errors" + "io" + "io/fs" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" + "go.uber.org/zap/zapcore" +) + +func TestOpenNoPaths(t *testing.T) { + ws, cleanup, err := Open() + defer cleanup() + + assert.NoError(t, err, "Expected opening no paths to succeed.") + assert.Equal( + t, + zapcore.AddSync(io.Discard), + ws, + "Expected opening no paths to return a no-op WriteSyncer.", + ) +} + +func TestOpen(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + assert.False(t, fileExists(tempName)) + require.True(t, filepath.IsAbs(tempName), "Expected absolute temp file path.") + + tests := []struct { + msg string + paths []string + }{ + { + msg: "stdout", + paths: []string{"stdout"}, + }, + { + msg: "stderr", + paths: []string{"stderr"}, + }, + { + msg: "temp file path only", + paths: []string{tempName}, + }, + { + msg: "temp file file scheme", + paths: []string{"file://" + tempName}, + }, + { + msg: "temp file with file scheme and host localhost", + paths: []string{"file://localhost" + tempName}, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if err == nil { + defer cleanup() + } + + assert.NoError(t, err, "Unexpected error opening paths %v.", tt.paths) + }) + } + + assert.True(t, fileExists(tempName)) +} + +func TestOpenPathsNotFound(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + + tests := []struct { + msg string + paths []string + wantNotFoundPaths []string + }{ + { + msg: "missing path", + paths: []string{"/foo/bar/baz"}, + wantNotFoundPaths: []string{"/foo/bar/baz"}, + }, + { + msg: "missing file scheme url with host localhost", + paths: []string{"file://localhost/foo/bar/baz"}, + wantNotFoundPaths: []string{"/foo/bar/baz"}, + }, + { + msg: "multiple paths", + paths: []string{"stdout", "/foo/bar/baz", tempName, "file:///baz/quux"}, + wantNotFoundPaths: []string{ + "/foo/bar/baz", + "/baz/quux", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if !assert.Error(t, err, "Open must fail.") { + cleanup() + return + } + + errs := multierr.Errors(err) + require.Len(t, errs, len(tt.wantNotFoundPaths)) + for i, err := range errs { + assert.ErrorIs(t, err, fs.ErrNotExist) + assert.ErrorContains(t, err, tt.wantNotFoundPaths[i], "missing path in error") + } + }) + } +} + +func TestOpenRelativePath(t *testing.T) { + const name = "test-relative-path.txt" + + require.False(t, fileExists(name), "Test file already exists.") + s, cleanup, err := Open(name) + require.NoError(t, err, "Open failed.") + defer func() { + err := os.Remove(name) + if !t.Failed() { + // If the test has already failed, we probably didn't create this file. + require.NoError(t, err, "Deleting test file failed.") + } + }() + defer cleanup() + + _, err = s.Write([]byte("test")) + assert.NoError(t, err, "Write failed.") + assert.True(t, fileExists(name), "Didn't create file for relative path.") +} + +func TestOpenFails(t *testing.T) { + tests := []struct { + paths []string + }{ + {paths: []string{"./non-existent-dir/file"}}, // directory doesn't exist + {paths: []string{"stdout", "./non-existent-dir/file"}}, // directory doesn't exist + {paths: []string{"://foo.log"}}, // invalid URL, scheme can't begin with colon + {paths: []string{"mem://somewhere"}}, // scheme not registered + } + + for _, tt := range tests { + _, cleanup, err := Open(tt.paths...) + require.Nil(t, cleanup, "Cleanup function should never be nil") + assert.Error(t, err, "Open with invalid URL should fail.") + } +} + +func TestOpenOtherErrors(t *testing.T) { + tempName := filepath.Join(t.TempDir(), "test.log") + + tests := []struct { + msg string + paths []string + wantErr string + }{ + { + msg: "file with unexpected host", + paths: []string{"file://host01.test.com" + tempName}, + wantErr: "empty or use localhost", + }, + { + msg: "file with user on localhost", + paths: []string{"file://rms@localhost" + tempName}, + wantErr: "user and password not allowed", + }, + { + msg: "file url with fragment", + paths: []string{"file://localhost" + tempName + "#foo"}, + wantErr: "fragments not allowed", + }, + { + msg: "file url with query", + paths: []string{"file://localhost" + tempName + "?foo=bar"}, + wantErr: "query parameters not allowed", + }, + { + msg: "file with port", + paths: []string{"file://localhost:8080" + tempName}, + wantErr: "ports not allowed", + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + _, cleanup, err := Open(tt.paths...) + if !assert.Error(t, err, "Open must fail.") { + cleanup() + return + } + + assert.ErrorContains(t, err, tt.wantErr, "Unexpected error opening paths %v.", tt.paths) + }) + } +} + +type testWriter struct { + expected string + t testing.TB +} + +func (w *testWriter) Write(actual []byte) (int, error) { + assert.Equal(w.t, []byte(w.expected), actual, "Unexpected write error.") + return len(actual), nil +} + +func (w *testWriter) Sync() error { + return nil +} + +func TestOpenWithErroringSinkFactory(t *testing.T) { + stubSinkRegistry(t) + + msg := "expected factory error" + factory := func(_ *url.URL) (Sink, error) { + return nil, errors.New(msg) + } + + assert.NoError(t, RegisterSink("test", factory), "Failed to register sink factory.") + _, _, err := Open("test://some/path") + assert.ErrorContains(t, err, msg) +} + +func TestCombineWriteSyncers(t *testing.T) { + tw := &testWriter{"test", t} + w := CombineWriteSyncers(tw) + _, err := w.Write([]byte("test")) + assert.NoError(t, err, "Unexpected write error.") +} + +func fileExists(name string) bool { + if _, err := os.Stat(name); os.IsNotExist(err) { + return false + } + return true +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer.go new file mode 100644 index 0000000..a40e93b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer.go @@ -0,0 +1,219 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bufio" + "sync" + "time" + + "go.uber.org/multierr" +) + +const ( + // _defaultBufferSize specifies the default size used by Buffer. + _defaultBufferSize = 256 * 1024 // 256 kB + + // _defaultFlushInterval specifies the default flush interval for + // Buffer. + _defaultFlushInterval = 30 * time.Second +) + +// A BufferedWriteSyncer is a WriteSyncer that buffers writes in-memory before +// flushing them to a wrapped WriteSyncer after reaching some limit, or at some +// fixed interval--whichever comes first. +// +// BufferedWriteSyncer is safe for concurrent use. You don't need to use +// zapcore.Lock for WriteSyncers with BufferedWriteSyncer. +// +// To set up a BufferedWriteSyncer, construct a WriteSyncer for your log +// destination (*os.File is a valid WriteSyncer), wrap it with +// BufferedWriteSyncer, and defer a Stop() call for when you no longer need the +// object. +// +// func main() { +// ws := ... // your log destination +// bws := &zapcore.BufferedWriteSyncer{WS: ws} +// defer bws.Stop() +// +// // ... +// core := zapcore.NewCore(enc, bws, lvl) +// logger := zap.New(core) +// +// // ... +// } +// +// By default, a BufferedWriteSyncer will buffer up to 256 kilobytes of logs, +// waiting at most 30 seconds between flushes. +// You can customize these parameters by setting the Size or FlushInterval +// fields. +// For example, the following buffers up to 512 kB of logs before flushing them +// to Stderr, with a maximum of one minute between each flush. +// +// ws := &BufferedWriteSyncer{ +// WS: os.Stderr, +// Size: 512 * 1024, // 512 kB +// FlushInterval: time.Minute, +// } +// defer ws.Stop() +type BufferedWriteSyncer struct { + // WS is the WriteSyncer around which BufferedWriteSyncer will buffer + // writes. + // + // This field is required. + WS WriteSyncer + + // Size specifies the maximum amount of data the writer will buffered + // before flushing. + // + // Defaults to 256 kB if unspecified. + Size int + + // FlushInterval specifies how often the writer should flush data if + // there have been no writes. + // + // Defaults to 30 seconds if unspecified. + FlushInterval time.Duration + + // Clock, if specified, provides control of the source of time for the + // writer. + // + // Defaults to the system clock. + Clock Clock + + // unexported fields for state + mu sync.Mutex + initialized bool // whether initialize() has run + stopped bool // whether Stop() has run + writer *bufio.Writer + ticker *time.Ticker + stop chan struct{} // closed when flushLoop should stop + done chan struct{} // closed when flushLoop has stopped +} + +func (s *BufferedWriteSyncer) initialize() { + size := s.Size + if size == 0 { + size = _defaultBufferSize + } + + flushInterval := s.FlushInterval + if flushInterval == 0 { + flushInterval = _defaultFlushInterval + } + + if s.Clock == nil { + s.Clock = DefaultClock + } + + s.ticker = s.Clock.NewTicker(flushInterval) + s.writer = bufio.NewWriterSize(s.WS, size) + s.stop = make(chan struct{}) + s.done = make(chan struct{}) + s.initialized = true + go s.flushLoop() +} + +// Write writes log data into buffer syncer directly, multiple Write calls will be batched, +// and log data will be flushed to disk when the buffer is full or periodically. +func (s *BufferedWriteSyncer) Write(bs []byte) (int, error) { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + s.initialize() + } + + // To avoid partial writes from being flushed, we manually flush the existing buffer if: + // * The current write doesn't fit into the buffer fully, and + // * The buffer is not empty (since bufio will not split large writes when the buffer is empty) + if len(bs) > s.writer.Available() && s.writer.Buffered() > 0 { + if err := s.writer.Flush(); err != nil { + return 0, err + } + } + + return s.writer.Write(bs) +} + +// Sync flushes buffered log data into disk directly. +func (s *BufferedWriteSyncer) Sync() error { + s.mu.Lock() + defer s.mu.Unlock() + + var err error + if s.initialized { + err = s.writer.Flush() + } + + return multierr.Append(err, s.WS.Sync()) +} + +// flushLoop flushes the buffer at the configured interval until Stop is +// called. +func (s *BufferedWriteSyncer) flushLoop() { + defer close(s.done) + + for { + select { + case <-s.ticker.C: + // we just simply ignore error here + // because the underlying bufio writer stores any errors + // and we return any error from Sync() as part of the close + _ = s.Sync() + case <-s.stop: + return + } + } +} + +// Stop closes the buffer, cleans up background goroutines, and flushes +// remaining unwritten data. +func (s *BufferedWriteSyncer) Stop() (err error) { + var stopped bool + + // Critical section. + func() { + s.mu.Lock() + defer s.mu.Unlock() + + if !s.initialized { + return + } + + stopped = s.stopped + if stopped { + return + } + s.stopped = true + + s.ticker.Stop() + close(s.stop) // tell flushLoop to stop + <-s.done // and wait until it has + }() + + // Don't call Sync on consecutive Stops. + if !stopped { + err = s.Sync() + } + + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_bench_test.go new file mode 100644 index 0000000..56ad5f2 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_bench_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func BenchmarkBufferedWriteSyncer(b *testing.B) { + b.Run("write file with buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + defer func() { + assert.NoError(b, file.Close()) + }() + + w := &BufferedWriteSyncer{ + WS: AddSync(file), + } + defer func() { + assert.NoError(b, w.Stop(), "failed to stop buffered write syncer") + }() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_test.go new file mode 100644 index 0000000..d0f6037 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/buffered_write_syncer_test.go @@ -0,0 +1,140 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func TestBufferWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + t.Run("sync", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("stop", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf)} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + assert.NoError(t, ws.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("stop twice", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}} + _, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + assert.Error(t, ws.Stop(), "Expected stop to fail.") + assert.NoError(t, ws.Stop(), "Expected stop to not fail.") + }) + + t.Run("wrap twice", func(t *testing.T) { + buf := &bytes.Buffer{} + bufsync := &BufferedWriteSyncer{WS: AddSync(buf)} + ws := &BufferedWriteSyncer{WS: bufsync} + requireWriteWorks(t, ws) + assert.Empty(t, buf.String(), "Unexpected log calling a no-op Write method.") + require.NoError(t, ws.Sync()) + assert.Equal(t, "foo", buf.String()) + assert.NoError(t, ws.Stop()) + assert.NoError(t, bufsync.Stop()) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + }) + + t.Run("small buffer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: AddSync(buf), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("with lockedWriteSyncer", func(t *testing.T) { + buf := &bytes.Buffer{} + ws := &BufferedWriteSyncer{WS: Lock(AddSync(buf)), Size: 5} + + requireWriteWorks(t, ws) + assert.Equal(t, "", buf.String(), "Unexpected log calling a no-op Write method.") + requireWriteWorks(t, ws) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) + + t.Run("flush error", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: &ztest.FailWriter{}, Size: 4} + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") + _, err = ws.Write([]byte("foo")) + assert.Error(t, err, "Expected error writing to WriteSyncer.") + assert.Error(t, ws.Stop(), "Expected stop to fail.") + }) + + t.Run("flush timer", func(t *testing.T) { + buf := &bytes.Buffer{} + clock := ztest.NewMockClock() + ws := &BufferedWriteSyncer{ + WS: AddSync(buf), + Size: 6, + FlushInterval: time.Microsecond, + Clock: clock, + } + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foo", buf.String(), "Unexpected log string") + + // flush twice to validate loop logic + requireWriteWorks(t, ws) + clock.Add(10 * time.Microsecond) + assert.Equal(t, "foofoo", buf.String(), "Unexpected log string") + assert.NoError(t, ws.Stop()) + }) +} + +func TestBufferWriterWithoutStart(t *testing.T) { + t.Run("stop", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Stop(), "Stop must not fail") + }) + + t.Run("Sync", func(t *testing.T) { + ws := &BufferedWriteSyncer{WS: AddSync(new(bytes.Buffer))} + assert.NoError(t, ws.Sync(), "Sync must not fail") + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock.go new file mode 100644 index 0000000..422fd82 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock.go @@ -0,0 +1,48 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// DefaultClock is the default clock used by Zap in operations that require +// time. This clock uses the system clock for all operations. +var DefaultClock = systemClock{} + +// Clock is a source of time for logged entries. +type Clock interface { + // Now returns the current local time. + Now() time.Time + + // NewTicker returns *time.Ticker that holds a channel + // that delivers "ticks" of a clock. + NewTicker(time.Duration) *time.Ticker +} + +// systemClock implements default Clock that uses system time. +type systemClock struct{} + +func (systemClock) Now() time.Time { + return time.Now() +} + +func (systemClock) NewTicker(duration time.Duration) *time.Ticker { + return time.NewTicker(duration) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock_test.go new file mode 100644 index 0000000..0dff349 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/clock_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "go.uber.org/zap/internal/ztest" +) + +// Verify that the mock clock satisfies the Clock interface. +var _ Clock = (*ztest.MockClock)(nil) + +func TestSystemClock_NewTicker(t *testing.T) { + want := 3 + + var n int + timer := DefaultClock.NewTicker(time.Millisecond) + for range timer.C { + n++ + if n == want { + return + } + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder.go new file mode 100644 index 0000000..cc2b4e0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder.go @@ -0,0 +1,157 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +var _sliceEncoderPool = pool.New(func() *sliceArrayEncoder { + return &sliceArrayEncoder{ + elems: make([]interface{}, 0, 2), + } +}) + +func getSliceEncoder() *sliceArrayEncoder { + return _sliceEncoderPool.Get() +} + +func putSliceEncoder(e *sliceArrayEncoder) { + e.elems = e.elems[:0] + _sliceEncoderPool.Put(e) +} + +type consoleEncoder struct { + *jsonEncoder +} + +// NewConsoleEncoder creates an encoder whose output is designed for human - +// rather than machine - consumption. It serializes the core log entry data +// (message, level, timestamp, etc.) in a plain-text format and leaves the +// structured context as JSON. +// +// Note that although the console encoder doesn't use the keys specified in the +// encoder configuration, it will omit any element whose key is set to the empty +// string. +func NewConsoleEncoder(cfg EncoderConfig) Encoder { + if cfg.ConsoleSeparator == "" { + // Use a default delimiter of '\t' for backwards compatibility + cfg.ConsoleSeparator = "\t" + } + return consoleEncoder{newJSONEncoder(cfg, true)} +} + +func (c consoleEncoder) Clone() Encoder { + return consoleEncoder{c.jsonEncoder.Clone().(*jsonEncoder)} +} + +func (c consoleEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + line := bufferpool.Get() + + // We don't want the entry's metadata to be quoted and escaped (if it's + // encoded as strings), which means that we can't use the JSON encoder. The + // simplest option is to use the memory encoder and fmt.Fprint. + // + // If this ever becomes a performance bottleneck, we can implement + // ArrayEncoder for our plain-text format. + arr := getSliceEncoder() + if c.TimeKey != "" && c.EncodeTime != nil && !ent.Time.IsZero() { + c.EncodeTime(ent.Time, arr) + } + if c.LevelKey != "" && c.EncodeLevel != nil { + c.EncodeLevel(ent.Level, arr) + } + if ent.LoggerName != "" && c.NameKey != "" { + nameEncoder := c.EncodeName + + if nameEncoder == nil { + // Fall back to FullNameEncoder for backward compatibility. + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, arr) + } + if ent.Caller.Defined { + if c.CallerKey != "" && c.EncodeCaller != nil { + c.EncodeCaller(ent.Caller, arr) + } + if c.FunctionKey != "" { + arr.AppendString(ent.Caller.Function) + } + } + for i := range arr.elems { + if i > 0 { + line.AppendString(c.ConsoleSeparator) + } + fmt.Fprint(line, arr.elems[i]) + } + putSliceEncoder(arr) + + // Add the message itself. + if c.MessageKey != "" { + c.addSeparatorIfNecessary(line) + line.AppendString(ent.Message) + } + + // Add any structured context. + c.writeContext(line, fields) + + // If there's no stacktrace key, honor that; this allows users to force + // single-line output. + if ent.Stack != "" && c.StacktraceKey != "" { + line.AppendByte('\n') + line.AppendString(ent.Stack) + } + + line.AppendString(c.LineEnding) + return line, nil +} + +func (c consoleEncoder) writeContext(line *buffer.Buffer, extra []Field) { + context := c.jsonEncoder.Clone().(*jsonEncoder) + defer func() { + // putJSONEncoder assumes the buffer is still used, but we write out the buffer so + // we can free it. + context.buf.Free() + putJSONEncoder(context) + }() + + addFields(context, extra) + context.closeOpenNamespaces() + if context.buf.Len() == 0 { + return + } + + c.addSeparatorIfNecessary(line) + line.AppendByte('{') + line.Write(context.buf.Bytes()) + line.AppendByte('}') +} + +func (c consoleEncoder) addSeparatorIfNecessary(line *buffer.Buffer) { + if line.Len() > 0 { + line.AppendString(c.ConsoleSeparator) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_bench_test.go new file mode 100644 index 0000000..a2a360f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_bench_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +func BenchmarkZapConsole(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewConsoleEncoder(humanEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_test.go new file mode 100644 index 0000000..be8adc7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/console_encoder_test.go @@ -0,0 +1,135 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +package zapcore_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +var testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, +} + +func TestConsoleEncodeEntry(t *testing.T) { + tests := []struct { + desc string + expected string + ent Entry + fields []Field + }{ + { + desc: "info no fields", + expected: "2018-06-19T16:33:42Z\tinfo\tbob\tlob law\n", + ent: Entry{ + Level: InfoLevel, + Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), + LoggerName: "bob", + Message: "lob law", + }, + }, + { + desc: "zero_time_omitted", + expected: "info\tname\tmessage\n", + ent: Entry{ + Level: InfoLevel, + Time: time.Time{}, + LoggerName: "name", + Message: "message", + }, + }, + } + + cfg := testEncoderConfig() + cfg.EncodeTime = RFC3339TimeEncoder + enc := NewConsoleEncoder(cfg) + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + buf, err := enc.EncodeEntry(tt.ent, tt.fields) + if assert.NoError(t, err, "Unexpected console encoding error.") { + assert.Equal(t, tt.expected, buf.String(), "Incorrect encoded entry.") + } + buf.Free() + }) + } +} + +func TestConsoleSeparator(t *testing.T) { + tests := []struct { + desc string + separator string + wantConsole string + }{ + { + desc: "space console separator", + separator: " ", + wantConsole: "0 info main foo.go:42 foo.Foo hello\nfake-stack\n", + }, + { + desc: "default console separator", + separator: "", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "tag console separator", + separator: "\t", + wantConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "dash console separator", + separator: "--", + wantConsole: "0--info--main--foo.go:42--foo.Foo--hello\nfake-stack\n", + }, + } + + for _, tt := range tests { + console := NewConsoleEncoder(encoderTestEncoderConfig(tt.separator)) + t.Run(tt.desc, func(t *testing.T) { + entry := testEntry + consoleOut, err := console.EncodeEntry(entry, nil) + if !assert.NoError(t, err) { + return + } + assert.Equal( + t, + tt.wantConsole, + consoleOut.String(), + "Unexpected console output", + ) + }) + + } +} + +func encoderTestEncoderConfig(separator string) EncoderConfig { + testEncoder := testEncoderConfig() + testEncoder.ConsoleSeparator = separator + return testEncoder +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core.go new file mode 100644 index 0000000..776e93f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// Core is a minimal, fast logger interface. It's designed for library authors +// to wrap in a more user-friendly API. +type Core interface { + LevelEnabler + + // With adds structured context to the Core. + With([]Field) Core + // Check determines whether the supplied Entry should be logged (using the + // embedded LevelEnabler and possibly some extra logic). If the entry + // should be logged, the Core adds itself to the CheckedEntry and returns + // the result. + // + // Callers must use Check before calling Write. + Check(Entry, *CheckedEntry) *CheckedEntry + // Write serializes the Entry and any Fields supplied at the log site and + // writes them to their destination. + // + // If called, Write should always log the Entry and Fields; it should not + // replicate the logic of Check. + Write(Entry, []Field) error + // Sync flushes buffered logs (if any). + Sync() error +} + +type nopCore struct{} + +// NewNopCore returns a no-op Core. +func NewNopCore() Core { return nopCore{} } +func (nopCore) Enabled(Level) bool { return false } +func (n nopCore) With([]Field) Core { return n } +func (nopCore) Check(_ Entry, ce *CheckedEntry) *CheckedEntry { return ce } +func (nopCore) Write(Entry, []Field) error { return nil } +func (nopCore) Sync() error { return nil } + +// NewCore creates a Core that writes logs to a WriteSyncer. +func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core { + return &ioCore{ + LevelEnabler: enab, + enc: enc, + out: ws, + } +} + +type ioCore struct { + LevelEnabler + enc Encoder + out WriteSyncer +} + +var ( + _ Core = (*ioCore)(nil) + _ leveledEnabler = (*ioCore)(nil) +) + +func (c *ioCore) Level() Level { + return LevelOf(c.LevelEnabler) +} + +func (c *ioCore) With(fields []Field) Core { + clone := c.clone() + addFields(clone.enc, fields) + return clone +} + +func (c *ioCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if c.Enabled(ent.Level) { + return ce.AddCore(ent, c) + } + return ce +} + +func (c *ioCore) Write(ent Entry, fields []Field) error { + buf, err := c.enc.EncodeEntry(ent, fields) + if err != nil { + return err + } + _, err = c.out.Write(buf.Bytes()) + buf.Free() + if err != nil { + return err + } + if ent.Level > ErrorLevel { + // Since we may be crashing the program, sync the output. + // Ignore Sync errors, pending a clean solution to issue #370. + _ = c.Sync() + } + return nil +} + +func (c *ioCore) Sync() error { + return c.out.Sync() +} + +func (c *ioCore) clone() *ioCore { + return &ioCore{ + LevelEnabler: c.LevelEnabler, + enc: c.enc.Clone(), + out: c.out, + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core_test.go new file mode 100644 index 0000000..3b23d2d --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/core_test.go @@ -0,0 +1,166 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "os" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func makeInt64Field(key string, val int) Field { + return Field{Type: Int64Type, Integer: int64(val), Key: key} +} + +func TestNopCore(t *testing.T) { + entry := Entry{ + Message: "test", + Level: InfoLevel, + Time: time.Now(), + LoggerName: "main", + Stack: "fake-stack", + } + ce := &CheckedEntry{} + + allLevels := []Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + DPanicLevel, + PanicLevel, + FatalLevel, + } + core := NewNopCore() + assert.Equal(t, core, core.With([]Field{makeInt64Field("k", 42)}), "Expected no-op With.") + for _, level := range allLevels { + assert.False(t, core.Enabled(level), "Expected all levels to be disabled in no-op core.") + assert.Equal(t, ce, core.Check(entry, ce), "Expected no-op Check to return checked entry unchanged.") + assert.NoError(t, core.Write(entry, nil), "Expected no-op Writes to always succeed.") + assert.NoError(t, core.Sync(), "Expected no-op Syncs to always succeed.") + } +} + +func TestIOCore(t *testing.T) { + temp, err := os.CreateTemp(t.TempDir(), "test.log") + require.NoError(t, err) + + // Drop timestamps for simpler assertions (timestamp encoding is tested + // elsewhere). + cfg := testEncoderConfig() + cfg.TimeKey = "" + + core := NewCore( + NewJSONEncoder(cfg), + temp, + InfoLevel, + ).With([]Field{makeInt64Field("k", 1)}) + defer assert.NoError(t, core.Sync(), "Expected Syncing a temp file to succeed.") + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, InfoLevel, LevelOf(core), "Incorrect Core Level") + }) + + if ce := core.Check(Entry{Level: DebugLevel, Message: "debug"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 2)) + } + if ce := core.Check(Entry{Level: InfoLevel, Message: "info"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 3)) + } + if ce := core.Check(Entry{Level: WarnLevel, Message: "warn"}, nil); ce != nil { + ce.Write(makeInt64Field("k", 4)) + } + + logged, err := os.ReadFile(temp.Name()) + require.NoError(t, err, "Failed to read from temp file.") + require.Equal( + t, + `{"level":"info","msg":"info","k":1,"k":3}`+"\n"+ + `{"level":"warn","msg":"warn","k":1,"k":4}`+"\n", + string(logged), + "Unexpected log output.", + ) +} + +func TestIOCoreSyncFail(t *testing.T) { + sink := &ztest.Discarder{} + err := errors.New("failed") + sink.SetError(err) + + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + assert.Equal( + t, + err, + core.Sync(), + "Expected core.Sync to return errors from underlying WriteSyncer.", + ) +} + +func TestIOCoreSyncsOutput(t *testing.T) { + tests := []struct { + entry Entry + shouldSync bool + }{ + {Entry{Level: DebugLevel}, false}, + {Entry{Level: InfoLevel}, false}, + {Entry{Level: WarnLevel}, false}, + {Entry{Level: ErrorLevel}, false}, + {Entry{Level: DPanicLevel}, true}, + {Entry{Level: PanicLevel}, true}, + {Entry{Level: FatalLevel}, true}, + } + + for _, tt := range tests { + sink := &ztest.Discarder{} + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + + assert.NoError(t, core.Write(tt.entry, nil), "Unexpected error writing entry.") + assert.Equal(t, tt.shouldSync, sink.Called(), "Incorrect Sync behavior.") + } +} + +func TestIOCoreWriteFailure(t *testing.T) { + core := NewCore( + NewJSONEncoder(testEncoderConfig()), + Lock(&ztest.FailWriter{}), + DebugLevel, + ) + err := core.Write(Entry{}, nil) + // Should log the error. + assert.Error(t, err, "Expected writing Entry to fail.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/doc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/doc.go new file mode 100644 index 0000000..31000e9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/doc.go @@ -0,0 +1,24 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapcore defines and implements the low-level interfaces upon which +// zap is built. By providing alternate implementations of these interfaces, +// external packages can extend zap's capabilities. +package zapcore // import "go.uber.org/zap/zapcore" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder.go new file mode 100644 index 0000000..0446254 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder.go @@ -0,0 +1,466 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" + "time" + + "go.uber.org/zap/buffer" +) + +// DefaultLineEnding defines the default line ending when writing logs. +// Alternate line endings specified in EncoderConfig can override this +// behavior. +const DefaultLineEnding = "\n" + +// OmitKey defines the key to use when callers want to remove a key from log output. +const OmitKey = "" + +// A LevelEncoder serializes a Level to a primitive type. +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type LevelEncoder func(Level, PrimitiveArrayEncoder) + +// LowercaseLevelEncoder serializes a Level to a lowercase string. For example, +// InfoLevel is serialized to "info". +func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.String()) +} + +// LowercaseColorLevelEncoder serializes a Level to a lowercase string and adds coloring. +// For example, InfoLevel is serialized to "info" and colored blue. +func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToLowercaseColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.String()) + } + enc.AppendString(s) +} + +// CapitalLevelEncoder serializes a Level to an all-caps string. For example, +// InfoLevel is serialized to "INFO". +func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + enc.AppendString(l.CapitalString()) +} + +// CapitalColorLevelEncoder serializes a Level to an all-caps string and adds color. +// For example, InfoLevel is serialized to "INFO" and colored blue. +func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder) { + s, ok := _levelToCapitalColorString[l] + if !ok { + s = _unknownLevelColor.Add(l.CapitalString()) + } + enc.AppendString(s) +} + +// UnmarshalText unmarshals text to a LevelEncoder. "capital" is unmarshaled to +// CapitalLevelEncoder, "coloredCapital" is unmarshaled to CapitalColorLevelEncoder, +// "colored" is unmarshaled to LowercaseColorLevelEncoder, and anything else +// is unmarshaled to LowercaseLevelEncoder. +func (e *LevelEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "capital": + *e = CapitalLevelEncoder + case "capitalColor": + *e = CapitalColorLevelEncoder + case "color": + *e = LowercaseColorLevelEncoder + default: + *e = LowercaseLevelEncoder + } + return nil +} + +// A TimeEncoder serializes a time.Time to a primitive type. +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type TimeEncoder func(time.Time, PrimitiveArrayEncoder) + +// EpochTimeEncoder serializes a time.Time to a floating-point number of seconds +// since the Unix epoch. +func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + sec := float64(nanos) / float64(time.Second) + enc.AppendFloat64(sec) +} + +// EpochMillisTimeEncoder serializes a time.Time to a floating-point number of +// milliseconds since the Unix epoch. +func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + nanos := t.UnixNano() + millis := float64(nanos) / float64(time.Millisecond) + enc.AppendFloat64(millis) +} + +// EpochNanosTimeEncoder serializes a time.Time to an integer number of +// nanoseconds since the Unix epoch. +func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + enc.AppendInt64(t.UnixNano()) +} + +func encodeTimeLayout(t time.Time, layout string, enc PrimitiveArrayEncoder) { + type appendTimeEncoder interface { + AppendTimeLayout(time.Time, string) + } + + if enc, ok := enc.(appendTimeEncoder); ok { + enc.AppendTimeLayout(t, layout) + return + } + + enc.AppendString(t.Format(layout)) +} + +// ISO8601TimeEncoder serializes a time.Time to an ISO8601-formatted string +// with millisecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func ISO8601TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, "2006-01-02T15:04:05.000Z0700", enc) +} + +// RFC3339TimeEncoder serializes a time.Time to an RFC3339-formatted string. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339TimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339, enc) +} + +// RFC3339NanoTimeEncoder serializes a time.Time to an RFC3339-formatted string +// with nanosecond precision. +// +// If enc supports AppendTimeLayout(t time.Time,layout string), it's used +// instead of appending a pre-formatted string value. +func RFC3339NanoTimeEncoder(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, time.RFC3339Nano, enc) +} + +// TimeEncoderOfLayout returns TimeEncoder which serializes a time.Time using +// given layout. +func TimeEncoderOfLayout(layout string) TimeEncoder { + return func(t time.Time, enc PrimitiveArrayEncoder) { + encodeTimeLayout(t, layout, enc) + } +} + +// UnmarshalText unmarshals text to a TimeEncoder. +// "rfc3339nano" and "RFC3339Nano" are unmarshaled to RFC3339NanoTimeEncoder. +// "rfc3339" and "RFC3339" are unmarshaled to RFC3339TimeEncoder. +// "iso8601" and "ISO8601" are unmarshaled to ISO8601TimeEncoder. +// "millis" is unmarshaled to EpochMillisTimeEncoder. +// "nanos" is unmarshaled to EpochNanosEncoder. +// Anything else is unmarshaled to EpochTimeEncoder. +func (e *TimeEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "rfc3339nano", "RFC3339Nano": + *e = RFC3339NanoTimeEncoder + case "rfc3339", "RFC3339": + *e = RFC3339TimeEncoder + case "iso8601", "ISO8601": + *e = ISO8601TimeEncoder + case "millis": + *e = EpochMillisTimeEncoder + case "nanos": + *e = EpochNanosTimeEncoder + default: + *e = EpochTimeEncoder + } + return nil +} + +// UnmarshalYAML unmarshals YAML to a TimeEncoder. +// If value is an object with a "layout" field, it will be unmarshaled to TimeEncoder with given layout. +// +// timeEncoder: +// layout: 06/01/02 03:04pm +// +// If value is string, it uses UnmarshalText. +// +// timeEncoder: iso8601 +func (e *TimeEncoder) UnmarshalYAML(unmarshal func(interface{}) error) error { + var o struct { + Layout string `json:"layout" yaml:"layout"` + } + if err := unmarshal(&o); err == nil { + *e = TimeEncoderOfLayout(o.Layout) + return nil + } + + var s string + if err := unmarshal(&s); err != nil { + return err + } + return e.UnmarshalText([]byte(s)) +} + +// UnmarshalJSON unmarshals JSON to a TimeEncoder as same way UnmarshalYAML does. +func (e *TimeEncoder) UnmarshalJSON(data []byte) error { + return e.UnmarshalYAML(func(v interface{}) error { + return json.Unmarshal(data, v) + }) +} + +// A DurationEncoder serializes a time.Duration to a primitive type. +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type DurationEncoder func(time.Duration, PrimitiveArrayEncoder) + +// SecondsDurationEncoder serializes a time.Duration to a floating-point number of seconds elapsed. +func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendFloat64(float64(d) / float64(time.Second)) +} + +// NanosDurationEncoder serializes a time.Duration to an integer number of +// nanoseconds elapsed. +func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(int64(d)) +} + +// MillisDurationEncoder serializes a time.Duration to an integer number of +// milliseconds elapsed. +func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendInt64(d.Nanoseconds() / 1e6) +} + +// StringDurationEncoder serializes a time.Duration using its built-in String +// method. +func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder) { + enc.AppendString(d.String()) +} + +// UnmarshalText unmarshals text to a DurationEncoder. "string" is unmarshaled +// to StringDurationEncoder, and anything else is unmarshaled to +// NanosDurationEncoder. +func (e *DurationEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "string": + *e = StringDurationEncoder + case "nanos": + *e = NanosDurationEncoder + case "ms": + *e = MillisDurationEncoder + default: + *e = SecondsDurationEncoder + } + return nil +} + +// A CallerEncoder serializes an EntryCaller to a primitive type. +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type CallerEncoder func(EntryCaller, PrimitiveArrayEncoder) + +// FullCallerEncoder serializes a caller in /full/path/to/package/file:line +// format. +func FullCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.String()) +} + +// ShortCallerEncoder serializes a caller in package/file:line format, trimming +// all but the final directory from the full path. +func ShortCallerEncoder(caller EntryCaller, enc PrimitiveArrayEncoder) { + // TODO: consider using a byte-oriented API to save an allocation. + enc.AppendString(caller.TrimmedPath()) +} + +// UnmarshalText unmarshals text to a CallerEncoder. "full" is unmarshaled to +// FullCallerEncoder and anything else is unmarshaled to ShortCallerEncoder. +func (e *CallerEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullCallerEncoder + default: + *e = ShortCallerEncoder + } + return nil +} + +// A NameEncoder serializes a period-separated logger name to a primitive +// type. +// +// This function must make exactly one call +// to a PrimitiveArrayEncoder's Append* method. +type NameEncoder func(string, PrimitiveArrayEncoder) + +// FullNameEncoder serializes the logger name as-is. +func FullNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(loggerName) +} + +// UnmarshalText unmarshals text to a NameEncoder. Currently, everything is +// unmarshaled to FullNameEncoder. +func (e *NameEncoder) UnmarshalText(text []byte) error { + switch string(text) { + case "full": + *e = FullNameEncoder + default: + *e = FullNameEncoder + } + return nil +} + +// An EncoderConfig allows users to configure the concrete encoders supplied by +// zapcore. +type EncoderConfig struct { + // Set the keys used for each log entry. If any key is empty, that portion + // of the entry is omitted. + MessageKey string `json:"messageKey" yaml:"messageKey"` + LevelKey string `json:"levelKey" yaml:"levelKey"` + TimeKey string `json:"timeKey" yaml:"timeKey"` + NameKey string `json:"nameKey" yaml:"nameKey"` + CallerKey string `json:"callerKey" yaml:"callerKey"` + FunctionKey string `json:"functionKey" yaml:"functionKey"` + StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` + SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` + LineEnding string `json:"lineEnding" yaml:"lineEnding"` + // Configure the primitive representations of common complex types. For + // example, some users may want all time.Times serialized as floating-point + // seconds since epoch, while others may prefer ISO8601 strings. + EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` + EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` + EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` + EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` + // Unlike the other primitive type encoders, EncodeName is optional. The + // zero value falls back to FullNameEncoder. + EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` + // Configure the encoder for interface{} type objects. + // If not provided, objects are encoded using json.Encoder + NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` + // Configures the field separator used by the console encoder. Defaults + // to tab. + ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` +} + +// ObjectEncoder is a strongly-typed, encoding-agnostic interface for adding a +// map- or struct-like object to the logging context. Like maps, ObjectEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ObjectEncoder interface { + // Logging-specific marshalers. + AddArray(key string, marshaler ArrayMarshaler) error + AddObject(key string, marshaler ObjectMarshaler) error + + // Built-in types. + AddBinary(key string, value []byte) // for arbitrary bytes + AddByteString(key string, value []byte) // for UTF-8 encoded bytes + AddBool(key string, value bool) + AddComplex128(key string, value complex128) + AddComplex64(key string, value complex64) + AddDuration(key string, value time.Duration) + AddFloat64(key string, value float64) + AddFloat32(key string, value float32) + AddInt(key string, value int) + AddInt64(key string, value int64) + AddInt32(key string, value int32) + AddInt16(key string, value int16) + AddInt8(key string, value int8) + AddString(key, value string) + AddTime(key string, value time.Time) + AddUint(key string, value uint) + AddUint64(key string, value uint64) + AddUint32(key string, value uint32) + AddUint16(key string, value uint16) + AddUint8(key string, value uint8) + AddUintptr(key string, value uintptr) + + // AddReflected uses reflection to serialize arbitrary objects, so it can be + // slow and allocation-heavy. + AddReflected(key string, value interface{}) error + // OpenNamespace opens an isolated namespace where all subsequent fields will + // be added. Applications can use namespaces to prevent key collisions when + // injecting loggers into sub-components or third-party libraries. + OpenNamespace(key string) +} + +// ArrayEncoder is a strongly-typed, encoding-agnostic interface for adding +// array-like objects to the logging context. Of note, it supports mixed-type +// arrays even though they aren't typical in Go. Like slices, ArrayEncoders +// aren't safe for concurrent use (though typical use shouldn't require locks). +type ArrayEncoder interface { + // Built-in types. + PrimitiveArrayEncoder + + // Time-related types. + AppendDuration(time.Duration) + AppendTime(time.Time) + + // Logging-specific marshalers. + AppendArray(ArrayMarshaler) error + AppendObject(ObjectMarshaler) error + + // AppendReflected uses reflection to serialize arbitrary objects, so it's + // slow and allocation-heavy. + AppendReflected(value interface{}) error +} + +// PrimitiveArrayEncoder is the subset of the ArrayEncoder interface that deals +// only in Go's built-in types. It's included only so that Duration- and +// TimeEncoders cannot trigger infinite recursion. +type PrimitiveArrayEncoder interface { + // Built-in types. + AppendBool(bool) + AppendByteString([]byte) // for UTF-8 encoded bytes + AppendComplex128(complex128) + AppendComplex64(complex64) + AppendFloat64(float64) + AppendFloat32(float32) + AppendInt(int) + AppendInt64(int64) + AppendInt32(int32) + AppendInt16(int16) + AppendInt8(int8) + AppendString(string) + AppendUint(uint) + AppendUint64(uint64) + AppendUint32(uint32) + AppendUint16(uint16) + AppendUint8(uint8) + AppendUintptr(uintptr) +} + +// Encoder is a format-agnostic interface for all log entry marshalers. Since +// log encoders don't need to support the same wide range of use cases as +// general-purpose marshalers, it's possible to make them faster and +// lower-allocation. +// +// Implementations of the ObjectEncoder interface's methods can, of course, +// freely modify the receiver. However, the Clone and EncodeEntry methods will +// be called concurrently and shouldn't modify the receiver. +type Encoder interface { + ObjectEncoder + + // Clone copies the encoder, ensuring that adding fields to the copy doesn't + // affect the original. + Clone() Encoder + + // EncodeEntry encodes an entry and fields, along with any accumulated + // context, into a byte buffer and returns it. Any fields that are empty, + // including fields on the `Entry` type, should be omitted. + EncodeEntry(Entry, []Field) (*buffer.Buffer, error) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder_test.go new file mode 100644 index 0000000..f89f489 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/encoder_test.go @@ -0,0 +1,734 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +var ( + _epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + _testEntry = Entry{ + LoggerName: "main", + Level: InfoLevel, + Message: `hello`, + Time: _epoch, + Stack: "fake-stack", + Caller: EntryCaller{Defined: true, File: "foo.go", Line: 42, Function: "foo.Foo"}, + } +) + +func testEncoderConfig() EncoderConfig { + return EncoderConfig{ + MessageKey: "msg", + LevelKey: "level", + NameKey: "name", + TimeKey: "ts", + CallerKey: "caller", + FunctionKey: "func", + StacktraceKey: "stacktrace", + LineEnding: "\n", + EncodeTime: EpochTimeEncoder, + EncodeLevel: LowercaseLevelEncoder, + EncodeDuration: SecondsDurationEncoder, + EncodeCaller: ShortCallerEncoder, + } +} + +func humanEncoderConfig() EncoderConfig { + cfg := testEncoderConfig() + cfg.EncodeTime = ISO8601TimeEncoder + cfg.EncodeLevel = CapitalLevelEncoder + cfg.EncodeDuration = StringDurationEncoder + return cfg +} + +func capitalNameEncoder(loggerName string, enc PrimitiveArrayEncoder) { + enc.AppendString(strings.ToUpper(loggerName)) +} + +func TestEncoderConfiguration(t *testing.T) { + base := testEncoderConfig() + + tests := []struct { + desc string + cfg EncoderConfig + amendEntry func(Entry) Entry + extra func(Encoder) + expectedJSON string + expectedConsole string + }{ + { + desc: "messages to be escaped", + cfg: base, + amendEntry: func(ent Entry) Entry { + ent.Message = `hello\` + return ent + }, + expectedJSON: `{"level":"info","ts":0,"name":"main","caller":"foo.go:42","func":"foo.Foo","msg":"hello\\","stacktrace":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\\\nfake-stack\n", + }, + { + desc: "use custom entry keys in JSON output and ignore them in console output", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip line ending if SkipLineEnding is 'true'", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + SkipLineEnding: true, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}`, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack", + }, + { + desc: "skip level if LevelKey is omitted", + cfg: EncoderConfig{ + LevelKey: OmitKey, + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip timestamp if TimeKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: OmitKey, + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip message if MessageKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: OmitKey, + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\nfake-stack\n", + }, + { + desc: "skip name if NameKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: OmitKey, + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip caller if CallerKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: OmitKey, + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "skip function if FunctionKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: OmitKey, + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\thello\nfake-stack\n", + }, + { + desc: "skip stacktrace if StacktraceKey is omitted", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: OmitKey, + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\n", + }, + { + desc: "use the supplied EncodeTime, for both the entry and any times added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(t time.Time, enc PrimitiveArrayEncoder) { enc.AppendString(t.String()) }, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddTime("extra", _epoch) + err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(_epoch) + return nil + })) + assert.NoError(t, err) + }, + expectedJSON: `{"L":"info","T":"1970-01-01 00:00:00 +0000 UTC","N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1970-01-01 00:00:00 +0000 UTC","extras":["1970-01-01 00:00:00 +0000 UTC"],"S":"fake-stack"}` + "\n", + expectedConsole: "1970-01-01 00:00:00 +0000 UTC\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // plain-text preamble + `{"extra": "1970-01-01 00:00:00 +0000 UTC", "extras": ["1970-01-01 00:00:00 +0000 UTC"]}` + // JSON context + "\nfake-stack\n", // stacktrace after newline + }, + { + desc: "use the supplied EncodeDuration for any durations added", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: StringDurationEncoder, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.AddDuration("extra", time.Second) + err := enc.AddArray("extras", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendDuration(time.Minute) + return nil + })) + assert.NoError(t, err) + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","extra":"1s","extras":["1m0s"],"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + // preamble + `{"extra": "1s", "extras": ["1m0s"]}` + // context + "\nfake-stack\n", // stacktrace + }, + { + desc: "use the supplied EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: CapitalLevelEncoder, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"INFO","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tINFO\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use the supplied EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: capitalNameEncoder, + }, + expectedJSON: `{"L":"info","T":0,"N":"MAIN","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tMAIN\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "close all open namespaces", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { + enc.OpenNamespace("outer") + enc.OpenNamespace("inner") + enc.AddString("foo", "bar") + enc.OpenNamespace("innermost") + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","outer":{"inner":{"foo":"bar","innermost":{}}},"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + + `{"outer": {"inner": {"foo": "bar", "innermost": {}}}}` + + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeTime", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: func(time.Time, PrimitiveArrayEncoder) {}, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddTime("sometime", time.Unix(0, 100)) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","sometime":100,"S":"fake-stack"}` + "\n", + expectedConsole: "info\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"sometime": 100}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeDuration", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: func(time.Duration, PrimitiveArrayEncoder) {}, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + extra: func(enc Encoder) { enc.AddDuration("someduration", time.Microsecond) }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","someduration":1000,"S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\t" + `{"someduration": 1000}` + "\nfake-stack\n", + }, + { + desc: "handle no-op EncodeLevel", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: func(Level, PrimitiveArrayEncoder) {}, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeCaller", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: func(EntryCaller, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tmain\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "handle no-op EncodeName", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: base.LineEnding, + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + EncodeName: func(string, PrimitiveArrayEncoder) {}, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\n", + expectedConsole: "0\tinfo\tfoo.go:42\tfoo.Foo\thello\nfake-stack\n", + }, + { + desc: "use custom line separator", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + LineEnding: "\r\n", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + "\r\n", + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack\r\n", + }, + { + desc: "omit line separator definition - fall back to default", + cfg: EncoderConfig{ + LevelKey: "L", + TimeKey: "T", + MessageKey: "M", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: base.EncodeTime, + EncodeDuration: base.EncodeDuration, + EncodeLevel: base.EncodeLevel, + EncodeCaller: base.EncodeCaller, + }, + expectedJSON: `{"L":"info","T":0,"N":"main","C":"foo.go:42","F":"foo.Foo","M":"hello","S":"fake-stack"}` + DefaultLineEnding, + expectedConsole: "0\tinfo\tmain\tfoo.go:42\tfoo.Foo\thello\nfake-stack" + DefaultLineEnding, + }, + } + + for i, tt := range tests { + json := NewJSONEncoder(tt.cfg) + console := NewConsoleEncoder(tt.cfg) + if tt.extra != nil { + tt.extra(json) + tt.extra(console) + } + entry := _testEntry + if tt.amendEntry != nil { + entry = tt.amendEntry(_testEntry) + } + jsonOut, jsonErr := json.EncodeEntry(entry, nil) + if assert.NoError(t, jsonErr, "Unexpected error JSON-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedJSON, + jsonOut.String(), + "Unexpected JSON output: expected to %v.", tt.desc, + ) + } + consoleOut, consoleErr := console.EncodeEntry(entry, nil) + if assert.NoError(t, consoleErr, "Unexpected error console-encoding entry in case #%d.", i) { + assert.Equal( + t, + tt.expectedConsole, + consoleOut.String(), + "Unexpected console output: expected to %v.", tt.desc, + ) + } + } +} + +func TestLevelEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"capital", "INFO"}, + {"lower", "info"}, + {"", "info"}, + {"something-random", "info"}, + } + + for _, tt := range tests { + var le LevelEncoder + require.NoError(t, le.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { le(InfoLevel, arr) }, + "Unexpected output serializing InfoLevel with %q.", tt.name, + ) + } +} + +func TestTimeEncoders(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + yamlDoc string + expected interface{} // output of serializing moment + }{ + {"timeEncoder: iso8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: ISO8601", "1970-01-01T00:01:40.050Z"}, + {"timeEncoder: millis", 100050.005}, + {"timeEncoder: nanos", int64(100050005000)}, + {"timeEncoder: {layout: 06/01/02 03:04pm}", "70/01/01 12:01am"}, + {"timeEncoder: ''", 100.050005}, + {"timeEncoder: something-random", 100.050005}, + {"timeEncoder: rfc3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: RFC3339", "1970-01-01T00:01:40Z"}, + {"timeEncoder: rfc3339nano", "1970-01-01T00:01:40.050005Z"}, + {"timeEncoder: RFC3339Nano", "1970-01-01T00:01:40.050005Z"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, yaml.Unmarshal([]byte(tt.yamlDoc), &cfg), "Unexpected error unmarshaling %q.", tt.yamlDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.yamlDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.yamlDoc, + ) + } +} + +func TestTimeEncodersWrongYAML(t *testing.T) { + tests := []string{ + "timeEncoder: [1, 2, 3]", // wrong type + "timeEncoder: {foo:bar", // broken yaml + } + for _, tt := range tests { + cfg := EncoderConfig{} + assert.Error(t, yaml.Unmarshal([]byte(tt), &cfg), "Expected unmarshaling %q to become error, but not.", tt) + } +} + +func TestTimeEncodersParseFromJSON(t *testing.T) { + moment := time.Unix(100, 50005000).UTC() + tests := []struct { + jsonDoc string + expected interface{} // output of serializing moment + }{ + {`{"timeEncoder": "iso8601"}`, "1970-01-01T00:01:40.050Z"}, + {`{"timeEncoder": {"layout": "06/01/02 03:04pm"}}`, "70/01/01 12:01am"}, + } + + for _, tt := range tests { + cfg := EncoderConfig{} + require.NoError(t, json.Unmarshal([]byte(tt.jsonDoc), &cfg), "Unexpected error unmarshaling %q.", tt.jsonDoc) + require.NotNil(t, cfg.EncodeTime, "Unmashalled timeEncoder is nil for %q.", tt.jsonDoc) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { cfg.EncodeTime(moment, arr) }, + "Unexpected output serializing %v with %q.", moment, tt.jsonDoc, + ) + } +} + +func TestDurationEncoders(t *testing.T) { + elapsed := time.Second + 500*time.Nanosecond + tests := []struct { + name string + expected interface{} // output of serializing elapsed + }{ + {"string", "1.0000005s"}, + {"nanos", int64(1000000500)}, + {"ms", int64(1000)}, + {"", 1.0000005}, + {"something-random", 1.0000005}, + } + + for _, tt := range tests { + var de DurationEncoder + require.NoError(t, de.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { de(elapsed, arr) }, + "Unexpected output serializing %v with %q.", elapsed, tt.name, + ) + } +} + +func TestCallerEncoders(t *testing.T) { + caller := EntryCaller{Defined: true, File: "/home/jack/src/github.com/foo/foo.go", Line: 42} + tests := []struct { + name string + expected interface{} // output of serializing caller + }{ + {"", "foo/foo.go:42"}, + {"something-random", "foo/foo.go:42"}, + {"short", "foo/foo.go:42"}, + {"full", "/home/jack/src/github.com/foo/foo.go:42"}, + } + + for _, tt := range tests { + var ce CallerEncoder + require.NoError(t, ce.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ce(caller, arr) }, + "Unexpected output serializing file name as %v with %q.", tt.expected, tt.name, + ) + } +} + +func TestNameEncoders(t *testing.T) { + tests := []struct { + name string + expected interface{} // output of encoding InfoLevel + }{ + {"", "main"}, + {"full", "main"}, + {"something-random", "main"}, + } + + for _, tt := range tests { + var ne NameEncoder + require.NoError(t, ne.UnmarshalText([]byte(tt.name)), "Unexpected error unmarshaling %q.", tt.name) + assertAppended( + t, + tt.expected, + func(arr ArrayEncoder) { ne("main", arr) }, + "Unexpected output serializing logger name with %q.", tt.name, + ) + } +} + +func assertAppended(t testing.TB, expected interface{}, f func(ArrayEncoder), msgAndArgs ...interface{}) { + mem := NewMapObjectEncoder() + err := mem.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + f(arr) + return nil + })) + assert.NoError(t, err, msgAndArgs...) + arr := mem.Fields["k"].([]interface{}) + require.Equal(t, 1, len(arr), "Expected to append exactly one element to array.") + assert.Equal(t, expected, arr[0], msgAndArgs...) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry.go new file mode 100644 index 0000000..459a5d7 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry.go @@ -0,0 +1,298 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "runtime" + "strings" + "time" + + "go.uber.org/multierr" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/exit" + "go.uber.org/zap/internal/pool" +) + +var _cePool = pool.New(func() *CheckedEntry { + // Pre-allocate some space for cores. + return &CheckedEntry{ + cores: make([]Core, 4), + } +}) + +func getCheckedEntry() *CheckedEntry { + ce := _cePool.Get() + ce.reset() + return ce +} + +func putCheckedEntry(ce *CheckedEntry) { + if ce == nil { + return + } + _cePool.Put(ce) +} + +// NewEntryCaller makes an EntryCaller from the return signature of +// runtime.Caller. +func NewEntryCaller(pc uintptr, file string, line int, ok bool) EntryCaller { + if !ok { + return EntryCaller{} + } + return EntryCaller{ + PC: pc, + File: file, + Line: line, + Defined: true, + } +} + +// EntryCaller represents the caller of a logging function. +type EntryCaller struct { + Defined bool + PC uintptr + File string + Line int + Function string +} + +// String returns the full path and line number of the caller. +func (ec EntryCaller) String() string { + return ec.FullPath() +} + +// FullPath returns a /full/path/to/package/file:line description of the +// caller. +func (ec EntryCaller) FullPath() string { + if !ec.Defined { + return "undefined" + } + buf := bufferpool.Get() + buf.AppendString(ec.File) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// TrimmedPath returns a package/file:line description of the caller, +// preserving only the leaf directory name and file name. +func (ec EntryCaller) TrimmedPath() string { + if !ec.Defined { + return "undefined" + } + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + // + // Find the last separator. + // + idx := strings.LastIndexByte(ec.File, '/') + if idx == -1 { + return ec.FullPath() + } + // Find the penultimate separator. + idx = strings.LastIndexByte(ec.File[:idx], '/') + if idx == -1 { + return ec.FullPath() + } + buf := bufferpool.Get() + // Keep everything after the penultimate separator. + buf.AppendString(ec.File[idx+1:]) + buf.AppendByte(':') + buf.AppendInt(int64(ec.Line)) + caller := buf.String() + buf.Free() + return caller +} + +// An Entry represents a complete log message. The entry's structured context +// is already serialized, but the log level, time, message, and call site +// information are available for inspection and modification. Any fields left +// empty will be omitted when encoding. +// +// Entries are pooled, so any functions that accept them MUST be careful not to +// retain references to them. +type Entry struct { + Level Level + Time time.Time + LoggerName string + Message string + Caller EntryCaller + Stack string +} + +// CheckWriteHook is a custom action that may be executed after an entry is +// written. +// +// Register one on a CheckedEntry with the After method. +// +// if ce := logger.Check(...); ce != nil { +// ce = ce.After(hook) +// ce.Write(...) +// } +// +// You can configure the hook for Fatal log statements at the logger level with +// the zap.WithFatalHook option. +type CheckWriteHook interface { + // OnWrite is invoked with the CheckedEntry that was written and a list + // of fields added with that entry. + // + // The list of fields DOES NOT include fields that were already added + // to the logger with the With method. + OnWrite(*CheckedEntry, []Field) +} + +// CheckWriteAction indicates what action to take after a log entry is +// processed. Actions are ordered in increasing severity. +type CheckWriteAction uint8 + +const ( + // WriteThenNoop indicates that nothing special needs to be done. It's the + // default behavior. + WriteThenNoop CheckWriteAction = iota + // WriteThenGoexit runs runtime.Goexit after Write. + WriteThenGoexit + // WriteThenPanic causes a panic after Write. + WriteThenPanic + // WriteThenFatal causes an os.Exit(1) after Write. + WriteThenFatal +) + +// OnWrite implements the OnWrite method to keep CheckWriteAction compatible +// with the new CheckWriteHook interface which deprecates CheckWriteAction. +func (a CheckWriteAction) OnWrite(ce *CheckedEntry, _ []Field) { + switch a { + case WriteThenGoexit: + runtime.Goexit() + case WriteThenPanic: + panic(ce.Message) + case WriteThenFatal: + exit.With(1) + } +} + +var _ CheckWriteHook = CheckWriteAction(0) + +// CheckedEntry is an Entry together with a collection of Cores that have +// already agreed to log it. +// +// CheckedEntry references should be created by calling AddCore or After on a +// nil *CheckedEntry. References are returned to a pool after Write, and MUST +// NOT be retained after calling their Write method. +type CheckedEntry struct { + Entry + ErrorOutput WriteSyncer + dirty bool // best-effort detection of pool misuse + after CheckWriteHook + cores []Core +} + +func (ce *CheckedEntry) reset() { + ce.Entry = Entry{} + ce.ErrorOutput = nil + ce.dirty = false + ce.after = nil + for i := range ce.cores { + // don't keep references to cores + ce.cores[i] = nil + } + ce.cores = ce.cores[:0] +} + +// Write writes the entry to the stored Cores, returns any errors, and returns +// the CheckedEntry reference to a pool for immediate re-use. Finally, it +// executes any required CheckWriteAction. +func (ce *CheckedEntry) Write(fields ...Field) { + if ce == nil { + return + } + + if ce.dirty { + if ce.ErrorOutput != nil { + // Make a best effort to detect unsafe re-use of this CheckedEntry. + // If the entry is dirty, log an internal error; because the + // CheckedEntry is being used after it was returned to the pool, + // the message may be an amalgamation from multiple call sites. + fmt.Fprintf(ce.ErrorOutput, "%v Unsafe CheckedEntry re-use near Entry %+v.\n", ce.Time, ce.Entry) + _ = ce.ErrorOutput.Sync() // ignore error + } + return + } + ce.dirty = true + + var err error + for i := range ce.cores { + err = multierr.Append(err, ce.cores[i].Write(ce.Entry, fields)) + } + if err != nil && ce.ErrorOutput != nil { + fmt.Fprintf(ce.ErrorOutput, "%v write error: %v\n", ce.Time, err) + _ = ce.ErrorOutput.Sync() // ignore error + } + + hook := ce.after + if hook != nil { + hook.OnWrite(ce, fields) + } + putCheckedEntry(ce) +} + +// AddCore adds a Core that has agreed to log this CheckedEntry. It's intended to be +// used by Core.Check implementations, and is safe to call on nil CheckedEntry +// references. +func (ce *CheckedEntry) AddCore(ent Entry, core Core) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.cores = append(ce.cores, core) + return ce +} + +// Should sets this CheckedEntry's CheckWriteAction, which controls whether a +// Core will panic or fatal after writing this log entry. Like AddCore, it's +// safe to call on nil CheckedEntry references. +// +// Deprecated: Use [CheckedEntry.After] instead. +func (ce *CheckedEntry) Should(ent Entry, should CheckWriteAction) *CheckedEntry { + return ce.After(ent, should) +} + +// After sets this CheckEntry's CheckWriteHook, which will be called after this +// log entry has been written. It's safe to call this on nil CheckedEntry +// references. +func (ce *CheckedEntry) After(ent Entry, hook CheckWriteHook) *CheckedEntry { + if ce == nil { + ce = getCheckedEntry() + ce.Entry = ent + } + ce.after = hook + return ce +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_ext_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_ext_test.go new file mode 100644 index 0000000..fd1a05c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_ext_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "bytes" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" +) + +func TestCheckedEntryIllegalReuse(t *testing.T) { + t.Parallel() + + var errOut bytes.Buffer + + testCore := zaptest.NewLogger(t).Core() + ce := testCore.Check(zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Now(), + Message: "hello", + }, nil) + ce.ErrorOutput = zapcore.AddSync(&errOut) + + // The first write should succeed. + ce.Write(zap.String("k", "v"), zap.Int("n", 42)) + assert.Empty(t, errOut.String(), "Expected no errors on first write.") + + // The second write should fail. + ce.Write(zap.String("foo", "bar"), zap.Int("x", 1)) + assert.Contains(t, errOut.String(), "Unsafe CheckedEntry re-use near Entry", + "Expected error logged on second write.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_test.go new file mode 100644 index 0000000..6555ab6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/entry_test.go @@ -0,0 +1,149 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "sync" + "testing" + + "go.uber.org/zap/internal/exit" + + "github.com/stretchr/testify/assert" +) + +func assertGoexit(t *testing.T, f func()) { + var finished bool + recovered := make(chan interface{}) + go func() { + defer func() { + recovered <- recover() + }() + + f() + finished = true + }() + + assert.Nil(t, <-recovered, "Goexit should cause recover to return nil") + assert.False(t, finished, "Goroutine should not finish after Goexit") +} + +func TestPutNilEntry(t *testing.T) { + // Pooling nil entries defeats the purpose. + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + putCheckedEntry(nil) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + ce := getCheckedEntry() + assert.NotNil(t, ce, "Expected only non-nil CheckedEntries in pool.") + assert.False(t, ce.dirty, "Unexpected dirty bit set.") + assert.Nil(t, ce.ErrorOutput, "Non-nil ErrorOutput.") + assert.Nil(t, ce.after, "Unexpected terminal behavior.") + assert.Equal(t, 0, len(ce.cores), "Expected empty slice of cores.") + assert.True(t, cap(ce.cores) > 0, "Expected pooled CheckedEntries to pre-allocate slice of Cores.") + } + }() + + wg.Wait() +} + +func TestEntryCaller(t *testing.T) { + tests := []struct { + caller EntryCaller + full string + short string + }{ + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, false), + full: "undefined", + short: "undefined", + }, + { + caller: NewEntryCaller(100, "/path/to/foo.go", 42, true), + full: "/path/to/foo.go:42", + short: "to/foo.go:42", + }, + { + caller: NewEntryCaller(100, "to/foo.go", 42, true), + full: "to/foo.go:42", + short: "to/foo.go:42", + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.full, tt.caller.String(), "Unexpected string from EntryCaller.") + assert.Equal(t, tt.full, tt.caller.FullPath(), "Unexpected FullPath from EntryCaller.") + assert.Equal(t, tt.short, tt.caller.TrimmedPath(), "Unexpected TrimmedPath from EntryCaller.") + } +} + +func TestCheckedEntryWrite(t *testing.T) { + t.Run("nil is safe", func(t *testing.T) { + var ce *CheckedEntry + assert.NotPanics(t, func() { ce.Write() }, "Unexpected panic writing nil CheckedEntry.") + }) + + t.Run("WriteThenPanic", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenPanic) + assert.Panics(t, func() { ce.Write() }, "Expected to panic when WriteThenPanic is set.") + }) + + t.Run("WriteThenGoexit", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenGoexit) + assertGoexit(t, func() { ce.Write() }) + }) + + t.Run("WriteThenFatal", func(t *testing.T) { + var ce *CheckedEntry + ce = ce.After(Entry{}, WriteThenFatal) + stub := exit.WithStub(func() { + ce.Write() + }) + assert.True(t, stub.Exited, "Expected to exit when WriteThenFatal is set.") + assert.Equal(t, 1, stub.Code, "Expected to exit when WriteThenFatal is set.") + }) + + t.Run("After", func(t *testing.T) { + var ce *CheckedEntry + hook := &customHook{} + ce = ce.After(Entry{}, hook) + ce.Write() + assert.True(t, hook.called, "Expected to call custom action after Write.") + }) +} + +type customHook struct { + called bool +} + +func (c *customHook) OnWrite(_ *CheckedEntry, _ []Field) { + c.called = true +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error.go new file mode 100644 index 0000000..c40df13 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error.go @@ -0,0 +1,136 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "fmt" + "reflect" + + "go.uber.org/zap/internal/pool" +) + +// Encodes the given error into fields of an object. A field with the given +// name is added for the error message. +// +// If the error implements fmt.Formatter, a field with the name ${key}Verbose +// is also added with the full verbose error message. +// +// Finally, if the error implements errorGroup (from go.uber.org/multierr) or +// causer (from github.com/pkg/errors), a ${key}Causes field is added with an +// array of objects containing the errors this error was comprised of. +// +// { +// "error": err.Error(), +// "errorVerbose": fmt.Sprintf("%+v", err), +// "errorCauses": [ +// ... +// ], +// } +func encodeError(key string, err error, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the Error() method + defer func() { + if rerr := recover(); rerr != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // error that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", rerr) + } + }() + + basic := err.Error() + enc.AddString(key, basic) + + switch e := err.(type) { + case errorGroup: + return enc.AddArray(key+"Causes", errArray(e.Errors())) + case fmt.Formatter: + verbose := fmt.Sprintf("%+v", e) + if verbose != basic { + // This is a rich error type, like those produced by + // github.com/pkg/errors. + enc.AddString(key+"Verbose", verbose) + } + } + return nil +} + +type errorGroup interface { + // Provides read-only access to the underlying list of errors, preferably + // without causing any allocs. + Errors() []error +} + +// Note that errArray and errArrayElem are very similar to the version +// implemented in the top-level error.go file. We can't re-use this because +// that would require exporting errArray as part of the zapcore API. + +// Encodes a list of errors using the standard error encoding logic. +type errArray []error + +func (errs errArray) MarshalLogArray(arr ArrayEncoder) error { + for i := range errs { + if errs[i] == nil { + continue + } + + el := newErrArrayElem(errs[i]) + err := arr.AppendObject(el) + el.Free() + if err != nil { + return err + } + } + return nil +} + +var _errArrayElemPool = pool.New(func() *errArrayElem { + return &errArrayElem{} +}) + +// Encodes any error into a {"error": ...} re-using the same errors logic. +// +// May be passed in place of an array to build a single-element array. +type errArrayElem struct{ err error } + +func newErrArrayElem(err error) *errArrayElem { + e := _errArrayElemPool.Get() + e.err = err + return e +} + +func (e *errArrayElem) MarshalLogArray(arr ArrayEncoder) error { + return arr.AppendObject(e) +} + +func (e *errArrayElem) MarshalLogObject(enc ObjectEncoder) error { + return encodeError("error", e.err, enc) +} + +func (e *errArrayElem) Free() { + e.err = nil + _errArrayElemPool.Put(e) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error_test.go new file mode 100644 index 0000000..41f243a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/error_test.go @@ -0,0 +1,210 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/assert" + + "go.uber.org/multierr" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +type errTooManyUsers int + +func (e errTooManyUsers) Error() string { + return fmt.Sprintf("%d too many users", int(e)) +} + +func (e errTooManyUsers) Format(s fmt.State, verb rune) { + // Implement fmt.Formatter, but don't add any information beyond the basic + // Error method. + if verb == 'v' && s.Flag('+') { + io.WriteString(s, e.Error()) + } +} + +type customMultierr struct{} + +func (e customMultierr) Error() string { + return "great sadness" +} + +func (e customMultierr) Errors() []error { + return []error{ + errors.New("foo"), + nil, + multierr.Append( + errors.New("bar"), + errors.New("baz"), + ), + } +} + +func TestErrorEncoding(t *testing.T) { + tests := []struct { + k string + t FieldType // defaults to ErrorType + iface interface{} + want map[string]interface{} + }{ + { + k: "k", + iface: errTooManyUsers(2), + want: map[string]interface{}{ + "k": "2 too many users", + }, + }, + { + k: "err", + iface: multierr.Combine( + errors.New("foo"), + errors.New("bar"), + errors.New("baz"), + ), + want: map[string]interface{}{ + "err": "foo; bar; baz", + "errCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + { + k: "e", + iface: customMultierr{}, + want: map[string]interface{}{ + "e": "great sadness", + "eCauses": []interface{}{ + map[string]interface{}{"error": "foo"}, + map[string]interface{}{ + "error": "bar; baz", + "errorCauses": []interface{}{ + map[string]interface{}{"error": "bar"}, + map[string]interface{}{"error": "baz"}, + }, + }, + }, + }, + }, + { + k: "k", + iface: fmt.Errorf("failed: %w", errors.New("egad")), + want: map[string]interface{}{ + "k": "failed: egad", + }, + }, + { + k: "error", + iface: multierr.Combine( + fmt.Errorf("hello: %w", + multierr.Combine(errors.New("foo"), errors.New("bar")), + ), + errors.New("baz"), + fmt.Errorf("world: %w", errors.New("qux")), + ), + want: map[string]interface{}{ + "error": "hello: foo; bar; baz; world: qux", + "errorCauses": []interface{}{ + map[string]interface{}{ + "error": "hello: foo; bar", + }, + map[string]interface{}{"error": "baz"}, + map[string]interface{}{"error": "world: qux"}, + }, + }, + }, + } + + for _, tt := range tests { + if tt.t == UnknownType { + tt.t = ErrorType + } + + enc := NewMapObjectEncoder() + f := Field{Key: tt.k, Type: tt.t, Interface: tt.iface} + f.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields, "Unexpected output from field %+v.", f) + } +} + +func TestRichErrorSupport(t *testing.T) { + f := Field{ + Type: ErrorType, + Interface: fmt.Errorf("failed: %w", errors.New("egad")), + Key: "k", + } + enc := NewMapObjectEncoder() + f.AddTo(enc) + assert.Equal(t, "failed: egad", enc.Fields["k"], "Unexpected basic error message.") +} + +func TestErrArrayBrokenEncoder(t *testing.T) { + t.Parallel() + + f := Field{ + Key: "foo", + Type: ErrorType, + Interface: multierr.Combine( + errors.New("foo"), + errors.New("bar"), + ), + } + + failWith := errors.New("great sadness") + enc := NewMapObjectEncoder() + f.AddTo(brokenArrayObjectEncoder{ + Err: failWith, + ObjectEncoder: enc, + }) + + // Failure to add the field to the encoder + // causes the error to be added as a string field. + assert.Equal(t, "great sadness", enc.Fields["fooError"], + "Unexpected error message.") +} + +// brokenArrayObjectEncoder is an ObjectEncoder +// that builds a broken ArrayEncoder. +type brokenArrayObjectEncoder struct { + ObjectEncoder + ArrayEncoder + + Err error // error to return +} + +func (enc brokenArrayObjectEncoder) AddArray(key string, marshaler ArrayMarshaler) error { + return enc.ObjectEncoder.AddArray(key, + ArrayMarshalerFunc(func(ae ArrayEncoder) error { + enc.ArrayEncoder = ae + return marshaler.MarshalLogArray(enc) + })) +} + +func (enc brokenArrayObjectEncoder) AppendObject(ObjectMarshaler) error { + return enc.Err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field.go new file mode 100644 index 0000000..308c978 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field.go @@ -0,0 +1,233 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "fmt" + "math" + "reflect" + "time" +) + +// A FieldType indicates which member of the Field union struct should be used +// and how it should be serialized. +type FieldType uint8 + +const ( + // UnknownType is the default field type. Attempting to add it to an encoder will panic. + UnknownType FieldType = iota + // ArrayMarshalerType indicates that the field carries an ArrayMarshaler. + ArrayMarshalerType + // ObjectMarshalerType indicates that the field carries an ObjectMarshaler. + ObjectMarshalerType + // BinaryType indicates that the field carries an opaque binary blob. + BinaryType + // BoolType indicates that the field carries a bool. + BoolType + // ByteStringType indicates that the field carries UTF-8 encoded bytes. + ByteStringType + // Complex128Type indicates that the field carries a complex128. + Complex128Type + // Complex64Type indicates that the field carries a complex64. + Complex64Type + // DurationType indicates that the field carries a time.Duration. + DurationType + // Float64Type indicates that the field carries a float64. + Float64Type + // Float32Type indicates that the field carries a float32. + Float32Type + // Int64Type indicates that the field carries an int64. + Int64Type + // Int32Type indicates that the field carries an int32. + Int32Type + // Int16Type indicates that the field carries an int16. + Int16Type + // Int8Type indicates that the field carries an int8. + Int8Type + // StringType indicates that the field carries a string. + StringType + // TimeType indicates that the field carries a time.Time that is + // representable by a UnixNano() stored as an int64. + TimeType + // TimeFullType indicates that the field carries a time.Time stored as-is. + TimeFullType + // Uint64Type indicates that the field carries a uint64. + Uint64Type + // Uint32Type indicates that the field carries a uint32. + Uint32Type + // Uint16Type indicates that the field carries a uint16. + Uint16Type + // Uint8Type indicates that the field carries a uint8. + Uint8Type + // UintptrType indicates that the field carries a uintptr. + UintptrType + // ReflectType indicates that the field carries an interface{}, which should + // be serialized using reflection. + ReflectType + // NamespaceType signals the beginning of an isolated namespace. All + // subsequent fields should be added to the new namespace. + NamespaceType + // StringerType indicates that the field carries a fmt.Stringer. + StringerType + // ErrorType indicates that the field carries an error. + ErrorType + // SkipType indicates that the field is a no-op. + SkipType + + // InlineMarshalerType indicates that the field carries an ObjectMarshaler + // that should be inlined. + InlineMarshalerType +) + +// A Field is a marshaling operation used to add a key-value pair to a logger's +// context. Most fields are lazily marshaled, so it's inexpensive to add fields +// to disabled debug-level log statements. +type Field struct { + Key string + Type FieldType + Integer int64 + String string + Interface interface{} +} + +// AddTo exports a field through the ObjectEncoder interface. It's primarily +// useful to library authors, and shouldn't be necessary in most applications. +func (f Field) AddTo(enc ObjectEncoder) { + var err error + + switch f.Type { + case ArrayMarshalerType: + err = enc.AddArray(f.Key, f.Interface.(ArrayMarshaler)) + case ObjectMarshalerType: + err = enc.AddObject(f.Key, f.Interface.(ObjectMarshaler)) + case InlineMarshalerType: + err = f.Interface.(ObjectMarshaler).MarshalLogObject(enc) + case BinaryType: + enc.AddBinary(f.Key, f.Interface.([]byte)) + case BoolType: + enc.AddBool(f.Key, f.Integer == 1) + case ByteStringType: + enc.AddByteString(f.Key, f.Interface.([]byte)) + case Complex128Type: + enc.AddComplex128(f.Key, f.Interface.(complex128)) + case Complex64Type: + enc.AddComplex64(f.Key, f.Interface.(complex64)) + case DurationType: + enc.AddDuration(f.Key, time.Duration(f.Integer)) + case Float64Type: + enc.AddFloat64(f.Key, math.Float64frombits(uint64(f.Integer))) + case Float32Type: + enc.AddFloat32(f.Key, math.Float32frombits(uint32(f.Integer))) + case Int64Type: + enc.AddInt64(f.Key, f.Integer) + case Int32Type: + enc.AddInt32(f.Key, int32(f.Integer)) + case Int16Type: + enc.AddInt16(f.Key, int16(f.Integer)) + case Int8Type: + enc.AddInt8(f.Key, int8(f.Integer)) + case StringType: + enc.AddString(f.Key, f.String) + case TimeType: + if f.Interface != nil { + enc.AddTime(f.Key, time.Unix(0, f.Integer).In(f.Interface.(*time.Location))) + } else { + // Fall back to UTC if location is nil. + enc.AddTime(f.Key, time.Unix(0, f.Integer)) + } + case TimeFullType: + enc.AddTime(f.Key, f.Interface.(time.Time)) + case Uint64Type: + enc.AddUint64(f.Key, uint64(f.Integer)) + case Uint32Type: + enc.AddUint32(f.Key, uint32(f.Integer)) + case Uint16Type: + enc.AddUint16(f.Key, uint16(f.Integer)) + case Uint8Type: + enc.AddUint8(f.Key, uint8(f.Integer)) + case UintptrType: + enc.AddUintptr(f.Key, uintptr(f.Integer)) + case ReflectType: + err = enc.AddReflected(f.Key, f.Interface) + case NamespaceType: + enc.OpenNamespace(f.Key) + case StringerType: + err = encodeStringer(f.Key, f.Interface, enc) + case ErrorType: + err = encodeError(f.Key, f.Interface.(error), enc) + case SkipType: + break + default: + panic(fmt.Sprintf("unknown field type: %v", f)) + } + + if err != nil { + enc.AddString(fmt.Sprintf("%sError", f.Key), err.Error()) + } +} + +// Equals returns whether two fields are equal. For non-primitive types such as +// errors, marshalers, or reflect types, it uses reflect.DeepEqual. +func (f Field) Equals(other Field) bool { + if f.Type != other.Type { + return false + } + if f.Key != other.Key { + return false + } + + switch f.Type { + case BinaryType, ByteStringType: + return bytes.Equal(f.Interface.([]byte), other.Interface.([]byte)) + case ArrayMarshalerType, ObjectMarshalerType, ErrorType, ReflectType: + return reflect.DeepEqual(f.Interface, other.Interface) + default: + return f == other + } +} + +func addFields(enc ObjectEncoder, fields []Field) { + for i := range fields { + fields[i].AddTo(enc) + } +} + +func encodeStringer(key string, stringer interface{}, enc ObjectEncoder) (retErr error) { + // Try to capture panics (from nil references or otherwise) when calling + // the String() method, similar to https://golang.org/src/fmt/print.go#L540 + defer func() { + if err := recover(); err != nil { + // If it's a nil pointer, just say "<nil>". The likeliest causes are a + // Stringer that fails to guard against nil or a nil pointer for a + // value receiver, and in either case, "<nil>" is a nice result. + if v := reflect.ValueOf(stringer); v.Kind() == reflect.Ptr && v.IsNil() { + enc.AddString(key, "<nil>") + return + } + + retErr = fmt.Errorf("PANIC=%v", err) + } + }() + + enc.AddString(key, stringer.(fmt.Stringer).String()) + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field_test.go new file mode 100644 index 0000000..06bcef2 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/field_test.go @@ -0,0 +1,329 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "fmt" + "math" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +type users int + +func (u users) String() string { + return fmt.Sprintf("%d users", int(u)) +} + +func (u users) MarshalLogObject(enc ObjectEncoder) error { + if int(u) < 0 { + return errors.New("too few users") + } + enc.AddInt("users", int(u)) + return nil +} + +func (u users) MarshalLogArray(enc ArrayEncoder) error { + if int(u) < 0 { + return errors.New("too few users") + } + for i := 0; i < int(u); i++ { + enc.AppendString("user") + } + return nil +} + +type obj struct { + kind int +} + +func (o *obj) String() string { + if o == nil { + return "nil obj" + } + + if o.kind == 1 { + panic("panic with string") + } else if o.kind == 2 { + panic(errors.New("panic with error")) + } else if o.kind == 3 { + // panic with an arbitrary object that causes a panic itself + // when being converted to a string + panic((*url.URL)(nil)) + } + + return "obj" +} + +type errObj struct { + kind int + errMsg string +} + +func (eobj *errObj) Error() string { + if eobj.kind == 1 { + panic("panic in Error() method") + } + return eobj.errMsg +} + +func TestUnknownFieldType(t *testing.T) { + unknown := Field{Key: "k", String: "foo"} + assert.Equal(t, UnknownType, unknown.Type, "Expected zero value of FieldType to be UnknownType.") + assert.Panics(t, func() { + unknown.AddTo(NewMapObjectEncoder()) + }, "Expected using a field with unknown type to panic.") +} + +func TestFieldAddingError(t *testing.T) { + var empty interface{} + tests := []struct { + t FieldType + iface interface{} + want interface{} + err string + }{ + {t: ArrayMarshalerType, iface: users(-1), want: []interface{}{}, err: "too few users"}, + {t: ObjectMarshalerType, iface: users(-1), want: map[string]interface{}{}, err: "too few users"}, + {t: InlineMarshalerType, iface: users(-1), want: nil, err: "too few users"}, + {t: StringerType, iface: obj{}, want: empty, err: "PANIC=interface conversion: zapcore_test.obj is not fmt.Stringer: missing method String"}, + {t: StringerType, iface: &obj{1}, want: empty, err: "PANIC=panic with string"}, + {t: StringerType, iface: &obj{2}, want: empty, err: "PANIC=panic with error"}, + {t: StringerType, iface: &obj{3}, want: empty, err: "PANIC=<nil>"}, + {t: ErrorType, iface: &errObj{kind: 1}, want: empty, err: "PANIC=panic in Error() method"}, + } + for _, tt := range tests { + f := Field{Key: "k", Interface: tt.iface, Type: tt.t} + enc := NewMapObjectEncoder() + assert.NotPanics(t, func() { f.AddTo(enc) }, "Unexpected panic when adding fields returns an error.") + assert.Equal(t, tt.want, enc.Fields["k"], "On error, expected zero value in field.Key.") + assert.Equal(t, tt.err, enc.Fields["kError"], "Expected error message in log context.") + } +} + +func TestFields(t *testing.T) { + tests := []struct { + t FieldType + i int64 + s string + iface interface{} + want interface{} + }{ + {t: ArrayMarshalerType, iface: users(2), want: []interface{}{"user", "user"}}, + {t: ObjectMarshalerType, iface: users(2), want: map[string]interface{}{"users": 2}}, + {t: BoolType, i: 0, want: false}, + {t: ByteStringType, iface: []byte("foo"), want: "foo"}, + {t: Complex128Type, iface: 1 + 2i, want: 1 + 2i}, + {t: Complex64Type, iface: complex64(1 + 2i), want: complex64(1 + 2i)}, + {t: DurationType, i: 1000, want: time.Microsecond}, + {t: Float64Type, i: int64(math.Float64bits(3.14)), want: 3.14}, + {t: Float32Type, i: int64(math.Float32bits(3.14)), want: float32(3.14)}, + {t: Int64Type, i: 42, want: int64(42)}, + {t: Int32Type, i: 42, want: int32(42)}, + {t: Int16Type, i: 42, want: int16(42)}, + {t: Int8Type, i: 42, want: int8(42)}, + {t: StringType, s: "foo", want: "foo"}, + {t: TimeType, i: 1000, iface: time.UTC, want: time.Unix(0, 1000).In(time.UTC)}, + {t: TimeType, i: 1000, want: time.Unix(0, 1000)}, + {t: Uint64Type, i: 42, want: uint64(42)}, + {t: Uint32Type, i: 42, want: uint32(42)}, + {t: Uint16Type, i: 42, want: uint16(42)}, + {t: Uint8Type, i: 42, want: uint8(42)}, + {t: UintptrType, i: 42, want: uintptr(42)}, + {t: ReflectType, iface: users(2), want: users(2)}, + {t: NamespaceType, want: map[string]interface{}{}}, + {t: StringerType, iface: users(2), want: "2 users"}, + {t: StringerType, iface: &obj{}, want: "obj"}, + {t: StringerType, iface: (*obj)(nil), want: "nil obj"}, + {t: SkipType, want: interface{}(nil)}, + {t: StringerType, iface: (*url.URL)(nil), want: "<nil>"}, + {t: StringerType, iface: (*users)(nil), want: "<nil>"}, + {t: ErrorType, iface: (*errObj)(nil), want: "<nil>"}, + } + + for _, tt := range tests { + enc := NewMapObjectEncoder() + f := Field{Key: "k", Type: tt.t, Integer: tt.i, Interface: tt.iface, String: tt.s} + f.AddTo(enc) + assert.Equal(t, tt.want, enc.Fields["k"], "Unexpected output from field %+v.", f) + + delete(enc.Fields, "k") + assert.Equal(t, 0, len(enc.Fields), "Unexpected extra fields present.") + + assert.True(t, f.Equals(f), "Field does not equal itself") + } +} + +func TestInlineMarshaler(t *testing.T) { + enc := NewMapObjectEncoder() + + topLevelStr := Field{Key: "k", Type: StringType, String: "s"} + topLevelStr.AddTo(enc) + + inlineObj := Field{Key: "ignored", Type: InlineMarshalerType, Interface: users(10)} + inlineObj.AddTo(enc) + + nestedObj := Field{Key: "nested", Type: ObjectMarshalerType, Interface: users(11)} + nestedObj.AddTo(enc) + + assert.Equal(t, map[string]interface{}{ + "k": "s", + "users": 10, + "nested": map[string]interface{}{ + "users": 11, + }, + }, enc.Fields) +} + +func TestEquals(t *testing.T) { + // Values outside the UnixNano range were encoded incorrectly (#737, #803). + timeOutOfRangeHigh := time.Unix(0, math.MaxInt64).Add(time.Nanosecond) + timeOutOfRangeLow := time.Unix(0, math.MinInt64).Add(-time.Nanosecond) + timeOutOfRangeHighNano := time.Unix(0, timeOutOfRangeHigh.UnixNano()) + timeOutOfRangeLowNano := time.Unix(0, timeOutOfRangeLow.UnixNano()) + require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is > UnixNano range") + require.False(t, timeOutOfRangeHigh.Equal(timeOutOfRangeHighNano), "should be different as value is < UnixNano range") + + tests := []struct { + a, b Field + want bool + }{ + { + a: zap.Int16("a", 1), + b: zap.Int32("a", 1), + want: false, + }, + { + a: zap.String("k", "a"), + b: zap.String("k", "a"), + want: true, + }, + { + a: zap.String("k", "a"), + b: zap.String("k2", "a"), + want: false, + }, + { + a: zap.String("k", "a"), + b: zap.String("k", "b"), + want: false, + }, + { + a: zap.Time("k", time.Unix(1000, 1000)), + b: zap.Time("k", time.Unix(1000, 1000)), + want: true, + }, + { + a: zap.Time("k", time.Unix(1000, 1000).In(time.UTC)), + b: zap.Time("k", time.Unix(1000, 1000).In(time.FixedZone("TEST", -8))), + want: false, + }, + { + a: zap.Time("k", timeOutOfRangeLow), + b: zap.Time("k", timeOutOfRangeLowNano), + want: false, + }, + { + a: zap.Time("k", timeOutOfRangeHigh), + b: zap.Time("k", timeOutOfRangeHighNano), + want: false, + }, + { + a: zap.Time("k", time.Unix(1000, 1000)), + b: zap.Time("k", time.Unix(1000, 2000)), + want: false, + }, + { + a: zap.Binary("k", []byte{1, 2}), + b: zap.Binary("k", []byte{1, 2}), + want: true, + }, + { + a: zap.Binary("k", []byte{1, 2}), + b: zap.Binary("k", []byte{1, 3}), + want: false, + }, + { + a: zap.ByteString("k", []byte("abc")), + b: zap.ByteString("k", []byte("abc")), + want: true, + }, + { + a: zap.ByteString("k", []byte("abc")), + b: zap.ByteString("k", []byte("abd")), + want: false, + }, + { + a: zap.Ints("k", []int{1, 2}), + b: zap.Ints("k", []int{1, 2}), + want: true, + }, + { + a: zap.Ints("k", []int{1, 2}), + b: zap.Ints("k", []int{1, 3}), + want: false, + }, + { + a: zap.Object("k", users(10)), + b: zap.Object("k", users(10)), + want: true, + }, + { + a: zap.Object("k", users(10)), + b: zap.Object("k", users(20)), + want: false, + }, + { + a: zap.Any("k", map[string]string{"a": "b"}), + b: zap.Any("k", map[string]string{"a": "b"}), + want: true, + }, + { + a: zap.Any("k", map[string]string{"a": "b"}), + b: zap.Any("k", map[string]string{"a": "d"}), + want: false, + }, + { + a: zap.Dict("k", zap.String("a", "b")), + b: zap.Dict("k", zap.String("a", "b")), + want: true, + }, + { + a: zap.Dict("k", zap.String("a", "b")), + b: zap.Dict("k", zap.String("a", "d")), + want: false, + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.want, tt.a.Equals(tt.b), "a.Equals(b) a: %#v b: %#v", tt.a, tt.b) + assert.Equal(t, tt.want, tt.b.Equals(tt.a), "b.Equals(a) a: %#v b: %#v", tt.a, tt.b) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook.go new file mode 100644 index 0000000..198def9 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook.go @@ -0,0 +1,77 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type hooked struct { + Core + funcs []func(Entry) error +} + +var ( + _ Core = (*hooked)(nil) + _ leveledEnabler = (*hooked)(nil) +) + +// RegisterHooks wraps a Core and runs a collection of user-defined callback +// hooks each time a message is logged. Execution of the callbacks is blocking. +// +// This offers users an easy way to register simple callbacks (e.g., metrics +// collection) without implementing the full Core interface. +func RegisterHooks(core Core, hooks ...func(Entry) error) Core { + funcs := append([]func(Entry) error{}, hooks...) + return &hooked{ + Core: core, + funcs: funcs, + } +} + +func (h *hooked) Level() Level { + return LevelOf(h.Core) +} + +func (h *hooked) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + // Let the wrapped Core decide whether to log this message or not. This + // also gives the downstream a chance to register itself directly with the + // CheckedEntry. + if downstream := h.Core.Check(ent, ce); downstream != nil { + return downstream.AddCore(ent, h) + } + return ce +} + +func (h *hooked) With(fields []Field) Core { + return &hooked{ + Core: h.Core.With(fields), + funcs: h.funcs, + } +} + +func (h *hooked) Write(ent Entry, _ []Field) error { + // Since our downstream had a chance to register itself directly with the + // CheckedMessage, we don't need to call it here. + var err error + for i := range h.funcs { + err = multierr.Append(err, h.funcs[i](ent)) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook_test.go new file mode 100644 index 0000000..360b222 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/hook_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHooks(t *testing.T) { + tests := []struct { + entryLevel Level + coreLevel Level + expectCall bool + }{ + {DebugLevel, InfoLevel, false}, + {InfoLevel, InfoLevel, true}, + {WarnLevel, InfoLevel, true}, + } + + for _, tt := range tests { + fac, logs := observer.New(tt.coreLevel) + + // sanity check + require.Equal(t, tt.coreLevel, LevelOf(fac), "Original logger has the wrong level") + + intField := makeInt64Field("foo", 42) + ent := Entry{Message: "bar", Level: tt.entryLevel} + + var called int + f := func(e Entry) error { + called++ + assert.Equal(t, ent, e, "Hook called with unexpected Entry.") + return nil + } + + h := RegisterHooks(fac, f) + if ce := h.With([]Field{intField}).Check(ent, nil); ce != nil { + ce.Write() + } + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, tt.coreLevel, LevelOf(h), "Wrapped logger has the wrong log level") + }) + + if tt.expectCall { + assert.Equal(t, 1, called, "Expected to call hook once.") + assert.Equal( + t, + []observer.LoggedEntry{{Entry: ent, Context: []Field{intField}}}, + logs.AllUntimed(), + "Unexpected logs written out.", + ) + } else { + assert.Equal(t, 0, called, "Didn't expect to call hook.") + assert.Equal(t, 0, logs.Len(), "Unexpected logs written out.") + } + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level.go new file mode 100644 index 0000000..7a11237 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level.go @@ -0,0 +1,75 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "fmt" + +type levelFilterCore struct { + core Core + level LevelEnabler +} + +var ( + _ Core = (*levelFilterCore)(nil) + _ leveledEnabler = (*levelFilterCore)(nil) +) + +// NewIncreaseLevelCore creates a core that can be used to increase the level of +// an existing Core. It cannot be used to decrease the logging level, as it acts +// as a filter before calling the underlying core. If level decreases the log level, +// an error is returned. +func NewIncreaseLevelCore(core Core, level LevelEnabler) (Core, error) { + for l := _maxLevel; l >= _minLevel; l-- { + if !core.Enabled(l) && level.Enabled(l) { + return nil, fmt.Errorf("invalid increase level, as level %q is allowed by increased level, but not by existing core", l) + } + } + + return &levelFilterCore{core, level}, nil +} + +func (c *levelFilterCore) Enabled(lvl Level) bool { + return c.level.Enabled(lvl) +} + +func (c *levelFilterCore) Level() Level { + return LevelOf(c.level) +} + +func (c *levelFilterCore) With(fields []Field) Core { + return &levelFilterCore{c.core.With(fields), c.level} +} + +func (c *levelFilterCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !c.Enabled(ent.Level) { + return ce + } + + return c.core.Check(ent, ce) +} + +func (c *levelFilterCore) Write(ent Entry, fields []Field) error { + return c.core.Write(ent, fields) +} + +func (c *levelFilterCore) Sync() error { + return c.core.Sync() +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level_test.go new file mode 100644 index 0000000..14cd857 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/increase_level_test.go @@ -0,0 +1,130 @@ +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestIncreaseLevel(t *testing.T) { + tests := []struct { + coreLevel Level + increaseLevel Level + wantErr bool + with []Field + }{ + { + coreLevel: InfoLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: InfoLevel, + increaseLevel: InfoLevel, + }, + { + coreLevel: InfoLevel, + increaseLevel: ErrorLevel, + }, + { + coreLevel: InfoLevel, + increaseLevel: ErrorLevel, + with: []Field{zap.String("k", "v")}, + }, + { + coreLevel: ErrorLevel, + increaseLevel: DebugLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: InfoLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: WarnLevel, + wantErr: true, + }, + { + coreLevel: ErrorLevel, + increaseLevel: PanicLevel, + }, + } + + for _, tt := range tests { + msg := fmt.Sprintf("increase %v to %v", tt.coreLevel, tt.increaseLevel) + t.Run(msg, func(t *testing.T) { + logger, logs := observer.New(tt.coreLevel) + + // sanity check + require.Equal(t, tt.coreLevel, LevelOf(logger), "Original logger has the wrong level") + + filteredLogger, err := NewIncreaseLevelCore(logger, tt.increaseLevel) + if tt.wantErr { + assert.ErrorContains(t, err, "invalid increase level") + return + } + + if len(tt.with) > 0 { + filteredLogger = filteredLogger.With(tt.with) + } + + require.NoError(t, err) + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, tt.increaseLevel, LevelOf(filteredLogger), "Filtered logger has the wrong level") + }) + + for l := DebugLevel; l <= FatalLevel; l++ { + enabled := filteredLogger.Enabled(l) + entry := Entry{Level: l} + ce := filteredLogger.Check(entry, nil) + ce.Write() + entries := logs.TakeAll() + + if l >= tt.increaseLevel { + assert.True(t, enabled, "expect %v to be enabled", l) + assert.NotNil(t, ce, "expect non-nil Check") + assert.NotEmpty(t, entries, "Expect log to be written") + } else { + assert.False(t, enabled, "expect %v to be disabled", l) + assert.Nil(t, ce, "expect nil Check") + assert.Empty(t, entries, "No logs should have been written") + } + + // Write should always log the entry as per the Core interface + require.NoError(t, filteredLogger.Write(entry, nil), "Write failed") + require.NoError(t, filteredLogger.Sync(), "Sync failed") + assert.NotEmpty(t, logs.TakeAll(), "Write should always log") + } + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder.go new file mode 100644 index 0000000..9685169 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder.go @@ -0,0 +1,583 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/base64" + "math" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + "go.uber.org/zap/internal/pool" +) + +// For JSON-escaping; see jsonEncoder.safeAddString below. +const _hex = "0123456789abcdef" + +var _jsonPool = pool.New(func() *jsonEncoder { + return &jsonEncoder{} +}) + +func putJSONEncoder(enc *jsonEncoder) { + if enc.reflectBuf != nil { + enc.reflectBuf.Free() + } + enc.EncoderConfig = nil + enc.buf = nil + enc.spaced = false + enc.openNamespaces = 0 + enc.reflectBuf = nil + enc.reflectEnc = nil + _jsonPool.Put(enc) +} + +type jsonEncoder struct { + *EncoderConfig + buf *buffer.Buffer + spaced bool // include spaces after colons and commas + openNamespaces int + + // for encoding generic values by reflection + reflectBuf *buffer.Buffer + reflectEnc ReflectedEncoder +} + +// NewJSONEncoder creates a fast, low-allocation JSON encoder. The encoder +// appropriately escapes all field keys and values. +// +// Note that the encoder doesn't deduplicate keys, so it's possible to produce +// a message like +// +// {"foo":"bar","foo":"baz"} +// +// This is permitted by the JSON specification, but not encouraged. Many +// libraries will ignore duplicate key-value pairs (typically keeping the last +// pair) when unmarshaling, but users should attempt to avoid adding duplicate +// keys. +func NewJSONEncoder(cfg EncoderConfig) Encoder { + return newJSONEncoder(cfg, false) +} + +func newJSONEncoder(cfg EncoderConfig, spaced bool) *jsonEncoder { + if cfg.SkipLineEnding { + cfg.LineEnding = "" + } else if cfg.LineEnding == "" { + cfg.LineEnding = DefaultLineEnding + } + + // If no EncoderConfig.NewReflectedEncoder is provided by the user, then use default + if cfg.NewReflectedEncoder == nil { + cfg.NewReflectedEncoder = defaultReflectedEncoder + } + + return &jsonEncoder{ + EncoderConfig: &cfg, + buf: bufferpool.Get(), + spaced: spaced, + } +} + +func (enc *jsonEncoder) AddArray(key string, arr ArrayMarshaler) error { + enc.addKey(key) + return enc.AppendArray(arr) +} + +func (enc *jsonEncoder) AddObject(key string, obj ObjectMarshaler) error { + enc.addKey(key) + return enc.AppendObject(obj) +} + +func (enc *jsonEncoder) AddBinary(key string, val []byte) { + enc.AddString(key, base64.StdEncoding.EncodeToString(val)) +} + +func (enc *jsonEncoder) AddByteString(key string, val []byte) { + enc.addKey(key) + enc.AppendByteString(val) +} + +func (enc *jsonEncoder) AddBool(key string, val bool) { + enc.addKey(key) + enc.AppendBool(val) +} + +func (enc *jsonEncoder) AddComplex128(key string, val complex128) { + enc.addKey(key) + enc.AppendComplex128(val) +} + +func (enc *jsonEncoder) AddComplex64(key string, val complex64) { + enc.addKey(key) + enc.AppendComplex64(val) +} + +func (enc *jsonEncoder) AddDuration(key string, val time.Duration) { + enc.addKey(key) + enc.AppendDuration(val) +} + +func (enc *jsonEncoder) AddFloat64(key string, val float64) { + enc.addKey(key) + enc.AppendFloat64(val) +} + +func (enc *jsonEncoder) AddFloat32(key string, val float32) { + enc.addKey(key) + enc.AppendFloat32(val) +} + +func (enc *jsonEncoder) AddInt64(key string, val int64) { + enc.addKey(key) + enc.AppendInt64(val) +} + +func (enc *jsonEncoder) resetReflectBuf() { + if enc.reflectBuf == nil { + enc.reflectBuf = bufferpool.Get() + enc.reflectEnc = enc.NewReflectedEncoder(enc.reflectBuf) + } else { + enc.reflectBuf.Reset() + } +} + +var nullLiteralBytes = []byte("null") + +// Only invoke the standard JSON encoder if there is actually something to +// encode; otherwise write JSON null literal directly. +func (enc *jsonEncoder) encodeReflected(obj interface{}) ([]byte, error) { + if obj == nil { + return nullLiteralBytes, nil + } + enc.resetReflectBuf() + if err := enc.reflectEnc.Encode(obj); err != nil { + return nil, err + } + enc.reflectBuf.TrimNewline() + return enc.reflectBuf.Bytes(), nil +} + +func (enc *jsonEncoder) AddReflected(key string, obj interface{}) error { + valueBytes, err := enc.encodeReflected(obj) + if err != nil { + return err + } + enc.addKey(key) + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) OpenNamespace(key string) { + enc.addKey(key) + enc.buf.AppendByte('{') + enc.openNamespaces++ +} + +func (enc *jsonEncoder) AddString(key, val string) { + enc.addKey(key) + enc.AppendString(val) +} + +func (enc *jsonEncoder) AddTime(key string, val time.Time) { + enc.addKey(key) + enc.AppendTime(val) +} + +func (enc *jsonEncoder) AddUint64(key string, val uint64) { + enc.addKey(key) + enc.AppendUint64(val) +} + +func (enc *jsonEncoder) AppendArray(arr ArrayMarshaler) error { + enc.addElementSeparator() + enc.buf.AppendByte('[') + err := arr.MarshalLogArray(enc) + enc.buf.AppendByte(']') + return err +} + +func (enc *jsonEncoder) AppendObject(obj ObjectMarshaler) error { + // Close ONLY new openNamespaces that are created during + // AppendObject(). + old := enc.openNamespaces + enc.openNamespaces = 0 + enc.addElementSeparator() + enc.buf.AppendByte('{') + err := obj.MarshalLogObject(enc) + enc.buf.AppendByte('}') + enc.closeOpenNamespaces() + enc.openNamespaces = old + return err +} + +func (enc *jsonEncoder) AppendBool(val bool) { + enc.addElementSeparator() + enc.buf.AppendBool(val) +} + +func (enc *jsonEncoder) AppendByteString(val []byte) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddByteString(val) + enc.buf.AppendByte('"') +} + +// appendComplex appends the encoded form of the provided complex128 value. +// precision specifies the encoding precision for the real and imaginary +// components of the complex number. +func (enc *jsonEncoder) appendComplex(val complex128, precision int) { + enc.addElementSeparator() + // Cast to a platform-independent, fixed-size type. + r, i := float64(real(val)), float64(imag(val)) + enc.buf.AppendByte('"') + // Because we're always in a quoted string, we can use strconv without + // special-casing NaN and +/-Inf. + enc.buf.AppendFloat(r, precision) + // If imaginary part is less than 0, minus (-) sign is added by default + // by AppendFloat. + if i >= 0 { + enc.buf.AppendByte('+') + } + enc.buf.AppendFloat(i, precision) + enc.buf.AppendByte('i') + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendDuration(val time.Duration) { + cur := enc.buf.Len() + if e := enc.EncodeDuration; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeDuration is a no-op. Fall back to nanoseconds to keep + // JSON valid. + enc.AppendInt64(int64(val)) + } +} + +func (enc *jsonEncoder) AppendInt64(val int64) { + enc.addElementSeparator() + enc.buf.AppendInt(val) +} + +func (enc *jsonEncoder) AppendReflected(val interface{}) error { + valueBytes, err := enc.encodeReflected(val) + if err != nil { + return err + } + enc.addElementSeparator() + _, err = enc.buf.Write(valueBytes) + return err +} + +func (enc *jsonEncoder) AppendString(val string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(val) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTimeLayout(time time.Time, layout string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.buf.AppendTime(time, layout) + enc.buf.AppendByte('"') +} + +func (enc *jsonEncoder) AppendTime(val time.Time) { + cur := enc.buf.Len() + if e := enc.EncodeTime; e != nil { + e(val, enc) + } + if cur == enc.buf.Len() { + // User-supplied EncodeTime is a no-op. Fall back to nanos since epoch to keep + // output JSON valid. + enc.AppendInt64(val.UnixNano()) + } +} + +func (enc *jsonEncoder) AppendUint64(val uint64) { + enc.addElementSeparator() + enc.buf.AppendUint(val) +} + +func (enc *jsonEncoder) AddInt(k string, v int) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt32(k string, v int32) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt16(k string, v int16) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddInt8(k string, v int8) { enc.AddInt64(k, int64(v)) } +func (enc *jsonEncoder) AddUint(k string, v uint) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint32(k string, v uint32) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint16(k string, v uint16) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUint8(k string, v uint8) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AddUintptr(k string, v uintptr) { enc.AddUint64(k, uint64(v)) } +func (enc *jsonEncoder) AppendComplex64(v complex64) { enc.appendComplex(complex128(v), 32) } +func (enc *jsonEncoder) AppendComplex128(v complex128) { enc.appendComplex(complex128(v), 64) } +func (enc *jsonEncoder) AppendFloat64(v float64) { enc.appendFloat(v, 64) } +func (enc *jsonEncoder) AppendFloat32(v float32) { enc.appendFloat(float64(v), 32) } +func (enc *jsonEncoder) AppendInt(v int) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt32(v int32) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt16(v int16) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendInt8(v int8) { enc.AppendInt64(int64(v)) } +func (enc *jsonEncoder) AppendUint(v uint) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint32(v uint32) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint16(v uint16) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUint8(v uint8) { enc.AppendUint64(uint64(v)) } +func (enc *jsonEncoder) AppendUintptr(v uintptr) { enc.AppendUint64(uint64(v)) } + +func (enc *jsonEncoder) Clone() Encoder { + clone := enc.clone() + clone.buf.Write(enc.buf.Bytes()) + return clone +} + +func (enc *jsonEncoder) clone() *jsonEncoder { + clone := _jsonPool.Get() + clone.EncoderConfig = enc.EncoderConfig + clone.spaced = enc.spaced + clone.openNamespaces = enc.openNamespaces + clone.buf = bufferpool.Get() + return clone +} + +func (enc *jsonEncoder) EncodeEntry(ent Entry, fields []Field) (*buffer.Buffer, error) { + final := enc.clone() + final.buf.AppendByte('{') + + if final.LevelKey != "" && final.EncodeLevel != nil { + final.addKey(final.LevelKey) + cur := final.buf.Len() + final.EncodeLevel(ent.Level, final) + if cur == final.buf.Len() { + // User-supplied EncodeLevel was a no-op. Fall back to strings to keep + // output JSON valid. + final.AppendString(ent.Level.String()) + } + } + if final.TimeKey != "" && !ent.Time.IsZero() { + final.AddTime(final.TimeKey, ent.Time) + } + if ent.LoggerName != "" && final.NameKey != "" { + final.addKey(final.NameKey) + cur := final.buf.Len() + nameEncoder := final.EncodeName + + // if no name encoder provided, fall back to FullNameEncoder for backwards + // compatibility + if nameEncoder == nil { + nameEncoder = FullNameEncoder + } + + nameEncoder(ent.LoggerName, final) + if cur == final.buf.Len() { + // User-supplied EncodeName was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.LoggerName) + } + } + if ent.Caller.Defined { + if final.CallerKey != "" { + final.addKey(final.CallerKey) + cur := final.buf.Len() + final.EncodeCaller(ent.Caller, final) + if cur == final.buf.Len() { + // User-supplied EncodeCaller was a no-op. Fall back to strings to + // keep output JSON valid. + final.AppendString(ent.Caller.String()) + } + } + if final.FunctionKey != "" { + final.addKey(final.FunctionKey) + final.AppendString(ent.Caller.Function) + } + } + if final.MessageKey != "" { + final.addKey(enc.MessageKey) + final.AppendString(ent.Message) + } + if enc.buf.Len() > 0 { + final.addElementSeparator() + final.buf.Write(enc.buf.Bytes()) + } + addFields(final, fields) + final.closeOpenNamespaces() + if ent.Stack != "" && final.StacktraceKey != "" { + final.AddString(final.StacktraceKey, ent.Stack) + } + final.buf.AppendByte('}') + final.buf.AppendString(final.LineEnding) + + ret := final.buf + putJSONEncoder(final) + return ret, nil +} + +func (enc *jsonEncoder) truncate() { + enc.buf.Reset() +} + +func (enc *jsonEncoder) closeOpenNamespaces() { + for i := 0; i < enc.openNamespaces; i++ { + enc.buf.AppendByte('}') + } + enc.openNamespaces = 0 +} + +func (enc *jsonEncoder) addKey(key string) { + enc.addElementSeparator() + enc.buf.AppendByte('"') + enc.safeAddString(key) + enc.buf.AppendByte('"') + enc.buf.AppendByte(':') + if enc.spaced { + enc.buf.AppendByte(' ') + } +} + +func (enc *jsonEncoder) addElementSeparator() { + last := enc.buf.Len() - 1 + if last < 0 { + return + } + switch enc.buf.Bytes()[last] { + case '{', '[', ':', ',', ' ': + return + default: + enc.buf.AppendByte(',') + if enc.spaced { + enc.buf.AppendByte(' ') + } + } +} + +func (enc *jsonEncoder) appendFloat(val float64, bitSize int) { + enc.addElementSeparator() + switch { + case math.IsNaN(val): + enc.buf.AppendString(`"NaN"`) + case math.IsInf(val, 1): + enc.buf.AppendString(`"+Inf"`) + case math.IsInf(val, -1): + enc.buf.AppendString(`"-Inf"`) + default: + enc.buf.AppendFloat(val, bitSize) + } +} + +// safeAddString JSON-escapes a string and appends it to the internal buffer. +// Unlike the standard library's encoder, it doesn't attempt to protect the +// user from browser vulnerabilities or JSONP-related problems. +func (enc *jsonEncoder) safeAddString(s string) { + safeAppendStringLike( + (*buffer.Buffer).AppendString, + utf8.DecodeRuneInString, + enc.buf, + s, + ) +} + +// safeAddByteString is no-alloc equivalent of safeAddString(string(s)) for s []byte. +func (enc *jsonEncoder) safeAddByteString(s []byte) { + safeAppendStringLike( + (*buffer.Buffer).AppendBytes, + utf8.DecodeRune, + enc.buf, + s, + ) +} + +// safeAppendStringLike is a generic implementation of safeAddString and safeAddByteString. +// It appends a string or byte slice to the buffer, escaping all special characters. +func safeAppendStringLike[S []byte | string]( + // appendTo appends this string-like object to the buffer. + appendTo func(*buffer.Buffer, S), + // decodeRune decodes the next rune from the string-like object + // and returns its value and width in bytes. + decodeRune func(S) (rune, int), + buf *buffer.Buffer, + s S, +) { + // The encoding logic below works by skipping over characters + // that can be safely copied as-is, + // until a character is found that needs special handling. + // At that point, we copy everything we've seen so far, + // and then handle that special character. + // + // last is the index of the last byte that was copied to the buffer. + last := 0 + for i := 0; i < len(s); { + if s[i] >= utf8.RuneSelf { + // Character >= RuneSelf may be part of a multi-byte rune. + // They need to be decoded before we can decide how to handle them. + r, size := decodeRune(s[i:]) + if r != utf8.RuneError || size != 1 { + // No special handling required. + // Skip over this rune and continue. + i += size + continue + } + + // Invalid UTF-8 sequence. + // Replace it with the Unicode replacement character. + appendTo(buf, s[last:i]) + buf.AppendString(`\ufffd`) + + i++ + last = i + } else { + // Character < RuneSelf is a single-byte UTF-8 rune. + if s[i] >= 0x20 && s[i] != '\\' && s[i] != '"' { + // No escaping necessary. + // Skip over this character and continue. + i++ + continue + } + + // This character needs to be escaped. + appendTo(buf, s[last:i]) + switch s[i] { + case '\\', '"': + buf.AppendByte('\\') + buf.AppendByte(s[i]) + case '\n': + buf.AppendByte('\\') + buf.AppendByte('n') + case '\r': + buf.AppendByte('\\') + buf.AppendByte('r') + case '\t': + buf.AppendByte('\\') + buf.AppendByte('t') + default: + // Encode bytes < 0x20, except for the escape sequences above. + buf.AppendString(`\u00`) + buf.AppendByte(_hex[s[i]>>4]) + buf.AppendByte(_hex[s[i]&0xF]) + } + + i++ + last = i + } + } + + // add remaining + appendTo(buf, s[last:]) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_bench_test.go new file mode 100644 index 0000000..d870e07 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_bench_test.go @@ -0,0 +1,132 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +func BenchmarkJSONLogMarshalerFunc(b *testing.B) { + for i := 0; i < b.N; i++ { + enc := NewJSONEncoder(testEncoderConfig()) + err := enc.AddObject("nested", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + enc.AddInt64("i", int64(i)) + return nil + })) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkZapJSONFloat32AndComplex64(b *testing.B) { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddFloat32("float32", 3.14) + enc.AddComplex64("complex64", 2.71+3.14i) + } + }) +} + +const _sliceSize = 5000 + +type StringSlice []string + +func (s StringSlice) MarshalLogArray(encoder ArrayEncoder) error { + for _, str := range s { + encoder.AppendString(str) + } + return nil +} + +func generateStringSlice(n int) StringSlice { + output := make(StringSlice, 0, n) + for i := 0; i < n; i++ { + output = append(output, fmt.Sprint("00000000-0000-0000-0000-0000000000", i)) + } + return output +} + +func BenchmarkZapJSON(b *testing.B) { + additional := generateStringSlice(_sliceSize) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + enc := NewJSONEncoder(testEncoderConfig()) + enc.AddString("str", "foo") + enc.AddInt64("int64-1", 1) + enc.AddInt64("int64-2", 2) + enc.AddFloat64("float64", 1.0) + enc.AddString("string1", "\n") + enc.AddString("string2", "💩") + enc.AddString("string3", "🤔") + enc.AddString("string4", "🙊") + enc.AddBool("bool", true) + _ = enc.AddArray("test", additional) + buf, _ := enc.EncodeEntry(Entry{ + Message: "fake", + Level: DebugLevel, + }, nil) + buf.Free() + } + }) +} + +func BenchmarkStandardJSON(b *testing.B) { + record := struct { + Level string `json:"level"` + Message string `json:"msg"` + Time time.Time `json:"ts"` + Fields map[string]interface{} `json:"fields"` + Additional StringSlice + }{ + Level: "debug", + Message: "fake", + Time: time.Unix(0, 0), + Fields: map[string]interface{}{ + "str": "foo", + "int64-1": int64(1), + "int64-2": int64(1), + "float64": float64(1.0), + "string1": "\n", + "string2": "💩", + "string3": "🤔", + "string4": "🙊", + "bool": true, + }, + Additional: generateStringSlice(_sliceSize), + } + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := json.Marshal(record); err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_impl_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_impl_test.go new file mode 100644 index 0000000..5f81262 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_impl_test.go @@ -0,0 +1,736 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "errors" + "math" + "math/rand" + "reflect" + "testing" + "testing/quick" + "time" + "unicode/utf8" + + "go.uber.org/zap/buffer" + "go.uber.org/zap/internal/bufferpool" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/multierr" +) + +var _defaultEncoderConfig = EncoderConfig{ + EncodeTime: EpochTimeEncoder, + EncodeDuration: SecondsDurationEncoder, +} + +func TestJSONClone(t *testing.T) { + // The parent encoder is created with plenty of excess capacity. + parent := &jsonEncoder{buf: bufferpool.Get()} + clone := parent.Clone() + + // Adding to the parent shouldn't affect the clone, and vice versa. + parent.AddString("foo", "bar") + clone.AddString("baz", "bing") + + assertJSON(t, `"foo":"bar"`, parent) + assertJSON(t, `"baz":"bing"`, clone.(*jsonEncoder)) +} + +func TestJSONEscaping(t *testing.T) { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Test all the edge cases of JSON escaping directly. + cases := map[string]string{ + // ASCII. + `foo`: `foo`, + // Special-cased characters. + `"`: `\"`, + `\`: `\\`, + // Special-cased characters within everyday ASCII. + `foo"foo`: `foo\"foo`, + "foo\n": `foo\n`, + // Special-cased control characters. + "\n": `\n`, + "\r": `\r`, + "\t": `\t`, + // \b and \f are sometimes backslash-escaped, but this representation is also + // conformant. + "\b": `\u0008`, + "\f": `\u000c`, + // The standard lib special-cases angle brackets and ampersands by default, + // because it wants to protect users from browser exploits. In a logging + // context, we shouldn't special-case these characters. + "<": "<", + ">": ">", + "&": "&", + // ASCII bell - not special-cased. + string(byte(0x07)): `\u0007`, + // Astral-plane unicode. + `☃`: `☃`, + // Decodes to (RuneError, 1) + "\xed\xa0\x80": `\ufffd\ufffd\ufffd`, + "foo\xed\xa0\x80": `foo\ufffd\ufffd\ufffd`, + } + + t.Run("String", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddString(input) + assertJSON(t, output, enc) + } + }) + + t.Run("ByteString", func(t *testing.T) { + for input, output := range cases { + enc.truncate() + enc.safeAddByteString([]byte(input)) + assertJSON(t, output, enc) + } + }) +} + +func TestJSONEncoderObjectFields(t *testing.T) { + tests := []struct { + desc string + expected string + f func(Encoder) + }{ + {"binary", `"k":"YWIxMg=="`, func(e Encoder) { e.AddBinary("k", []byte("ab12")) }}, + {"bool", `"k\\":true`, func(e Encoder) { e.AddBool(`k\`, true) }}, // test key escaping once + {"bool", `"k":true`, func(e Encoder) { e.AddBool("k", true) }}, + {"bool", `"k":false`, func(e Encoder) { e.AddBool("k", false) }}, + {"byteString", `"k":"v\\"`, func(e Encoder) { e.AddByteString(`k`, []byte(`v\`)) }}, + {"byteString", `"k":"v"`, func(e Encoder) { e.AddByteString("k", []byte("v")) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", []byte{}) }}, + {"byteString", `"k":""`, func(e Encoder) { e.AddByteString("k", nil) }}, + {"complex128", `"k":"1+2i"`, func(e Encoder) { e.AddComplex128("k", 1+2i) }}, + {"complex128/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex128("k", 1-2i) }}, + {"complex64", `"k":"1+2i"`, func(e Encoder) { e.AddComplex64("k", 1+2i) }}, + {"complex64/negative_i", `"k":"1-2i"`, func(e Encoder) { e.AddComplex64("k", 1-2i) }}, + {"complex64", `"k":"2.71+3.14i"`, func(e Encoder) { e.AddComplex64("k", 2.71+3.14i) }}, + {"duration", `"k":0.000000001`, func(e Encoder) { e.AddDuration("k", 1) }}, + {"duration/negative", `"k":-0.000000001`, func(e Encoder) { e.AddDuration("k", -1) }}, + {"float64", `"k":1`, func(e Encoder) { e.AddFloat64("k", 1.0) }}, + {"float64", `"k":10000000000`, func(e Encoder) { e.AddFloat64("k", 1e10) }}, + {"float64", `"k":"NaN"`, func(e Encoder) { e.AddFloat64("k", math.NaN()) }}, + {"float64", `"k":"+Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(1)) }}, + {"float64", `"k":"-Inf"`, func(e Encoder) { e.AddFloat64("k", math.Inf(-1)) }}, + {"float64/pi", `"k":3.141592653589793`, func(e Encoder) { e.AddFloat64("k", math.Pi) }}, + {"float32", `"k":1`, func(e Encoder) { e.AddFloat32("k", 1.0) }}, + {"float32", `"k":2.71`, func(e Encoder) { e.AddFloat32("k", 2.71) }}, + {"float32", `"k":0.1`, func(e Encoder) { e.AddFloat32("k", 0.1) }}, + {"float32", `"k":10000000000`, func(e Encoder) { e.AddFloat32("k", 1e10) }}, + {"float32", `"k":"NaN"`, func(e Encoder) { e.AddFloat32("k", float32(math.NaN())) }}, + {"float32", `"k":"+Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(1))) }}, + {"float32", `"k":"-Inf"`, func(e Encoder) { e.AddFloat32("k", float32(math.Inf(-1))) }}, + {"float32/pi", `"k":3.1415927`, func(e Encoder) { e.AddFloat32("k", math.Pi) }}, + {"int", `"k":42`, func(e Encoder) { e.AddInt("k", 42) }}, + {"int64", `"k":42`, func(e Encoder) { e.AddInt64("k", 42) }}, + {"int64/min", `"k":-9223372036854775808`, func(e Encoder) { e.AddInt64("k", math.MinInt64) }}, + {"int64/max", `"k":9223372036854775807`, func(e Encoder) { e.AddInt64("k", math.MaxInt64) }}, + {"int32", `"k":42`, func(e Encoder) { e.AddInt32("k", 42) }}, + {"int32/min", `"k":-2147483648`, func(e Encoder) { e.AddInt32("k", math.MinInt32) }}, + {"int32/max", `"k":2147483647`, func(e Encoder) { e.AddInt32("k", math.MaxInt32) }}, + {"int16", `"k":42`, func(e Encoder) { e.AddInt16("k", 42) }}, + {"int16/min", `"k":-32768`, func(e Encoder) { e.AddInt16("k", math.MinInt16) }}, + {"int16/max", `"k":32767`, func(e Encoder) { e.AddInt16("k", math.MaxInt16) }}, + {"int8", `"k":42`, func(e Encoder) { e.AddInt8("k", 42) }}, + {"int8/min", `"k":-128`, func(e Encoder) { e.AddInt8("k", math.MinInt8) }}, + {"int8/max", `"k":127`, func(e Encoder) { e.AddInt8("k", math.MaxInt8) }}, + {"string", `"k":"v\\"`, func(e Encoder) { e.AddString(`k`, `v\`) }}, + {"string", `"k":"v"`, func(e Encoder) { e.AddString("k", "v") }}, + {"string", `"k":""`, func(e Encoder) { e.AddString("k", "") }}, + {"time", `"k":1`, func(e Encoder) { e.AddTime("k", time.Unix(1, 0)) }}, + {"uint", `"k":42`, func(e Encoder) { e.AddUint("k", 42) }}, + {"uint64", `"k":42`, func(e Encoder) { e.AddUint64("k", 42) }}, + {"uint64/max", `"k":18446744073709551615`, func(e Encoder) { e.AddUint64("k", math.MaxUint64) }}, + {"uint32", `"k":42`, func(e Encoder) { e.AddUint32("k", 42) }}, + {"uint32/max", `"k":4294967295`, func(e Encoder) { e.AddUint32("k", math.MaxUint32) }}, + {"uint16", `"k":42`, func(e Encoder) { e.AddUint16("k", 42) }}, + {"uint16/max", `"k":65535`, func(e Encoder) { e.AddUint16("k", math.MaxUint16) }}, + {"uint8", `"k":42`, func(e Encoder) { e.AddUint8("k", 42) }}, + {"uint8/max", `"k":255`, func(e Encoder) { e.AddUint8("k", math.MaxUint8) }}, + {"uintptr", `"k":42`, func(e Encoder) { e.AddUintptr("k", 42) }}, + { + desc: "object (success)", + expected: `"k":{"loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Unexpected error calling MarshalLogObject.") + }, + }, + { + desc: "object (error)", + expected: `"k":{}`, + f: func(e Encoder) { + assert.Error(t, e.AddObject("k", loggable{false}), "Expected an error calling MarshalLogObject.") + }, + }, + { + desc: "object (with nested array)", + expected: `"turducken":{"ducks":[{"in":"chicken"},{"in":"chicken"}]}`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddObject("turducken", turducken{}), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (with nested object)", + expected: `"turduckens":[{"ducks":[{"in":"chicken"},{"in":"chicken"}]},{"ducks":[{"in":"chicken"},{"in":"chicken"}]}]`, + f: func(e Encoder) { + assert.NoError( + t, + e.AddArray("turduckens", turduckens(2)), + "Unexpected error calling MarshalLogObject with nested ObjectMarshalers and ArrayMarshalers.", + ) + }, + }, + { + desc: "array (success)", + expected: `"k":[true]`, + f: func(e Encoder) { + assert.NoError(t, e.AddArray(`k`, loggable{true}), "Unexpected error calling MarshalLogArray.") + }, + }, + { + desc: "array (error)", + expected: `"k":[]`, + f: func(e Encoder) { + assert.Error(t, e.AddArray("k", loggable{false}), "Expected an error calling MarshalLogArray.") + }, + }, + { + desc: "reflect (success)", + expected: `"k":{"escape":"<&>","loggable":"yes"}`, + f: func(e Encoder) { + assert.NoError(t, e.AddReflected("k", map[string]string{"escape": "<&>", "loggable": "yes"}), "Unexpected error JSON-serializing a map.") + }, + }, + { + desc: "reflect (failure)", + expected: "", + f: func(e Encoder) { + assert.Error(t, e.AddReflected("k", noJSON{}), "Unexpected success JSON-serializing a noJSON.") + }, + }, + { + desc: "namespace", + // EncodeEntry is responsible for closing all open namespaces. + expected: `"outermost":{"outer":{"foo":1,"inner":{"foo":2,"innermost":{`, + f: func(e Encoder) { + e.OpenNamespace("outermost") + e.OpenNamespace("outer") + e.AddInt("foo", 1) + e.OpenNamespace("inner") + e.AddInt("foo", 2) + e.OpenNamespace("innermost") + }, + }, + { + desc: "object (no nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace"},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("obj", maybeNamespace{false})) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace)", + expected: `"obj":{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"not-obj":"should-be-outside-obj"`, + f: func(e Encoder) { + assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) + e.AddString("not-obj", "should-be-outside-obj") + }, + }, + { + desc: "multiple open namespaces", + expected: `"k":{"foo":1,"middle":{"foo":2,"inner":{"foo":3}}}`, + f: func(e Encoder) { + err := e.AddObject("k", ObjectMarshalerFunc(func(enc ObjectEncoder) error { + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + return nil + })) + assert.NoError(t, err) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, _defaultEncoderConfig, tt.expected, tt.f) + }) + } +} + +func TestJSONEncoderTimeFormats(t *testing.T) { + date := time.Date(2000, time.January, 2, 3, 4, 5, 6, time.UTC) + + f := func(e Encoder) { + e.AddTime("k", date) + err := e.AddArray("a", ArrayMarshalerFunc(func(enc ArrayEncoder) error { + enc.AppendTime(date) + return nil + })) + assert.NoError(t, err) + } + tests := []struct { + desc string + cfg EncoderConfig + expected string + }{ + { + desc: "time.Time ISO8601", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: ISO8601TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000Z","a":["2000-01-02T03:04:05.000Z"]`, + }, + { + desc: "time.Time RFC3339", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339TimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05Z","a":["2000-01-02T03:04:05Z"]`, + }, + { + desc: "time.Time RFC3339Nano", + cfg: EncoderConfig{ + EncodeDuration: NanosDurationEncoder, + EncodeTime: RFC3339NanoTimeEncoder, + }, + expected: `"k":"2000-01-02T03:04:05.000000006Z","a":["2000-01-02T03:04:05.000000006Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + assertOutput(t, tt.cfg, tt.expected, f) + }) + } +} + +func TestJSONEncoderArrays(t *testing.T) { + tests := []struct { + desc string + expected string // expect f to be called twice + f func(ArrayEncoder) + }{ + {"bool", `[true,true]`, func(e ArrayEncoder) { e.AppendBool(true) }}, + {"byteString", `["k","k"]`, func(e ArrayEncoder) { e.AppendByteString([]byte("k")) }}, + {"byteString", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendByteString([]byte(`k\`)) }}, + {"complex128", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }}, + {"complex64", `["1+2i","1+2i"]`, func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }}, + {"durations", `[0.000000002,0.000000002]`, func(e ArrayEncoder) { e.AppendDuration(2) }}, + {"float64", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat64(3.14) }}, + {"float32", `[3.14,3.14]`, func(e ArrayEncoder) { e.AppendFloat32(3.14) }}, + {"int", `[42,42]`, func(e ArrayEncoder) { e.AppendInt(42) }}, + {"int64", `[42,42]`, func(e ArrayEncoder) { e.AppendInt64(42) }}, + {"int32", `[42,42]`, func(e ArrayEncoder) { e.AppendInt32(42) }}, + {"int16", `[42,42]`, func(e ArrayEncoder) { e.AppendInt16(42) }}, + {"int8", `[42,42]`, func(e ArrayEncoder) { e.AppendInt8(42) }}, + {"string", `["k","k"]`, func(e ArrayEncoder) { e.AppendString("k") }}, + {"string", `["k\\","k\\"]`, func(e ArrayEncoder) { e.AppendString(`k\`) }}, + {"times", `[1,1]`, func(e ArrayEncoder) { e.AppendTime(time.Unix(1, 0)) }}, + {"uint", `[42,42]`, func(e ArrayEncoder) { e.AppendUint(42) }}, + {"uint64", `[42,42]`, func(e ArrayEncoder) { e.AppendUint64(42) }}, + {"uint32", `[42,42]`, func(e ArrayEncoder) { e.AppendUint32(42) }}, + {"uint16", `[42,42]`, func(e ArrayEncoder) { e.AppendUint16(42) }}, + {"uint8", `[42,42]`, func(e ArrayEncoder) { e.AppendUint8(42) }}, + {"uintptr", `[42,42]`, func(e ArrayEncoder) { e.AppendUintptr(42) }}, + { + desc: "arrays (success)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return nil + })), "Unexpected error appending an array.") + }, + }, + { + desc: "arrays (error)", + expected: `[[true],[true]]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + return errors.New("fail") + })), "Expected an error appending an array.") + }, + }, + { + desc: "objects (success)", + expected: `[{"loggable":"yes"},{"loggable":"yes"}]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(loggable{true}), "Unexpected error appending an object.") + }, + }, + { + desc: "objects (error)", + expected: `[{},{}]`, + f: func(arr ArrayEncoder) { + assert.Error(t, arr.AppendObject(loggable{false}), "Expected an error appending an object.") + }, + }, + { + desc: "reflect (success)", + expected: `[{"foo":5},{"foo":5}]`, + f: func(arr ArrayEncoder) { + assert.NoError( + t, + arr.AppendReflected(map[string]int{"foo": 5}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "reflect (error)", + expected: `[]`, + f: func(arr ArrayEncoder) { + assert.Error( + t, + arr.AppendReflected(noJSON{}), + "Unexpected an error appending an object with reflection.", + ) + }, + }, + { + desc: "object (no nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace"},"should-be-outside-obj",{"obj-out":"obj-outside-namespace"},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(maybeNamespace{false})) + arr.AppendString("should-be-outside-obj") + }, + }, + { + desc: "object (with nested namespace) then string", + expected: `[{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj",{"obj-out":"obj-outside-namespace","obj-namespace":{"obj-in":"obj-inside-namespace"}},"should-be-outside-obj"]`, + f: func(arr ArrayEncoder) { + assert.NoError(t, arr.AppendObject(maybeNamespace{true})) + arr.AppendString("should-be-outside-obj") + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + f := func(enc Encoder) error { + return enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })) + } + assertOutput(t, _defaultEncoderConfig, `"array":`+tt.expected, func(enc Encoder) { + err := f(enc) + assert.NoError(t, err, "Unexpected error adding array to JSON encoder.") + }) + }) + } +} + +func TestJSONEncoderTimeArrays(t *testing.T) { + times := []time.Time{ + time.Unix(1008720000, 0).UTC(), // 2001-12-19 + time.Unix(1040169600, 0).UTC(), // 2002-12-18 + time.Unix(1071619200, 0).UTC(), // 2003-12-17 + } + + tests := []struct { + desc string + encoder TimeEncoder + want string + }{ + { + desc: "epoch", + encoder: EpochTimeEncoder, + want: `[1008720000,1040169600,1071619200]`, + }, + { + desc: "epoch millis", + encoder: EpochMillisTimeEncoder, + want: `[1008720000000,1040169600000,1071619200000]`, + }, + { + desc: "iso8601", + encoder: ISO8601TimeEncoder, + want: `["2001-12-19T00:00:00.000Z","2002-12-18T00:00:00.000Z","2003-12-17T00:00:00.000Z"]`, + }, + { + desc: "rfc3339", + encoder: RFC3339TimeEncoder, + want: `["2001-12-19T00:00:00Z","2002-12-18T00:00:00Z","2003-12-17T00:00:00Z"]`, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + cfg := _defaultEncoderConfig + cfg.EncodeTime = tt.encoder + + enc := &jsonEncoder{buf: bufferpool.Get(), EncoderConfig: &cfg} + err := enc.AddArray("array", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for _, time := range times { + arr.AppendTime(time) + } + return nil + })) + assert.NoError(t, err) + assert.Equal(t, `"array":`+tt.want, enc.buf.String()) + }) + } +} + +func assertJSON(t *testing.T, expected string, enc *jsonEncoder) { + assert.Equal(t, expected, enc.buf.String(), "Encoded JSON didn't match expectations.") +} + +func assertOutput(t testing.TB, cfg EncoderConfig, expected string, f func(Encoder)) { + enc := NewJSONEncoder(cfg).(*jsonEncoder) + f(enc) + assert.Equal(t, expected, enc.buf.String(), "Unexpected encoder output after adding.") + + enc.truncate() + enc.AddString("foo", "bar") + f(enc) + expectedPrefix := `"foo":"bar"` + if expected != "" { + // If we expect output, it should be comma-separated from the previous + // field. + expectedPrefix += "," + } + assert.Equal(t, expectedPrefix+expected, enc.buf.String(), "Unexpected encoder output after adding as a second field.") +} + +// Nested Array- and ObjectMarshalers. +type turducken struct{} + +func (t turducken) MarshalLogObject(enc ObjectEncoder) error { + return enc.AddArray("ducks", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + for i := 0; i < 2; i++ { + err := arr.AppendObject(ObjectMarshalerFunc(func(inner ObjectEncoder) error { + inner.AddString("in", "chicken") + return nil + })) + if err != nil { + return err + } + } + return nil + })) +} + +type turduckens int + +func (t turduckens) MarshalLogArray(enc ArrayEncoder) error { + var err error + tur := turducken{} + for i := 0; i < int(t); i++ { + err = multierr.Append(err, enc.AppendObject(tur)) + } + return err +} + +type loggable struct{ bool } + +func (l loggable) MarshalLogObject(enc ObjectEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AddString("loggable", "yes") + return nil +} + +func (l loggable) MarshalLogArray(enc ArrayEncoder) error { + if !l.bool { + return errors.New("can't marshal") + } + enc.AppendBool(true) + return nil +} + +// maybeNamespace is an ObjectMarshaler that sometimes opens a namespace +type maybeNamespace struct{ bool } + +func (m maybeNamespace) MarshalLogObject(enc ObjectEncoder) error { + enc.AddString("obj-out", "obj-outside-namespace") + if m.bool { + enc.OpenNamespace("obj-namespace") + enc.AddString("obj-in", "obj-inside-namespace") + } + return nil +} + +type noJSON struct{} + +func (nj noJSON) MarshalJSON() ([]byte, error) { + return nil, errors.New("no") +} + +func zapEncode(encode func(*jsonEncoder, string)) func(s string) []byte { + return func(s string) []byte { + enc := &jsonEncoder{buf: bufferpool.Get()} + // Escape and quote a string using our encoder. + var ret []byte + encode(enc, s) + ret = make([]byte, 0, enc.buf.Len()+2) + ret = append(ret, '"') + ret = append(ret, enc.buf.Bytes()...) + ret = append(ret, '"') + return ret + } +} + +func roundTripsCorrectly(encode func(string) []byte, original string) bool { + // Encode using our encoder, decode using the standard library, and assert + // that we haven't lost any information. + encoded := encode(original) + + var decoded string + err := json.Unmarshal(encoded, &decoded) + if err != nil { + return false + } + return original == decoded +} + +func roundTripsCorrectlyString(original string) bool { + return roundTripsCorrectly(zapEncode((*jsonEncoder).safeAddString), original) +} + +func roundTripsCorrectlyByteString(original string) bool { + return roundTripsCorrectly( + zapEncode(func(enc *jsonEncoder, s string) { + enc.safeAddByteString([]byte(s)) + }), + original) +} + +type ASCII string + +func (s ASCII) Generate(r *rand.Rand, size int) reflect.Value { + bs := make([]byte, size) + for i := range bs { + bs[i] = byte(r.Intn(128)) + } + a := ASCII(bs) + return reflect.ValueOf(a) +} + +func asciiRoundTripsCorrectlyString(s ASCII) bool { + return roundTripsCorrectlyString(string(s)) +} + +func asciiRoundTripsCorrectlyByteString(s ASCII) bool { + return roundTripsCorrectlyByteString(string(s)) +} + +func TestJSONQuick(t *testing.T) { + check := func(f interface{}) { + err := quick.Check(f, &quick.Config{MaxCountScale: 100.0}) + assert.NoError(t, err) + } + // Test the full range of UTF-8 strings. + check(roundTripsCorrectlyString) + check(roundTripsCorrectlyByteString) + + // Focus on ASCII strings. + check(asciiRoundTripsCorrectlyString) + check(asciiRoundTripsCorrectlyByteString) +} + +var _stringLikeCorpus = []string{ + "", + "foo", + "bar", + "a\nb", + "a\tb", + "a\\b", + `a"b`, +} + +func FuzzSafeAppendStringLike_bytes(f *testing.F) { + for _, s := range _stringLikeCorpus { + f.Add([]byte(s)) + } + f.Fuzz(func(t *testing.T, b []byte) { + if !utf8.Valid(b) { + t.Skip() + } + + fuzzSafeAppendStringLike(t, string(b), func(buf *buffer.Buffer) { + safeAppendStringLike( + (*buffer.Buffer).AppendBytes, + utf8.DecodeRune, + buf, + b, + ) + }) + }) +} + +func FuzzSafeAppendStringLike_string(f *testing.F) { + for _, s := range _stringLikeCorpus { + f.Add(s) + } + f.Fuzz(func(t *testing.T, s string) { + if !utf8.ValidString(s) { + t.Skip() + } + + fuzzSafeAppendStringLike(t, s, func(buf *buffer.Buffer) { + safeAppendStringLike( + (*buffer.Buffer).AppendString, + utf8.DecodeRuneInString, + buf, + s, + ) + }) + }) +} + +func fuzzSafeAppendStringLike( + t *testing.T, + want string, + writeString func(*buffer.Buffer), +) { + t.Helper() + + buf := bufferpool.Get() + defer buf.Free() + + buf.AppendByte('"') + writeString(buf) + buf.AppendByte('"') + + var got string + require.NoError(t, json.Unmarshal(buf.Bytes(), &got)) + assert.Equal(t, want, got) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_test.go new file mode 100644 index 0000000..b215025 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/json_encoder_test.go @@ -0,0 +1,282 @@ +// Copyright (c) 2018 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "io" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// TestJSONEncodeEntry is an more "integrated" test that makes it easier to get +// coverage on the json encoder (e.g. putJSONEncoder, let alone EncodeEntry +// itself) than the tests in json_encoder_impl_test.go; it needs to be in the +// zapcore_test package, so that it can import the toplevel zap package for +// field constructor convenience. +func TestJSONEncodeEntry(t *testing.T) { + type bar struct { + Key string `json:"key"` + Val float64 `json:"val"` + } + + type foo struct { + A string `json:"aee"` + B int `json:"bee"` + C float64 `json:"cee"` + D []bar `json:"dee"` + } + + tests := []struct { + desc string + expected string + ent zapcore.Entry + fields []zapcore.Field + }{ + { + desc: "info entry with some fields", + expected: `{ + "L": "info", + "T": "2018-06-19T16:33:42.000Z", + "N": "bob", + "M": "lob law", + "so": "passes", + "answer": 42, + "a_float32": 2.71, + "common_pie": 3.14, + "complex_value": "3.14-2.71i", + "null_value": null, + "array_with_null_elements": [{}, null, null, 2], + "such": { + "aee": "lol", + "bee": 123, + "cee": 0.9999, + "dee": [ + {"key": "pi", "val": 3.141592653589793}, + {"key": "tau", "val": 6.283185307179586} + ] + } + }`, + ent: zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), + LoggerName: "bob", + Message: "lob law", + }, + fields: []zapcore.Field{ + zap.String("so", "passes"), + zap.Int("answer", 42), + zap.Float64("common_pie", 3.14), + zap.Float32("a_float32", 2.71), + zap.Complex128("complex_value", 3.14-2.71i), + // Cover special-cased handling of nil in AddReflect() and + // AppendReflect(). Note that for the latter, we explicitly test + // correct results for both the nil static interface{} value + // (`nil`), as well as the non-nil interface value with a + // dynamic type and nil value (`(*struct{})(nil)`). + zap.Reflect("null_value", nil), + zap.Reflect("array_with_null_elements", []interface{}{&struct{}{}, nil, (*struct{})(nil), 2}), + zap.Reflect("such", foo{ + A: "lol", + B: 123, + C: 0.9999, + D: []bar{ + {"pi", 3.141592653589793}, + {"tau", 6.283185307179586}, + }, + }), + }, + }, + { + desc: "zero_time_omitted", + expected: `{ + "L": "info", + "N": "name", + "M": "message" + }`, + ent: zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Time{}, + LoggerName: "name", + Message: "message", + }, + }, + } + + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }) + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + buf, err := enc.EncodeEntry(tt.ent, tt.fields) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + buf.Free() + }) + } +} + +func TestNoEncodeLevelSupplied(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + MessageKey: "M", + LevelKey: "L", + TimeKey: "T", + NameKey: "N", + CallerKey: "C", + FunctionKey: "F", + StacktraceKey: "S", + EncodeTime: zapcore.ISO8601TimeEncoder, + EncodeDuration: zapcore.SecondsDurationEncoder, + EncodeCaller: zapcore.ShortCallerEncoder, + }) + + ent := zapcore.Entry{ + Level: zapcore.InfoLevel, + Time: time.Date(2018, 6, 19, 16, 33, 42, 99, time.UTC), + LoggerName: "bob", + Message: "lob law", + } + + fields := []zapcore.Field{ + zap.Int("answer", 42), + } + + _, err := enc.EncodeEntry(ent, fields) + assert.NoError(t, err, "Unexpected JSON encoding error.") +} + +func TestJSONEmptyConfig(t *testing.T) { + tests := []struct { + name string + field zapcore.Field + expected string + }{ + { + name: "time", + field: zap.Time("foo", time.Unix(1591287718, 0)), // 2020-06-04 09:21:58 -0700 PDT + expected: `{"foo": 1591287718000000000}`, + }, + { + name: "duration", + field: zap.Duration("bar", time.Microsecond), + expected: `{"bar": 1000}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{}) + + buf, err := enc.EncodeEntry(zapcore.Entry{ + Level: zapcore.DebugLevel, + Time: time.Now(), + LoggerName: "mylogger", + Message: "things happened", + }, []zapcore.Field{tt.field}) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + + buf.Free() + }) + } +} + +// Encodes any object into empty json '{}' +type emptyReflectedEncoder struct { + writer io.Writer +} + +func (enc *emptyReflectedEncoder) Encode(obj interface{}) error { + _, err := enc.writer.Write([]byte("{}")) + return err +} + +func TestJSONCustomReflectedEncoder(t *testing.T) { + tests := []struct { + name string + field zapcore.Field + expected string + }{ + { + name: "encode custom map object", + field: zapcore.Field{ + Key: "data", + Type: zapcore.ReflectType, + Interface: map[string]interface{}{ + "foo": "hello", + "bar": 1111, + }, + }, + expected: `{"data":{}}`, + }, + { + name: "encode nil object", + field: zapcore.Field{ + Key: "data", + Type: zapcore.ReflectType, + }, + expected: `{"data":null}`, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + enc := zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + NewReflectedEncoder: func(writer io.Writer) zapcore.ReflectedEncoder { + return &emptyReflectedEncoder{ + writer: writer, + } + }, + }) + + buf, err := enc.EncodeEntry(zapcore.Entry{ + Level: zapcore.DebugLevel, + Time: time.Now(), + LoggerName: "logger", + Message: "things happened", + }, []zapcore.Field{tt.field}) + if assert.NoError(t, err, "Unexpected JSON encoding error.") { + assert.JSONEq(t, tt.expected, buf.String(), "Incorrect encoded JSON entry.") + } + buf.Free() + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with.go new file mode 100644 index 0000000..05288d6 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "sync" + +type lazyWithCore struct { + Core + sync.Once + fields []Field +} + +// NewLazyWith wraps a Core with a "lazy" Core that will only encode fields if +// the logger is written to (or is further chained in a lon-lazy manner). +func NewLazyWith(core Core, fields []Field) Core { + return &lazyWithCore{ + Core: core, + fields: fields, + } +} + +func (d *lazyWithCore) initOnce() { + d.Once.Do(func() { + d.Core = d.Core.With(d.fields) + }) +} + +func (d *lazyWithCore) With(fields []Field) Core { + d.initOnce() + return d.Core.With(fields) +} + +func (d *lazyWithCore) Check(e Entry, ce *CheckedEntry) *CheckedEntry { + d.initOnce() + return d.Core.Check(e, ce) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with_test.go new file mode 100644 index 0000000..c86b59e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/lazy_with_test.go @@ -0,0 +1,154 @@ +// Copyright (c) 2023 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +type proxyCore struct { + zapcore.Core + + withCount atomic.Int64 + checkCount atomic.Int64 +} + +func newProxyCore(inner zapcore.Core) *proxyCore { + return &proxyCore{Core: inner} +} + +func (p *proxyCore) With(fields []zapcore.Field) zapcore.Core { + p.withCount.Add(1) + return p.Core.With(fields) +} + +func (p *proxyCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + p.checkCount.Add(1) + return p.Core.Check(e, ce) +} + +func withLazyCore(f func(zapcore.Core, *proxyCore, *observer.ObservedLogs), initialFields ...zapcore.Field) { + infoLogger, infoLogs := observer.New(zapcore.InfoLevel) + proxyCore := newProxyCore(infoLogger) + lazyCore := zapcore.NewLazyWith(proxyCore, initialFields) + f(lazyCore, proxyCore, infoLogs) +} + +func TestLazyCore(t *testing.T) { + tests := []struct { + name string + entries []zapcore.Entry + initialFields []zapcore.Field + withChains [][]zapcore.Field + wantLogs []observer.LoggedEntry + }{ + { + name: "no logging, no with, inner core with never called, inner core check never called", + wantLogs: []observer.LoggedEntry{}, + }, + { + name: "2 logs, 1 dropped, no with, inner core with called once, inner core check never called", + entries: []zapcore.Entry{ + {Level: zapcore.DebugLevel, Message: "log-at-debug"}, + {Level: zapcore.WarnLevel, Message: "log-at-warn"}, + }, + wantLogs: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{ + Level: zapcore.WarnLevel, + Message: "log-at-warn", + }, + Context: []zapcore.Field{}, + }, + }, + }, + { + name: "no logs, 2-chained with, inner core with called once, inner core check never called", + withChains: [][]zapcore.Field{ + {makeInt64Field("a", 11), makeInt64Field("b", 22)}, + {makeInt64Field("c", 33), makeInt64Field("d", 44)}, + }, + wantLogs: []observer.LoggedEntry{}, + }, + { + name: "2 logs, 1 dropped, 2-chained with, inner core with called once, inner core check never called", + entries: []zapcore.Entry{ + {Level: zapcore.DebugLevel, Message: "log-at-debug"}, + {Level: zapcore.WarnLevel, Message: "log-at-warn"}, + }, + withChains: [][]zapcore.Field{ + {makeInt64Field("a", 11), makeInt64Field("b", 22)}, + {makeInt64Field("c", 33), makeInt64Field("d", 44)}, + }, + wantLogs: []observer.LoggedEntry{ + { + Entry: zapcore.Entry{ + Level: zapcore.WarnLevel, + Message: "log-at-warn", + }, + Context: []zapcore.Field{ + makeInt64Field("a", 11), + makeInt64Field("b", 22), + makeInt64Field("c", 33), + makeInt64Field("d", 44), + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + withLazyCore(func(lazy zapcore.Core, proxy *proxyCore, logs *observer.ObservedLogs) { + checkCounts := func(withCount int64, msg string) { + assert.Equal(t, withCount, proxy.withCount.Load(), msg) + } + checkCounts(0, "expected no with calls because the logger is not used yet") + + for _, chain := range tt.withChains { + lazy = lazy.With(chain) + } + if len(tt.withChains) > 0 { + checkCounts(1, "expected with calls because the logger was with-chained") + } else { + checkCounts(0, "expected no with calls because the logger is not used yet") + } + + for _, ent := range tt.entries { + if ce := lazy.Check(ent, nil); ce != nil { + ce.Write() + } + } + if len(tt.entries) > 0 || len(tt.withChains) > 0 { + checkCounts(1, "expected with calls because the logger had entries or with chains") + } else { + checkCounts(0, "expected no with calls because the logger is not used yet") + } + assert.Zero(t, proxy.checkCount.Load(), "expected no check calls because the inner core is copied") + assert.Equal(t, tt.wantLogs, logs.AllUntimed()) + }, tt.initialFields...) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/leak_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/leak_test.go new file mode 100644 index 0000000..4ef412e --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/leak_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level.go new file mode 100644 index 0000000..e01a241 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level.go @@ -0,0 +1,229 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "fmt" +) + +var errUnmarshalNilLevel = errors.New("can't unmarshal a nil *Level") + +// A Level is a logging priority. Higher levels are more important. +type Level int8 + +const ( + // DebugLevel logs are typically voluminous, and are usually disabled in + // production. + DebugLevel Level = iota - 1 + // InfoLevel is the default logging priority. + InfoLevel + // WarnLevel logs are more important than Info, but don't need individual + // human review. + WarnLevel + // ErrorLevel logs are high-priority. If an application is running smoothly, + // it shouldn't generate any error-level logs. + ErrorLevel + // DPanicLevel logs are particularly important errors. In development the + // logger panics after writing the message. + DPanicLevel + // PanicLevel logs a message, then panics. + PanicLevel + // FatalLevel logs a message, then calls os.Exit(1). + FatalLevel + + _minLevel = DebugLevel + _maxLevel = FatalLevel + + // InvalidLevel is an invalid value for Level. + // + // Core implementations may panic if they see messages of this level. + InvalidLevel = _maxLevel + 1 +) + +// ParseLevel parses a level based on the lower-case or all-caps ASCII +// representation of the log level. If the provided ASCII representation is +// invalid an error is returned. +// +// This is particularly useful when dealing with text input to configure log +// levels. +func ParseLevel(text string) (Level, error) { + var level Level + err := level.UnmarshalText([]byte(text)) + return level, err +} + +type leveledEnabler interface { + LevelEnabler + + Level() Level +} + +// LevelOf reports the minimum enabled log level for the given LevelEnabler +// from Zap's supported log levels, or [InvalidLevel] if none of them are +// enabled. +// +// A LevelEnabler may implement a 'Level() Level' method to override the +// behavior of this function. +// +// func (c *core) Level() Level { +// return c.currentLevel +// } +// +// It is recommended that [Core] implementations that wrap other cores use +// LevelOf to retrieve the level of the wrapped core. For example, +// +// func (c *coreWrapper) Level() Level { +// return zapcore.LevelOf(c.wrappedCore) +// } +func LevelOf(enab LevelEnabler) Level { + if lvler, ok := enab.(leveledEnabler); ok { + return lvler.Level() + } + + for lvl := _minLevel; lvl <= _maxLevel; lvl++ { + if enab.Enabled(lvl) { + return lvl + } + } + + return InvalidLevel +} + +// String returns a lower-case ASCII representation of the log level. +func (l Level) String() string { + switch l { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warn" + case ErrorLevel: + return "error" + case DPanicLevel: + return "dpanic" + case PanicLevel: + return "panic" + case FatalLevel: + return "fatal" + default: + return fmt.Sprintf("Level(%d)", l) + } +} + +// CapitalString returns an all-caps ASCII representation of the log level. +func (l Level) CapitalString() string { + // Printing levels in all-caps is common enough that we should export this + // functionality. + switch l { + case DebugLevel: + return "DEBUG" + case InfoLevel: + return "INFO" + case WarnLevel: + return "WARN" + case ErrorLevel: + return "ERROR" + case DPanicLevel: + return "DPANIC" + case PanicLevel: + return "PANIC" + case FatalLevel: + return "FATAL" + default: + return fmt.Sprintf("LEVEL(%d)", l) + } +} + +// MarshalText marshals the Level to text. Note that the text representation +// drops the -Level suffix (see example). +func (l Level) MarshalText() ([]byte, error) { + return []byte(l.String()), nil +} + +// UnmarshalText unmarshals text to a level. Like MarshalText, UnmarshalText +// expects the text representation of a Level to drop the -Level suffix (see +// example). +// +// In particular, this makes it easy to configure logging levels using YAML, +// TOML, or JSON files. +func (l *Level) UnmarshalText(text []byte) error { + if l == nil { + return errUnmarshalNilLevel + } + if !l.unmarshalText(text) && !l.unmarshalText(bytes.ToLower(text)) { + return fmt.Errorf("unrecognized level: %q", text) + } + return nil +} + +func (l *Level) unmarshalText(text []byte) bool { + switch string(text) { + case "debug", "DEBUG": + *l = DebugLevel + case "info", "INFO", "": // make the zero value useful + *l = InfoLevel + case "warn", "WARN": + *l = WarnLevel + case "error", "ERROR": + *l = ErrorLevel + case "dpanic", "DPANIC": + *l = DPanicLevel + case "panic", "PANIC": + *l = PanicLevel + case "fatal", "FATAL": + *l = FatalLevel + default: + return false + } + return true +} + +// Set sets the level for the flag.Value interface. +func (l *Level) Set(s string) error { + return l.UnmarshalText([]byte(s)) +} + +// Get gets the level for the flag.Getter interface. +func (l *Level) Get() interface{} { + return *l +} + +// Enabled returns true if the given level is at or above this level. +func (l Level) Enabled(lvl Level) bool { + return lvl >= l +} + +// LevelEnabler decides whether a given logging level is enabled when logging a +// message. +// +// Enablers are intended to be used to implement deterministic filters; +// concerns like sampling are better implemented as a Core. +// +// Each concrete Level value implements a static LevelEnabler which returns +// true for itself and all higher logging levels. For example WarnLevel.Enabled() +// will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and +// FatalLevel, but return false for InfoLevel and DebugLevel. +type LevelEnabler interface { + Enabled(Level) bool +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings.go new file mode 100644 index 0000000..7af8dad --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings.go @@ -0,0 +1,46 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/zap/internal/color" + +var ( + _levelToColor = map[Level]color.Color{ + DebugLevel: color.Magenta, + InfoLevel: color.Blue, + WarnLevel: color.Yellow, + ErrorLevel: color.Red, + DPanicLevel: color.Red, + PanicLevel: color.Red, + FatalLevel: color.Red, + } + _unknownLevelColor = color.Red + + _levelToLowercaseColorString = make(map[Level]string, len(_levelToColor)) + _levelToCapitalColorString = make(map[Level]string, len(_levelToColor)) +) + +func init() { + for level, color := range _levelToColor { + _levelToLowercaseColorString[level] = color.Add(level.String()) + _levelToCapitalColorString[level] = color.Add(level.CapitalString()) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings_test.go new file mode 100644 index 0000000..14b0bac --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_strings_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAllLevelsCoveredByLevelString(t *testing.T) { + numLevels := int((_maxLevel - _minLevel) + 1) + + isComplete := func(m map[Level]string) bool { + return len(m) == numLevels + } + + assert.True(t, isComplete(_levelToLowercaseColorString), "Colored lowercase strings don't cover all levels.") + assert.True(t, isComplete(_levelToCapitalColorString), "Colored capital strings don't cover all levels.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_test.go new file mode 100644 index 0000000..d8eb962 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/level_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "flag" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLevelString(t *testing.T) { + tests := map[Level]string{ + DebugLevel: "debug", + InfoLevel: "info", + WarnLevel: "warn", + ErrorLevel: "error", + DPanicLevel: "dpanic", + PanicLevel: "panic", + FatalLevel: "fatal", + Level(-42): "Level(-42)", + InvalidLevel: "Level(6)", // InvalidLevel does not have a name + } + + for lvl, stringLevel := range tests { + assert.Equal(t, stringLevel, lvl.String(), "Unexpected lowercase level string.") + assert.Equal(t, strings.ToUpper(stringLevel), lvl.CapitalString(), "Unexpected all-caps level string.") + } +} + +func TestLevelText(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"debug", DebugLevel}, + {"info", InfoLevel}, + {"", InfoLevel}, // make the zero value useful + {"warn", WarnLevel}, + {"error", ErrorLevel}, + {"dpanic", DPanicLevel}, + {"panic", PanicLevel}, + {"fatal", FatalLevel}, + } + for _, tt := range tests { + if tt.text != "" { + lvl := tt.level + marshaled, err := lvl.MarshalText() + assert.NoError(t, err, "Unexpected error marshaling level %v to text.", &lvl) + assert.Equal(t, tt.text, string(marshaled), "Marshaling level %v to text yielded unexpected result.", &lvl) + } + + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestParseLevel(t *testing.T) { + tests := []struct { + text string + level Level + err string + }{ + {"info", InfoLevel, ""}, + {"DEBUG", DebugLevel, ""}, + {"FOO", 0, `unrecognized level: "FOO"`}, + } + for _, tt := range tests { + parsedLevel, err := ParseLevel(tt.text) + if len(tt.err) == 0 { + assert.NoError(t, err) + assert.Equal(t, tt.level, parsedLevel) + } else { + assert.ErrorContains(t, err, tt.err) + } + } +} + +func TestCapitalLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + {"DEBUG", DebugLevel}, + {"INFO", InfoLevel}, + {"WARN", WarnLevel}, + {"ERROR", ErrorLevel}, + {"DPANIC", DPanicLevel}, + {"PANIC", PanicLevel}, + {"FATAL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestWeirdLevelsParse(t *testing.T) { + tests := []struct { + text string + level Level + }{ + // I guess... + {"Debug", DebugLevel}, + {"Info", InfoLevel}, + {"Warn", WarnLevel}, + {"Error", ErrorLevel}, + {"Dpanic", DPanicLevel}, + {"Panic", PanicLevel}, + {"Fatal", FatalLevel}, + + // What even is... + {"DeBuG", DebugLevel}, + {"InFo", InfoLevel}, + {"WaRn", WarnLevel}, + {"ErRor", ErrorLevel}, + {"DpAnIc", DPanicLevel}, + {"PaNiC", PanicLevel}, + {"FaTaL", FatalLevel}, + } + for _, tt := range tests { + var unmarshaled Level + err := unmarshaled.UnmarshalText([]byte(tt.text)) + assert.NoError(t, err, `Unexpected error unmarshaling text %q to level.`, tt.text) + assert.Equal(t, tt.level, unmarshaled, `Text %q unmarshaled to an unexpected level.`, tt.text) + } +} + +func TestLevelNils(t *testing.T) { + var l *Level + + // The String() method will not handle nil level properly. + assert.Panics(t, func() { + assert.Equal(t, "Level(nil)", l.String(), "Unexpected result stringifying nil *Level.") + }, "Level(nil).String() should panic") + + assert.Panics(t, func() { + _, _ = l.MarshalText() // should panic + }, "Expected to panic when marshalling a nil level.") + + err := l.UnmarshalText([]byte("debug")) + assert.Equal(t, errUnmarshalNilLevel, err, "Expected to error unmarshalling into a nil Level.") +} + +func TestLevelUnmarshalUnknownText(t *testing.T) { + var l Level + err := l.UnmarshalText([]byte("foo")) + assert.ErrorContains(t, err, "unrecognized level", "Expected unmarshaling arbitrary text to fail.") +} + +func TestLevelAsFlagValue(t *testing.T) { + var ( + buf bytes.Buffer + lvl Level + ) + fs := flag.NewFlagSet("levelTest", flag.ContinueOnError) + fs.SetOutput(&buf) + fs.Var(&lvl, "level", "log level") + + for _, expected := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { + assert.NoError(t, fs.Parse([]string{"-level", expected.String()})) + assert.Equal(t, expected, lvl, "Unexpected level after parsing flag.") + assert.Equal(t, expected, lvl.Get(), "Unexpected output using flag.Getter API.") + assert.Empty(t, buf.String(), "Unexpected error output parsing level flag.") + buf.Reset() + } + + assert.Error(t, fs.Parse([]string{"-level", "nope"})) + assert.Equal( + t, + `invalid value "nope" for flag -level: unrecognized level: "nope"`, + strings.Split(buf.String(), "\n")[0], // second line is help message + "Unexpected error output from invalid flag input.", + ) +} + +// enablerWithCustomLevel is a LevelEnabler that implements a custom Level +// method. +type enablerWithCustomLevel struct{ lvl Level } + +var _ leveledEnabler = (*enablerWithCustomLevel)(nil) + +func (l *enablerWithCustomLevel) Enabled(lvl Level) bool { + return l.lvl.Enabled(lvl) +} + +func (l *enablerWithCustomLevel) Level() Level { + return l.lvl +} + +func TestLevelOf(t *testing.T) { + tests := []struct { + desc string + give LevelEnabler + want Level + }{ + {desc: "debug", give: DebugLevel, want: DebugLevel}, + {desc: "info", give: InfoLevel, want: InfoLevel}, + {desc: "warn", give: WarnLevel, want: WarnLevel}, + {desc: "error", give: ErrorLevel, want: ErrorLevel}, + {desc: "dpanic", give: DPanicLevel, want: DPanicLevel}, + {desc: "panic", give: PanicLevel, want: PanicLevel}, + {desc: "fatal", give: FatalLevel, want: FatalLevel}, + { + desc: "leveledEnabler", + give: &enablerWithCustomLevel{lvl: InfoLevel}, + want: InfoLevel, + }, + { + desc: "noop", + give: NewNopCore(), // always disabled + want: InvalidLevel, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tt.want, LevelOf(tt.give), "Reported level did not match.") + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/marshaler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/marshaler.go new file mode 100644 index 0000000..c3c55ba --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/marshaler.go @@ -0,0 +1,61 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +// ObjectMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ObjectMarshaler is only used when zap.Object is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ObjectMarshaler interface { + MarshalLogObject(ObjectEncoder) error +} + +// ObjectMarshalerFunc is a type adapter that turns a function into an +// ObjectMarshaler. +type ObjectMarshalerFunc func(ObjectEncoder) error + +// MarshalLogObject calls the underlying function. +func (f ObjectMarshalerFunc) MarshalLogObject(enc ObjectEncoder) error { + return f(enc) +} + +// ArrayMarshaler allows user-defined types to efficiently add themselves to the +// logging context, and to selectively omit information which shouldn't be +// included in logs (e.g., passwords). +// +// Note: ArrayMarshaler is only used when zap.Array is used or when +// passed directly to zap.Any. It is not used when reflection-based +// encoding is used. +type ArrayMarshaler interface { + MarshalLogArray(ArrayEncoder) error +} + +// ArrayMarshalerFunc is a type adapter that turns a function into an +// ArrayMarshaler. +type ArrayMarshalerFunc func(ArrayEncoder) error + +// MarshalLogArray calls the underlying function. +func (f ArrayMarshalerFunc) MarshalLogArray(enc ArrayEncoder) error { + return f(enc) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder.go new file mode 100644 index 0000000..dfead08 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder.go @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "time" + +// MapObjectEncoder is an ObjectEncoder backed by a simple +// map[string]interface{}. It's not fast enough for production use, but it's +// helpful in tests. +type MapObjectEncoder struct { + // Fields contains the entire encoded log context. + Fields map[string]interface{} + // cur is a pointer to the namespace we're currently writing to. + cur map[string]interface{} +} + +// NewMapObjectEncoder creates a new map-backed ObjectEncoder. +func NewMapObjectEncoder() *MapObjectEncoder { + m := make(map[string]interface{}) + return &MapObjectEncoder{ + Fields: m, + cur: m, + } +} + +// AddArray implements ObjectEncoder. +func (m *MapObjectEncoder) AddArray(key string, v ArrayMarshaler) error { + arr := &sliceArrayEncoder{elems: make([]interface{}, 0)} + err := v.MarshalLogArray(arr) + m.cur[key] = arr.elems + return err +} + +// AddObject implements ObjectEncoder. +func (m *MapObjectEncoder) AddObject(k string, v ObjectMarshaler) error { + newMap := NewMapObjectEncoder() + m.cur[k] = newMap.Fields + return v.MarshalLogObject(newMap) +} + +// AddBinary implements ObjectEncoder. +func (m *MapObjectEncoder) AddBinary(k string, v []byte) { m.cur[k] = v } + +// AddByteString implements ObjectEncoder. +func (m *MapObjectEncoder) AddByteString(k string, v []byte) { m.cur[k] = string(v) } + +// AddBool implements ObjectEncoder. +func (m *MapObjectEncoder) AddBool(k string, v bool) { m.cur[k] = v } + +// AddDuration implements ObjectEncoder. +func (m MapObjectEncoder) AddDuration(k string, v time.Duration) { m.cur[k] = v } + +// AddComplex128 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex128(k string, v complex128) { m.cur[k] = v } + +// AddComplex64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddComplex64(k string, v complex64) { m.cur[k] = v } + +// AddFloat64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat64(k string, v float64) { m.cur[k] = v } + +// AddFloat32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddFloat32(k string, v float32) { m.cur[k] = v } + +// AddInt implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt(k string, v int) { m.cur[k] = v } + +// AddInt64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt64(k string, v int64) { m.cur[k] = v } + +// AddInt32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt32(k string, v int32) { m.cur[k] = v } + +// AddInt16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt16(k string, v int16) { m.cur[k] = v } + +// AddInt8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddInt8(k string, v int8) { m.cur[k] = v } + +// AddString implements ObjectEncoder. +func (m *MapObjectEncoder) AddString(k string, v string) { m.cur[k] = v } + +// AddTime implements ObjectEncoder. +func (m MapObjectEncoder) AddTime(k string, v time.Time) { m.cur[k] = v } + +// AddUint implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint(k string, v uint) { m.cur[k] = v } + +// AddUint64 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint64(k string, v uint64) { m.cur[k] = v } + +// AddUint32 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint32(k string, v uint32) { m.cur[k] = v } + +// AddUint16 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint16(k string, v uint16) { m.cur[k] = v } + +// AddUint8 implements ObjectEncoder. +func (m *MapObjectEncoder) AddUint8(k string, v uint8) { m.cur[k] = v } + +// AddUintptr implements ObjectEncoder. +func (m *MapObjectEncoder) AddUintptr(k string, v uintptr) { m.cur[k] = v } + +// AddReflected implements ObjectEncoder. +func (m *MapObjectEncoder) AddReflected(k string, v interface{}) error { + m.cur[k] = v + return nil +} + +// OpenNamespace implements ObjectEncoder. +func (m *MapObjectEncoder) OpenNamespace(k string) { + ns := make(map[string]interface{}) + m.cur[k] = ns + m.cur = ns +} + +// sliceArrayEncoder is an ArrayEncoder backed by a simple []interface{}. Like +// the MapObjectEncoder, it's not designed for production use. +type sliceArrayEncoder struct { + elems []interface{} +} + +func (s *sliceArrayEncoder) AppendArray(v ArrayMarshaler) error { + enc := &sliceArrayEncoder{} + err := v.MarshalLogArray(enc) + s.elems = append(s.elems, enc.elems) + return err +} + +func (s *sliceArrayEncoder) AppendObject(v ObjectMarshaler) error { + m := NewMapObjectEncoder() + err := v.MarshalLogObject(m) + s.elems = append(s.elems, m.Fields) + return err +} + +func (s *sliceArrayEncoder) AppendReflected(v interface{}) error { + s.elems = append(s.elems, v) + return nil +} + +func (s *sliceArrayEncoder) AppendBool(v bool) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendByteString(v []byte) { s.elems = append(s.elems, string(v)) } +func (s *sliceArrayEncoder) AppendComplex128(v complex128) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendComplex64(v complex64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendDuration(v time.Duration) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat64(v float64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendFloat32(v float32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt(v int) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt64(v int64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt32(v int32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt16(v int16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendInt8(v int8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendString(v string) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendTime(v time.Time) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint(v uint) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint64(v uint64) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint32(v uint32) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint16(v uint16) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUint8(v uint8) { s.elems = append(s.elems, v) } +func (s *sliceArrayEncoder) AppendUintptr(v uintptr) { s.elems = append(s.elems, v) } diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder_test.go new file mode 100644 index 0000000..d5f215f --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/memory_encoder_test.go @@ -0,0 +1,370 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMapObjectEncoderAdd(t *testing.T) { + // Expected output of a turducken. + wantTurducken := map[string]interface{}{ + "ducks": []interface{}{ + map[string]interface{}{"in": "chicken"}, + map[string]interface{}{"in": "chicken"}, + }, + } + + tests := []struct { + desc string + f func(ObjectEncoder) + expected interface{} + }{ + { + desc: "AddObject", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", loggable{true}), "Expected AddObject to succeed.") + }, + expected: map[string]interface{}{"loggable": "yes"}, + }, + { + desc: "AddObject (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddObject("k", turducken{}), "Expected AddObject to succeed.") + }, + expected: wantTurducken, + }, + { + desc: "AddArray", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + arr.AppendBool(true) + arr.AppendBool(false) + arr.AppendBool(true) + return nil + })), "Expected AddArray to succeed.") + }, + expected: []interface{}{true, false, true}, + }, + { + desc: "AddArray (nested)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(2)), "Expected AddArray to succeed.") + }, + expected: []interface{}{wantTurducken, wantTurducken}, + }, + { + desc: "AddArray (empty)", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddArray("k", turduckens(0)), "Expected AddArray to succeed.") + }, + expected: []interface{}{}, + }, + { + desc: "AddBinary", + f: func(e ObjectEncoder) { e.AddBinary("k", []byte("foo")) }, + expected: []byte("foo"), + }, + { + desc: "AddByteString", + f: func(e ObjectEncoder) { e.AddByteString("k", []byte("foo")) }, + expected: "foo", + }, + { + desc: "AddBool", + f: func(e ObjectEncoder) { e.AddBool("k", true) }, + expected: true, + }, + { + desc: "AddComplex128", + f: func(e ObjectEncoder) { e.AddComplex128("k", 1+2i) }, + expected: 1 + 2i, + }, + { + desc: "AddComplex64", + f: func(e ObjectEncoder) { e.AddComplex64("k", 1+2i) }, + expected: complex64(1 + 2i), + }, + { + desc: "AddDuration", + f: func(e ObjectEncoder) { e.AddDuration("k", time.Millisecond) }, + expected: time.Millisecond, + }, + { + desc: "AddFloat64", + f: func(e ObjectEncoder) { e.AddFloat64("k", 3.14) }, + expected: 3.14, + }, + { + desc: "AddFloat32", + f: func(e ObjectEncoder) { e.AddFloat32("k", 3.14) }, + expected: float32(3.14), + }, + { + desc: "AddInt", + f: func(e ObjectEncoder) { e.AddInt("k", 42) }, + expected: 42, + }, + { + desc: "AddInt64", + f: func(e ObjectEncoder) { e.AddInt64("k", 42) }, + expected: int64(42), + }, + { + desc: "AddInt32", + f: func(e ObjectEncoder) { e.AddInt32("k", 42) }, + expected: int32(42), + }, + { + desc: "AddInt16", + f: func(e ObjectEncoder) { e.AddInt16("k", 42) }, + expected: int16(42), + }, + { + desc: "AddInt8", + f: func(e ObjectEncoder) { e.AddInt8("k", 42) }, + expected: int8(42), + }, + { + desc: "AddString", + f: func(e ObjectEncoder) { e.AddString("k", "v") }, + expected: "v", + }, + { + desc: "AddTime", + f: func(e ObjectEncoder) { e.AddTime("k", time.Unix(0, 100)) }, + expected: time.Unix(0, 100), + }, + { + desc: "AddUint", + f: func(e ObjectEncoder) { e.AddUint("k", 42) }, + expected: uint(42), + }, + { + desc: "AddUint64", + f: func(e ObjectEncoder) { e.AddUint64("k", 42) }, + expected: uint64(42), + }, + { + desc: "AddUint32", + f: func(e ObjectEncoder) { e.AddUint32("k", 42) }, + expected: uint32(42), + }, + { + desc: "AddUint16", + f: func(e ObjectEncoder) { e.AddUint16("k", 42) }, + expected: uint16(42), + }, + { + desc: "AddUint8", + f: func(e ObjectEncoder) { e.AddUint8("k", 42) }, + expected: uint8(42), + }, + { + desc: "AddUintptr", + f: func(e ObjectEncoder) { e.AddUintptr("k", 42) }, + expected: uintptr(42), + }, + { + desc: "AddReflected", + f: func(e ObjectEncoder) { + assert.NoError(t, e.AddReflected("k", map[string]interface{}{"foo": 5}), "Expected AddReflected to succeed.") + }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "OpenNamespace", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + e.AddInt("foo", 1) + e.OpenNamespace("middle") + e.AddInt("foo", 2) + e.OpenNamespace("inner") + e.AddInt("foo", 3) + }, + expected: map[string]interface{}{ + "foo": 1, + "middle": map[string]interface{}{ + "foo": 2, + "inner": map[string]interface{}{ + "foo": 3, + }, + }, + }, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + assert.NoError(t, e.AddObject("obj", maybeNamespace{false})) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "not-obj": "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ObjectEncoder) { + e.OpenNamespace("k") + assert.NoError(t, e.AddObject("obj", maybeNamespace{true})) + e.AddString("not-obj", "should-be-outside-obj") + }, + expected: map[string]interface{}{ + "obj": map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "not-obj": "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + tt.f(enc) + assert.Equal(t, tt.expected, enc.Fields["k"], "Unexpected encoder output.") + }) + } +} + +func TestSliceArrayEncoderAppend(t *testing.T) { + tests := []struct { + desc string + f func(ArrayEncoder) + expected interface{} + }{ + // AppendObject and AppendArray are covered by the AddObject (nested) and + // AddArray (nested) cases above. + {"AppendBool", func(e ArrayEncoder) { e.AppendBool(true) }, true}, + {"AppendByteString", func(e ArrayEncoder) { e.AppendByteString([]byte("foo")) }, "foo"}, + {"AppendComplex128", func(e ArrayEncoder) { e.AppendComplex128(1 + 2i) }, 1 + 2i}, + {"AppendComplex64", func(e ArrayEncoder) { e.AppendComplex64(1 + 2i) }, complex64(1 + 2i)}, + {"AppendDuration", func(e ArrayEncoder) { e.AppendDuration(time.Second) }, time.Second}, + {"AppendFloat64", func(e ArrayEncoder) { e.AppendFloat64(3.14) }, 3.14}, + {"AppendFloat32", func(e ArrayEncoder) { e.AppendFloat32(3.14) }, float32(3.14)}, + {"AppendInt", func(e ArrayEncoder) { e.AppendInt(42) }, 42}, + {"AppendInt64", func(e ArrayEncoder) { e.AppendInt64(42) }, int64(42)}, + {"AppendInt32", func(e ArrayEncoder) { e.AppendInt32(42) }, int32(42)}, + {"AppendInt16", func(e ArrayEncoder) { e.AppendInt16(42) }, int16(42)}, + {"AppendInt8", func(e ArrayEncoder) { e.AppendInt8(42) }, int8(42)}, + {"AppendString", func(e ArrayEncoder) { e.AppendString("foo") }, "foo"}, + {"AppendTime", func(e ArrayEncoder) { e.AppendTime(time.Unix(0, 100)) }, time.Unix(0, 100)}, + {"AppendUint", func(e ArrayEncoder) { e.AppendUint(42) }, uint(42)}, + {"AppendUint64", func(e ArrayEncoder) { e.AppendUint64(42) }, uint64(42)}, + {"AppendUint32", func(e ArrayEncoder) { e.AppendUint32(42) }, uint32(42)}, + {"AppendUint16", func(e ArrayEncoder) { e.AppendUint16(42) }, uint16(42)}, + {"AppendUint8", func(e ArrayEncoder) { e.AppendUint8(42) }, uint8(42)}, + {"AppendUintptr", func(e ArrayEncoder) { e.AppendUintptr(42) }, uintptr(42)}, + { + desc: "AppendReflected", + f: func(e ArrayEncoder) { + assert.NoError(t, e.AppendReflected(map[string]interface{}{"foo": 5})) + }, + expected: map[string]interface{}{"foo": 5}, + }, + { + desc: "AppendArray (arrays of arrays)", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + inner.AppendBool(true) + inner.AppendBool(false) + return nil + })) + assert.NoError(t, err) + }, + expected: []interface{}{true, false}, + }, + { + desc: "object (no nested namespace) then string", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{false}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + }, + "should-be-outside-obj", + }, + }, + { + desc: "object (with nested namespace) then string", + f: func(e ArrayEncoder) { + err := e.AppendArray(ArrayMarshalerFunc(func(inner ArrayEncoder) error { + err := inner.AppendObject(maybeNamespace{true}) + inner.AppendString("should-be-outside-obj") + return err + })) + assert.NoError(t, err) + }, + expected: []interface{}{ + map[string]interface{}{ + "obj-out": "obj-outside-namespace", + "obj-namespace": map[string]interface{}{ + "obj-in": "obj-inside-namespace", + }, + }, + "should-be-outside-obj", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + enc := NewMapObjectEncoder() + assert.NoError(t, enc.AddArray("k", ArrayMarshalerFunc(func(arr ArrayEncoder) error { + tt.f(arr) + tt.f(arr) + return nil + })), "Expected AddArray to succeed.") + + arr, ok := enc.Fields["k"].([]interface{}) + require.True(t, ok, "Test case %s didn't encode an array.", tt.desc) + assert.Equal(t, []interface{}{tt.expected, tt.expected}, arr, "Unexpected encoder output.") + }) + } +} + +func TestMapObjectEncoderReflectionFailures(t *testing.T) { + enc := NewMapObjectEncoder() + assert.Error(t, enc.AddObject("object", loggable{false}), "Expected AddObject to fail.") + assert.Equal( + t, + map[string]interface{}{"object": map[string]interface{}{}}, + enc.Fields, + "Expected encoder to use empty values on errors.", + ) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/reflected_encoder.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/reflected_encoder.go new file mode 100644 index 0000000..8746360 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/reflected_encoder.go @@ -0,0 +1,41 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "encoding/json" + "io" +) + +// ReflectedEncoder serializes log fields that can't be serialized with Zap's +// JSON encoder. These have the ReflectType field type. +// Use EncoderConfig.NewReflectedEncoder to set this. +type ReflectedEncoder interface { + // Encode encodes and writes to the underlying data stream. + Encode(interface{}) error +} + +func defaultReflectedEncoder(w io.Writer) ReflectedEncoder { + enc := json.NewEncoder(w) + // For consistency with our custom JSON encoder. + enc.SetEscapeHTML(false) + return enc +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler.go new file mode 100644 index 0000000..b7c093a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler.go @@ -0,0 +1,229 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "sync/atomic" + "time" +) + +const ( + _numLevels = _maxLevel - _minLevel + 1 + _countersPerLevel = 4096 +) + +type counter struct { + resetAt atomic.Int64 + counter atomic.Uint64 +} + +type counters [_numLevels][_countersPerLevel]counter + +func newCounters() *counters { + return &counters{} +} + +func (cs *counters) get(lvl Level, key string) *counter { + i := lvl - _minLevel + j := fnv32a(key) % _countersPerLevel + return &cs[i][j] +} + +// fnv32a, adapted from "hash/fnv", but without a []byte(string) alloc +func fnv32a(s string) uint32 { + const ( + offset32 = 2166136261 + prime32 = 16777619 + ) + hash := uint32(offset32) + for i := 0; i < len(s); i++ { + hash ^= uint32(s[i]) + hash *= prime32 + } + return hash +} + +func (c *counter) IncCheckReset(t time.Time, tick time.Duration) uint64 { + tn := t.UnixNano() + resetAfter := c.resetAt.Load() + if resetAfter > tn { + return c.counter.Add(1) + } + + c.counter.Store(1) + + newResetAfter := tn + tick.Nanoseconds() + if !c.resetAt.CompareAndSwap(resetAfter, newResetAfter) { + // We raced with another goroutine trying to reset, and it also reset + // the counter to 1, so we need to reincrement the counter. + return c.counter.Add(1) + } + + return 1 +} + +// SamplingDecision is a decision represented as a bit field made by sampler. +// More decisions may be added in the future. +type SamplingDecision uint32 + +const ( + // LogDropped indicates that the Sampler dropped a log entry. + LogDropped SamplingDecision = 1 << iota + // LogSampled indicates that the Sampler sampled a log entry. + LogSampled +) + +// optionFunc wraps a func so it satisfies the SamplerOption interface. +type optionFunc func(*sampler) + +func (f optionFunc) apply(s *sampler) { + f(s) +} + +// SamplerOption configures a Sampler. +type SamplerOption interface { + apply(*sampler) +} + +// nopSamplingHook is the default hook used by sampler. +func nopSamplingHook(Entry, SamplingDecision) {} + +// SamplerHook registers a function which will be called when Sampler makes a +// decision. +// +// This hook may be used to get visibility into the performance of the sampler. +// For example, use it to track metrics of dropped versus sampled logs. +// +// var dropped atomic.Int64 +// zapcore.SamplerHook(func(ent zapcore.Entry, dec zapcore.SamplingDecision) { +// if dec&zapcore.LogDropped > 0 { +// dropped.Inc() +// } +// }) +func SamplerHook(hook func(entry Entry, dec SamplingDecision)) SamplerOption { + return optionFunc(func(s *sampler) { + s.hook = hook + }) +} + +// NewSamplerWithOptions creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// For example, +// +// core = NewSamplerWithOptions(core, time.Second, 10, 5) +// +// This will log the first 10 log entries with the same level and message +// in a one second interval as-is. Following that, it will allow through +// every 5th log entry with the same level and message in that interval. +// +// If thereafter is zero, the Core will drop all log entries after the first N +// in that interval. +// +// Sampler can be configured to report sampling decisions with the SamplerHook +// option. +// +// Keep in mind that Zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +func NewSamplerWithOptions(core Core, tick time.Duration, first, thereafter int, opts ...SamplerOption) Core { + s := &sampler{ + Core: core, + tick: tick, + counts: newCounters(), + first: uint64(first), + thereafter: uint64(thereafter), + hook: nopSamplingHook, + } + for _, opt := range opts { + opt.apply(s) + } + + return s +} + +type sampler struct { + Core + + counts *counters + tick time.Duration + first, thereafter uint64 + hook func(Entry, SamplingDecision) +} + +var ( + _ Core = (*sampler)(nil) + _ leveledEnabler = (*sampler)(nil) +) + +// NewSampler creates a Core that samples incoming entries, which +// caps the CPU and I/O load of logging while attempting to preserve a +// representative subset of your logs. +// +// Zap samples by logging the first N entries with a given level and message +// each tick. If more Entries with the same level and message are seen during +// the same interval, every Mth message is logged and the rest are dropped. +// +// Keep in mind that zap's sampling implementation is optimized for speed over +// absolute precision; under load, each tick may be slightly over- or +// under-sampled. +// +// Deprecated: use NewSamplerWithOptions. +func NewSampler(core Core, tick time.Duration, first, thereafter int) Core { + return NewSamplerWithOptions(core, tick, first, thereafter) +} + +func (s *sampler) Level() Level { + return LevelOf(s.Core) +} + +func (s *sampler) With(fields []Field) Core { + return &sampler{ + Core: s.Core.With(fields), + tick: s.tick, + counts: s.counts, + first: s.first, + thereafter: s.thereafter, + hook: s.hook, + } +} + +func (s *sampler) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + if !s.Enabled(ent.Level) { + return ce + } + + if ent.Level >= _minLevel && ent.Level <= _maxLevel { + counter := s.counts.get(ent.Level, ent.Message) + n := counter.IncCheckReset(ent.Time, s.tick) + if n > s.first && (s.thereafter == 0 || (n-s.first)%s.thereafter != 0) { + s.hook(ent, LogDropped) + return ce + } + s.hook(ent, LogSampled) + } + return s.Core.Check(ent, ce) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_bench_test.go new file mode 100644 index 0000000..1b250cd --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_bench_test.go @@ -0,0 +1,287 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/ztest" + + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +var counterTestCases = [][]string{ + // some stuff I made up + { + "foo", + "bar", + "baz", + "alpha", + "bravo", + "charlie", + "delta", + }, + + // shuf -n50 /usr/share/dict/words + { + "unbracing", + "stereotomy", + "supranervian", + "moaning", + "exchangeability", + "gunyang", + "sulcation", + "dariole", + "archheresy", + "synchronistically", + "clips", + "unsanctioned", + "Argoan", + "liparomphalus", + "layship", + "Fregatae", + "microzoology", + "glaciaria", + "Frugivora", + "patterist", + "Grossulariaceae", + "lithotint", + "bargander", + "opisthographical", + "cacography", + "chalkstone", + "nonsubstantialism", + "sardonicism", + "calamiform", + "lodginghouse", + "predisposedly", + "topotypic", + "broideress", + "outrange", + "gingivolabial", + "monoazo", + "sparlike", + "concameration", + "untoothed", + "Camorrism", + "reissuer", + "soap", + "palaiotype", + "countercharm", + "yellowbird", + "palterly", + "writinger", + "boatfalls", + "tuglike", + "underbitten", + }, + + // shuf -n100 /usr/share/dict/words + { + "rooty", + "malcultivation", + "degrade", + "pseudoindependent", + "stillatory", + "antiseptize", + "protoamphibian", + "antiar", + "Esther", + "pseudelminth", + "superfluitance", + "teallite", + "disunity", + "spirignathous", + "vergency", + "myliobatid", + "inosic", + "overabstemious", + "patriarchally", + "foreimagine", + "coetaneity", + "hemimellitene", + "hyperspatial", + "aulophyte", + "electropoion", + "antitrope", + "Amarantus", + "smaltine", + "lighthead", + "syntonically", + "incubous", + "versation", + "cirsophthalmia", + "Ulidian", + "homoeography", + "Velella", + "Hecatean", + "serfage", + "Spermaphyta", + "palatoplasty", + "electroextraction", + "aconite", + "avirulence", + "initiator", + "besmear", + "unrecognizably", + "euphoniousness", + "balbuties", + "pascuage", + "quebracho", + "Yakala", + "auriform", + "sevenbark", + "superorganism", + "telesterion", + "ensand", + "nagaika", + "anisuria", + "etching", + "soundingly", + "grumpish", + "drillmaster", + "perfumed", + "dealkylate", + "anthracitiferous", + "predefiance", + "sulphoxylate", + "freeness", + "untucking", + "misworshiper", + "Nestorianize", + "nonegoistical", + "construe", + "upstroke", + "teated", + "nasolachrymal", + "Mastodontidae", + "gallows", + "radioluminescent", + "uncourtierlike", + "phasmatrope", + "Clunisian", + "drainage", + "sootless", + "brachyfacial", + "antiheroism", + "irreligionize", + "ked", + "unfact", + "nonprofessed", + "milady", + "conjecture", + "Arctomys", + "guapilla", + "Sassenach", + "emmetrope", + "rosewort", + "raphidiferous", + "pooh", + "Tyndallize", + }, +} + +func BenchmarkSampler_Check(b *testing.B) { + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, 1, 1000) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + }) + } +} + +func makeSamplerCountingHook() (func(_ Entry, dec SamplingDecision), *atomic.Int64, *atomic.Int64) { + droppedCount := new(atomic.Int64) + sampledCount := new(atomic.Int64) + h := func(_ Entry, dec SamplingDecision) { + if dec&LogDropped > 0 { + droppedCount.Add(1) + } else if dec&LogSampled > 0 { + sampledCount.Add(1) + } + } + return h, droppedCount, sampledCount +} + +func BenchmarkSampler_CheckWithHook(b *testing.B) { + hook, dropped, sampled := makeSamplerCountingHook() + for _, keys := range counterTestCases { + b.Run(fmt.Sprintf("%v keys", len(keys)), func(b *testing.B) { + fac := NewSamplerWithOptions( + NewCore( + NewJSONEncoder(testEncoderConfig()), + &ztest.Discarder{}, + DebugLevel, + ), + time.Millisecond, + 1, + 1000, + SamplerHook(hook), + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + ent := Entry{ + Level: DebugLevel + Level(i%4), + Message: keys[i], + } + _ = fac.Check(ent, nil) + i++ + if n := len(keys); i >= n { + i -= n + } + } + }) + // We expect to see 1000 dropped messages for every sampled per settings, + // with a delta due to less 1000 messages getting dropped after initial one + // is sampled. + assert.Greater(b, dropped.Load()/1000, sampled.Load()-1000) + dropped.Store(0) + sampled.Store(0) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_test.go new file mode 100644 index 0000000..55b4afa --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/sampler_test.go @@ -0,0 +1,329 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "go.uber.org/zap/internal/ztest" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func fakeSampler(lvl LevelEnabler, tick time.Duration, first, thereafter int) (Core, *observer.ObservedLogs) { + core, logs := observer.New(lvl) + // Keep using deprecated constructor for cc. + core = NewSampler(core, tick, first, thereafter) + return core, logs +} + +func assertSequence(t testing.TB, logs []observer.LoggedEntry, lvl Level, seq ...int64) { + seen := make([]int64, len(logs)) + for i, entry := range logs { + require.Equal(t, "", entry.Message, "Message wasn't created by writeSequence.") + require.Equal(t, 1, len(entry.Context), "Unexpected number of fields.") + require.Equal(t, lvl, entry.Level, "Unexpected level.") + f := entry.Context[0] + require.Equal(t, "iter", f.Key, "Unexpected field key.") + require.Equal(t, Int64Type, f.Type, "Unexpected field type") + seen[i] = f.Integer + } + assert.Equal(t, seq, seen, "Unexpected sequence logged at level %v.", lvl) +} + +func writeSequence(core Core, n int, lvl Level) { + // All tests using writeSequence verify that counters are shared between + // parent and child cores. + core = core.With([]Field{makeInt64Field("iter", n)}) + if ce := core.Check(Entry{Level: lvl, Time: time.Now()}, nil); ce != nil { + ce.Write() + } +} + +func TestSampler(t *testing.T) { + for _, lvl := range []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} { + sampler, logs := fakeSampler(DebugLevel, time.Minute, 2, 3) + + // Ensure that counts aren't shared between levels. + probeLevel := DebugLevel + if lvl == DebugLevel { + probeLevel = InfoLevel + } + for i := 0; i < 10; i++ { + writeSequence(sampler, 1, probeLevel) + } + // Clear any output. + logs.TakeAll() + + for i := 1; i < 10; i++ { + writeSequence(sampler, i, lvl) + } + assertSequence(t, logs.TakeAll(), lvl, 1, 2, 5, 8) + } +} + +func TestLevelOfSampler(t *testing.T) { + levels := []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel} + for _, lvl := range levels { + lvl := lvl + t.Run(lvl.String(), func(t *testing.T) { + t.Parallel() + + sampler, _ := fakeSampler(lvl, time.Minute, 2, 3) + assert.Equal(t, lvl, LevelOf(sampler), "Sampler level did not match.") + }) + } +} + +func TestSamplerDisabledLevels(t *testing.T) { + sampler, logs := fakeSampler(InfoLevel, time.Minute, 1, 100) + + // Shouldn't be counted, because debug logging isn't enabled. + writeSequence(sampler, 1, DebugLevel) + writeSequence(sampler, 2, InfoLevel) + assertSequence(t, logs.TakeAll(), InfoLevel, 2) +} + +func TestSamplerTicking(t *testing.T) { + // Ensure that we're resetting the sampler's counter every tick. + sampler, logs := fakeSampler(DebugLevel, 10*time.Millisecond, 5, 10) + + // If we log five or fewer messages every tick, none of them should be + // dropped. + for tick := 0; tick < 2; tick++ { + for i := 1; i <= 5; i++ { + writeSequence(sampler, i, InfoLevel) + } + ztest.Sleep(15 * time.Millisecond) + } + assertSequence( + t, + logs.TakeAll(), + InfoLevel, + 1, 2, 3, 4, 5, // first tick + 1, 2, 3, 4, 5, // second tick + ) + + // If we log quickly, we should drop some logs. The first five statements + // each tick should be logged, then every tenth. + for tick := 0; tick < 3; tick++ { + for i := 1; i < 18; i++ { + writeSequence(sampler, i, InfoLevel) + } + ztest.Sleep(10 * time.Millisecond) + } + + assertSequence( + t, + logs.TakeAll(), + InfoLevel, + 1, 2, 3, 4, 5, 15, // first tick + 1, 2, 3, 4, 5, 15, // second tick + 1, 2, 3, 4, 5, 15, // third tick + ) +} + +type countingCore struct { + logs atomic.Uint32 +} + +func (c *countingCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + return ce.AddCore(ent, c) +} + +func (c *countingCore) Write(Entry, []Field) error { + c.logs.Add(1) + return nil +} + +func (c *countingCore) With([]Field) Core { return c } +func (*countingCore) Enabled(Level) bool { return true } +func (*countingCore) Sync() error { return nil } + +func TestSamplerConcurrent(t *testing.T) { + const ( + logsPerTick = 10 + numMessages = 5 + numTicks = 25 + numGoroutines = 10 + tick = 10 * time.Millisecond + + // We'll make a total of, + // (numGoroutines * numTicks * logsPerTick * 2) log attempts + // with numMessages unique messages. + numLogAttempts = numGoroutines * logsPerTick * numTicks * 2 + // Of those, we'll accept (logsPerTick * numTicks) entries + // for each unique message. + expectedCount = numMessages * logsPerTick * numTicks + // The rest will be dropped. + expectedDropped = numLogAttempts - expectedCount + ) + + clock := ztest.NewMockClock() + + cc := &countingCore{} + + hook, dropped, sampled := makeSamplerCountingHook() + sampler := NewSamplerWithOptions(cc, tick, logsPerTick, 100000, SamplerHook(hook)) + + stop := make(chan struct{}) + var wg sync.WaitGroup + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func(i int, ticker *time.Ticker) { + defer wg.Done() + defer ticker.Stop() + + for { + select { + case <-stop: + return + + case <-ticker.C: + for j := 0; j < logsPerTick*2; j++ { + msg := fmt.Sprintf("msg%v", i%numMessages) + ent := Entry{ + Level: DebugLevel, + Message: msg, + Time: clock.Now(), + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + + // Give a chance for other goroutines to run. + runtime.Gosched() + } + } + } + }(i, clock.NewTicker(tick)) + } + + clock.Add(tick * numTicks) + close(stop) + wg.Wait() + + assert.Equal( + t, + expectedCount, + int(cc.logs.Load()), + "Unexpected number of logs", + ) + assert.Equal(t, + expectedCount, + int(sampled.Load()), + "Unexpected number of logs sampled", + ) + assert.Equal(t, + expectedDropped, + int(dropped.Load()), + "Unexpected number of logs dropped", + ) +} + +func TestSamplerRaces(t *testing.T) { + sampler, _ := fakeSampler(DebugLevel, time.Minute, 1, 1000) + + var wg sync.WaitGroup + start := make(chan struct{}) + + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + <-start + for j := 0; j < 100; j++ { + writeSequence(sampler, j, InfoLevel) + } + wg.Done() + }() + } + + close(start) + wg.Wait() +} + +func TestSamplerUnknownLevels(t *testing.T) { + // Prove that out-of-bounds levels don't panic. + unknownLevels := []Level{ + DebugLevel - 1, + FatalLevel + 1, + } + + for _, lvl := range unknownLevels { + t.Run(lvl.String(), func(t *testing.T) { + sampler, logs := fakeSampler(lvl, time.Minute, 2, 3) + for i := 1; i < 10; i++ { + writeSequence(sampler, i, lvl) + } + + // Expect no sampling for unknown levels. + assertSequence(t, logs.TakeAll(), lvl, 1, 2, 3, 4, 5, 6, 7, 8, 9) + }) + } +} + +func TestSamplerWithZeroThereafter(t *testing.T) { + var counter countingCore + + // Logs two messages per second. + sampler := NewSamplerWithOptions(&counter, time.Second, 2, 0) + + now := time.Now() + + for i := 0; i < 1000; i++ { + ent := Entry{ + Level: InfoLevel, + Message: "msg", + Time: now, + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, 2, int(counter.logs.Load()), + "Unexpected number of logs") + + now = now.Add(time.Second) + + for i := 0; i < 1000; i++ { + ent := Entry{ + Level: InfoLevel, + Message: "msg", + Time: now, + } + if ce := sampler.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, 4, int(counter.logs.Load()), + "Unexpected number of logs") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee.go new file mode 100644 index 0000000..9bb32f0 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee.go @@ -0,0 +1,96 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import "go.uber.org/multierr" + +type multiCore []Core + +var ( + _ leveledEnabler = multiCore(nil) + _ Core = multiCore(nil) +) + +// NewTee creates a Core that duplicates log entries into two or more +// underlying Cores. +// +// Calling it with a single Core returns the input unchanged, and calling +// it with no input returns a no-op Core. +func NewTee(cores ...Core) Core { + switch len(cores) { + case 0: + return NewNopCore() + case 1: + return cores[0] + default: + return multiCore(cores) + } +} + +func (mc multiCore) With(fields []Field) Core { + clone := make(multiCore, len(mc)) + for i := range mc { + clone[i] = mc[i].With(fields) + } + return clone +} + +func (mc multiCore) Level() Level { + minLvl := _maxLevel // mc is never empty + for i := range mc { + if lvl := LevelOf(mc[i]); lvl < minLvl { + minLvl = lvl + } + } + return minLvl +} + +func (mc multiCore) Enabled(lvl Level) bool { + for i := range mc { + if mc[i].Enabled(lvl) { + return true + } + } + return false +} + +func (mc multiCore) Check(ent Entry, ce *CheckedEntry) *CheckedEntry { + for i := range mc { + ce = mc[i].Check(ent, ce) + } + return ce +} + +func (mc multiCore) Write(ent Entry, fields []Field) error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Write(ent, fields)) + } + return err +} + +func (mc multiCore) Sync() error { + var err error + for i := range mc { + err = multierr.Append(err, mc[i].Sync()) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_logger_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_logger_bench_test.go new file mode 100644 index 0000000..d2fc42b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_logger_bench_test.go @@ -0,0 +1,63 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "testing" + + "go.uber.org/zap/internal/ztest" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" +) + +func withBenchedTee(b *testing.B, f func(Core)) { + fac := NewTee( + NewCore(NewJSONEncoder(testEncoderConfig()), &ztest.Discarder{}, DebugLevel), + NewCore(NewJSONEncoder(testEncoderConfig()), &ztest.Discarder{}, InfoLevel), + ) + b.ResetTimer() + f(fac) +} + +func BenchmarkTeeCheck(b *testing.B) { + cases := []struct { + lvl Level + msg string + }{ + {DebugLevel, "foo"}, + {InfoLevel, "bar"}, + {WarnLevel, "baz"}, + {ErrorLevel, "babble"}, + } + withBenchedTee(b, func(core Core) { + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + tt := cases[i] + entry := Entry{Level: tt.lvl, Message: tt.msg} + if cm := core.Check(entry, nil); cm != nil { + cm.Write(Field{Key: "i", Integer: int64(i), Type: Int64Type}) + } + i = (i + 1) % len(cases) + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_test.go new file mode 100644 index 0000000..f6b14eb --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/tee_test.go @@ -0,0 +1,192 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore_test + +import ( + "errors" + "testing" + + "go.uber.org/zap/internal/ztest" + //revive:disable:dot-imports + . "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/assert" +) + +func withTee(f func(core Core, debugLogs, warnLogs *observer.ObservedLogs)) { + debugLogger, debugLogs := observer.New(DebugLevel) + warnLogger, warnLogs := observer.New(WarnLevel) + tee := NewTee(debugLogger, warnLogger) + f(tee, debugLogs, warnLogs) +} + +func TestTeeUnusualInput(t *testing.T) { + // Verify that Tee handles receiving one and no inputs correctly. + t.Run("one input", func(t *testing.T) { + obs, _ := observer.New(DebugLevel) + assert.Equal(t, obs, NewTee(obs), "Expected to return single inputs unchanged.") + }) + t.Run("no input", func(t *testing.T) { + assert.Equal(t, NewNopCore(), NewTee(), "Expected to return NopCore.") + }) +} + +func TestLevelOfTee(t *testing.T) { + debugLogger, _ := observer.New(DebugLevel) + warnLogger, _ := observer.New(WarnLevel) + + tests := []struct { + desc string + give []Core + want Level + }{ + {desc: "empty", want: InvalidLevel}, + { + desc: "debug", + give: []Core{debugLogger}, + want: DebugLevel, + }, + { + desc: "warn", + give: []Core{warnLogger}, + want: WarnLevel, + }, + { + desc: "debug and warn", + give: []Core{warnLogger, debugLogger}, + want: DebugLevel, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + core := NewTee(tt.give...) + assert.Equal(t, tt.want, LevelOf(core), "Level of Tee core did not match.") + }) + } +} + +func TestTeeCheck(t *testing.T) { + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + debugEntry := Entry{Level: DebugLevel, Message: "log-at-debug"} + infoEntry := Entry{Level: InfoLevel, Message: "log-at-info"} + warnEntry := Entry{Level: WarnLevel, Message: "log-at-warn"} + errorEntry := Entry{Level: ErrorLevel, Message: "log-at-error"} + for _, ent := range []Entry{debugEntry, infoEntry, warnEntry, errorEntry} { + if ce := tee.Check(ent, nil); ce != nil { + ce.Write() + } + } + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: debugEntry, Context: []Field{}}, + {Entry: infoEntry, Context: []Field{}}, + {Entry: warnEntry, Context: []Field{}}, + {Entry: errorEntry, Context: []Field{}}, + }, debugLogs.All()) + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: warnEntry, Context: []Field{}}, + {Entry: errorEntry, Context: []Field{}}, + }, warnLogs.All()) + }) +} + +func TestTeeWrite(t *testing.T) { + // Calling the tee's Write method directly should always log, regardless of + // the configured level. + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + debugEntry := Entry{Level: DebugLevel, Message: "log-at-debug"} + warnEntry := Entry{Level: WarnLevel, Message: "log-at-warn"} + for _, ent := range []Entry{debugEntry, warnEntry} { + assert.NoError(t, tee.Write(ent, nil)) + } + + for _, logs := range []*observer.ObservedLogs{debugLogs, warnLogs} { + assert.Equal(t, []observer.LoggedEntry{ + {Entry: debugEntry, Context: []Field{}}, + {Entry: warnEntry, Context: []Field{}}, + }, logs.All()) + } + }) +} + +func TestTeeWith(t *testing.T) { + withTee(func(tee Core, debugLogs, warnLogs *observer.ObservedLogs) { + f := makeInt64Field("k", 42) + tee = tee.With([]Field{f}) + ent := Entry{Level: WarnLevel, Message: "log-at-warn"} + if ce := tee.Check(ent, nil); ce != nil { + ce.Write() + } + + for _, logs := range []*observer.ObservedLogs{debugLogs, warnLogs} { + assert.Equal(t, []observer.LoggedEntry{ + {Entry: ent, Context: []Field{f}}, + }, logs.All()) + } + }) +} + +func TestTeeEnabled(t *testing.T) { + infoLogger, _ := observer.New(InfoLevel) + warnLogger, _ := observer.New(WarnLevel) + tee := NewTee(infoLogger, warnLogger) + tests := []struct { + lvl Level + enabled bool + }{ + {DebugLevel, false}, + {InfoLevel, true}, + {WarnLevel, true}, + {ErrorLevel, true}, + {DPanicLevel, true}, + {PanicLevel, true}, + {FatalLevel, true}, + } + + for _, tt := range tests { + assert.Equal(t, tt.enabled, tee.Enabled(tt.lvl), "Unexpected Enabled result for level %s.", tt.lvl) + } +} + +func TestTeeSync(t *testing.T) { + infoLogger, _ := observer.New(InfoLevel) + warnLogger, _ := observer.New(WarnLevel) + tee := NewTee(infoLogger, warnLogger) + assert.NoError(t, tee.Sync(), "Unexpected error from Syncing a tee.") + + sink := &ztest.Discarder{} + err := errors.New("failed") + sink.SetError(err) + + noSync := NewCore( + NewJSONEncoder(testEncoderConfig()), + sink, + DebugLevel, + ) + tee = NewTee(tee, noSync) + assert.Equal(t, err, tee.Sync(), "Expected an error when part of tee can't Sync.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer.go new file mode 100644 index 0000000..d4a1af3 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer.go @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "io" + "sync" + + "go.uber.org/multierr" +) + +// A WriteSyncer is an io.Writer that can also flush any buffered data. Note +// that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer. +type WriteSyncer interface { + io.Writer + Sync() error +} + +// AddSync converts an io.Writer to a WriteSyncer. It attempts to be +// intelligent: if the concrete type of the io.Writer implements WriteSyncer, +// we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync. +func AddSync(w io.Writer) WriteSyncer { + switch w := w.(type) { + case WriteSyncer: + return w + default: + return writerWrapper{w} + } +} + +type lockedWriteSyncer struct { + sync.Mutex + ws WriteSyncer +} + +// Lock wraps a WriteSyncer in a mutex to make it safe for concurrent use. In +// particular, *os.Files must be locked before use. +func Lock(ws WriteSyncer) WriteSyncer { + if _, ok := ws.(*lockedWriteSyncer); ok { + // no need to layer on another lock + return ws + } + return &lockedWriteSyncer{ws: ws} +} + +func (s *lockedWriteSyncer) Write(bs []byte) (int, error) { + s.Lock() + n, err := s.ws.Write(bs) + s.Unlock() + return n, err +} + +func (s *lockedWriteSyncer) Sync() error { + s.Lock() + err := s.ws.Sync() + s.Unlock() + return err +} + +type writerWrapper struct { + io.Writer +} + +func (w writerWrapper) Sync() error { + return nil +} + +type multiWriteSyncer []WriteSyncer + +// NewMultiWriteSyncer creates a WriteSyncer that duplicates its writes +// and sync calls, much like io.MultiWriter. +func NewMultiWriteSyncer(ws ...WriteSyncer) WriteSyncer { + if len(ws) == 1 { + return ws[0] + } + return multiWriteSyncer(ws) +} + +// See https://golang.org/src/io/multi.go +// When not all underlying syncers write the same number of bytes, +// the smallest number is returned even though Write() is called on +// all of them. +func (ws multiWriteSyncer) Write(p []byte) (int, error) { + var writeErr error + nWritten := 0 + for _, w := range ws { + n, err := w.Write(p) + writeErr = multierr.Append(writeErr, err) + if nWritten == 0 && n != 0 { + nWritten = n + } else if n < nWritten { + nWritten = n + } + } + return nWritten, writeErr +} + +func (ws multiWriteSyncer) Sync() error { + var err error + for _, w := range ws { + err = multierr.Append(err, w.Sync()) + } + return err +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_bench_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_bench_test.go new file mode 100644 index 0000000..90ae475 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_bench_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +func BenchmarkMultiWriteSyncer(b *testing.B) { + b.Run("2 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) + b.Run("4 discarder", func(b *testing.B) { + w := NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) + b.Run("4 discarder with buffer", func(b *testing.B) { + w := &BufferedWriteSyncer{ + WS: NewMultiWriteSyncer( + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + &ztest.Discarder{}, + ), + } + defer func() { + assert.NoError(b, w.Stop(), "Unexpected error stopping buffered write syncer.") + }() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} + +func BenchmarkWriteSyncer(b *testing.B) { + b.Run("write file with no buffer", func(b *testing.B) { + file, err := os.CreateTemp(b.TempDir(), "test.log") + require.NoError(b, err) + + w := AddSync(file) + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := w.Write([]byte("foobarbazbabble")); err != nil { + b.Fatal(err) + } + } + }) + }) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_test.go new file mode 100644 index 0000000..c0c2698 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapcore/write_syncer_test.go @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapcore + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap/internal/ztest" +) + +type writeSyncSpy struct { + io.Writer + ztest.Syncer +} + +func requireWriteWorks(t testing.TB, ws WriteSyncer) { + n, err := ws.Write([]byte("foo")) + require.NoError(t, err, "Unexpected error writing to WriteSyncer.") + require.Equal(t, 3, n, "Wrote an unexpected number of bytes.") +} + +func TestAddSyncWriteSyncer(t *testing.T) { + buf := &bytes.Buffer{} + concrete := &writeSyncSpy{Writer: buf} + ws := AddSync(concrete) + requireWriteWorks(t, ws) + + require.NoError(t, ws.Sync(), "Unexpected error syncing a WriteSyncer.") + require.True(t, concrete.Called(), "Expected to dispatch to concrete type's Sync method.") + + concrete.SetError(errors.New("fail")) + assert.Error(t, ws.Sync(), "Expected to propagate errors from concrete type's Sync method.") +} + +func TestAddSyncWriter(t *testing.T) { + // If we pass a plain io.Writer, make sure that we still get a WriteSyncer + // with a no-op Sync. + buf := &bytes.Buffer{} + ws := AddSync(buf) + requireWriteWorks(t, ws) + assert.NoError(t, ws.Sync(), "Unexpected error calling a no-op Sync method.") +} + +func TestNewMultiWriteSyncerWorksForSingleWriter(t *testing.T) { + w := &ztest.Buffer{} + + ws := NewMultiWriteSyncer(w) + assert.Equal(t, w, ws, "Expected NewMultiWriteSyncer to return the same WriteSyncer object for a single argument.") + + assert.NoError(t, ws.Sync(), "Expected Sync to succeed.") + assert.True(t, w.Called(), "Expected Sync to be called on the created WriteSyncer") +} + +func TestMultiWriteSyncerWritesBoth(t *testing.T) { + first := &bytes.Buffer{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(first), AddSync(second)) + + msg := []byte("dumbledore") + n, err := ws.Write(msg) + require.NoError(t, err, "Expected successful buffer write") + assert.Equal(t, len(msg), n) + + assert.Equal(t, msg, first.Bytes()) + assert.Equal(t, msg, second.Bytes()) +} + +func TestMultiWriteSyncerFailsWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.FailWriter{})) + _, err := ws.Write([]byte("test")) + assert.Error(t, err, "Write error should propagate") +} + +func TestMultiWriteSyncerFailsShortWrite(t *testing.T) { + ws := NewMultiWriteSyncer(AddSync(&ztest.ShortWriter{})) + n, err := ws.Write([]byte("test")) + assert.NoError(t, err, "Expected fake-success from short write") + assert.Equal(t, 3, n, "Expected byte count to return from underlying writer") +} + +func TestWritestoAllSyncs_EvenIfFirstErrors(t *testing.T) { + failer := &ztest.FailWriter{} + second := &bytes.Buffer{} + ws := NewMultiWriteSyncer(AddSync(failer), AddSync(second)) + + _, err := ws.Write([]byte("fail")) + assert.Error(t, err, "Expected error from call to a writer that failed") + assert.Equal(t, []byte("fail"), second.Bytes(), "Expected second sink to be written after first error") +} + +func TestMultiWriteSyncerSync_PropagatesErrors(t *testing.T) { + badsink := &ztest.Buffer{} + badsink.SetError(errors.New("sink is full")) + ws := NewMultiWriteSyncer(&ztest.Discarder{}, badsink) + + assert.Error(t, ws.Sync(), "Expected sync error to propagate") +} + +func TestMultiWriteSyncerSync_NoErrorsOnDiscard(t *testing.T) { + ws := NewMultiWriteSyncer(&ztest.Discarder{}) + assert.NoError(t, ws.Sync(), "Expected error-free sync to /dev/null") +} + +func TestMultiWriteSyncerSync_AllCalled(t *testing.T) { + failed, second := &ztest.Buffer{}, &ztest.Buffer{} + + failed.SetError(errors.New("disposal broken")) + ws := NewMultiWriteSyncer(failed, second) + + assert.Error(t, ws.Sync(), "Expected first sink to fail") + assert.True(t, failed.Called(), "Expected first sink to have Sync method called.") + assert.True(t, second.Called(), "Expected call to Sync even with first failure.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc.go new file mode 100644 index 0000000..682de25 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc.go @@ -0,0 +1,243 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapgrpc provides a logger that is compatible with grpclog. +package zapgrpc // import "go.uber.org/zap/zapgrpc" + +import ( + "fmt" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// See https://github.com/grpc/grpc-go/blob/v1.35.0/grpclog/loggerv2.go#L77-L86 +const ( + grpcLvlInfo int = iota + grpcLvlWarn + grpcLvlError + grpcLvlFatal +) + +// _grpcToZapLevel maps gRPC log levels to zap log levels. +// See https://pkg.go.dev/go.uber.org/zap@v1.16.0/zapcore#Level +var _grpcToZapLevel = map[int]zapcore.Level{ + grpcLvlInfo: zapcore.InfoLevel, + grpcLvlWarn: zapcore.WarnLevel, + grpcLvlError: zapcore.ErrorLevel, + grpcLvlFatal: zapcore.FatalLevel, +} + +// An Option overrides a Logger's default configuration. +type Option interface { + apply(*Logger) +} + +type optionFunc func(*Logger) + +func (f optionFunc) apply(log *Logger) { + f(log) +} + +// WithDebug configures a Logger to print at zap's DebugLevel instead of +// InfoLevel. +// It only affects the Printf, Println and Print methods, which are only used in the gRPC v1 grpclog.Logger API. +// +// Deprecated: use grpclog.SetLoggerV2() for v2 API. +func WithDebug() Option { + return optionFunc(func(logger *Logger) { + logger.print = &printer{ + enab: logger.levelEnabler, + level: zapcore.DebugLevel, + print: logger.delegate.Debug, + printf: logger.delegate.Debugf, + } + }) +} + +// withWarn redirects the fatal level to the warn level, which makes testing +// easier. This is intentionally unexported. +func withWarn() Option { + return optionFunc(func(logger *Logger) { + logger.fatal = &printer{ + enab: logger.levelEnabler, + level: zapcore.WarnLevel, + print: logger.delegate.Warn, + printf: logger.delegate.Warnf, + } + }) +} + +// NewLogger returns a new Logger. +func NewLogger(l *zap.Logger, options ...Option) *Logger { + logger := &Logger{ + delegate: l.Sugar(), + levelEnabler: l.Core(), + } + logger.print = &printer{ + enab: logger.levelEnabler, + level: zapcore.InfoLevel, + print: logger.delegate.Info, + printf: logger.delegate.Infof, + } + logger.fatal = &printer{ + enab: logger.levelEnabler, + level: zapcore.FatalLevel, + print: logger.delegate.Fatal, + printf: logger.delegate.Fatalf, + } + for _, option := range options { + option.apply(logger) + } + return logger +} + +// printer implements Print, Printf, and Println operations for a Zap level. +// +// We use it to customize Debug vs Info, and Warn vs Fatal for Print and Fatal +// respectively. +type printer struct { + enab zapcore.LevelEnabler + level zapcore.Level + print func(...interface{}) + printf func(string, ...interface{}) +} + +func (v *printer) Print(args ...interface{}) { + v.print(args...) +} + +func (v *printer) Printf(format string, args ...interface{}) { + v.printf(format, args...) +} + +func (v *printer) Println(args ...interface{}) { + if v.enab.Enabled(v.level) { + v.print(sprintln(args)) + } +} + +// Logger adapts zap's Logger to be compatible with grpclog.LoggerV2 and the deprecated grpclog.Logger. +type Logger struct { + delegate *zap.SugaredLogger + levelEnabler zapcore.LevelEnabler + print *printer + fatal *printer + // printToDebug bool + // fatalToWarn bool +} + +// Print implements grpclog.Logger. +// +// Deprecated: use [Logger.Info]. +func (l *Logger) Print(args ...interface{}) { + l.print.Print(args...) +} + +// Printf implements grpclog.Logger. +// +// Deprecated: use [Logger.Infof]. +func (l *Logger) Printf(format string, args ...interface{}) { + l.print.Printf(format, args...) +} + +// Println implements grpclog.Logger. +// +// Deprecated: use [Logger.Info]. +func (l *Logger) Println(args ...interface{}) { + l.print.Println(args...) +} + +// Info implements grpclog.LoggerV2. +func (l *Logger) Info(args ...interface{}) { + l.delegate.Info(args...) +} + +// Infoln implements grpclog.LoggerV2. +func (l *Logger) Infoln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.InfoLevel) { + l.delegate.Info(sprintln(args)) + } +} + +// Infof implements grpclog.LoggerV2. +func (l *Logger) Infof(format string, args ...interface{}) { + l.delegate.Infof(format, args...) +} + +// Warning implements grpclog.LoggerV2. +func (l *Logger) Warning(args ...interface{}) { + l.delegate.Warn(args...) +} + +// Warningln implements grpclog.LoggerV2. +func (l *Logger) Warningln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.WarnLevel) { + l.delegate.Warn(sprintln(args)) + } +} + +// Warningf implements grpclog.LoggerV2. +func (l *Logger) Warningf(format string, args ...interface{}) { + l.delegate.Warnf(format, args...) +} + +// Error implements grpclog.LoggerV2. +func (l *Logger) Error(args ...interface{}) { + l.delegate.Error(args...) +} + +// Errorln implements grpclog.LoggerV2. +func (l *Logger) Errorln(args ...interface{}) { + if l.levelEnabler.Enabled(zapcore.ErrorLevel) { + l.delegate.Error(sprintln(args)) + } +} + +// Errorf implements grpclog.LoggerV2. +func (l *Logger) Errorf(format string, args ...interface{}) { + l.delegate.Errorf(format, args...) +} + +// Fatal implements grpclog.LoggerV2. +func (l *Logger) Fatal(args ...interface{}) { + l.fatal.Print(args...) +} + +// Fatalln implements grpclog.LoggerV2. +func (l *Logger) Fatalln(args ...interface{}) { + l.fatal.Println(args...) +} + +// Fatalf implements grpclog.LoggerV2. +func (l *Logger) Fatalf(format string, args ...interface{}) { + l.fatal.Printf(format, args...) +} + +// V implements grpclog.LoggerV2. +func (l *Logger) V(level int) bool { + return l.levelEnabler.Enabled(_grpcToZapLevel[level]) +} + +func sprintln(args []interface{}) string { + s := fmt.Sprintln(args...) + // Drop the new line character added by Sprintln + return s[:len(s)-1] +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc_test.go new file mode 100644 index 0000000..a231d65 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapgrpc/zapgrpc_test.go @@ -0,0 +1,263 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapgrpc + +import ( + "fmt" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" + + "github.com/stretchr/testify/require" +) + +func TestLoggerInfoExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.InfoLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Info("hello") + logger.Info("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Infof("%s world", "hello") + logger.Infoln() + logger.Infoln("foo") + logger.Infoln("foo", "bar") + logger.Infoln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Print("hello") + logger.Print("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + logger.Println("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerDebugExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, []Option{WithDebug()}, zapcore.DebugLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Print("hello") + logger.Print("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + logger.Println("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerDebugSuppressed(t *testing.T) { + checkMessages(t, zapcore.InfoLevel, []Option{WithDebug()}, zapcore.DebugLevel, nil, func(logger *Logger) { + logger.Print("hello") + logger.Printf("%s world", "hello") + logger.Println() + logger.Println("foo") + logger.Println("foo", "bar") + }) +} + +func TestLoggerWarningExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.WarnLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Warning("hello") + logger.Warning("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Warningf("%s world", "hello") + logger.Warningln() + logger.Warningln("foo") + logger.Warningln("foo", "bar") + logger.Warningln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerErrorExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.ErrorLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Error("hello") + logger.Error("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Errorf("%s world", "hello") + logger.Errorln() + logger.Errorln("foo") + logger.Errorln("foo", "bar") + logger.Errorln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerFatalExpected(t *testing.T) { + checkMessages(t, zapcore.DebugLevel, nil, zapcore.FatalLevel, []string{ + "hello", + "s1s21 2 3s34s56", + "hello world", + "", + "foo", + "foo bar", + "s1 s2 1 2 3 s3 4 s5 6", + }, func(logger *Logger) { + logger.Fatal("hello") + logger.Fatal("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + logger.Fatalf("%s world", "hello") + logger.Fatalln() + logger.Fatalln("foo") + logger.Fatalln("foo", "bar") + logger.Fatalln("s1", "s2", 1, 2, 3, "s3", 4, "s5", 6) + }) +} + +func TestLoggerV(t *testing.T) { + tests := []struct { + zapLevel zapcore.Level + grpcEnabled []int + grpcDisabled []int + }{ + { + zapLevel: zapcore.DebugLevel, + grpcEnabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{}, // everything is enabled, nothing is disabled + }, + { + zapLevel: zapcore.InfoLevel, + grpcEnabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{}, // everything is enabled, nothing is disabled + }, + { + zapLevel: zapcore.WarnLevel, + grpcEnabled: []int{grpcLvlWarn, grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo}, + }, + { + zapLevel: zapcore.ErrorLevel, + grpcEnabled: []int{grpcLvlError, grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn}, + }, + { + zapLevel: zapcore.DPanicLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + { + zapLevel: zapcore.PanicLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + { + zapLevel: zapcore.FatalLevel, + grpcEnabled: []int{grpcLvlFatal}, + grpcDisabled: []int{grpcLvlInfo, grpcLvlWarn, grpcLvlError}, + }, + } + for _, tst := range tests { + for _, grpcLvl := range tst.grpcEnabled { + t.Run(fmt.Sprintf("enabled %s %d", tst.zapLevel, grpcLvl), func(t *testing.T) { + checkLevel(t, tst.zapLevel, true, func(logger *Logger) bool { + return logger.V(grpcLvl) + }) + }) + } + for _, grpcLvl := range tst.grpcDisabled { + t.Run(fmt.Sprintf("disabled %s %d", tst.zapLevel, grpcLvl), func(t *testing.T) { + checkLevel(t, tst.zapLevel, false, func(logger *Logger) bool { + return logger.V(grpcLvl) + }) + }) + } + } +} + +func checkLevel( + t testing.TB, + enab zapcore.LevelEnabler, + expectedBool bool, + f func(*Logger) bool, +) { + withLogger(enab, nil, func(logger *Logger, observedLogs *observer.ObservedLogs) { + actualBool := f(logger) + if expectedBool { + require.True(t, actualBool) + } else { + require.False(t, actualBool) + } + }) +} + +func checkMessages( + t testing.TB, + enab zapcore.LevelEnabler, + opts []Option, + expectedLevel zapcore.Level, + expectedMessages []string, + f func(*Logger), +) { + if expectedLevel == zapcore.FatalLevel { + expectedLevel = zapcore.WarnLevel + } + withLogger(enab, opts, func(logger *Logger, observedLogs *observer.ObservedLogs) { + f(logger) + logEntries := observedLogs.All() + require.Equal(t, len(expectedMessages), len(logEntries)) + for i, logEntry := range logEntries { + require.Equal(t, expectedLevel, logEntry.Level) + require.Equal(t, expectedMessages[i], logEntry.Message) + } + }) +} + +func withLogger( + enab zapcore.LevelEnabler, + opts []Option, + f func(*Logger, *observer.ObservedLogs), +) { + core, observedLogs := observer.New(enab) + f(NewLogger(zap.New(core), append(opts, withWarn())...), observedLogs) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/example_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/example_test.go new file mode 100644 index 0000000..e9565db --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/example_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapio_test + +import ( + "io" + "log" + + "go.uber.org/zap" + "go.uber.org/zap/zapio" +) + +func ExampleWriter() { + logger := zap.NewExample() + w := &zapio.Writer{Log: logger} + + io.WriteString(w, "starting up\n") + io.WriteString(w, "running\n") + io.WriteString(w, "shutting down\n") + + if err := w.Close(); err != nil { + log.Fatal(err) + } + + // Output: + // {"level":"info","msg":"starting up"} + // {"level":"info","msg":"running"} + // {"level":"info","msg":"shutting down"} +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer.go new file mode 100644 index 0000000..a87d910 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer.go @@ -0,0 +1,149 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zapio provides tools for interacting with IO streams through Zap. +package zapio + +import ( + "bytes" + "io" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// Writer is an io.Writer that writes to the provided Zap logger, splitting log +// messages on line boundaries. The Writer will buffer writes in memory until +// it encounters a newline, or the caller calls Sync or Close. +// +// Use the Writer with packages like os/exec where an io.Writer is required, +// and you want to log the output using your existing logger configuration. For +// example, +// +// writer := &zapio.Writer{Log: logger, Level: zap.DebugLevel} +// defer writer.Close() +// +// cmd := exec.CommandContext(ctx, ...) +// cmd.Stdout = writer +// cmd.Stderr = writer +// if err := cmd.Run(); err != nil { +// return err +// } +// +// Writer must be closed when finished to flush buffered data to the logger. +type Writer struct { + // Log specifies the logger to which the Writer will write messages. + // + // The Writer will panic if Log is unspecified. + Log *zap.Logger + + // Log level for the messages written to the provided logger. + // + // If unspecified, defaults to Info. + Level zapcore.Level + + buff bytes.Buffer +} + +var ( + _ zapcore.WriteSyncer = (*Writer)(nil) + _ io.Closer = (*Writer)(nil) +) + +// Write writes the provided bytes to the underlying logger at the configured +// log level and returns the length of the bytes. +// +// Write will split the input on newlines and post each line as a new log entry +// to the logger. +func (w *Writer) Write(bs []byte) (n int, err error) { + // Skip all checks if the level isn't enabled. + if !w.Log.Core().Enabled(w.Level) { + return len(bs), nil + } + + n = len(bs) + for len(bs) > 0 { + bs = w.writeLine(bs) + } + + return n, nil +} + +// writeLine writes a single line from the input, returning the remaining, +// unconsumed bytes. +func (w *Writer) writeLine(line []byte) (remaining []byte) { + idx := bytes.IndexByte(line, '\n') + if idx < 0 { + // If there are no newlines, buffer the entire string. + w.buff.Write(line) + return nil + } + + // Split on the newline, buffer and flush the left. + line, remaining = line[:idx], line[idx+1:] + + // Fast path: if we don't have a partial message from a previous write + // in the buffer, skip the buffer and log directly. + if w.buff.Len() == 0 { + w.log(line) + return + } + + w.buff.Write(line) + + // Log empty messages in the middle of the stream so that we don't lose + // information when the user writes "foo\n\nbar". + w.flush(true /* allowEmpty */) + + return remaining +} + +// Close closes the writer, flushing any buffered data in the process. +// +// Always call Close once you're done with the Writer to ensure that it flushes +// all data. +func (w *Writer) Close() error { + return w.Sync() +} + +// Sync flushes buffered data to the logger as a new log entry even if it +// doesn't contain a newline. +func (w *Writer) Sync() error { + // Don't allow empty messages on explicit Sync calls or on Close + // because we don't want an extraneous empty message at the end of the + // stream -- it's common for files to end with a newline. + w.flush(false /* allowEmpty */) + return nil +} + +// flush flushes the buffered data to the logger, allowing empty messages only +// if the bool is set. +func (w *Writer) flush(allowEmpty bool) { + if allowEmpty || w.buff.Len() > 0 { + w.log(w.buff.Bytes()) + } + w.buff.Reset() +} + +func (w *Writer) log(b []byte) { + if ce := w.Log.Check(w.Level, string(b)); ce != nil { + ce.Write() + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer_test.go new file mode 100644 index 0000000..9bdf348 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zapio/writer_test.go @@ -0,0 +1,248 @@ +// Copyright (c) 2021 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zapio + +import ( + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" +) + +func TestWriter(t *testing.T) { + t.Parallel() + + tests := []struct { + desc string + level zapcore.Level // defaults to info + writes []string + want []zapcore.Entry + }{ + { + desc: "simple", + writes: []string{ + "foo\n", + "bar\n", + "baz\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "level too low", + level: zap.DebugLevel, + writes: []string{ + "foo\n", + "bar\n", + }, + want: []zapcore.Entry{}, + }, + { + desc: "multiple newlines in a message", + level: zap.WarnLevel, + writes: []string{ + "foo\nbar\n", + "baz\n", + "qux\nquux\n", + }, + want: []zapcore.Entry{ + {Level: zap.WarnLevel, Message: "foo"}, + {Level: zap.WarnLevel, Message: "bar"}, + {Level: zap.WarnLevel, Message: "baz"}, + {Level: zap.WarnLevel, Message: "qux"}, + {Level: zap.WarnLevel, Message: "quux"}, + }, + }, + { + desc: "message split across multiple writes", + level: zap.ErrorLevel, + writes: []string{ + "foo", + "bar\nbaz", + "qux", + }, + want: []zapcore.Entry{ + {Level: zap.ErrorLevel, Message: "foobar"}, + {Level: zap.ErrorLevel, Message: "bazqux"}, + }, + }, + { + desc: "blank lines in the middle", + writes: []string{ + "foo\n\nbar\nbaz", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: ""}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "blank line at the end", + writes: []string{ + "foo\nbar\nbaz\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + }, + }, + { + desc: "multiple blank line at the end", + writes: []string{ + "foo\nbar\nbaz\n\n", + }, + want: []zapcore.Entry{ + {Level: zap.InfoLevel, Message: "foo"}, + {Level: zap.InfoLevel, Message: "bar"}, + {Level: zap.InfoLevel, Message: "baz"}, + {Level: zap.InfoLevel, Message: ""}, + }, + }, + } + + for _, tt := range tests { + tt := tt // for t.Parallel + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + core, observed := observer.New(zap.InfoLevel) + + w := Writer{ + Log: zap.New(core), + Level: tt.level, + } + + for _, s := range tt.writes { + _, err := io.WriteString(&w, s) + require.NoError(t, err, "Writer.Write failed.") + } + + assert.NoError(t, w.Close(), "Writer.Close failed.") + + // Turn []observer.LoggedEntry => []zapcore.Entry + got := make([]zapcore.Entry, observed.Len()) + for i, ent := range observed.AllUntimed() { + got[i] = ent.Entry + } + assert.Equal(t, tt.want, got, "Logged entries do not match.") + }) + } +} + +func TestWrite_Sync(t *testing.T) { + t.Parallel() + + core, observed := observer.New(zap.InfoLevel) + + w := Writer{ + Log: zap.New(core), + Level: zap.InfoLevel, + } + + io.WriteString(&w, "foo") + io.WriteString(&w, "bar") + + t.Run("no sync", func(t *testing.T) { + assert.Zero(t, observed.Len(), "Expected no logs yet") + }) + + t.Run("sync", func(t *testing.T) { + defer observed.TakeAll() + + require.NoError(t, w.Sync(), "Sync must not fail") + + assert.Equal(t, []observer.LoggedEntry{ + {Entry: zapcore.Entry{Message: "foobar"}, Context: []zapcore.Field{}}, + }, observed.AllUntimed(), "Log messages did not match") + }) + + t.Run("sync on empty", func(t *testing.T) { + require.NoError(t, w.Sync(), "Sync must not fail") + assert.Zero(t, observed.Len(), "Expected no logs yet") + }) +} + +func BenchmarkWriter(b *testing.B) { + tests := []struct { + name string + writes [][]byte + }{ + { + name: "single", + writes: [][]byte{ + []byte("foobar\n"), + []byte("bazqux\n"), + }, + }, + { + name: "splits", + writes: [][]byte{ + []byte("foo"), + []byte("bar\nbaz"), + []byte("qux\n"), + }, + }, + } + + writer := Writer{ + Log: zap.New(new(partiallyNopCore)), + Level: zapcore.DebugLevel, + } + + for _, tt := range tests { + b.Run(tt.name, func(b *testing.B) { + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, bs := range tt.writes { + writer.Write(bs) + } + } + }) + } +} + +// partiallyNopCore behaves exactly like NopCore except it always returns true +// for whether the provided level is enabled, and accepts all Check requests. +// +// This lets us measure the overhead of the writer without measuring the cost +// of logging. +type partiallyNopCore struct{} + +func (*partiallyNopCore) Enabled(zapcore.Level) bool { return true } + +func (c *partiallyNopCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + return ce.AddCore(ent, c) +} + +func (c *partiallyNopCore) With([]zapcore.Field) zapcore.Core { return c } +func (*partiallyNopCore) Write(zapcore.Entry, []zapcore.Field) error { return nil } +func (*partiallyNopCore) Sync() error { return nil } diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/doc.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/doc.go new file mode 100644 index 0000000..b377859 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/doc.go @@ -0,0 +1,22 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package zaptest provides a variety of helpers for testing log output. +package zaptest // import "go.uber.org/zap/zaptest" diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger.go new file mode 100644 index 0000000..4734c33 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger.go @@ -0,0 +1,157 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "bytes" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// LoggerOption configures the test logger built by NewLogger. +type LoggerOption interface { + applyLoggerOption(*loggerOptions) +} + +type loggerOptions struct { + Level zapcore.LevelEnabler + zapOptions []zap.Option +} + +type loggerOptionFunc func(*loggerOptions) + +func (f loggerOptionFunc) applyLoggerOption(opts *loggerOptions) { + f(opts) +} + +// Level controls which messages are logged by a test Logger built by +// NewLogger. +func Level(enab zapcore.LevelEnabler) LoggerOption { + return loggerOptionFunc(func(opts *loggerOptions) { + opts.Level = enab + }) +} + +// WrapOptions adds zap.Option's to a test Logger built by NewLogger. +func WrapOptions(zapOpts ...zap.Option) LoggerOption { + return loggerOptionFunc(func(opts *loggerOptions) { + opts.zapOptions = zapOpts + }) +} + +// NewLogger builds a new Logger that logs all messages to the given +// testing.TB. +// +// logger := zaptest.NewLogger(t) +// +// Use this with a *testing.T or *testing.B to get logs which get printed only +// if a test fails or if you ran go test -v. +// +// The returned logger defaults to logging debug level messages and above. +// This may be changed by passing a zaptest.Level during construction. +// +// logger := zaptest.NewLogger(t, zaptest.Level(zap.WarnLevel)) +// +// You may also pass zap.Option's to customize test logger. +// +// logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) +func NewLogger(t TestingT, opts ...LoggerOption) *zap.Logger { + cfg := loggerOptions{ + Level: zapcore.DebugLevel, + } + for _, o := range opts { + o.applyLoggerOption(&cfg) + } + + writer := NewTestingWriter(t) + zapOptions := []zap.Option{ + // Send zap errors to the same writer and mark the test as failed if + // that happens. + zap.ErrorOutput(writer.WithMarkFailed(true)), + } + zapOptions = append(zapOptions, cfg.zapOptions...) + + return zap.New( + zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + writer, + cfg.Level, + ), + zapOptions..., + ) +} + +// TestingWriter is a WriteSyncer that writes to the given testing.TB. +type TestingWriter struct { + t TestingT + + // If true, the test will be marked as failed if this TestingWriter is + // ever used. + markFailed bool +} + +// NewTestingWriter builds a new TestingWriter that writes to the given +// testing.TB. +// +// Use this if you need more flexibility when creating *zap.Logger +// than zaptest.NewLogger() provides. +// +// E.g., if you want to use custom core with zaptest.TestingWriter: +// +// encoder := newCustomEncoder() +// writer := zaptest.NewTestingWriter(t) +// level := zap.NewAtomicLevelAt(zapcore.DebugLevel) +// +// core := newCustomCore(encoder, writer, level) +// +// logger := zap.New(core, zap.AddCaller()) +func NewTestingWriter(t TestingT) TestingWriter { + return TestingWriter{t: t} +} + +// WithMarkFailed returns a copy of this TestingWriter with markFailed set to +// the provided value. +func (w TestingWriter) WithMarkFailed(v bool) TestingWriter { + w.markFailed = v + return w +} + +// Write writes bytes from p to the underlying testing.TB. +func (w TestingWriter) Write(p []byte) (n int, err error) { + n = len(p) + + // Strip trailing newline because t.Log always adds one. + p = bytes.TrimRight(p, "\n") + + // Note: t.Log is safe for concurrent use. + w.t.Logf("%s", p) + if w.markFailed { + w.t.Fail() + } + + return n, nil +} + +// Sync commits the current contents (a no-op for TestingWriter). +func (w TestingWriter) Sync() error { + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger_test.go new file mode 100644 index 0000000..40e368b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/logger_test.go @@ -0,0 +1,193 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "errors" + "fmt" + "io" + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/internal/ztest" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestTestLogger(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + "INFO received work order", + "DEBUG starting work", + "WARN work may fail", + `ERROR work failed {"error": "great sadness"}`, + "PANIC failed to do work", + ) +} + +func TestTestLoggerSupportsLevels(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts, Level(zap.WarnLevel)) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + "WARN work may fail", + `ERROR work failed {"error": "great sadness"}`, + "PANIC failed to do work", + ) +} + +func TestTestLoggerSupportsWrappedZapOptions(t *testing.T) { + ts := newTestLogSpy(t) + defer ts.AssertPassed() + + log := NewLogger(ts, WrapOptions(zap.AddCaller(), zap.Fields(zap.String("k1", "v1")))) + + log.Info("received work order") + log.Debug("starting work") + log.Warn("work may fail") + log.Error("work failed", zap.Error(errors.New("great sadness"))) + + assert.Panics(t, func() { + log.Panic("failed to do work") + }, "log.Panic should panic") + + ts.AssertMessages( + `INFO zaptest/logger_test.go:89 received work order {"k1": "v1"}`, + `DEBUG zaptest/logger_test.go:90 starting work {"k1": "v1"}`, + `WARN zaptest/logger_test.go:91 work may fail {"k1": "v1"}`, + `ERROR zaptest/logger_test.go:92 work failed {"k1": "v1", "error": "great sadness"}`, + `PANIC zaptest/logger_test.go:95 failed to do work {"k1": "v1"}`, + ) +} + +func TestTestingWriter(t *testing.T) { + ts := newTestLogSpy(t) + w := NewTestingWriter(ts) + + n, err := io.WriteString(w, "hello\n\n") + assert.NoError(t, err, "WriteString must not fail") + assert.Equal(t, 7, n) +} + +func TestTestLoggerErrorOutput(t *testing.T) { + // This test verifies that the test logger logs internal messages to the + // testing.T and marks the test as failed. + + ts := newTestLogSpy(t) + defer ts.AssertFailed() + + log := NewLogger(ts) + + // Replace with a core that fails. + log = log.WithOptions(zap.WrapCore(func(zapcore.Core) zapcore.Core { + return zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + zapcore.Lock(zapcore.AddSync(ztest.FailWriter{})), + zapcore.DebugLevel, + ) + })) + + log.Info("foo") // this fails + + if assert.Len(t, ts.Messages, 1, "expected a log message") { + assert.Regexp(t, `write error: failed`, ts.Messages[0]) + } +} + +// testLogSpy is a testing.TB that captures logged messages. +type testLogSpy struct { + testing.TB + + failed bool + Messages []string +} + +func newTestLogSpy(t testing.TB) *testLogSpy { + return &testLogSpy{TB: t} +} + +func (t *testLogSpy) Fail() { + t.failed = true +} + +func (t *testLogSpy) Failed() bool { + return t.failed +} + +func (t *testLogSpy) FailNow() { + t.Fail() + t.TB.FailNow() +} + +func (t *testLogSpy) Logf(format string, args ...interface{}) { + // Log messages are in the format, + // + // 2017-10-27T13:03:01.000-0700 DEBUG your message here {data here} + // + // We strip the first part of these messages because we can't really test + // for the timestamp from these tests. + m := fmt.Sprintf(format, args...) + m = m[strings.IndexByte(m, '\t')+1:] + t.Messages = append(t.Messages, m) + t.TB.Log(m) +} + +func (t *testLogSpy) AssertMessages(msgs ...string) { + assert.Equal(t.TB, msgs, t.Messages, "logged messages did not match") +} + +func (t *testLogSpy) AssertPassed() { + t.assertFailed(false, "expected test to pass") +} + +func (t *testLogSpy) AssertFailed() { + t.assertFailed(true, "expected test to fail") +} + +func (t *testLogSpy) assertFailed(v bool, msg string) { + assert.Equal(t.TB, v, t.failed, msg) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry.go new file mode 100644 index 0000000..a4ea7ec --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry.go @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer + +import "go.uber.org/zap/zapcore" + +// An LoggedEntry is an encoding-agnostic representation of a log message. +// Field availability is context dependant. +type LoggedEntry struct { + zapcore.Entry + Context []zapcore.Field +} + +// ContextMap returns a map for all fields in Context. +func (e LoggedEntry) ContextMap() map[string]interface{} { + encoder := zapcore.NewMapObjectEncoder() + for _, f := range e.Context { + f.AddTo(encoder) + } + return encoder.Fields +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry_test.go new file mode 100644 index 0000000..50f6123 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/logged_entry_test.go @@ -0,0 +1,88 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer + +import ( + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/stretchr/testify/assert" +) + +func TestLoggedEntryContextMap(t *testing.T) { + tests := []struct { + msg string + fields []zapcore.Field + want map[string]interface{} + }{ + { + msg: "no fields", + fields: nil, + want: map[string]interface{}{}, + }, + { + msg: "simple", + fields: []zapcore.Field{ + zap.String("k1", "v"), + zap.Int64("k2", 10), + }, + want: map[string]interface{}{ + "k1": "v", + "k2": int64(10), + }, + }, + { + msg: "overwrite", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.String("k1", "v2"), + }, + want: map[string]interface{}{ + "k1": "v2", + }, + }, + { + msg: "nested", + fields: []zapcore.Field{ + zap.String("k1", "v1"), + zap.Namespace("nested"), + zap.String("k2", "v2"), + }, + want: map[string]interface{}{ + "k1": "v1", + "nested": map[string]interface{}{ + "k2": "v2", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + entry := LoggedEntry{ + Context: tt.fields, + } + assert.Equal(t, tt.want, entry.ContextMap()) + }) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer.go new file mode 100644 index 0000000..f77f130 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer.go @@ -0,0 +1,196 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package observer provides a zapcore.Core that keeps an in-memory, +// encoding-agnostic representation of log entries. It's useful for +// applications that want to unit test their log output without tying their +// tests to a particular output encoding. +package observer // import "go.uber.org/zap/zaptest/observer" + +import ( + "strings" + "sync" + "time" + + "go.uber.org/zap/internal" + "go.uber.org/zap/zapcore" +) + +// ObservedLogs is a concurrency-safe, ordered collection of observed logs. +type ObservedLogs struct { + mu sync.RWMutex + logs []LoggedEntry +} + +// Len returns the number of items in the collection. +func (o *ObservedLogs) Len() int { + o.mu.RLock() + n := len(o.logs) + o.mu.RUnlock() + return n +} + +// All returns a copy of all the observed logs. +func (o *ObservedLogs) All() []LoggedEntry { + o.mu.RLock() + ret := make([]LoggedEntry, len(o.logs)) + copy(ret, o.logs) + o.mu.RUnlock() + return ret +} + +// TakeAll returns a copy of all the observed logs, and truncates the observed +// slice. +func (o *ObservedLogs) TakeAll() []LoggedEntry { + o.mu.Lock() + ret := o.logs + o.logs = nil + o.mu.Unlock() + return ret +} + +// AllUntimed returns a copy of all the observed logs, but overwrites the +// observed timestamps with time.Time's zero value. This is useful when making +// assertions in tests. +func (o *ObservedLogs) AllUntimed() []LoggedEntry { + ret := o.All() + for i := range ret { + ret[i].Time = time.Time{} + } + return ret +} + +// FilterLevelExact filters entries to those logged at exactly the given level. +func (o *ObservedLogs) FilterLevelExact(level zapcore.Level) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Level == level + }) +} + +// FilterMessage filters entries to those that have the specified message. +func (o *ObservedLogs) FilterMessage(msg string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return e.Message == msg + }) +} + +// FilterMessageSnippet filters entries to those that have a message containing the specified snippet. +func (o *ObservedLogs) FilterMessageSnippet(snippet string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + return strings.Contains(e.Message, snippet) + }) +} + +// FilterField filters entries to those that have the specified field. +func (o *ObservedLogs) FilterField(field zapcore.Field) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Equals(field) { + return true + } + } + return false + }) +} + +// FilterFieldKey filters entries to those that have the specified key. +func (o *ObservedLogs) FilterFieldKey(key string) *ObservedLogs { + return o.Filter(func(e LoggedEntry) bool { + for _, ctxField := range e.Context { + if ctxField.Key == key { + return true + } + } + return false + }) +} + +// Filter returns a copy of this ObservedLogs containing only those entries +// for which the provided function returns true. +func (o *ObservedLogs) Filter(keep func(LoggedEntry) bool) *ObservedLogs { + o.mu.RLock() + defer o.mu.RUnlock() + + var filtered []LoggedEntry + for _, entry := range o.logs { + if keep(entry) { + filtered = append(filtered, entry) + } + } + return &ObservedLogs{logs: filtered} +} + +func (o *ObservedLogs) add(log LoggedEntry) { + o.mu.Lock() + o.logs = append(o.logs, log) + o.mu.Unlock() +} + +// New creates a new Core that buffers logs in memory (without any encoding). +// It's particularly useful in tests. +func New(enab zapcore.LevelEnabler) (zapcore.Core, *ObservedLogs) { + ol := &ObservedLogs{} + return &contextObserver{ + LevelEnabler: enab, + logs: ol, + }, ol +} + +type contextObserver struct { + zapcore.LevelEnabler + logs *ObservedLogs + context []zapcore.Field +} + +var ( + _ zapcore.Core = (*contextObserver)(nil) + _ internal.LeveledEnabler = (*contextObserver)(nil) +) + +func (co *contextObserver) Level() zapcore.Level { + return zapcore.LevelOf(co.LevelEnabler) +} + +func (co *contextObserver) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry { + if co.Enabled(ent.Level) { + return ce.AddCore(ent, co) + } + return ce +} + +func (co *contextObserver) With(fields []zapcore.Field) zapcore.Core { + return &contextObserver{ + LevelEnabler: co.LevelEnabler, + logs: co.logs, + context: append(co.context[:len(co.context):len(co.context)], fields...), + } +} + +func (co *contextObserver) Write(ent zapcore.Entry, fields []zapcore.Field) error { + all := make([]zapcore.Field, 0, len(fields)+len(co.context)) + all = append(all, co.context...) + all = append(all, fields...) + co.logs.add(LoggedEntry{ent, all}) + return nil +} + +func (co *contextObserver) Sync() error { + return nil +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer_test.go new file mode 100644 index 0000000..0cf631c --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/observer/observer_test.go @@ -0,0 +1,260 @@ +// Copyright (c) 2016-2022 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package observer_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + //revive:disable:dot-imports + . "go.uber.org/zap/zaptest/observer" +) + +func assertEmpty(t testing.TB, logs *ObservedLogs) { + assert.Equal(t, 0, logs.Len(), "Expected empty ObservedLogs to have zero length.") + assert.Equal(t, []LoggedEntry{}, logs.All(), "Unexpected LoggedEntries in empty ObservedLogs.") +} + +func TestObserver(t *testing.T) { + observer, logs := New(zap.InfoLevel) + assertEmpty(t, logs) + + t.Run("LevelOf", func(t *testing.T) { + assert.Equal(t, zap.InfoLevel, zapcore.LevelOf(observer), "Observer reported the wrong log level.") + }) + + assert.NoError(t, observer.Sync(), "Unexpected failure in no-op Sync") + + obs := zap.New(observer).With(zap.Int("i", 1)) + obs.Info("foo") + obs.Debug("bar") + want := []LoggedEntry{{ + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "foo"}, + Context: []zapcore.Field{zap.Int("i", 1)}, + }} + + assert.Equal(t, 1, logs.Len(), "Unexpected observed logs Len.") + assert.Equal(t, want, logs.AllUntimed(), "Unexpected contents from AllUntimed.") + + all := logs.All() + require.Equal(t, 1, len(all), "Unexpected number of LoggedEntries returned from All.") + assert.NotEqual(t, time.Time{}, all[0].Time, "Expected non-zero time on LoggedEntry.") + + // copy & zero time for stable assertions + untimed := append([]LoggedEntry{}, all...) + untimed[0].Time = time.Time{} + assert.Equal(t, want, untimed, "Unexpected LoggedEntries from All.") + + assert.Equal(t, all, logs.TakeAll(), "Expected All and TakeAll to return identical results.") + assertEmpty(t, logs) +} + +func TestObserverWith(t *testing.T) { + sf1, logs := New(zap.InfoLevel) + + // need to pad out enough initial fields so that the underlying slice cap() + // gets ahead of its len() so that the sf3/4 With append's could choose + // not to copy (if the implementation doesn't force them) + sf1 = sf1.With([]zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}) + + sf2 := sf1.With([]zapcore.Field{zap.Int("c", 3)}) + sf3 := sf2.With([]zapcore.Field{zap.Int("d", 4)}) + sf4 := sf2.With([]zapcore.Field{zap.Int("e", 5)}) + ent := zapcore.Entry{Level: zap.InfoLevel, Message: "hello"} + + for i, core := range []zapcore.Core{sf2, sf3, sf4} { + if ce := core.Check(ent, nil); ce != nil { + ce.Write(zap.Int("i", i)) + } + } + + assert.Equal(t, []LoggedEntry{ + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("i", 0), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("d", 4), + zap.Int("i", 1), + }, + }, + { + Entry: ent, + Context: []zapcore.Field{ + zap.Int("a", 1), + zap.Int("b", 2), + zap.Int("c", 3), + zap.Int("e", 5), + zap.Int("i", 2), + }, + }, + }, logs.All(), "expected no field sharing between With siblings") +} + +func TestFilters(t *testing.T) { + logs := []LoggedEntry{ + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "1"), zap.Int("a", 1)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log a"}, + Context: []zapcore.Field{zap.String("fStr", "2"), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log b"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Int("b", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "log c"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns"), zap.Int("a", 2)}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 1"}, + Context: []zapcore.Field{zap.Int("a", 1), zap.Namespace("ns")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any map"}, + Context: []zapcore.Field{zap.Any("map", map[string]string{"a": "b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("slice", []string{"a"})}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "msg 2"}, + Context: []zapcore.Field{zap.Int("b", 2), zap.Namespace("filterMe")}, + }, + { + Entry: zapcore.Entry{Level: zap.InfoLevel, Message: "any slice"}, + Context: []zapcore.Field{zap.Any("filterMe", []string{"b"})}, + }, + { + Entry: zapcore.Entry{Level: zap.WarnLevel, Message: "danger will robinson"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + { + Entry: zapcore.Entry{Level: zap.ErrorLevel, Message: "warp core breach"}, + Context: []zapcore.Field{zap.Int("b", 42)}, + }, + } + + logger, sink := New(zap.InfoLevel) + for _, log := range logs { + assert.NoError(t, logger.Write(log.Entry, log.Context), "Unexpected error writing log entry.") + } + + tests := []struct { + msg string + filtered *ObservedLogs + want []LoggedEntry + }{ + { + msg: "filter by message", + filtered: sink.FilterMessage("log a"), + want: logs[0:2], + }, + { + msg: "filter by field", + filtered: sink.FilterField(zap.String("fStr", "1")), + want: logs[0:1], + }, + { + msg: "filter by message and field", + filtered: sink.FilterMessage("log a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter by field with duplicate fields", + filtered: sink.FilterField(zap.Int("a", 2)), + want: logs[3:4], + }, + { + msg: "filter doesn't match any messages", + filtered: sink.FilterMessage("no match"), + want: []LoggedEntry{}, + }, + { + msg: "filter by snippet", + filtered: sink.FilterMessageSnippet("log"), + want: logs[0:4], + }, + { + msg: "filter by snippet and field", + filtered: sink.FilterMessageSnippet("a").FilterField(zap.Int("b", 2)), + want: logs[1:2], + }, + { + msg: "filter for map", + filtered: sink.FilterField(zap.Any("map", map[string]string{"a": "b"})), + want: logs[5:6], + }, + { + msg: "filter for slice", + filtered: sink.FilterField(zap.Any("slice", []string{"a"})), + want: logs[6:7], + }, + { + msg: "filter field key", + filtered: sink.FilterFieldKey("filterMe"), + want: logs[7:9], + }, + { + msg: "filter by arbitrary function", + filtered: sink.Filter(func(e LoggedEntry) bool { + return len(e.Context) > 1 + }), + want: func() []LoggedEntry { + // Do not modify logs slice. + w := make([]LoggedEntry, 0, len(logs)) + w = append(w, logs[0:5]...) + w = append(w, logs[7]) + return w + }(), + }, + { + msg: "filter level", + filtered: sink.FilterLevelExact(zap.WarnLevel), + want: logs[9:10], + }, + } + + for _, tt := range tests { + got := tt.filtered.AllUntimed() + assert.Equal(t, tt.want, got, tt.msg) + } +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt.go new file mode 100644 index 0000000..792463b --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt.go @@ -0,0 +1,47 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +// TestingT is a subset of the API provided by all *testing.T and *testing.B +// objects. +type TestingT interface { + // Logs the given message without failing the test. + Logf(string, ...interface{}) + + // Logs the given message and marks the test as failed. + Errorf(string, ...interface{}) + + // Marks the test as failed. + Fail() + + // Returns true if the test has been marked as failed. + Failed() bool + + // Returns the name of the test. + Name() string + + // Marks the test as failed and stops execution of that test. + FailNow() +} + +// Note: We currently only rely on Logf. We are including Errorf and FailNow +// in the interface in anticipation of future need since we can't extend the +// interface without a breaking change. diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt_test.go new file mode 100644 index 0000000..d847796 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/testingt_test.go @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import "testing" + +// Just a compile-time test to ensure that TestingT matches the testing.TB +// interface. We could do this in testingt.go but that would put a dependency +// on the "testing" package from zaptest. + +var _ TestingT = (testing.TB)(nil) diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout.go new file mode 100644 index 0000000..f0be444 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout.go @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "time" + + "go.uber.org/zap/internal/ztest" +) + +// Timeout scales the provided duration by $TEST_TIMEOUT_SCALE. +// +// Deprecated: This function is intended for internal testing and shouldn't be +// used outside zap itself. It was introduced before Go supported internal +// packages. +func Timeout(base time.Duration) time.Duration { + return ztest.Timeout(base) +} + +// Sleep scales the sleep duration by $TEST_TIMEOUT_SCALE. +// +// Deprecated: This function is intended for internal testing and shouldn't be +// used outside zap itself. It was introduced before Go supported internal +// packages. +func Sleep(base time.Duration) { + ztest.Sleep(base) +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout_test.go new file mode 100644 index 0000000..3962ecd --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/timeout_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap/internal/ztest" +) + +func TestTimeout(t *testing.T) { + defer ztest.Initialize("2")() + assert.Equal(t, time.Duration(100), Timeout(50), "Expected to scale up timeout.") +} + +func TestSleep(t *testing.T) { + defer ztest.Initialize("2")() + const sleepFor = 50 * time.Millisecond + now := time.Now() + Sleep(sleepFor) + elapsed := time.Since(now) + assert.True(t, 2*sleepFor <= elapsed, "Expected to scale up timeout.") +} diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer.go new file mode 100644 index 0000000..4b772f8 --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer.go @@ -0,0 +1,44 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import "go.uber.org/zap/internal/ztest" + +type ( + // A Syncer is a spy for the Sync portion of zapcore.WriteSyncer. + Syncer = ztest.Syncer + + // A Discarder sends all writes to io.Discard. + Discarder = ztest.Discarder + + // FailWriter is a WriteSyncer that always returns an error on writes. + FailWriter = ztest.FailWriter + + // ShortWriter is a WriteSyncer whose write method never returns an error, + // but always reports that it wrote one byte less than the input slice's + // length (thus, a "short write"). + ShortWriter = ztest.ShortWriter + + // Buffer is an implementation of zapcore.WriteSyncer that sends all writes to + // a bytes.Buffer. It has convenience methods to split the accumulated buffer + // on newlines. + Buffer = ztest.Buffer +) diff --git a/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer_test.go b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer_test.go new file mode 100644 index 0000000..c18f18a --- /dev/null +++ b/dependencies/pkg/mod/go.uber.org/zap@v1.27.0/zaptest/writer_test.go @@ -0,0 +1,68 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package zaptest + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSyncer(t *testing.T) { + err := errors.New("sentinel") + s := &Syncer{} + s.SetError(err) + assert.Equal(t, err, s.Sync(), "Expected Sync to fail with provided error.") + assert.True(t, s.Called(), "Expected to record that Sync was called.") +} + +func TestDiscarder(t *testing.T) { + d := &Discarder{} + payload := []byte("foo") + n, err := d.Write(payload) + assert.NoError(t, err, "Unexpected error writing to Discarder.") + assert.Equal(t, len(payload), n, "Wrong number of bytes written.") +} + +func TestFailWriter(t *testing.T) { + w := &FailWriter{} + payload := []byte("foo") + n, err := w.Write(payload) + assert.Error(t, err, "Expected an error writing to FailWriter.") + assert.Equal(t, len(payload), n, "Wrong number of bytes written.") +} + +func TestShortWriter(t *testing.T) { + w := &ShortWriter{} + payload := []byte("foo") + n, err := w.Write(payload) + assert.NoError(t, err, "Unexpected error writing to ShortWriter.") + assert.Equal(t, len(payload)-1, n, "Wrong number of bytes written.") +} + +func TestBuffer(t *testing.T) { + buf := &Buffer{} + buf.WriteString("foo\n") + buf.WriteString("bar\n") + assert.Equal(t, []string{"foo", "bar"}, buf.Lines(), "Unexpected output from Lines.") + assert.Equal(t, "foo\nbar", buf.Stripped(), "Unexpected output from Stripped.") +} |