pax_global_header00006660000000000000000000000064145006665060014522gustar00rootroot0000000000000052 comment=96038b71a7d96b8c14fedb3aeddcb3d0d7605b2d zap-1.26.0/000077500000000000000000000000001450066650600124025ustar00rootroot00000000000000zap-1.26.0/.codecov.yml000066400000000000000000000014411450066650600146250ustar00rootroot00000000000000coverage: 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 zap-1.26.0/.github/000077500000000000000000000000001450066650600137425ustar00rootroot00000000000000zap-1.26.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001450066650600161255ustar00rootroot00000000000000zap-1.26.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000006001450066650600206130ustar00rootroot00000000000000--- 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. zap-1.26.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002021450066650600201070ustar00rootroot00000000000000contact_links: - name: Questions about: Please use our Discussions page url: https://github.com/uber-go/zap/discussions zap-1.26.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000014011450066650600216460ustar00rootroot00000000000000--- 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. zap-1.26.0/.github/dependabot.yml000066400000000000000000000012311450066650600165670ustar00rootroot00000000000000version: 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" zap-1.26.0/.github/workflows/000077500000000000000000000000001450066650600157775ustar00rootroot00000000000000zap-1.26.0/.github/workflows/fossa.yaml000066400000000000000000000005411450066650600177760ustar00rootroot00000000000000name: 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 }} zap-1.26.0/.github/workflows/go.yml000066400000000000000000000025031450066650600171270ustar00rootroot00000000000000name: Go on: push: branches: [master] tags: ['v*'] pull_request: branches: ['*'] permissions: contents: read jobs: build: runs-on: ubuntu-latest strategy: matrix: go: ["1.20.x", "1.21.x"] include: - go: 1.21.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.21.x cache: false # managed by golangci-lint - uses: golangci/golangci-lint-action@v3 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 zap-1.26.0/.gitignore000066400000000000000000000005001450066650600143650ustar00rootroot00000000000000# 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 zap-1.26.0/.golangci.yml000066400000000000000000000045201450066650600147670ustar00rootroot00000000000000output: # 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: - gofmt - 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' zap-1.26.0/.readme.tmpl000066400000000000000000000076211450066650600146210ustar00rootroot00000000000000# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] Blazing fast, structured, leveled logging in Go. ## 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.[1](#footnote-versions) 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.
Released under the [MIT License](LICENSE.txt). 1 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 zap-1.26.0/CHANGELOG.md000066400000000000000000000577571450066650600142400ustar00rootroot00000000000000# 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.26.0 (14 Sep 2023) Enhancements: * [#1319][]: Add `WithLazy` method to `Logger` which lazily evaluates the structured context. * [#1350][]: String encoding is much (~50%) faster now. Thanks to @jquirke, @cdvr1993 for their contributions to this release. [#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 `` 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 `` 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 ## v1.9.1 (06 Aug 2018) Bugfixes: * [#614][]: MapObjectEncoder should not ignore empty slices. [#614]: https://github.com/uber-go/zap/pull/614 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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 ## v1.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. ## v0.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. zap-1.26.0/CODE_OF_CONDUCT.md000066400000000000000000000062301450066650600152020ustar00rootroot00000000000000# 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/ zap-1.26.0/CONTRIBUTING.md000066400000000000000000000041421450066650600146340ustar00rootroot00000000000000# 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 zap-1.26.0/FAQ.md000066400000000000000000000147411450066650600133420ustar00rootroot00000000000000# 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 zap-1.26.0/LICENSE.txt000066400000000000000000000020601450066650600142230ustar00rootroot00000000000000Copyright (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. zap-1.26.0/Makefile000066400000000000000000000033721450066650600140470ustar00rootroot00000000000000# 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) ./... zap-1.26.0/README.md000066400000000000000000000124141450066650600136630ustar00rootroot00000000000000# :zap: zap [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] Blazing fast, structured, leveled logging in Go. ## 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.[1](#footnote-versions) Log a message and 10 fields: | Package | Time | Time % to zap | Objects Allocated | | :------ | :--: | :-----------: | :---------------: | | :zap: zap | 1744 ns/op | +0% | 5 allocs/op | :zap: zap (sugared) | 2483 ns/op | +42% | 10 allocs/op | zerolog | 918 ns/op | -47% | 1 allocs/op | go-kit | 5590 ns/op | +221% | 57 allocs/op | slog | 5640 ns/op | +223% | 40 allocs/op | apex/log | 21184 ns/op | +1115% | 63 allocs/op | logrus | 24338 ns/op | +1296% | 79 allocs/op | log15 | 26054 ns/op | +1394% | 74 allocs/op Log a message with a logger that already has 10 fields of context: | Package | Time | Time % to zap | Objects Allocated | | :------ | :--: | :-----------: | :---------------: | | :zap: zap | 193 ns/op | +0% | 0 allocs/op | :zap: zap (sugared) | 227 ns/op | +18% | 1 allocs/op | zerolog | 81 ns/op | -58% | 0 allocs/op | slog | 322 ns/op | +67% | 0 allocs/op | go-kit | 5377 ns/op | +2686% | 56 allocs/op | apex/log | 19518 ns/op | +10013% | 53 allocs/op | log15 | 19812 ns/op | +10165% | 70 allocs/op | logrus | 21997 ns/op | +11297% | 68 allocs/op Log a static string, without any context or `printf`-style templating: | Package | Time | Time % to zap | Objects Allocated | | :------ | :--: | :-----------: | :---------------: | | :zap: zap | 165 ns/op | +0% | 0 allocs/op | :zap: zap (sugared) | 212 ns/op | +28% | 1 allocs/op | zerolog | 95 ns/op | -42% | 0 allocs/op | slog | 296 ns/op | +79% | 0 allocs/op | go-kit | 415 ns/op | +152% | 9 allocs/op | standard library | 422 ns/op | +156% | 2 allocs/op | apex/log | 1601 ns/op | +870% | 5 allocs/op | logrus | 3017 ns/op | +1728% | 23 allocs/op | log15 | 3469 ns/op | +2002% | 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.
Released under the [MIT License](LICENSE.txt). 1 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 zap-1.26.0/array.go000066400000000000000000000303711450066650600140530ustar00rootroot00000000000000// 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 } zap-1.26.0/array_test.go000066400000000000000000000216371450066650600151170ustar00rootroot00000000000000// 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"]) }) } } zap-1.26.0/benchmarks/000077500000000000000000000000001450066650600145175ustar00rootroot00000000000000zap-1.26.0/benchmarks/apex_test.go000066400000000000000000000033611450066650600170450ustar00rootroot00000000000000// 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 benchmarks import ( "io" "github.com/apex/log" "github.com/apex/log/handlers/json" ) func newDisabledApexLog() *log.Logger { return &log.Logger{ Handler: json.New(io.Discard), Level: log.ErrorLevel, } } func newApexLog() *log.Logger { return &log.Logger{ Handler: json.New(io.Discard), Level: log.DebugLevel, } } func fakeApexFields() log.Fields { return log.Fields{ "int": _tenInts[0], "ints": _tenInts, "string": _tenStrings[0], "strings": _tenStrings, "time": _tenTimes[0], "times": _tenTimes, "user1": _oneUser, "user2": _oneUser, "users": _tenUsers, "error": errExample, } } zap-1.26.0/benchmarks/doc.go000066400000000000000000000023341450066650600156150ustar00rootroot00000000000000// 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 benchmarks contains only benchmarks comparing zap to other // structured logging libraries. package benchmarks zap-1.26.0/benchmarks/go.mod000066400000000000000000000013111450066650600156210ustar00rootroot00000000000000module go.uber.org/zap/benchmarks go 1.19 replace go.uber.org/zap => ../ require ( github.com/apex/log v1.9.0 github.com/go-kit/log v0.2.1 github.com/rs/zerolog v1.28.0 github.com/sirupsen/logrus v1.9.0 go.uber.org/multierr v1.10.0 go.uber.org/zap v1.23.0 golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 gopkg.in/inconshreveable/log15.v2 v2.0.0-20221122034931-555555054819 ) require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/pkg/errors v0.9.1 // indirect golang.org/x/sys v0.2.0 // indirect golang.org/x/term v0.2.0 // indirect ) zap-1.26.0/benchmarks/go.sum000066400000000000000000000235251450066650600156610ustar00rootroot00000000000000github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0= github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA= github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj52Uc= github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 h1:LGJsf5LRplCck6jUCH3dBL2dmycNruWNF5xugkSlfXw= golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20221122034931-555555054819 h1:zkW/QtDjt1BM9JuQOCJBLSTkqY9YQvCHJt1Xv8YuSTs= gopkg.in/inconshreveable/log15.v2 v2.0.0-20221122034931-555555054819/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= zap-1.26.0/benchmarks/kit_test.go000066400000000000000000000024221450066650600166740ustar00rootroot00000000000000// 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 benchmarks import ( "io" "github.com/go-kit/log" ) func newKitLog(fields ...interface{}) log.Logger { return log.With(log.NewJSONLogger(io.Discard), fields...) } zap-1.26.0/benchmarks/log15_test.go000066400000000000000000000024751450066650600170440ustar00rootroot00000000000000// 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 benchmarks import ( "io" "gopkg.in/inconshreveable/log15.v2" ) func newLog15() log15.Logger { logger := log15.New() logger.SetHandler(log15.StreamHandler(io.Discard, log15.JsonFormat())) return logger } zap-1.26.0/benchmarks/logrus_test.go000066400000000000000000000034411450066650600174220ustar00rootroot00000000000000// 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 benchmarks import ( "io" "github.com/sirupsen/logrus" ) func newDisabledLogrus() *logrus.Logger { logger := newLogrus() logger.Level = logrus.ErrorLevel return logger } func newLogrus() *logrus.Logger { return &logrus.Logger{ Out: io.Discard, Formatter: new(logrus.JSONFormatter), Hooks: make(logrus.LevelHooks), Level: logrus.DebugLevel, } } func fakeLogrusFields() logrus.Fields { return logrus.Fields{ "int": _tenInts[0], "ints": _tenInts, "string": _tenStrings[0], "strings": _tenStrings, "time": _tenTimes[0], "times": _tenTimes, "user1": _oneUser, "user2": _oneUser, "users": _tenUsers, "error": errExample, } } zap-1.26.0/benchmarks/scenario_bench_test.go000066400000000000000000000376731450066650600210670ustar00rootroot00000000000000// 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 benchmarks import ( "log" "testing" "go.uber.org/zap" "go.uber.org/zap/internal/ztest" ) func BenchmarkDisabledWithoutFields(b *testing.B) { b.Logf("Logging at a disabled level without any structured context.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil { m.Write() } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.SugarFormatting", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newDisabledApexLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newDisabledLogrus() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := newDisabledZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msg(getMessage(0)) } }) }) b.Run("slog", func(b *testing.B) { logger := newDisabledSlog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) } func BenchmarkDisabledAccumulatedContext(b *testing.B) { b.Logf("Logging at a disabled level with some accumulated context.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil { m.Write() } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.SugarFormatting", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).With(fakeFields()...).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newDisabledApexLog().WithFields(fakeApexFields()) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newDisabledLogrus().WithFields(fakeLogrusFields()) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := fakeZerologContext(newDisabledZerolog().With()).Logger() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msg(getMessage(0)) } }) }) b.Run("slog", func(b *testing.B) { logger := newDisabledSlog(fakeSlogFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) } func BenchmarkDisabledAddingFields(b *testing.B) { b.Logf("Logging at a disabled level, adding context at each log site.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0), fakeFields()...) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if m := logger.Check(zap.InfoLevel, getMessage(0)); m != nil { m.Write(fakeFields()...) } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.ErrorLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infow(getMessage(0), fakeSugarFields()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newDisabledApexLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.WithFields(fakeApexFields()).Info(getMessage(0)) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newDisabledLogrus() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.WithFields(fakeLogrusFields()).Info(getMessage(0)) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := newDisabledZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { fakeZerologFields(logger.Info()).Msg(getMessage(0)) } }) }) b.Run("slog", func(b *testing.B) { logger := newSlog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0), fakeSlogFields()) } }) }) } func BenchmarkWithoutFields(b *testing.B) { b.Logf("Logging without any structured context.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil { ce.Write() } } }) }) b.Run("Zap.CheckSampled", func(b *testing.B) { logger := newSampledLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { i++ if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil { ce.Write() } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.SugarFormatting", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newApexLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("go-kit/kit/log", func(b *testing.B) { logger := newKitLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := logger.Log(getMessage(0), getMessage(1)); err != nil { b.Fatal(err) } } }) }) b.Run("inconshreveable/log15", func(b *testing.B) { logger := newLog15() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newLogrus() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("stdlib.Println", func(b *testing.B) { logger := log.New(&ztest.Discarder{}, "", log.LstdFlags) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Println(getMessage(0)) } }) }) b.Run("stdlib.Printf", func(b *testing.B) { logger := log.New(&ztest.Discarder{}, "", log.LstdFlags) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Printf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := newZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msg(getMessage(0)) } }) }) b.Run("rs/zerolog.Formatting", func(b *testing.B) { logger := newZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msgf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("rs/zerolog.Check", func(b *testing.B) { logger := newZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if e := logger.Info(); e.Enabled() { e.Msg(getMessage(0)) } } }) }) b.Run("slog", func(b *testing.B) { logger := newSlog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) } func BenchmarkAccumulatedContext(b *testing.B) { b.Logf("Logging with some accumulated context.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).With(fakeFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).With(fakeFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil { ce.Write() } } }) }) b.Run("Zap.CheckSampled", func(b *testing.B) { logger := newSampledLogger(zap.DebugLevel).With(fakeFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { i++ if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil { ce.Write() } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).With(fakeFields()...).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("Zap.SugarFormatting", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).With(fakeFields()...).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infof("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newApexLog().WithFields(fakeApexFields()) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("go-kit/kit/log", func(b *testing.B) { logger := newKitLog(fakeSugarFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := logger.Log(getMessage(0), getMessage(1)); err != nil { b.Fatal(err) } } }) }) b.Run("inconshreveable/log15", func(b *testing.B) { logger := newLog15().New(fakeSugarFields()) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newLogrus().WithFields(fakeLogrusFields()) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := fakeZerologContext(newZerolog().With()).Logger() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msg(getMessage(0)) } }) }) b.Run("rs/zerolog.Check", func(b *testing.B) { logger := fakeZerologContext(newZerolog().With()).Logger() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if e := logger.Info(); e.Enabled() { e.Msg(getMessage(0)) } } }) }) b.Run("rs/zerolog.Formatting", func(b *testing.B) { logger := fakeZerologContext(newZerolog().With()).Logger() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info().Msgf("%v %v %v %s %v %v %v %v %v %s\n", fakeFmtArgs()...) } }) }) b.Run("slog", func(b *testing.B) { logger := newSlog(fakeSlogFields()...) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0)) } }) }) } func BenchmarkAddingFields(b *testing.B) { b.Logf("Logging with additional context at each log site.") b.Run("Zap", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0), fakeFields()...) } }) }) b.Run("Zap.Check", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if ce := logger.Check(zap.InfoLevel, getMessage(0)); ce != nil { ce.Write(fakeFields()...) } } }) }) b.Run("Zap.CheckSampled", func(b *testing.B) { logger := newSampledLogger(zap.DebugLevel) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { i++ if ce := logger.Check(zap.InfoLevel, getMessage(i)); ce != nil { ce.Write(fakeFields()...) } } }) }) b.Run("Zap.Sugar", func(b *testing.B) { logger := newZapLogger(zap.DebugLevel).Sugar() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Infow(getMessage(0), fakeSugarFields()...) } }) }) b.Run("apex/log", func(b *testing.B) { logger := newApexLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.WithFields(fakeApexFields()).Info(getMessage(0)) } }) }) b.Run("go-kit/kit/log", func(b *testing.B) { logger := newKitLog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if err := logger.Log(fakeSugarFields()...); err != nil { b.Fatal(err) } } }) }) b.Run("inconshreveable/log15", func(b *testing.B) { logger := newLog15() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0), fakeSugarFields()...) } }) }) b.Run("sirupsen/logrus", func(b *testing.B) { logger := newLogrus() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.WithFields(fakeLogrusFields()).Info(getMessage(0)) } }) }) b.Run("rs/zerolog", func(b *testing.B) { logger := newZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { fakeZerologFields(logger.Info()).Msg(getMessage(0)) } }) }) b.Run("rs/zerolog.Check", func(b *testing.B) { logger := newZerolog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if e := logger.Info(); e.Enabled() { fakeZerologFields(e).Msg(getMessage(0)) } } }) }) b.Run("slog", func(b *testing.B) { logger := newSlog() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { logger.Info(getMessage(0), fakeSlogFields()) } }) }) } zap-1.26.0/benchmarks/slog_test.go000066400000000000000000000035141450066650600170540ustar00rootroot00000000000000// 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 benchmarks import ( "io" "golang.org/x/exp/slog" ) func newSlog(fields ...slog.Attr) *slog.Logger { return slog.New(slog.NewJSONHandler(io.Discard).WithAttrs(fields)) } func newDisabledSlog(fields ...slog.Attr) *slog.Logger { return slog.New(slog.HandlerOptions{Level: slog.LevelError}.NewJSONHandler(io.Discard).WithAttrs(fields)) } func fakeSlogFields() []slog.Attr { return []slog.Attr{ slog.Int("int", _tenInts[0]), slog.Any("ints", _tenInts), slog.String("string", _tenStrings[0]), slog.Any("strings", _tenStrings), slog.Time("time", _tenTimes[0]), slog.Any("times", _tenTimes), slog.Any("user1", _oneUser), slog.Any("user2", _oneUser), slog.Any("users", _tenUsers), slog.Any("error", errExample), } } zap-1.26.0/benchmarks/zap_test.go000066400000000000000000000103401450066650600166750ustar00rootroot00000000000000// 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 benchmarks import ( "errors" "fmt" "time" "go.uber.org/multierr" "go.uber.org/zap" "go.uber.org/zap/internal/ztest" "go.uber.org/zap/zapcore" ) var ( errExample = errors.New("fail") _messages = fakeMessages(1000) _tenInts = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0} _tenStrings = []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"} _tenTimes = []time.Time{ time.Unix(0, 0), time.Unix(1, 0), time.Unix(2, 0), time.Unix(3, 0), time.Unix(4, 0), time.Unix(5, 0), time.Unix(6, 0), time.Unix(7, 0), time.Unix(8, 0), time.Unix(9, 0), } _oneUser = &user{ Name: "Jane Doe", Email: "jane@test.com", CreatedAt: time.Date(1980, 1, 1, 12, 0, 0, 0, time.UTC), } _tenUsers = users{ _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, _oneUser, } ) func fakeMessages(n int) []string { messages := make([]string, n) for i := range messages { messages[i] = fmt.Sprintf("Test logging, but use a somewhat realistic message length. (#%v)", i) } return messages } func getMessage(iter int) string { return _messages[iter%1000] } type users []*user func (uu users) MarshalLogArray(arr zapcore.ArrayEncoder) error { var err error for i := range uu { err = multierr.Append(err, arr.AppendObject(uu[i])) } return err } type user struct { Name string `json:"name"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` } func (u *user) MarshalLogObject(enc zapcore.ObjectEncoder) error { enc.AddString("name", u.Name) enc.AddString("email", u.Email) enc.AddInt64("createdAt", u.CreatedAt.UnixNano()) return nil } func newZapLogger(lvl zapcore.Level) *zap.Logger { ec := zap.NewProductionEncoderConfig() ec.EncodeDuration = zapcore.NanosDurationEncoder ec.EncodeTime = zapcore.EpochNanosTimeEncoder enc := zapcore.NewJSONEncoder(ec) return zap.New(zapcore.NewCore( enc, &ztest.Discarder{}, lvl, )) } func newSampledLogger(lvl zapcore.Level) *zap.Logger { return zap.New(zapcore.NewSamplerWithOptions( newZapLogger(zap.DebugLevel).Core(), 100*time.Millisecond, 10, // first 10, // thereafter )) } func fakeFields() []zap.Field { return []zap.Field{ zap.Int("int", _tenInts[0]), zap.Ints("ints", _tenInts), zap.String("string", _tenStrings[0]), zap.Strings("strings", _tenStrings), zap.Time("time", _tenTimes[0]), zap.Times("times", _tenTimes), zap.Object("user1", _oneUser), zap.Object("user2", _oneUser), zap.Array("users", _tenUsers), zap.Error(errExample), } } func fakeSugarFields() []interface{} { return []interface{}{ "int", _tenInts[0], "ints", _tenInts, "string", _tenStrings[0], "strings", _tenStrings, "time", _tenTimes[0], "times", _tenTimes, "user1", _oneUser, "user2", _oneUser, "users", _tenUsers, "error", errExample, } } func fakeFmtArgs() []interface{} { // Need to keep this a function instead of a package-global var so that we // pay the cast-to-interface{} penalty on each call. return []interface{}{ _tenInts[0], _tenInts, _tenStrings[0], _tenStrings, _tenTimes[0], _tenTimes, _oneUser, _oneUser, _tenUsers, errExample, } } zap-1.26.0/benchmarks/zerolog_test.go000066400000000000000000000044421450066650600175720ustar00rootroot00000000000000// 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 benchmarks import ( "io" "github.com/rs/zerolog" ) func newZerolog() zerolog.Logger { return zerolog.New(io.Discard).With().Timestamp().Logger() } func newDisabledZerolog() zerolog.Logger { return newZerolog().Level(zerolog.Disabled) } func (u *user) MarshalZerologObject(e *zerolog.Event) { e.Str("name", u.Name). Str("email", u.Email). Int64("createdAt", u.CreatedAt.UnixNano()) } func (uu users) MarshalZerologArray(a *zerolog.Array) { for _, u := range uu { a.Object(u) } } func fakeZerologFields(e *zerolog.Event) *zerolog.Event { return e. Int("int", _tenInts[0]). Ints("ints", _tenInts). Str("string", _tenStrings[0]). Strs("strings", _tenStrings). Time("time", _tenTimes[0]). Times("times", _tenTimes). Object("user1", _oneUser). Object("user2", _oneUser). Array("users", _tenUsers). Err(errExample) } func fakeZerologContext(c zerolog.Context) zerolog.Context { return c. Int("int", _tenInts[0]). Ints("ints", _tenInts). Str("string", _tenStrings[0]). Strs("strings", _tenStrings). Time("time", _tenTimes[0]). Times("times", _tenTimes). Object("user1", _oneUser). Object("user2", _oneUser). Array("users", _tenUsers). Err(errExample) } zap-1.26.0/buffer/000077500000000000000000000000001450066650600136535ustar00rootroot00000000000000zap-1.26.0/buffer/buffer.go000066400000000000000000000104211450066650600154510ustar00rootroot00000000000000// 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 a single byte 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) } zap-1.26.0/buffer/buffer_test.go000066400000000000000000000066631450066650600165250ustar00rootroot00000000000000// 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() } }) } zap-1.26.0/buffer/pool.go000066400000000000000000000031711450066650600151550ustar00rootroot00000000000000// 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) } zap-1.26.0/buffer/pool_test.go000066400000000000000000000032621450066650600162150ustar00rootroot00000000000000// 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() } zap-1.26.0/checklicense.sh000077500000000000000000000004461450066650600153650ustar00rootroot00000000000000#!/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 zap-1.26.0/clock_test.go000066400000000000000000000035071450066650600150700ustar00rootroot00000000000000// 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.") }) } zap-1.26.0/common_test.go000066400000000000000000000037611450066650600152670ustar00rootroot00000000000000// 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() } }() } } zap-1.26.0/config.go000066400000000000000000000277151450066650600142120ustar00rootroot00000000000000// 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) } zap-1.26.0/config_test.go000066400000000000000000000145451450066650600152460ustar00rootroot00000000000000// 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()) } zap-1.26.0/doc.go000066400000000000000000000122001450066650600134710ustar00rootroot00000000000000// 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" zap-1.26.0/encoder.go000066400000000000000000000054431450066650600143560ustar00rootroot00000000000000// 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) } zap-1.26.0/encoder_test.go000066400000000000000000000066601450066650600154170ustar00rootroot00000000000000// 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 } zap-1.26.0/error.go000066400000000000000000000054061450066650600140670ustar00rootroot00000000000000// 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 } zap-1.26.0/error_test.go000066400000000000000000000106141450066650600151230ustar00rootroot00000000000000// 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 } zap-1.26.0/example_test.go000066400000000000000000000305341450066650600154300ustar00rootroot00000000000000// 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}}]} } zap-1.26.0/exp/000077500000000000000000000000001450066650600131765ustar00rootroot00000000000000zap-1.26.0/exp/CHANGELOG.md000066400000000000000000000013471450066650600150140ustar00rootroot00000000000000# 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). ## 0.2.0 - 9 Sep 2023 Breaking changes: * [#1315](https://github.com/uber-go/zap/pull/1315): zapslog: Drop HandlerOptions.New in favor of just the NewHandler constructor. * [#1320](https://github.com/uber-go/zap/pull/1320), [#1338](https://github.com/uber-go/zap/pull/1338): zapslog: Drop support for golang.org/x/exp/slog in favor of log/slog released in Go 1.21. ## 0.1.0 - 1 Aug 2023 Initial release of go.uber.org/zap/exp. This submodule contains experimental features for Zap. Features incubated here may be promoted to the root Zap module, but it's not guaranteed. zap-1.26.0/exp/go.mod000066400000000000000000000005131450066650600143030ustar00rootroot00000000000000module go.uber.org/zap/exp go 1.19 require ( github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.24.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.uber.org/zap => ../ zap-1.26.0/exp/go.sum000066400000000000000000000033311450066650600143310ustar00rootroot00000000000000github.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/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.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= zap-1.26.0/exp/zapfield/000077500000000000000000000000001450066650600147745ustar00rootroot00000000000000zap-1.26.0/exp/zapfield/zapfield.go000066400000000000000000000034311450066650600171220ustar00rootroot00000000000000// Copyright (c) 2016-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 zapfield provides experimental zap.Field helpers whose APIs may be unstable. package zapfield import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // Str constructs a field with the given string-like key and value. func Str[K ~string, V ~string](k K, v V) zap.Field { return zap.String(string(k), string(v)) } type stringArray[T ~string] []T func (a stringArray[T]) MarshalLogArray(enc zapcore.ArrayEncoder) error { for i := range a { enc.AppendString(string(a[i])) } return nil } // Strs constructs a field that carries a slice of string-like values. func Strs[K ~string, V ~[]S, S ~string](k K, v V) zap.Field { return zap.Array(string(k), stringArray[S](v)) } zap-1.26.0/exp/zapfield/zapfield_test.go000066400000000000000000000047241450066650600201670ustar00rootroot00000000000000// Copyright (c) 2016-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 zapfield import ( "sync" "testing" "github.com/stretchr/testify/assert" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type ( MyKey string MyValue string MyValues []MyValue ) func TestFieldConstructors(t *testing.T) { var ( key = MyKey("test key") value = MyValue("test value") values = []MyValue{ MyValue("test value 1"), MyValue("test value 2"), } ) tests := []struct { name string expect zap.Field field zap.Field }{ {"Str", zap.Field{Type: zapcore.StringType, Key: "test key", String: "test value"}, Str(key, value)}, {"Strs", zap.Array("test key", stringArray[MyValue]{"test value 1", "test value 2"}), Strs(key, values)}, } 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 assertCanBeReused(t testing.TB, field zap.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() } zap-1.26.0/exp/zapslog/000077500000000000000000000000001450066650600146555ustar00rootroot00000000000000zap-1.26.0/exp/zapslog/doc.go000066400000000000000000000024731450066650600157570ustar00rootroot00000000000000// 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 zapslog provides an implementation of slog.Handler which writes to // the supplied zapcore.Core. // // Use of this package requires at least Go 1.21. package zapslog // import "go.uber.org/zap/exp/zapslog" zap-1.26.0/exp/zapslog/example_test.go000066400000000000000000000047121450066650600177020ustar00rootroot00000000000000// 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. //go:build go1.21 package zapslog_test import ( "context" "log/slog" "net" "time" "go.uber.org/zap" "go.uber.org/zap/exp/zapslog" ) type Password string func (p Password) LogValue() slog.Value { return slog.StringValue("REDACTED") } func Example_slog() { logger := zap.NewExample(zap.IncreaseLevel(zap.InfoLevel)) defer logger.Sync() sl := slog.New(zapslog.NewHandler(logger.Core())) ctx := context.Background() sl.Info("user", "name", "Al", "secret", Password("secret")) sl.Error("oops", "err", net.ErrClosed, "status", 500) sl.LogAttrs( ctx, slog.LevelError, "oops", slog.Any("err", net.ErrClosed), slog.Int("status", 500), ) sl.Info("message", slog.Group("group", slog.Float64("pi", 3.14), slog.Duration("1min", time.Minute), ), ) sl.WithGroup("s").LogAttrs( ctx, slog.LevelWarn, "warn msg", // message slog.Uint64("u", 1), slog.Any("m", map[string]any{ "foo": "bar", })) sl.LogAttrs(ctx, slog.LevelDebug, "not show up") // Output: // {"level":"info","msg":"user","name":"Al","secret":"REDACTED"} // {"level":"error","msg":"oops","err":"use of closed network connection","status":500} // {"level":"error","msg":"oops","err":"use of closed network connection","status":500} // {"level":"info","msg":"message","group":{"pi":3.14,"1min":"1m0s"}} // {"level":"warn","msg":"warn msg","s":{"u":1,"m":{"foo":"bar"}}} } zap-1.26.0/exp/zapslog/handler.go000066400000000000000000000130371450066650600166250ustar00rootroot00000000000000// 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. //go:build go1.21 package zapslog import ( "context" "log/slog" "runtime" "go.uber.org/zap" "go.uber.org/zap/internal/stacktrace" "go.uber.org/zap/zapcore" ) // Handler implements the slog.Handler by writing to a zap Core. type Handler struct { core zapcore.Core name string // logger name addCaller bool addStackAt slog.Level callerSkip int } // NewHandler builds a [Handler] that writes to the supplied [zapcore.Core] // with options. func NewHandler(core zapcore.Core, opts ...Option) *Handler { h := &Handler{ core: core, addStackAt: slog.LevelError, } for _, v := range opts { v.apply(h) } return h } var _ slog.Handler = (*Handler)(nil) // groupObject holds all the Attrs saved in a slog.GroupValue. type groupObject []slog.Attr func (gs groupObject) MarshalLogObject(enc zapcore.ObjectEncoder) error { for _, attr := range gs { convertAttrToField(attr).AddTo(enc) } return nil } func convertAttrToField(attr slog.Attr) zapcore.Field { if attr.Equal(slog.Attr{}) { // Ignore empty attrs. return zap.Skip() } switch attr.Value.Kind() { case slog.KindBool: return zap.Bool(attr.Key, attr.Value.Bool()) case slog.KindDuration: return zap.Duration(attr.Key, attr.Value.Duration()) case slog.KindFloat64: return zap.Float64(attr.Key, attr.Value.Float64()) case slog.KindInt64: return zap.Int64(attr.Key, attr.Value.Int64()) case slog.KindString: return zap.String(attr.Key, attr.Value.String()) case slog.KindTime: return zap.Time(attr.Key, attr.Value.Time()) case slog.KindUint64: return zap.Uint64(attr.Key, attr.Value.Uint64()) case slog.KindGroup: return zap.Object(attr.Key, groupObject(attr.Value.Group())) case slog.KindLogValuer: return convertAttrToField(slog.Attr{ Key: attr.Key, // TODO: resolve the value in a lazy way. // This probably needs a new Zap field type // that can be resolved lazily. Value: attr.Value.Resolve(), }) default: return zap.Any(attr.Key, attr.Value.Any()) } } // convertSlogLevel maps slog Levels to zap Levels. // Note that there is some room between slog levels while zap levels are continuous, so we can't 1:1 map them. // See also https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.md?pli=1#levels func convertSlogLevel(l slog.Level) zapcore.Level { switch { case l >= slog.LevelError: return zapcore.ErrorLevel case l >= slog.LevelWarn: return zapcore.WarnLevel case l >= slog.LevelInfo: return zapcore.InfoLevel default: return zapcore.DebugLevel } } // Enabled reports whether the handler handles records at the given level. func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool { return h.core.Enabled(convertSlogLevel(level)) } // Handle handles the Record. func (h *Handler) Handle(ctx context.Context, record slog.Record) error { ent := zapcore.Entry{ Level: convertSlogLevel(record.Level), Time: record.Time, Message: record.Message, LoggerName: h.name, } ce := h.core.Check(ent, nil) if ce == nil { return nil } if h.addCaller && record.PC != 0 { frame, _ := runtime.CallersFrames([]uintptr{record.PC}).Next() if frame.PC != 0 { ce.Caller = zapcore.EntryCaller{ Defined: true, PC: frame.PC, File: frame.File, Line: frame.Line, Function: frame.Function, } } } if record.Level >= h.addStackAt { // Skipping 3: // zapslog/handler log/slog.(*Logger).log // slog/logger log/slog.(*Logger).log // slog/logger log/slog.(*Logger). ce.Stack = stacktrace.Take(3 + h.callerSkip) } fields := make([]zapcore.Field, 0, record.NumAttrs()) record.Attrs(func(attr slog.Attr) bool { fields = append(fields, convertAttrToField(attr)) return true }) ce.Write(fields...) return nil } // WithAttrs returns a new Handler whose attributes consist of // both the receiver's attributes and the arguments. func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler { fields := make([]zapcore.Field, 0, len(attrs)) for _, attr := range attrs { fields = append(fields, convertAttrToField(attr)) } return h.withFields(fields...) } // WithGroup returns a new Handler with the given group appended to // the receiver's existing groups. func (h *Handler) WithGroup(group string) slog.Handler { return h.withFields(zap.Namespace(group)) } // withFields returns a cloned Handler with the given fields. func (h *Handler) withFields(fields ...zapcore.Field) *Handler { cloned := *h cloned.core = h.core.With(fields) return &cloned } zap-1.26.0/exp/zapslog/handler_test.go000066400000000000000000000131261450066650600176630ustar00rootroot00000000000000// 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. //go:build go1.21 package zapslog import ( "log/slog" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" ) func TestAddCaller(t *testing.T) { t.Parallel() fac, logs := observer.New(zapcore.DebugLevel) sl := slog.New(NewHandler(fac, WithCaller(true))) sl.Info("msg") require.Len(t, logs.AllUntimed(), 1, "Expected exactly one entry to be logged") entry := logs.AllUntimed()[0] assert.Equal(t, "msg", entry.Message, "Unexpected message") assert.Regexp(t, `/handler_test.go:\d+$`, entry.Caller.String(), "Unexpected caller annotation.", ) } func TestAddStack(t *testing.T) { fac, logs := observer.New(zapcore.DebugLevel) sl := slog.New(NewHandler(fac, AddStacktraceAt(slog.LevelDebug))) sl.Info("msg") require.Len(t, logs.AllUntimed(), 1, "Expected exactly one entry to be logged") entry := logs.AllUntimed()[0] require.Equal(t, "msg", entry.Message, "Unexpected message") assert.Regexp(t, `^go.uber.org/zap/exp/zapslog.TestAddStack`, entry.Stack, "Unexpected stack trace annotation.", ) assert.Regexp(t, `/zapslog/handler_test.go:\d+`, entry.Stack, "Unexpected stack trace annotation.", ) } func TestAddStackSkip(t *testing.T) { fac, logs := observer.New(zapcore.DebugLevel) sl := slog.New(NewHandler(fac, AddStacktraceAt(slog.LevelDebug), WithCallerSkip(1))) sl.Info("msg") require.Len(t, logs.AllUntimed(), 1, "Expected exactly one entry to be logged") entry := logs.AllUntimed()[0] assert.Regexp(t, `src/testing/testing.go:\d+`, entry.Stack, "Unexpected stack trace annotation.", ) } func TestEmptyAttr(t *testing.T) { t.Parallel() fac, observedLogs := observer.New(zapcore.DebugLevel) sl := slog.New(NewHandler(fac)) t.Run("Handle", func(t *testing.T) { sl.Info( "msg", slog.String("foo", "bar"), slog.Attr{}, ) logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected exactly one entry to be logged") assert.Equal(t, map[string]any{ "foo": "bar", }, logs[0].ContextMap(), "Unexpected context") }) t.Run("WithAttrs", func(t *testing.T) { sl.With(slog.String("foo", "bar"), slog.Attr{}).Info("msg") logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected exactly one entry to be logged") assert.Equal(t, map[string]any{ "foo": "bar", }, logs[0].ContextMap(), "Unexpected context") }) t.Run("Group", func(t *testing.T) { sl.With("k", slog.GroupValue(slog.String("foo", "bar"), slog.Attr{})).Info("msg") logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected exactly one entry to be logged") assert.Equal(t, map[string]any{ "k": map[string]any{ "foo": "bar", }, }, logs[0].ContextMap(), "Unexpected context") }) } func TestWithName(t *testing.T) { t.Parallel() fac, observedLogs := observer.New(zapcore.DebugLevel) t.Run("default", func(t *testing.T) { sl := slog.New(NewHandler(fac)) sl.Info("msg") logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected exactly one entry to be logged") entry := logs[0] assert.Equal(t, "", entry.LoggerName, "Unexpected logger name") }) t.Run("with name", func(t *testing.T) { sl := slog.New(NewHandler(fac, WithName("test-name"))) sl.Info("msg") logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected exactly one entry to be logged") entry := logs[0] assert.Equal(t, "test-name", entry.LoggerName, "Unexpected logger name") }) } type Token string func (Token) LogValue() slog.Value { return slog.StringValue("REDACTED_TOKEN") } func TestAttrKinds(t *testing.T) { fac, logs := observer.New(zapcore.DebugLevel) sl := slog.New(NewHandler(fac)) testToken := Token("no") sl.Info( "msg", slog.Bool("bool", true), slog.Duration("duration", time.Hour), slog.Float64("float64", 42.0), slog.Int64("int64", -1234), slog.Time("time", time.Date(2015, 10, 21, 7, 28, 00, 0, time.UTC)), slog.Uint64("uint64", 2), slog.Group("group", slog.String("inner", "inner-group")), "logvaluer", testToken, "any", "what am i?", ) require.Len(t, logs.AllUntimed(), 1, "Expected exactly one entry to be logged") entry := logs.AllUntimed()[0] assert.Equal(t, map[string]any{ "bool": true, "duration": time.Hour, "float64": float64(42), "group": map[string]any{"inner": "inner-group"}, "int64": int64(-1234), "time": time.Date(2015, time.October, 21, 7, 28, 0, 0, time.UTC), "uint64": uint64(2), "logvaluer": "REDACTED_TOKEN", "any": "what am i?", }, entry.ContextMap()) } zap-1.26.0/exp/zapslog/options.go000066400000000000000000000046151450066650600167050ustar00rootroot00000000000000// 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. //go:build go1.21 package zapslog import "log/slog" // An Option configures a slog Handler. type Option interface { apply(*Handler) } // optionFunc wraps a func so it satisfies the Option interface. type optionFunc func(*Handler) func (f optionFunc) apply(handler *Handler) { f(handler) } // WithName configures the Logger to annotate each message with the logger name. func WithName(name string) Option { return optionFunc(func(h *Handler) { h.name = name }) } // WithCaller configures the Logger to include the filename and line number // of the caller in log messages--if available. func WithCaller(enabled bool) Option { return optionFunc(func(handler *Handler) { handler.addCaller = enabled }) } // WithCallerSkip increases the number of callers skipped by caller annotation // (as enabled by the [WithCaller] option). // // When building wrappers around the Logger, // supplying this Option prevents Zap from always reporting // the wrapper code as the caller. func WithCallerSkip(skip int) Option { return optionFunc(func(log *Handler) { log.callerSkip += skip }) } // AddStacktraceAt configures the Logger to record a stack trace // for all messages at or above a given level. func AddStacktraceAt(lvl slog.Level) Option { return optionFunc(func(log *Handler) { log.addStackAt = lvl }) } zap-1.26.0/field.go000066400000000000000000000462661450066650600140320ustar00rootroot00000000000000// 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 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) } zap-1.26.0/field_test.go000066400000000000000000000372111450066650600150570ustar00rootroot00000000000000// 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) }) } } zap-1.26.0/flag.go000066400000000000000000000032071450066650600136440ustar00rootroot00000000000000// 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 } zap-1.26.0/flag_test.go000066400000000000000000000063641450066650600147120ustar00rootroot00000000000000// 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.") } zap-1.26.0/glide.yaml000066400000000000000000000014031450066650600143500ustar00rootroot00000000000000package: 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 zap-1.26.0/global.go000066400000000000000000000124061450066650600141740ustar00rootroot00000000000000// 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 } zap-1.26.0/global_test.go000066400000000000000000000222171450066650600152340ustar00rootroot00000000000000// 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.", ) } zap-1.26.0/go.mod000066400000000000000000000004701450066650600135110ustar00rootroot00000000000000module go.uber.org/zap go 1.19 require ( github.com/stretchr/testify v1.8.1 go.uber.org/goleak v1.2.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 ) zap-1.26.0/go.sum000066400000000000000000000044241450066650600135410ustar00rootroot00000000000000github.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.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= 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= zap-1.26.0/http_handler.go000066400000000000000000000103511450066650600154050ustar00rootroot00000000000000// 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 } zap-1.26.0/http_handler_test.go000066400000000000000000000145751450066650600164600ustar00rootroot00000000000000// 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") } zap-1.26.0/increase_level_test.go000066400000000000000000000066171450066650600167620ustar00rootroot00000000000000// 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") }) } zap-1.26.0/internal/000077500000000000000000000000001450066650600142165ustar00rootroot00000000000000zap-1.26.0/internal/bufferpool/000077500000000000000000000000001450066650600163615ustar00rootroot00000000000000zap-1.26.0/internal/bufferpool/bufferpool.go000066400000000000000000000026411450066650600210560ustar00rootroot00000000000000// 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 ) zap-1.26.0/internal/color/000077500000000000000000000000001450066650600153345ustar00rootroot00000000000000zap-1.26.0/internal/color/color.go000066400000000000000000000027461450066650600170120ustar00rootroot00000000000000// 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) } zap-1.26.0/internal/color/color_test.go000066400000000000000000000024731450066650600200460ustar00rootroot00000000000000// 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.", ) } zap-1.26.0/internal/exit/000077500000000000000000000000001450066650600151675ustar00rootroot00000000000000zap-1.26.0/internal/exit/exit.go000066400000000000000000000041061450066650600164700ustar00rootroot00000000000000// 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 } zap-1.26.0/internal/exit/exit_test.go000066400000000000000000000032061450066650600175270ustar00rootroot00000000000000// 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.") } } zap-1.26.0/internal/level_enabler.go000066400000000000000000000031471450066650600173510ustar00rootroot00000000000000// 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 } zap-1.26.0/internal/pool/000077500000000000000000000000001450066650600151675ustar00rootroot00000000000000zap-1.26.0/internal/pool/pool.go000066400000000000000000000036411450066650600164730ustar00rootroot00000000000000// 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) } zap-1.26.0/internal/pool/pool_test.go000066400000000000000000000057561450066650600175430ustar00rootroot00000000000000// 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 } }() } } zap-1.26.0/internal/readme/000077500000000000000000000000001450066650600154535ustar00rootroot00000000000000zap-1.26.0/internal/readme/readme.go000066400000000000000000000151231450066650600172410ustar00rootroot00000000000000// 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", } ) 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 } zap-1.26.0/internal/stacktrace/000077500000000000000000000000001450066650600163425ustar00rootroot00000000000000zap-1.26.0/internal/stacktrace/stack.go000066400000000000000000000127011450066650600177770ustar00rootroot00000000000000// 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)) } zap-1.26.0/internal/stacktrace/stack_test.go000066400000000000000000000056621450066650600210460ustar00rootroot00000000000000// 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)) } zap-1.26.0/internal/ztest/000077500000000000000000000000001450066650600153675ustar00rootroot00000000000000zap-1.26.0/internal/ztest/clock.go000066400000000000000000000103301450066650600170060ustar00rootroot00000000000000// 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 } zap-1.26.0/internal/ztest/clock_test.go000066400000000000000000000042371450066650600200560ustar00rootroot00000000000000// 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) }) } zap-1.26.0/internal/ztest/doc.go000066400000000000000000000025431450066650600164670ustar00rootroot00000000000000// 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" zap-1.26.0/internal/ztest/timeout.go000066400000000000000000000036711450066650600174130ustar00rootroot00000000000000// 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) } } zap-1.26.0/internal/ztest/writer.go000066400000000000000000000055671450066650600172470ustar00rootroot00000000000000// 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") } zap-1.26.0/leak_test.go000066400000000000000000000023271450066650600147100ustar00rootroot00000000000000// 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) } zap-1.26.0/level.go000066400000000000000000000122261450066650600140430ustar00rootroot00000000000000// 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() } zap-1.26.0/level_test.go000066400000000000000000000103461450066650600151030ustar00rootroot00000000000000// 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") } } } zap-1.26.0/logger.go000066400000000000000000000330671450066650600142210ustar00rootroot00000000000000// 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 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, zapcore.WriteThenPanic) case zapcore.FatalLevel: onFatal := log.onFatal // nil or WriteThenNoop will lead to continued execution after // a 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 onFatal == nil || onFatal == zapcore.WriteThenNoop { onFatal = zapcore.WriteThenFatal } ce = ce.After(ent, onFatal) case zapcore.DPanicLevel: if log.development { ce = ce.After(ent, zapcore.WriteThenPanic) } } // 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 } zap-1.26.0/logger_bench_test.go000066400000000000000000000207261450066650600164150ustar00rootroot00000000000000// 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() }) }) }) }) } } zap-1.26.0/logger_test.go000066400000000000000000000625351450066650600152620ustar00rootroot00000000000000// 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 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...) } zap-1.26.0/options.go000066400000000000000000000133451450066650600144320ustar00rootroot00000000000000// 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 } }) } // 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 }) } zap-1.26.0/sink.go000066400000000000000000000127611450066650600137040ustar00rootroot00000000000000// 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 } zap-1.26.0/sink_test.go000066400000000000000000000064411450066650600147410ustar00rootroot00000000000000// 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) }) } } zap-1.26.0/sink_windows_test.go000066400000000000000000000041441450066650600165110ustar00rootroot00000000000000// 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 := "" 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") }) } } zap-1.26.0/stacktrace_ext_test.go000066400000000000000000000163521450066650600170030ustar00rootroot00000000000000// 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 } zap-1.26.0/sugar.go000066400000000000000000000337011450066650600140560ustar00rootroot00000000000000// 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)...)} } // 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) } // 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) } // 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) } // 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) } // 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 } zap-1.26.0/sugar_test.go000066400000000000000000000421471450066650600151210ustar00rootroot00000000000000// 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" "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)}, } } 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 { withSugar(t, DebugLevel, nil, func(logger *SugaredLogger, logs *observer.ObservedLogs) { logger.With(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 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...) expected := make([]observer.LoggedEntry, 5) for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { 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}, ""}, } // 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...) expected := make([]observer.LoggedEntry, 5) for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { 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...) expected := make([]observer.LoggedEntry, 5) for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { 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...) expected := make([]observer.LoggedEntry, 5) for i, lvl := range []zapcore.Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel} { 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") } }) } zap-1.26.0/time.go000066400000000000000000000023261450066650600136720ustar00rootroot00000000000000// 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) } zap-1.26.0/time_test.go000066400000000000000000000030311450066650600147230ustar00rootroot00000000000000// 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) } } zap-1.26.0/tools/000077500000000000000000000000001450066650600135425ustar00rootroot00000000000000zap-1.26.0/tools/go.mod000066400000000000000000000004151450066650600146500ustar00rootroot00000000000000module go.uber.org/zap/tools require golang.org/x/vuln v1.0.1 require ( golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect ) go 1.20 zap-1.26.0/tools/go.sum000066400000000000000000000021211450066650600146710ustar00rootroot00000000000000github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/vuln v1.0.1 h1:KUas02EjQK5LTuIx1OylBQdKKZ9jeugs+HiqO5HormU= golang.org/x/vuln v1.0.1/go.mod h1:bb2hMwln/tqxg32BNY4CcxHWtHXuYa3SbIBmtsyjxtM= zap-1.26.0/tools/tools.go000066400000000000000000000023521450066650600152330ustar00rootroot00000000000000// Copyright (c) 2019 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 tools // +build tools package tools import ( // Tools we use during development. _ "golang.org/x/vuln/cmd/govulncheck" ) zap-1.26.0/writer.go000066400000000000000000000067641450066650600142620ustar00rootroot00000000000000// 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...)) } zap-1.26.0/writer_test.go000066400000000000000000000156621450066650600153160ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/000077500000000000000000000000001450066650600140455ustar00rootroot00000000000000zap-1.26.0/zapcore/buffered_write_syncer.go000066400000000000000000000137341450066650600207630ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/buffered_write_syncer_bench_test.go000066400000000000000000000034341450066650600231550ustar00rootroot00000000000000// 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) } } }) }) } zap-1.26.0/zapcore/buffered_write_syncer_test.go000066400000000000000000000117051450066650600220160ustar00rootroot00000000000000// 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") }) } zap-1.26.0/zapcore/clock.go000066400000000000000000000034641450066650600154760ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/clock_test.go000066400000000000000000000027211450066650600165300ustar00rootroot00000000000000// 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 } } } zap-1.26.0/zapcore/console_encoder.go000066400000000000000000000111211450066650600175310ustar00rootroot00000000000000// 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 { 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) } } zap-1.26.0/zapcore/console_encoder_bench_test.go000066400000000000000000000033351450066650600217370ustar00rootroot00000000000000// 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/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() } }) } zap-1.26.0/zapcore/console_encoder_test.go000066400000000000000000000052601450066650600205770ustar00rootroot00000000000000// 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" "github.com/stretchr/testify/assert" . "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 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 } zap-1.26.0/zapcore/core.go000066400000000000000000000073521450066650600153330ustar00rootroot00000000000000// 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, } } zap-1.26.0/zapcore/core_test.go000066400000000000000000000111651450066650600163670ustar00rootroot00000000000000// 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" . "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.") } zap-1.26.0/zapcore/doc.go000066400000000000000000000025451450066650600151470ustar00rootroot00000000000000// 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" zap-1.26.0/zapcore/encoder.go000066400000000000000000000376221450066650600160250ustar00rootroot00000000000000// 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. 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. 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. 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. 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. 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) } zap-1.26.0/zapcore/encoder_test.go000066400000000000000000000574471450066650600170730ustar00rootroot00000000000000// 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" . "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...) } zap-1.26.0/zapcore/entry.go000066400000000000000000000207371450066650600155460ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/entry_ext_test.go000066400000000000000000000036511450066650600174610ustar00rootroot00000000000000// 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.") } zap-1.26.0/zapcore/entry_test.go000066400000000000000000000103731450066650600166000ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/error.go000066400000000000000000000101161450066650600155240ustar00rootroot00000000000000// 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 "". The likeliest causes are a // error that fails to guard against nil or a nil pointer for a // value receiver, and in either case, "" is a nice result. if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { enc.AddString(key, "") 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) } zap-1.26.0/zapcore/error_test.go000066400000000000000000000122521450066650600165660ustar00rootroot00000000000000// 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" "go.uber.org/zap/zapcore" . "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(zapcore.ObjectMarshaler) error { return enc.Err } zap-1.26.0/zapcore/field.go000066400000000000000000000173111450066650600154620ustar00rootroot00000000000000// 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 complex128. 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 "". The likeliest causes are a // Stringer that fails to guard against nil or a nil pointer for a // value receiver, and in either case, "" is a nice result. if v := reflect.ValueOf(stringer); v.Kind() == reflect.Ptr && v.IsNil() { enc.AddString(key, "") return } retErr = fmt.Errorf("PANIC=%v", err) } }() enc.AddString(key, stringer.(fmt.Stringer).String()) return nil } zap-1.26.0/zapcore/field_test.go000066400000000000000000000232201450066650600165150ustar00rootroot00000000000000// 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" . "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") } else { 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="}, {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: ""}, {t: StringerType, iface: (*users)(nil), want: ""}, {t: ErrorType, iface: (*errObj)(nil), want: ""}, } 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) } } zap-1.26.0/zapcore/hook.go000066400000000000000000000050251450066650600153360ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/hook_test.go000066400000000000000000000050131450066650600163720ustar00rootroot00000000000000// 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/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.") } } } zap-1.26.0/zapcore/increase_level.go000066400000000000000000000047221450066650600173610ustar00rootroot00000000000000// 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() } zap-1.26.0/zapcore/increase_level_test.go000066400000000000000000000073061450066650600204210ustar00rootroot00000000000000// 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" . "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") } }) } } zap-1.26.0/zapcore/json_encoder.go000066400000000000000000000404041450066650600170460ustar00rootroot00000000000000// 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 != "" { 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:]) } zap-1.26.0/zapcore/json_encoder_bench_test.go000066400000000000000000000070731450066650600212510ustar00rootroot00000000000000// 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" . "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) } } }) } zap-1.26.0/zapcore/json_encoder_impl_test.go000066400000000000000000000562731450066650600211410ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/json_encoder_test.go000066400000000000000000000162461450066650600201140ustar00rootroot00000000000000// 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}, }, }), }, }, } 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() }) } } zap-1.26.0/zapcore/lazy_with.go000066400000000000000000000034001450066650600164030ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/lazy_with_test.go000066400000000000000000000116531450066650600174530ustar00rootroot00000000000000// 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...) }) } } zap-1.26.0/zapcore/leak_test.go000066400000000000000000000023331450066650600163500ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/level.go000066400000000000000000000145601450066650600155110ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/level_strings.go000066400000000000000000000034141450066650600172560ustar00rootroot00000000000000// 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()) } } zap-1.26.0/zapcore/level_strings_test.go000066400000000000000000000031041450066650600203110ustar00rootroot00000000000000// 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.") } zap-1.26.0/zapcore/level_test.go000066400000000000000000000162351450066650600165510ustar00rootroot00000000000000// 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.") }) } } zap-1.26.0/zapcore/marshaler.go000066400000000000000000000050001450066650600163450ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/memory_encoder.go000066400000000000000000000164021450066650600174060ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/memory_encoder_test.go000066400000000000000000000260141450066650600204450ustar00rootroot00000000000000// 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.", ) } zap-1.26.0/zapcore/reflected_encoder.go000066400000000000000000000032011450066650600200240ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/sampler.go000066400000000000000000000152001450066650600160350ustar00rootroot00000000000000// 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) } zap-1.26.0/zapcore/sampler_bench_test.go000066400000000000000000000133661450066650600202460ustar00rootroot00000000000000// 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" . "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) }) } } zap-1.26.0/zapcore/sampler_test.go000066400000000000000000000206401450066650600171000ustar00rootroot00000000000000// 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" . "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") } zap-1.26.0/zapcore/tee.go000066400000000000000000000047671450066650600151670ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/tee_logger_bench_test.go000066400000000000000000000037351450066650600207160ustar00rootroot00000000000000// 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" . "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) } }) }) } zap-1.26.0/zapcore/tee_test.go000066400000000000000000000131171450066650600162130ustar00rootroot00000000000000// 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" . "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.") } zap-1.26.0/zapcore/write_syncer.go000066400000000000000000000064531450066650600171210ustar00rootroot00000000000000// 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 } zap-1.26.0/zapcore/write_syncer_bench_test.go000066400000000000000000000054551450066650600213200ustar00rootroot00000000000000// 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) } } }) }) } zap-1.26.0/zapcore/write_syncer_test.go000066400000000000000000000114201450066650600201460ustar00rootroot00000000000000// 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.") } zap-1.26.0/zapgrpc/000077500000000000000000000000001450066650600140505ustar00rootroot00000000000000zap-1.26.0/zapgrpc/internal/000077500000000000000000000000001450066650600156645ustar00rootroot00000000000000zap-1.26.0/zapgrpc/internal/test/000077500000000000000000000000001450066650600166435ustar00rootroot00000000000000zap-1.26.0/zapgrpc/internal/test/README.md000066400000000000000000000001451450066650600201220ustar00rootroot00000000000000This submodule exists to test zapgrpc against grpc-go without adding a dependency on grpc-go to Zap. zap-1.26.0/zapgrpc/internal/test/go.mod000066400000000000000000000010411450066650600177450ustar00rootroot00000000000000module go.uber.org/zap/zapgrpc/internal/test go 1.17 require ( github.com/stretchr/testify v1.8.1 go.uber.org/zap v1.16.0 google.golang.org/grpc v1.42.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect go.uber.org/multierr v1.10.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) replace go.uber.org/zap => ../../.. zap-1.26.0/zapgrpc/internal/test/go.sum000066400000000000000000000357331450066650600200110ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= zap-1.26.0/zapgrpc/internal/test/grpc.go000066400000000000000000000024711450066650600201310ustar00rootroot00000000000000// 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 grpc tests Zap's zapgrpc package without requring a dependency on // grpc from Zap itself. package grpc // This file exists to treat this directory as a valid package with at least // one non-test file. zap-1.26.0/zapgrpc/internal/test/grpc_test.go000066400000000000000000000034751450066650600211750ustar00rootroot00000000000000// 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 grpc import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zapgrpc" "go.uber.org/zap/zaptest/observer" "google.golang.org/grpc/grpclog" ) func TestLoggerV2(t *testing.T) { core, observedLogs := observer.New(zapcore.InfoLevel) zlog := zap.New(core) grpclog.SetLoggerV2(zapgrpc.NewLogger(zlog)) grpclog.Info("hello from grpc") logs := observedLogs.TakeAll() require.Len(t, logs, 1, "Expected one log entry.") entry := logs[0] assert.Equal(t, zapcore.InfoLevel, entry.Level, "Log entry level did not match.") assert.Equal(t, "hello from grpc", entry.Message, "Log entry message did not match.") } zap-1.26.0/zapgrpc/zapgrpc.go000066400000000000000000000150031450066650600160440ustar00rootroot00000000000000// 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 ) var ( // _grpcToZapLevel maps gRPC log levels to zap log levels. // See https://pkg.go.dev/go.uber.org/zap@v1.16.0/zapcore#Level _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] } zap-1.26.0/zapgrpc/zapgrpc_test.go000066400000000000000000000163271450066650600171150ustar00rootroot00000000000000// 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) } zap-1.26.0/zapio/000077500000000000000000000000001450066650600135245ustar00rootroot00000000000000zap-1.26.0/zapio/example_test.go000066400000000000000000000030731450066650600165500ustar00rootroot00000000000000// 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"} } zap-1.26.0/zapio/writer.go000066400000000000000000000107451450066650600153760ustar00rootroot00000000000000// 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() } } zap-1.26.0/zapio/writer_test.go000066400000000000000000000141541450066650600164330ustar00rootroot00000000000000// 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 } zap-1.26.0/zaptest/000077500000000000000000000000001450066650600140745ustar00rootroot00000000000000zap-1.26.0/zaptest/doc.go000066400000000000000000000023371450066650600151750ustar00rootroot00000000000000// 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" zap-1.26.0/zaptest/logger.go000066400000000000000000000076271450066650600157160ustar00rootroot00000000000000// 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 } 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 } 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 } func (w testingWriter) Sync() error { return nil } zap-1.26.0/zaptest/logger_test.go000066400000000000000000000122561450066650600167470ustar00rootroot00000000000000// 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) } zap-1.26.0/zaptest/observer/000077500000000000000000000000001450066650600157235ustar00rootroot00000000000000zap-1.26.0/zaptest/observer/logged_entry.go000066400000000000000000000030741450066650600207400ustar00rootroot00000000000000// 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 } zap-1.26.0/zaptest/observer/logged_entry_test.go000066400000000000000000000043571450066650600220040ustar00rootroot00000000000000// 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()) }) } } zap-1.26.0/zaptest/observer/observer.go000066400000000000000000000131351450066650600201040ustar00rootroot00000000000000// 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 } zap-1.26.0/zaptest/observer/observer_test.go000066400000000000000000000171751450066650600211530ustar00rootroot00000000000000// 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" . "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) } } zap-1.26.0/zaptest/testingt.go000066400000000000000000000035041450066650600162660ustar00rootroot00000000000000// 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. zap-1.26.0/zaptest/testingt_test.go000066400000000000000000000025541450066650600173310ustar00rootroot00000000000000// 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) zap-1.26.0/zaptest/timeout.go000066400000000000000000000033771450066650600161230ustar00rootroot00000000000000// 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) } zap-1.26.0/zaptest/timeout_test.go000066400000000000000000000031411450066650600171470ustar00rootroot00000000000000// 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.") } zap-1.26.0/zaptest/writer.go000066400000000000000000000035311450066650600157410ustar00rootroot00000000000000// 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 ) zap-1.26.0/zaptest/writer_test.go000066400000000000000000000046551450066650600170100ustar00rootroot00000000000000// 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.") }