dev-api: add developer portal, OpenAPI spec, and Flatenbadet case study
- New /developers/ page with API docs, SDKs, pricing, use cases - OpenAPI 3.0 spec for Orders, Missions, Photos, Analytics - Case study: Glasskiosken i Flatenbadet — complete ROI analysis - Updated /order/ with recurring missions and frequency dropdown
@@ -0,0 +1,2 @@
|
||||
pino.d.ts
|
||||
test/types/pino.test-d.ts
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"standard"
|
||||
],
|
||||
"rules": {
|
||||
"no-var": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
open-pull-requests-limit: 10
|
||||
@@ -0,0 +1,61 @@
|
||||
name: Benchmarks
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
benchmark_current:
|
||||
name: benchmark current
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
persist-credentials: false
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install Modules
|
||||
run: npm i --ignore-scripts
|
||||
- name: Run Benchmark
|
||||
run: npm run bench | tee current.txt
|
||||
- name: Upload Current Results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: current
|
||||
path: current.txt
|
||||
|
||||
benchmark_branch:
|
||||
name: benchmark branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install Modules
|
||||
run: npm i --ignore-scripts
|
||||
- name: Run Benchmark
|
||||
run: npm run bench | tee branch.txt
|
||||
- name: Upload Branch Results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: branch
|
||||
path: branch.txt
|
||||
@@ -0,0 +1,86 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- '*.md'
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Dependency review
|
||||
uses: actions/dependency-review-action@v3
|
||||
|
||||
test:
|
||||
name: ${{ matrix.node-version }} ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macOS-latest, windows-latest, ubuntu-latest]
|
||||
node-version: [14, 16, 18, 19, 20, 21]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
node-version: 14
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm i --ignore-scripts
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test-ci
|
||||
|
||||
- name: Run smoke test
|
||||
if: >
|
||||
matrix.os != 'windows-latest' &&
|
||||
matrix.node-version > 14
|
||||
run: npm run test:smoke
|
||||
|
||||
automerge:
|
||||
name: Automerge Dependabot PRs
|
||||
if: >
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.user.login == 'dependabot[bot]'
|
||||
needs: test
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fastify/github-action-merge-dependabot@v3
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
exclude: 'sonic-boom,pino-std-serializers,quick-format-unescaped,fast-redact'
|
||||
@@ -0,0 +1,30 @@
|
||||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jsumners/lock-threads@b27edac0ac998d42b2815e122b6c24b32b568321
|
||||
with:
|
||||
log-output: true
|
||||
issue-inactive-days: '30'
|
||||
issue-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
pr-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
@@ -0,0 +1,99 @@
|
||||
name: package-manager-ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- package-manager-load
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
pnpm:
|
||||
name: pnpm package manager on ${{ matrix.node-version }} ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
node-version: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Use pnpm
|
||||
uses: pnpm/action-setup@v2.4.0
|
||||
with:
|
||||
version: ^6.0.0
|
||||
- name: Install dependancies
|
||||
run: pnpm install
|
||||
- name: Tests
|
||||
run: pnpm run test-ci-pnpm
|
||||
|
||||
yarn:
|
||||
name: yarn package manager on ${{ matrix.node-version }} ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
node-version: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Use yarn
|
||||
run: |
|
||||
yarn set version berry && yarn set version 2
|
||||
echo "nodeLinker: node-modules" >> .yarnrc.yml
|
||||
# see https://github.com/yarnpkg/berry/issues/2935#issuecomment-911299992
|
||||
yarn add --dev typescript@~4.3.2
|
||||
yarn install
|
||||
env:
|
||||
# needed due the yarn.lock file in pino's .gitignore
|
||||
YARN_ENABLE_IMMUTABLE_INSTALLS: false
|
||||
- name: Tests
|
||||
run: yarn run test-ci
|
||||
|
||||
yarn-pnp:
|
||||
name: yarn-pnp package manager on ${{ matrix.node-version }} ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
node-version: [16]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Use yarn
|
||||
run: |
|
||||
yarn set version berry
|
||||
echo 'nodeLinker: pnp
|
||||
packageExtensions:
|
||||
debug@*:
|
||||
dependencies:
|
||||
supports-color: "*"
|
||||
treport@*:
|
||||
dependencies:
|
||||
tap-yaml: "*"
|
||||
' >> .yarnrc.yml
|
||||
yarn add --dev typescript@~4.3.2
|
||||
yarn install
|
||||
yarn add --dev transport@link:./test/fixtures/transport
|
||||
env:
|
||||
# needed due the yarn.lock file in pino's .gitignore
|
||||
YARN_ENABLE_IMMUTABLE_INSTALLS: false
|
||||
- name: Tests
|
||||
run: yarn run test-ci-yarn-pnp
|
||||
@@ -0,0 +1,23 @@
|
||||
name: PR Target Check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment:
|
||||
if: ${{ github.base_ref != "master" }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '⚠️ This pull request does not target the master branch.'
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
*
|
||||
@@ -0,0 +1,8 @@
|
||||
coverage: true
|
||||
timeout: 480
|
||||
check-coverage: false
|
||||
|
||||
reporter: terse
|
||||
|
||||
files:
|
||||
- 'test/**/*.test.js'
|
||||
@@ -0,0 +1 @@
|
||||
getpino.io
|
||||
@@ -0,0 +1,26 @@
|
||||
# Pino is an OPEN Open Source Project
|
||||
|
||||
## What?
|
||||
|
||||
Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
|
||||
|
||||
## Rules
|
||||
|
||||
There are a few basic ground-rules for contributors:
|
||||
|
||||
1. **No `--force` pushes** on `master` or modifying the Git history in any way after a PR has been merged.
|
||||
1. **Non-master branches** ought to be used for ongoing work.
|
||||
1. **Non-trivial changes** ought to be subject to an **internal pull-request** to solicit feedback from other contributors.
|
||||
1. All pull-requests for new features **must** target the `master` branch. PRs to fix bugs in LTS releases are also allowed.
|
||||
1. Contributors should attempt to adhere to the prevailing code-style.
|
||||
1. 100% code coverage
|
||||
|
||||
## Releases
|
||||
|
||||
Declaring formal releases remains the prerogative of the project maintainer.
|
||||
|
||||
## Changes to this arrangement
|
||||
|
||||
This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
|
||||
|
||||
-----------------------------------------
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2024 Matteo Collina, David Mark Clements and the Pino contributors listed at https://github.com/pinojs/pino#the-team and in the README file.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,160 @@
|
||||

|
||||
|
||||
# pino
|
||||
[](https://www.npmjs.com/package/pino)
|
||||
[](https://github.com/pinojs/pino/actions)
|
||||
[](https://standardjs.com/)
|
||||
|
||||
[Very low overhead](#low-overhead) Node.js logger.
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Benchmarks ⇗](/docs/benchmarks.md)
|
||||
* [API ⇗](/docs/api.md)
|
||||
* [Browser API ⇗](/docs/browser.md)
|
||||
* [Redaction ⇗](/docs/redaction.md)
|
||||
* [Child Loggers ⇗](/docs/child-loggers.md)
|
||||
* [Transports ⇗](/docs/transports.md)
|
||||
* [Web Frameworks ⇗](/docs/web.md)
|
||||
* [Pretty Printing ⇗](/docs/pretty.md)
|
||||
* [Asynchronous Logging ⇗](/docs/asynchronous.md)
|
||||
* [Ecosystem ⇗](/docs/ecosystem.md)
|
||||
* [Help ⇗](/docs/help.md)
|
||||
* [Long Term Support Policy ⇗](/docs/lts.md)
|
||||
|
||||
## Install
|
||||
|
||||
Using NPM:
|
||||
```
|
||||
$ npm install pino
|
||||
```
|
||||
|
||||
Using YARN:
|
||||
```
|
||||
$ yarn add pino
|
||||
```
|
||||
|
||||
If you would like to install pino v6, refer to https://github.com/pinojs/pino/tree/v6.x.
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
|
||||
logger.info('hello world')
|
||||
|
||||
const child = logger.child({ a: 'property' })
|
||||
child.info('hello child!')
|
||||
```
|
||||
|
||||
This produces:
|
||||
|
||||
```
|
||||
{"level":30,"time":1531171074631,"msg":"hello world","pid":657,"hostname":"Davids-MBP-3.fritz.box"}
|
||||
{"level":30,"time":1531171082399,"msg":"hello child!","pid":657,"hostname":"Davids-MBP-3.fritz.box","a":"property"}
|
||||
```
|
||||
|
||||
For using Pino with a web framework see:
|
||||
|
||||
* [Pino with Fastify](docs/web.md#fastify)
|
||||
* [Pino with Express](docs/web.md#express)
|
||||
* [Pino with Hapi](docs/web.md#hapi)
|
||||
* [Pino with Restify](docs/web.md#restify)
|
||||
* [Pino with Koa](docs/web.md#koa)
|
||||
* [Pino with Node core `http`](docs/web.md#http)
|
||||
* [Pino with Nest](docs/web.md#nest)
|
||||
|
||||
|
||||
<a name="essentials"></a>
|
||||
## Essentials
|
||||
|
||||
### Development Formatting
|
||||
|
||||
The [`pino-pretty`](https://github.com/pinojs/pino-pretty) module can be used to
|
||||
format logs during development:
|
||||
|
||||

|
||||
|
||||
### Transports & Log Processing
|
||||
|
||||
Due to Node's single-threaded event-loop, it's highly recommended that sending,
|
||||
alert triggering, reformatting, and all forms of log processing
|
||||
are conducted in a separate process or thread.
|
||||
|
||||
In Pino terminology, we call all log processors "transports" and recommend that the
|
||||
transports be run in a worker thread using our `pino.transport` API.
|
||||
|
||||
For more details see our [Transports⇗](docs/transports.md) document.
|
||||
|
||||
### Low overhead
|
||||
|
||||
Using minimum resources for logging is very important. Log messages
|
||||
tend to get added over time and this can lead to a throttling effect
|
||||
on applications – such as reduced requests per second.
|
||||
|
||||
In many cases, Pino is over 5x faster than alternatives.
|
||||
|
||||
See the [Benchmarks](docs/benchmarks.md) document for comparisons.
|
||||
|
||||
### Bundling support
|
||||
|
||||
Pino supports being bundled using tools like webpack or esbuild.
|
||||
|
||||
See [Bundling](docs/bundling.md) document for more information.
|
||||
|
||||
<a name="team"></a>
|
||||
## The Team
|
||||
|
||||
### Matteo Collina
|
||||
|
||||
<https://github.com/mcollina>
|
||||
|
||||
<https://www.npmjs.com/~matteo.collina>
|
||||
|
||||
<https://twitter.com/matteocollina>
|
||||
|
||||
### David Mark Clements
|
||||
|
||||
<https://github.com/davidmarkclements>
|
||||
|
||||
<https://www.npmjs.com/~davidmarkclements>
|
||||
|
||||
<https://twitter.com/davidmarkclem>
|
||||
|
||||
### James Sumners
|
||||
|
||||
<https://github.com/jsumners>
|
||||
|
||||
<https://www.npmjs.com/~jsumners>
|
||||
|
||||
<https://twitter.com/jsumners79>
|
||||
|
||||
### Thomas Watson Steen
|
||||
|
||||
<https://github.com/watson>
|
||||
|
||||
<https://www.npmjs.com/~watson>
|
||||
|
||||
<https://twitter.com/wa7son>
|
||||
|
||||
## Contributing
|
||||
|
||||
Pino is an **OPEN Open Source Project**. This means that:
|
||||
|
||||
> Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.
|
||||
|
||||
See the [CONTRIBUTING.md](https://github.com/pinojs/pino/blob/master/CONTRIBUTING.md) file for more details.
|
||||
|
||||
<a name="acknowledgments"></a>
|
||||
## Acknowledgments
|
||||
|
||||
This project was kindly sponsored by [nearForm](https://nearform.com).
|
||||
|
||||
Logo and identity designed by Cosmic Fox Design: https://www.behance.net/cosmicfox.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under [MIT](./LICENSE).
|
||||
|
||||
[elasticsearch]: https://www.elastic.co/products/elasticsearch
|
||||
[kibana]: https://www.elastic.co/products/kibana
|
||||
@@ -0,0 +1,68 @@
|
||||
# Security Policy
|
||||
|
||||
This document describes the management of vulnerabilities for the
|
||||
Pino project and all modules within the Pino organization.
|
||||
|
||||
## Reporting vulnerabilities
|
||||
|
||||
Individuals who find potential vulnerabilities in Pino are invited
|
||||
to report them via email at matteo.collina@gmail.com.
|
||||
|
||||
### Strict measures when reporting vulnerabilities
|
||||
|
||||
Avoid creating new "informative" reports. Only create new
|
||||
report a potential vulnerability if you are absolutely sure this
|
||||
should be tagged as an actual vulnerability. Be careful on the maintainers time.
|
||||
|
||||
## Handling vulnerability reports
|
||||
|
||||
When a potential vulnerability is reported, the following actions are taken:
|
||||
|
||||
### Triage
|
||||
|
||||
**Delay:** 5 business days
|
||||
|
||||
Within 5 business days, a member of the security team provides a first answer to the
|
||||
individual who submitted the potential vulnerability. The possible responses
|
||||
can be:
|
||||
|
||||
* Acceptance: what was reported is considered as a new vulnerability
|
||||
* Rejection: what was reported is not considered as a new vulnerability
|
||||
* Need more information: the security team needs more information in order to evaluate what was reported.
|
||||
|
||||
Triaging should include updating issue fields:
|
||||
* Asset - set/create the module affected by the report
|
||||
* Severity - TBD, currently left empty
|
||||
|
||||
### Correction follow-up
|
||||
|
||||
**Delay:** 90 days
|
||||
|
||||
When a vulnerability is confirmed, a member of the security team volunteers to follow
|
||||
up on this report.
|
||||
|
||||
With the help of the individual who reported the vulnerability, they contact
|
||||
the maintainers of the vulnerable package to make them aware of the
|
||||
vulnerability. The maintainers can be invited as participants to the reported issue.
|
||||
|
||||
With the package maintainer, they define a release date for the publication
|
||||
of the vulnerability. Ideally, this release date should not happen before
|
||||
the package has been patched.
|
||||
|
||||
The report's vulnerable versions upper limit should be set to:
|
||||
* `*` if there is no fixed version available by the time of publishing the report.
|
||||
* the last vulnerable version. For example: `<=1.2.3` if a fix exists in `1.2.4`
|
||||
|
||||
### Publication
|
||||
|
||||
**Delay:** 90 days
|
||||
|
||||
Within 90 days after the triage date, the vulnerability must be made public.
|
||||
|
||||
**Severity**: Vulnerability severity is assessed using [CVSS v.3](https://www.first.org/cvss/user-guide).
|
||||
|
||||
If the package maintainer is actively developing a patch, an additional delay
|
||||
can be added with the approval of the security team and the individual who
|
||||
reported the vulnerability.
|
||||
|
||||
At this point, a CVE will be requested by the team.
|
||||
@@ -0,0 +1,95 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const winston = require('winston')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const loglevel = require('./utils/wrap-log-level')(dest)
|
||||
const plogNodeStream = pino(dest)
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', minLength: 4096 }))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null'))
|
||||
|
||||
process.env.DEBUG = 'dlog'
|
||||
const debug = require('debug')
|
||||
const dlog = debug('dlog')
|
||||
dlog.log = function (s) { dest.write(s) }
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const chill = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Stream({
|
||||
stream: fs.createWriteStream('/dev/null')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const run = bench([
|
||||
function benchBunyan (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinston (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log('info', 'hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBole (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchDebug (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
dlog('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchLogLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
loglevel.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPino (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLength (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStream (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest).child({ a: 'property' }).child({ sub: 'child' })
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null')).child({ a: 'property' }).child({ sub: 'child' })
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
.child({ a: 'property' })
|
||||
.child({ sub: 'child' })
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
}).child({ a: 'property' }).child({ sub: 'child' })
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,73 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest)
|
||||
const plogDest = pino(pino.destination(('/dev/null')))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanCreation (cb) {
|
||||
const child = blog.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleCreation (cb) {
|
||||
const child = bole('child')
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoCreation (cb) {
|
||||
const child = plogDest.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthCreation (cb) {
|
||||
const child = plogMinLength.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamCreation (cb) {
|
||||
const child = plogNodeStream.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoCreationWithOption (cb) {
|
||||
const child = plogDest.child({ a: 'property' }, { redact: [] })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,62 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')('child')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest).child({ a: 'property' })
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null')).child({ a: 'property' })
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
}).child({ a: 'property' })
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const winston = require('winston')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest)
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
delete require.cache[require.resolve('../')]
|
||||
|
||||
const loglevel = require('./utils/wrap-log-level')(dest)
|
||||
|
||||
const deep = Object.assign({}, require('../package.json'), { level: 'info' })
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const chill = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Stream({
|
||||
stream: fs.createWriteStream('/dev/null')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinstonDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchLogLevelDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
loglevel.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
const formatters = {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
},
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
log (obj) {
|
||||
return { foo: 'bar', ...obj }
|
||||
}
|
||||
}
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
delete require.cache[require.resolve('../')]
|
||||
const pinoNoFormatters = require('../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const pinoFormatters = require('../')({ formatters }, pino.destination('/dev/null'))
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchPinoNoFormatters (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
pinoNoFormatters.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoFormatters (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
pinoFormatters.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,67 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
|
||||
const base = pino(pino.destination('/dev/null'))
|
||||
const baseCl = pino({
|
||||
customLevels: { foo: 31 }
|
||||
}, pino.destination('/dev/null'))
|
||||
const child = base.child({})
|
||||
const childCl = base.child({
|
||||
customLevels: { foo: 31 }
|
||||
})
|
||||
const childOfBaseCl = baseCl.child({})
|
||||
|
||||
const max = 100
|
||||
|
||||
const run = bench([
|
||||
function benchPinoNoCustomLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
base.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoCustomLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
baseCl.foo({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchChildNoCustomLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildCustomLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
childCl.foo({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildInheritedCustomLevel (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
childOfBaseCl.foo({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildCreation (cb) {
|
||||
const child = base.child({})
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildCreationCustomLevel (cb) {
|
||||
const child = base.child({
|
||||
customLevels: { foo: 31 }
|
||||
})
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.foo({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,76 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plog = pino(dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogDest = require('../../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogAsync = require('../../')(pino.destination({ dest: '/dev/null', sync: false }))
|
||||
const deep = require('../../package.json')
|
||||
deep.deep = JSON.parse(JSON.stringify(deep))
|
||||
deep.deep.deep = JSON.parse(JSON.stringify(deep))
|
||||
const longStr = JSON.stringify(deep)
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchPinoLongString (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestLongString (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncLongString (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncDeepObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info(deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 1000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,182 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plog = pino(dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogDest = require('../../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogAsync = require('../../')(pino.destination({ dest: '/dev/null', sync: false }))
|
||||
const plogChild = plog.child({ a: 'property' })
|
||||
const plogDestChild = plogDest.child({ a: 'property' })
|
||||
const plogAsyncChild = plogAsync.child({ a: 'property' })
|
||||
const plogChildChild = plog.child({ a: 'property' }).child({ sub: 'child' })
|
||||
const plogDestChildChild = plogDest.child({ a: 'property' }).child({ sub: 'child' })
|
||||
const plogAsyncChildChild = plogAsync.child({ a: 'property' }).child({ sub: 'child' })
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchPino (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDest (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoExtreme (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDestChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsyncChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDestChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsyncChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildCreation (cb) {
|
||||
const child = plog.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestChildCreation (cb) {
|
||||
const child = plogDest.child({ a: 'property' })
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncMulti (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoDestInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,75 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
|
||||
const base = pino(pino.destination('/dev/null'))
|
||||
const child = base.child({})
|
||||
const childChild = child.child({})
|
||||
const childChildChild = childChild.child({})
|
||||
const childChildChildChild = childChildChild.child({})
|
||||
const child2 = base.child({})
|
||||
const baseSerializers = pino(pino.destination('/dev/null'))
|
||||
const baseSerializersChild = baseSerializers.child({})
|
||||
const baseSerializersChildSerializers = baseSerializers.child({})
|
||||
|
||||
const max = 100
|
||||
|
||||
const run = bench([
|
||||
function benchPinoBase (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
base.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
child.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
childChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
childChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChildChildChildChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
childChildChildChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoChild2 (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
child2.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoBaseSerializers (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
baseSerializers.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoBaseSerializersChild (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
baseSerializersChild.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoBaseSerializersChildSerializers (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
baseSerializersChildSerializers.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,86 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../../')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plog = pino(dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogAsync = require('../../')(pino.destination({ dest: '/dev/null', sync: false }))
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogUnsafe = require('../../')({ safe: false }, dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogUnsafeAsync = require('../../')(
|
||||
{ safe: false },
|
||||
pino.destination({ dest: '/dev/null', sync: false })
|
||||
)
|
||||
const plogRedact = pino({ redact: ['a.b.c'] }, dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogAsyncRedact = require('../../')(
|
||||
{ redact: ['a.b.c'] },
|
||||
pino.destination({ dest: '/dev/null', sync: false })
|
||||
)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogUnsafeRedact = require('../../')({ redact: ['a.b.c'], safe: false }, dest)
|
||||
delete require.cache[require.resolve('../../')]
|
||||
const plogUnsafeAsyncRedact = require('../../')(
|
||||
{ redact: ['a.b.c'], safe: false },
|
||||
pino.destination({ dest: '/dev/null', sync: false })
|
||||
)
|
||||
|
||||
const max = 10
|
||||
|
||||
// note that "redact me." is the same amount of bytes as the censor: "[Redacted]"
|
||||
|
||||
const run = bench([
|
||||
function benchPinoNoRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plog.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogRedact.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoUnsafeNoRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogUnsafe.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoUnsafeRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogUnsafeRedact.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncNoRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsync.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoAsyncRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogAsyncRedact.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoUnsafeAsyncNoRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogUnsafeAsync.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoUnsafeAsyncRedact (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogUnsafeAsyncRedact.info({ a: { b: { c: 'redact me.', d: 'leave me' } } })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,81 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const winston = require('winston')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest)
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
|
||||
const crypto = require('crypto')
|
||||
|
||||
const longStr = crypto.randomBytes(2000).toString()
|
||||
|
||||
const max = 10
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const chill = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Stream({
|
||||
stream: fs.createWriteStream('/dev/null')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const run = bench([
|
||||
function benchBunyan (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinston (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBole (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPino (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLength (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStream (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info(longStr)
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 1000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,193 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const winston = require('winston')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const plogNodeStream = pino(dest)
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
delete require.cache[require.resolve('../')]
|
||||
|
||||
const deep = require('../package.json')
|
||||
deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
||||
deep.deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
||||
deep.deep.deep.deep = Object.assign({}, JSON.parse(JSON.stringify(deep)))
|
||||
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
|
||||
const chill = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Stream({
|
||||
stream: fs.createWriteStream('/dev/null')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinstonInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log('info', 'hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamInterpolate (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info('hello %s', 'world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBunyanInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
|
||||
function benchWinstonInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log('info', 'hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamInterpolateAll (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info('hello %s %j %d', 'world', { obj: true }, 4)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBunyanInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinstonInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log('info', 'hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamInterpolateExtra (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info('hello %s %j %d', 'world', { obj: true }, 4, { another: 'obj' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBunyanInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinstonInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.log('info', 'hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamInterpolateDeep (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info('hello %j', deep)
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,98 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const bunyan = require('bunyan')
|
||||
const pino = require('../')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
|
||||
const tenStreams = [
|
||||
{ stream: dest },
|
||||
{ stream: dest },
|
||||
{ stream: dest },
|
||||
{ stream: dest },
|
||||
{ stream: dest },
|
||||
{ level: 'debug', stream: dest },
|
||||
{ level: 'debug', stream: dest },
|
||||
{ level: 'trace', stream: dest },
|
||||
{ level: 'warn', stream: dest },
|
||||
{ level: 'fatal', stream: dest }
|
||||
]
|
||||
const pinomsTen = pino({ level: 'debug' }, pino.multistream(tenStreams))
|
||||
|
||||
const fourStreams = [
|
||||
{ stream: dest },
|
||||
{ stream: dest },
|
||||
{ level: 'debug', stream: dest },
|
||||
{ level: 'trace', stream: dest }
|
||||
]
|
||||
const pinomsFour = pino({ level: 'debug' }, pino.multistream(fourStreams))
|
||||
|
||||
const pinomsOne = pino({ level: 'info' }, pino.multistream(dest))
|
||||
const blogOne = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{ stream: dest }]
|
||||
})
|
||||
|
||||
const blogTen = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: tenStreams
|
||||
})
|
||||
const blogFour = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: fourStreams
|
||||
})
|
||||
|
||||
const max = 10
|
||||
const run = bench([
|
||||
function benchBunyanTen (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
blogTen.info('hello world')
|
||||
blogTen.debug('hello world')
|
||||
blogTen.trace('hello world')
|
||||
blogTen.warn('hello world')
|
||||
blogTen.fatal('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMSTen (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
pinomsTen.info('hello world')
|
||||
pinomsTen.debug('hello world')
|
||||
pinomsTen.trace('hello world')
|
||||
pinomsTen.warn('hello world')
|
||||
pinomsTen.fatal('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBunyanFour (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
blogFour.info('hello world')
|
||||
blogFour.debug('hello world')
|
||||
blogFour.trace('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMSFour (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
pinomsFour.info('hello world')
|
||||
pinomsFour.debug('hello world')
|
||||
pinomsFour.trace('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBunyanOne (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
blogOne.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMSOne (cb) {
|
||||
for (let i = 0; i < max; i++) {
|
||||
pinomsOne.info('hello world')
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,82 @@
|
||||
'use strict'
|
||||
|
||||
const bench = require('fastbench')
|
||||
const pino = require('../')
|
||||
const bunyan = require('bunyan')
|
||||
const bole = require('bole')('bench')
|
||||
const winston = require('winston')
|
||||
const fs = require('fs')
|
||||
const dest = fs.createWriteStream('/dev/null')
|
||||
const loglevel = require('./utils/wrap-log-level')(dest)
|
||||
const plogNodeStream = pino(dest)
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogDest = require('../')(pino.destination('/dev/null'))
|
||||
delete require.cache[require.resolve('../')]
|
||||
const plogMinLength = require('../')(pino.destination({ dest: '/dev/null', sync: false, minLength: 4096 }))
|
||||
const blog = bunyan.createLogger({
|
||||
name: 'myapp',
|
||||
streams: [{
|
||||
level: 'trace',
|
||||
stream: dest
|
||||
}]
|
||||
})
|
||||
require('bole').output({
|
||||
level: 'info',
|
||||
stream: dest
|
||||
}).setFastTime(true)
|
||||
const chill = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Stream({
|
||||
stream: fs.createWriteStream('/dev/null')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
const max = 10
|
||||
|
||||
const run = bench([
|
||||
function benchBunyanObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
blog.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchWinstonObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
chill.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchBoleObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
bole.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchLogLevelObject (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
loglevel.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogDest.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoMinLengthObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogMinLength.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchPinoNodeStreamObj (cb) {
|
||||
for (var i = 0; i < max; i++) {
|
||||
plogNodeStream.info({ hello: 'world' })
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 10000)
|
||||
|
||||
run(run)
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
const { join } = require('path')
|
||||
const { execSync } = require('child_process')
|
||||
|
||||
const run = (type) => {
|
||||
process.stderr.write(`benchmarking ${type}\n`)
|
||||
return execSync(`node ${join(__dirname, 'runbench')} ${type} -q`)
|
||||
}
|
||||
|
||||
console.log(`
|
||||
# Benchmarks
|
||||
|
||||
\`pino.info('hello world')\`:
|
||||
|
||||
\`\`\`
|
||||
${run('basic')}
|
||||
\`\`\`
|
||||
|
||||
\`pino.info({'hello': 'world'})\`:
|
||||
|
||||
\`\`\`
|
||||
${run('object')}
|
||||
\`\`\`
|
||||
|
||||
\`pino.info(aBigDeeplyNestedObject)\`:
|
||||
|
||||
\`\`\`
|
||||
${run('deep-object')}
|
||||
\`\`\`
|
||||
|
||||
\`pino.info('hello %s %j %d', 'world', {obj: true}, 4, {another: 'obj'})\`:
|
||||
|
||||
For a fair comparison, [LogLevel](http://npm.im/loglevel) was extended
|
||||
to include a timestamp and [bole](http://npm.im/bole) had
|
||||
\`fastTime\` mode switched on.
|
||||
`)
|
||||
@@ -0,0 +1,138 @@
|
||||
'use strict'
|
||||
|
||||
const { type, platform, arch, release, cpus } = require('os')
|
||||
const { resolve, join } = require('path')
|
||||
const spawn = require('child_process').spawn
|
||||
const pump = require('pump')
|
||||
const split = require('split2')
|
||||
const through = require('through2')
|
||||
const steed = require('steed')
|
||||
|
||||
function usage () {
|
||||
console.log(`
|
||||
Pino Benchmarks
|
||||
|
||||
To run a benchmark, specify which to run:
|
||||
|
||||
・all ⁃ run all benchmarks (takes a while)
|
||||
・basic ⁃ log a simple string
|
||||
・object ⁃ logging a basic object
|
||||
・deep-object ⁃ logging a large object
|
||||
・multi-arg ⁃ multiple log method arguments
|
||||
・child ⁃ child from a parent
|
||||
・child-child ⁃ child from a child
|
||||
・child-creation ⁃ child constructor
|
||||
・formatters ⁃ difference between with or without formatters
|
||||
|
||||
Example:
|
||||
|
||||
node runbench basic
|
||||
`)
|
||||
}
|
||||
|
||||
if (!process.argv[2]) {
|
||||
usage()
|
||||
process.exit()
|
||||
}
|
||||
|
||||
const quiet = process.argv[3] === '-q'
|
||||
|
||||
const selectedBenchmark = process.argv[2].toLowerCase()
|
||||
const benchmarkDir = resolve(__dirname, '..')
|
||||
const benchmarks = {
|
||||
basic: 'basic.bench.js',
|
||||
object: 'object.bench.js',
|
||||
'deep-object': 'deep-object.bench.js',
|
||||
'multi-arg': 'multi-arg.bench.js',
|
||||
'long-string': 'long-string.bench.js',
|
||||
child: 'child.bench.js',
|
||||
'child-child': 'child-child.bench.js',
|
||||
'child-creation': 'child-creation.bench.js',
|
||||
formatters: 'formatters.bench.js'
|
||||
}
|
||||
|
||||
function runBenchmark (name, done) {
|
||||
const benchmarkResults = {}
|
||||
benchmarkResults[name] = {}
|
||||
|
||||
const processor = through(function (line, enc, cb) {
|
||||
const [label, time] = ('' + line).split(': ')
|
||||
const [target, iterations] = label.split('*')
|
||||
const logger = target.replace('bench', '')
|
||||
|
||||
if (!benchmarkResults[name][logger]) benchmarkResults[name][logger] = []
|
||||
|
||||
benchmarkResults[name][logger].push({
|
||||
time: time.replace('ms', ''),
|
||||
iterations: iterations.replace(':', '')
|
||||
})
|
||||
|
||||
cb()
|
||||
})
|
||||
|
||||
if (quiet === false) console.log(`Running ${name.toUpperCase()} benchmark\n`)
|
||||
|
||||
const benchmark = spawn(
|
||||
process.argv[0],
|
||||
[join(benchmarkDir, benchmarks[name])]
|
||||
)
|
||||
|
||||
if (quiet === false) {
|
||||
benchmark.stdout.pipe(process.stdout)
|
||||
}
|
||||
|
||||
pump(benchmark.stdout, split(), processor)
|
||||
|
||||
benchmark.on('exit', () => {
|
||||
console.log()
|
||||
if (done && typeof done === 'function') done(null, benchmarkResults)
|
||||
})
|
||||
}
|
||||
|
||||
function sum (arr) {
|
||||
let result = 0
|
||||
for (var i = 0; i < arr.length; i += 1) {
|
||||
result += Number.parseFloat(arr[i].time)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function displayResults (results) {
|
||||
if (quiet === false) console.log('==========')
|
||||
const benchNames = Object.keys(results)
|
||||
for (var i = 0; i < benchNames.length; i += 1) {
|
||||
console.log(`${benchNames[i].toUpperCase()} benchmark averages`)
|
||||
const benchmark = results[benchNames[i]]
|
||||
const loggers = Object.keys(benchmark)
|
||||
for (var j = 0; j < loggers.length; j += 1) {
|
||||
const logger = benchmark[loggers[j]]
|
||||
const average = sum(logger) / logger.length
|
||||
console.log(`${loggers[j]} average: ${average.toFixed(3)}ms`)
|
||||
}
|
||||
}
|
||||
if (quiet === false) {
|
||||
console.log('==========')
|
||||
console.log(
|
||||
`System: ${type()}/${platform()} ${arch()} ${release()}`,
|
||||
`~ ${cpus()[0].model} (cores/threads: ${cpus().length})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function toBench (done) {
|
||||
runBenchmark(this.name, done)
|
||||
}
|
||||
|
||||
const benchQueue = []
|
||||
if (selectedBenchmark !== 'all') {
|
||||
benchQueue.push(toBench.bind({ name: selectedBenchmark }))
|
||||
} else {
|
||||
const keys = Object.keys(benchmarks)
|
||||
for (var i = 0; i < keys.length; i += 1) {
|
||||
benchQueue.push(toBench.bind({ name: keys[i] }))
|
||||
}
|
||||
}
|
||||
steed.series(benchQueue, function (err, results) {
|
||||
if (err) return console.error(err.message)
|
||||
results.forEach(displayResults)
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const { readFileSync } = require('fs')
|
||||
const vm = require('vm')
|
||||
const { join } = require('path')
|
||||
const code = readFileSync(
|
||||
join(__dirname, '..', '..', 'node_modules', 'loglevel', 'lib', 'loglevel.js')
|
||||
)
|
||||
const { Console } = require('console')
|
||||
|
||||
function build (dest) {
|
||||
const sandbox = {
|
||||
module: {},
|
||||
console: new Console(dest, dest)
|
||||
}
|
||||
const context = vm.createContext(sandbox)
|
||||
|
||||
const script = new vm.Script(code)
|
||||
script.runInContext(context)
|
||||
|
||||
const loglevel = sandbox.log
|
||||
|
||||
const originalFactory = loglevel.methodFactory
|
||||
loglevel.methodFactory = function (methodName, logLevel, loggerName) {
|
||||
const rawMethod = originalFactory(methodName, logLevel, loggerName)
|
||||
|
||||
return function () {
|
||||
const time = new Date()
|
||||
let array
|
||||
if (typeof arguments[0] === 'string') {
|
||||
arguments[0] = '[' + time.toISOString() + '] ' + arguments[0]
|
||||
rawMethod.apply(null, arguments)
|
||||
} else {
|
||||
array = new Array(arguments.length + 1)
|
||||
array[0] = '[' + time.toISOString() + ']'
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
array[i + 1] = arguments[i]
|
||||
}
|
||||
rawMethod.apply(null, array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loglevel.setLevel(loglevel.levels.INFO)
|
||||
return loglevel
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
|
||||
if (require.main === module) {
|
||||
const loglevel = build(process.stdout)
|
||||
loglevel.info('hello')
|
||||
loglevel.info({ hello: 'world' })
|
||||
loglevel.info('hello %j', { hello: 'world' })
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
console.error(
|
||||
'`pino` cli has been removed. Use `pino-pretty` cli instead.\n' +
|
||||
'\nSee: https://github.com/pinojs/pino-pretty'
|
||||
)
|
||||
process.exit(1)
|
||||
@@ -0,0 +1,465 @@
|
||||
'use strict'
|
||||
|
||||
const format = require('quick-format-unescaped')
|
||||
|
||||
module.exports = pino
|
||||
|
||||
const _console = pfGlobalThisOrFallback().console || {}
|
||||
const stdSerializers = {
|
||||
mapHttpRequest: mock,
|
||||
mapHttpResponse: mock,
|
||||
wrapRequestSerializer: passthrough,
|
||||
wrapResponseSerializer: passthrough,
|
||||
wrapErrorSerializer: passthrough,
|
||||
req: mock,
|
||||
res: mock,
|
||||
err: asErrValue,
|
||||
errWithCause: asErrValue
|
||||
}
|
||||
function levelToValue (level, logger) {
|
||||
return level === 'silent'
|
||||
? Infinity
|
||||
: logger.levels.values[level]
|
||||
}
|
||||
const baseLogFunctionSymbol = Symbol('pino.logFuncs')
|
||||
const hierarchySymbol = Symbol('pino.hierarchy')
|
||||
|
||||
const logFallbackMap = {
|
||||
error: 'log',
|
||||
fatal: 'error',
|
||||
warn: 'error',
|
||||
info: 'log',
|
||||
debug: 'log',
|
||||
trace: 'log'
|
||||
}
|
||||
|
||||
function appendChildLogger (parentLogger, childLogger) {
|
||||
const newEntry = {
|
||||
logger: childLogger,
|
||||
parent: parentLogger[hierarchySymbol]
|
||||
}
|
||||
childLogger[hierarchySymbol] = newEntry
|
||||
}
|
||||
|
||||
function setupBaseLogFunctions (logger, levels, proto) {
|
||||
const logFunctions = {}
|
||||
levels.forEach(level => {
|
||||
logFunctions[level] = proto[level] ? proto[level] : (_console[level] || _console[logFallbackMap[level] || 'log'] || noop)
|
||||
})
|
||||
logger[baseLogFunctionSymbol] = logFunctions
|
||||
}
|
||||
|
||||
function shouldSerialize (serialize, serializers) {
|
||||
if (Array.isArray(serialize)) {
|
||||
const hasToFilter = serialize.filter(function (k) {
|
||||
return k !== '!stdSerializers.err'
|
||||
})
|
||||
return hasToFilter
|
||||
} else if (serialize === true) {
|
||||
return Object.keys(serializers)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function pino (opts) {
|
||||
opts = opts || {}
|
||||
opts.browser = opts.browser || {}
|
||||
|
||||
const transmit = opts.browser.transmit
|
||||
if (transmit && typeof transmit.send !== 'function') { throw Error('pino: transmit option must have a send function') }
|
||||
|
||||
const proto = opts.browser.write || _console
|
||||
if (opts.browser.write) opts.browser.asObject = true
|
||||
const serializers = opts.serializers || {}
|
||||
const serialize = shouldSerialize(opts.browser.serialize, serializers)
|
||||
let stdErrSerialize = opts.browser.serialize
|
||||
|
||||
if (
|
||||
Array.isArray(opts.browser.serialize) &&
|
||||
opts.browser.serialize.indexOf('!stdSerializers.err') > -1
|
||||
) stdErrSerialize = false
|
||||
|
||||
const customLevels = Object.keys(opts.customLevels || {})
|
||||
const levels = ['error', 'fatal', 'warn', 'info', 'debug', 'trace'].concat(customLevels)
|
||||
|
||||
if (typeof proto === 'function') {
|
||||
levels.forEach(function (level) {
|
||||
proto[level] = proto
|
||||
})
|
||||
}
|
||||
if (opts.enabled === false || opts.browser.disabled) opts.level = 'silent'
|
||||
const level = opts.level || 'info'
|
||||
const logger = Object.create(proto)
|
||||
if (!logger.log) logger.log = noop
|
||||
|
||||
setupBaseLogFunctions(logger, levels, proto)
|
||||
// setup root hierarchy entry
|
||||
appendChildLogger({}, logger)
|
||||
|
||||
Object.defineProperty(logger, 'levelVal', {
|
||||
get: getLevelVal
|
||||
})
|
||||
Object.defineProperty(logger, 'level', {
|
||||
get: getLevel,
|
||||
set: setLevel
|
||||
})
|
||||
|
||||
const setOpts = {
|
||||
transmit,
|
||||
serialize,
|
||||
asObject: opts.browser.asObject,
|
||||
formatters: opts.browser.formatters,
|
||||
levels,
|
||||
timestamp: getTimeFunction(opts)
|
||||
}
|
||||
logger.levels = getLevels(opts)
|
||||
logger.level = level
|
||||
|
||||
logger.setMaxListeners = logger.getMaxListeners =
|
||||
logger.emit = logger.addListener = logger.on =
|
||||
logger.prependListener = logger.once =
|
||||
logger.prependOnceListener = logger.removeListener =
|
||||
logger.removeAllListeners = logger.listeners =
|
||||
logger.listenerCount = logger.eventNames =
|
||||
logger.write = logger.flush = noop
|
||||
logger.serializers = serializers
|
||||
logger._serialize = serialize
|
||||
logger._stdErrSerialize = stdErrSerialize
|
||||
logger.child = child
|
||||
|
||||
if (transmit) logger._logEvent = createLogEventShape()
|
||||
|
||||
function getLevelVal () {
|
||||
return levelToValue(this.level, this)
|
||||
}
|
||||
|
||||
function getLevel () {
|
||||
return this._level
|
||||
}
|
||||
function setLevel (level) {
|
||||
if (level !== 'silent' && !this.levels.values[level]) {
|
||||
throw Error('unknown level ' + level)
|
||||
}
|
||||
this._level = level
|
||||
|
||||
set(this, setOpts, logger, 'error') // <-- must stay first
|
||||
set(this, setOpts, logger, 'fatal')
|
||||
set(this, setOpts, logger, 'warn')
|
||||
set(this, setOpts, logger, 'info')
|
||||
set(this, setOpts, logger, 'debug')
|
||||
set(this, setOpts, logger, 'trace')
|
||||
|
||||
customLevels.forEach((level) => {
|
||||
set(this, setOpts, logger, level)
|
||||
})
|
||||
}
|
||||
|
||||
function child (bindings, childOptions) {
|
||||
if (!bindings) {
|
||||
throw new Error('missing bindings for child Pino')
|
||||
}
|
||||
childOptions = childOptions || {}
|
||||
if (serialize && bindings.serializers) {
|
||||
childOptions.serializers = bindings.serializers
|
||||
}
|
||||
const childOptionsSerializers = childOptions.serializers
|
||||
if (serialize && childOptionsSerializers) {
|
||||
var childSerializers = Object.assign({}, serializers, childOptionsSerializers)
|
||||
var childSerialize = opts.browser.serialize === true
|
||||
? Object.keys(childSerializers)
|
||||
: serialize
|
||||
delete bindings.serializers
|
||||
applySerializers([bindings], childSerialize, childSerializers, this._stdErrSerialize)
|
||||
}
|
||||
function Child (parent) {
|
||||
this._childLevel = (parent._childLevel | 0) + 1
|
||||
|
||||
// make sure bindings are available in the `set` function
|
||||
this.bindings = bindings
|
||||
|
||||
if (childSerializers) {
|
||||
this.serializers = childSerializers
|
||||
this._serialize = childSerialize
|
||||
}
|
||||
if (transmit) {
|
||||
this._logEvent = createLogEventShape(
|
||||
[].concat(parent._logEvent.bindings, bindings)
|
||||
)
|
||||
}
|
||||
}
|
||||
Child.prototype = this
|
||||
const newLogger = new Child(this)
|
||||
|
||||
// must happen before the level is assigned
|
||||
appendChildLogger(this, newLogger)
|
||||
// required to actually initialize the logger functions for any given child
|
||||
newLogger.level = this.level
|
||||
|
||||
return newLogger
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
function getLevels (opts) {
|
||||
const customLevels = opts.customLevels || {}
|
||||
|
||||
const values = Object.assign({}, pino.levels.values, customLevels)
|
||||
const labels = Object.assign({}, pino.levels.labels, invertObject(customLevels))
|
||||
|
||||
return {
|
||||
values,
|
||||
labels
|
||||
}
|
||||
}
|
||||
|
||||
function invertObject (obj) {
|
||||
const inverted = {}
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
inverted[obj[key]] = key
|
||||
})
|
||||
return inverted
|
||||
}
|
||||
|
||||
pino.levels = {
|
||||
values: {
|
||||
fatal: 60,
|
||||
error: 50,
|
||||
warn: 40,
|
||||
info: 30,
|
||||
debug: 20,
|
||||
trace: 10
|
||||
},
|
||||
labels: {
|
||||
10: 'trace',
|
||||
20: 'debug',
|
||||
30: 'info',
|
||||
40: 'warn',
|
||||
50: 'error',
|
||||
60: 'fatal'
|
||||
}
|
||||
}
|
||||
|
||||
pino.stdSerializers = stdSerializers
|
||||
pino.stdTimeFunctions = Object.assign({}, { nullTime, epochTime, unixTime, isoTime })
|
||||
|
||||
function getBindingChain (logger) {
|
||||
const bindings = []
|
||||
if (logger.bindings) {
|
||||
bindings.push(logger.bindings)
|
||||
}
|
||||
|
||||
// traverse up the tree to get all bindings
|
||||
let hierarchy = logger[hierarchySymbol]
|
||||
while (hierarchy.parent) {
|
||||
hierarchy = hierarchy.parent
|
||||
if (hierarchy.logger.bindings) {
|
||||
bindings.push(hierarchy.logger.bindings)
|
||||
}
|
||||
}
|
||||
|
||||
return bindings.reverse()
|
||||
}
|
||||
|
||||
function set (self, opts, rootLogger, level) {
|
||||
// override the current log functions with either `noop` or the base log function
|
||||
Object.defineProperty(self, level, {
|
||||
value: (levelToValue(self.level, rootLogger) > levelToValue(level, rootLogger)
|
||||
? noop
|
||||
: rootLogger[baseLogFunctionSymbol][level]),
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
|
||||
if (!opts.transmit && self[level] === noop) {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the log format is correct
|
||||
self[level] = createWrap(self, opts, rootLogger, level)
|
||||
|
||||
// prepend bindings if it is not the root logger
|
||||
const bindings = getBindingChain(self)
|
||||
if (bindings.length === 0) {
|
||||
// early exit in case for rootLogger
|
||||
return
|
||||
}
|
||||
self[level] = prependBindingsInArguments(bindings, self[level])
|
||||
}
|
||||
|
||||
function prependBindingsInArguments (bindings, logFunc) {
|
||||
return function () {
|
||||
return logFunc.apply(this, [...bindings, ...arguments])
|
||||
}
|
||||
}
|
||||
|
||||
function createWrap (self, opts, rootLogger, level) {
|
||||
return (function (write) {
|
||||
return function LOG () {
|
||||
const ts = opts.timestamp()
|
||||
const args = new Array(arguments.length)
|
||||
const proto = (Object.getPrototypeOf && Object.getPrototypeOf(this) === _console) ? _console : this
|
||||
for (var i = 0; i < args.length; i++) args[i] = arguments[i]
|
||||
|
||||
if (opts.serialize && !opts.asObject) {
|
||||
applySerializers(args, this._serialize, this.serializers, this._stdErrSerialize)
|
||||
}
|
||||
if (opts.asObject || opts.formatters) {
|
||||
write.call(proto, asObject(this, level, args, ts, opts.formatters))
|
||||
} else write.apply(proto, args)
|
||||
|
||||
if (opts.transmit) {
|
||||
const transmitLevel = opts.transmit.level || self._level
|
||||
const transmitValue = rootLogger.levels.values[transmitLevel]
|
||||
const methodValue = rootLogger.levels.values[level]
|
||||
if (methodValue < transmitValue) return
|
||||
transmit(this, {
|
||||
ts,
|
||||
methodLevel: level,
|
||||
methodValue,
|
||||
transmitLevel,
|
||||
transmitValue: rootLogger.levels.values[opts.transmit.level || self._level],
|
||||
send: opts.transmit.send,
|
||||
val: levelToValue(self._level, rootLogger)
|
||||
}, args)
|
||||
}
|
||||
}
|
||||
})(self[baseLogFunctionSymbol][level])
|
||||
}
|
||||
|
||||
function asObject (logger, level, args, ts, formatters = {}) {
|
||||
const {
|
||||
level: levelFormatter = () => logger.levels.values[level],
|
||||
log: logObjectFormatter = (obj) => obj
|
||||
} = formatters
|
||||
if (logger._serialize) applySerializers(args, logger._serialize, logger.serializers, logger._stdErrSerialize)
|
||||
const argsCloned = args.slice()
|
||||
let msg = argsCloned[0]
|
||||
const logObject = {}
|
||||
if (ts) {
|
||||
logObject.time = ts
|
||||
}
|
||||
logObject.level = levelFormatter(level, logger.levels.values[level])
|
||||
|
||||
let lvl = (logger._childLevel | 0) + 1
|
||||
if (lvl < 1) lvl = 1
|
||||
// deliberate, catching objects, arrays
|
||||
if (msg !== null && typeof msg === 'object') {
|
||||
while (lvl-- && typeof argsCloned[0] === 'object') {
|
||||
Object.assign(logObject, argsCloned.shift())
|
||||
}
|
||||
msg = argsCloned.length ? format(argsCloned.shift(), argsCloned) : undefined
|
||||
} else if (typeof msg === 'string') msg = format(argsCloned.shift(), argsCloned)
|
||||
if (msg !== undefined) logObject.msg = msg
|
||||
|
||||
const formattedLogObject = logObjectFormatter(logObject)
|
||||
return formattedLogObject
|
||||
}
|
||||
|
||||
function applySerializers (args, serialize, serializers, stdErrSerialize) {
|
||||
for (const i in args) {
|
||||
if (stdErrSerialize && args[i] instanceof Error) {
|
||||
args[i] = pino.stdSerializers.err(args[i])
|
||||
} else if (typeof args[i] === 'object' && !Array.isArray(args[i])) {
|
||||
for (const k in args[i]) {
|
||||
if (serialize && serialize.indexOf(k) > -1 && k in serializers) {
|
||||
args[i][k] = serializers[k](args[i][k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transmit (logger, opts, args) {
|
||||
const send = opts.send
|
||||
const ts = opts.ts
|
||||
const methodLevel = opts.methodLevel
|
||||
const methodValue = opts.methodValue
|
||||
const val = opts.val
|
||||
const bindings = logger._logEvent.bindings
|
||||
|
||||
applySerializers(
|
||||
args,
|
||||
logger._serialize || Object.keys(logger.serializers),
|
||||
logger.serializers,
|
||||
logger._stdErrSerialize === undefined ? true : logger._stdErrSerialize
|
||||
)
|
||||
logger._logEvent.ts = ts
|
||||
logger._logEvent.messages = args.filter(function (arg) {
|
||||
// bindings can only be objects, so reference equality check via indexOf is fine
|
||||
return bindings.indexOf(arg) === -1
|
||||
})
|
||||
|
||||
logger._logEvent.level.label = methodLevel
|
||||
logger._logEvent.level.value = methodValue
|
||||
|
||||
send(methodLevel, logger._logEvent, val)
|
||||
|
||||
logger._logEvent = createLogEventShape(bindings)
|
||||
}
|
||||
|
||||
function createLogEventShape (bindings) {
|
||||
return {
|
||||
ts: 0,
|
||||
messages: [],
|
||||
bindings: bindings || [],
|
||||
level: { label: '', value: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
function asErrValue (err) {
|
||||
const obj = {
|
||||
type: err.constructor.name,
|
||||
msg: err.message,
|
||||
stack: err.stack
|
||||
}
|
||||
for (const key in err) {
|
||||
if (obj[key] === undefined) {
|
||||
obj[key] = err[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function getTimeFunction (opts) {
|
||||
if (typeof opts.timestamp === 'function') {
|
||||
return opts.timestamp
|
||||
}
|
||||
if (opts.timestamp === false) {
|
||||
return nullTime
|
||||
}
|
||||
return epochTime
|
||||
}
|
||||
|
||||
function mock () { return {} }
|
||||
function passthrough (a) { return a }
|
||||
function noop () {}
|
||||
|
||||
function nullTime () { return false }
|
||||
function epochTime () { return Date.now() }
|
||||
function unixTime () { return Math.round(Date.now() / 1000.0) }
|
||||
function isoTime () { return new Date(Date.now()).toISOString() } // using Date.now() for testability
|
||||
|
||||
/* eslint-disable */
|
||||
/* istanbul ignore next */
|
||||
function pfGlobalThisOrFallback () {
|
||||
function defd (o) { return typeof o !== 'undefined' && o }
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined') return globalThis
|
||||
Object.defineProperty(Object.prototype, 'globalThis', {
|
||||
get: function () {
|
||||
delete Object.prototype.globalThis
|
||||
return (this.globalThis = this)
|
||||
},
|
||||
configurable: true
|
||||
})
|
||||
return globalThis
|
||||
} catch (e) {
|
||||
return defd(self) || defd(window) || defd(this) || {}
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
module.exports.default = pino
|
||||
module.exports.pino = pino
|
||||
@@ -0,0 +1,10 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { version } = require('../package.json')
|
||||
|
||||
const metaContent = `'use strict'
|
||||
|
||||
module.exports = { version: '${version}' }
|
||||
`
|
||||
|
||||
fs.writeFileSync(path.resolve('./lib/meta.js'), metaContent, { encoding: 'utf-8' })
|
||||
@@ -0,0 +1,1446 @@
|
||||
# API
|
||||
|
||||
* [pino() => logger](#export)
|
||||
* [options](#options)
|
||||
* [destination](#destination)
|
||||
* [destination\[Symbol.for('pino.metadata')\]](#metadata)
|
||||
* [Logger Instance](#logger)
|
||||
* [logger.trace()](#trace)
|
||||
* [logger.debug()](#debug)
|
||||
* [logger.info()](#info)
|
||||
* [logger.warn()](#warn)
|
||||
* [logger.error()](#error)
|
||||
* [logger.fatal()](#fatal)
|
||||
* [logger.silent()](#silent)
|
||||
* [logger.child()](#child)
|
||||
* [logger.bindings()](#logger-bindings)
|
||||
* [logger.setBindings()](#logger-set-bindings)
|
||||
* [logger.flush()](#flush)
|
||||
* [logger.level](#logger-level)
|
||||
* [logger.isLevelEnabled()](#islevelenabled)
|
||||
* [logger.levels](#levels)
|
||||
* [logger\[Symbol.for('pino.serializers')\]](#serializers)
|
||||
* [Event: 'level-change'](#level-change)
|
||||
* [logger.version](#version)
|
||||
* [Statics](#statics)
|
||||
* [pino.destination()](#pino-destination)
|
||||
* [pino.transport()](#pino-transport)
|
||||
* [pino.multistream()](#pino-multistream)
|
||||
* [pino.stdSerializers](#pino-stdserializers)
|
||||
* [pino.stdTimeFunctions](#pino-stdtimefunctions)
|
||||
* [pino.symbols](#pino-symbols)
|
||||
* [pino.version](#pino-version)
|
||||
* [Interfaces](#interfaces)
|
||||
* [MultiStreamRes](#multistreamres)
|
||||
* [StreamEntry](#streamentry)
|
||||
* [DestinationStream](#destinationstream)
|
||||
* [Types](#types)
|
||||
* [Level](#level-1)
|
||||
|
||||
<a id="export"></a>
|
||||
## `pino([options], [destination]) => logger`
|
||||
|
||||
The exported `pino` function takes two optional arguments,
|
||||
[`options`](#options) and [`destination`](#destination), and
|
||||
returns a [logger instance](#logger).
|
||||
|
||||
<a id=options></a>
|
||||
### `options` (Object)
|
||||
|
||||
#### `name` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The name of the logger. When set adds a `name` field to every JSON line logged.
|
||||
|
||||
#### `level` (String)
|
||||
|
||||
Default: `'info'`
|
||||
|
||||
The minimum level to log: Pino will not log messages with a lower level. Setting this option reduces the load, as typically, debug and trace logs are only valid for development, and not needed in production.
|
||||
|
||||
One of `'fatal'`, `'error'`, `'warn'`, `'info'`, `'debug'`, `'trace'` or `'silent'`.
|
||||
|
||||
Additional levels can be added to the instance via the `customLevels` option.
|
||||
|
||||
* See [`customLevels` option](#opt-customlevels)
|
||||
|
||||
<a id=opt-customlevels></a>
|
||||
|
||||
#### `levelComparison` ("ASC", "DESC", Function)
|
||||
|
||||
Default: `ASC`
|
||||
|
||||
Use this option to customize levels order.
|
||||
In order to be able to define custom levels ordering pass a function which will accept `current` and `expected` values and return `boolean` which shows should `current` level to be shown or not.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
levelComparison: 'DESC',
|
||||
customLevels: {
|
||||
foo: 20, // `foo` is more valuable than `bar`
|
||||
bar: 10
|
||||
},
|
||||
})
|
||||
|
||||
// OR
|
||||
|
||||
const logger = pino({
|
||||
levelComparison: function(current, expected) {
|
||||
return current >= expected;
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### `customLevels` (Object)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
Use this option to define additional logging levels.
|
||||
The keys of the object correspond to the namespace of the log level,
|
||||
and the values should be the numerical value of the level.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
})
|
||||
logger.foo('hi')
|
||||
```
|
||||
|
||||
<a id=opt-useOnlyCustomLevels></a>
|
||||
#### `useOnlyCustomLevels` (Boolean)
|
||||
|
||||
Default: `false`
|
||||
|
||||
Use this option to only use defined `customLevels` and omit Pino's levels.
|
||||
Logger's default `level` must be changed to a value in `customLevels` to use `useOnlyCustomLevels`
|
||||
Warning: this option may not be supported by downstream transports.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
},
|
||||
useOnlyCustomLevels: true,
|
||||
level: 'foo'
|
||||
})
|
||||
logger.foo('hi')
|
||||
logger.info('hello') // Will throw an error saying info is not found in logger object
|
||||
```
|
||||
#### `depthLimit` (Number)
|
||||
|
||||
Default: `5`
|
||||
|
||||
Option to limit stringification at a specific nesting depth when logging circular objects.
|
||||
|
||||
#### `edgeLimit` (Number)
|
||||
|
||||
Default: `100`
|
||||
|
||||
Option to limit stringification of properties/elements when logging a specific object/array with circular references.
|
||||
|
||||
<a id="opt-mixin"></a>
|
||||
#### `mixin` (Function):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
If provided, the `mixin` function is called each time one of the active
|
||||
logging methods is called. The first parameter is the value `mergeObject` or an empty object. The second parameter is the log level number.
|
||||
The third parameter is the logger or child logger itself, which can be used to
|
||||
retrieve logger-specific context from within the `mixin` function.
|
||||
The function must synchronously return an object. The properties of the returned object will be added to the
|
||||
logged JSON.
|
||||
|
||||
```js
|
||||
let n = 0
|
||||
const logger = pino({
|
||||
mixin () {
|
||||
return { line: ++n }
|
||||
}
|
||||
})
|
||||
logger.info('hello')
|
||||
// {"level":30,"time":1573664685466,"pid":78742,"hostname":"x","line":1,"msg":"hello"}
|
||||
logger.info('world')
|
||||
// {"level":30,"time":1573664685469,"pid":78742,"hostname":"x","line":2,"msg":"world"}
|
||||
```
|
||||
|
||||
The result of `mixin()` is supposed to be a _new_ object. For performance reason, the object returned by `mixin()` will be mutated by pino.
|
||||
In the following example, passing `mergingObject` argument to the first `info` call will mutate the global `mixin` object by default:
|
||||
(* See [`mixinMergeStrategy` option](#opt-mixin-merge-strategy)):
|
||||
```js
|
||||
const mixin = {
|
||||
appName: 'My app'
|
||||
}
|
||||
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return mixin;
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 1"}
|
||||
logger.info('Message 2')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","appName":"My app","description":"Ok","msg":"Message 2"}
|
||||
// Note: the second log contains "description":"Ok" text, even if it was not provided.
|
||||
```
|
||||
|
||||
The `mixin` method can be used to add the level label to each log message such as in the following example:
|
||||
```js
|
||||
const logger = pino({
|
||||
mixin(_context, level) {
|
||||
return { 'level-label': logger.levels.labels[level] }
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","description":"Ok","level-label":"info","msg":"Message 1"}
|
||||
logger.error('Message 2')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","level-label":"error","msg":"Message 2"}
|
||||
```
|
||||
|
||||
If the `mixin` feature is being used merely to add static metadata to each log message,
|
||||
then a [child logger ⇗](/docs/child-loggers.md) should be used instead. Unless your application
|
||||
needs to concatenate values for a specific key multiple times, in which case `mixin` can be
|
||||
used to avoid the [duplicate keys caveat](/docs/child-loggers.md#duplicate-keys-caveat):
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
mixin (obj, num, logger) {
|
||||
return {
|
||||
tags: logger.tags
|
||||
}
|
||||
}
|
||||
})
|
||||
logger.tags = {}
|
||||
|
||||
logger.addTag = function (key, value) {
|
||||
logger.tags[key] = value
|
||||
}
|
||||
|
||||
function createChild (parent, ...context) {
|
||||
const newChild = logger.child(...context)
|
||||
newChild.tags = { ...logger.tags }
|
||||
newChild.addTag = function (key, value) {
|
||||
newChild.tags[key] = value
|
||||
}
|
||||
return newChild
|
||||
}
|
||||
|
||||
logger.addTag('foo', 1)
|
||||
const child = createChild(logger, {})
|
||||
child.addTag('bar', 2)
|
||||
logger.info('this will only have `foo: 1`')
|
||||
child.info('this will have both `foo: 1` and `bar: 2`')
|
||||
logger.info('this will still only have `foo: 1`')
|
||||
```
|
||||
|
||||
As of pino 7.x, when the `mixin` is used with the [`nestedKey` option](#opt-nestedkey),
|
||||
the object returned from the `mixin` method will also be nested. Prior versions would mix
|
||||
this object into the root.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
nestedKey: 'payload',
|
||||
mixin() {
|
||||
return { requestId: requestId.currentId() }
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
description: 'Ok'
|
||||
}, 'Message 1')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","payload":{"requestId":"dfe9a9014b","description":"Ok"},"msg":"Message 1"}
|
||||
```
|
||||
|
||||
<a id="opt-mixin-merge-strategy"></a>
|
||||
#### `mixinMergeStrategy` (Function):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
If provided, the `mixinMergeStrategy` function is called each time one of the active
|
||||
logging methods is called. The first parameter is the value `mergeObject` or an empty object,
|
||||
the second parameter is the value resulting from `mixin()` (* See [`mixin` option](#opt-mixin) or an empty object.
|
||||
The function must synchronously return an object.
|
||||
|
||||
```js
|
||||
// Default strategy, `mergeObject` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'docker' }
|
||||
},
|
||||
// mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
// return Object.assign(mixinMeta, mergeObject)
|
||||
// }
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"local","msg":"Message"}
|
||||
```
|
||||
|
||||
```js
|
||||
// Custom mutable strategy, `mixin` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'k8s' }
|
||||
},
|
||||
mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
return Object.assign(mergeObject, mixinObject)
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
|
||||
```
|
||||
|
||||
```js
|
||||
// Custom immutable strategy, `mixin` has priority
|
||||
const logger = pino({
|
||||
mixin() {
|
||||
return { tag: 'k8s' }
|
||||
},
|
||||
mixinMergeStrategy(mergeObject, mixinObject) {
|
||||
return Object.assign({}, mergeObject, mixinObject)
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
tag: 'local'
|
||||
}, 'Message')
|
||||
// {"level":30,"time":1591195061437,"pid":16012,"hostname":"x","tag":"k8s","msg":"Message"}
|
||||
```
|
||||
|
||||
<a id="opt-redact"></a>
|
||||
#### `redact` (Array | Object):
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
As an array, the `redact` option specifies paths that should
|
||||
have their values redacted from any log output.
|
||||
|
||||
Each path must be a string using a syntax that corresponds to JavaScript dot and bracket notation.
|
||||
|
||||
If an object is supplied, three options can be specified:
|
||||
* `paths` (array): Required. An array of paths. See [redaction - Path Syntax ⇗](/docs/redaction.md#paths) for specifics.
|
||||
* `censor` (String|Function|Undefined): Optional. When supplied as a String the `censor` option will overwrite keys that are to be redacted. When set to `undefined` the key will be removed entirely from the object.
|
||||
The `censor` option may also be a mapping function. The (synchronous) mapping function has the signature `(value, path) => redactedValue` and is called with the unredacted `value` and `path` to the key being redacted, as an array. For example given a redaction path of `a.b.c` the `path` argument would be `['a', 'b', 'c']`. The value returned from the mapping function becomes the applied censor value. Default: `'[Redacted]'`
|
||||
value synchronously.
|
||||
Default: `'[Redacted]'`
|
||||
* `remove` (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: `false`
|
||||
|
||||
**WARNING**: Never allow user input to define redacted paths.
|
||||
|
||||
* See the [redaction ⇗](/docs/redaction.md) documentation.
|
||||
* See [fast-redact#caveat ⇗](https://github.com/davidmarkclements/fast-redact#caveat)
|
||||
|
||||
<a id=opt-hooks></a>
|
||||
#### `hooks` (Object)
|
||||
|
||||
An object mapping to hook functions. Hook functions allow for customizing
|
||||
internal logger operations. Hook functions ***must*** be synchronous functions.
|
||||
|
||||
<a id="logmethod"></a>
|
||||
##### `logMethod`
|
||||
|
||||
Allows for manipulating the parameters passed to logger methods. The signature
|
||||
for this hook is `logMethod (args, method, level) {}`, where `args` is an array
|
||||
of the arguments that were passed to the log method and `method` is the log
|
||||
method itself, `level` is the log level itself. This hook ***must*** invoke the
|
||||
`method` function by using apply, like so: `method.apply(this, newArgumentsArray)`.
|
||||
|
||||
For example, Pino expects a binding object to be the first parameter with an
|
||||
optional string message as the second parameter. Using this hook the parameters
|
||||
can be flipped:
|
||||
|
||||
```js
|
||||
const hooks = {
|
||||
logMethod (inputArgs, method, level) {
|
||||
if (inputArgs.length >= 2) {
|
||||
const arg1 = inputArgs.shift()
|
||||
const arg2 = inputArgs.shift()
|
||||
return method.apply(this, [arg2, arg1, ...inputArgs])
|
||||
}
|
||||
return method.apply(this, inputArgs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a id=opt-formatters></a>
|
||||
#### `formatters` (Object)
|
||||
|
||||
An object containing functions for formatting the shape of the log lines.
|
||||
These functions should return a JSONifiable object and
|
||||
should never throw. These functions allow for full customization of
|
||||
the resulting log lines. For example, they can be used to change
|
||||
the level key name or to enrich the default metadata.
|
||||
|
||||
##### `level`
|
||||
|
||||
Changes the shape of the log level. The default shape is `{ level: number }`.
|
||||
The function takes two arguments, the label of the level (e.g. `'info'`)
|
||||
and the numeric value (e.g. `30`).
|
||||
|
||||
ps: The log level cannot be customized when using multiple transports
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
level (label, number) {
|
||||
return { level: number }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### `bindings`
|
||||
|
||||
Changes the shape of the bindings. The default shape is `{ pid, hostname }`.
|
||||
The function takes a single argument, the bindings object, which can be configured
|
||||
using the [`base` option](#opt-base). Called once when creating logger.
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
bindings (bindings) {
|
||||
return { pid: bindings.pid, hostname: bindings.hostname }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### `log`
|
||||
|
||||
Changes the shape of the log object. This function will be called every time
|
||||
one of the log methods (such as `.info`) is called. All arguments passed to the
|
||||
log method, except the message, will be passed to this function. By default, it does
|
||||
not change the shape of the log object.
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
log (object) {
|
||||
return object
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a id=opt-serializers></a>
|
||||
#### `serializers` (Object)
|
||||
|
||||
Default: `{err: pino.stdSerializers.err}`
|
||||
|
||||
An object containing functions for custom serialization of objects.
|
||||
These functions should return an JSONifiable object and they
|
||||
should never throw. When logging an object, each top-level property
|
||||
matching the exact key of a serializer will be serialized using the defined serializer.
|
||||
|
||||
The serializers are applied when a property in the logged object matches a property
|
||||
in the serializers. The only exception is the `err` serializer as it is also applied in case
|
||||
the object is an instance of `Error`, e.g. `logger.info(new Error('kaboom'))`.
|
||||
See `errorKey` option to change `err` namespace.
|
||||
|
||||
* See [pino.stdSerializers](#pino-stdserializers)
|
||||
|
||||
#### `msgPrefix` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The `msgPrefix` property allows you to specify a prefix for every message of the logger and its children.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
msgPrefix: '[HTTP] '
|
||||
})
|
||||
logger.info('got new request!')
|
||||
// > [HTTP] got new request!
|
||||
|
||||
const child = logger.child({})
|
||||
child.info('User authenticated!')
|
||||
// > [HTTP] User authenticated!
|
||||
```
|
||||
|
||||
<a id=opt-base></a>
|
||||
#### `base` (Object)
|
||||
|
||||
Default: `{pid: process.pid, hostname: os.hostname}`
|
||||
|
||||
Key-value object added as child logger to each log line.
|
||||
|
||||
Set to `undefined` to avoid adding `pid`, `hostname` properties to each log.
|
||||
|
||||
#### `enabled` (Boolean)
|
||||
|
||||
Default: `true`
|
||||
|
||||
Set to `false` to disable logging.
|
||||
|
||||
#### `crlf` (Boolean)
|
||||
|
||||
Default: `false`
|
||||
|
||||
Set to `true` to logs newline delimited JSON with `\r\n` instead of `\n`.
|
||||
|
||||
<a id=opt-timestamp></a>
|
||||
#### `timestamp` (Boolean | Function)
|
||||
|
||||
Default: `true`
|
||||
|
||||
Enables or disables the inclusion of a timestamp in the
|
||||
log message. If a function is supplied, it must synchronously return a partial JSON string
|
||||
representation of the time, e.g. `,"time":1493426328206` (which is the default).
|
||||
|
||||
If set to `false`, no timestamp will be included in the output.
|
||||
|
||||
See [stdTimeFunctions](#pino-stdtimefunctions) for a set of available functions
|
||||
for passing in as a value for this option.
|
||||
|
||||
Example:
|
||||
```js
|
||||
timestamp: () => `,"time":"${new Date(Date.now()).toISOString()}"`
|
||||
// which is equivalent to:
|
||||
// timestamp: stdTimeFunctions.isoTime
|
||||
```
|
||||
|
||||
**Caution**: attempting to format time in-process will significantly impact logging performance.
|
||||
|
||||
<a id=opt-messagekey></a>
|
||||
#### `messageKey` (String)
|
||||
|
||||
Default: `'msg'`
|
||||
|
||||
The string key for the 'message' in the JSON object.
|
||||
|
||||
<a id=opt-messagekey></a>
|
||||
#### `errorKey` (String)
|
||||
|
||||
Default: `'err'`
|
||||
|
||||
The string key for the 'error' in the JSON object.
|
||||
|
||||
<a id=opt-nestedkey></a>
|
||||
#### `nestedKey` (String)
|
||||
|
||||
Default: `null`
|
||||
|
||||
If there's a chance that objects being logged have properties that conflict with those from pino itself (`level`, `timestamp`, `pid`, etc)
|
||||
and duplicate keys in your log records are undesirable, pino can be configured with a `nestedKey` option that causes any `object`s that are logged
|
||||
to be placed under a key whose name is the value of `nestedKey`.
|
||||
|
||||
This way, when searching something like Kibana for values, one can consistently search under the configured `nestedKey` value instead of the root log record keys.
|
||||
|
||||
For example,
|
||||
```js
|
||||
const logger = require('pino')({
|
||||
nestedKey: 'payload'
|
||||
})
|
||||
|
||||
const thing = { level: 'hi', time: 'never', foo: 'bar'} // has pino-conflicting properties!
|
||||
logger.info(thing)
|
||||
|
||||
// logs the following:
|
||||
// {"level":30,"time":1578357790020,"pid":91736,"hostname":"x","payload":{"level":"hi","time":"never","foo":"bar"}}
|
||||
```
|
||||
In this way, logged objects' properties don't conflict with pino's standard logging properties,
|
||||
and searching for logged objects can start from a consistent path.
|
||||
|
||||
#### `browser` (Object)
|
||||
|
||||
Browser only, may have `asObject` and `write` keys. This option is separately
|
||||
documented in the [Browser API ⇗](/docs/browser.md) documentation.
|
||||
|
||||
* See [Browser API ⇗](/docs/browser.md)
|
||||
|
||||
#### `transport` (Object)
|
||||
|
||||
The `transport` option is a shorthand for the [pino.transport()](#pino-transport) function.
|
||||
It supports the same input options:
|
||||
```js
|
||||
require('pino')({
|
||||
transport: {
|
||||
target: '/absolute/path/to/my-transport.mjs'
|
||||
}
|
||||
})
|
||||
|
||||
// or multiple transports
|
||||
require('pino')({
|
||||
transport: {
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If the transport option is supplied to `pino`, a [`destination`](#destination) parameter may not also be passed as a separate argument to `pino`:
|
||||
|
||||
```js
|
||||
pino({ transport: {}}, '/path/to/somewhere') // THIS WILL NOT WORK, DO NOT DO THIS
|
||||
pino({ transport: {}}, process.stderr) // THIS WILL NOT WORK, DO NOT DO THIS
|
||||
```
|
||||
|
||||
when using the `transport` option. In this case, an `Error` will be thrown.
|
||||
|
||||
* See [pino.transport()](#pino-transport)
|
||||
|
||||
#### `onChild` (Function)
|
||||
|
||||
The `onChild` function is a synchronous callback that will be called on each creation of a new child, passing the child instance as its first argument.
|
||||
Any error thrown inside the callback will be uncaught and should be handled inside the callback.
|
||||
```js
|
||||
const parent = require('pino')({ onChild: (instance) => {
|
||||
// Execute call back code for each newly created child.
|
||||
}})
|
||||
// `onChild` will now be executed with the new child.
|
||||
parent.child(bindings)
|
||||
```
|
||||
|
||||
|
||||
<a id="destination"></a>
|
||||
### `destination` (Number | String | Object | DestinationStream | SonicBoomOpts | WritableStream)
|
||||
|
||||
Default: `pino.destination(1)` (STDOUT)
|
||||
|
||||
The `destination` parameter can be a file descriptor, a file path, or an
|
||||
object with `dest` property pointing to a fd or path.
|
||||
An ordinary Node.js `stream` file descriptor can be passed as the
|
||||
destination (such as the result
|
||||
of `fs.createWriteStream`) but for peak log writing performance, it is strongly
|
||||
recommended to use `pino.destination` to create the destination stream.
|
||||
Note that the `destination` parameter can be the result of `pino.transport()`.
|
||||
|
||||
```js
|
||||
// pino.destination(1) by default
|
||||
const stdoutLogger = require('pino')()
|
||||
|
||||
// destination param may be in first position when no options:
|
||||
const fileLogger = require('pino')( pino.destination('/log/path'))
|
||||
|
||||
// use the stderr file handle to log to stderr:
|
||||
const opts = {name: 'my-logger'}
|
||||
const stderrLogger = require('pino')(opts, pino.destination(2))
|
||||
|
||||
// automatic wrapping in pino.destination
|
||||
const fileLogger = require('pino')('/log/path')
|
||||
|
||||
// Asynchronous logging
|
||||
const fileLogger = pino(pino.destination({ dest: '/log/path', sync: false }))
|
||||
```
|
||||
|
||||
However, there are some special instances where `pino.destination` is not used as the default:
|
||||
|
||||
+ When something, e.g a process manager, has monkey-patched `process.stdout.write`.
|
||||
|
||||
In these cases `process.stdout` is used instead.
|
||||
|
||||
Note: If the parameter is a string integer, e.g. `'1'`, it will be coerced to
|
||||
a number and used as a file descriptor. If this is not desired, provide a full
|
||||
path, e.g. `/tmp/1`.
|
||||
|
||||
* See [`pino.destination`](#pino-destination)
|
||||
|
||||
<a id="metadata"></a>
|
||||
#### `destination[Symbol.for('pino.metadata')]`
|
||||
|
||||
Default: `false`
|
||||
|
||||
Using the global symbol `Symbol.for('pino.metadata')` as a key on the `destination` parameter and
|
||||
setting the key to `true`, indicates that the following properties should be
|
||||
set on the `destination` object after each log line is written:
|
||||
|
||||
* the last logging level as `destination.lastLevel`
|
||||
* the last logging message as `destination.lastMsg`
|
||||
* the last logging object as `destination.lastObj`
|
||||
* the last time as `destination.lastTime`, which will be the partial string returned
|
||||
by the time function.
|
||||
* the last logger instance as `destination.lastLogger` (to support child
|
||||
loggers)
|
||||
|
||||
The following is a succinct usage example:
|
||||
|
||||
```js
|
||||
const dest = pino.destination('/dev/null')
|
||||
dest[Symbol.for('pino.metadata')] = true
|
||||
const logger = pino(dest)
|
||||
logger.info({a: 1}, 'hi')
|
||||
const { lastMsg, lastLevel, lastObj, lastTime} = dest
|
||||
console.log(
|
||||
'Logged message "%s" at level %d with object %o at time %s',
|
||||
lastMsg, lastLevel, lastObj, lastTime
|
||||
) // Logged message "hi" at level 30 with object { a: 1 } at time 1531590545089
|
||||
```
|
||||
|
||||
<a id="logger"></a>
|
||||
## Logger Instance
|
||||
|
||||
The logger instance is the object returned by the main exported
|
||||
[`pino`](#export) function.
|
||||
|
||||
The primary purpose of the logger instance is to provide logging methods.
|
||||
|
||||
The default logging methods are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`.
|
||||
|
||||
Each logging method has the following signature:
|
||||
`([mergingObject], [message], [...interpolationValues])`.
|
||||
|
||||
The parameters are explained below using the `logger.info` method but the same applies to all logging methods.
|
||||
|
||||
### Logging Method Parameters
|
||||
|
||||
<a id=mergingobject></a>
|
||||
#### `mergingObject` (Object)
|
||||
|
||||
An object can optionally be supplied as the first parameter. Each enumerable key and value
|
||||
of the `mergingObject` is copied into the JSON log line.
|
||||
|
||||
```js
|
||||
logger.info({MIX: {IN: true}})
|
||||
// {"level":30,"time":1531254555820,"pid":55956,"hostname":"x","MIX":{"IN":true}}
|
||||
```
|
||||
|
||||
If the object is of type Error, it is wrapped in an object containing a property err (`{ err: mergingObject }`).
|
||||
This allows for a unified error handling flow.
|
||||
|
||||
Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
|
||||
from `err` to another string as preferred.
|
||||
|
||||
<a id="message"></a>
|
||||
#### `message` (String)
|
||||
|
||||
A `message` string can optionally be supplied as the first parameter, or
|
||||
as the second parameter after supplying a `mergingObject`.
|
||||
|
||||
By default, the contents of the `message` parameter will be merged into the
|
||||
JSON log line under the `msg` key:
|
||||
|
||||
```js
|
||||
logger.info('hello world')
|
||||
// {"level":30,"time":1531257112193,"msg":"hello world","pid":55956,"hostname":"x"}
|
||||
```
|
||||
|
||||
The `message` parameter takes precedence over the `mergingObject`.
|
||||
That is, if a `mergingObject` contains a `msg` property, and a `message` parameter
|
||||
is supplied in addition, the `msg` property in the output log will be the value of
|
||||
the `message` parameter not the value of the `msg` property on the `mergingObject`.
|
||||
See [Avoid Message Conflict](/docs/help.md#avoid-message-conflict) for information
|
||||
on how to overcome this limitation.
|
||||
|
||||
If no `message` parameter is provided, and the `mergingObject` is of type `Error` or it has a property named `err`, the
|
||||
`message` parameter is set to the `message` value of the error. See option `errorKey` if you want to change the namespace.
|
||||
|
||||
The `messageKey` option can be used at instantiation time to change the namespace
|
||||
from `msg` to another string as preferred.
|
||||
|
||||
The `message` string may contain a printf style string with support for
|
||||
the following placeholders:
|
||||
|
||||
* `%s` – string placeholder
|
||||
* `%d` – digit placeholder
|
||||
* `%O`, `%o`, and `%j` – object placeholder
|
||||
|
||||
Values supplied as additional arguments to the logger method will
|
||||
then be interpolated accordingly.
|
||||
|
||||
* See [`messageKey` pino option](#opt-messagekey)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="interpolationvalues"></a>
|
||||
#### `...interpolationValues` (Any)
|
||||
|
||||
All arguments supplied after `message` are serialized and interpolated according
|
||||
to any supplied printf-style placeholders (`%s`, `%d`, `%o`|`%O`|`%j`) to form
|
||||
the final output `msg` value for the JSON log line.
|
||||
|
||||
```js
|
||||
logger.info('%o hello %s', {worldly: 1}, 'world')
|
||||
// {"level":30,"time":1531257826880,"msg":"{\"worldly\":1} hello world","pid":55956,"hostname":"x"}
|
||||
```
|
||||
|
||||
Since pino v6, we do not automatically concatenate and cast to string
|
||||
consecutive parameters:
|
||||
|
||||
```js
|
||||
logger.info('hello', 'world')
|
||||
// {"level":30,"time":1531257618044,"msg":"hello","pid":55956,"hostname":"x"}
|
||||
// world is missing
|
||||
```
|
||||
|
||||
However, it's possible to inject a hook to modify this behavior:
|
||||
|
||||
```js
|
||||
const pinoOptions = {
|
||||
hooks: { logMethod }
|
||||
}
|
||||
|
||||
function logMethod (args, method) {
|
||||
if (args.length === 2) {
|
||||
args[0] = `${args[0]} %j`
|
||||
}
|
||||
method.apply(this, args)
|
||||
}
|
||||
|
||||
const logger = pino(pinoOptions)
|
||||
```
|
||||
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`logMethod` hook](#logmethod)
|
||||
|
||||
<a id="error-serialization"></a>
|
||||
#### Errors
|
||||
|
||||
Errors can be supplied as either the first parameter or if already using `mergingObject` then as the `err` property on the `mergingObject`.
|
||||
|
||||
Options `serializers` and `errorKey` could be used at instantiation time to change the namespace
|
||||
from `err` to another string as preferred.
|
||||
|
||||
> ## Note
|
||||
> This section describes the default configuration. The error serializer can be
|
||||
> mapped to a different key using the [`serializers`](#opt-serializers) option.
|
||||
```js
|
||||
logger.info(new Error("test"))
|
||||
// {"level":30,"time":1531257618044,"msg":"test","stack":"...","type":"Error","pid":55956,"hostname":"x"}
|
||||
|
||||
logger.info({ err: new Error("test"), otherkey: 123 }, "some text")
|
||||
// {"level":30,"time":1531257618044,"err":{"msg": "test", "stack":"...","type":"Error"},"msg":"some text","pid":55956,"hostname":"x","otherkey":123}
|
||||
```
|
||||
|
||||
<a id="trace"></a>
|
||||
### `logger.trace([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'trace'` level log, if the configured [`level`](#level) allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="debug"></a>
|
||||
### `logger.debug([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'debug'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="info"></a>
|
||||
### `logger.info([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write an `'info'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="warn"></a>
|
||||
### `logger.warn([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'warn'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="error"></a>
|
||||
### `logger.error([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'error'` level log, if the configured `level` allows for it.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="fatal"></a>
|
||||
### `logger.fatal([mergingObject], [message], [...interpolationValues])`
|
||||
|
||||
Write a `'fatal'` level log, if the configured `level` allows for it.
|
||||
|
||||
Since `'fatal'` level messages are intended to be logged just before the process exiting the `fatal`
|
||||
method will always sync flush the destination.
|
||||
Therefore it's important not to misuse `fatal` since
|
||||
it will cause performance overhead if used for any
|
||||
other purpose than writing final log messages before
|
||||
the process crashes or exits.
|
||||
|
||||
* See [`mergingObject` log method parameter](#mergingobject)
|
||||
* See [`message` log method parameter](#message)
|
||||
* See [`...interpolationValues` log method parameter](#interpolationvalues)
|
||||
|
||||
<a id="silent"><a>
|
||||
### `logger.silent()`
|
||||
|
||||
Noop function.
|
||||
|
||||
<a id="child"></a>
|
||||
### `logger.child(bindings, [options]) => logger`
|
||||
|
||||
The `logger.child` method allows for the creation of stateful loggers,
|
||||
where key-value pairs can be pinned to a logger causing them to be output
|
||||
on every log line.
|
||||
|
||||
Child loggers use the same output stream as the parent and inherit
|
||||
the current log level of the parent at the time they are spawned.
|
||||
|
||||
The log level of a child is mutable. It can be set independently
|
||||
of the parent either by setting the [`level`](#level) accessor after creating
|
||||
the child logger or using the [`options.level`](#optionslevel-string) key.
|
||||
|
||||
<a id="logger-child-bindings"></a>
|
||||
#### `bindings` (Object)
|
||||
|
||||
An object of key-value pairs to include in every log line output
|
||||
via the returned child logger.
|
||||
|
||||
```js
|
||||
const child = logger.child({ MIX: {IN: 'always'} })
|
||||
child.info('hello')
|
||||
// {"level":30,"time":1531258616689,"msg":"hello","pid":64849,"hostname":"x","MIX":{"IN":"always"}}
|
||||
child.info('child!')
|
||||
// {"level":30,"time":1531258617401,"msg":"child!","pid":64849,"hostname":"x","MIX":{"IN":"always"}}
|
||||
```
|
||||
|
||||
The `bindings` object may contain any key except for reserved configuration keys `level` and `serializers`.
|
||||
|
||||
##### `bindings.serializers` (Object) - DEPRECATED
|
||||
|
||||
Use `options.serializers` instead.
|
||||
|
||||
#### `options` (Object)
|
||||
|
||||
Options for child logger. These options will override the parent logger options.
|
||||
|
||||
##### `options.level` (String)
|
||||
|
||||
The `level` property overrides the log level of the child logger.
|
||||
By default, the parent log level is inherited.
|
||||
After the creation of the child logger, it is also accessible using the [`logger.level`](#logger-level) key.
|
||||
|
||||
```js
|
||||
const logger = pino()
|
||||
logger.debug('nope') // will not log, since default level is info
|
||||
const child = logger.child({foo: 'bar'}, {level: 'debug'})
|
||||
child.debug('debug!') // will log as the `level` property set the level to debug
|
||||
```
|
||||
|
||||
##### `options.msgPrefix` (String)
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The `msgPrefix` property allows you to specify a prefix for every message of the child logger.
|
||||
By default, the parent prefix is inherited.
|
||||
If the parent already has a prefix, the prefix of the parent and then the child will be displayed.
|
||||
|
||||
```js
|
||||
const logger = pino({
|
||||
msgPrefix: '[HTTP] '
|
||||
})
|
||||
logger.info('got new request!')
|
||||
// > [HTTP] got new request!
|
||||
|
||||
const child = logger.child({avengers: 'assemble'}, {msgPrefix: '[Proxy] '})
|
||||
child.info('message proxied!')
|
||||
// > [HTTP] [Proxy] message proxied!
|
||||
```
|
||||
|
||||
##### `options.redact` (Array | Object)
|
||||
|
||||
Setting `options.redact` to an array or object will override the parent `redact` options. To remove `redact` options inherited from the parent logger set this value as an empty array (`[]`).
|
||||
|
||||
```js
|
||||
const logger = require('pino')({ redact: ['hello'] })
|
||||
logger.info({ hello: 'world' })
|
||||
// {"level":30,"time":1625794363403,"pid":67930,"hostname":"x","hello":"[Redacted]"}
|
||||
const child = logger.child({ foo: 'bar' }, { redact: ['foo'] })
|
||||
logger.info({ hello: 'world' })
|
||||
// {"level":30,"time":1625794553558,"pid":67930,"hostname":"x","hello":"world", "foo": "[Redacted]" }
|
||||
```
|
||||
|
||||
* See [`redact` option](#opt-redact)
|
||||
|
||||
##### `options.serializers` (Object)
|
||||
|
||||
Child loggers inherit the [serializers](#opt-serializers) from the parent logger.
|
||||
|
||||
Setting the `serializers` key of the `options` object will override
|
||||
any configured parent serializers.
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.info({test: 'will appear'})
|
||||
// {"level":30,"time":1531259759482,"pid":67930,"hostname":"x","test":"will appear"}
|
||||
const child = logger.child({}, {serializers: {test: () => `child-only serializer`}})
|
||||
child.info({test: 'will be overwritten'})
|
||||
// {"level":30,"time":1531259784008,"pid":67930,"hostname":"x","test":"child-only serializer"}
|
||||
```
|
||||
|
||||
* See [`serializers` option](#opt-serializers)
|
||||
* See [pino.stdSerializers](#pino-stdSerializers)
|
||||
|
||||
<a id="logger-bindings"></a>
|
||||
### `logger.bindings()`
|
||||
|
||||
Returns an object containing all the current bindings, cloned from the ones passed in via `logger.child()`.
|
||||
```js
|
||||
const child = logger.child({ foo: 'bar' })
|
||||
console.log(child.bindings())
|
||||
// { foo: 'bar' }
|
||||
const anotherChild = child.child({ MIX: { IN: 'always' } })
|
||||
console.log(anotherChild.bindings())
|
||||
// { foo: 'bar', MIX: { IN: 'always' } }
|
||||
```
|
||||
|
||||
<a id="logger-set-bindings"></a>
|
||||
### `logger.setBindings(bindings)`
|
||||
|
||||
Adds to the bindings of this logger instance.
|
||||
|
||||
**Note:** Does not overwrite bindings. Can potentially result in duplicate keys in
|
||||
log lines.
|
||||
|
||||
* See [`bindings` parameter in `logger.child`](#logger-child-bindings)
|
||||
|
||||
<a id="flush"></a>
|
||||
### `logger.flush([cb])`
|
||||
|
||||
Flushes the content of the buffer when using `pino.destination({
|
||||
sync: false })`.
|
||||
|
||||
This is an asynchronous, best used as fire and forget, operation.
|
||||
|
||||
The use case is primarily for asynchronous logging, which may buffer
|
||||
log lines while others are being written. The `logger.flush` method can be
|
||||
used to flush the logs
|
||||
on a long interval, say ten seconds. Such a strategy can provide an
|
||||
optimum balance between extremely efficient logging at high demand periods
|
||||
and safer logging at low demand periods.
|
||||
|
||||
If there is a need to wait for the logs to be flushed, a callback should be used.
|
||||
|
||||
* See [`destination` parameter](#destination)
|
||||
* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
|
||||
|
||||
<a id="logger-level"></a>
|
||||
### `logger.level` (String) [Getter/Setter]
|
||||
|
||||
Set this property to the desired logging level.
|
||||
|
||||
The core levels and their values are as follows:
|
||||
|
||||
| | | | | | | | |
|
||||
|:-----------|-------|-------|------|------|-------|-------|---------:|
|
||||
| **Level:** | trace | debug | info | warn | error | fatal | silent |
|
||||
| **Value:** | 10 | 20 | 30 | 40 | 50 | 60 | Infinity |
|
||||
|
||||
The logging level is a *minimum* level based on the associated value of that level.
|
||||
|
||||
For instance if `logger.level` is `info` *(30)* then `info` *(30)*, `warn` *(40)*, `error` *(50)*, and `fatal` *(60)* log methods will be enabled but the `trace` *(10)* and `debug` *(20)* methods, being less than 30, will not.
|
||||
|
||||
The `silent` logging level is a specialized level that will disable all logging,
|
||||
the `silent` log method is a noop function.
|
||||
|
||||
<a id="islevelenabled"></a>
|
||||
### `logger.isLevelEnabled(level)`
|
||||
|
||||
A utility method for determining if a given log level will write to the destination.
|
||||
|
||||
#### `level` (String)
|
||||
|
||||
The given level to check against:
|
||||
|
||||
```js
|
||||
if (logger.isLevelEnabled('debug')) logger.debug('conditional log')
|
||||
```
|
||||
|
||||
#### `levelLabel` (String)
|
||||
|
||||
Defines the method name of the new level.
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
#### `levelValue` (Number)
|
||||
|
||||
Defines the associated minimum threshold value for the level, and
|
||||
therefore where it sits in order of priority among other levels.
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
<a id="levelVal"></a>
|
||||
### `logger.levelVal` (Number)
|
||||
|
||||
Supplies the integer value for the current logging level.
|
||||
|
||||
```js
|
||||
if (logger.levelVal === 30) {
|
||||
console.log('logger level is `info`')
|
||||
}
|
||||
```
|
||||
|
||||
<a id="levels"></a>
|
||||
### `logger.levels` (Object)
|
||||
|
||||
Levels are mapped to values to determine the minimum threshold that a
|
||||
logging method should be enabled at (see [`logger.level`](#level)).
|
||||
|
||||
The `logger.levels` property holds the mappings between levels and values,
|
||||
and vice versa.
|
||||
|
||||
```sh
|
||||
$ node -p "require('pino')().levels"
|
||||
```
|
||||
|
||||
```js
|
||||
{ labels:
|
||||
{ '10': 'trace',
|
||||
'20': 'debug',
|
||||
'30': 'info',
|
||||
'40': 'warn',
|
||||
'50': 'error',
|
||||
'60': 'fatal' },
|
||||
values:
|
||||
{ fatal: 60, error: 50, warn: 40, info: 30, debug: 20, trace: 10 } }
|
||||
```
|
||||
|
||||
* See [`logger.level`](#level)
|
||||
|
||||
<a id="serializers"></a>
|
||||
### logger\[Symbol.for('pino.serializers')\]
|
||||
|
||||
Returns the serializers as applied to the current logger instance. If a child logger did not
|
||||
register its own serializer upon instantiation the serializers of the parent will be returned.
|
||||
|
||||
<a id="level-change"></a>
|
||||
### Event: 'level-change'
|
||||
|
||||
The logger instance is also an [`EventEmitter ⇗`](https://nodejs.org/dist/latest/docs/api/events.html#events_class_eventemitter)
|
||||
|
||||
A listener function can be attached to a logger via the `level-change` event
|
||||
|
||||
The listener is passed five arguments:
|
||||
|
||||
* `levelLabel` – the new level string, e.g `trace`
|
||||
* `levelValue` – the new level number, e.g `10`
|
||||
* `previousLevelLabel` – the prior level string, e.g `info`
|
||||
* `previousLevelValue` – the prior level number, e.g `30`
|
||||
* `logger` – the logger instance from which the event originated
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.on('level-change', (lvl, val, prevLvl, prevVal) => {
|
||||
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
|
||||
})
|
||||
logger.level = 'trace' // trigger event
|
||||
```
|
||||
|
||||
Please note that due to a [known bug](https://github.com/pinojs/pino/issues/1006), every `logger.child()` call will
|
||||
fire a `level-change` event. These events can be ignored by writing an event handler like:
|
||||
|
||||
```js
|
||||
const logger = require('pino')()
|
||||
logger.on('level-change', function (lvl, val, prevLvl, prevVal, instance) {
|
||||
if (logger !== instance) {
|
||||
return
|
||||
}
|
||||
console.log('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
|
||||
})
|
||||
logger.child({}); // trigger an event by creating a child instance, notice no console.log
|
||||
logger.level = 'trace' // trigger event using actual value change, notice console.log
|
||||
```
|
||||
|
||||
<a id="version"></a>
|
||||
### `logger.version` (String)
|
||||
|
||||
Exposes the Pino package version. Also available on the exported `pino` function.
|
||||
|
||||
* See [`pino.version`](#pino-version)
|
||||
|
||||
## Statics
|
||||
|
||||
<a id="pino-destination"></a>
|
||||
### `pino.destination([opts]) => SonicBoom`
|
||||
|
||||
Create a Pino Destination instance: a stream-like object with
|
||||
significantly more throughput than a standard Node.js stream.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino(pino.destination('./my-file'))
|
||||
const logger2 = pino(pino.destination())
|
||||
const logger3 = pino(pino.destination({
|
||||
dest: './my-file',
|
||||
minLength: 4096, // Buffer before writing
|
||||
sync: false // Asynchronous logging, the default
|
||||
}))
|
||||
const logger4 = pino(pino.destination({
|
||||
dest: './my-file2',
|
||||
sync: true // Synchronous logging
|
||||
}))
|
||||
```
|
||||
|
||||
The `pino.destination` method may be passed a file path or a numerical file descriptor.
|
||||
By default, `pino.destination` will use `process.stdout.fd` (1) as the file descriptor.
|
||||
|
||||
`pino.destination` is implemented on [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom).
|
||||
|
||||
A `pino.destination` instance can also be used to reopen closed files
|
||||
(for example, for some log rotation scenarios), see [Reopening log files](/docs/help.md#reopening).
|
||||
|
||||
* See [`destination` parameter](#destination)
|
||||
* See [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom)
|
||||
* See [Reopening log files](/docs/help.md#reopening)
|
||||
* See [Asynchronous Logging ⇗](/docs/asynchronous.md)
|
||||
|
||||
<a id="pino-transport"></a>
|
||||
### `pino.transport(options) => ThreadStream`
|
||||
|
||||
Create a stream that routes logs to a worker thread that
|
||||
wraps around a [Pino Transport](/docs/transports.md).
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'some-transport',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
Multiple transports may also be defined, and specific levels can be logged to each transport:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: 'pino-pretty' // must be installed separately
|
||||
}, {
|
||||
level: 'trace',
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/store/logs' }
|
||||
}]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
A pipeline could also be created to transform log lines _before_ sending them:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: 'pino-syslog' // must be installed separately
|
||||
}, {
|
||||
target: 'pino-socket' // must be installed separately
|
||||
}]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
If `WeakRef`, `WeakMap`, and `FinalizationRegistry` are available in the current runtime (v14.5.0+), then the thread
|
||||
will be automatically terminated in case the stream or logger goes out of scope.
|
||||
The `transport()` function adds a listener to `process.on('beforeExit')` and `process.on('exit')` to ensure the worker
|
||||
is flushed and all data synced before the process exits.
|
||||
|
||||
Note that calling `process.exit()` on the main thread will stop the event loop on the main thread from turning. As a result,
|
||||
using `console.log` and `process.stdout` after the main thread called `process.exit()` will not produce any output.
|
||||
|
||||
If you are embedding/integrating pino within your framework, you will need to make pino aware of the script that is calling it,
|
||||
like so:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const getCaller = require('get-caller-file')
|
||||
|
||||
module.exports = function build () {
|
||||
const logger = pino({
|
||||
transport: {
|
||||
caller: getCaller(),
|
||||
target: 'transport',
|
||||
options: { destination: './destination' }
|
||||
}
|
||||
})
|
||||
return logger
|
||||
}
|
||||
```
|
||||
|
||||
For more on transports, how they work, and how to create them see the [`Transports documentation`](/docs/transports.md).
|
||||
|
||||
* See [`Transports`](/docs/transports.md)
|
||||
* See [`thread-stream` ⇗](https://github.com/mcollina/thread-stream)
|
||||
|
||||
#### Options
|
||||
|
||||
* `target`: The transport to pass logs through. This may be an installed module name or an absolute path.
|
||||
* `options`: An options object which is serialized (see [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)), passed to the worker thread, parsed and then passed to the exported transport function.
|
||||
* `worker`: [Worker thread](https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options) configuration options. Additionally, the `worker` option supports `worker.autoEnd`. If this is set to `false` logs will not be flushed on process exit. It is then up to the developer to call `transport.end()` to flush logs.
|
||||
* `targets`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options plus a `level` option which will send only logs above a specified level to a transport.
|
||||
* `pipeline`: May be specified instead of `target`. Must be an array of transport configurations. Transport configurations include the aforementioned `options` and `target` options. All intermediate steps in the pipeline _must_ be `Transform` streams and not `Writable`.
|
||||
* `dedupe`: See [pino.multistream options](#pino-multistream)
|
||||
|
||||
<a id="pino-multistream"></a>
|
||||
|
||||
### `pino.multistream(streamsArray, opts) => MultiStreamRes`
|
||||
|
||||
Create a stream composed by multiple destination streams and returns an
|
||||
object implementing the [MultiStreamRes](#multistreamres) interface.
|
||||
|
||||
```js
|
||||
var fs = require('fs')
|
||||
var pino = require('pino')
|
||||
var pretty = require('pino-pretty')
|
||||
var streams = [
|
||||
{stream: fs.createWriteStream('/tmp/info.stream.out')},
|
||||
{stream: pretty() },
|
||||
{level: 'debug', stream: fs.createWriteStream('/tmp/debug.stream.out')},
|
||||
{level: 'fatal', stream: fs.createWriteStream('/tmp/fatal.stream.out')}
|
||||
]
|
||||
|
||||
var log = pino({
|
||||
level: 'debug' // this MUST be set at the lowest level of the
|
||||
// destinations
|
||||
}, pino.multistream(streams))
|
||||
|
||||
log.debug('this will be written to /tmp/debug.stream.out')
|
||||
log.info('this will be written to /tmp/debug.stream.out and /tmp/info.stream.out')
|
||||
log.fatal('this will be written to /tmp/debug.stream.out, /tmp/info.stream.out and /tmp/fatal.stream.out')
|
||||
```
|
||||
|
||||
In order for `multistream` to work, the log level __must__ be set to the lowest level used in the streams array. Default is `info`.
|
||||
|
||||
#### Options
|
||||
|
||||
* `levels`: Pass custom log level definitions to the instance as an object.
|
||||
|
||||
+ `dedupe`: Set this to `true` to send logs only to the stream with the higher level. Default: `false`
|
||||
|
||||
`dedupe` flag can be useful for example when using `pino.multistream` to redirect `error` logs to `process.stderr` and others to `process.stdout`:
|
||||
|
||||
```js
|
||||
var pino = require('pino')
|
||||
var multistream = pino.multistream
|
||||
var streams = [
|
||||
{level: 'debug', stream: process.stdout},
|
||||
{level: 'error', stream: process.stderr},
|
||||
]
|
||||
|
||||
var opts = {
|
||||
levels: {
|
||||
silent: Infinity,
|
||||
fatal: 60,
|
||||
error: 50,
|
||||
warn: 50,
|
||||
info: 30,
|
||||
debug: 20,
|
||||
trace: 10
|
||||
},
|
||||
dedupe: true,
|
||||
}
|
||||
|
||||
var log = pino({
|
||||
level: 'debug' // this MUST be set at the lowest level of the
|
||||
// destinations
|
||||
}, multistream(streams, opts))
|
||||
|
||||
log.debug('this will be written ONLY to process.stdout')
|
||||
log.info('this will be written ONLY to process.stdout')
|
||||
log.error('this will be written ONLY to process.stderr')
|
||||
log.fatal('this will be written ONLY to process.stderr')
|
||||
```
|
||||
|
||||
<a id="pino-stdserializers"></a>
|
||||
### `pino.stdSerializers` (Object)
|
||||
|
||||
The `pino.stdSerializers` object provides functions for serializing objects common to many projects. The standard serializers are directly imported from [pino-std-serializers](https://github.com/pinojs/pino-std-serializers).
|
||||
|
||||
* See [pino-std-serializers ⇗](https://github.com/pinojs/pino-std-serializers)
|
||||
|
||||
<a id="pino-stdtimefunctions"></a>
|
||||
### `pino.stdTimeFunctions` (Object)
|
||||
|
||||
The [`timestamp`](#opt-timestamp) option can accept a function that determines the
|
||||
`timestamp` value in a log line.
|
||||
|
||||
The `pino.stdTimeFunctions` object provides a very small set of common functions for generating the
|
||||
`timestamp` property. These consist of the following
|
||||
|
||||
* `pino.stdTimeFunctions.epochTime`: Milliseconds since Unix epoch (Default)
|
||||
* `pino.stdTimeFunctions.unixTime`: Seconds since Unix epoch
|
||||
* `pino.stdTimeFunctions.nullTime`: Clears timestamp property (Used when `timestamp: false`)
|
||||
* `pino.stdTimeFunctions.isoTime`: ISO 8601-formatted time in UTC
|
||||
|
||||
* See [`timestamp` option](#opt-timestamp)
|
||||
|
||||
<a id="pino-symbols"></a>
|
||||
### `pino.symbols` (Object)
|
||||
|
||||
For integration purposes with ecosystem and third-party libraries `pino.symbols`
|
||||
exposes the symbols used to hold non-public state and methods on the logger instance.
|
||||
|
||||
Access to the symbols allows logger state to be adjusted, and methods to be overridden or
|
||||
proxied for performant integration where necessary.
|
||||
|
||||
The `pino.symbols` object is intended for library implementers and shouldn't be utilized
|
||||
for general use.
|
||||
|
||||
<a id="pino-version"></a>
|
||||
### `pino.version` (String)
|
||||
|
||||
Exposes the Pino package version. Also available on the logger instance.
|
||||
|
||||
* See [`logger.version`](#version)
|
||||
|
||||
## Interfaces
|
||||
<a id="pino-multistreamres"></a>
|
||||
|
||||
### `MultiStreamRes`
|
||||
Properties:
|
||||
|
||||
* `write(data)`
|
||||
- `data` Object | string
|
||||
- Returns: void
|
||||
|
||||
Write `data` onto the streams held by the current instance.
|
||||
* `add(dest)`
|
||||
- `dest` [StreamEntry](#streamentry) | [DestinationStream](#destinationstream)
|
||||
- Returns: [MultiStreamRes](#multistreamres)
|
||||
|
||||
Add `dest` stream to the array of streams of the current instance.
|
||||
* `flushSync()`
|
||||
- Returns: `undefined`
|
||||
|
||||
Call `flushSync` on each stream held by the current instance.
|
||||
* `minLevel`
|
||||
- number
|
||||
|
||||
The minimum level amongst all the streams held by the current instance.
|
||||
* `streams`
|
||||
- Returns: [StreamEntry[]](#streamentry)
|
||||
|
||||
The array of streams currently held by the current instance.
|
||||
* `clone(level)`
|
||||
- `level` [Level](#level-1)
|
||||
- Returns: [MultiStreamRes](#multistreamres)
|
||||
|
||||
Returns a cloned object of the current instance but with the provided `level`.
|
||||
|
||||
### `StreamEntry`
|
||||
Properties:
|
||||
|
||||
* `stream`
|
||||
- DestinationStream
|
||||
* `level`
|
||||
- Optional: [Level](#level-1)
|
||||
|
||||
### `DestinationStream`
|
||||
Properties:
|
||||
|
||||
* `write(msg)`
|
||||
- `msg` string
|
||||
|
||||
## Types
|
||||
### `Level`
|
||||
|
||||
* Values: `"fatal"` | `"error"` | `"warn"` | `"info"` | `"debug"` | `"trace"`
|
||||
@@ -0,0 +1,40 @@
|
||||
# Asynchronous Logging
|
||||
|
||||
Asynchronous logging enables the minimum overhead of Pino.
|
||||
Asynchronous logging works by buffering log messages and writing them in larger chunks.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino(pino.destination({
|
||||
dest: './my-file', // omit for stdout
|
||||
minLength: 4096, // Buffer before writing
|
||||
sync: false // Asynchronous logging
|
||||
}))
|
||||
```
|
||||
|
||||
It's always possible to turn on synchronous logging by passing `sync: true`.
|
||||
In this mode of operation, log messages are directly written to the
|
||||
output stream as the messages are generated with a _blocking_ operation.
|
||||
|
||||
* See [`pino.destination`](/docs/api.md#pino-destination)
|
||||
* `pino.destination` is implemented on [`sonic-boom` ⇗](https://github.com/mcollina/sonic-boom).
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
Asynchronous logging is disabled by default on AWS Lambda or any other environment
|
||||
that modifies `process.stdout`. If forcefully turned on, we recommend calling `dest.flushSync()` at the end
|
||||
of each function execution to avoid losing data.
|
||||
|
||||
## Caveats
|
||||
|
||||
Asynchronous logging has a couple of important caveats:
|
||||
|
||||
* As opposed to the synchronous mode, there is not a one-to-one relationship between
|
||||
calls to logging methods (e.g. `logger.info`) and writes to a log file
|
||||
* There is a possibility of the most recently buffered log messages being lost
|
||||
in case of a system failure, e.g. a power cut.
|
||||
|
||||
See also:
|
||||
|
||||
* [`pino.destination` API](/docs/api.md#pino-destination)
|
||||
* [`destination` parameter](/docs/api.md#destination)
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
# Benchmarks
|
||||
|
||||
`pino.info('hello world')`:
|
||||
|
||||
```
|
||||
|
||||
BASIC benchmark averages
|
||||
Bunyan average: 377.434ms
|
||||
Winston average: 270.249ms
|
||||
Bole average: 172.690ms
|
||||
Debug average: 220.527ms
|
||||
LogLevel average: 222.802ms
|
||||
Pino average: 114.801ms
|
||||
PinoMinLength average: 70.968ms
|
||||
PinoNodeStream average: 159.192ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info({'hello': 'world'})`:
|
||||
|
||||
```
|
||||
|
||||
OBJECT benchmark averages
|
||||
BunyanObj average: 410.379ms
|
||||
WinstonObj average: 273.120ms
|
||||
BoleObj average: 185.069ms
|
||||
LogLevelObject average: 433.425ms
|
||||
PinoObj average: 119.315ms
|
||||
PinoMinLengthObj average: 76.968ms
|
||||
PinoNodeStreamObj average: 164.268ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info(aBigDeeplyNestedObject)`:
|
||||
|
||||
```
|
||||
|
||||
DEEP-OBJECT benchmark averages
|
||||
BunyanDeepObj average: 1.839ms
|
||||
WinstonDeepObj average: 5.604ms
|
||||
BoleDeepObj average: 3.422ms
|
||||
LogLevelDeepObj average: 11.716ms
|
||||
PinoDeepObj average: 2.256ms
|
||||
PinoMinLengthDeepObj average: 2.240ms
|
||||
PinoNodeStreamDeepObj average: 2.595ms
|
||||
|
||||
```
|
||||
|
||||
`pino.info('hello %s %j %d', 'world', {obj: true}, 4, {another: 'obj'})`:
|
||||
|
||||
For a fair comparison, [LogLevel](http://npm.im/loglevel) was extended
|
||||
to include a timestamp and [bole](http://npm.im/bole) had
|
||||
`fastTime` mode switched on.
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# Browser API
|
||||
|
||||
Pino is compatible with [`browserify`](https://npm.im/browserify) for browser-side usage:
|
||||
|
||||
This can be useful with isomorphic/universal JavaScript code.
|
||||
|
||||
By default, in the browser,
|
||||
`pino` uses corresponding [Log4j](https://en.wikipedia.org/wiki/Log4j) `console` methods (`console.error`, `console.warn`, `console.info`, `console.debug`, `console.trace`) and uses `console.error` for any `fatal` level logs.
|
||||
|
||||
## Options
|
||||
|
||||
Pino can be passed a `browser` object in the options object,
|
||||
which can have the following properties:
|
||||
|
||||
### `asObject` (Boolean)
|
||||
|
||||
```js
|
||||
const pino = require('pino')({browser: {asObject: true}})
|
||||
```
|
||||
|
||||
The `asObject` option will create a pino-like log object instead of
|
||||
passing all arguments to a console method, for instance:
|
||||
|
||||
```js
|
||||
pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: <ts>}
|
||||
```
|
||||
|
||||
When `write` is set, `asObject` will always be `true`.
|
||||
|
||||
### `formatters` (Object)
|
||||
|
||||
An object containing functions for formatting the shape of the log lines. When provided, it enables the logger to produce a pino-like log object with customized formatting. Currently, it supports formatting for the `level` object only.
|
||||
|
||||
##### `level`
|
||||
|
||||
Changes the shape of the log level. The default shape is `{ level: number }`.
|
||||
The function takes two arguments, the label of the level (e.g. `'info'`)
|
||||
and the numeric value (e.g. `30`).
|
||||
|
||||
```js
|
||||
const formatters = {
|
||||
level (label, number) {
|
||||
return { level: number }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `write` (Function | Object)
|
||||
|
||||
Instead of passing log messages to `console.log` they can be passed to
|
||||
a supplied function.
|
||||
|
||||
If `write` is set to a single function, all logging objects are passed
|
||||
to this function.
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
write: (o) => {
|
||||
// do something with o
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If `write` is an object, it can have methods that correspond to the
|
||||
levels. When a message is logged at a given level, the corresponding
|
||||
method is called. If a method isn't present, the logging falls back
|
||||
to using the `console`.
|
||||
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
write: {
|
||||
info: function (o) {
|
||||
//process info log object
|
||||
},
|
||||
error: function (o) {
|
||||
//process error log object
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `serialize`: (Boolean | Array)
|
||||
|
||||
The serializers provided to `pino` are ignored by default in the browser, including
|
||||
the standard serializers provided with Pino. Since the default destination for log
|
||||
messages is the console, values such as `Error` objects are enhanced for inspection,
|
||||
which they otherwise wouldn't be if the Error serializer was enabled.
|
||||
|
||||
We can turn all serializers on,
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
serialize: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Or we can selectively enable them via an array:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
serializers: {
|
||||
custom: myCustomSerializer,
|
||||
another: anotherSerializer
|
||||
},
|
||||
browser: {
|
||||
serialize: ['custom']
|
||||
}
|
||||
})
|
||||
// following will apply myCustomSerializer to the custom property,
|
||||
// but will not apply anotherSerializer to another key
|
||||
pino.info({custom: 'a', another: 'b'})
|
||||
```
|
||||
|
||||
When `serialize` is `true` the standard error serializer is also enabled (see https://github.com/pinojs/pino/blob/master/docs/api.md#stdSerializers).
|
||||
This is a global serializer, which will apply to any `Error` objects passed to the logger methods.
|
||||
|
||||
If `serialize` is an array the standard error serializer is also automatically enabled, it can
|
||||
be explicitly disabled by including a string in the serialize array: `!stdSerializers.err`, like so:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
serializers: {
|
||||
custom: myCustomSerializer,
|
||||
another: anotherSerializer
|
||||
},
|
||||
browser: {
|
||||
serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `serialize` array also applies to any child logger serializers (see https://github.com/pinojs/pino/blob/master/docs/api.md#discussion-2
|
||||
for how to set child-bound serializers).
|
||||
|
||||
Unlike server pino the serializers apply to every object passed to the logger method,
|
||||
if the `asObject` option is `true`, this results in the serializers applying to the
|
||||
first object (as in server pino).
|
||||
|
||||
For more info on serializers see https://github.com/pinojs/pino/blob/master/docs/api.md#parameters.
|
||||
|
||||
### `transmit` (Object)
|
||||
|
||||
An object with `send` and `level` properties.
|
||||
|
||||
The `transmit.level` property specifies the minimum level (inclusive) of when the `send` function
|
||||
should be called, if not supplied the `send` function be called based on the main logging `level`
|
||||
(set via `options.level`, defaulting to `info`).
|
||||
|
||||
The `transmit` object must have a `send` function which will be called after
|
||||
writing the log message. The `send` function is passed the level of the log
|
||||
message and a `logEvent` object.
|
||||
|
||||
The `logEvent` object is a data structure representing a log message, it represents
|
||||
the arguments passed to a logger statement, the level
|
||||
at which they were logged, and the hierarchy of child bindings.
|
||||
|
||||
The `logEvent` format is structured like so:
|
||||
|
||||
```js
|
||||
{
|
||||
ts = Number,
|
||||
messages = Array,
|
||||
bindings = Array,
|
||||
level: { label = String, value = Number}
|
||||
}
|
||||
```
|
||||
|
||||
The `ts` property is a Unix epoch timestamp in milliseconds, the time is taken from the moment the
|
||||
logger method is called.
|
||||
|
||||
The `messages` array is all arguments passed to logger method, (for instance `logger.info('a', 'b', 'c')`
|
||||
would result in `messages` array `['a', 'b', 'c']`).
|
||||
|
||||
The `bindings` array represents each child logger (if any), and the relevant bindings.
|
||||
For instance, given `logger.child({a: 1}).child({b: 2}).info({c: 3})`, the bindings array
|
||||
would hold `[{a: 1}, {b: 2}]` and the `messages` array would be `[{c: 3}]`. The `bindings`
|
||||
are ordered according to their position in the child logger hierarchy, with the lowest index
|
||||
being the top of the hierarchy.
|
||||
|
||||
By default, serializers are not applied to log output in the browser, but they will *always* be
|
||||
applied to `messages` and `bindings` in the `logEvent` object. This allows us to ensure a consistent
|
||||
format for all values between server and client.
|
||||
|
||||
The `level` holds the label (for instance `info`), and the corresponding numerical value
|
||||
(for instance `30`). This could be important in cases where client-side level values and
|
||||
labels differ from server-side.
|
||||
|
||||
The point of the `send` function is to remotely record log messages:
|
||||
|
||||
```js
|
||||
const pino = require('pino')({
|
||||
browser: {
|
||||
transmit: {
|
||||
level: 'warn',
|
||||
send: function (level, logEvent) {
|
||||
if (level === 'warn') {
|
||||
// maybe send the logEvent to a separate endpoint
|
||||
// or maybe analyze the messages further before sending
|
||||
}
|
||||
// we could also use the `logEvent.level.value` property to determine
|
||||
// numerical value
|
||||
if (logEvent.level.value >= 50) { // covers error and fatal
|
||||
|
||||
// send the logEvent somewhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `disabled` (Boolean)
|
||||
|
||||
```js
|
||||
const pino = require('pino')({browser: {disabled: true}})
|
||||
```
|
||||
|
||||
The `disabled` option will disable logging in browser if set
|
||||
to `true`, by default it is set to `false`.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Bundling
|
||||
|
||||
Due to its internal architecture based on Worker Threads, it is not possible to bundle Pino *without* generating additional files.
|
||||
|
||||
In particular, a bundler must ensure that the following files are also bundled separately:
|
||||
|
||||
* `lib/worker.js` from the `thread-stream` dependency
|
||||
* `file.js`
|
||||
* `lib/worker.js`
|
||||
* `lib/worker-pipeline.js`
|
||||
* Any transport used by the user (like `pino-pretty`)
|
||||
|
||||
Once the files above have been generated, the bundler must also add information about the files above by injecting a code that sets `__bundlerPathsOverrides` in the `globalThis` object.
|
||||
|
||||
The variable is an object whose keys are an identifier for the files and the values are the paths of files relative to the currently bundle files.
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
// Inject this using your bundle plugin
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
'thread-stream-worker': pinoWebpackAbsolutePath('./thread-stream-worker.js')
|
||||
'pino/file': pinoWebpackAbsolutePath('./pino-file.js'),
|
||||
'pino-worker': pinoWebpackAbsolutePath('./pino-worker.js'),
|
||||
'pino-pipeline-worker': pinoWebpackAbsolutePath('./pino-pipeline-worker.js'),
|
||||
'pino-pretty': pinoWebpackAbsolutePath('./pino-pretty.js'),
|
||||
};
|
||||
```
|
||||
|
||||
Note that `pino/file`, `pino-worker`, `pino-pipeline-worker`, and `thread-stream-worker` are required identifiers. Other identifiers are possible based on the user configuration.
|
||||
|
||||
## Webpack Plugin
|
||||
|
||||
If you are a Webpack user, you can achieve this with [pino-webpack-plugin](https://github.com/pinojs/pino-webpack-plugin) without manual configuration of `__bundlerPathsOverrides`; however, you still need to configure it manually if you are using other bundlers.
|
||||
|
||||
## Esbuild Plugin
|
||||
|
||||
[esbuild-plugin-pino](https://github.com/davipon/esbuild-plugin-pino) is the esbuild plugin to generate extra pino files for bundling.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Child loggers
|
||||
|
||||
Let's assume we want to have `"module":"foo"` added to every log within a
|
||||
module `foo.js`.
|
||||
|
||||
To accomplish this, simply use a child logger:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
// imports a pino logger instance of `require('pino')()`
|
||||
const parentLogger = require('./lib/logger')
|
||||
const log = parentLogger.child({module: 'foo'})
|
||||
|
||||
function doSomething () {
|
||||
log.info('doSomething invoked')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
doSomething
|
||||
}
|
||||
```
|
||||
|
||||
## Cost of child logging
|
||||
|
||||
Child logger creation is fast:
|
||||
|
||||
```
|
||||
benchBunyanCreation*10000: 564.514ms
|
||||
benchBoleCreation*10000: 283.276ms
|
||||
benchPinoCreation*10000: 258.745ms
|
||||
benchPinoExtremeCreation*10000: 150.506ms
|
||||
```
|
||||
|
||||
Logging through a child logger has little performance penalty:
|
||||
|
||||
```
|
||||
benchBunyanChild*10000: 556.275ms
|
||||
benchBoleChild*10000: 288.124ms
|
||||
benchPinoChild*10000: 231.695ms
|
||||
benchPinoExtremeChild*10000: 122.117ms
|
||||
```
|
||||
|
||||
Logging via the child logger of a child logger also has negligible overhead:
|
||||
|
||||
```
|
||||
benchBunyanChildChild*10000: 559.082ms
|
||||
benchPinoChildChild*10000: 229.264ms
|
||||
benchPinoExtremeChildChild*10000: 127.753ms
|
||||
```
|
||||
|
||||
## Duplicate keys caveat
|
||||
|
||||
Naming conflicts can arise between child loggers and
|
||||
children of child loggers.
|
||||
|
||||
This isn't as bad as it sounds, even if the same keys between
|
||||
parent and child loggers are used, Pino resolves the conflict in the sanest way.
|
||||
|
||||
For example, consider the following:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
pino(pino.destination('./my-log'))
|
||||
.child({a: 'property'})
|
||||
.child({a: 'prop'})
|
||||
.info('howdy')
|
||||
```
|
||||
|
||||
```sh
|
||||
$ cat my-log
|
||||
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":1459534114473,"a":"property","a":"prop"}
|
||||
```
|
||||
|
||||
Notice how there are two keys named `a` in the JSON output. The sub-child's properties
|
||||
appear after the parent child properties.
|
||||
|
||||
At some point, the logs will most likely be processed (for instance with a [transport](transports.md)),
|
||||
and this generally involves parsing. `JSON.parse` will return an object where the conflicting
|
||||
namespace holds the final value assigned to it:
|
||||
|
||||
```sh
|
||||
$ cat my-log | node -e "process.stdin.once('data', (line) => console.log(JSON.stringify(JSON.parse(line))))"
|
||||
{"pid":95469,"hostname":"MacBook-Pro-3.home","level":30,"msg":"howdy","time":"2016-04-01T18:08:34.473Z","a":"prop"}
|
||||
```
|
||||
|
||||
Ultimately the conflict is resolved by taking the last value, which aligns with Bunyan's child logging
|
||||
behavior.
|
||||
|
||||
There may be cases where this edge case becomes problematic if a JSON parser with alternative behavior
|
||||
is used to process the logs. It's recommended to be conscious of namespace conflicts with child loggers,
|
||||
in light of an expected log processing approach.
|
||||
|
||||
One of Pino's performance tricks is to avoid building objects and stringifying
|
||||
them, so we're building strings instead. This is why duplicate keys between
|
||||
parents and children will end up in the log output.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Pino Ecosystem
|
||||
|
||||
This is a list of ecosystem modules that integrate with `pino`.
|
||||
|
||||
Modules listed under [Core](#core) are maintained by the Pino team. Modules
|
||||
listed under [Community](#community) are maintained by independent community
|
||||
members.
|
||||
|
||||
Please send a PR to add new modules!
|
||||
|
||||
<a id="core"></a>
|
||||
## Core
|
||||
|
||||
### Frameworks
|
||||
+ [`express-pino-logger`](https://github.com/pinojs/express-pino-logger): use
|
||||
Pino to log requests within [express](https://expressjs.com/).
|
||||
+ [`koa-pino-logger`](https://github.com/pinojs/koa-pino-logger): use Pino to
|
||||
log requests within [Koa](https://koajs.com/).
|
||||
+ [`rill-pino-logger`](https://github.com/pinojs/rill-pino-logger): use Pino as
|
||||
the logger for the [Rill framework](https://rill.site/).
|
||||
+ [`restify-pino-logger`](https://github.com/pinojs/restify-pino-logger): use
|
||||
Pino to log requests within [restify](http://restify.com/).
|
||||
|
||||
### Utilities
|
||||
+ [`pino-arborsculpture`](https://github.com/pinojs/pino-arborsculpture): change
|
||||
log levels at runtime.
|
||||
+ [`pino-caller`](https://github.com/pinojs/pino-caller): add callsite to the log line.
|
||||
+ [`pino-clf`](https://github.com/pinojs/pino-clf): reformat Pino logs into
|
||||
Common Log Format.
|
||||
+ [`pino-debug`](https://github.com/pinojs/pino-debug): use Pino to interpret
|
||||
[`debug`](https://npm.im/debug) logs.
|
||||
+ [`pino-elasticsearch`](https://github.com/pinojs/pino-elasticsearch): send
|
||||
Pino logs to an Elasticsearch instance.
|
||||
+ [`pino-eventhub`](https://github.com/pinojs/pino-eventhub): send Pino logs
|
||||
to an [Event Hub](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-what-is-event-hubs).
|
||||
+ [`pino-filter`](https://github.com/pinojs/pino-filter): filter Pino logs in
|
||||
the same fashion as the [`debug`](https://npm.im/debug) module.
|
||||
+ [`pino-gelf`](https://github.com/pinojs/pino-gelf): reformat Pino logs into
|
||||
GELF format for Graylog.
|
||||
+ [`pino-hapi`](https://github.com/pinojs/hapi-pino): use Pino as the logger
|
||||
for [Hapi](https://hapijs.com/).
|
||||
+ [`pino-http`](https://github.com/pinojs/pino-http): easily use Pino to log
|
||||
requests with the core `http` module.
|
||||
+ [`pino-http-print`](https://github.com/pinojs/pino-http-print): reformat Pino
|
||||
logs into traditional [HTTPD](https://httpd.apache.org/) style request logs.
|
||||
+ [`pino-multi-stream`](https://github.com/pinojs/pino-multi-stream): send
|
||||
logs to multiple destination streams (slow!).
|
||||
+ [`pino-mongodb`](https://github.com/pinojs/pino-mongodb): store Pino logs
|
||||
in a MongoDB database.
|
||||
+ [`pino-noir`](https://github.com/pinojs/pino-noir): redact sensitive information
|
||||
in logs.
|
||||
+ [`pino-pretty`](https://github.com/pinojs/pino-pretty): basic prettifier to
|
||||
make log lines human-readable.
|
||||
+ [`pino-socket`](https://github.com/pinojs/pino-socket): send logs to TCP or UDP
|
||||
destinations.
|
||||
+ [`pino-std-serializers`](https://github.com/pinojs/pino-std-serializers): the
|
||||
core object serializers used within Pino.
|
||||
+ [`pino-syslog`](https://github.com/pinojs/pino-syslog): reformat Pino logs
|
||||
to standard syslog format.
|
||||
+ [`pino-tee`](https://github.com/pinojs/pino-tee): pipe Pino logs into files
|
||||
based upon log levels.
|
||||
+ [`pino-toke`](https://github.com/pinojs/pino-toke): reformat Pino logs
|
||||
according to a given format string.
|
||||
+ [`pino-test`](https://github.com/pinojs/pino-test): a set of utilities for
|
||||
verifying logs generated by the Pino logger.
|
||||
|
||||
|
||||
<a id="community"></a>
|
||||
## Community
|
||||
|
||||
+ [`pino-colada`](https://github.com/lrlna/pino-colada): cute ndjson formatter for pino.
|
||||
+ [`pino-fluentd`](https://github.com/davidedantonio/pino-fluentd): send Pino logs to Elasticsearch,
|
||||
MongoDB, and many [others](https://www.fluentd.org/dataoutputs) via Fluentd.
|
||||
+ [`pino-pretty-min`](https://github.com/unjello/pino-pretty-min): a minimal
|
||||
prettifier inspired by the [logrus](https://github.com/sirupsen/logrus) logger.
|
||||
+ [`pino-rotating-file`](https://github.com/homeaway/pino-rotating-file): a hapi-pino log transport for splitting logs into separate, automatically rotating files.
|
||||
+ [`cls-proxify`](https://github.com/keenondrums/cls-proxify): integration of pino and [CLS](https://github.com/jeff-lewis/cls-hooked). Useful for creating dynamically configured child loggers (e.g. with added trace ID) for each request.
|
||||
+ [`pino-tiny`](https://github.com/holmok/pino-tiny): a tiny (and extensible?) little log formatter for pino.
|
||||
+ [`pino-dev`](https://github.com/dnjstrom/pino-dev): simple prettifier for pino with built-in support for common ecosystem packages.
|
||||
+ [`@newrelic/pino-enricher`](https://github.com/newrelic/newrelic-node-log-extensions/blob/main/packages/pino-log-enricher): a log customization to add New Relic context to use [Logs In Context](https://docs.newrelic.com/docs/logs/logs-context/logs-in-context/)
|
||||
+ [`pino-lambda`](https://github.com/FormidableLabs/pino-lambda): log transport for cloudwatch support inside aws-lambda
|
||||
+ [`cloud-pine`](https://github.com/metcoder95/cloud-pine): transport that provides abstraction and compatibility with [`@google-cloud/logging`](https://www.npmjs.com/package/@google-cloud/logging).
|
||||
+ [`crawlee-pino`](https://github.com/imyelo/crawlee-pino): use Pino to log within Crawlee
|
||||
@@ -0,0 +1,319 @@
|
||||
# Help
|
||||
|
||||
* [Log rotation](#rotate)
|
||||
* [Reopening log files](#reopening)
|
||||
* [Saving to multiple files](#multiple)
|
||||
* [Log filtering](#filter-logs)
|
||||
* [Transports and systemd](#transport-systemd)
|
||||
* [Log to different streams](#multi-stream)
|
||||
* [Duplicate keys](#dupe-keys)
|
||||
* [Log levels as labels instead of numbers](#level-string)
|
||||
* [Pino with `debug`](#debug)
|
||||
* [Unicode and Windows terminal](#windows)
|
||||
* [Mapping Pino Log Levels to Google Cloud Logging (Stackdriver) Severity Levels](#stackdriver)
|
||||
* [Using Grafana Loki to evaluate pino logs in a kubernetes cluster](#grafana-loki)
|
||||
* [Avoid Message Conflict](#avoid-message-conflict)
|
||||
* [Best performance for logging to `stdout`](#best-performance-for-stdout)
|
||||
* [Testing](#testing)
|
||||
|
||||
<a id="rotate"></a>
|
||||
## Log rotation
|
||||
|
||||
Use a separate tool for log rotation:
|
||||
We recommend [logrotate](https://github.com/logrotate/logrotate).
|
||||
Consider we output our logs to `/var/log/myapp.log` like so:
|
||||
|
||||
```
|
||||
$ node server.js > /var/log/myapp.log
|
||||
```
|
||||
|
||||
We would rotate our log files with logrotate, by adding the following to `/etc/logrotate.d/myapp`:
|
||||
|
||||
```
|
||||
/var/log/myapp.log {
|
||||
su root
|
||||
daily
|
||||
rotate 7
|
||||
delaycompress
|
||||
compress
|
||||
notifempty
|
||||
missingok
|
||||
copytruncate
|
||||
}
|
||||
```
|
||||
|
||||
The `copytruncate` configuration has a very slight possibility of lost log lines due
|
||||
to a gap between copying and truncating - the truncate may occur after additional lines
|
||||
have been written. To perform log rotation without `copytruncate`, see the [Reopening log files](#reopening)
|
||||
help.
|
||||
|
||||
<a id="reopening"></a>
|
||||
## Reopening log files
|
||||
|
||||
In cases where a log rotation tool doesn't offer copy-truncate capabilities,
|
||||
or where using them is deemed inappropriate, `pino.destination`
|
||||
can reopen file paths after a file has been moved away.
|
||||
|
||||
One way to use this is to set up a `SIGUSR2` or `SIGHUP` signal handler that
|
||||
reopens the log file destination, making sure to write the process PID out
|
||||
somewhere so the log rotation tool knows where to send the signal.
|
||||
|
||||
```js
|
||||
// write the process pid to a well known location for later
|
||||
const fs = require('fs')
|
||||
fs.writeFileSync('/var/run/myapp.pid', process.pid)
|
||||
|
||||
const dest = pino.destination('/log/file')
|
||||
const logger = require('pino')(dest)
|
||||
process.on('SIGHUP', () => dest.reopen())
|
||||
```
|
||||
|
||||
The log rotation tool can then be configured to send this signal to the process
|
||||
after a log rotation event has occurred.
|
||||
|
||||
Given a similar scenario as in the [Log rotation](#rotate) section a basic
|
||||
`logrotate` config that aligns with this strategy would look similar to the following:
|
||||
|
||||
```
|
||||
/var/log/myapp.log {
|
||||
su root
|
||||
daily
|
||||
rotate 7
|
||||
delaycompress
|
||||
compress
|
||||
notifempty
|
||||
missingok
|
||||
postrotate
|
||||
kill -HUP `cat /var/run/myapp.pid`
|
||||
endscript
|
||||
}
|
||||
```
|
||||
|
||||
<a id="multiple"></a>
|
||||
## Saving to multiple files
|
||||
|
||||
See [`pino.multistream`](/docs/api.md#pino-multistream).
|
||||
|
||||
<a id="filter-logs"></a>
|
||||
## Log Filtering
|
||||
The Pino philosophy advocates common, preexisting, system utilities.
|
||||
|
||||
Some recommendations in line with this philosophy are:
|
||||
|
||||
1. Use [`grep`](https://linux.die.net/man/1/grep):
|
||||
```sh
|
||||
$ # View all "INFO" level logs
|
||||
$ node app.js | grep '"level":30'
|
||||
```
|
||||
1. Use [`jq`](https://stedolan.github.io/jq/):
|
||||
```sh
|
||||
$ # View all "ERROR" level logs
|
||||
$ node app.js | jq 'select(.level == 50)'
|
||||
```
|
||||
|
||||
<a id="transport-systemd"></a>
|
||||
## Transports and systemd
|
||||
`systemd` makes it complicated to use pipes in services. One method for overcoming
|
||||
this challenge is to use a subshell:
|
||||
|
||||
```
|
||||
ExecStart=/bin/sh -c '/path/to/node app.js | pino-transport'
|
||||
```
|
||||
|
||||
<a id="multi-stream"></a>
|
||||
## Log to different streams
|
||||
|
||||
Pino's default log destination is the singular destination of `stdout`. While
|
||||
not recommended for performance reasons, multiple destinations can be targeted
|
||||
by using [`pino.multistream`](/doc/api.md#pino-multistream).
|
||||
|
||||
In this example, we use `stderr` for `error` level logs and `stdout` as default
|
||||
for all other levels (e.g. `debug`, `info`, and `warn`).
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
var streams = [
|
||||
{level: 'debug', stream: process.stdout},
|
||||
{level: 'error', stream: process.stderr},
|
||||
{level: 'fatal', stream: process.stderr}
|
||||
]
|
||||
|
||||
const logger = pino({
|
||||
name: 'my-app',
|
||||
level: 'debug', // must be the lowest level of all streams
|
||||
}, pino.multistream(streams))
|
||||
```
|
||||
|
||||
<a id="dupe-keys"></a>
|
||||
## How Pino handles duplicate keys
|
||||
|
||||
Duplicate keys are possibly when a child logger logs an object with a key that
|
||||
collides with a key in the child loggers bindings.
|
||||
|
||||
See the [child logger duplicate keys caveat](/docs/child-loggers.md#duplicate-keys-caveat)
|
||||
for information on this is handled.
|
||||
|
||||
<a id="level-string"></a>
|
||||
## Log levels as labels instead of numbers
|
||||
Pino log lines are meant to be parsable. Thus, Pino's default mode of operation
|
||||
is to print the level value instead of the string name.
|
||||
However, you can use the [`formatters`](/docs/api.md#formatters-object) option
|
||||
with a [`level`](/docs/api.md#level) function to print the string name instead of the level value :
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
|
||||
const log = pino({
|
||||
formatters: {
|
||||
level: (label) => {
|
||||
return {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.info('message')
|
||||
|
||||
// {"level":"info","time":1661632832200,"pid":18188,"hostname":"foo","msg":"message"}
|
||||
```
|
||||
|
||||
Although it works, we recommend using one of these options instead if you are able:
|
||||
|
||||
1. If the only change desired is the name then a transport can be used. One such
|
||||
transport is [`pino-text-level-transport`](https://npm.im/pino-text-level-transport).
|
||||
1. Use a prettifier like [`pino-pretty`](https://npm.im/pino-pretty) to make
|
||||
the logs human friendly.
|
||||
|
||||
<a id="debug"></a>
|
||||
## Pino with `debug`
|
||||
|
||||
The popular [`debug`](https://npm.im/debug) is used in many modules across the ecosystem.
|
||||
|
||||
The [`pino-debug`](https://github.com/pinojs/pino-debug) module
|
||||
can capture calls to `debug` loggers and run them
|
||||
through `pino` instead. This results in a 10x (20x in asynchronous mode)
|
||||
performance improvement - even though `pino-debug` is logging additional
|
||||
data and wrapping it in JSON.
|
||||
|
||||
To quickly enable this install [`pino-debug`](https://github.com/pinojs/pino-debug)
|
||||
and preload it with the `-r` flag, enabling any `debug` logs with the
|
||||
`DEBUG` environment variable:
|
||||
|
||||
```sh
|
||||
$ npm i pino-debug
|
||||
$ DEBUG=* node -r pino-debug app.js
|
||||
```
|
||||
|
||||
[`pino-debug`](https://github.com/pinojs/pino-debug) also offers fine-grain control to map specific `debug`
|
||||
namespaces to `pino` log levels. See [`pino-debug`](https://github.com/pinojs/pino-debug)
|
||||
for more.
|
||||
|
||||
<a id="windows"></a>
|
||||
## Unicode and Windows terminal
|
||||
|
||||
Pino uses [sonic-boom](https://github.com/mcollina/sonic-boom) to speed
|
||||
up logging. Internally, it uses [`fs.write`](https://nodejs.org/dist/latest-v10.x/docs/api/fs.html#fs_fs_write_fd_string_position_encoding_callback) to write log lines directly to a file
|
||||
descriptor. On Windows, Unicode output is not handled properly in the
|
||||
terminal (both `cmd.exe` and PowerShell), and as such the output could
|
||||
be visualized incorrectly if the log lines include utf8 characters. It
|
||||
is possible to configure the terminal to visualize those characters
|
||||
correctly with the use of [`chcp`](https://ss64.com/nt/chcp.html) by
|
||||
executing in the terminal `chcp 65001`. This is a known limitation of
|
||||
Node.js.
|
||||
|
||||
<a id="stackdriver"></a>
|
||||
## Mapping Pino Log Levels to Google Cloud Logging (Stackdriver) Severity Levels
|
||||
|
||||
Google Cloud Logging uses `severity` levels instead of log levels. As a result, all logs may show as INFO
|
||||
level logs while completely ignoring the level set in the pino log. Google Cloud Logging also prefers that
|
||||
log data is present inside a `message` key instead of the default `msg` key that Pino uses. Use a technique
|
||||
similar to the one below to retain log levels in Google Cloud Logging
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
|
||||
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
|
||||
const PinoLevelToSeverityLookup = {
|
||||
trace: 'DEBUG',
|
||||
debug: 'DEBUG',
|
||||
info: 'INFO',
|
||||
warn: 'WARNING',
|
||||
error: 'ERROR',
|
||||
fatal: 'CRITICAL',
|
||||
};
|
||||
|
||||
const defaultPinoConf = {
|
||||
messageKey: 'message',
|
||||
formatters: {
|
||||
level(label, number) {
|
||||
return {
|
||||
severity: PinoLevelToSeverityLookup[label] || PinoLevelToSeverityLookup['info'],
|
||||
level: number,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = function createLogger(options) {
|
||||
return pino(Object.assign({}, options, defaultPinoConf))
|
||||
}
|
||||
```
|
||||
|
||||
<a id="grafana-loki"></a>
|
||||
## Using Grafana Loki to evaluate pino logs in a kubernetes cluster
|
||||
|
||||
To get pino logs into Grafana Loki there are two options:
|
||||
|
||||
1. **Push:** Use [pino-loki](https://github.com/Julien-R44/pino-loki) to send logs directly to Loki.
|
||||
1. **Pull:** Configure Grafana Promtail to read and properly parse the logs before sending them to Loki.
|
||||
Similar to Google Cloud logging, this involves remapping the log levels. See this [article](https://medium.com/@janpaepke/structured-logging-in-the-grafana-monitoring-stack-8aff0a5af2f5) for details.
|
||||
|
||||
<a id="avoid-message-conflict"></a>
|
||||
## Avoid Message Conflict
|
||||
|
||||
As described in the [`message` documentation](/docs/api.md#message), when a log
|
||||
is written like `log.info({ msg: 'a message' }, 'another message')` then the
|
||||
final output JSON will have `"msg":"another message"` and the `'a message'`
|
||||
string will be lost. To overcome this, the [`logMethod` hook](/docs/api.md#logmethod)
|
||||
can be used:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const log = require('pino')({
|
||||
level: 'debug',
|
||||
hooks: {
|
||||
logMethod (inputArgs, method) {
|
||||
if (inputArgs.length === 2 && inputArgs[0].msg) {
|
||||
inputArgs[0].originalMsg = inputArgs[0].msg
|
||||
}
|
||||
return method.apply(this, inputArgs)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
log.info('no original message')
|
||||
log.info({ msg: 'mapped to originalMsg' }, 'a message')
|
||||
|
||||
// {"level":30,"time":1596313323106,"pid":63739,"hostname":"foo","msg":"no original message"}
|
||||
// {"level":30,"time":1596313323107,"pid":63739,"hostname":"foo","msg":"a message","originalMsg":"mapped to originalMsg"}
|
||||
```
|
||||
|
||||
<a id="best-performance-for-stdout"></a>
|
||||
## Best performance for logging to `stdout`
|
||||
|
||||
The best performance for logging directly to stdout is _usually_ achieved by using the
|
||||
default configuration:
|
||||
|
||||
```js
|
||||
const log = require('pino')();
|
||||
```
|
||||
|
||||
You should only have to configure custom transports or other settings
|
||||
if you have broader logging requirements.
|
||||
|
||||
<a id="testing"></a>
|
||||
## Testing
|
||||
|
||||
See [`pino-test`](https://github.com/pinojs/pino-test).
|
||||
@@ -0,0 +1,63 @@
|
||||
## Long Term Support
|
||||
|
||||
Pino's Long Term Support (LTS) is provided according to the schedule laid
|
||||
out in this document:
|
||||
|
||||
1. Major releases, "X" release of [semantic versioning][semver] X.Y.Z release
|
||||
versions, are supported for a minimum period of six months from their release
|
||||
date. The release date of any specific version can be found at
|
||||
[https://github.com/pinojs/pino/releases](https://github.com/pinojs/pino/releases).
|
||||
|
||||
1. Major releases will receive security updates for an additional six months
|
||||
from the release of the next major release. After this period
|
||||
we will still review and release security fixes as long as they are
|
||||
provided by the community and they do not violate other constraints,
|
||||
e.g. minimum supported Node.js version.
|
||||
|
||||
1. Major releases will be tested and verified against all Node.js
|
||||
release lines that are supported by the
|
||||
[Node.js LTS policy](https://github.com/nodejs/Release) within the
|
||||
LTS period of that given Pino release line. This implies that only
|
||||
the latest Node.js release of a given line is supported.
|
||||
|
||||
A "month" is defined as 30 consecutive days.
|
||||
|
||||
> ## Security Releases and Semver
|
||||
>
|
||||
> As a consequence of providing long-term support for major releases, there
|
||||
> are occasions where we need to release breaking changes as a _minor_
|
||||
> version release. Such changes will _always_ be noted in the
|
||||
> [release notes](https://github.com/pinojs/pino/releases).
|
||||
>
|
||||
> To avoid automatically receiving breaking security updates it is possible to use
|
||||
> the tilde (`~`) range qualifier. For example, to get patches for the 6.1
|
||||
> release, and avoid automatically updating to the 6.1 release, specify
|
||||
> the dependency as `"pino": "~6.1.x"`. This will leave your application vulnerable,
|
||||
> so please use with caution.
|
||||
|
||||
[semver]: https://semver.org/
|
||||
|
||||
<a name="lts-schedule"></a>
|
||||
|
||||
### Schedule
|
||||
|
||||
| Version | Release Date | End Of LTS Date | Node.js |
|
||||
| :------ | :----------- | :-------------- | :------------------- |
|
||||
| 8.x | 2022-06-01 | TBD | 14, 16, 18 |
|
||||
| 7.x | 2021-10-14 | 2023-06-01 | 12, 14, 16 |
|
||||
| 6.x | 2020-03-07 | 2022-04-14 | 10, 12, 14, 16 |
|
||||
|
||||
<a name="supported-os"></a>
|
||||
|
||||
### CI tested operating systems
|
||||
|
||||
Pino uses GitHub Actions for CI testing, please refer to
|
||||
[GitHub's documentation regarding workflow runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)
|
||||
for further details on what the latest virtual environment is in relation to
|
||||
the YAML workflow labels below:
|
||||
|
||||
| OS | YAML Workflow Label | Node.js |
|
||||
|---------|------------------------|--------------|
|
||||
| Linux | `ubuntu-latest` | 14,16,18 |
|
||||
| Windows | `windows-latest` | 14,16,18 |
|
||||
| MacOS | `macos-latest` | 14,16,18 |
|
||||
@@ -0,0 +1,35 @@
|
||||
# Pretty Printing
|
||||
|
||||
By default, Pino log lines are newline delimited JSON (NDJSON). This is perfect
|
||||
for production usage and long-term storage. It's not so great for development
|
||||
environments. Thus, Pino logs can be prettified by using a Pino prettifier
|
||||
module like [`pino-pretty`][pp]:
|
||||
|
||||
1. Install a prettifier module as a separate dependency, e.g. `npm install pino-pretty`.
|
||||
2. Instantiate the logger with the `transport.target` option set to `'pino-pretty'`:
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
})
|
||||
|
||||
logger.info('hi')
|
||||
```
|
||||
3. The transport option can also have an options object containing `pino-pretty` options:
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('hi')
|
||||
```
|
||||
|
||||
[pp]: https://github.com/pinojs/pino-pretty
|
||||
@@ -0,0 +1,135 @@
|
||||
# Redaction
|
||||
|
||||
> Redaction is not supported in the browser [#670](https://github.com/pinojs/pino/issues/670)
|
||||
|
||||
To redact sensitive information, supply paths to keys that hold sensitive data
|
||||
using the `redact` option. Note that paths that contain hyphens need to use
|
||||
brackets to access the hyphenated property:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: ['key', 'path.to.key', 'stuff.thats[*].secret', 'path["with-hyphen"]']
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527777350011,"pid":3186,"hostname":"Davids-MacBook-Pro-3.local","key":"[Redacted]","path":{"to":{"key":"[Redacted]","another":"thing"}},"stuff":{"thats":[{"secret":"[Redacted]","logme":"will be logged"},{"secret":"[Redacted]","logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
The `redact` option can take an array (as shown in the above example) or
|
||||
an object. This allows control over *how* information is redacted.
|
||||
|
||||
For instance, setting the censor:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: {
|
||||
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
|
||||
censor: '**GDPR COMPLIANT**'
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output:
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527778563934,"pid":3847,"hostname":"Davids-MacBook-Pro-3.local","key":"**GDPR COMPLIANT**","path":{"to":{"key":"**GDPR COMPLIANT**","another":"thing"}},"stuff":{"thats":[{"secret":"**GDPR COMPLIANT**","logme":"will be logged"},{"secret":"**GDPR COMPLIANT**","logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
The `redact.remove` option also allows for the key and value to be removed from output:
|
||||
|
||||
```js
|
||||
const logger = require('.')({
|
||||
redact: {
|
||||
paths: ['key', 'path.to.key', 'stuff.thats[*].secret'],
|
||||
remove: true
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({
|
||||
key: 'will be redacted',
|
||||
path: {
|
||||
to: {key: 'sensitive', another: 'thing'}
|
||||
},
|
||||
stuff: {
|
||||
thats: [
|
||||
{secret: 'will be redacted', logme: 'will be logged'},
|
||||
{secret: 'as will this', logme: 'as will this'}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This will output
|
||||
|
||||
```JSON
|
||||
{"level":30,"time":1527782356751,"pid":5758,"hostname":"Davids-MacBook-Pro-3.local","path":{"to":{"another":"thing"}},"stuff":{"thats":[{"logme":"will be logged"},{"logme":"as will this"}]}}
|
||||
```
|
||||
|
||||
See [pino options in API](/docs/api.md#redact-array-object) for `redact` API details.
|
||||
|
||||
<a name="paths"></a>
|
||||
## Path Syntax
|
||||
|
||||
The syntax for paths supplied to the `redact` option conform to the syntax in path lookups
|
||||
in standard ECMAScript, with two additions:
|
||||
|
||||
* paths may start with bracket notation
|
||||
* paths may contain the asterisk `*` to denote a wildcard
|
||||
* paths are **case sensitive**
|
||||
|
||||
By way of example, the following are all valid paths:
|
||||
|
||||
* `a.b.c`
|
||||
* `a["b-c"].d`
|
||||
* `["a-b"].c`
|
||||
* `a.b.*`
|
||||
* `a[*].b`
|
||||
|
||||
## Overhead
|
||||
|
||||
Pino's redaction functionality is built on top of [`fast-redact`](https://github.com/davidmarkclements/fast-redact)
|
||||
which adds about 2% overhead to `JSON.stringify` when using paths without wildcards.
|
||||
|
||||
When used with pino logger with a single redacted path, any overhead is within noise -
|
||||
a way to deterministically measure its effect has not been found. This is because it is not a bottleneck.
|
||||
|
||||
However, wildcard redaction does carry a non-trivial cost relative to explicitly declaring the keys
|
||||
(50% in a case where four keys are redacted across two objects). See
|
||||
the [`fast-redact` benchmarks](https://github.com/davidmarkclements/fast-redact#benchmarks) for details.
|
||||
|
||||
## Safety
|
||||
|
||||
The `redact` option is intended as an initialization time configuration option.
|
||||
Path strings must not originate from user input.
|
||||
The `fast-redact` module uses a VM context to syntax check the paths, user input
|
||||
should never be combined with such an approach. See the [`fast-redact` Caveat](https://github.com/davidmarkclements/fast-redact#caveat)
|
||||
and the [`fast-redact` Approach](https://github.com/davidmarkclements/fast-redact#approach) for in-depth information.
|
||||
@@ -0,0 +1,1092 @@
|
||||
# Transports
|
||||
|
||||
Pino transports can be used for both transmitting and transforming log output.
|
||||
|
||||
The way Pino generates logs:
|
||||
|
||||
1. Reduces the impact of logging on an application to the absolute minimum.
|
||||
2. Gives greater flexibility in how logs are processed and stored.
|
||||
|
||||
It is recommended that any log transformation or transmission is performed either
|
||||
in a separate thread or a separate process.
|
||||
|
||||
Before Pino v7 transports would ideally operate in a separate process - these are
|
||||
now referred to as [Legacy Transports](#legacy-transports).
|
||||
|
||||
From Pino v7 and upwards transports can also operate inside a [Worker Thread][worker-thread]
|
||||
and can be used or configured via the options object passed to `pino` on initialization.
|
||||
In this case the transports would always operate asynchronously, and logs would be
|
||||
flushed as quickly as possible (there is nothing to do).
|
||||
|
||||
[worker-thread]: https://nodejs.org/dist/latest-v14.x/docs/api/worker_threads.html
|
||||
|
||||
## v7+ Transports
|
||||
|
||||
A transport is a module that exports a default function that returns a writable stream:
|
||||
|
||||
```js
|
||||
import { createWriteStream } from 'fs'
|
||||
|
||||
export default (options) => {
|
||||
return createWriteStream(options.destination)
|
||||
}
|
||||
```
|
||||
|
||||
Let's imagine the above defines our "transport" as the file `my-transport.mjs`
|
||||
(ESM files are supported even if the project is written in CJS).
|
||||
|
||||
We would set up our transport by creating a transport stream with `pino.transport`
|
||||
and passing it to the `pino` function:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '/absolute/path/to/my-transport.mjs'
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
The transport code will be executed in a separate worker thread. The main thread
|
||||
will write logs to the worker thread, which will write them to the stream returned
|
||||
from the function exported from the transport file/module.
|
||||
|
||||
The exported function can also be async. If we use an async function we can throw early
|
||||
if the transform could not be opened. As an example:
|
||||
|
||||
```js
|
||||
import fs from 'fs'
|
||||
import { once } from 'events'
|
||||
export default async (options) => {
|
||||
const stream = fs.createWriteStream(options.destination)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
```
|
||||
|
||||
While initializing the stream we're able to use `await` to perform asynchronous operations. In this
|
||||
case, waiting for the write streams `open` event.
|
||||
|
||||
Let's imagine the above was published to npm with the module name `some-file-transport`.
|
||||
|
||||
The `options.destination` value can be set when creating the transport stream with `pino.transport` like so:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'some-file-transport',
|
||||
options: { destination: '/dev/null' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
Note here we've specified a module by package rather than by relative path. The options object we provide
|
||||
is serialized and injected into the transport worker thread, then passed to the module's exported function.
|
||||
This means that the options object can only contain types that are supported by the
|
||||
[Structured Clone Algorithm][sca] which is used to (de)serialize objects between threads.
|
||||
|
||||
What if we wanted to use both transports, but send only error logs to `some-file-transport` while
|
||||
sending all logs to `my-transport.mjs`? We can use the `pino.transport` function's `destinations` option:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }}
|
||||
]
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
If we're using custom levels, they should be passed in when using more than one transport.
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
],
|
||||
levels: { foo: 35 }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
It is also possible to use the `dedupe` option to send logs only to the stream with the higher level.
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' }
|
||||
],
|
||||
dedupe: true
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
For more details on `pino.transport` see the [API docs for `pino.transport`][pino-transport].
|
||||
|
||||
[pino-transport]: /docs/api.md#pino-transport
|
||||
[sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
||||
|
||||
<a id="writing"></a>
|
||||
### Writing a Transport
|
||||
|
||||
The module [pino-abstract-transport](https://github.com/pinojs/pino-abstract-transport) provides
|
||||
a simple utility to parse each line. Its usage is highly recommended.
|
||||
|
||||
You can see an example using an async iterator with ESM:
|
||||
|
||||
```js
|
||||
import build from 'pino-abstract-transport'
|
||||
import SonicBoom from 'sonic-boom'
|
||||
import { once } from 'events'
|
||||
|
||||
export default async function (opts) {
|
||||
// SonicBoom is necessary to avoid loops with the main thread.
|
||||
// It is the same of pino.destination().
|
||||
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
|
||||
await once(destination, 'ready')
|
||||
|
||||
return build(async function (source) {
|
||||
for await (let obj of source) {
|
||||
const toDrain = !destination.write(obj.msg.toUpperCase() + '\n')
|
||||
// This block will handle backpressure
|
||||
if (toDrain) {
|
||||
await once(destination, 'drain')
|
||||
}
|
||||
}
|
||||
}, {
|
||||
async close (err) {
|
||||
destination.end()
|
||||
await once(destination, 'close')
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
or using Node.js streams and CommonJS:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const build = require('pino-abstract-transport')
|
||||
const SonicBoom = require('sonic-boom')
|
||||
|
||||
module.exports = function (opts) {
|
||||
const destination = new SonicBoom({ dest: opts.destination || 1, sync: false })
|
||||
return build(function (source) {
|
||||
source.pipe(destination)
|
||||
}, {
|
||||
close (err, cb) {
|
||||
destination.end()
|
||||
destination.on('close', cb.bind(null, err))
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
(It is possible to use the async iterators with CommonJS and streams with ESM.)
|
||||
|
||||
To consume async iterators in batches, consider using the [hwp](https://github.com/mcollina/hwp) library.
|
||||
|
||||
The `close()` function is needed to make sure that the stream is closed and flushed when its
|
||||
callback is called or the returned promise resolves. Otherwise, log lines will be lost.
|
||||
|
||||
### Writing to a custom transport & stdout
|
||||
|
||||
In case you want to both use a custom transport, and output the log entries with default processing to STDOUT, you can use 'pino/file' transport configured with `destination: 1`:
|
||||
|
||||
```js
|
||||
const transports = [
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: 1 } // this writes to STDOUT
|
||||
},
|
||||
{
|
||||
target: 'my-custom-transport',
|
||||
options: { someParameter: true }
|
||||
}
|
||||
]
|
||||
|
||||
const logger = pino(pino.transport({ targets: transports })
|
||||
```
|
||||
|
||||
### Creating a transport pipeline
|
||||
|
||||
As an example, the following transport returns a `Transform` stream:
|
||||
|
||||
```js
|
||||
import build from 'pino-abstract-transport'
|
||||
import { pipeline, Transform } from 'stream'
|
||||
export default async function (options) {
|
||||
return build(function (source) {
|
||||
const myTransportStream = new Transform({
|
||||
// Make sure autoDestroy is set,
|
||||
// this is needed in Node v12 or when using the
|
||||
// readable-stream module.
|
||||
autoDestroy: true,
|
||||
|
||||
objectMode: true,
|
||||
transform (chunk, enc, cb) {
|
||||
|
||||
// modifies the payload somehow
|
||||
chunk.service = 'pino'
|
||||
|
||||
// stringify the payload again
|
||||
this.push(`${JSON.stringify(chunk)}\n`)
|
||||
cb()
|
||||
}
|
||||
})
|
||||
pipeline(source, myTransportStream, () => {})
|
||||
return myTransportStream
|
||||
}, {
|
||||
// This is needed to be able to pipeline transports.
|
||||
enablePipelining: true
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then you can pipeline them with:
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
pipeline: [{
|
||||
target: './my-transform.js'
|
||||
}, {
|
||||
// Use target: 'pino/file' with STDOUT descriptor 1 to write
|
||||
// logs without any change.
|
||||
target: 'pino/file',
|
||||
options: { destination: 1 }
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('hello world')
|
||||
```
|
||||
|
||||
__NOTE: there is no "default" destination for a pipeline but
|
||||
a terminating target, i.e. a `Writable` stream.__
|
||||
|
||||
### TypeScript compatibility
|
||||
|
||||
Pino provides basic support for transports written in TypeScript.
|
||||
|
||||
Ideally, they should be transpiled to ensure maximum compatibility, but sometimes
|
||||
you might want to use tools such as TS-Node, to execute your TypeScript
|
||||
code without having to go through an explicit transpilation step.
|
||||
|
||||
You can use your TypeScript code without explicit transpilation, but there are
|
||||
some known caveats:
|
||||
- For "pure" TypeScript code, ES imports are still not supported (ES imports are
|
||||
supported once the code is transpiled).
|
||||
- Only TS-Node is supported for now, there's no TSM support.
|
||||
- Running transports TypeScript code on TS-Node seems to be problematic on
|
||||
Windows systems, there's no official support for that yet.
|
||||
|
||||
### Notable transports
|
||||
|
||||
#### `pino/file`
|
||||
|
||||
The `pino/file` transport routes logs to a file (or file descriptor).
|
||||
|
||||
The `options.destination` property may be set to specify the desired file destination.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/file' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
By default, the `pino/file` transport assumes the directory of the destination file exists. If it does not exist, the transport will throw an error when it attempts to open the file for writing. The `mkdir` option may be set to `true` to configure the transport to create the directory, if it does not exist, before opening the file for writing.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/file', mkdir: true }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
By default, the `pino/file` transport appends to the destination file if it exists. The `append` option may be set to `false` to configure the transport to truncate the file upon opening it for writing.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '/path/to/file', append: false }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
The `options.destination` property may also be a number to represent a file descriptor. Typically this would be `1` to write to STDOUT or `2` to write to STDERR. If `options.destination` is not set, it defaults to `1` which means logs will be written to STDOUT. If `options.destination` is a string integer, e.g. `'1'`, it will be coerced to a number and used as a file descriptor. If this is not desired, provide a full path, e.g. `/tmp/1`.
|
||||
|
||||
The difference between using the `pino/file` transport builtin and using `pino.destination` is that `pino.destination` runs in the main thread, whereas `pino/file` sets up `pino.destination` in a worker thread.
|
||||
|
||||
#### `pino-pretty`
|
||||
|
||||
The [`pino-pretty`][pino-pretty] transport prettifies logs.
|
||||
|
||||
By default the `pino-pretty` builtin logs to STDOUT.
|
||||
|
||||
The `options.destination` property may be set to log pretty logs to a file descriptor or file. The following would send the prettified logs to STDERR:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-pretty',
|
||||
options: { destination: 1 } // use 2 for stderr
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
### Asynchronous startup
|
||||
|
||||
The new transports boot asynchronously and calling `process.exit()` before the transport
|
||||
starts will cause logs to not be delivered.
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{ target: '/absolute/path/to/my-transport.mjs', level: 'error' },
|
||||
{ target: 'some-file-transport', options: { destination: '/dev/null' } }
|
||||
]
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('hello')
|
||||
|
||||
// If logs are printed before the transport is ready when process.exit(0) is called,
|
||||
// they will be lost.
|
||||
transport.on('ready', function () {
|
||||
process.exit(0)
|
||||
})
|
||||
```
|
||||
|
||||
## Legacy Transports
|
||||
|
||||
A legacy Pino "transport" is a supplementary tool that consumes Pino logs.
|
||||
|
||||
Consider the following example for creating a transport:
|
||||
|
||||
```js
|
||||
const { pipeline, Writable } = require('stream')
|
||||
const split = require('split2')
|
||||
|
||||
const myTransportStream = new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
// apply a transform and send to STDOUT
|
||||
console.log(chunk.toString().toUpperCase())
|
||||
cb()
|
||||
}
|
||||
})
|
||||
|
||||
pipeline(process.stdin, split(JSON.parse), myTransportStream)
|
||||
```
|
||||
|
||||
The above defines our "transport" as the file `my-transport-process.js`.
|
||||
|
||||
Logs can now be consumed using shell piping:
|
||||
|
||||
```sh
|
||||
node my-app-which-logs-stuff-to-stdout.js | node my-transport-process.js
|
||||
```
|
||||
|
||||
Ideally, a transport should consume logs in a separate process to the application,
|
||||
Using transports in the same process causes unnecessary load and slows down
|
||||
Node's single-threaded event loop.
|
||||
|
||||
## Known Transports
|
||||
|
||||
PRs to this document are welcome for any new transports!
|
||||
|
||||
### Pino v7+ Compatible
|
||||
|
||||
+ [@logtail/pino](#@logtail/pino)
|
||||
+ [pino-elasticsearch](#pino-elasticsearch)
|
||||
+ [pino-pretty](#pino-pretty)
|
||||
+ [pino-loki](#pino-loki)
|
||||
+ [pino-seq-transport](#pino-seq-transport)
|
||||
+ [pino-sentry-transport](#pino-sentry-transport)
|
||||
+ [pino-airbrake-transport](#pino-airbrake-transport)
|
||||
+ [pino-datadog-transport](#pino-datadog-transport)
|
||||
+ [pino-slack-webhook](#pino-slack-webhook)
|
||||
+ [pino-axiom](#pino-axiom)
|
||||
+ [pino-opentelemetry-transport](#pino-opentelemetry-transport)
|
||||
+ [@axiomhq/pino](#@axiomhq/pino)
|
||||
+ [pino-discord-webhook](#pino-discord-webhook)
|
||||
+ [pino-logfmt](#pino-logfmt)
|
||||
|
||||
### Legacy
|
||||
|
||||
+ [pino-applicationinsights](#pino-applicationinsights)
|
||||
+ [pino-azuretable](#pino-azuretable)
|
||||
+ [pino-cloudwatch](#pino-cloudwatch)
|
||||
+ [pino-couch](#pino-couch)
|
||||
+ [pino-datadog](#pino-datadog)
|
||||
+ [pino-gelf](#pino-gelf)
|
||||
+ [pino-http-send](#pino-http-send)
|
||||
+ [pino-kafka](#pino-kafka)
|
||||
+ [pino-logdna](#pino-logdna)
|
||||
+ [pino-logflare](#pino-logflare)
|
||||
+ [pino-loki](#pino-loki)
|
||||
+ [pino-mq](#pino-mq)
|
||||
+ [pino-mysql](#pino-mysql)
|
||||
+ [pino-papertrail](#pino-papertrail)
|
||||
+ [pino-pg](#pino-pg)
|
||||
+ [pino-redis](#pino-redis)
|
||||
+ [pino-sentry](#pino-sentry)
|
||||
+ [pino-seq](#pino-seq)
|
||||
+ [pino-socket](#pino-socket)
|
||||
+ [pino-stackdriver](#pino-stackdriver)
|
||||
+ [pino-syslog](#pino-syslog)
|
||||
+ [pino-websocket](#pino-websocket)
|
||||
|
||||
|
||||
<a id="@logtail/pino"></a>
|
||||
### @logtail/pino
|
||||
|
||||
The [@logtail/pino](https://www.npmjs.com/package/@logtail/pino) NPM package is a transport that forwards logs to [Logtail](https://logtail.com) by [Better Stack](https://betterstack.com).
|
||||
|
||||
[Quick start guide ⇗](https://betterstack.com/docs/logs/javascript/pino)
|
||||
|
||||
<a id="pino-applicationinsights"></a>
|
||||
### pino-applicationinsights
|
||||
The [pino-applicationinsights](https://www.npmjs.com/package/pino-applicationinsights) module is a transport that will forward logs to [Azure Application Insights](https://docs.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview).
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-applicationinsights` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-applicationinsights --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-applicationinsights#readme)
|
||||
|
||||
<a id="pino-azuretable"></a>
|
||||
### pino-azuretable
|
||||
The [pino-azuretable](https://www.npmjs.com/package/pino-azuretable) module is a transport that will forward logs to the [Azure Table Storage](https://azure.microsoft.com/en-us/services/storage/tables/).
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-azuretable` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-azuretable --account storageaccount --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-azuretable#readme)
|
||||
|
||||
<a id="pino-cloudwatch"></a>
|
||||
### pino-cloudwatch
|
||||
|
||||
[pino-cloudwatch][pino-cloudwatch] is a transport that buffers and forwards logs to [Amazon CloudWatch][].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-cloudwatch --group my-log-group
|
||||
```
|
||||
|
||||
[pino-cloudwatch]: https://github.com/dbhowell/pino-cloudwatch
|
||||
[Amazon CloudWatch]: https://aws.amazon.com/cloudwatch/
|
||||
|
||||
<a id="pino-couch"></a>
|
||||
### pino-couch
|
||||
|
||||
[pino-couch][pino-couch] uploads each log line as a [CouchDB][CouchDB] document.
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-couch -U https://couch-server -d mylogs
|
||||
```
|
||||
|
||||
[pino-couch]: https://github.com/IBM/pino-couch
|
||||
[CouchDB]: https://couchdb.apache.org
|
||||
|
||||
<a id="pino-datadog"></a>
|
||||
### pino-datadog
|
||||
The [pino-datadog](https://www.npmjs.com/package/pino-datadog) module is a transport that will forward logs to [DataDog](https://www.datadoghq.com/) through its API.
|
||||
|
||||
Given an application `foo` that logs via pino, you would use `pino-datadog` like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-datadog --key blablabla
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-datadog#readme)
|
||||
|
||||
<a id="pino-elasticsearch"></a>
|
||||
### pino-elasticsearch
|
||||
|
||||
[pino-elasticsearch][pino-elasticsearch] uploads the log lines in bulk
|
||||
to [Elasticsearch][elasticsearch], to be displayed in [Kibana][kibana].
|
||||
|
||||
It is extremely simple to use and setup
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch
|
||||
```
|
||||
|
||||
Assuming Elasticsearch is running on localhost.
|
||||
|
||||
To connect to an external Elasticsearch instance (recommended for production):
|
||||
|
||||
* Check that `network.host` is defined in the `elasticsearch.yml` configuration file. See [Elasticsearch Network Settings documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html#common-network-settings) for more details.
|
||||
* Launch:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch --node http://192.168.1.42:9200
|
||||
```
|
||||
|
||||
Assuming Elasticsearch is running on `192.168.1.42`.
|
||||
|
||||
To connect to AWS Elasticsearch:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-elasticsearch --node https://es-url.us-east-1.es.amazonaws.com --es-version 6
|
||||
```
|
||||
|
||||
Then [create an index pattern](https://www.elastic.co/guide/en/kibana/current/setup.html) on `'pino'` (the default index key for `pino-elasticsearch`) on the Kibana instance.
|
||||
|
||||
[pino-elasticsearch]: https://github.com/pinojs/pino-elasticsearch
|
||||
[elasticsearch]: https://www.elastic.co/products/elasticsearch
|
||||
[kibana]: https://www.elastic.co/products/kibana
|
||||
|
||||
<a id="pino-gelf"></a>
|
||||
### pino-gelf
|
||||
|
||||
Pino GELF ([pino-gelf]) is a transport for the Pino logger. Pino GELF receives Pino logs from stdin and transforms them into [GELF format][gelf] before sending them to a remote [Graylog server][graylog] via UDP.
|
||||
|
||||
```sh
|
||||
$ node your-app.js | pino-gelf log
|
||||
```
|
||||
|
||||
[pino-gelf]: https://github.com/pinojs/pino-gelf
|
||||
[gelf]: https://docs.graylog.org/en/2.1/pages/gelf.html
|
||||
[graylog]: https://www.graylog.org/
|
||||
|
||||
<a id="pino-http-send"></a>
|
||||
### pino-http-send
|
||||
|
||||
[pino-http-send](https://npmjs.com/package/pino-http-send) is a configurable and low overhead
|
||||
transport that will batch logs and send to a specified URL.
|
||||
|
||||
```console
|
||||
$ node app.js | pino-http-send -u http://localhost:8080/logs
|
||||
```
|
||||
|
||||
<a id="pino-kafka"></a>
|
||||
### pino-kafka
|
||||
|
||||
[pino-kafka](https://github.com/ayZagen/pino-kafka) transport to send logs to [Apache Kafka](https://kafka.apache.org/).
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-kafka -b 10.10.10.5:9200 -d mytopic
|
||||
```
|
||||
|
||||
<a id="pino-logdna"></a>
|
||||
### pino-logdna
|
||||
|
||||
[pino-logdna](https://github.com/logdna/pino-logdna) transport to send logs to [LogDNA](https://logdna.com).
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-logdna --key YOUR_INGESTION_KEY
|
||||
```
|
||||
|
||||
Tags and other metadata can be included using the available command line options. See the [pino-logdna README](https://github.com/logdna/pino-logdna#options) for a full list.
|
||||
|
||||
<a id="pino-logflare"></a>
|
||||
### pino-logflare
|
||||
|
||||
[pino-logflare](https://github.com/Logflare/pino-logflare) transport to send logs to a [Logflare](https://logflare.app) `source`.
|
||||
|
||||
```sh
|
||||
$ node index.js | pino-logflare --key YOUR_KEY --source YOUR_SOURCE
|
||||
```
|
||||
|
||||
<a id="pino-mq"></a>
|
||||
### pino-mq
|
||||
|
||||
The `pino-mq` transport will take all messages received on `process.stdin` and send them over a message bus using JSON serialization.
|
||||
|
||||
This is useful for:
|
||||
|
||||
* moving backpressure from application to broker
|
||||
* transforming messages pressure to another component
|
||||
|
||||
```
|
||||
node app.js | pino-mq -u "amqp://guest:guest@localhost/" -q "pino-logs"
|
||||
```
|
||||
|
||||
Alternatively, a configuration file can be used:
|
||||
|
||||
```
|
||||
node app.js | pino-mq -c pino-mq.json
|
||||
```
|
||||
|
||||
A base configuration file can be initialized with:
|
||||
|
||||
```
|
||||
pino-mq -g
|
||||
```
|
||||
|
||||
For full documentation of command line switches and configuration see [the `pino-mq` README](https://github.com/itavy/pino-mq#readme)
|
||||
|
||||
<a id="pino-loki"></a>
|
||||
### pino-loki
|
||||
pino-loki is a transport that will forwards logs into [Grafana Loki](https://grafana.com/oss/loki/).
|
||||
Can be used in CLI version in a separate process or in a dedicated worker:
|
||||
|
||||
CLI :
|
||||
```console
|
||||
node app.js | pino-loki --hostname localhost:3100 --labels='{ "application": "my-application"}' --user my-username --password my-password
|
||||
```
|
||||
|
||||
Worker :
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-loki',
|
||||
options: { host: 'localhost:3100' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
For full documentation and configuration, see the [README](https://github.com/Julien-R44/pino-loki).
|
||||
|
||||
<a id="pino-papertrail"></a>
|
||||
### pino-papertrail
|
||||
pino-papertrail is a transport that will forward logs to the [papertrail](https://papertrailapp.com) log service through an UDPv4 socket.
|
||||
|
||||
Given an application `foo` that logs via pino, and a papertrail destination that collects logs on port UDP `12345` on address `bar.papertrailapp.com`, you would use `pino-papertrail`
|
||||
like so:
|
||||
|
||||
```
|
||||
node yourapp.js | pino-papertrail --host bar.papertrailapp.com --port 12345 --appname foo
|
||||
```
|
||||
|
||||
|
||||
for full documentation of command line switches read [README](https://github.com/ovhemert/pino-papertrail#readme)
|
||||
|
||||
<a id="pino-pg"></a>
|
||||
### pino-pg
|
||||
[pino-pg](https://www.npmjs.com/package/pino-pg) stores logs into PostgreSQL.
|
||||
Full documentation in the [README](https://github.com/Xstoudi/pino-pg).
|
||||
|
||||
<a id="pino-mysql"></a>
|
||||
### pino-mysql
|
||||
|
||||
[pino-mysql][pino-mysql] loads pino logs into [MySQL][MySQL] and [MariaDB][MariaDB].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-mysql -c db-configuration.json
|
||||
```
|
||||
|
||||
`pino-mysql` can extract and save log fields into corresponding database fields
|
||||
and/or save the entire log stream as a [JSON Data Type][JSONDT].
|
||||
|
||||
For full documentation and command line switches read the [README][pino-mysql].
|
||||
|
||||
[pino-mysql]: https://www.npmjs.com/package/pino-mysql
|
||||
[MySQL]: https://www.mysql.com/
|
||||
[MariaDB]: https://mariadb.org/
|
||||
[JSONDT]: https://dev.mysql.com/doc/refman/8.0/en/json.html
|
||||
|
||||
<a id="pino-redis"></a>
|
||||
### pino-redis
|
||||
|
||||
[pino-redis][pino-redis] loads pino logs into [Redis][Redis].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-redis -U redis://username:password@localhost:6379
|
||||
```
|
||||
|
||||
[pino-redis]: https://github.com/buianhthang/pino-redis
|
||||
[Redis]: https://redis.io/
|
||||
|
||||
<a id="pino-sentry"></a>
|
||||
### pino-sentry
|
||||
|
||||
[pino-sentry][pino-sentry] loads pino logs into [Sentry][Sentry].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-sentry --dsn=https://******@sentry.io/12345
|
||||
```
|
||||
|
||||
For full documentation of command line switches see the [pino-sentry README](https://github.com/aandrewww/pino-sentry/blob/master/README.md).
|
||||
|
||||
[pino-sentry]: https://www.npmjs.com/package/pino-sentry
|
||||
[Sentry]: https://sentry.io/
|
||||
|
||||
|
||||
<a id="pino-seq"></a>
|
||||
### pino-seq
|
||||
|
||||
[pino-seq][pino-seq] supports both out-of-process and in-process log forwarding to [Seq][Seq].
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-seq --serverUrl http://localhost:5341 --apiKey 1234567890 --property applicationName=MyNodeApp
|
||||
```
|
||||
|
||||
[pino-seq]: https://www.npmjs.com/package/pino-seq
|
||||
[Seq]: https://datalust.co/seq
|
||||
|
||||
<a id="pino-seq-transport"></a>
|
||||
### pino-seq-transport
|
||||
|
||||
[pino-seq-transport][pino-seq-transport] is a Pino v7+ compatible transport to forward log events to [Seq][Seq]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '@autotelic/pino-seq-transport',
|
||||
options: { serverUrl: 'http://localhost:5341' }
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-seq-transport]: https://github.com/autotelic/pino-seq-transport
|
||||
[Seq]: https://datalust.co/seq
|
||||
|
||||
<a id="pino-sentry-transport"></a>
|
||||
### pino-sentry-transport
|
||||
|
||||
[pino-sentry-transport][pino-sentry-transport] is a Pino v7+ compatible transport to forward log events to [Sentry][Sentry]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-sentry-transport',
|
||||
options: {
|
||||
sentry: {
|
||||
dsn: 'https://******@sentry.io/12345',
|
||||
}
|
||||
}
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-sentry-transport]: https://github.com/tomer-yechiel/pino-sentry-transport
|
||||
[Sentry]: https://sentry.io/
|
||||
|
||||
|
||||
<a id="pino-airbrake-transport"></a>
|
||||
### pino-airbrake-transport
|
||||
|
||||
[pino-airbrake-transport][pino-airbrake-transport] is a Pino v7+ compatible transport to forward log events to [Airbrake][Airbrake]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-airbrake-transport',
|
||||
options: {
|
||||
airbrake: {
|
||||
projectId: 1,
|
||||
projectKey: "REPLACE_ME",
|
||||
environment: "production",
|
||||
// additional options for airbrake
|
||||
performanceStats: false,
|
||||
},
|
||||
},
|
||||
level: "error", // minimum log level that should be sent to airbrake
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-airbrake-transport]: https://github.com/enricodeleo/pino-airbrake-transport
|
||||
[Airbrake]: https://airbrake.io/
|
||||
|
||||
<a id="pino-socket"></a>
|
||||
### pino-socket
|
||||
|
||||
[pino-socket][pino-socket] is a transport that will forward logs to an IPv4
|
||||
UDP or TCP socket.
|
||||
|
||||
As an example, use `socat` to fake a listener:
|
||||
|
||||
```sh
|
||||
$ socat -v udp4-recvfrom:6000,fork exec:'/bin/cat'
|
||||
```
|
||||
|
||||
Then run an application that uses `pino` for logging:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-socket -p 6000
|
||||
```
|
||||
|
||||
Logs from the application should be observed on both consoles.
|
||||
|
||||
[pino-socket]: https://www.npmjs.com/package/pino-socket
|
||||
|
||||
<a id="pino-datadog-transport"></a>
|
||||
### pino-datadog-transport
|
||||
|
||||
[pino-datadog-transport][pino-datadog-transport] is a Pino v7+ compatible transport to forward log events to [Datadog][Datadog]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-datadog-transport',
|
||||
options: {
|
||||
ddClientConf: {
|
||||
authMethods: {
|
||||
apiKeyAuth: <your datadog API key>
|
||||
}
|
||||
},
|
||||
},
|
||||
level: "error", // minimum log level that should be sent to datadog
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-datadog-transport]: https://github.com/theogravity/pino-datadog-transport
|
||||
[Datadog]: https://www.datadoghq.com/
|
||||
|
||||
#### Logstash
|
||||
|
||||
The [pino-socket][pino-socket] module can also be used to upload logs to
|
||||
[Logstash][logstash] via:
|
||||
|
||||
```
|
||||
$ node app.js | pino-socket -a 127.0.0.1 -p 5000 -m tcp
|
||||
```
|
||||
|
||||
Assuming logstash is running on the same host and configured as
|
||||
follows:
|
||||
|
||||
```
|
||||
input {
|
||||
tcp {
|
||||
port => 5000
|
||||
}
|
||||
}
|
||||
|
||||
filter {
|
||||
json {
|
||||
source => "message"
|
||||
}
|
||||
}
|
||||
|
||||
output {
|
||||
elasticsearch {
|
||||
hosts => "127.0.0.1:9200"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See <https://www.elastic.co/guide/en/kibana/current/setup.html> to learn
|
||||
how to setup [Kibana][kibana].
|
||||
|
||||
For Docker users, see
|
||||
https://github.com/deviantony/docker-elk to setup an ELK stack.
|
||||
|
||||
<a id="pino-stackdriver"></a>
|
||||
### pino-stackdriver
|
||||
The [pino-stackdriver](https://www.npmjs.com/package/pino-stackdriver) module is a transport that will forward logs to the [Google Stackdriver](https://cloud.google.com/logging/) log service through its API.
|
||||
|
||||
Given an application `foo` that logs via pino, a stackdriver log project `bar`, and credentials in the file `/credentials.json`, you would use `pino-stackdriver`
|
||||
like so:
|
||||
|
||||
``` sh
|
||||
$ node foo | pino-stackdriver --project bar --credentials /credentials.json
|
||||
```
|
||||
|
||||
For full documentation of command line switches read [README](https://github.com/ovhemert/pino-stackdriver#readme)
|
||||
|
||||
<a id="pino-syslog"></a>
|
||||
### pino-syslog
|
||||
|
||||
[pino-syslog][pino-syslog] is a transforming transport that converts
|
||||
`pino` NDJSON logs to [RFC3164][rfc3164] compatible log messages. The `pino-syslog` module does not
|
||||
forward the logs anywhere, it merely re-writes the messages to `stdout`. But
|
||||
when used in combination with `pino-socket` the log messages can be relayed to a syslog server:
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-syslog | pino-socket -a syslog.example.com
|
||||
```
|
||||
|
||||
Example output for the "hello world" log:
|
||||
|
||||
```
|
||||
<134>Apr 1 16:44:58 MacBook-Pro-3 none[94473]: {"pid":94473,"hostname":"MacBook-Pro-3","level":30,"msg":"hello world","time":1459529098958}
|
||||
```
|
||||
|
||||
[pino-syslog]: https://www.npmjs.com/package/pino-syslog
|
||||
[rfc3164]: https://tools.ietf.org/html/rfc3164
|
||||
[logstash]: https://www.elastic.co/products/logstash
|
||||
|
||||
|
||||
<a id="pino-websocket"></a>
|
||||
### pino-websocket
|
||||
|
||||
[pino-websocket](https://www.npmjs.com/package/@abeai/pino-websocket) is a transport that will forward each log line to a websocket server.
|
||||
|
||||
```sh
|
||||
$ node app.js | pino-websocket -a my-websocket-server.example.com -p 3004
|
||||
```
|
||||
|
||||
For full documentation of command line switches read the [README](https://github.com/abeai/pino-websocket#readme).
|
||||
|
||||
<a id="pino-slack-webhook"></a>
|
||||
### pino-slack-webhook
|
||||
|
||||
[pino-slack-webhook][pino-slack-webhook] is a Pino v7+ compatible transport to forward log events to [Slack][Slack]
|
||||
from a dedicated worker:
|
||||
|
||||
```js
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: '@youngkiu/pino-slack-webhook',
|
||||
options: {
|
||||
webhookUrl: 'https://hooks.slack.com/services/xxx/xxx/xxx',
|
||||
channel: '#pino-log',
|
||||
username: 'webhookbot',
|
||||
icon_emoji: ':ghost:'
|
||||
}
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
[pino-slack-webhook]: https://github.com/youngkiu/pino-slack-webhook
|
||||
[Slack]: https://slack.com/
|
||||
|
||||
[pino-pretty]: https://github.com/pinojs/pino-pretty
|
||||
|
||||
For full documentation of command line switches read the [README](https://github.com/abeai/pino-websocket#readme).
|
||||
|
||||
<a id="pino-axiom"></a>
|
||||
### pino-axiom
|
||||
|
||||
[pino-axiom](https://www.npmjs.com/package/pino-axiom) is a transport that will forward logs to [Axiom](https://axiom.co).
|
||||
|
||||
```javascript
|
||||
const pino = require('pino')
|
||||
const transport = pino.transport({
|
||||
target: 'pino-axiom',
|
||||
options: {
|
||||
orgId: 'YOUR-ORG-ID',
|
||||
token: 'YOUR-TOKEN',
|
||||
dataset: 'YOUR-DATASET',
|
||||
},
|
||||
})
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
<a id="pino-opentelemetry-transport"></a>
|
||||
### pino-opentelemetry-transport
|
||||
|
||||
[pino-opentelemetry-transport](https://www.npmjs.com/package/pino-opentelemetry-transport) is a transport that will forward logs to an [OpenTelemetry log collector](https://opentelemetry.io/docs/collector/) using [OpenTelemetry JS instrumentation](https://opentelemetry.io/docs/instrumentation/js/).
|
||||
|
||||
```javascript
|
||||
const pino = require('pino')
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'pino-opentelemetry-transport',
|
||||
options: {
|
||||
resourceAttributes: {
|
||||
'service.name': 'test-service',
|
||||
'service.version': '1.0.0'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
pino(transport)
|
||||
```
|
||||
|
||||
Documentation on running a minimal example is available in the [README](https://github.com/Vunovati/pino-opentelemetry-transport#minimalistic-example).
|
||||
|
||||
<a id="@axiomhq/pino"></a>
|
||||
### @axiomhq/pino
|
||||
|
||||
[@axiomhq/pino](https://www.npmjs.com/package/@axiomhq/pino) is the official [Axiom](https://axiom.co/) transport for Pino, using [axiom-js](https://github.com/axiomhq/axiom-js).
|
||||
|
||||
```javascript
|
||||
import pino from 'pino';
|
||||
|
||||
const logger = pino(
|
||||
{ level: 'info' },
|
||||
pino.transport({
|
||||
target: '@axiomhq/pino',
|
||||
options: {
|
||||
dataset: process.env.AXIOM_DATASET,
|
||||
token: process.env.AXIOM_TOKEN,
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
then you can use the logger as usual:
|
||||
|
||||
```js
|
||||
logger.info('Hello from pino!');
|
||||
```
|
||||
|
||||
For further examples, head over to the [examples](https://github.com/axiomhq/axiom-js/tree/main/examples/pino) directory.
|
||||
|
||||
<a id="pino-discord-webhook"></a>
|
||||
### pino-discord-webhook
|
||||
|
||||
[pino-discord-webhook](https://github.com/fabulousgk/pino-discord-webhook) is a Pino v7+ compatible transport to forward log events to a [Discord](http://discord.com) webhook from a dedicated worker.
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-discord-webhook',
|
||||
options: {
|
||||
webhookUrl: 'https://discord.com/api/webhooks/xxxx/xxxx',
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<a id="pino-logfmt"></a>
|
||||
### pino-logfmt
|
||||
|
||||
[pino-logfmt](https://github.com/botflux/pino-logfmt) is a Pino v7+ transport that formats logs into [logfmt](https://brandur.org/logfmt). This transport can output the formatted logs to stdout or file.
|
||||
|
||||
```js
|
||||
import pino from 'pino'
|
||||
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'pino-logfmt'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
<a id="communication-between-pino-and-transport"></a>
|
||||
## Communication between Pino and Transports
|
||||
Here we discuss some technical details of how Pino communicates with its [worker threads](https://nodejs.org/api/worker_threads.html).
|
||||
|
||||
Pino uses [`thread-stream`](https://github.com/pinojs/thread-stream) to create a stream for transports.
|
||||
When we create a stream with `thread-stream`, `thread-stream` spawns a [worker](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/index.js#L50-L60) (an independent JavaScript execution thread).
|
||||
|
||||
### Error messages
|
||||
How are error messages propagated from a transport worker to Pino?
|
||||
|
||||
Let's assume we have a transport with an error listener:
|
||||
```js
|
||||
// index.js
|
||||
const transport = pino.transport({
|
||||
target: './transport.js'
|
||||
})
|
||||
|
||||
transport.on('error', err => {
|
||||
console.error('error caught', err)
|
||||
})
|
||||
|
||||
const log = pino(transport)
|
||||
```
|
||||
|
||||
When our worker emits an error event, the worker has listeners for it: [error](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/lib/worker.js#L59-L70) and [unhandledRejection](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/lib/worker.js#L135-L141). These listeners send the error message to the main thread where Pino is present.
|
||||
|
||||
When Pino receives the error message, it further [emits](https://github.com/pinojs/thread-stream/blob/f19ac8dbd602837d2851e17fbc7dfc5bbc51083f/index.js#L349) the error message. Finally, the error message arrives at our `index.js` and is caught by our error listener.
|
||||
@@ -0,0 +1,259 @@
|
||||
# Web Frameworks
|
||||
|
||||
Since HTTP logging is a primary use case, Pino has first-class support for the Node.js
|
||||
web framework ecosystem.
|
||||
|
||||
- [Web Frameworks](#web-frameworks)
|
||||
- [Pino with Fastify](#pino-with-fastify)
|
||||
- [Pino with Express](#pino-with-express)
|
||||
- [Pino with Hapi](#pino-with-hapi)
|
||||
- [Pino with Restify](#pino-with-restify)
|
||||
- [Pino with Koa](#pino-with-koa)
|
||||
- [Pino with Node core `http`](#pino-with-node-core-http)
|
||||
- [Pino with Nest](#pino-with-nest)
|
||||
- [Pino with H3](#pino-with-h3)
|
||||
|
||||
<a id="fastify"></a>
|
||||
## Pino with Fastify
|
||||
|
||||
The Fastify web framework comes bundled with Pino by default, simply set Fastify's
|
||||
`logger` option to `true` and use `request.log` or `reply.log` for log messages that correspond
|
||||
to each request:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
request.log.info('something')
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err) => {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
The `logger` option can also be set to an object, which will be passed through directly
|
||||
as the [`pino` options object](/docs/api.md#options-object).
|
||||
|
||||
See the [fastify documentation](https://www.fastify.io/docs/latest/Reference/Logging/) for more information.
|
||||
|
||||
<a id="express"></a>
|
||||
## Pino with Express
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
const app = require('express')()
|
||||
const pino = require('pino-http')()
|
||||
|
||||
app.use(pino)
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
req.log.info('something')
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
|
||||
<a id="hapi"></a>
|
||||
## Pino with Hapi
|
||||
|
||||
```sh
|
||||
npm install hapi-pino
|
||||
```
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const Hapi = require('@hapi/hapi')
|
||||
const Pino = require('hapi-pino');
|
||||
|
||||
async function start () {
|
||||
// Create a server with a host and port
|
||||
const server = Hapi.server({
|
||||
host: 'localhost',
|
||||
port: 3000
|
||||
})
|
||||
|
||||
// Add the route
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/',
|
||||
handler: async function (request, h) {
|
||||
// request.log is HAPI's standard way of logging
|
||||
request.log(['a', 'b'], 'Request into hello world')
|
||||
|
||||
// a pino instance can also be used, which will be faster
|
||||
request.logger.info('In handler %s', request.path)
|
||||
|
||||
return 'hello world'
|
||||
}
|
||||
})
|
||||
|
||||
await server.register(Pino)
|
||||
|
||||
// also as a decorated API
|
||||
server.logger.info('another way for accessing it')
|
||||
|
||||
// and through Hapi standard logging system
|
||||
server.log(['subsystem'], 'third way for accessing it')
|
||||
|
||||
await server.start()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
console.log(err)
|
||||
process.exit(1)
|
||||
})
|
||||
```
|
||||
|
||||
See the [hapi-pino README](https://npm.im/hapi-pino) for more info.
|
||||
|
||||
<a id="restify"></a>
|
||||
## Pino with Restify
|
||||
|
||||
```sh
|
||||
npm install restify-pino-logger
|
||||
```
|
||||
|
||||
```js
|
||||
const server = require('restify').createServer({name: 'server'})
|
||||
const pino = require('restify-pino-logger')()
|
||||
|
||||
server.use(pino)
|
||||
|
||||
server.get('/', function (req, res) {
|
||||
req.log.info('something')
|
||||
res.send('hello world')
|
||||
})
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
See the [restify-pino-logger README](https://npm.im/restify-pino-logger) for more info.
|
||||
|
||||
<a id="koa"></a>
|
||||
## Pino with Koa
|
||||
|
||||
```sh
|
||||
npm install koa-pino-logger
|
||||
```
|
||||
|
||||
```js
|
||||
const Koa = require('koa')
|
||||
const app = new Koa()
|
||||
const pino = require('koa-pino-logger')()
|
||||
|
||||
app.use(pino)
|
||||
|
||||
app.use((ctx) => {
|
||||
ctx.log.info('something else')
|
||||
ctx.body = 'hello world'
|
||||
})
|
||||
|
||||
app.listen(3000)
|
||||
```
|
||||
|
||||
See the [koa-pino-logger README](https://github.com/pinojs/koa-pino-logger) for more info.
|
||||
|
||||
<a id="http"></a>
|
||||
## Pino with Node core `http`
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
const http = require('http')
|
||||
const server = http.createServer(handle)
|
||||
const logger = require('pino-http')()
|
||||
|
||||
function handle (req, res) {
|
||||
logger(req, res)
|
||||
req.log.info('something else')
|
||||
res.end('hello world')
|
||||
}
|
||||
|
||||
server.listen(3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
|
||||
|
||||
<a id="nest"></a>
|
||||
## Pino with Nest
|
||||
|
||||
```sh
|
||||
npm install nestjs-pino
|
||||
```
|
||||
|
||||
```ts
|
||||
import { NestFactory } from '@nestjs/core'
|
||||
import { Controller, Get, Module } from '@nestjs/common'
|
||||
import { LoggerModule, Logger } from 'nestjs-pino'
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly logger: Logger) {}
|
||||
|
||||
@Get()
|
||||
getHello() {
|
||||
this.logger.log('something')
|
||||
return `Hello world`
|
||||
}
|
||||
}
|
||||
|
||||
@Module({
|
||||
controllers: [AppController],
|
||||
imports: [LoggerModule.forRoot()]
|
||||
})
|
||||
class MyModule {}
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(MyModule)
|
||||
await app.listen(3000)
|
||||
}
|
||||
bootstrap()
|
||||
```
|
||||
|
||||
See the [nestjs-pino README](https://npm.im/nestjs-pino) for more info.
|
||||
|
||||
|
||||
<a id="h3"></a>
|
||||
## Pino with H3
|
||||
|
||||
```sh
|
||||
npm install pino-http
|
||||
```
|
||||
|
||||
```js
|
||||
import { createServer } from 'http'
|
||||
import { createApp } from 'h3'
|
||||
import pino from 'pino-http'
|
||||
|
||||
const app = createApp()
|
||||
|
||||
app.use(pino())
|
||||
|
||||
app.use('/', (req) => {
|
||||
req.log.info('something')
|
||||
return 'hello world'
|
||||
})
|
||||
|
||||
createServer(app).listen(process.env.PORT || 3000)
|
||||
```
|
||||
|
||||
See the [pino-http README](https://npm.im/pino-http) for more info.
|
||||
@@ -0,0 +1,26 @@
|
||||
* [Readme](/)
|
||||
* [API](/docs/api.md)
|
||||
* [Browser API](/docs/browser.md)
|
||||
* [Redaction](/docs/redaction.md)
|
||||
* [Child Loggers](/docs/child-loggers.md)
|
||||
* [Transports](/docs/transports.md)
|
||||
* [Web Frameworks](/docs/web.md)
|
||||
* [Pretty Printing](/docs/pretty.md)
|
||||
* [Asynchronous Logging](/docs/asynchronous.md)
|
||||
* [Ecosystem](/docs/ecosystem.md)
|
||||
* [Benchmarks](/docs/benchmarks.md)
|
||||
* [Long Term Support](/docs/lts.md)
|
||||
* [Help](/docs/help.md)
|
||||
* [Log rotation](/docs/help.md#rotate)
|
||||
* [Reopening log files](/docs/help.md#reopening)
|
||||
* [Saving to multiple files](/docs/help.md#multiple)
|
||||
* [Log filtering](/docs/help.md#filter-logs)
|
||||
* [Transports and systemd](/docs/help.md#transport-systemd)
|
||||
* [Duplicate keys](/docs/help.md#dupe-keys)
|
||||
* [Log levels as labels instead of numbers](/docs/help.md#level-string)
|
||||
* [Pino with `debug`](/docs/help.md#debug)
|
||||
* [Unicode and Windows terminal](/docs/help.md#windows)
|
||||
* [Mapping Pino Log Levels to Google Cloud Logging (Stackdriver) Severity Levels](/docs/help.md#stackdriver)
|
||||
* [Avoid Message Conflict](/docs/help.md#avoid-message-conflict)
|
||||
* [Best performance for logging to `stdout`](/docs/help.md#best-performance-for-stdout)
|
||||
* [Testing](/docs/help.md#testing)
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
// Pino's primary usage writes ndjson to `stdout`:
|
||||
const pino = require('..')()
|
||||
|
||||
// However, if "human readable" output is desired,
|
||||
// `pino-pretty` can be provided as the destination
|
||||
// stream by uncommenting the following line in place
|
||||
// of the previous declaration:
|
||||
// const pino = require('..')(require('pino-pretty')())
|
||||
|
||||
pino.info('hello world')
|
||||
pino.error('this is at error level')
|
||||
pino.info('the answer is %d', 42)
|
||||
pino.info({ obj: 42 }, 'hello world')
|
||||
pino.info({ obj: 42, b: 2 }, 'hello world')
|
||||
pino.info({ nested: { obj: 42 } }, 'nested')
|
||||
setImmediate(() => {
|
||||
pino.info('after setImmediate')
|
||||
})
|
||||
pino.error(new Error('an error'))
|
||||
|
||||
const child = pino.child({ a: 'property' })
|
||||
child.info('hello child!')
|
||||
|
||||
const childsChild = child.child({ another: 'property' })
|
||||
childsChild.info('hello baby..')
|
||||
|
||||
pino.debug('this should be mute')
|
||||
|
||||
pino.level = 'trace'
|
||||
|
||||
pino.debug('this is a debug statement')
|
||||
|
||||
pino.child({ another: 'property' }).debug('this is a debug statement via child')
|
||||
pino.trace('this is a trace statement')
|
||||
|
||||
pino.debug('this is a "debug" statement with "')
|
||||
|
||||
pino.info(new Error('kaboom'))
|
||||
pino.info(null)
|
||||
|
||||
pino.info(new Error('kaboom'), 'with', 'a', 'message')
|
||||
@@ -0,0 +1,68 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('..')
|
||||
const { tmpdir } = require('os')
|
||||
const { join } = require('path')
|
||||
|
||||
const file = join(tmpdir(), `pino-${process.pid}-example`)
|
||||
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'warn',
|
||||
target: 'pino/file',
|
||||
options: {
|
||||
destination: file
|
||||
}
|
||||
/*
|
||||
}, {
|
||||
level: 'info',
|
||||
target: 'pino-elasticsearch',
|
||||
options: {
|
||||
node: 'http://localhost:9200'
|
||||
}
|
||||
*/
|
||||
}, {
|
||||
level: 'info',
|
||||
target: 'pino-pretty'
|
||||
}]
|
||||
})
|
||||
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info({
|
||||
file
|
||||
}, 'logging destination')
|
||||
|
||||
logger.info('hello world')
|
||||
logger.error('this is at error level')
|
||||
logger.info('the answer is %d', 42)
|
||||
logger.info({ obj: 42 }, 'hello world')
|
||||
logger.info({ obj: 42, b: 2 }, 'hello world')
|
||||
logger.info({ nested: { obj: 42 } }, 'nested')
|
||||
logger.warn('WARNING!')
|
||||
setImmediate(() => {
|
||||
logger.info('after setImmediate')
|
||||
})
|
||||
logger.error(new Error('an error'))
|
||||
|
||||
const child = logger.child({ a: 'property' })
|
||||
child.info('hello child!')
|
||||
|
||||
const childsChild = child.child({ another: 'property' })
|
||||
childsChild.info('hello baby..')
|
||||
|
||||
logger.debug('this should be mute')
|
||||
|
||||
logger.level = 'trace'
|
||||
|
||||
logger.debug('this is a debug statement')
|
||||
|
||||
logger.child({ another: 'property' }).debug('this is a debug statement via child')
|
||||
logger.trace('this is a trace statement')
|
||||
|
||||
logger.debug('this is a "debug" statement with "')
|
||||
|
||||
logger.info(new Error('kaboom'))
|
||||
logger.info(null)
|
||||
|
||||
logger.info(new Error('kaboom'), 'with', 'a', 'message')
|
||||
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('./pino')
|
||||
const { once } = require('events')
|
||||
|
||||
module.exports = async function (opts = {}) {
|
||||
const destOpts = Object.assign({}, opts, { dest: opts.destination || 1, sync: false })
|
||||
delete destOpts.destination
|
||||
const destination = pino.destination(destOpts)
|
||||
await once(destination, 'ready')
|
||||
return destination
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
PATH=./node_modules/.bin:${PATH}
|
||||
CURRENT_VERSION=$(jq -r .version package.json)
|
||||
|
||||
case ${1} in
|
||||
Major | MAJOR | major)
|
||||
LEVEL=major
|
||||
;;
|
||||
|
||||
Minor | MINOR | minor)
|
||||
LEVEL=minor
|
||||
;;
|
||||
|
||||
Patch | PATCH | patch)
|
||||
LEVEL=patch
|
||||
;;
|
||||
|
||||
*)
|
||||
LEVEL=patch
|
||||
;;
|
||||
esac
|
||||
|
||||
NEW_VERSION=$(semver -i ${LEVEL} ${CURRENT_VERSION})
|
||||
echo "${CURRENT_VERSION} => ${NEW_VERSION}"
|
||||
read -n 1 -s -r -p "Press any key to continue (ctrl+c to abort)..."
|
||||
echo ""
|
||||
|
||||
echo "Patching package.json..."
|
||||
cat package.json | \
|
||||
jq --arg vers "${NEW_VERSION}" '.version = $vers' | \
|
||||
tee package.json 1>/dev/null
|
||||
|
||||
echo "Patching lib/meta.js ..."
|
||||
SED_SCRIPT=$(printf 's/%s/%s/' ${CURRENT_VERSION//\./\\.} ${NEW_VERSION//\./\\.})
|
||||
cat ./lib/meta.js | \
|
||||
sed -e ${SED_SCRIPT} | \
|
||||
tee ./lib/meta.js 1>/dev/null
|
||||
|
||||
echo "Done."
|
||||
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Pino - Super fast, all natural JSON logger for Node.js</title>
|
||||
<meta name="description" content="Super fast, all natural JSON logger for Node.js">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify-themeable/dist/css/theme-simple.css">
|
||||
<style>
|
||||
:root {
|
||||
--base-font-size: 16px;
|
||||
--theme-color: rgb(104, 118, 52);
|
||||
--link-color: rgb(104, 118, 52);
|
||||
--link-color--hover: rgb(137, 152, 100);
|
||||
--sidebar-name-margin: 0;
|
||||
--sidebar-name-padding: 0;
|
||||
--code-font-size: .9em;
|
||||
}
|
||||
.sidebar > h1 {
|
||||
margin-bottom: -.75em;
|
||||
margin-top: .75em;
|
||||
}
|
||||
.sidebar > h1 img {
|
||||
height: 4em;
|
||||
}
|
||||
.markdown-section a code {
|
||||
color: var(--link-color)!important;
|
||||
}
|
||||
.markdown-section code:not([class*="lang-"]):not([class*="language-"]) {
|
||||
white-space: unset
|
||||
}
|
||||
</style>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
<script>
|
||||
window.$docsify = {
|
||||
name: 'pino',
|
||||
logo: './pino-tree.png',
|
||||
loadSidebar: 'docsify/sidebar.md',
|
||||
repo: 'https://github.com/pinojs/pino',
|
||||
auto2top: true,
|
||||
ga: 'UA-103155139-1'
|
||||
}
|
||||
</script>
|
||||
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.min.js"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/ga.min.js"></script>
|
||||
<!-- To enable syntax highlighting on TypeScript codes: -->
|
||||
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-typescript.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
function noOpPrepareStackTrace (_, stack) {
|
||||
return stack
|
||||
}
|
||||
|
||||
module.exports = function getCallers () {
|
||||
const originalPrepare = Error.prepareStackTrace
|
||||
Error.prepareStackTrace = noOpPrepareStackTrace
|
||||
const stack = new Error().stack
|
||||
Error.prepareStackTrace = originalPrepare
|
||||
|
||||
if (!Array.isArray(stack)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const entries = stack.slice(2)
|
||||
|
||||
const fileNames = []
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry) {
|
||||
continue
|
||||
}
|
||||
|
||||
fileNames.push(entry.getFileName())
|
||||
}
|
||||
|
||||
return fileNames
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Represents default log level values
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
const DEFAULT_LEVELS = {
|
||||
trace: 10,
|
||||
debug: 20,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50,
|
||||
fatal: 60
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents sort order direction: `ascending` or `descending`
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
const SORTING_ORDER = {
|
||||
ASC: 'ASC',
|
||||
DESC: 'DESC'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
DEFAULT_LEVELS,
|
||||
SORTING_ORDER
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
'use strict'
|
||||
|
||||
const warning = require('process-warning')()
|
||||
module.exports = warning
|
||||
|
||||
// const warnName = 'PinoWarning'
|
||||
|
||||
// warning.create(warnName, 'PINODEP010', 'A new deprecation')
|
||||
@@ -0,0 +1,241 @@
|
||||
'use strict'
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
const {
|
||||
lsCacheSym,
|
||||
levelValSym,
|
||||
useOnlyCustomLevelsSym,
|
||||
streamSym,
|
||||
formattersSym,
|
||||
hooksSym,
|
||||
levelCompSym
|
||||
} = require('./symbols')
|
||||
const { noop, genLog } = require('./tools')
|
||||
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./constants')
|
||||
|
||||
const levelMethods = {
|
||||
fatal: (hook) => {
|
||||
const logFatal = genLog(DEFAULT_LEVELS.fatal, hook)
|
||||
return function (...args) {
|
||||
const stream = this[streamSym]
|
||||
logFatal.call(this, ...args)
|
||||
if (typeof stream.flushSync === 'function') {
|
||||
try {
|
||||
stream.flushSync()
|
||||
} catch (e) {
|
||||
// https://github.com/pinojs/pino/pull/740#discussion_r346788313
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (hook) => genLog(DEFAULT_LEVELS.error, hook),
|
||||
warn: (hook) => genLog(DEFAULT_LEVELS.warn, hook),
|
||||
info: (hook) => genLog(DEFAULT_LEVELS.info, hook),
|
||||
debug: (hook) => genLog(DEFAULT_LEVELS.debug, hook),
|
||||
trace: (hook) => genLog(DEFAULT_LEVELS.trace, hook)
|
||||
}
|
||||
|
||||
const nums = Object.keys(DEFAULT_LEVELS).reduce((o, k) => {
|
||||
o[DEFAULT_LEVELS[k]] = k
|
||||
return o
|
||||
}, {})
|
||||
|
||||
const initialLsCache = Object.keys(nums).reduce((o, k) => {
|
||||
o[k] = '{"level":' + Number(k)
|
||||
return o
|
||||
}, {})
|
||||
|
||||
function genLsCache (instance) {
|
||||
const formatter = instance[formattersSym].level
|
||||
const { labels } = instance.levels
|
||||
const cache = {}
|
||||
for (const label in labels) {
|
||||
const level = formatter(labels[label], Number(label))
|
||||
cache[label] = JSON.stringify(level).slice(0, -1)
|
||||
}
|
||||
instance[lsCacheSym] = cache
|
||||
return instance
|
||||
}
|
||||
|
||||
function isStandardLevel (level, useOnlyCustomLevels) {
|
||||
if (useOnlyCustomLevels) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (level) {
|
||||
case 'fatal':
|
||||
case 'error':
|
||||
case 'warn':
|
||||
case 'info':
|
||||
case 'debug':
|
||||
case 'trace':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function setLevel (level) {
|
||||
const { labels, values } = this.levels
|
||||
if (typeof level === 'number') {
|
||||
if (labels[level] === undefined) throw Error('unknown level value' + level)
|
||||
level = labels[level]
|
||||
}
|
||||
if (values[level] === undefined) throw Error('unknown level ' + level)
|
||||
const preLevelVal = this[levelValSym]
|
||||
const levelVal = this[levelValSym] = values[level]
|
||||
const useOnlyCustomLevelsVal = this[useOnlyCustomLevelsSym]
|
||||
const levelComparison = this[levelCompSym]
|
||||
const hook = this[hooksSym].logMethod
|
||||
|
||||
for (const key in values) {
|
||||
if (levelComparison(values[key], levelVal) === false) {
|
||||
this[key] = noop
|
||||
continue
|
||||
}
|
||||
this[key] = isStandardLevel(key, useOnlyCustomLevelsVal) ? levelMethods[key](hook) : genLog(values[key], hook)
|
||||
}
|
||||
|
||||
this.emit(
|
||||
'level-change',
|
||||
level,
|
||||
levelVal,
|
||||
labels[preLevelVal],
|
||||
preLevelVal,
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
function getLevel (level) {
|
||||
const { levels, levelVal } = this
|
||||
// protection against potential loss of Pino scope from serializers (edge case with circular refs - https://github.com/pinojs/pino/issues/833)
|
||||
return (levels && levels.labels) ? levels.labels[levelVal] : ''
|
||||
}
|
||||
|
||||
function isLevelEnabled (logLevel) {
|
||||
const { values } = this.levels
|
||||
const logLevelVal = values[logLevel]
|
||||
return logLevelVal !== undefined && this[levelCompSym](logLevelVal, this[levelValSym])
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given `current` level is enabled by comparing it
|
||||
* against the current threshold (`expected`).
|
||||
*
|
||||
* @param {SORTING_ORDER} direction comparison direction "ASC" or "DESC"
|
||||
* @param {number} current current log level number representation
|
||||
* @param {number} expected threshold value to compare with
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function compareLevel (direction, current, expected) {
|
||||
if (direction === SORTING_ORDER.DESC) {
|
||||
return current <= expected
|
||||
}
|
||||
|
||||
return current >= expected
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a level comparison function based on `levelComparison`
|
||||
* it could a default function which compares levels either in "ascending" or "descending" order or custom comparison function
|
||||
*
|
||||
* @param {SORTING_ORDER | Function} levelComparison sort levels order direction or custom comparison function
|
||||
* @returns Function
|
||||
*/
|
||||
function genLevelComparison (levelComparison) {
|
||||
if (typeof levelComparison === 'string') {
|
||||
return compareLevel.bind(null, levelComparison)
|
||||
}
|
||||
|
||||
return levelComparison
|
||||
}
|
||||
|
||||
function mappings (customLevels = null, useOnlyCustomLevels = false) {
|
||||
const customNums = customLevels
|
||||
/* eslint-disable */
|
||||
? Object.keys(customLevels).reduce((o, k) => {
|
||||
o[customLevels[k]] = k
|
||||
return o
|
||||
}, {})
|
||||
: null
|
||||
/* eslint-enable */
|
||||
|
||||
const labels = Object.assign(
|
||||
Object.create(Object.prototype, { Infinity: { value: 'silent' } }),
|
||||
useOnlyCustomLevels ? null : nums,
|
||||
customNums
|
||||
)
|
||||
const values = Object.assign(
|
||||
Object.create(Object.prototype, { silent: { value: Infinity } }),
|
||||
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
|
||||
customLevels
|
||||
)
|
||||
return { labels, values }
|
||||
}
|
||||
|
||||
function assertDefaultLevelFound (defaultLevel, customLevels, useOnlyCustomLevels) {
|
||||
if (typeof defaultLevel === 'number') {
|
||||
const values = [].concat(
|
||||
Object.keys(customLevels || {}).map(key => customLevels[key]),
|
||||
useOnlyCustomLevels ? [] : Object.keys(nums).map(level => +level),
|
||||
Infinity
|
||||
)
|
||||
if (!values.includes(defaultLevel)) {
|
||||
throw Error(`default level:${defaultLevel} must be included in custom levels`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const labels = Object.assign(
|
||||
Object.create(Object.prototype, { silent: { value: Infinity } }),
|
||||
useOnlyCustomLevels ? null : DEFAULT_LEVELS,
|
||||
customLevels
|
||||
)
|
||||
if (!(defaultLevel in labels)) {
|
||||
throw Error(`default level:${defaultLevel} must be included in custom levels`)
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoLevelCollisions (levels, customLevels) {
|
||||
const { labels, values } = levels
|
||||
for (const k in customLevels) {
|
||||
if (k in values) {
|
||||
throw Error('levels cannot be overridden')
|
||||
}
|
||||
if (customLevels[k] in labels) {
|
||||
throw Error('pre-existing level values cannot be used for new levels')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether `levelComparison` is correct
|
||||
*
|
||||
* @throws Error
|
||||
* @param {SORTING_ORDER | Function} levelComparison - value to validate
|
||||
* @returns
|
||||
*/
|
||||
function assertLevelComparison (levelComparison) {
|
||||
if (typeof levelComparison === 'function') {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof levelComparison === 'string' && Object.values(SORTING_ORDER).includes(levelComparison)) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Levels comparison should be one of "ASC", "DESC" or "function" type')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initialLsCache,
|
||||
genLsCache,
|
||||
levelMethods,
|
||||
getLevel,
|
||||
setLevel,
|
||||
isLevelEnabled,
|
||||
mappings,
|
||||
assertNoLevelCollisions,
|
||||
assertDefaultLevelFound,
|
||||
genLevelComparison,
|
||||
assertLevelComparison
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = { version: '8.21.0' }
|
||||
@@ -0,0 +1,188 @@
|
||||
'use strict'
|
||||
|
||||
const metadata = Symbol.for('pino.metadata')
|
||||
const { DEFAULT_LEVELS } = require('./constants')
|
||||
|
||||
const DEFAULT_INFO_LEVEL = DEFAULT_LEVELS.info
|
||||
|
||||
function multistream (streamsArray, opts) {
|
||||
let counter = 0
|
||||
streamsArray = streamsArray || []
|
||||
opts = opts || { dedupe: false }
|
||||
|
||||
const streamLevels = Object.create(DEFAULT_LEVELS)
|
||||
streamLevels.silent = Infinity
|
||||
if (opts.levels && typeof opts.levels === 'object') {
|
||||
Object.keys(opts.levels).forEach(i => {
|
||||
streamLevels[i] = opts.levels[i]
|
||||
})
|
||||
}
|
||||
|
||||
const res = {
|
||||
write,
|
||||
add,
|
||||
emit,
|
||||
flushSync,
|
||||
end,
|
||||
minLevel: 0,
|
||||
streams: [],
|
||||
clone,
|
||||
[metadata]: true,
|
||||
streamLevels
|
||||
}
|
||||
|
||||
if (Array.isArray(streamsArray)) {
|
||||
streamsArray.forEach(add, res)
|
||||
} else {
|
||||
add.call(res, streamsArray)
|
||||
}
|
||||
|
||||
// clean this object up
|
||||
// or it will stay allocated forever
|
||||
// as it is closed on the following closures
|
||||
streamsArray = null
|
||||
|
||||
return res
|
||||
|
||||
// we can exit early because the streams are ordered by level
|
||||
function write (data) {
|
||||
let dest
|
||||
const level = this.lastLevel
|
||||
const { streams } = this
|
||||
// for handling situation when several streams has the same level
|
||||
let recordedLevel = 0
|
||||
let stream
|
||||
|
||||
// if dedupe set to true we send logs to the stream with the highest level
|
||||
// therefore, we have to change sorting order
|
||||
for (let i = initLoopVar(streams.length, opts.dedupe); checkLoopVar(i, streams.length, opts.dedupe); i = adjustLoopVar(i, opts.dedupe)) {
|
||||
dest = streams[i]
|
||||
if (dest.level <= level) {
|
||||
if (recordedLevel !== 0 && recordedLevel !== dest.level) {
|
||||
break
|
||||
}
|
||||
stream = dest.stream
|
||||
if (stream[metadata]) {
|
||||
const { lastTime, lastMsg, lastObj, lastLogger } = this
|
||||
stream.lastLevel = level
|
||||
stream.lastTime = lastTime
|
||||
stream.lastMsg = lastMsg
|
||||
stream.lastObj = lastObj
|
||||
stream.lastLogger = lastLogger
|
||||
}
|
||||
stream.write(data)
|
||||
if (opts.dedupe) {
|
||||
recordedLevel = dest.level
|
||||
}
|
||||
} else if (!opts.dedupe) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emit (...args) {
|
||||
for (const { stream } of this.streams) {
|
||||
if (typeof stream.emit === 'function') {
|
||||
stream.emit(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flushSync () {
|
||||
for (const { stream } of this.streams) {
|
||||
if (typeof stream.flushSync === 'function') {
|
||||
stream.flushSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function add (dest) {
|
||||
if (!dest) {
|
||||
return res
|
||||
}
|
||||
|
||||
// Check that dest implements either StreamEntry or DestinationStream
|
||||
const isStream = typeof dest.write === 'function' || dest.stream
|
||||
const stream_ = dest.write ? dest : dest.stream
|
||||
// This is necessary to provide a meaningful error message, otherwise it throws somewhere inside write()
|
||||
if (!isStream) {
|
||||
throw Error('stream object needs to implement either StreamEntry or DestinationStream interface')
|
||||
}
|
||||
|
||||
const { streams, streamLevels } = this
|
||||
|
||||
let level
|
||||
if (typeof dest.levelVal === 'number') {
|
||||
level = dest.levelVal
|
||||
} else if (typeof dest.level === 'string') {
|
||||
level = streamLevels[dest.level]
|
||||
} else if (typeof dest.level === 'number') {
|
||||
level = dest.level
|
||||
} else {
|
||||
level = DEFAULT_INFO_LEVEL
|
||||
}
|
||||
|
||||
const dest_ = {
|
||||
stream: stream_,
|
||||
level,
|
||||
levelVal: undefined,
|
||||
id: counter++
|
||||
}
|
||||
|
||||
streams.unshift(dest_)
|
||||
streams.sort(compareByLevel)
|
||||
|
||||
this.minLevel = streams[0].level
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function end () {
|
||||
for (const { stream } of this.streams) {
|
||||
if (typeof stream.flushSync === 'function') {
|
||||
stream.flushSync()
|
||||
}
|
||||
stream.end()
|
||||
}
|
||||
}
|
||||
|
||||
function clone (level) {
|
||||
const streams = new Array(this.streams.length)
|
||||
|
||||
for (let i = 0; i < streams.length; i++) {
|
||||
streams[i] = {
|
||||
level,
|
||||
stream: this.streams[i].stream
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
write,
|
||||
add,
|
||||
minLevel: level,
|
||||
streams,
|
||||
clone,
|
||||
emit,
|
||||
flushSync,
|
||||
[metadata]: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function compareByLevel (a, b) {
|
||||
return a.level - b.level
|
||||
}
|
||||
|
||||
function initLoopVar (length, dedupe) {
|
||||
return dedupe ? length - 1 : 0
|
||||
}
|
||||
|
||||
function adjustLoopVar (i, dedupe) {
|
||||
return dedupe ? i - 1 : i + 1
|
||||
}
|
||||
|
||||
function checkLoopVar (i, length, dedupe) {
|
||||
return dedupe ? i >= 0 : i < length
|
||||
}
|
||||
|
||||
module.exports = multistream
|
||||
@@ -0,0 +1,232 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const {
|
||||
lsCacheSym,
|
||||
levelValSym,
|
||||
setLevelSym,
|
||||
getLevelSym,
|
||||
chindingsSym,
|
||||
parsedChindingsSym,
|
||||
mixinSym,
|
||||
asJsonSym,
|
||||
writeSym,
|
||||
mixinMergeStrategySym,
|
||||
timeSym,
|
||||
timeSliceIndexSym,
|
||||
streamSym,
|
||||
serializersSym,
|
||||
formattersSym,
|
||||
errorKeySym,
|
||||
messageKeySym,
|
||||
useOnlyCustomLevelsSym,
|
||||
needsMetadataGsym,
|
||||
redactFmtSym,
|
||||
stringifySym,
|
||||
formatOptsSym,
|
||||
stringifiersSym,
|
||||
msgPrefixSym
|
||||
} = require('./symbols')
|
||||
const {
|
||||
getLevel,
|
||||
setLevel,
|
||||
isLevelEnabled,
|
||||
mappings,
|
||||
initialLsCache,
|
||||
genLsCache,
|
||||
assertNoLevelCollisions
|
||||
} = require('./levels')
|
||||
const {
|
||||
asChindings,
|
||||
asJson,
|
||||
buildFormatters,
|
||||
stringify
|
||||
} = require('./tools')
|
||||
const {
|
||||
version
|
||||
} = require('./meta')
|
||||
const redaction = require('./redaction')
|
||||
|
||||
// note: use of class is satirical
|
||||
// https://github.com/pinojs/pino/pull/433#pullrequestreview-127703127
|
||||
const constructor = class Pino {}
|
||||
const prototype = {
|
||||
constructor,
|
||||
child,
|
||||
bindings,
|
||||
setBindings,
|
||||
flush,
|
||||
isLevelEnabled,
|
||||
version,
|
||||
get level () { return this[getLevelSym]() },
|
||||
set level (lvl) { this[setLevelSym](lvl) },
|
||||
get levelVal () { return this[levelValSym] },
|
||||
set levelVal (n) { throw Error('levelVal is read-only') },
|
||||
[lsCacheSym]: initialLsCache,
|
||||
[writeSym]: write,
|
||||
[asJsonSym]: asJson,
|
||||
[getLevelSym]: getLevel,
|
||||
[setLevelSym]: setLevel
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(prototype, EventEmitter.prototype)
|
||||
|
||||
// exporting and consuming the prototype object using factory pattern fixes scoping issues with getters when serializing
|
||||
module.exports = function () {
|
||||
return Object.create(prototype)
|
||||
}
|
||||
|
||||
const resetChildingsFormatter = bindings => bindings
|
||||
function child (bindings, options) {
|
||||
if (!bindings) {
|
||||
throw Error('missing bindings for child Pino')
|
||||
}
|
||||
options = options || {} // default options to empty object
|
||||
const serializers = this[serializersSym]
|
||||
const formatters = this[formattersSym]
|
||||
const instance = Object.create(this)
|
||||
|
||||
if (options.hasOwnProperty('serializers') === true) {
|
||||
instance[serializersSym] = Object.create(null)
|
||||
|
||||
for (const k in serializers) {
|
||||
instance[serializersSym][k] = serializers[k]
|
||||
}
|
||||
const parentSymbols = Object.getOwnPropertySymbols(serializers)
|
||||
/* eslint no-var: off */
|
||||
for (var i = 0; i < parentSymbols.length; i++) {
|
||||
const ks = parentSymbols[i]
|
||||
instance[serializersSym][ks] = serializers[ks]
|
||||
}
|
||||
|
||||
for (const bk in options.serializers) {
|
||||
instance[serializersSym][bk] = options.serializers[bk]
|
||||
}
|
||||
const bindingsSymbols = Object.getOwnPropertySymbols(options.serializers)
|
||||
for (var bi = 0; bi < bindingsSymbols.length; bi++) {
|
||||
const bks = bindingsSymbols[bi]
|
||||
instance[serializersSym][bks] = options.serializers[bks]
|
||||
}
|
||||
} else instance[serializersSym] = serializers
|
||||
if (options.hasOwnProperty('formatters')) {
|
||||
const { level, bindings: chindings, log } = options.formatters
|
||||
instance[formattersSym] = buildFormatters(
|
||||
level || formatters.level,
|
||||
chindings || resetChildingsFormatter,
|
||||
log || formatters.log
|
||||
)
|
||||
} else {
|
||||
instance[formattersSym] = buildFormatters(
|
||||
formatters.level,
|
||||
resetChildingsFormatter,
|
||||
formatters.log
|
||||
)
|
||||
}
|
||||
if (options.hasOwnProperty('customLevels') === true) {
|
||||
assertNoLevelCollisions(this.levels, options.customLevels)
|
||||
instance.levels = mappings(options.customLevels, instance[useOnlyCustomLevelsSym])
|
||||
genLsCache(instance)
|
||||
}
|
||||
|
||||
// redact must place before asChindings and only replace if exist
|
||||
if ((typeof options.redact === 'object' && options.redact !== null) || Array.isArray(options.redact)) {
|
||||
instance.redact = options.redact // replace redact directly
|
||||
const stringifiers = redaction(instance.redact, stringify)
|
||||
const formatOpts = { stringify: stringifiers[redactFmtSym] }
|
||||
instance[stringifySym] = stringify
|
||||
instance[stringifiersSym] = stringifiers
|
||||
instance[formatOptsSym] = formatOpts
|
||||
}
|
||||
|
||||
if (typeof options.msgPrefix === 'string') {
|
||||
instance[msgPrefixSym] = (this[msgPrefixSym] || '') + options.msgPrefix
|
||||
}
|
||||
|
||||
instance[chindingsSym] = asChindings(instance, bindings)
|
||||
const childLevel = options.level || this.level
|
||||
instance[setLevelSym](childLevel)
|
||||
this.onChild(instance)
|
||||
return instance
|
||||
}
|
||||
|
||||
function bindings () {
|
||||
const chindings = this[chindingsSym]
|
||||
const chindingsJson = `{${chindings.substr(1)}}` // at least contains ,"pid":7068,"hostname":"myMac"
|
||||
const bindingsFromJson = JSON.parse(chindingsJson)
|
||||
delete bindingsFromJson.pid
|
||||
delete bindingsFromJson.hostname
|
||||
return bindingsFromJson
|
||||
}
|
||||
|
||||
function setBindings (newBindings) {
|
||||
const chindings = asChindings(this, newBindings)
|
||||
this[chindingsSym] = chindings
|
||||
delete this[parsedChindingsSym]
|
||||
}
|
||||
|
||||
/**
|
||||
* Default strategy for creating `mergeObject` from arguments and the result from `mixin()`.
|
||||
* Fields from `mergeObject` have higher priority in this strategy.
|
||||
*
|
||||
* @param {Object} mergeObject The object a user has supplied to the logging function.
|
||||
* @param {Object} mixinObject The result of the `mixin` method.
|
||||
* @return {Object}
|
||||
*/
|
||||
function defaultMixinMergeStrategy (mergeObject, mixinObject) {
|
||||
return Object.assign(mixinObject, mergeObject)
|
||||
}
|
||||
|
||||
function write (_obj, msg, num) {
|
||||
const t = this[timeSym]()
|
||||
const mixin = this[mixinSym]
|
||||
const errorKey = this[errorKeySym]
|
||||
const messageKey = this[messageKeySym]
|
||||
const mixinMergeStrategy = this[mixinMergeStrategySym] || defaultMixinMergeStrategy
|
||||
let obj
|
||||
|
||||
if (_obj === undefined || _obj === null) {
|
||||
obj = {}
|
||||
} else if (_obj instanceof Error) {
|
||||
obj = { [errorKey]: _obj }
|
||||
if (msg === undefined) {
|
||||
msg = _obj.message
|
||||
}
|
||||
} else {
|
||||
obj = _obj
|
||||
if (msg === undefined && _obj[messageKey] === undefined && _obj[errorKey]) {
|
||||
msg = _obj[errorKey].message
|
||||
}
|
||||
}
|
||||
|
||||
if (mixin) {
|
||||
obj = mixinMergeStrategy(obj, mixin(obj, num, this))
|
||||
}
|
||||
|
||||
const s = this[asJsonSym](obj, msg, num, t)
|
||||
|
||||
const stream = this[streamSym]
|
||||
if (stream[needsMetadataGsym] === true) {
|
||||
stream.lastLevel = num
|
||||
stream.lastObj = obj
|
||||
stream.lastMsg = msg
|
||||
stream.lastTime = t.slice(this[timeSliceIndexSym])
|
||||
stream.lastLogger = this // for child loggers
|
||||
}
|
||||
stream.write(s)
|
||||
}
|
||||
|
||||
function noop () {}
|
||||
|
||||
function flush (cb) {
|
||||
if (cb != null && typeof cb !== 'function') {
|
||||
throw Error('callback must be a function')
|
||||
}
|
||||
|
||||
const stream = this[streamSym]
|
||||
|
||||
if (typeof stream.flush === 'function') {
|
||||
stream.flush(cb || noop)
|
||||
} else if (cb) cb()
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
'use strict'
|
||||
|
||||
const fastRedact = require('fast-redact')
|
||||
const { redactFmtSym, wildcardFirstSym } = require('./symbols')
|
||||
const { rx, validator } = fastRedact
|
||||
|
||||
const validate = validator({
|
||||
ERR_PATHS_MUST_BE_STRINGS: () => 'pino – redacted paths must be strings',
|
||||
ERR_INVALID_PATH: (s) => `pino – redact paths array contains an invalid path (${s})`
|
||||
})
|
||||
|
||||
const CENSOR = '[Redacted]'
|
||||
const strict = false // TODO should this be configurable?
|
||||
|
||||
function redaction (opts, serialize) {
|
||||
const { paths, censor } = handle(opts)
|
||||
|
||||
const shape = paths.reduce((o, str) => {
|
||||
rx.lastIndex = 0
|
||||
const first = rx.exec(str)
|
||||
const next = rx.exec(str)
|
||||
|
||||
// ns is the top-level path segment, brackets + quoting removed.
|
||||
let ns = first[1] !== undefined
|
||||
? first[1].replace(/^(?:"|'|`)(.*)(?:"|'|`)$/, '$1')
|
||||
: first[0]
|
||||
|
||||
if (ns === '*') {
|
||||
ns = wildcardFirstSym
|
||||
}
|
||||
|
||||
// top level key:
|
||||
if (next === null) {
|
||||
o[ns] = null
|
||||
return o
|
||||
}
|
||||
|
||||
// path with at least two segments:
|
||||
// if ns is already redacted at the top level, ignore lower level redactions
|
||||
if (o[ns] === null) {
|
||||
return o
|
||||
}
|
||||
|
||||
const { index } = next
|
||||
const nextPath = `${str.substr(index, str.length - 1)}`
|
||||
|
||||
o[ns] = o[ns] || []
|
||||
|
||||
// shape is a mix of paths beginning with literal values and wildcard
|
||||
// paths [ "a.b.c", "*.b.z" ] should reduce to a shape of
|
||||
// { "a": [ "b.c", "b.z" ], *: [ "b.z" ] }
|
||||
// note: "b.z" is in both "a" and * arrays because "a" matches the wildcard.
|
||||
// (* entry has wildcardFirstSym as key)
|
||||
if (ns !== wildcardFirstSym && o[ns].length === 0) {
|
||||
// first time ns's get all '*' redactions so far
|
||||
o[ns].push(...(o[wildcardFirstSym] || []))
|
||||
}
|
||||
|
||||
if (ns === wildcardFirstSym) {
|
||||
// new * path gets added to all previously registered literal ns's.
|
||||
Object.keys(o).forEach(function (k) {
|
||||
if (o[k]) {
|
||||
o[k].push(nextPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
o[ns].push(nextPath)
|
||||
return o
|
||||
}, {})
|
||||
|
||||
// the redactor assigned to the format symbol key
|
||||
// provides top level redaction for instances where
|
||||
// an object is interpolated into the msg string
|
||||
const result = {
|
||||
[redactFmtSym]: fastRedact({ paths, censor, serialize, strict })
|
||||
}
|
||||
|
||||
const topCensor = (...args) => {
|
||||
return typeof censor === 'function' ? serialize(censor(...args)) : serialize(censor)
|
||||
}
|
||||
|
||||
return [...Object.keys(shape), ...Object.getOwnPropertySymbols(shape)].reduce((o, k) => {
|
||||
// top level key:
|
||||
if (shape[k] === null) {
|
||||
o[k] = (value) => topCensor(value, [k])
|
||||
} else {
|
||||
const wrappedCensor = typeof censor === 'function'
|
||||
? (value, path) => {
|
||||
return censor(value, [k, ...path])
|
||||
}
|
||||
: censor
|
||||
o[k] = fastRedact({
|
||||
paths: shape[k],
|
||||
censor: wrappedCensor,
|
||||
serialize,
|
||||
strict
|
||||
})
|
||||
}
|
||||
return o
|
||||
}, result)
|
||||
}
|
||||
|
||||
function handle (opts) {
|
||||
if (Array.isArray(opts)) {
|
||||
opts = { paths: opts, censor: CENSOR }
|
||||
validate(opts)
|
||||
return opts
|
||||
}
|
||||
let { paths, censor = CENSOR, remove } = opts
|
||||
if (Array.isArray(paths) === false) { throw Error('pino – redact must contain an array of strings') }
|
||||
if (remove === true) censor = undefined
|
||||
validate({ paths, censor })
|
||||
|
||||
return { paths, censor }
|
||||
}
|
||||
|
||||
module.exports = redaction
|
||||
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const setLevelSym = Symbol('pino.setLevel')
|
||||
const getLevelSym = Symbol('pino.getLevel')
|
||||
const levelValSym = Symbol('pino.levelVal')
|
||||
const levelCompSym = Symbol('pino.levelComp')
|
||||
const useLevelLabelsSym = Symbol('pino.useLevelLabels')
|
||||
const useOnlyCustomLevelsSym = Symbol('pino.useOnlyCustomLevels')
|
||||
const mixinSym = Symbol('pino.mixin')
|
||||
|
||||
const lsCacheSym = Symbol('pino.lsCache')
|
||||
const chindingsSym = Symbol('pino.chindings')
|
||||
|
||||
const asJsonSym = Symbol('pino.asJson')
|
||||
const writeSym = Symbol('pino.write')
|
||||
const redactFmtSym = Symbol('pino.redactFmt')
|
||||
|
||||
const timeSym = Symbol('pino.time')
|
||||
const timeSliceIndexSym = Symbol('pino.timeSliceIndex')
|
||||
const streamSym = Symbol('pino.stream')
|
||||
const stringifySym = Symbol('pino.stringify')
|
||||
const stringifySafeSym = Symbol('pino.stringifySafe')
|
||||
const stringifiersSym = Symbol('pino.stringifiers')
|
||||
const endSym = Symbol('pino.end')
|
||||
const formatOptsSym = Symbol('pino.formatOpts')
|
||||
const messageKeySym = Symbol('pino.messageKey')
|
||||
const errorKeySym = Symbol('pino.errorKey')
|
||||
const nestedKeySym = Symbol('pino.nestedKey')
|
||||
const nestedKeyStrSym = Symbol('pino.nestedKeyStr')
|
||||
const mixinMergeStrategySym = Symbol('pino.mixinMergeStrategy')
|
||||
const msgPrefixSym = Symbol('pino.msgPrefix')
|
||||
|
||||
const wildcardFirstSym = Symbol('pino.wildcardFirst')
|
||||
|
||||
// public symbols, no need to use the same pino
|
||||
// version for these
|
||||
const serializersSym = Symbol.for('pino.serializers')
|
||||
const formattersSym = Symbol.for('pino.formatters')
|
||||
const hooksSym = Symbol.for('pino.hooks')
|
||||
const needsMetadataGsym = Symbol.for('pino.metadata')
|
||||
|
||||
module.exports = {
|
||||
setLevelSym,
|
||||
getLevelSym,
|
||||
levelValSym,
|
||||
levelCompSym,
|
||||
useLevelLabelsSym,
|
||||
mixinSym,
|
||||
lsCacheSym,
|
||||
chindingsSym,
|
||||
asJsonSym,
|
||||
writeSym,
|
||||
serializersSym,
|
||||
redactFmtSym,
|
||||
timeSym,
|
||||
timeSliceIndexSym,
|
||||
streamSym,
|
||||
stringifySym,
|
||||
stringifySafeSym,
|
||||
stringifiersSym,
|
||||
endSym,
|
||||
formatOptsSym,
|
||||
messageKeySym,
|
||||
errorKeySym,
|
||||
nestedKeySym,
|
||||
wildcardFirstSym,
|
||||
needsMetadataGsym,
|
||||
useOnlyCustomLevelsSym,
|
||||
formattersSym,
|
||||
hooksSym,
|
||||
nestedKeyStrSym,
|
||||
mixinMergeStrategySym,
|
||||
msgPrefixSym
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const nullTime = () => ''
|
||||
|
||||
const epochTime = () => `,"time":${Date.now()}`
|
||||
|
||||
const unixTime = () => `,"time":${Math.round(Date.now() / 1000.0)}`
|
||||
|
||||
const isoTime = () => `,"time":"${new Date(Date.now()).toISOString()}"` // using Date.now() for testability
|
||||
|
||||
module.exports = { nullTime, epochTime, unixTime, isoTime }
|
||||
@@ -0,0 +1,394 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const format = require('quick-format-unescaped')
|
||||
const { mapHttpRequest, mapHttpResponse } = require('pino-std-serializers')
|
||||
const SonicBoom = require('sonic-boom')
|
||||
const onExit = require('on-exit-leak-free')
|
||||
const {
|
||||
lsCacheSym,
|
||||
chindingsSym,
|
||||
writeSym,
|
||||
serializersSym,
|
||||
formatOptsSym,
|
||||
endSym,
|
||||
stringifiersSym,
|
||||
stringifySym,
|
||||
stringifySafeSym,
|
||||
wildcardFirstSym,
|
||||
nestedKeySym,
|
||||
formattersSym,
|
||||
messageKeySym,
|
||||
errorKeySym,
|
||||
nestedKeyStrSym,
|
||||
msgPrefixSym
|
||||
} = require('./symbols')
|
||||
const { isMainThread } = require('worker_threads')
|
||||
const transport = require('./transport')
|
||||
|
||||
function noop () {
|
||||
}
|
||||
|
||||
function genLog (level, hook) {
|
||||
if (!hook) return LOG
|
||||
|
||||
return function hookWrappedLog (...args) {
|
||||
hook.call(this, args, LOG, level)
|
||||
}
|
||||
|
||||
function LOG (o, ...n) {
|
||||
if (typeof o === 'object') {
|
||||
let msg = o
|
||||
if (o !== null) {
|
||||
if (o.method && o.headers && o.socket) {
|
||||
o = mapHttpRequest(o)
|
||||
} else if (typeof o.setHeader === 'function') {
|
||||
o = mapHttpResponse(o)
|
||||
}
|
||||
}
|
||||
let formatParams
|
||||
if (msg === null && n.length === 0) {
|
||||
formatParams = [null]
|
||||
} else {
|
||||
msg = n.shift()
|
||||
formatParams = n
|
||||
}
|
||||
// We do not use a coercive check for `msg` as it is
|
||||
// measurably slower than the explicit checks.
|
||||
if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
|
||||
msg = this[msgPrefixSym] + msg
|
||||
}
|
||||
this[writeSym](o, format(msg, formatParams, this[formatOptsSym]), level)
|
||||
} else {
|
||||
let msg = o === undefined ? n.shift() : o
|
||||
|
||||
// We do not use a coercive check for `msg` as it is
|
||||
// measurably slower than the explicit checks.
|
||||
if (typeof this[msgPrefixSym] === 'string' && msg !== undefined && msg !== null) {
|
||||
msg = this[msgPrefixSym] + msg
|
||||
}
|
||||
this[writeSym](null, format(msg, n, this[formatOptsSym]), level)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// magically escape strings for json
|
||||
// relying on their charCodeAt
|
||||
// everything below 32 needs JSON.stringify()
|
||||
// 34 and 92 happens all the time, so we
|
||||
// have a fast case for them
|
||||
function asString (str) {
|
||||
let result = ''
|
||||
let last = 0
|
||||
let found = false
|
||||
let point = 255
|
||||
const l = str.length
|
||||
if (l > 100) {
|
||||
return JSON.stringify(str)
|
||||
}
|
||||
for (var i = 0; i < l && point >= 32; i++) {
|
||||
point = str.charCodeAt(i)
|
||||
if (point === 34 || point === 92) {
|
||||
result += str.slice(last, i) + '\\'
|
||||
last = i
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result = str
|
||||
} else {
|
||||
result += str.slice(last)
|
||||
}
|
||||
return point < 32 ? JSON.stringify(str) : '"' + result + '"'
|
||||
}
|
||||
|
||||
function asJson (obj, msg, num, time) {
|
||||
const stringify = this[stringifySym]
|
||||
const stringifySafe = this[stringifySafeSym]
|
||||
const stringifiers = this[stringifiersSym]
|
||||
const end = this[endSym]
|
||||
const chindings = this[chindingsSym]
|
||||
const serializers = this[serializersSym]
|
||||
const formatters = this[formattersSym]
|
||||
const messageKey = this[messageKeySym]
|
||||
const errorKey = this[errorKeySym]
|
||||
let data = this[lsCacheSym][num] + time
|
||||
|
||||
// we need the child bindings added to the output first so instance logged
|
||||
// objects can take precedence when JSON.parse-ing the resulting log line
|
||||
data = data + chindings
|
||||
|
||||
let value
|
||||
if (formatters.log) {
|
||||
obj = formatters.log(obj)
|
||||
}
|
||||
const wildcardStringifier = stringifiers[wildcardFirstSym]
|
||||
let propStr = ''
|
||||
for (const key in obj) {
|
||||
value = obj[key]
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key) && value !== undefined) {
|
||||
if (serializers[key]) {
|
||||
value = serializers[key](value)
|
||||
} else if (key === errorKey && serializers.err) {
|
||||
value = serializers.err(value)
|
||||
}
|
||||
|
||||
const stringifier = stringifiers[key] || wildcardStringifier
|
||||
|
||||
switch (typeof value) {
|
||||
case 'undefined':
|
||||
case 'function':
|
||||
continue
|
||||
case 'number':
|
||||
/* eslint no-fallthrough: "off" */
|
||||
if (Number.isFinite(value) === false) {
|
||||
value = null
|
||||
}
|
||||
// this case explicitly falls through to the next one
|
||||
case 'boolean':
|
||||
if (stringifier) value = stringifier(value)
|
||||
break
|
||||
case 'string':
|
||||
value = (stringifier || asString)(value)
|
||||
break
|
||||
default:
|
||||
value = (stringifier || stringify)(value, stringifySafe)
|
||||
}
|
||||
if (value === undefined) continue
|
||||
const strKey = asString(key)
|
||||
propStr += ',' + strKey + ':' + value
|
||||
}
|
||||
}
|
||||
|
||||
let msgStr = ''
|
||||
if (msg !== undefined) {
|
||||
value = serializers[messageKey] ? serializers[messageKey](msg) : msg
|
||||
const stringifier = stringifiers[messageKey] || wildcardStringifier
|
||||
|
||||
switch (typeof value) {
|
||||
case 'function':
|
||||
break
|
||||
case 'number':
|
||||
/* eslint no-fallthrough: "off" */
|
||||
if (Number.isFinite(value) === false) {
|
||||
value = null
|
||||
}
|
||||
// this case explicitly falls through to the next one
|
||||
case 'boolean':
|
||||
if (stringifier) value = stringifier(value)
|
||||
msgStr = ',"' + messageKey + '":' + value
|
||||
break
|
||||
case 'string':
|
||||
value = (stringifier || asString)(value)
|
||||
msgStr = ',"' + messageKey + '":' + value
|
||||
break
|
||||
default:
|
||||
value = (stringifier || stringify)(value, stringifySafe)
|
||||
msgStr = ',"' + messageKey + '":' + value
|
||||
}
|
||||
}
|
||||
|
||||
if (this[nestedKeySym] && propStr) {
|
||||
// place all the obj properties under the specified key
|
||||
// the nested key is already formatted from the constructor
|
||||
return data + this[nestedKeyStrSym] + propStr.slice(1) + '}' + msgStr + end
|
||||
} else {
|
||||
return data + propStr + msgStr + end
|
||||
}
|
||||
}
|
||||
|
||||
function asChindings (instance, bindings) {
|
||||
let value
|
||||
let data = instance[chindingsSym]
|
||||
const stringify = instance[stringifySym]
|
||||
const stringifySafe = instance[stringifySafeSym]
|
||||
const stringifiers = instance[stringifiersSym]
|
||||
const wildcardStringifier = stringifiers[wildcardFirstSym]
|
||||
const serializers = instance[serializersSym]
|
||||
const formatter = instance[formattersSym].bindings
|
||||
bindings = formatter(bindings)
|
||||
|
||||
for (const key in bindings) {
|
||||
value = bindings[key]
|
||||
const valid = key !== 'level' &&
|
||||
key !== 'serializers' &&
|
||||
key !== 'formatters' &&
|
||||
key !== 'customLevels' &&
|
||||
bindings.hasOwnProperty(key) &&
|
||||
value !== undefined
|
||||
if (valid === true) {
|
||||
value = serializers[key] ? serializers[key](value) : value
|
||||
value = (stringifiers[key] || wildcardStringifier || stringify)(value, stringifySafe)
|
||||
if (value === undefined) continue
|
||||
data += ',"' + key + '":' + value
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function hasBeenTampered (stream) {
|
||||
return stream.write !== stream.constructor.prototype.write
|
||||
}
|
||||
|
||||
const hasNodeCodeCoverage = process.env.NODE_V8_COVERAGE || process.env.V8_COVERAGE
|
||||
|
||||
function buildSafeSonicBoom (opts) {
|
||||
const stream = new SonicBoom(opts)
|
||||
stream.on('error', filterBrokenPipe)
|
||||
// If we are sync: false, we must flush on exit
|
||||
// We must disable this if there is node code coverage due to
|
||||
// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308.
|
||||
if (!hasNodeCodeCoverage && !opts.sync && isMainThread) {
|
||||
onExit.register(stream, autoEnd)
|
||||
|
||||
stream.on('close', function () {
|
||||
onExit.unregister(stream)
|
||||
})
|
||||
}
|
||||
return stream
|
||||
|
||||
function filterBrokenPipe (err) {
|
||||
// Impossible to replicate across all operating systems
|
||||
/* istanbul ignore next */
|
||||
if (err.code === 'EPIPE') {
|
||||
// If we get EPIPE, we should stop logging here
|
||||
// however we have no control to the consumer of
|
||||
// SonicBoom, so we just overwrite the write method
|
||||
stream.write = noop
|
||||
stream.end = noop
|
||||
stream.flushSync = noop
|
||||
stream.destroy = noop
|
||||
return
|
||||
}
|
||||
stream.removeListener('error', filterBrokenPipe)
|
||||
stream.emit('error', err)
|
||||
}
|
||||
}
|
||||
|
||||
function autoEnd (stream, eventName) {
|
||||
// This check is needed only on some platforms
|
||||
/* istanbul ignore next */
|
||||
if (stream.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (eventName === 'beforeExit') {
|
||||
// We still have an event loop, let's use it
|
||||
stream.flush()
|
||||
stream.on('drain', function () {
|
||||
stream.end()
|
||||
})
|
||||
} else {
|
||||
// For some reason istanbul is not detecting this, but it's there
|
||||
/* istanbul ignore next */
|
||||
// We do not have an event loop, so flush synchronously
|
||||
stream.flushSync()
|
||||
}
|
||||
}
|
||||
|
||||
function createArgsNormalizer (defaultOptions) {
|
||||
return function normalizeArgs (instance, caller, opts = {}, stream) {
|
||||
// support stream as a string
|
||||
if (typeof opts === 'string') {
|
||||
stream = buildSafeSonicBoom({ dest: opts })
|
||||
opts = {}
|
||||
} else if (typeof stream === 'string') {
|
||||
if (opts && opts.transport) {
|
||||
throw Error('only one of option.transport or stream can be specified')
|
||||
}
|
||||
stream = buildSafeSonicBoom({ dest: stream })
|
||||
} else if (opts instanceof SonicBoom || opts.writable || opts._writableState) {
|
||||
stream = opts
|
||||
opts = {}
|
||||
} else if (opts.transport) {
|
||||
if (opts.transport instanceof SonicBoom || opts.transport.writable || opts.transport._writableState) {
|
||||
throw Error('option.transport do not allow stream, please pass to option directly. e.g. pino(transport)')
|
||||
}
|
||||
if (opts.transport.targets && opts.transport.targets.length && opts.formatters && typeof opts.formatters.level === 'function') {
|
||||
throw Error('option.transport.targets do not allow custom level formatters')
|
||||
}
|
||||
|
||||
let customLevels
|
||||
if (opts.customLevels) {
|
||||
customLevels = opts.useOnlyCustomLevels ? opts.customLevels : Object.assign({}, opts.levels, opts.customLevels)
|
||||
}
|
||||
stream = transport({ caller, ...opts.transport, levels: customLevels })
|
||||
}
|
||||
opts = Object.assign({}, defaultOptions, opts)
|
||||
opts.serializers = Object.assign({}, defaultOptions.serializers, opts.serializers)
|
||||
opts.formatters = Object.assign({}, defaultOptions.formatters, opts.formatters)
|
||||
|
||||
if (opts.prettyPrint) {
|
||||
throw new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)')
|
||||
}
|
||||
|
||||
const { enabled, onChild } = opts
|
||||
if (enabled === false) opts.level = 'silent'
|
||||
if (!onChild) opts.onChild = noop
|
||||
if (!stream) {
|
||||
if (!hasBeenTampered(process.stdout)) {
|
||||
// If process.stdout.fd is undefined, it means that we are running
|
||||
// in a worker thread. Let's assume we are logging to file descriptor 1.
|
||||
stream = buildSafeSonicBoom({ fd: process.stdout.fd || 1 })
|
||||
} else {
|
||||
stream = process.stdout
|
||||
}
|
||||
}
|
||||
return { opts, stream }
|
||||
}
|
||||
}
|
||||
|
||||
function stringify (obj, stringifySafeFn) {
|
||||
try {
|
||||
return JSON.stringify(obj)
|
||||
} catch (_) {
|
||||
try {
|
||||
const stringify = stringifySafeFn || this[stringifySafeSym]
|
||||
return stringify(obj)
|
||||
} catch (_) {
|
||||
return '"[unable to serialize, circular reference is too complex to analyze]"'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildFormatters (level, bindings, log) {
|
||||
return {
|
||||
level,
|
||||
bindings,
|
||||
log
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string integer file descriptor to a proper native integer
|
||||
* file descriptor.
|
||||
*
|
||||
* @param {string} destination The file descriptor string to attempt to convert.
|
||||
*
|
||||
* @returns {Number}
|
||||
*/
|
||||
function normalizeDestFileDescriptor (destination) {
|
||||
const fd = Number(destination)
|
||||
if (typeof destination === 'string' && Number.isFinite(fd)) {
|
||||
return fd
|
||||
}
|
||||
// destination could be undefined if we are in a worker
|
||||
if (destination === undefined) {
|
||||
// This is stdout in UNIX systems
|
||||
return 1
|
||||
}
|
||||
return destination
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
noop,
|
||||
buildSafeSonicBoom,
|
||||
asChindings,
|
||||
asJson,
|
||||
genLog,
|
||||
createArgsNormalizer,
|
||||
stringify,
|
||||
buildFormatters,
|
||||
normalizeDestFileDescriptor
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
const { realImport, realRequire } = require('real-require')
|
||||
|
||||
module.exports = loadTransportStreamBuilder
|
||||
|
||||
/**
|
||||
* Loads & returns a function to build transport streams
|
||||
* @param {string} target
|
||||
* @returns {function(object): Promise<import('stream').Writable>}
|
||||
* @throws {Error} In case the target module does not export a function
|
||||
*/
|
||||
async function loadTransportStreamBuilder (target) {
|
||||
let fn
|
||||
try {
|
||||
const toLoad = 'file://' + target
|
||||
|
||||
if (toLoad.endsWith('.ts') || toLoad.endsWith('.cts')) {
|
||||
// TODO: add support for the TSM modules loader ( https://github.com/lukeed/tsm ).
|
||||
if (process[Symbol.for('ts-node.register.instance')]) {
|
||||
realRequire('ts-node/register')
|
||||
} else if (process.env && process.env.TS_NODE_DEV) {
|
||||
realRequire('ts-node-dev')
|
||||
}
|
||||
// TODO: Support ES imports once tsc, tap & ts-node provide better compatibility guarantees.
|
||||
fn = realRequire(decodeURIComponent(target))
|
||||
} else {
|
||||
fn = (await realImport(toLoad))
|
||||
}
|
||||
} catch (error) {
|
||||
// See this PR for details: https://github.com/pinojs/thread-stream/pull/34
|
||||
if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) {
|
||||
fn = realRequire(target)
|
||||
} else if (error.code === undefined) {
|
||||
// When bundled with pkg, an undefined error is thrown when called with realImport
|
||||
fn = realRequire(decodeURIComponent(target))
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on how the default export is performed, and on how the code is
|
||||
// transpiled, we may find cases of two nested "default" objects.
|
||||
// See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762
|
||||
if (typeof fn === 'object') fn = fn.default
|
||||
if (typeof fn === 'object') fn = fn.default
|
||||
if (typeof fn !== 'function') throw Error('exported worker is not a function')
|
||||
|
||||
return fn
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
'use strict'
|
||||
|
||||
const { createRequire } = require('module')
|
||||
const getCallers = require('./caller')
|
||||
const { join, isAbsolute, sep } = require('path')
|
||||
const sleep = require('atomic-sleep')
|
||||
const onExit = require('on-exit-leak-free')
|
||||
const ThreadStream = require('thread-stream')
|
||||
|
||||
function setupOnExit (stream) {
|
||||
// This is leak free, it does not leave event handlers
|
||||
onExit.register(stream, autoEnd)
|
||||
onExit.registerBeforeExit(stream, flush)
|
||||
|
||||
stream.on('close', function () {
|
||||
onExit.unregister(stream)
|
||||
})
|
||||
}
|
||||
|
||||
function buildStream (filename, workerData, workerOpts) {
|
||||
const stream = new ThreadStream({
|
||||
filename,
|
||||
workerData,
|
||||
workerOpts
|
||||
})
|
||||
|
||||
stream.on('ready', onReady)
|
||||
stream.on('close', function () {
|
||||
process.removeListener('exit', onExit)
|
||||
})
|
||||
|
||||
process.on('exit', onExit)
|
||||
|
||||
function onReady () {
|
||||
process.removeListener('exit', onExit)
|
||||
stream.unref()
|
||||
|
||||
if (workerOpts.autoEnd !== false) {
|
||||
setupOnExit(stream)
|
||||
}
|
||||
}
|
||||
|
||||
function onExit () {
|
||||
/* istanbul ignore next */
|
||||
if (stream.closed) {
|
||||
return
|
||||
}
|
||||
stream.flushSync()
|
||||
// Apparently there is a very sporadic race condition
|
||||
// that in certain OS would prevent the messages to be flushed
|
||||
// because the thread might not have been created still.
|
||||
// Unfortunately we need to sleep(100) in this case.
|
||||
sleep(100)
|
||||
stream.end()
|
||||
}
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
function autoEnd (stream) {
|
||||
stream.ref()
|
||||
stream.flushSync()
|
||||
stream.end()
|
||||
stream.once('close', function () {
|
||||
stream.unref()
|
||||
})
|
||||
}
|
||||
|
||||
function flush (stream) {
|
||||
stream.flushSync()
|
||||
}
|
||||
|
||||
function transport (fullOptions) {
|
||||
const { pipeline, targets, levels, dedupe, options = {}, worker = {}, caller = getCallers() } = fullOptions
|
||||
|
||||
// Backwards compatibility
|
||||
const callers = typeof caller === 'string' ? [caller] : caller
|
||||
|
||||
// This will be eventually modified by bundlers
|
||||
const bundlerOverrides = '__bundlerPathsOverrides' in globalThis ? globalThis.__bundlerPathsOverrides : {}
|
||||
|
||||
let target = fullOptions.target
|
||||
|
||||
if (target && targets) {
|
||||
throw new Error('only one of target or targets can be specified')
|
||||
}
|
||||
|
||||
if (targets) {
|
||||
target = bundlerOverrides['pino-worker'] || join(__dirname, 'worker.js')
|
||||
options.targets = targets.map((dest) => {
|
||||
return {
|
||||
...dest,
|
||||
target: fixTarget(dest.target)
|
||||
}
|
||||
})
|
||||
} else if (pipeline) {
|
||||
target = bundlerOverrides['pino-pipeline-worker'] || join(__dirname, 'worker-pipeline.js')
|
||||
options.targets = pipeline.map((dest) => {
|
||||
return {
|
||||
...dest,
|
||||
target: fixTarget(dest.target)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (levels) {
|
||||
options.levels = levels
|
||||
}
|
||||
|
||||
if (dedupe) {
|
||||
options.dedupe = dedupe
|
||||
}
|
||||
|
||||
options.pinoWillSendConfig = true
|
||||
|
||||
return buildStream(fixTarget(target), options, worker)
|
||||
|
||||
function fixTarget (origin) {
|
||||
origin = bundlerOverrides[origin] || origin
|
||||
|
||||
if (isAbsolute(origin) || origin.indexOf('file://') === 0) {
|
||||
return origin
|
||||
}
|
||||
|
||||
if (origin === 'pino/file') {
|
||||
return join(__dirname, '..', 'file.js')
|
||||
}
|
||||
|
||||
let fixTarget
|
||||
|
||||
for (const filePath of callers) {
|
||||
try {
|
||||
const context = filePath === 'node:repl'
|
||||
? process.cwd() + sep
|
||||
: filePath
|
||||
|
||||
fixTarget = createRequire(context).resolve(origin)
|
||||
break
|
||||
} catch (err) {
|
||||
// Silent catch
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!fixTarget) {
|
||||
throw new Error(`unable to determine transport target for "${origin}"`)
|
||||
}
|
||||
|
||||
return fixTarget
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = transport
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const EE = require('events')
|
||||
const loadTransportStreamBuilder = require('./transport-stream')
|
||||
const { pipeline, PassThrough } = require('stream')
|
||||
|
||||
// This file is not checked by the code coverage tool,
|
||||
// as it is not reliable.
|
||||
|
||||
/* istanbul ignore file */
|
||||
|
||||
module.exports = async function ({ targets }) {
|
||||
const streams = await Promise.all(targets.map(async (t) => {
|
||||
const fn = await loadTransportStreamBuilder(t.target)
|
||||
const stream = await fn(t.options)
|
||||
return stream
|
||||
}))
|
||||
const ee = new EE()
|
||||
|
||||
const stream = new PassThrough({
|
||||
autoDestroy: true,
|
||||
destroy (_, cb) {
|
||||
ee.on('error', cb)
|
||||
ee.on('closed', cb)
|
||||
}
|
||||
})
|
||||
|
||||
pipeline(stream, ...streams, function (err) {
|
||||
if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
ee.emit('error', err)
|
||||
return
|
||||
}
|
||||
|
||||
ee.emit('closed')
|
||||
})
|
||||
|
||||
return stream
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../pino.js')
|
||||
const build = require('pino-abstract-transport')
|
||||
const loadTransportStreamBuilder = require('./transport-stream')
|
||||
|
||||
// This file is not checked by the code coverage tool,
|
||||
// as it is not reliable.
|
||||
|
||||
/* istanbul ignore file */
|
||||
|
||||
module.exports = async function ({ targets, levels, dedupe }) {
|
||||
targets = await Promise.all(targets.map(async (t) => {
|
||||
const fn = await loadTransportStreamBuilder(t.target)
|
||||
const stream = await fn(t.options)
|
||||
return {
|
||||
level: t.level,
|
||||
stream
|
||||
}
|
||||
}))
|
||||
return build(process, {
|
||||
parse: 'lines',
|
||||
metadata: true,
|
||||
close (err, cb) {
|
||||
let expected = 0
|
||||
for (const transport of targets) {
|
||||
expected++
|
||||
transport.stream.on('close', closeCb)
|
||||
transport.stream.end()
|
||||
}
|
||||
|
||||
function closeCb () {
|
||||
if (--expected === 0) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function process (stream) {
|
||||
const multi = pino.multistream(targets, { levels, dedupe })
|
||||
// TODO manage backpressure
|
||||
stream.on('data', function (chunk) {
|
||||
const { lastTime, lastMsg, lastObj, lastLevel } = this
|
||||
multi.lastLevel = lastLevel
|
||||
multi.lastTime = lastTime
|
||||
multi.lastMsg = lastMsg
|
||||
multi.lastObj = lastObj
|
||||
|
||||
// TODO handle backpressure
|
||||
multi.write(chunk + '\n')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"name": "pino",
|
||||
"version": "8.21.0",
|
||||
"description": "super fast, all natural json logger",
|
||||
"main": "pino.js",
|
||||
"type": "commonjs",
|
||||
"types": "pino.d.ts",
|
||||
"browser": "./browser.js",
|
||||
"scripts": {
|
||||
"docs": "docsify serve",
|
||||
"browser-test": "airtap --local 8080 test/browser*test.js",
|
||||
"lint": "eslint .",
|
||||
"prepublishOnly": "tap --no-check-coverage test/internals/version.test.js",
|
||||
"test": "npm run lint && npm run transpile && tap --ts && jest test/jest && npm run test-types",
|
||||
"test-ci": "npm run lint && npm run transpile && tap --ts --no-check-coverage --coverage-report=lcovonly && npm run test-types",
|
||||
"test-ci-pnpm": "pnpm run lint && npm run transpile && tap --ts --no-coverage --no-check-coverage && pnpm run test-types",
|
||||
"test-ci-yarn-pnp": "yarn run lint && npm run transpile && tap --ts --no-check-coverage --coverage-report=lcovonly",
|
||||
"test-types": "tsc && tsd && ts-node test/types/pino.ts",
|
||||
"test:smoke": "smoker smoke:pino && smoker smoke:browser && smoker smoke:file",
|
||||
"smoke:pino": "node ./pino.js",
|
||||
"smoke:browser": "node ./browser.js",
|
||||
"smoke:file": "node ./file.js",
|
||||
"transpile": "node ./test/fixtures/ts/transpile.cjs",
|
||||
"cov-ui": "tap --ts --coverage-report=html",
|
||||
"bench": "node benchmarks/utils/runbench all",
|
||||
"bench-basic": "node benchmarks/utils/runbench basic",
|
||||
"bench-object": "node benchmarks/utils/runbench object",
|
||||
"bench-deep-object": "node benchmarks/utils/runbench deep-object",
|
||||
"bench-multi-arg": "node benchmarks/utils/runbench multi-arg",
|
||||
"bench-longs-tring": "node benchmarks/utils/runbench long-string",
|
||||
"bench-child": "node benchmarks/utils/runbench child",
|
||||
"bench-child-child": "node benchmarks/utils/runbench child-child",
|
||||
"bench-child-creation": "node benchmarks/utils/runbench child-creation",
|
||||
"bench-formatters": "node benchmarks/utils/runbench formatters",
|
||||
"update-bench-doc": "node benchmarks/utils/generate-benchmark-doc > docs/benchmarks.md"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "./bin.js"
|
||||
},
|
||||
"precommit": "test",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/pinojs/pino.git"
|
||||
},
|
||||
"keywords": [
|
||||
"fast",
|
||||
"logger",
|
||||
"stream",
|
||||
"json"
|
||||
],
|
||||
"author": "Matteo Collina <hello@matteocollina.com>",
|
||||
"contributors": [
|
||||
"David Mark Clements <huperekchuno@googlemail.com>",
|
||||
"James Sumners <james.sumners@gmail.com>",
|
||||
"Thomas Watson Steen <w@tson.dk> (https://twitter.com/wa7son)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pinojs/pino/issues"
|
||||
},
|
||||
"homepage": "https://getpino.io",
|
||||
"devDependencies": {
|
||||
"@types/flush-write-stream": "^1.0.0",
|
||||
"@types/node": "^20.2.3",
|
||||
"@types/tap": "^15.0.6",
|
||||
"airtap": "4.0.4",
|
||||
"benchmark": "^2.1.4",
|
||||
"bole": "^5.0.5",
|
||||
"bunyan": "^1.8.14",
|
||||
"debug": "^4.3.4",
|
||||
"docsify-cli": "^4.4.4",
|
||||
"eslint": "^8.17.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "15.7.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"execa": "^5.0.0",
|
||||
"fastbench": "^1.0.1",
|
||||
"flush-write-stream": "^2.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"jest": "^29.0.3",
|
||||
"log": "^6.0.0",
|
||||
"loglevel": "^1.6.7",
|
||||
"midnight-smoker": "1.1.1",
|
||||
"pino-pretty": "^10.2.1",
|
||||
"pre-commit": "^1.2.2",
|
||||
"proxyquire": "^2.1.3",
|
||||
"pump": "^3.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"semver": "^7.3.7",
|
||||
"split2": "^4.0.0",
|
||||
"steed": "^1.1.3",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"tap": "^16.2.0",
|
||||
"tape": "^5.5.3",
|
||||
"through2": "^4.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsd": "^0.30.4",
|
||||
"typescript": "^5.1.3",
|
||||
"winston": "^3.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"fast-redact": "^3.1.1",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^1.2.0",
|
||||
"pino-std-serializers": "^6.0.0",
|
||||
"process-warning": "^3.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^3.7.0",
|
||||
"thread-stream": "^2.6.0"
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "test/types"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,863 @@
|
||||
// Project: https://github.com/pinojs/pino.git, http://getpino.io
|
||||
// Definitions by: Peter Snider <https://github.com/psnider>
|
||||
// BendingBender <https://github.com/BendingBender>
|
||||
// Christian Rackerseder <https://github.com/screendriver>
|
||||
// GP <https://github.com/paambaati>
|
||||
// Alex Ferrando <https://github.com/alferpal>
|
||||
// Oleksandr Sidko <https://github.com/mortiy>
|
||||
// Harris Lummis <https://github.com/lummish>
|
||||
// Raoul Jaeckel <https://github.com/raoulus>
|
||||
// Cory Donkin <https://github.com/Cooryd>
|
||||
// Adam Vigneaux <https://github.com/AdamVig>
|
||||
// Austin Beer <https://github.com/austin-beer>
|
||||
// Michel Nemnom <https://github.com/Pegase745>
|
||||
// Igor Savin <https://github.com/kibertoad>
|
||||
// James Bromwell <https://github.com/thw0rted>
|
||||
// TypeScript Version: 4.4
|
||||
|
||||
import type { EventEmitter } from "events";
|
||||
import * as pinoStdSerializers from "pino-std-serializers";
|
||||
import type { SonicBoom, SonicBoomOpts } from "sonic-boom";
|
||||
import type { WorkerOptions } from "worker_threads";
|
||||
|
||||
|
||||
|
||||
//// Non-exported types and interfaces
|
||||
|
||||
// ToDo https://github.com/pinojs/thread-stream/issues/24
|
||||
type ThreadStream = any
|
||||
|
||||
type TimeFn = () => string;
|
||||
type MixinFn = (mergeObject: object, level: number) => object;
|
||||
type MixinMergeStrategyFn = (mergeObject: object, mixinObject: object) => object;
|
||||
|
||||
type CustomLevelLogger<CustomLevels extends string> = { [level in CustomLevels]: LogFn }
|
||||
|
||||
/**
|
||||
* A synchronous callback that will run on each creation of a new child.
|
||||
* @param child: The newly created child logger instance.
|
||||
*/
|
||||
type OnChildCallback<CustomLevels extends string = never> = (child: pino.Logger<CustomLevels>) => void
|
||||
|
||||
export interface redactOptions {
|
||||
paths: string[];
|
||||
censor?: string | ((value: any, path: string[]) => any);
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
export interface LoggerExtras<CustomLevels extends string = never> extends EventEmitter {
|
||||
/**
|
||||
* Exposes the Pino package version. Also available on the exported pino function.
|
||||
*/
|
||||
readonly version: string;
|
||||
|
||||
levels: pino.LevelMapping;
|
||||
|
||||
/**
|
||||
* Outputs the level as a string instead of integer.
|
||||
*/
|
||||
useLevelLabels: boolean;
|
||||
/**
|
||||
* Define additional logging levels.
|
||||
*/
|
||||
customLevels: { [level in CustomLevels]: number };
|
||||
/**
|
||||
* Use only defined `customLevels` and omit Pino's levels.
|
||||
*/
|
||||
useOnlyCustomLevels: boolean;
|
||||
/**
|
||||
* Returns the integer value for the logger instance's logging level.
|
||||
*/
|
||||
levelVal: number;
|
||||
|
||||
/**
|
||||
* Creates a child logger, setting all key-value pairs in `bindings` as properties in the log lines. All serializers will be applied to the given pair.
|
||||
* Child loggers use the same output stream as the parent and inherit the current log level of the parent at the time they are spawned.
|
||||
* From v2.x.x the log level of a child is mutable (whereas in v1.x.x it was immutable), and can be set independently of the parent.
|
||||
* If a `level` property is present in the object passed to `child` it will override the child logger level.
|
||||
*
|
||||
* @param bindings: an object of key-value pairs to include in log lines as properties.
|
||||
* @param options: an options object that will override child logger inherited options.
|
||||
* @returns a child logger instance.
|
||||
*/
|
||||
child<ChildCustomLevels extends string = never>(bindings: pino.Bindings, options?: ChildLoggerOptions<ChildCustomLevels>): pino.Logger<CustomLevels | ChildCustomLevels>;
|
||||
|
||||
/**
|
||||
* This can be used to modify the callback function on creation of a new child.
|
||||
*/
|
||||
onChild: OnChildCallback<CustomLevels>;
|
||||
|
||||
/**
|
||||
* Registers a listener function that is triggered when the level is changed.
|
||||
* Note: When browserified, this functionality will only be available if the `events` module has been required elsewhere
|
||||
* (e.g. if you're using streams in the browser). This allows for a trade-off between bundle size and functionality.
|
||||
*
|
||||
* @param event: only ever fires the `'level-change'` event
|
||||
* @param listener: The listener is passed four arguments: `levelLabel`, `levelValue`, `previousLevelLabel`, `previousLevelValue`.
|
||||
*/
|
||||
on(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
addListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
once(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
prependListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
prependOnceListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
removeListener(event: "level-change", listener: pino.LevelChangeEventListener<CustomLevels>): this;
|
||||
|
||||
/**
|
||||
* A utility method for determining if a given log level will write to the destination.
|
||||
*/
|
||||
isLevelEnabled(level: pino.LevelWithSilentOrString): boolean;
|
||||
|
||||
/**
|
||||
* Returns an object containing all the current bindings, cloned from the ones passed in via logger.child().
|
||||
*/
|
||||
bindings(): pino.Bindings;
|
||||
|
||||
/**
|
||||
* Adds to the bindings of this logger instance.
|
||||
* Note: Does not overwrite bindings. Can potentially result in duplicate keys in log lines.
|
||||
*
|
||||
* @param bindings: an object of key-value pairs to include in log lines as properties.
|
||||
*/
|
||||
setBindings(bindings: pino.Bindings): void;
|
||||
|
||||
/**
|
||||
* Flushes the content of the buffer when using pino.destination({ sync: false }).
|
||||
* call the callback when finished
|
||||
*/
|
||||
flush(cb?: (err?: Error) => void): void;
|
||||
}
|
||||
|
||||
|
||||
declare namespace pino {
|
||||
//// Exported types and interfaces
|
||||
|
||||
interface BaseLogger {
|
||||
/**
|
||||
* Set this property to the desired logging level. In order of priority, available levels are:
|
||||
*
|
||||
* - 'fatal'
|
||||
* - 'error'
|
||||
* - 'warn'
|
||||
* - 'info'
|
||||
* - 'debug'
|
||||
* - 'trace'
|
||||
*
|
||||
* The logging level is a __minimum__ level. For instance if `logger.level` is `'info'` then all `'fatal'`, `'error'`, `'warn'`,
|
||||
* and `'info'` logs will be enabled.
|
||||
*
|
||||
* You can pass `'silent'` to disable logging.
|
||||
*/
|
||||
level: pino.LevelWithSilentOrString;
|
||||
|
||||
/**
|
||||
* Log at `'fatal'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
fatal: pino.LogFn;
|
||||
/**
|
||||
* Log at `'error'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
error: pino.LogFn;
|
||||
/**
|
||||
* Log at `'warn'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
warn: pino.LogFn;
|
||||
/**
|
||||
* Log at `'info'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
info: pino.LogFn;
|
||||
/**
|
||||
* Log at `'debug'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
debug: pino.LogFn;
|
||||
/**
|
||||
* Log at `'trace'` level the given msg. If the first argument is an object, all its properties will be included in the JSON line.
|
||||
* If more args follows `msg`, these will be used to format `msg` using `util.format`.
|
||||
*
|
||||
* @typeParam T: the interface of the object being serialized. Default is object.
|
||||
* @param obj: object to be serialized
|
||||
* @param msg: the log message to write
|
||||
* @param ...args: format string values when `msg` is a format string
|
||||
*/
|
||||
trace: pino.LogFn;
|
||||
/**
|
||||
* Noop function.
|
||||
*/
|
||||
silent: pino.LogFn;
|
||||
}
|
||||
|
||||
type Bindings = Record<string, any>;
|
||||
|
||||
type Level = "fatal" | "error" | "warn" | "info" | "debug" | "trace";
|
||||
type LevelOrString = Level | (string & {});
|
||||
type LevelWithSilent = pino.Level | "silent";
|
||||
type LevelWithSilentOrString = LevelWithSilent | (string & {});
|
||||
|
||||
type SerializerFn = (value: any) => any;
|
||||
type WriteFn = (o: object) => void;
|
||||
|
||||
type LevelChangeEventListener<CustomLevels extends string = never> = (
|
||||
lvl: LevelWithSilentOrString,
|
||||
val: number,
|
||||
prevLvl: LevelWithSilentOrString,
|
||||
prevVal: number,
|
||||
logger: Logger<CustomLevels>
|
||||
) => void;
|
||||
|
||||
type LogDescriptor = Record<string, any>;
|
||||
|
||||
type Logger<CustomLevels extends string = never> = BaseLogger & LoggerExtras<CustomLevels> & CustomLevelLogger<CustomLevels>;
|
||||
|
||||
type SerializedError = pinoStdSerializers.SerializedError;
|
||||
type SerializedResponse = pinoStdSerializers.SerializedResponse;
|
||||
type SerializedRequest = pinoStdSerializers.SerializedRequest;
|
||||
|
||||
|
||||
interface TransportTargetOptions<TransportOptions = Record<string, any>> {
|
||||
target: string
|
||||
options?: TransportOptions
|
||||
level?: LevelWithSilentOrString
|
||||
}
|
||||
|
||||
interface TransportBaseOptions<TransportOptions = Record<string, any>> {
|
||||
options?: TransportOptions
|
||||
worker?: WorkerOptions & { autoEnd?: boolean}
|
||||
}
|
||||
|
||||
interface TransportSingleOptions<TransportOptions = Record<string, any>> extends TransportBaseOptions<TransportOptions>{
|
||||
target: string
|
||||
}
|
||||
|
||||
interface TransportPipelineOptions<TransportOptions = Record<string, any>> extends TransportBaseOptions<TransportOptions>{
|
||||
pipeline: TransportSingleOptions<TransportOptions>[]
|
||||
}
|
||||
|
||||
interface TransportMultiOptions<TransportOptions = Record<string, any>> extends TransportBaseOptions<TransportOptions>{
|
||||
targets: readonly TransportTargetOptions<TransportOptions>[],
|
||||
levels?: Record<string, number>
|
||||
dedupe?: boolean
|
||||
}
|
||||
|
||||
interface MultiStreamOptions {
|
||||
levels?: Record<string, number>
|
||||
dedupe?: boolean
|
||||
}
|
||||
|
||||
interface DestinationStream {
|
||||
write(msg: string): void;
|
||||
}
|
||||
|
||||
interface DestinationStreamHasMetadata {
|
||||
[symbols.needsMetadataGsym]: true;
|
||||
lastLevel: number;
|
||||
lastTime: string;
|
||||
lastMsg: string;
|
||||
lastObj: object;
|
||||
lastLogger: pino.Logger;
|
||||
}
|
||||
|
||||
type DestinationStreamWithMetadata = DestinationStream & ({ [symbols.needsMetadataGsym]?: false } | DestinationStreamHasMetadata);
|
||||
|
||||
interface StreamEntry<TLevel = Level> {
|
||||
stream: DestinationStream
|
||||
level?: TLevel
|
||||
}
|
||||
|
||||
interface MultiStreamRes<TOriginLevel = Level> {
|
||||
write: (data: any) => void,
|
||||
add: <TLevel = Level>(dest: StreamEntry<TLevel> | DestinationStream) => MultiStreamRes<TOriginLevel & TLevel>,
|
||||
flushSync: () => void,
|
||||
minLevel: number,
|
||||
streams: StreamEntry<TOriginLevel>[],
|
||||
clone<TLevel = Level>(level: TLevel): MultiStreamRes<TLevel>,
|
||||
}
|
||||
|
||||
interface LevelMapping {
|
||||
/**
|
||||
* Returns the mappings of level names to their respective internal number representation.
|
||||
*/
|
||||
values: { [level: string]: number };
|
||||
/**
|
||||
* Returns the mappings of level internal level numbers to their string representations.
|
||||
*/
|
||||
labels: { [level: number]: string };
|
||||
}
|
||||
|
||||
interface LogFn {
|
||||
// TODO: why is this different from `obj: object` or `obj: any`?
|
||||
/* tslint:disable:no-unnecessary-generics */
|
||||
<T extends object>(obj: T, msg?: string, ...args: any[]): void;
|
||||
(obj: unknown, msg?: string, ...args: any[]): void;
|
||||
(msg: string, ...args: any[]): void;
|
||||
}
|
||||
|
||||
interface LoggerOptions<CustomLevels extends string = never> {
|
||||
transport?: TransportSingleOptions | TransportMultiOptions | TransportPipelineOptions
|
||||
/**
|
||||
* Avoid error causes by circular references in the object tree. Default: `true`.
|
||||
*/
|
||||
safe?: boolean;
|
||||
/**
|
||||
* The name of the logger. Default: `undefined`.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* an object containing functions for custom serialization of objects.
|
||||
* These functions should return an JSONifiable object and they should never throw. When logging an object,
|
||||
* each top-level property matching the exact key of a serializer will be serialized using the defined serializer.
|
||||
*/
|
||||
serializers?: { [key: string]: SerializerFn };
|
||||
/**
|
||||
* Enables or disables the inclusion of a timestamp in the log message. If a function is supplied, it must
|
||||
* synchronously return a JSON string representation of the time. If set to `false`, no timestamp will be included in the output.
|
||||
* See stdTimeFunctions for a set of available functions for passing in as a value for this option.
|
||||
* Caution: any sort of formatted time will significantly slow down Pino's performance.
|
||||
*/
|
||||
timestamp?: TimeFn | boolean;
|
||||
/**
|
||||
* One of the supported levels or `silent` to disable logging. Any other value defines a custom level and
|
||||
* requires supplying a level value via `levelVal`. Default: 'info'.
|
||||
*/
|
||||
level?: LevelWithSilentOrString;
|
||||
|
||||
/**
|
||||
* Use this option to define additional logging levels.
|
||||
* The keys of the object correspond the namespace of the log level, and the values should be the numerical value of the level.
|
||||
*/
|
||||
customLevels?: { [level in CustomLevels]: number };
|
||||
/**
|
||||
* Use this option to define custom comparison of log levels.
|
||||
* Useful to compare custom log levels or non-standard level values.
|
||||
* Default: "ASC"
|
||||
*/
|
||||
levelComparison?: "ASC" | "DESC" | ((current: number, expected: number) => boolean);
|
||||
/**
|
||||
* Use this option to only use defined `customLevels` and omit Pino's levels.
|
||||
* Logger's default `level` must be changed to a value in `customLevels` in order to use `useOnlyCustomLevels`
|
||||
* Warning: this option may not be supported by downstream transports.
|
||||
*/
|
||||
useOnlyCustomLevels?: boolean;
|
||||
|
||||
/**
|
||||
* If provided, the `mixin` function is called each time one of the active logging methods
|
||||
* is called. The function must synchronously return an object. The properties of the
|
||||
* returned object will be added to the logged JSON.
|
||||
*/
|
||||
mixin?: MixinFn;
|
||||
|
||||
/**
|
||||
* If provided, the `mixinMergeStrategy` function is called each time one of the active
|
||||
* logging methods is called. The first parameter is the value `mergeObject` or an empty object,
|
||||
* the second parameter is the value resulting from `mixin()` or an empty object.
|
||||
* The function must synchronously return an object.
|
||||
*/
|
||||
mixinMergeStrategy?: MixinMergeStrategyFn
|
||||
|
||||
/**
|
||||
* As an array, the redact option specifies paths that should have their values redacted from any log output.
|
||||
*
|
||||
* Each path must be a string using a syntax which corresponds to JavaScript dot and bracket notation.
|
||||
*
|
||||
* If an object is supplied, three options can be specified:
|
||||
*
|
||||
* paths (String[]): Required. An array of paths
|
||||
* censor (String): Optional. A value to overwrite key which are to be redacted. Default: '[Redacted]'
|
||||
* remove (Boolean): Optional. Instead of censoring the value, remove both the key and the value. Default: false
|
||||
*/
|
||||
redact?: string[] | redactOptions;
|
||||
|
||||
/**
|
||||
* When defining a custom log level via level, set to an integer value to define the new level. Default: `undefined`.
|
||||
*/
|
||||
levelVal?: number;
|
||||
/**
|
||||
* The string key for the 'message' in the JSON object. Default: "msg".
|
||||
*/
|
||||
messageKey?: string;
|
||||
/**
|
||||
* The string key for the 'error' in the JSON object. Default: "err".
|
||||
*/
|
||||
errorKey?: string;
|
||||
/**
|
||||
* The string key to place any logged object under.
|
||||
*/
|
||||
nestedKey?: string;
|
||||
/**
|
||||
* Enables logging. Default: `true`.
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* Browser only, see http://getpino.io/#/docs/browser.
|
||||
*/
|
||||
browser?: {
|
||||
/**
|
||||
* The `asObject` option will create a pino-like log object instead of passing all arguments to a console
|
||||
* method. When `write` is set, `asObject` will always be true.
|
||||
*
|
||||
* @example
|
||||
* pino.info('hi') // creates and logs {msg: 'hi', level: 30, time: <ts>}
|
||||
*/
|
||||
asObject?: boolean;
|
||||
/**
|
||||
* Instead of passing log messages to `console.log` they can be passed to a supplied function. If `write` is
|
||||
* set to a single function, all logging objects are passed to this function. If `write` is an object, it
|
||||
* can have methods that correspond to the levels. When a message is logged at a given level, the
|
||||
* corresponding method is called. If a method isn't present, the logging falls back to using the `console`.
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* browser: {
|
||||
* write: (o) => {
|
||||
* // do something with o
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* browser: {
|
||||
* write: {
|
||||
* info: function (o) {
|
||||
* //process info log object
|
||||
* },
|
||||
* error: function (o) {
|
||||
* //process error log object
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
write?:
|
||||
| WriteFn
|
||||
| ({
|
||||
fatal?: WriteFn;
|
||||
error?: WriteFn;
|
||||
warn?: WriteFn;
|
||||
info?: WriteFn;
|
||||
debug?: WriteFn;
|
||||
trace?: WriteFn;
|
||||
} & { [logLevel: string]: WriteFn });
|
||||
|
||||
/**
|
||||
* The serializers provided to `pino` are ignored by default in the browser, including the standard
|
||||
* serializers provided with Pino. Since the default destination for log messages is the console, values
|
||||
* such as `Error` objects are enhanced for inspection, which they otherwise wouldn't be if the Error
|
||||
* serializer was enabled. We can turn all serializers on or we can selectively enable them via an array.
|
||||
*
|
||||
* When `serialize` is `true` the standard error serializer is also enabled (see
|
||||
* {@link https://github.com/pinojs/pino/blob/master/docs/api.md#pino-stdserializers}). This is a global
|
||||
* serializer which will apply to any `Error` objects passed to the logger methods.
|
||||
*
|
||||
* If `serialize` is an array the standard error serializer is also automatically enabled, it can be
|
||||
* explicitly disabled by including a string in the serialize array: `!stdSerializers.err` (see example).
|
||||
*
|
||||
* The `serialize` array also applies to any child logger serializers (see
|
||||
* {@link https://github.com/pinojs/pino/blob/master/docs/api.md#bindingsserializers-object} for how to
|
||||
* set child-bound serializers).
|
||||
*
|
||||
* Unlike server pino the serializers apply to every object passed to the logger method, if the `asObject`
|
||||
* option is `true`, this results in the serializers applying to the first object (as in server pino).
|
||||
*
|
||||
* For more info on serializers see
|
||||
* {@link https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object}.
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* browser: {
|
||||
* serialize: true
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* serializers: {
|
||||
* custom: myCustomSerializer,
|
||||
* another: anotherSerializer
|
||||
* },
|
||||
* browser: {
|
||||
* serialize: ['custom']
|
||||
* }
|
||||
* })
|
||||
* // following will apply myCustomSerializer to the custom property,
|
||||
* // but will not apply anotherSerializer to another key
|
||||
* pino.info({custom: 'a', another: 'b'})
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* serializers: {
|
||||
* custom: myCustomSerializer,
|
||||
* another: anotherSerializer
|
||||
* },
|
||||
* browser: {
|
||||
* serialize: ['!stdSerializers.err', 'custom'] //will not serialize Errors, will serialize `custom` keys
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
serialize?: boolean | string[];
|
||||
|
||||
/**
|
||||
* Options for transmission of logs.
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({
|
||||
* browser: {
|
||||
* transmit: {
|
||||
* level: 'warn',
|
||||
* send: function (level, logEvent) {
|
||||
* if (level === 'warn') {
|
||||
* // maybe send the logEvent to a separate endpoint
|
||||
* // or maybe analyse the messages further before sending
|
||||
* }
|
||||
* // we could also use the `logEvent.level.value` property to determine
|
||||
* // numerical value
|
||||
* if (logEvent.level.value >= 50) { // covers error and fatal
|
||||
*
|
||||
* // send the logEvent somewhere
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
transmit?: {
|
||||
/**
|
||||
* Specifies the minimum level (inclusive) of when the `send` function should be called, if not supplied
|
||||
* the `send` function will be called based on the main logging `level` (set via `options.level`,
|
||||
* defaulting to `info`).
|
||||
*/
|
||||
level?: LevelOrString;
|
||||
/**
|
||||
* Remotely record log messages.
|
||||
*
|
||||
* @description Called after writing the log message.
|
||||
*/
|
||||
send: (level: Level, logEvent: LogEvent) => void;
|
||||
};
|
||||
/**
|
||||
* The disabled option will disable logging in browser if set to true, by default it is set to false.
|
||||
*
|
||||
* @example
|
||||
* const pino = require('pino')({browser: {disabled: true}})
|
||||
*/
|
||||
disabled?: boolean;
|
||||
};
|
||||
/**
|
||||
* key-value object added as child logger to each log line. If set to null the base child logger is not added
|
||||
*/
|
||||
base?: { [key: string]: any } | null;
|
||||
|
||||
/**
|
||||
* An object containing functions for formatting the shape of the log lines.
|
||||
* These functions should return a JSONifiable object and should never throw.
|
||||
* These functions allow for full customization of the resulting log lines.
|
||||
* For example, they can be used to change the level key name or to enrich the default metadata.
|
||||
*/
|
||||
formatters?: {
|
||||
/**
|
||||
* Changes the shape of the log level.
|
||||
* The default shape is { level: number }.
|
||||
* The function takes two arguments, the label of the level (e.g. 'info') and the numeric value (e.g. 30).
|
||||
*/
|
||||
level?: (label: string, number: number) => object;
|
||||
/**
|
||||
* Changes the shape of the bindings.
|
||||
* The default shape is { pid, hostname }.
|
||||
* The function takes a single argument, the bindings object.
|
||||
* It will be called every time a child logger is created.
|
||||
*/
|
||||
bindings?: (bindings: Bindings) => object;
|
||||
/**
|
||||
* Changes the shape of the log object.
|
||||
* This function will be called every time one of the log methods (such as .info) is called.
|
||||
* All arguments passed to the log method, except the message, will be pass to this function.
|
||||
* By default it does not change the shape of the log object.
|
||||
*/
|
||||
log?: (object: Record<string, unknown>) => Record<string, unknown>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A string that would be prefixed to every message (and child message)
|
||||
*/
|
||||
msgPrefix?: string
|
||||
|
||||
/**
|
||||
* An object mapping to hook functions. Hook functions allow for customizing internal logger operations.
|
||||
* Hook functions must be synchronous functions.
|
||||
*/
|
||||
hooks?: {
|
||||
/**
|
||||
* Allows for manipulating the parameters passed to logger methods. The signature for this hook is
|
||||
* logMethod (args, method, level) {}, where args is an array of the arguments that were passed to the
|
||||
* log method and method is the log method itself, and level is the log level. This hook must invoke the method function by
|
||||
* using apply, like so: method.apply(this, newArgumentsArray).
|
||||
*/
|
||||
logMethod?: (this: Logger, args: Parameters<LogFn>, method: LogFn, level: number) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stringification limit at a specific nesting depth when logging circular object. Default: `5`.
|
||||
*/
|
||||
depthLimit?: number
|
||||
|
||||
/**
|
||||
* Stringification limit of properties/elements when logging a specific object/array with circular references. Default: `100`.
|
||||
*/
|
||||
edgeLimit?: number
|
||||
|
||||
/**
|
||||
* Optional child creation callback.
|
||||
*/
|
||||
onChild?: OnChildCallback<CustomLevels>;
|
||||
|
||||
/**
|
||||
* logs newline delimited JSON with `\r\n` instead of `\n`. Default: `false`.
|
||||
*/
|
||||
crlf?: boolean;
|
||||
}
|
||||
|
||||
interface ChildLoggerOptions<CustomLevels extends string = never> {
|
||||
level?: LevelOrString;
|
||||
serializers?: { [key: string]: SerializerFn };
|
||||
customLevels?: { [level in CustomLevels]: number };
|
||||
formatters?: {
|
||||
level?: (label: string, number: number) => object;
|
||||
bindings?: (bindings: Bindings) => object;
|
||||
log?: (object: object) => object;
|
||||
};
|
||||
redact?: string[] | redactOptions;
|
||||
msgPrefix?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A data structure representing a log message, it represents the arguments passed to a logger statement, the level
|
||||
* at which they were logged and the hierarchy of child bindings.
|
||||
*
|
||||
* @description By default serializers are not applied to log output in the browser, but they will always be applied
|
||||
* to `messages` and `bindings` in the `logEvent` object. This allows us to ensure a consistent format for all
|
||||
* values between server and client.
|
||||
*/
|
||||
interface LogEvent {
|
||||
/**
|
||||
* Unix epoch timestamp in milliseconds, the time is taken from the moment the logger method is called.
|
||||
*/
|
||||
ts: number;
|
||||
/**
|
||||
* All arguments passed to logger method, (for instance `logger.info('a', 'b', 'c')` would result in `messages`
|
||||
* array `['a', 'b', 'c']`).
|
||||
*/
|
||||
messages: any[];
|
||||
/**
|
||||
* Represents each child logger (if any), and the relevant bindings.
|
||||
*
|
||||
* @description For instance, given `logger.child({a: 1}).child({b: 2}).info({c: 3})`, the bindings array would
|
||||
* hold `[{a: 1}, {b: 2}]` and the `messages` array would be `[{c: 3}]`. The `bindings` are ordered according to
|
||||
* their position in the child logger hierarchy, with the lowest index being the top of the hierarchy.
|
||||
*/
|
||||
bindings: Bindings[];
|
||||
/**
|
||||
* Holds the `label` (for instance `info`), and the corresponding numerical `value` (for instance `30`).
|
||||
* This could be important in cases where client side level values and labels differ from server side.
|
||||
*/
|
||||
level: {
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
//// Top level variable (const) exports
|
||||
|
||||
/**
|
||||
* Provides functions for serializing objects common to many projects.
|
||||
*/
|
||||
export const stdSerializers: typeof pinoStdSerializers;
|
||||
|
||||
/**
|
||||
* Holds the current log format version (as output in the v property of each log record).
|
||||
*/
|
||||
export const levels: LevelMapping;
|
||||
export const symbols: {
|
||||
readonly setLevelSym: unique symbol;
|
||||
readonly getLevelSym: unique symbol;
|
||||
readonly levelValSym: unique symbol;
|
||||
readonly useLevelLabelsSym: unique symbol;
|
||||
readonly mixinSym: unique symbol;
|
||||
readonly lsCacheSym: unique symbol;
|
||||
readonly chindingsSym: unique symbol;
|
||||
readonly parsedChindingsSym: unique symbol;
|
||||
readonly asJsonSym: unique symbol;
|
||||
readonly writeSym: unique symbol;
|
||||
readonly serializersSym: unique symbol;
|
||||
readonly redactFmtSym: unique symbol;
|
||||
readonly timeSym: unique symbol;
|
||||
readonly timeSliceIndexSym: unique symbol;
|
||||
readonly streamSym: unique symbol;
|
||||
readonly stringifySym: unique symbol;
|
||||
readonly stringifySafeSym: unique symbol;
|
||||
readonly stringifiersSym: unique symbol;
|
||||
readonly endSym: unique symbol;
|
||||
readonly formatOptsSym: unique symbol;
|
||||
readonly messageKeySym: unique symbol;
|
||||
readonly errorKeySym: unique symbol;
|
||||
readonly nestedKeySym: unique symbol;
|
||||
readonly wildcardFirstSym: unique symbol;
|
||||
readonly needsMetadataGsym: unique symbol;
|
||||
readonly useOnlyCustomLevelsSym: unique symbol;
|
||||
readonly formattersSym: unique symbol;
|
||||
readonly hooksSym: unique symbol;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exposes the Pino package version. Also available on the logger instance.
|
||||
*/
|
||||
export const version: string;
|
||||
|
||||
/**
|
||||
* Provides functions for generating the timestamp property in the log output. You can set the `timestamp` option during
|
||||
* initialization to one of these functions to adjust the output format. Alternatively, you can specify your own time function.
|
||||
* A time function must synchronously return a string that would be a valid component of a JSON string. For example,
|
||||
* the default function returns a string like `,"time":1493426328206`.
|
||||
*/
|
||||
export const stdTimeFunctions: {
|
||||
/**
|
||||
* The default time function for Pino. Returns a string like `,"time":1493426328206`.
|
||||
*/
|
||||
epochTime: TimeFn;
|
||||
/*
|
||||
* Returns the seconds since Unix epoch
|
||||
*/
|
||||
unixTime: TimeFn;
|
||||
/**
|
||||
* Returns an empty string. This function is used when the `timestamp` option is set to `false`.
|
||||
*/
|
||||
nullTime: TimeFn;
|
||||
/*
|
||||
* Returns ISO 8601-formatted time in UTC
|
||||
*/
|
||||
isoTime: TimeFn;
|
||||
};
|
||||
|
||||
//// Exported functions
|
||||
|
||||
/**
|
||||
* Create a Pino Destination instance: a stream-like object with significantly more throughput (over 30%) than a standard Node.js stream.
|
||||
* @param [dest]: The `destination` parameter, can be a file descriptor, a file path, or an object with `dest` property pointing to a fd or path.
|
||||
* An ordinary Node.js `stream` file descriptor can be passed as the destination (such as the result of `fs.createWriteStream`)
|
||||
* but for peak log writing performance, it is strongly recommended to use `pino.destination` to create the destination stream.
|
||||
* @returns A Sonic-Boom stream to be used as destination for the pino function
|
||||
*/
|
||||
export function destination(
|
||||
dest?: number | object | string | DestinationStream | NodeJS.WritableStream | SonicBoomOpts,
|
||||
): SonicBoom;
|
||||
|
||||
export function transport<TransportOptions = Record<string, any>>(
|
||||
options: TransportSingleOptions<TransportOptions> | TransportMultiOptions<TransportOptions> | TransportPipelineOptions<TransportOptions>
|
||||
): ThreadStream
|
||||
|
||||
export function multistream<TLevel = Level>(
|
||||
streamsArray: (DestinationStream | StreamEntry<TLevel>)[] | DestinationStream | StreamEntry<TLevel>,
|
||||
opts?: MultiStreamOptions
|
||||
): MultiStreamRes<TLevel>
|
||||
}
|
||||
|
||||
//// Callable default export
|
||||
|
||||
/**
|
||||
* @param [optionsOrStream]: an options object or a writable stream where the logs will be written. It can also receive some log-line metadata, if the
|
||||
* relative protocol is enabled. Default: process.stdout
|
||||
* @returns a new logger instance.
|
||||
*/
|
||||
declare function pino<CustomLevels extends string = never>(optionsOrStream?: LoggerOptions<CustomLevels> | DestinationStream): Logger<CustomLevels>;
|
||||
|
||||
/**
|
||||
* @param [options]: an options object
|
||||
* @param [stream]: a writable stream where the logs will be written. It can also receive some log-line metadata, if the
|
||||
* relative protocol is enabled. Default: process.stdout
|
||||
* @returns a new logger instance.
|
||||
*/
|
||||
declare function pino<CustomLevels extends string = never>(options: LoggerOptions<CustomLevels>, stream?: DestinationStream | undefined): Logger<CustomLevels>;
|
||||
|
||||
|
||||
// Pass through all the top-level exports, allows `import {version} from "pino"`
|
||||
// Constants and functions
|
||||
export const destination: typeof pino.destination;
|
||||
export const transport: typeof pino.transport;
|
||||
export const multistream: typeof pino.multistream;
|
||||
export const levels: typeof pino.levels;
|
||||
export const stdSerializers: typeof pino.stdSerializers;
|
||||
export const stdTimeFunctions: typeof pino.stdTimeFunctions;
|
||||
export const symbols: typeof pino.symbols;
|
||||
export const version: typeof pino.version;
|
||||
|
||||
// Types
|
||||
export type Bindings = pino.Bindings;
|
||||
export type DestinationStreamWithMetadata = pino.DestinationStreamWithMetadata;
|
||||
export type Level = pino.Level;
|
||||
export type LevelOrString = pino.LevelOrString;
|
||||
export type LevelWithSilent = pino.LevelWithSilent;
|
||||
export type LevelWithSilentOrString = pino.LevelWithSilentOrString;
|
||||
export type LevelChangeEventListener<CustomLevels extends string> = pino.LevelChangeEventListener<CustomLevels>;
|
||||
export type LogDescriptor = pino.LogDescriptor;
|
||||
export type Logger<CustomLevels extends string = never> = pino.Logger<CustomLevels>;
|
||||
export type SerializedError = pino.SerializedError;
|
||||
export type SerializerFn = pino.SerializerFn;
|
||||
export type SerializedRequest = pino.SerializedRequest;
|
||||
export type SerializedResponse = pino.SerializedResponse;
|
||||
export type WriteFn = pino.WriteFn;
|
||||
|
||||
// Interfaces
|
||||
export interface BaseLogger extends pino.BaseLogger {}
|
||||
export interface ChildLoggerOptions<CustomLevels extends string = never> extends pino.ChildLoggerOptions<CustomLevels> {}
|
||||
export interface DestinationStream extends pino.DestinationStream {}
|
||||
export interface LevelMapping extends pino.LevelMapping {}
|
||||
export interface LogEvent extends pino.LogEvent {}
|
||||
export interface LogFn extends pino.LogFn {}
|
||||
export interface LoggerOptions<CustomLevels extends string = never> extends pino.LoggerOptions<CustomLevels> {}
|
||||
export interface MultiStreamOptions extends pino.MultiStreamOptions {}
|
||||
export interface MultiStreamRes<TLevel = Level> extends pino.MultiStreamRes<TLevel> {}
|
||||
export interface StreamEntry<TLevel = Level> extends pino.StreamEntry<TLevel> {}
|
||||
export interface TransportBaseOptions extends pino.TransportBaseOptions {}
|
||||
export interface TransportMultiOptions extends pino.TransportMultiOptions {}
|
||||
export interface TransportPipelineOptions extends pino.TransportPipelineOptions {}
|
||||
export interface TransportSingleOptions extends pino.TransportSingleOptions {}
|
||||
export interface TransportTargetOptions extends pino.TransportTargetOptions {}
|
||||
|
||||
// Bundle all top level exports into a namespace, then export namespace both
|
||||
// as default (`import pino from "pino"`) and named variable
|
||||
// (`import {pino} from "pino"`).
|
||||
export { pino as default, pino };
|
||||
// Export just the type side of the namespace as "P", allows
|
||||
// `import {P} from "pino"; const log: P.Logger;`.
|
||||
// (Legacy support for early 7.x releases, remove in 8.x.)
|
||||
export type { pino as P };
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
'use strict'
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
const os = require('os')
|
||||
const stdSerializers = require('pino-std-serializers')
|
||||
const caller = require('./lib/caller')
|
||||
const redaction = require('./lib/redaction')
|
||||
const time = require('./lib/time')
|
||||
const proto = require('./lib/proto')
|
||||
const symbols = require('./lib/symbols')
|
||||
const { configure } = require('safe-stable-stringify')
|
||||
const { assertDefaultLevelFound, mappings, genLsCache, genLevelComparison, assertLevelComparison } = require('./lib/levels')
|
||||
const { DEFAULT_LEVELS, SORTING_ORDER } = require('./lib/constants')
|
||||
const {
|
||||
createArgsNormalizer,
|
||||
asChindings,
|
||||
buildSafeSonicBoom,
|
||||
buildFormatters,
|
||||
stringify,
|
||||
normalizeDestFileDescriptor,
|
||||
noop
|
||||
} = require('./lib/tools')
|
||||
const { version } = require('./lib/meta')
|
||||
const {
|
||||
chindingsSym,
|
||||
redactFmtSym,
|
||||
serializersSym,
|
||||
timeSym,
|
||||
timeSliceIndexSym,
|
||||
streamSym,
|
||||
stringifySym,
|
||||
stringifySafeSym,
|
||||
stringifiersSym,
|
||||
setLevelSym,
|
||||
endSym,
|
||||
formatOptsSym,
|
||||
messageKeySym,
|
||||
errorKeySym,
|
||||
nestedKeySym,
|
||||
mixinSym,
|
||||
levelCompSym,
|
||||
useOnlyCustomLevelsSym,
|
||||
formattersSym,
|
||||
hooksSym,
|
||||
nestedKeyStrSym,
|
||||
mixinMergeStrategySym,
|
||||
msgPrefixSym
|
||||
} = symbols
|
||||
const { epochTime, nullTime } = time
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
const defaultErrorSerializer = stdSerializers.err
|
||||
const defaultOptions = {
|
||||
level: 'info',
|
||||
levelComparison: SORTING_ORDER.ASC,
|
||||
levels: DEFAULT_LEVELS,
|
||||
messageKey: 'msg',
|
||||
errorKey: 'err',
|
||||
nestedKey: null,
|
||||
enabled: true,
|
||||
base: { pid, hostname },
|
||||
serializers: Object.assign(Object.create(null), {
|
||||
err: defaultErrorSerializer
|
||||
}),
|
||||
formatters: Object.assign(Object.create(null), {
|
||||
bindings (bindings) {
|
||||
return bindings
|
||||
},
|
||||
level (label, number) {
|
||||
return { level: number }
|
||||
}
|
||||
}),
|
||||
hooks: {
|
||||
logMethod: undefined
|
||||
},
|
||||
timestamp: epochTime,
|
||||
name: undefined,
|
||||
redact: null,
|
||||
customLevels: null,
|
||||
useOnlyCustomLevels: false,
|
||||
depthLimit: 5,
|
||||
edgeLimit: 100
|
||||
}
|
||||
|
||||
const normalize = createArgsNormalizer(defaultOptions)
|
||||
|
||||
const serializers = Object.assign(Object.create(null), stdSerializers)
|
||||
|
||||
function pino (...args) {
|
||||
const instance = {}
|
||||
const { opts, stream } = normalize(instance, caller(), ...args)
|
||||
const {
|
||||
redact,
|
||||
crlf,
|
||||
serializers,
|
||||
timestamp,
|
||||
messageKey,
|
||||
errorKey,
|
||||
nestedKey,
|
||||
base,
|
||||
name,
|
||||
level,
|
||||
customLevels,
|
||||
levelComparison,
|
||||
mixin,
|
||||
mixinMergeStrategy,
|
||||
useOnlyCustomLevels,
|
||||
formatters,
|
||||
hooks,
|
||||
depthLimit,
|
||||
edgeLimit,
|
||||
onChild,
|
||||
msgPrefix
|
||||
} = opts
|
||||
|
||||
const stringifySafe = configure({
|
||||
maximumDepth: depthLimit,
|
||||
maximumBreadth: edgeLimit
|
||||
})
|
||||
|
||||
const allFormatters = buildFormatters(
|
||||
formatters.level,
|
||||
formatters.bindings,
|
||||
formatters.log
|
||||
)
|
||||
|
||||
const stringifyFn = stringify.bind({
|
||||
[stringifySafeSym]: stringifySafe
|
||||
})
|
||||
const stringifiers = redact ? redaction(redact, stringifyFn) : {}
|
||||
const formatOpts = redact
|
||||
? { stringify: stringifiers[redactFmtSym] }
|
||||
: { stringify: stringifyFn }
|
||||
const end = '}' + (crlf ? '\r\n' : '\n')
|
||||
const coreChindings = asChindings.bind(null, {
|
||||
[chindingsSym]: '',
|
||||
[serializersSym]: serializers,
|
||||
[stringifiersSym]: stringifiers,
|
||||
[stringifySym]: stringify,
|
||||
[stringifySafeSym]: stringifySafe,
|
||||
[formattersSym]: allFormatters
|
||||
})
|
||||
|
||||
let chindings = ''
|
||||
if (base !== null) {
|
||||
if (name === undefined) {
|
||||
chindings = coreChindings(base)
|
||||
} else {
|
||||
chindings = coreChindings(Object.assign({}, base, { name }))
|
||||
}
|
||||
}
|
||||
|
||||
const time = (timestamp instanceof Function)
|
||||
? timestamp
|
||||
: (timestamp ? epochTime : nullTime)
|
||||
const timeSliceIndex = time().indexOf(':') + 1
|
||||
|
||||
if (useOnlyCustomLevels && !customLevels) throw Error('customLevels is required if useOnlyCustomLevels is set true')
|
||||
if (mixin && typeof mixin !== 'function') throw Error(`Unknown mixin type "${typeof mixin}" - expected "function"`)
|
||||
if (msgPrefix && typeof msgPrefix !== 'string') throw Error(`Unknown msgPrefix type "${typeof msgPrefix}" - expected "string"`)
|
||||
|
||||
assertDefaultLevelFound(level, customLevels, useOnlyCustomLevels)
|
||||
const levels = mappings(customLevels, useOnlyCustomLevels)
|
||||
|
||||
if (typeof stream.emit === 'function') {
|
||||
stream.emit('message', { code: 'PINO_CONFIG', config: { levels, messageKey, errorKey } })
|
||||
}
|
||||
|
||||
assertLevelComparison(levelComparison)
|
||||
const levelCompFunc = genLevelComparison(levelComparison)
|
||||
|
||||
Object.assign(instance, {
|
||||
levels,
|
||||
[levelCompSym]: levelCompFunc,
|
||||
[useOnlyCustomLevelsSym]: useOnlyCustomLevels,
|
||||
[streamSym]: stream,
|
||||
[timeSym]: time,
|
||||
[timeSliceIndexSym]: timeSliceIndex,
|
||||
[stringifySym]: stringify,
|
||||
[stringifySafeSym]: stringifySafe,
|
||||
[stringifiersSym]: stringifiers,
|
||||
[endSym]: end,
|
||||
[formatOptsSym]: formatOpts,
|
||||
[messageKeySym]: messageKey,
|
||||
[errorKeySym]: errorKey,
|
||||
[nestedKeySym]: nestedKey,
|
||||
// protect against injection
|
||||
[nestedKeyStrSym]: nestedKey ? `,${JSON.stringify(nestedKey)}:{` : '',
|
||||
[serializersSym]: serializers,
|
||||
[mixinSym]: mixin,
|
||||
[mixinMergeStrategySym]: mixinMergeStrategy,
|
||||
[chindingsSym]: chindings,
|
||||
[formattersSym]: allFormatters,
|
||||
[hooksSym]: hooks,
|
||||
silent: noop,
|
||||
onChild,
|
||||
[msgPrefixSym]: msgPrefix
|
||||
})
|
||||
|
||||
Object.setPrototypeOf(instance, proto())
|
||||
|
||||
genLsCache(instance)
|
||||
|
||||
instance[setLevelSym](level)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
module.exports = pino
|
||||
|
||||
module.exports.destination = (dest = process.stdout.fd) => {
|
||||
if (typeof dest === 'object') {
|
||||
dest.dest = normalizeDestFileDescriptor(dest.dest || process.stdout.fd)
|
||||
return buildSafeSonicBoom(dest)
|
||||
} else {
|
||||
return buildSafeSonicBoom({ dest: normalizeDestFileDescriptor(dest), minLength: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.transport = require('./lib/transport')
|
||||
module.exports.multistream = require('./lib/multistream')
|
||||
|
||||
module.exports.levels = mappings()
|
||||
module.exports.stdSerializers = serializers
|
||||
module.exports.stdTimeFunctions = Object.assign({}, time)
|
||||
module.exports.symbols = symbols
|
||||
module.exports.version = version
|
||||
|
||||
// Enables default and name export with TypeScript and Babel
|
||||
module.exports.default = pino
|
||||
module.exports.pino = pino
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,874 @@
|
||||
'use strict'
|
||||
const os = require('os')
|
||||
const { readFileSync } = require('fs')
|
||||
const { test } = require('tap')
|
||||
const { sink, check, once, watchFileCreated, file } = require('./helper')
|
||||
const pino = require('../')
|
||||
const { version } = require('../package.json')
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('pino version is exposed on export', async ({ equal }) => {
|
||||
equal(pino.version, version)
|
||||
})
|
||||
|
||||
test('pino version is exposed on instance', async ({ equal }) => {
|
||||
const instance = pino()
|
||||
equal(instance.version, version)
|
||||
})
|
||||
|
||||
test('child instance exposes pino version', async ({ equal }) => {
|
||||
const child = pino().child({ foo: 'bar' })
|
||||
equal(child.version, version)
|
||||
})
|
||||
|
||||
test('bindings are exposed on every instance', async ({ same }) => {
|
||||
const instance = pino()
|
||||
same(instance.bindings(), {})
|
||||
})
|
||||
|
||||
test('bindings contain the name and the child bindings', async ({ same }) => {
|
||||
const instance = pino({ name: 'basicTest', level: 'info' }).child({ foo: 'bar' }).child({ a: 2 })
|
||||
same(instance.bindings(), { name: 'basicTest', foo: 'bar', a: 2 })
|
||||
})
|
||||
|
||||
test('set bindings on instance', async ({ same }) => {
|
||||
const instance = pino({ name: 'basicTest', level: 'info' })
|
||||
instance.setBindings({ foo: 'bar' })
|
||||
same(instance.bindings(), { name: 'basicTest', foo: 'bar' })
|
||||
})
|
||||
|
||||
test('newly set bindings overwrite old bindings', async ({ same }) => {
|
||||
const instance = pino({ name: 'basicTest', level: 'info', base: { foo: 'bar' } })
|
||||
instance.setBindings({ foo: 'baz' })
|
||||
same(instance.bindings(), { name: 'basicTest', foo: 'baz' })
|
||||
})
|
||||
|
||||
test('set bindings on child instance', async ({ same }) => {
|
||||
const child = pino({ name: 'basicTest', level: 'info' }).child({})
|
||||
child.setBindings({ foo: 'bar' })
|
||||
same(child.bindings(), { name: 'basicTest', foo: 'bar' })
|
||||
})
|
||||
|
||||
test('child should have bindings set by parent', async ({ same }) => {
|
||||
const instance = pino({ name: 'basicTest', level: 'info' })
|
||||
instance.setBindings({ foo: 'bar' })
|
||||
const child = instance.child({})
|
||||
same(child.bindings(), { name: 'basicTest', foo: 'bar' })
|
||||
})
|
||||
|
||||
test('child should not share bindings of parent set after child creation', async ({ same }) => {
|
||||
const instance = pino({ name: 'basicTest', level: 'info' })
|
||||
const child = instance.child({})
|
||||
instance.setBindings({ foo: 'bar' })
|
||||
same(instance.bindings(), { name: 'basicTest', foo: 'bar' })
|
||||
same(child.bindings(), { name: 'basicTest' })
|
||||
})
|
||||
|
||||
function levelTest (name, level) {
|
||||
test(`${name} logs as ${level}`, async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name]('hello world')
|
||||
check(equal, await once(stream, 'data'), level, 'hello world')
|
||||
})
|
||||
|
||||
test(`passing objects at level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
const obj = { hello: 'world' }
|
||||
instance[name](obj)
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
equal(result.pid, pid)
|
||||
equal(result.hostname, hostname)
|
||||
equal(result.level, level)
|
||||
equal(result.hello, 'world')
|
||||
same(Object.keys(obj), ['hello'])
|
||||
})
|
||||
|
||||
test(`passing an object and a string at level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
const obj = { hello: 'world' }
|
||||
instance[name](obj, 'a string')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg: 'a string',
|
||||
hello: 'world'
|
||||
})
|
||||
same(Object.keys(obj), ['hello'])
|
||||
})
|
||||
|
||||
test(`passing a undefined and a string at level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name](undefined, 'a string')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg: 'a string'
|
||||
})
|
||||
})
|
||||
|
||||
test(`overriding object key by string at level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name]({ hello: 'world', msg: 'object' }, 'string')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg: 'string',
|
||||
hello: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
test(`formatting logs as ${name}`, async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name]('hello %d', 42)
|
||||
const result = await once(stream, 'data')
|
||||
check(equal, result, level, 'hello 42')
|
||||
})
|
||||
|
||||
test(`formatting a symbol at level ${name}`, async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
|
||||
const sym = Symbol('foo')
|
||||
instance[name]('hello %s', sym)
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
|
||||
check(equal, result, level, 'hello Symbol(foo)')
|
||||
})
|
||||
|
||||
test(`passing error with a serializer at level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const err = new Error('myerror')
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
err: pino.stdSerializers.err
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({ err })
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test(`child logger for level ${name}`, async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
const child = instance.child({ hello: 'world' })
|
||||
child[name]('hello world')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg: 'hello world',
|
||||
hello: 'world'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
levelTest('fatal', 60)
|
||||
levelTest('error', 50)
|
||||
levelTest('warn', 40)
|
||||
levelTest('info', 30)
|
||||
levelTest('debug', 20)
|
||||
levelTest('trace', 10)
|
||||
|
||||
test('serializers can return undefined to strip field', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
test () { return undefined }
|
||||
}
|
||||
}, stream)
|
||||
|
||||
instance.info({ test: 'sensitive info' })
|
||||
const result = await once(stream, 'data')
|
||||
equal('test' in result, false)
|
||||
})
|
||||
|
||||
test('streams receive a message event with PINO_CONFIG', ({ match, end }) => {
|
||||
const stream = sink()
|
||||
stream.once('message', (message) => {
|
||||
match(message, {
|
||||
code: 'PINO_CONFIG',
|
||||
config: {
|
||||
errorKey: 'err',
|
||||
levels: {
|
||||
labels: {
|
||||
10: 'trace',
|
||||
20: 'debug',
|
||||
30: 'info',
|
||||
40: 'warn',
|
||||
50: 'error',
|
||||
60: 'fatal'
|
||||
},
|
||||
values: {
|
||||
debug: 20,
|
||||
error: 50,
|
||||
fatal: 60,
|
||||
info: 30,
|
||||
trace: 10,
|
||||
warn: 40
|
||||
}
|
||||
},
|
||||
messageKey: 'msg'
|
||||
}
|
||||
})
|
||||
end()
|
||||
})
|
||||
pino(stream)
|
||||
})
|
||||
|
||||
test('does not explode with a circular ref', async ({ doesNotThrow }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const b = {}
|
||||
const a = {
|
||||
hello: b
|
||||
}
|
||||
b.a = a // circular ref
|
||||
doesNotThrow(() => instance.info(a))
|
||||
})
|
||||
|
||||
test('set the name', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
name: 'hello'
|
||||
}, stream)
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
name: 'hello',
|
||||
msg: 'this is fatal'
|
||||
})
|
||||
})
|
||||
|
||||
test('set the messageKey', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const message = 'hello world'
|
||||
const messageKey = 'fooMessage'
|
||||
const instance = pino({
|
||||
messageKey
|
||||
}, stream)
|
||||
instance.info(message)
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
fooMessage: message
|
||||
})
|
||||
})
|
||||
|
||||
test('set the nestedKey', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const object = { hello: 'world' }
|
||||
const nestedKey = 'stuff'
|
||||
const instance = pino({
|
||||
nestedKey
|
||||
}, stream)
|
||||
instance.info(object)
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
stuff: object
|
||||
})
|
||||
})
|
||||
|
||||
test('set undefined properties', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info({ hello: 'world', property: undefined })
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
hello: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
test('prototype properties are not logged', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info(Object.create({ hello: 'world' }))
|
||||
const { hello } = await once(stream, 'data')
|
||||
equal(hello, undefined)
|
||||
})
|
||||
|
||||
test('set the base', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
base: {
|
||||
a: 'b'
|
||||
}
|
||||
}, stream)
|
||||
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
a: 'b',
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
})
|
||||
})
|
||||
|
||||
test('set the base to null', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
base: null
|
||||
}, stream)
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
})
|
||||
})
|
||||
|
||||
test('set the base to null and use a formatter', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
base: null,
|
||||
formatters: {
|
||||
log (input) {
|
||||
return Object.assign({}, input, { additionalMessage: 'using pino' })
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
instance.fatal('this is fatal too')
|
||||
const result = await once(stream, 'data')
|
||||
equal(new Date(result.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
level: 60,
|
||||
msg: 'this is fatal too',
|
||||
additionalMessage: 'using pino'
|
||||
})
|
||||
})
|
||||
|
||||
test('throw if creating child without bindings', async ({ equal, fail }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
try {
|
||||
instance.child()
|
||||
fail('it should throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'missing bindings for child Pino')
|
||||
}
|
||||
})
|
||||
|
||||
test('correctly escapes msg strings with stray double quote at end', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
name: 'hello'
|
||||
}, stream)
|
||||
|
||||
instance.fatal('this contains "')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
name: 'hello',
|
||||
msg: 'this contains "'
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly escape msg strings with unclosed double quote', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
name: 'hello'
|
||||
}, stream)
|
||||
instance.fatal('" this contains')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
name: 'hello',
|
||||
msg: '" this contains'
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly escape quote in a key', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const obj = { 'some"obj': 'world' }
|
||||
instance.info(obj, 'a string')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
level: 30,
|
||||
pid,
|
||||
hostname,
|
||||
msg: 'a string',
|
||||
'some"obj': 'world'
|
||||
})
|
||||
same(Object.keys(obj), ['some"obj'])
|
||||
})
|
||||
|
||||
// https://github.com/pinojs/pino/issues/139
|
||||
test('object and format string', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info({}, 'foo %s', 'bar')
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'foo bar'
|
||||
})
|
||||
})
|
||||
|
||||
test('object and format string property', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info({ answer: 42 }, 'foo %s', 'bar')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'foo bar',
|
||||
answer: 42
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly strip undefined when returned from toJSON', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
test: 'this'
|
||||
}, stream)
|
||||
instance.fatal({ test: { toJSON () { return undefined } } })
|
||||
const result = await once(stream, 'data')
|
||||
equal('test' in result, false)
|
||||
})
|
||||
|
||||
test('correctly supports stderr', async ({ same }) => {
|
||||
// stderr inherits from Stream, rather than Writable
|
||||
const dest = {
|
||||
writable: true,
|
||||
write (result) {
|
||||
result = JSON.parse(result)
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
msg: 'a message'
|
||||
})
|
||||
}
|
||||
}
|
||||
const instance = pino(dest)
|
||||
instance.fatal('a message')
|
||||
})
|
||||
|
||||
test('normalize number to string', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info(1)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: '1'
|
||||
})
|
||||
})
|
||||
|
||||
test('normalize number to string with an object', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info({ answer: 42 }, 1)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: '1',
|
||||
answer: 42
|
||||
})
|
||||
})
|
||||
|
||||
test('handles objects with null prototype', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const o = Object.create(null)
|
||||
o.test = 'test'
|
||||
instance.info(o)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
test: 'test'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.destination', async ({ same }) => {
|
||||
const tmp = file()
|
||||
const instance = pino(pino.destination(tmp))
|
||||
instance.info('hello')
|
||||
await watchFileCreated(tmp)
|
||||
const result = JSON.parse(readFileSync(tmp).toString())
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('auto pino.destination with a string', async ({ same }) => {
|
||||
const tmp = file()
|
||||
const instance = pino(tmp)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(tmp)
|
||||
const result = JSON.parse(readFileSync(tmp).toString())
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('auto pino.destination with a string as second argument', async ({ same }) => {
|
||||
const tmp = file()
|
||||
const instance = pino(null, tmp)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(tmp)
|
||||
const result = JSON.parse(readFileSync(tmp).toString())
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('does not override opts with a string as second argument', async ({ same }) => {
|
||||
const tmp = file()
|
||||
const instance = pino({
|
||||
timestamp: () => ',"time":"none"'
|
||||
}, tmp)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(tmp)
|
||||
const result = JSON.parse(readFileSync(tmp).toString())
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
time: 'none',
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/pinojs/pino/issues/222
|
||||
test('children with same names render in correct order', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const root = pino(stream)
|
||||
root.child({ a: 1 }).child({ a: 2 }).info({ a: 3 })
|
||||
const { a } = await once(stream, 'data')
|
||||
equal(a, 3, 'last logged object takes precedence')
|
||||
})
|
||||
|
||||
test('use `safe-stable-stringify` to avoid circular dependencies', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const root = pino(stream)
|
||||
// circular depth
|
||||
const obj = {}
|
||||
obj.a = obj
|
||||
root.info(obj)
|
||||
const { a } = await once(stream, 'data')
|
||||
same(a, { a: '[Circular]' })
|
||||
})
|
||||
|
||||
test('correctly log non circular objects', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const root = pino(stream)
|
||||
const obj = {}
|
||||
let parent = obj
|
||||
for (let i = 0; i < 10; i++) {
|
||||
parent.node = {}
|
||||
parent = parent.node
|
||||
}
|
||||
root.info(obj)
|
||||
const { node } = await once(stream, 'data')
|
||||
same(node, { node: { node: { node: { node: { node: { node: { node: { node: { node: {} } } } } } } } } })
|
||||
})
|
||||
|
||||
test('safe-stable-stringify must be used when interpolating', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { a: { b: {} } }
|
||||
o.a.b.c = o.a.b
|
||||
instance.info('test %j', o)
|
||||
|
||||
const { msg } = await once(stream, 'data')
|
||||
t.equal(msg, 'test {"a":{"b":{"c":"[Circular]"}}}')
|
||||
})
|
||||
|
||||
test('throws when setting useOnlyCustomLevels without customLevels', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({
|
||||
useOnlyCustomLevels: true
|
||||
})
|
||||
}, 'customLevels is required if useOnlyCustomLevels is set true')
|
||||
})
|
||||
|
||||
test('correctly log Infinity', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: Infinity }
|
||||
instance.info(o)
|
||||
|
||||
const { num } = await once(stream, 'data')
|
||||
t.equal(num, null)
|
||||
})
|
||||
|
||||
test('correctly log -Infinity', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: -Infinity }
|
||||
instance.info(o)
|
||||
|
||||
const { num } = await once(stream, 'data')
|
||||
t.equal(num, null)
|
||||
})
|
||||
|
||||
test('correctly log NaN', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: NaN }
|
||||
instance.info(o)
|
||||
|
||||
const { num } = await once(stream, 'data')
|
||||
t.equal(num, null)
|
||||
})
|
||||
|
||||
test('offers a .default() method to please typescript', async ({ equal }) => {
|
||||
equal(pino.default, pino)
|
||||
|
||||
const stream = sink()
|
||||
const instance = pino.default(stream)
|
||||
instance.info('hello world')
|
||||
check(equal, await once(stream, 'data'), 30, 'hello world')
|
||||
})
|
||||
|
||||
test('correctly skip function', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: NaN }
|
||||
instance.info(o, () => {})
|
||||
|
||||
const { msg } = await once(stream, 'data')
|
||||
t.equal(msg, undefined)
|
||||
})
|
||||
|
||||
test('correctly skip Infinity', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: NaN }
|
||||
instance.info(o, Infinity)
|
||||
|
||||
const { msg } = await once(stream, 'data')
|
||||
t.equal(msg, null)
|
||||
})
|
||||
|
||||
test('correctly log number', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
const o = { num: NaN }
|
||||
instance.info(o, 42)
|
||||
|
||||
const { msg } = await once(stream, 'data')
|
||||
t.equal(msg, 42)
|
||||
})
|
||||
|
||||
test('nestedKey should not be used for non-objects', async ({ strictSame }) => {
|
||||
const stream = sink()
|
||||
const message = 'hello'
|
||||
const nestedKey = 'stuff'
|
||||
const instance = pino({
|
||||
nestedKey
|
||||
}, stream)
|
||||
instance.info(message)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
strictSame(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: message
|
||||
})
|
||||
})
|
||||
|
||||
test('throws if prettyPrint is passed in as an option', async (t) => {
|
||||
t.throws(() => {
|
||||
pino({
|
||||
prettyPrint: true
|
||||
})
|
||||
}, new Error('prettyPrint option is no longer supported, see the pino-pretty package (https://github.com/pinojs/pino-pretty)'))
|
||||
})
|
||||
|
||||
test('Should invoke `onChild` with the newly created child', async ({ equal }) => {
|
||||
let innerChild
|
||||
const child = pino({
|
||||
onChild: (instance) => {
|
||||
innerChild = instance
|
||||
}
|
||||
}).child({ foo: 'bar' })
|
||||
equal(child, innerChild)
|
||||
})
|
||||
|
||||
test('logger message should have the prefix message that defined in the logger creation', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
msgPrefix: 'My name is Bond '
|
||||
}, stream)
|
||||
logger.info('James Bond')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
|
||||
test('child message should have the prefix message that defined in the child creation', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
|
||||
child.info('James Bond')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
|
||||
test('child message should have the prefix message that defined in the child creation when logging with log meta', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
|
||||
child.info({ hello: 'world' }, 'James Bond')
|
||||
const { msg, hello } = await once(stream, 'data')
|
||||
equal(hello, 'world')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
|
||||
test('logged message should not have the prefix when not providing any message', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const child = instance.child({}, { msgPrefix: 'This should not be shown ' })
|
||||
child.info({ hello: 'world' })
|
||||
const { msg, hello } = await once(stream, 'data')
|
||||
equal(hello, 'world')
|
||||
equal(msg, undefined)
|
||||
})
|
||||
|
||||
test('child message should append parent prefix to current prefix that defined in the child creation', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
msgPrefix: 'My name is Bond '
|
||||
}, stream)
|
||||
const child = instance.child({}, { msgPrefix: 'James ' })
|
||||
child.info('Bond')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
|
||||
test('child message should inherent parent prefix', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
msgPrefix: 'My name is Bond '
|
||||
}, stream)
|
||||
const child = instance.child({})
|
||||
child.info('James Bond')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
|
||||
test('grandchild message should inherent parent prefix', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
const child = instance.child({}, { msgPrefix: 'My name is Bond ' })
|
||||
const grandchild = child.child({})
|
||||
grandchild.info('James Bond')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'My name is Bond James Bond')
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
'use strict'
|
||||
|
||||
const t = require('tap')
|
||||
const { join } = require('path')
|
||||
const { fork } = require('child_process')
|
||||
const { once } = require('./helper')
|
||||
const pino = require('..')
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
t.skip('skipping on windows')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (process.env.CITGM) {
|
||||
// This looks like a some form of limitations of the CITGM test runner
|
||||
// or the HW/SW we run it on. This file can hang on Node.js v18.x.
|
||||
// The failure does not reproduce locally or on our CI.
|
||||
// Skipping it is the only way to keep pino in CITGM.
|
||||
// https://github.com/nodejs/citgm/pull/1002#issuecomment-1751942988
|
||||
t.skip('Skipping on Node.js core CITGM because it hangs on v18.x')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
function test (file) {
|
||||
file = join('fixtures', 'broken-pipe', file)
|
||||
t.test(file, { parallel: true }, async ({ equal }) => {
|
||||
const child = fork(join(__dirname, file), { silent: true })
|
||||
child.stdout.destroy()
|
||||
|
||||
child.stderr.pipe(process.stdout)
|
||||
|
||||
const res = await once(child, 'close')
|
||||
equal(res, 0) // process exits successfully
|
||||
})
|
||||
}
|
||||
|
||||
t.jobs = 42
|
||||
|
||||
test('basic.js')
|
||||
test('destination.js')
|
||||
test('syncfalse.js')
|
||||
|
||||
t.test('let error pass through', ({ equal, plan }) => {
|
||||
plan(3)
|
||||
const stream = pino.destination({ sync: true })
|
||||
|
||||
// side effect of the pino constructor is that it will set an
|
||||
// event handler for error
|
||||
pino(stream)
|
||||
|
||||
process.nextTick(() => stream.emit('error', new Error('kaboom')))
|
||||
process.nextTick(() => stream.emit('error', new Error('kaboom')))
|
||||
|
||||
stream.on('error', (err) => {
|
||||
equal(err.message, 'kaboom')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
test('child has parent level', ({ end, same, is }) => {
|
||||
const instance = pino({
|
||||
level: 'error',
|
||||
browser: {}
|
||||
})
|
||||
|
||||
const child = instance.child({})
|
||||
|
||||
same(child.level, instance.level)
|
||||
end()
|
||||
})
|
||||
|
||||
test('changing child level does not affect parent', ({ end, same, is }) => {
|
||||
const instance = pino({
|
||||
level: 'error',
|
||||
browser: {}
|
||||
})
|
||||
|
||||
const child = instance.child({})
|
||||
child.level = 'info'
|
||||
|
||||
same(instance.level, 'error')
|
||||
end()
|
||||
})
|
||||
|
||||
test('child should log, if its own level allows it', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 30,
|
||||
msg: 'this is info'
|
||||
},
|
||||
{
|
||||
level: 40,
|
||||
msg: 'this is warn'
|
||||
},
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
level: 'error',
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = instance.child({})
|
||||
child.level = 'info'
|
||||
|
||||
child.debug('this is debug')
|
||||
child.info('this is info')
|
||||
child.warn('this is warn')
|
||||
child.error('this is an error')
|
||||
|
||||
same(expected.length, 0, 'not all messages were read')
|
||||
end()
|
||||
})
|
||||
|
||||
test('changing child log level should not affect parent log behavior', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
level: 'error',
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = instance.child({})
|
||||
child.level = 'info'
|
||||
|
||||
instance.warn('this is warn')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
same(expected.length, 0, 'not all messages were read')
|
||||
end()
|
||||
})
|
||||
|
||||
function checkLogObjects (is, same, actual, expected) {
|
||||
is(actual.time <= Date.now(), true, 'time is greater than Date.now()')
|
||||
|
||||
const actualCopy = Object.assign({}, actual)
|
||||
const expectedCopy = Object.assign({}, expected)
|
||||
delete actualCopy.time
|
||||
delete expectedCopy.time
|
||||
|
||||
same(actualCopy, expectedCopy)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
test('set browser opts disabled to true', ({ end, same }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
disabled: true,
|
||||
write (actual) {
|
||||
checkLogObjects(same, actual, [])
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('set browser opts disabled to false', ({ end, same }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 30,
|
||||
msg: 'hello world'
|
||||
},
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
browser: {
|
||||
disabled: false,
|
||||
write (actual) {
|
||||
checkLogObjects(same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('disabled is not set in browser opts', ({ end, same }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 30,
|
||||
msg: 'hello world'
|
||||
},
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
function checkLogObjects (same, actual, expected, is) {
|
||||
const actualCopy = Object.assign({}, actual)
|
||||
const expectedCopy = Object.assign({}, expected)
|
||||
delete actualCopy.time
|
||||
delete expectedCopy.time
|
||||
|
||||
same(actualCopy, expectedCopy)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
Object.freeze(console)
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
test('silent level', ({ end, fail, pass }) => {
|
||||
pino({
|
||||
level: 'silent',
|
||||
browser: { }
|
||||
})
|
||||
end()
|
||||
})
|
||||
@@ -0,0 +1,241 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
test('set the level by string', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.level = 'error'
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('set the level by string. init with silent', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
level: 'silent',
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.level = 'error'
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('set the level by string. init with silent and transmit', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
level: 'silent',
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
},
|
||||
transmit: {
|
||||
send () {}
|
||||
}
|
||||
})
|
||||
|
||||
instance.level = 'error'
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('set the level via constructor', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
},
|
||||
{
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
level: 'error',
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('set custom level and use it', ({ end, same, is }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 31,
|
||||
msg: 'this is a custom level'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
customLevels: {
|
||||
success: 31
|
||||
},
|
||||
browser: {
|
||||
write (actual) {
|
||||
checkLogObjects(is, same, actual, expected.shift())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.success('this is a custom level')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('the wrong level throws', ({ end, throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => {
|
||||
instance.level = 'kaboom'
|
||||
})
|
||||
end()
|
||||
})
|
||||
|
||||
test('the wrong level by number throws', ({ end, throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => {
|
||||
instance.levelVal = 55
|
||||
})
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposes level string mappings', ({ end, is }) => {
|
||||
is(pino.levels.values.error, 50)
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposes level number mappings', ({ end, is }) => {
|
||||
is(pino.levels.labels[50], 'error')
|
||||
end()
|
||||
})
|
||||
|
||||
test('returns level integer', ({ end, is }) => {
|
||||
const instance = pino({ level: 'error' })
|
||||
is(instance.levelVal, 50)
|
||||
end()
|
||||
})
|
||||
|
||||
test('silent level via constructor', ({ end, fail }) => {
|
||||
const instance = pino({
|
||||
level: 'silent',
|
||||
browser: {
|
||||
write () {
|
||||
fail('no data should be logged')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(pino.levels.values).forEach((level) => {
|
||||
instance[level]('hello world')
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('silent level by string', ({ end, fail }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write () {
|
||||
fail('no data should be logged')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.level = 'silent'
|
||||
|
||||
Object.keys(pino.levels.values).forEach((level) => {
|
||||
instance[level]('hello world')
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposed levels', ({ end, same }) => {
|
||||
same(Object.keys(pino.levels.values), [
|
||||
'fatal',
|
||||
'error',
|
||||
'warn',
|
||||
'info',
|
||||
'debug',
|
||||
'trace'
|
||||
])
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposed labels', ({ end, same }) => {
|
||||
same(Object.keys(pino.levels.labels), [
|
||||
'10',
|
||||
'20',
|
||||
'30',
|
||||
'40',
|
||||
'50',
|
||||
'60'
|
||||
])
|
||||
end()
|
||||
})
|
||||
|
||||
function checkLogObjects (is, same, actual, expected) {
|
||||
is(actual.time <= Date.now(), true, 'time is greater than Date.now()')
|
||||
|
||||
const actualCopy = Object.assign({}, actual)
|
||||
const expectedCopy = Object.assign({}, expected)
|
||||
delete actualCopy.time
|
||||
delete expectedCopy.time
|
||||
|
||||
same(actualCopy, expectedCopy)
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
'use strict'
|
||||
// eslint-disable-next-line
|
||||
if (typeof $1 !== 'undefined') $1 = arguments.callee.caller.arguments[0]
|
||||
|
||||
const test = require('tape')
|
||||
const fresh = require('import-fresh')
|
||||
const pino = require('../browser')
|
||||
|
||||
const parentSerializers = {
|
||||
test: () => 'parent'
|
||||
}
|
||||
|
||||
const childSerializers = {
|
||||
test: () => 'child'
|
||||
}
|
||||
|
||||
test('serializers override values', ({ end, is }) => {
|
||||
const parent = pino({
|
||||
serializers: parentSerializers,
|
||||
browser: {
|
||||
serialize: true,
|
||||
write (o) {
|
||||
is(o.test, 'parent')
|
||||
end()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
parent.fatal({ test: 'test' })
|
||||
})
|
||||
|
||||
test('without the serialize option, serializers do not override values', ({ end, is }) => {
|
||||
const parent = pino({
|
||||
serializers: parentSerializers,
|
||||
browser: {
|
||||
write (o) {
|
||||
is(o.test, 'test')
|
||||
end()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
parent.fatal({ test: 'test' })
|
||||
})
|
||||
|
||||
if (process.title !== 'browser') {
|
||||
test('if serialize option is true, standard error serializer is auto enabled', ({ end, same }) => {
|
||||
const err = Error('test')
|
||||
err.code = 'test'
|
||||
err.type = 'Error' // get that cov
|
||||
const expect = pino.stdSerializers.err(err)
|
||||
|
||||
const consoleError = console.error
|
||||
console.error = function (err) {
|
||||
same(err, expect)
|
||||
}
|
||||
|
||||
const logger = fresh('../browser')({
|
||||
browser: { serialize: true }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.fatal(err)
|
||||
end()
|
||||
})
|
||||
|
||||
test('if serialize option is array, standard error serializer is auto enabled', ({ end, same }) => {
|
||||
const err = Error('test')
|
||||
err.code = 'test'
|
||||
const expect = pino.stdSerializers.err(err)
|
||||
|
||||
const consoleError = console.error
|
||||
console.error = function (err) {
|
||||
same(err, expect)
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
browser: { serialize: [] }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.fatal(err)
|
||||
end()
|
||||
})
|
||||
|
||||
test('if serialize option is array containing !stdSerializers.err, standard error serializer is disabled', ({ end, is }) => {
|
||||
const err = Error('test')
|
||||
err.code = 'test'
|
||||
const expect = err
|
||||
|
||||
const consoleError = console.error
|
||||
console.error = function (err) {
|
||||
is(err, expect)
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
browser: { serialize: ['!stdSerializers.err'] }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.fatal(err)
|
||||
end()
|
||||
})
|
||||
|
||||
test('in browser, serializers apply to all objects', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (test, test2, test3, test4, test5) {
|
||||
is(test.key, 'serialized')
|
||||
is(test2.key2, 'serialized2')
|
||||
is(test5.key3, 'serialized3')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
serializers: {
|
||||
key: () => 'serialized',
|
||||
key2: () => 'serialized2',
|
||||
key3: () => 'serialized3'
|
||||
},
|
||||
browser: { serialize: true }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('serialize can be an array of selected serializers', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (test, test2, test3, test4, test5) {
|
||||
is(test.key, 'test')
|
||||
is(test2.key2, 'serialized2')
|
||||
is(test5.key3, 'test')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
serializers: {
|
||||
key: () => 'serialized',
|
||||
key2: () => 'serialized2',
|
||||
key3: () => 'serialized3'
|
||||
},
|
||||
browser: { serialize: ['key2'] }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('serialize filter applies to child loggers', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (binding, test, test2, test3, test4, test5) {
|
||||
is(test.key, 'test')
|
||||
is(test2.key2, 'serialized2')
|
||||
is(test5.key3, 'test')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
browser: { serialize: ['key2'] }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.child({
|
||||
aBinding: 'test'
|
||||
}, {
|
||||
serializers: {
|
||||
key: () => 'serialized',
|
||||
key2: () => 'serialized2',
|
||||
key3: () => 'serialized3'
|
||||
}
|
||||
}).fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('serialize filter applies to child loggers through bindings', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (binding, test, test2, test3, test4, test5) {
|
||||
is(test.key, 'test')
|
||||
is(test2.key2, 'serialized2')
|
||||
is(test5.key3, 'test')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
browser: { serialize: ['key2'] }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.child({
|
||||
aBinding: 'test',
|
||||
serializers: {
|
||||
key: () => 'serialized',
|
||||
key2: () => 'serialized2',
|
||||
key3: () => 'serialized3'
|
||||
}
|
||||
}).fatal({ key: 'test' }, { key2: 'test' }, 'str should skip', [{ foo: 'array should skip' }], { key3: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('parent serializers apply to child bindings', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (binding) {
|
||||
is(binding.key, 'serialized')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
serializers: {
|
||||
key: () => 'serialized'
|
||||
},
|
||||
browser: { serialize: true }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.child({ key: 'test' }).fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('child serializers apply to child bindings', ({ end, is }) => {
|
||||
const consoleError = console.error
|
||||
console.error = function (binding) {
|
||||
is(binding.key, 'serialized')
|
||||
}
|
||||
|
||||
const logger = fresh('../browser', require)({
|
||||
browser: { serialize: true }
|
||||
})
|
||||
|
||||
console.error = consoleError
|
||||
|
||||
logger.child({
|
||||
key: 'test'
|
||||
}, {
|
||||
serializers: {
|
||||
key: () => 'serialized'
|
||||
}
|
||||
}).fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
}
|
||||
|
||||
test('child does not overwrite parent serializers', ({ end, is }) => {
|
||||
let c = 0
|
||||
const parent = pino({
|
||||
serializers: parentSerializers,
|
||||
browser: {
|
||||
serialize: true,
|
||||
write (o) {
|
||||
c++
|
||||
if (c === 1) is(o.test, 'parent')
|
||||
if (c === 2) {
|
||||
is(o.test, 'child')
|
||||
end()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const child = parent.child({}, { serializers: childSerializers })
|
||||
|
||||
parent.fatal({ test: 'test' })
|
||||
child.fatal({ test: 'test' })
|
||||
})
|
||||
|
||||
test('children inherit parent serializers', ({ end, is }) => {
|
||||
const parent = pino({
|
||||
serializers: parentSerializers,
|
||||
browser: {
|
||||
serialize: true,
|
||||
write (o) {
|
||||
is(o.test, 'parent')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = parent.child({ a: 'property' })
|
||||
child.fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('children serializers get called', ({ end, is }) => {
|
||||
const parent = pino({
|
||||
test: 'this',
|
||||
browser: {
|
||||
serialize: true,
|
||||
write (o) {
|
||||
is(o.test, 'child')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = parent.child({ a: 'property' }, { serializers: childSerializers })
|
||||
|
||||
child.fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('children serializers get called when inherited from parent', ({ end, is }) => {
|
||||
const parent = pino({
|
||||
test: 'this',
|
||||
serializers: parentSerializers,
|
||||
browser: {
|
||||
serialize: true,
|
||||
write: (o) => {
|
||||
is(o.test, 'pass')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = parent.child({}, { serializers: { test: () => 'pass' } })
|
||||
|
||||
child.fatal({ test: 'fail' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('non overridden serializers are available in the children', ({ end, is }) => {
|
||||
const pSerializers = {
|
||||
onlyParent: () => 'parent',
|
||||
shared: () => 'parent'
|
||||
}
|
||||
|
||||
const cSerializers = {
|
||||
shared: () => 'child',
|
||||
onlyChild: () => 'child'
|
||||
}
|
||||
|
||||
let c = 0
|
||||
|
||||
const parent = pino({
|
||||
serializers: pSerializers,
|
||||
browser: {
|
||||
serialize: true,
|
||||
write (o) {
|
||||
c++
|
||||
if (c === 1) is(o.shared, 'child')
|
||||
if (c === 2) is(o.onlyParent, 'parent')
|
||||
if (c === 3) is(o.onlyChild, 'child')
|
||||
if (c === 4) is(o.onlyChild, 'test')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = parent.child({}, { serializers: cSerializers })
|
||||
|
||||
child.fatal({ shared: 'test' })
|
||||
child.fatal({ onlyParent: 'test' })
|
||||
child.fatal({ onlyChild: 'test' })
|
||||
parent.fatal({ onlyChild: 'test' })
|
||||
end()
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
Date.now = () => 1599400603614
|
||||
|
||||
test('null timestamp', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
timestamp: pino.stdTimeFunctions.nullTime,
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, undefined)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
|
||||
test('iso timestamp', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
timestamp: pino.stdTimeFunctions.isoTime,
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, '2020-09-06T13:56:43.614Z')
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
|
||||
test('epoch timestamp', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
timestamp: pino.stdTimeFunctions.epochTime,
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, 1599400603614)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
|
||||
test('unix timestamp', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
timestamp: pino.stdTimeFunctions.unixTime,
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, Math.round(1599400603614 / 1000.0))
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
|
||||
test('epoch timestamp by default', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, 1599400603614)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
|
||||
test('not print timestamp if the option is false', ({ end, is }) => {
|
||||
const instance = pino({
|
||||
timestamp: false,
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.time, undefined)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('hello world')
|
||||
end()
|
||||
})
|
||||
@@ -0,0 +1,349 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const pino = require('../browser')
|
||||
|
||||
function noop () {}
|
||||
|
||||
test('throws if transmit object does not have send function', ({ end, throws }) => {
|
||||
throws(() => {
|
||||
pino({ browser: { transmit: {} } })
|
||||
})
|
||||
|
||||
throws(() => {
|
||||
pino({ browser: { transmit: { send: 'not a func' } } })
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('calls send function after write', ({ end, is }) => {
|
||||
let c = 0
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: () => {
|
||||
c++
|
||||
},
|
||||
transmit: {
|
||||
send () { is(c, 1) }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function the logged level', ({ end, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write () {},
|
||||
transmit: {
|
||||
send (level) {
|
||||
is(level, 'fatal')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal({ test: 'test' })
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function message strings in logEvent object when asObject is not set', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, { messages }) {
|
||||
is(messages[0], 'test')
|
||||
is(messages[1], 'another test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal('test', 'another test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function message objects in logEvent object when asObject is not set', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, { messages }) {
|
||||
same(messages[0], { test: 'test' })
|
||||
is(messages[1], 'another test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal({ test: 'test' }, 'another test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function message strings in logEvent object when asObject is set', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, { messages }) {
|
||||
is(messages[0], 'test')
|
||||
is(messages[1], 'another test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal('test', 'another test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function message objects in logEvent object when asObject is set', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, { messages }) {
|
||||
same(messages[0], { test: 'test' })
|
||||
is(messages[1], 'another test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal({ test: 'test' }, 'another test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('supplies a timestamp (ts) in logEvent object which is exactly the same as the `time` property in asObject mode', ({ end, is }) => {
|
||||
let expected
|
||||
const logger = pino({
|
||||
browser: {
|
||||
asObject: true, // implicit because `write`, but just to be explicit
|
||||
write (o) {
|
||||
expected = o.time
|
||||
},
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
is(logEvent.ts, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function child bindings via logEvent object', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
const messages = logEvent.messages
|
||||
const bindings = logEvent.bindings
|
||||
same(bindings[0], { first: 'binding' })
|
||||
same(bindings[1], { second: 'binding2' })
|
||||
same(messages[0], { test: 'test' })
|
||||
is(messages[1], 'another test')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger
|
||||
.child({ first: 'binding' })
|
||||
.child({ second: 'binding2' })
|
||||
.fatal({ test: 'test' }, 'another test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('passes send function level:{label, value} via logEvent object', ({ end, is }) => {
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
const label = logEvent.level.label
|
||||
const value = logEvent.level.value
|
||||
|
||||
is(label, 'fatal')
|
||||
is(value, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.fatal({ test: 'test' }, 'another test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('calls send function according to transmit.level', ({ end, is }) => {
|
||||
let c = 0
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
level: 'error',
|
||||
send (level) {
|
||||
c++
|
||||
if (c === 1) is(level, 'error')
|
||||
if (c === 2) is(level, 'fatal')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
logger.warn('ignored')
|
||||
logger.error('test')
|
||||
logger.fatal('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('transmit.level defaults to logger level', ({ end, is }) => {
|
||||
let c = 0
|
||||
const logger = pino({
|
||||
level: 'error',
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level) {
|
||||
c++
|
||||
if (c === 1) is(level, 'error')
|
||||
if (c === 2) is(level, 'fatal')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
logger.warn('ignored')
|
||||
logger.error('test')
|
||||
logger.fatal('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('transmit.level is effective even if lower than logger level', ({ end, is }) => {
|
||||
let c = 0
|
||||
const logger = pino({
|
||||
level: 'error',
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
level: 'info',
|
||||
send (level) {
|
||||
c++
|
||||
if (c === 1) is(level, 'warn')
|
||||
if (c === 2) is(level, 'error')
|
||||
if (c === 3) is(level, 'fatal')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
logger.warn('ignored')
|
||||
logger.error('test')
|
||||
logger.fatal('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('applies all serializers to messages and bindings (serialize:false - default)', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
serializers: {
|
||||
first: () => 'first',
|
||||
second: () => 'second',
|
||||
test: () => 'serialize it'
|
||||
},
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
const messages = logEvent.messages
|
||||
const bindings = logEvent.bindings
|
||||
same(bindings[0], { first: 'first' })
|
||||
same(bindings[1], { second: 'second' })
|
||||
same(messages[0], { test: 'serialize it' })
|
||||
is(messages[1].type, 'Error')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger
|
||||
.child({ first: 'binding' })
|
||||
.child({ second: 'binding2' })
|
||||
.fatal({ test: 'test' }, Error())
|
||||
end()
|
||||
})
|
||||
|
||||
test('applies all serializers to messages and bindings (serialize:true)', ({ end, same, is }) => {
|
||||
const logger = pino({
|
||||
serializers: {
|
||||
first: () => 'first',
|
||||
second: () => 'second',
|
||||
test: () => 'serialize it'
|
||||
},
|
||||
browser: {
|
||||
serialize: true,
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
const messages = logEvent.messages
|
||||
const bindings = logEvent.bindings
|
||||
same(bindings[0], { first: 'first' })
|
||||
same(bindings[1], { second: 'second' })
|
||||
same(messages[0], { test: 'serialize it' })
|
||||
is(messages[1].type, 'Error')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger
|
||||
.child({ first: 'binding' })
|
||||
.child({ second: 'binding2' })
|
||||
.fatal({ test: 'test' }, Error())
|
||||
end()
|
||||
})
|
||||
|
||||
test('extracts correct bindings and raw messages over multiple transmits', ({ end, same, is }) => {
|
||||
let messages = null
|
||||
let bindings = null
|
||||
|
||||
const logger = pino({
|
||||
browser: {
|
||||
write: noop,
|
||||
transmit: {
|
||||
send (level, logEvent) {
|
||||
messages = logEvent.messages
|
||||
bindings = logEvent.bindings
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const child = logger.child({ child: true })
|
||||
const grandchild = child.child({ grandchild: true })
|
||||
|
||||
logger.fatal({ test: 'parent:test1' })
|
||||
logger.fatal({ test: 'parent:test2' })
|
||||
same([], bindings)
|
||||
same([{ test: 'parent:test2' }], messages)
|
||||
|
||||
child.fatal({ test: 'child:test1' })
|
||||
child.fatal({ test: 'child:test2' })
|
||||
same([{ child: true }], bindings)
|
||||
same([{ test: 'child:test2' }], messages)
|
||||
|
||||
grandchild.fatal({ test: 'grandchild:test1' })
|
||||
grandchild.fatal({ test: 'grandchild:test2' })
|
||||
same([{ child: true }, { grandchild: true }], bindings)
|
||||
same([{ test: 'grandchild:test2' }], messages)
|
||||
|
||||
end()
|
||||
})
|
||||
@@ -0,0 +1,594 @@
|
||||
'use strict'
|
||||
const test = require('tape')
|
||||
const fresh = require('import-fresh')
|
||||
const pinoStdSerializers = require('pino-std-serializers')
|
||||
const pino = require('../browser')
|
||||
|
||||
levelTest('fatal')
|
||||
levelTest('error')
|
||||
levelTest('warn')
|
||||
levelTest('info')
|
||||
levelTest('debug')
|
||||
levelTest('trace')
|
||||
|
||||
test('silent level', ({ end, fail, pass }) => {
|
||||
const instance = pino({
|
||||
level: 'silent',
|
||||
browser: { write: fail }
|
||||
})
|
||||
instance.info('test')
|
||||
const child = instance.child({ test: 'test' })
|
||||
child.info('msg-test')
|
||||
// use setTimeout because setImmediate isn't supported in most browsers
|
||||
setTimeout(() => {
|
||||
pass()
|
||||
end()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
test('enabled false', ({ end, fail, pass }) => {
|
||||
const instance = pino({
|
||||
enabled: false,
|
||||
browser: { write: fail }
|
||||
})
|
||||
instance.info('test')
|
||||
const child = instance.child({ test: 'test' })
|
||||
child.info('msg-test')
|
||||
// use setTimeout because setImmediate isn't supported in most browsers
|
||||
setTimeout(() => {
|
||||
pass()
|
||||
end()
|
||||
}, 0)
|
||||
})
|
||||
|
||||
test('throw if creating child without bindings', ({ end, throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => instance.child())
|
||||
end()
|
||||
})
|
||||
|
||||
test('stubs write, flush and ee methods on instance', ({ end, ok, is }) => {
|
||||
const instance = pino()
|
||||
|
||||
ok(isFunc(instance.setMaxListeners))
|
||||
ok(isFunc(instance.getMaxListeners))
|
||||
ok(isFunc(instance.emit))
|
||||
ok(isFunc(instance.addListener))
|
||||
ok(isFunc(instance.on))
|
||||
ok(isFunc(instance.prependListener))
|
||||
ok(isFunc(instance.once))
|
||||
ok(isFunc(instance.prependOnceListener))
|
||||
ok(isFunc(instance.removeListener))
|
||||
ok(isFunc(instance.removeAllListeners))
|
||||
ok(isFunc(instance.listeners))
|
||||
ok(isFunc(instance.listenerCount))
|
||||
ok(isFunc(instance.eventNames))
|
||||
ok(isFunc(instance.write))
|
||||
ok(isFunc(instance.flush))
|
||||
|
||||
is(instance.on(), undefined)
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposes levels object', ({ end, same }) => {
|
||||
same(pino.levels, {
|
||||
values: {
|
||||
fatal: 60,
|
||||
error: 50,
|
||||
warn: 40,
|
||||
info: 30,
|
||||
debug: 20,
|
||||
trace: 10
|
||||
},
|
||||
labels: {
|
||||
10: 'trace',
|
||||
20: 'debug',
|
||||
30: 'info',
|
||||
40: 'warn',
|
||||
50: 'error',
|
||||
60: 'fatal'
|
||||
}
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposes faux stdSerializers', ({ end, ok, same }) => {
|
||||
ok(pino.stdSerializers)
|
||||
// make sure faux stdSerializers match pino-std-serializers
|
||||
for (const serializer in pinoStdSerializers) {
|
||||
ok(pino.stdSerializers[serializer], `pino.stdSerializers.${serializer}`)
|
||||
}
|
||||
// confirm faux methods return empty objects
|
||||
same(pino.stdSerializers.req(), {})
|
||||
same(pino.stdSerializers.mapHttpRequest(), {})
|
||||
same(pino.stdSerializers.mapHttpResponse(), {})
|
||||
same(pino.stdSerializers.res(), {})
|
||||
// confirm wrapping function is a passthrough
|
||||
const noChange = { foo: 'bar', fuz: 42 }
|
||||
same(pino.stdSerializers.wrapRequestSerializer(noChange), noChange)
|
||||
same(pino.stdSerializers.wrapResponseSerializer(noChange), noChange)
|
||||
end()
|
||||
})
|
||||
|
||||
test('exposes err stdSerializer', ({ end, ok }) => {
|
||||
ok(pino.stdSerializers.err)
|
||||
ok(pino.stdSerializers.err(Error()))
|
||||
end()
|
||||
})
|
||||
|
||||
consoleMethodTest('error')
|
||||
consoleMethodTest('fatal', 'error')
|
||||
consoleMethodTest('warn')
|
||||
consoleMethodTest('info')
|
||||
consoleMethodTest('debug')
|
||||
consoleMethodTest('trace')
|
||||
absentConsoleMethodTest('error', 'log')
|
||||
absentConsoleMethodTest('warn', 'error')
|
||||
absentConsoleMethodTest('info', 'log')
|
||||
absentConsoleMethodTest('debug', 'log')
|
||||
absentConsoleMethodTest('trace', 'log')
|
||||
|
||||
// do not run this with airtap
|
||||
if (process.title !== 'browser') {
|
||||
test('in absence of console, log methods become noops', ({ end, ok }) => {
|
||||
const console = global.console
|
||||
delete global.console
|
||||
const instance = fresh('../browser')()
|
||||
global.console = console
|
||||
ok(fnName(instance.log).match(/noop/))
|
||||
ok(fnName(instance.fatal).match(/noop/))
|
||||
ok(fnName(instance.error).match(/noop/))
|
||||
ok(fnName(instance.warn).match(/noop/))
|
||||
ok(fnName(instance.info).match(/noop/))
|
||||
ok(fnName(instance.debug).match(/noop/))
|
||||
ok(fnName(instance.trace).match(/noop/))
|
||||
end()
|
||||
})
|
||||
}
|
||||
|
||||
test('opts.browser.asObject logs pino-like object to console', ({ end, ok, is }) => {
|
||||
const info = console.info
|
||||
console.info = function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test')
|
||||
ok(o.time)
|
||||
console.info = info
|
||||
}
|
||||
const instance = require('../browser')({
|
||||
browser: {
|
||||
asObject: true
|
||||
}
|
||||
})
|
||||
|
||||
instance.info('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.formatters logs pino-like object to console', ({ end, ok, is }) => {
|
||||
const info = console.info
|
||||
console.info = function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.label, 'info')
|
||||
is(o.msg, 'test')
|
||||
ok(o.time)
|
||||
console.info = info
|
||||
}
|
||||
const instance = require('../browser')({
|
||||
browser: {
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { label, level: number }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.info('test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.formatters logs pino-like object to console', ({ end, ok, is }) => {
|
||||
const info = console.info
|
||||
console.info = function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test')
|
||||
is(o.hello, 'world')
|
||||
is(o.newField, 'test')
|
||||
ok(o.time, `Logged at ${o.time}`)
|
||||
console.info = info
|
||||
}
|
||||
const instance = require('../browser')({
|
||||
browser: {
|
||||
formatters: {
|
||||
log (o) {
|
||||
return { ...o, newField: 'test', time: `Logged at ${o.time}` }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
instance.info({ hello: 'world' }, 'test')
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func log single string', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string joining', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test test2 test3')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test %s %s', 'test2', 'test3')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string joining when asObject is true', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test test2 test3')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test %s %s', 'test2', 'test3')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string joining when asObject is true', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test test2 test3')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test %s %s', 'test2', 'test3')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string object joining', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test {"test":"test2"} {"test":"test3"}')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test %j %j', { test: 'test2' }, { test: 'test3' })
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string object joining when asObject is true', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
asObject: true,
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test {"test":"test2"} {"test":"test3"}')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('test %j %j', { test: 'test2' }, { test: 'test3' })
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func string interpolation', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test2 test ({"test":"test3"})')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info('%s test (%j)', 'test2', { test: 'test3' })
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func number', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 1)
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info(1)
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write func log single object', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: function (o) {
|
||||
is(o.level, 30)
|
||||
is(o.test, 'test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.info({ test: 'test' })
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write obj writes to methods corresponding to level', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write: {
|
||||
error: function (o) {
|
||||
is(o.level, 50)
|
||||
is(o.test, 'test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.error({ test: 'test' })
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.asObject/write supports child loggers', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write (o) {
|
||||
is(o.level, 30)
|
||||
is(o.test, 'test')
|
||||
is(o.msg, 'msg-test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
const child = instance.child({ test: 'test' })
|
||||
child.info('msg-test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.asObject/write supports child child loggers', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write (o) {
|
||||
is(o.level, 30)
|
||||
is(o.test, 'test')
|
||||
is(o.foo, 'bar')
|
||||
is(o.msg, 'msg-test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
const child = instance.child({ test: 'test' }).child({ foo: 'bar' })
|
||||
child.info('msg-test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.asObject/write supports child child child loggers', ({ end, ok, is }) => {
|
||||
const instance = pino({
|
||||
browser: {
|
||||
write (o) {
|
||||
is(o.level, 30)
|
||||
is(o.test, 'test')
|
||||
is(o.foo, 'bar')
|
||||
is(o.baz, 'bop')
|
||||
is(o.msg, 'msg-test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
})
|
||||
const child = instance.child({ test: 'test' }).child({ foo: 'bar' }).child({ baz: 'bop' })
|
||||
child.info('msg-test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.asObject defensively mitigates naughty numbers', ({ end, pass }) => {
|
||||
const instance = pino({
|
||||
browser: { asObject: true, write: () => {} }
|
||||
})
|
||||
const child = instance.child({ test: 'test' })
|
||||
child._childLevel = -10
|
||||
child.info('test')
|
||||
pass() // if we reached here, there was no infinite loop, so, .. pass.
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('opts.browser.write obj falls back to console where a method is not supplied', ({ end, ok, is }) => {
|
||||
const info = console.info
|
||||
console.info = (o) => {
|
||||
is(o.level, 30)
|
||||
is(o.msg, 'test')
|
||||
ok(o.time)
|
||||
console.info = info
|
||||
}
|
||||
const instance = require('../browser')({
|
||||
browser: {
|
||||
write: {
|
||||
error (o) {
|
||||
is(o.level, 50)
|
||||
is(o.test, 'test')
|
||||
ok(o.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
instance.error({ test: 'test' })
|
||||
instance.info('test')
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
function levelTest (name) {
|
||||
test(name + ' logs', ({ end, is }) => {
|
||||
const msg = 'hello world'
|
||||
sink(name, (args) => {
|
||||
is(args[0], msg)
|
||||
end()
|
||||
})
|
||||
pino({ level: name })[name](msg)
|
||||
})
|
||||
|
||||
test('passing objects at level ' + name, ({ end, is }) => {
|
||||
const msg = { hello: 'world' }
|
||||
sink(name, (args) => {
|
||||
is(args[0], msg)
|
||||
end()
|
||||
})
|
||||
pino({ level: name })[name](msg)
|
||||
})
|
||||
|
||||
test('passing an object and a string at level ' + name, ({ end, is }) => {
|
||||
const a = { hello: 'world' }
|
||||
const b = 'a string'
|
||||
sink(name, (args) => {
|
||||
is(args[0], a)
|
||||
is(args[1], b)
|
||||
end()
|
||||
})
|
||||
pino({ level: name })[name](a, b)
|
||||
})
|
||||
|
||||
test('formatting logs as ' + name, ({ end, is }) => {
|
||||
sink(name, (args) => {
|
||||
is(args[0], 'hello %d')
|
||||
is(args[1], 42)
|
||||
end()
|
||||
})
|
||||
pino({ level: name })[name]('hello %d', 42)
|
||||
})
|
||||
|
||||
test('passing error at level ' + name, ({ end, is }) => {
|
||||
const err = new Error('myerror')
|
||||
sink(name, (args) => {
|
||||
is(args[0], err)
|
||||
end()
|
||||
})
|
||||
pino({ level: name })[name](err)
|
||||
})
|
||||
|
||||
test('passing error with a serializer at level ' + name, ({ end, is }) => {
|
||||
// in browser - should have no effect (should not crash)
|
||||
const err = new Error('myerror')
|
||||
sink(name, (args) => {
|
||||
is(args[0].err, err)
|
||||
end()
|
||||
})
|
||||
const instance = pino({
|
||||
level: name,
|
||||
serializers: {
|
||||
err: pino.stdSerializers.err
|
||||
}
|
||||
})
|
||||
instance[name]({ err })
|
||||
})
|
||||
|
||||
test('child logger for level ' + name, ({ end, is }) => {
|
||||
const msg = 'hello world'
|
||||
const parent = { hello: 'world' }
|
||||
sink(name, (args) => {
|
||||
is(args[0], parent)
|
||||
is(args[1], msg)
|
||||
end()
|
||||
})
|
||||
const instance = pino({ level: name })
|
||||
const child = instance.child(parent)
|
||||
child[name](msg)
|
||||
})
|
||||
|
||||
test('child-child logger for level ' + name, ({ end, is }) => {
|
||||
const msg = 'hello world'
|
||||
const grandParent = { hello: 'world' }
|
||||
const parent = { hello: 'you' }
|
||||
sink(name, (args) => {
|
||||
is(args[0], grandParent)
|
||||
is(args[1], parent)
|
||||
is(args[2], msg)
|
||||
end()
|
||||
})
|
||||
const instance = pino({ level: name })
|
||||
const child = instance.child(grandParent).child(parent)
|
||||
child[name](msg)
|
||||
})
|
||||
}
|
||||
|
||||
function consoleMethodTest (level, method) {
|
||||
if (!method) method = level
|
||||
test('pino().' + level + ' uses console.' + method, ({ end, is }) => {
|
||||
sink(method, (args) => {
|
||||
is(args[0], 'test')
|
||||
end()
|
||||
})
|
||||
const instance = require('../browser')({ level })
|
||||
instance[level]('test')
|
||||
})
|
||||
}
|
||||
|
||||
function absentConsoleMethodTest (method, fallback) {
|
||||
test('in absence of console.' + method + ', console.' + fallback + ' is used', ({ end, is }) => {
|
||||
const fn = console[method]
|
||||
console[method] = undefined
|
||||
sink(fallback, function (args) {
|
||||
is(args[0], 'test')
|
||||
end()
|
||||
console[method] = fn
|
||||
})
|
||||
const instance = require('../browser')({ level: method })
|
||||
instance[method]('test')
|
||||
})
|
||||
}
|
||||
|
||||
function isFunc (fn) { return typeof fn === 'function' }
|
||||
function fnName (fn) {
|
||||
const rx = /^\s*function\s*([^(]*)/i
|
||||
const match = rx.exec(fn)
|
||||
return match && match[1]
|
||||
}
|
||||
function sink (method, fn) {
|
||||
if (method === 'fatal') method = 'error'
|
||||
const orig = console[method]
|
||||
console[method] = function () {
|
||||
console[method] = orig
|
||||
fn(Array.prototype.slice.call(arguments))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const { PassThrough } = require('stream')
|
||||
const pino = require('../')
|
||||
|
||||
test('Proxy and stream objects', async ({ equal }) => {
|
||||
const s = new PassThrough()
|
||||
s.resume()
|
||||
s.write('', () => {})
|
||||
const obj = { s, p: new Proxy({}, { get () { throw new Error('kaboom') } }) }
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info({ obj })
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
|
||||
equal(result.obj, '[unable to serialize, circular reference is too complex to analyze]')
|
||||
})
|
||||
|
||||
test('Proxy and stream objects', async ({ equal }) => {
|
||||
const s = new PassThrough()
|
||||
s.resume()
|
||||
s.write('', () => {})
|
||||
const obj = { s, p: new Proxy({}, { get () { throw new Error('kaboom') } }) }
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info(obj)
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
|
||||
equal(result.p, '[unable to serialize, circular reference is too complex to analyze]')
|
||||
})
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const writer = require('flush-write-stream')
|
||||
const pino = require('../')
|
||||
|
||||
function capture () {
|
||||
const ws = writer((chunk, enc, cb) => {
|
||||
ws.data += chunk.toString()
|
||||
cb()
|
||||
})
|
||||
ws.data = ''
|
||||
return ws
|
||||
}
|
||||
|
||||
test('pino uses LF by default', async ({ ok }) => {
|
||||
const stream = capture()
|
||||
const logger = pino(stream)
|
||||
logger.info('foo')
|
||||
logger.error('bar')
|
||||
ok(/foo[^\r\n]+\n[^\r\n]+bar[^\r\n]+\n/.test(stream.data))
|
||||
})
|
||||
|
||||
test('pino can log CRLF', async ({ ok }) => {
|
||||
const stream = capture()
|
||||
const logger = pino({
|
||||
crlf: true
|
||||
}, stream)
|
||||
logger.info('foo')
|
||||
logger.error('bar')
|
||||
ok(/foo[^\n]+\r\n[^\n]+bar[^\n]+\r\n/.test(stream.data))
|
||||
})
|
||||
@@ -0,0 +1,253 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
// Silence all warnings for this test
|
||||
process.removeAllListeners('warning')
|
||||
process.on('warning', () => {})
|
||||
|
||||
test('adds additional levels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35,
|
||||
bar: 45
|
||||
}
|
||||
}, stream)
|
||||
|
||||
logger.foo('test')
|
||||
const { level } = await once(stream, 'data')
|
||||
equal(level, 35)
|
||||
})
|
||||
|
||||
test('custom levels does not override default levels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, stream)
|
||||
|
||||
logger.info('test')
|
||||
const { level } = await once(stream, 'data')
|
||||
equal(level, 30)
|
||||
})
|
||||
|
||||
test('default levels can be redefined using custom levels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
info: 35,
|
||||
debug: 45
|
||||
},
|
||||
useOnlyCustomLevels: true
|
||||
}, stream)
|
||||
|
||||
equal(logger.hasOwnProperty('info'), true)
|
||||
|
||||
logger.info('test')
|
||||
const { level } = await once(stream, 'data')
|
||||
equal(level, 35)
|
||||
})
|
||||
|
||||
test('custom levels overrides default level label if use useOnlyCustomLevels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
},
|
||||
useOnlyCustomLevels: true,
|
||||
level: 'foo'
|
||||
}, stream)
|
||||
|
||||
equal(logger.hasOwnProperty('info'), false)
|
||||
})
|
||||
|
||||
test('custom levels overrides default level value if use useOnlyCustomLevels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
},
|
||||
useOnlyCustomLevels: true,
|
||||
level: 35
|
||||
}, stream)
|
||||
|
||||
equal(logger.hasOwnProperty('info'), false)
|
||||
})
|
||||
|
||||
test('custom levels are inherited by children', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, stream)
|
||||
|
||||
logger.child({ childMsg: 'ok' }).foo('test')
|
||||
const { msg, childMsg, level } = await once(stream, 'data')
|
||||
equal(level, 35)
|
||||
equal(childMsg, 'ok')
|
||||
equal(msg, 'test')
|
||||
})
|
||||
|
||||
test('custom levels can be specified on child bindings', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino(stream).child({
|
||||
childMsg: 'ok'
|
||||
}, {
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
})
|
||||
|
||||
logger.foo('test')
|
||||
const { msg, childMsg, level } = await once(stream, 'data')
|
||||
equal(level, 35)
|
||||
equal(childMsg, 'ok')
|
||||
equal(msg, 'test')
|
||||
})
|
||||
|
||||
test('customLevels property child bindings does not get logged', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino(stream).child({
|
||||
childMsg: 'ok'
|
||||
}, {
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
})
|
||||
|
||||
logger.foo('test')
|
||||
const { customLevels } = await once(stream, 'data')
|
||||
equal(customLevels, undefined)
|
||||
})
|
||||
|
||||
test('throws when specifying pre-existing parent labels via child bindings', async ({ throws }) => {
|
||||
const stream = sink()
|
||||
throws(() => pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, stream).child({}, {
|
||||
customLevels: {
|
||||
foo: 45
|
||||
}
|
||||
}), 'levels cannot be overridden')
|
||||
})
|
||||
|
||||
test('throws when specifying pre-existing parent values via child bindings', async ({ throws }) => {
|
||||
const stream = sink()
|
||||
throws(() => pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, stream).child({}, {
|
||||
customLevels: {
|
||||
bar: 35
|
||||
}
|
||||
}), 'pre-existing level values cannot be used for new levels')
|
||||
})
|
||||
|
||||
test('throws when specifying core values via child bindings', async ({ throws }) => {
|
||||
const stream = sink()
|
||||
throws(() => pino(stream).child({}, {
|
||||
customLevels: {
|
||||
foo: 30
|
||||
}
|
||||
}), 'pre-existing level values cannot be used for new levels')
|
||||
})
|
||||
|
||||
test('throws when useOnlyCustomLevels is set true without customLevels', async ({ throws }) => {
|
||||
const stream = sink()
|
||||
throws(() => pino({
|
||||
useOnlyCustomLevels: true
|
||||
}, stream), 'customLevels is required if useOnlyCustomLevels is set true')
|
||||
})
|
||||
|
||||
test('custom level on one instance does not affect other instances', async ({ equal }) => {
|
||||
pino({
|
||||
customLevels: {
|
||||
foo: 37
|
||||
}
|
||||
})
|
||||
equal(typeof pino().foo, 'undefined')
|
||||
})
|
||||
|
||||
test('setting level below or at custom level will successfully log', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ customLevels: { foo: 35 } }, stream)
|
||||
instance.level = 'foo'
|
||||
instance.info('nope')
|
||||
instance.foo('bar')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'bar')
|
||||
})
|
||||
|
||||
test('custom level below level threshold will not log', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ customLevels: { foo: 15 } }, stream)
|
||||
instance.level = 'info'
|
||||
instance.info('bar')
|
||||
instance.foo('nope')
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(msg, 'bar')
|
||||
})
|
||||
|
||||
test('does not share custom level state across siblings', async ({ doesNotThrow }) => {
|
||||
const stream = sink()
|
||||
const logger = pino(stream)
|
||||
logger.child({}, {
|
||||
customLevels: { foo: 35 }
|
||||
})
|
||||
doesNotThrow(() => {
|
||||
logger.child({}, {
|
||||
customLevels: { foo: 35 }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('custom level does not affect the levels serializer', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
foo: 35,
|
||||
bar: 45
|
||||
},
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { priority: number }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
logger.foo('test')
|
||||
const { priority } = await once(stream, 'data')
|
||||
equal(priority, 35)
|
||||
})
|
||||
|
||||
test('When useOnlyCustomLevels is set to true, the level formatter should only get custom levels', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
customLevels: {
|
||||
answer: 42
|
||||
},
|
||||
useOnlyCustomLevels: true,
|
||||
level: 42,
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
equal(label, 'answer')
|
||||
equal(number, 42)
|
||||
return { level: number }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
logger.answer('test')
|
||||
const { level } = await once(stream, 'data')
|
||||
equal(level, 42)
|
||||
})
|
||||
@@ -0,0 +1,398 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const os = require('os')
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
const level = 50
|
||||
const name = 'error'
|
||||
|
||||
test('err is serialized with additional properties set on the Error object', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const err = Object.assign(new Error('myerror'), { foo: 'bar' })
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name](err)
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
foo: err.foo
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('type should be detected based on constructor', async ({ ok, same }) => {
|
||||
class Bar extends Error {}
|
||||
const stream = sink()
|
||||
const err = new Bar('myerror')
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name](err)
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Bar',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('type, message and stack should be first level properties', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const err = Object.assign(new Error('foo'), { foo: 'bar' })
|
||||
const instance = pino(stream)
|
||||
instance.level = name
|
||||
instance[name](err)
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
foo: err.foo
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('err serializer', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const err = Object.assign(new Error('myerror'), { foo: 'bar' })
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
err: pino.stdSerializers.err
|
||||
}
|
||||
}, stream)
|
||||
|
||||
instance.level = name
|
||||
instance[name]({ err })
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
foo: err.foo
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('an error with statusCode property is not confused for a http response', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const err = Object.assign(new Error('StatusCodeErr'), { statusCode: 500 })
|
||||
const instance = pino(stream)
|
||||
|
||||
instance.level = name
|
||||
instance[name](err)
|
||||
const result = await once(stream, 'data')
|
||||
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
statusCode: err.statusCode
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('stack is omitted if it is not set on err', t => {
|
||||
t.plan(2)
|
||||
const err = new Error('myerror')
|
||||
delete err.stack
|
||||
const instance = pino(sink(function (chunk, enc, cb) {
|
||||
t.ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
t.equal(chunk.hasOwnProperty('stack'), false)
|
||||
cb()
|
||||
}))
|
||||
|
||||
instance.level = name
|
||||
instance[name](err)
|
||||
})
|
||||
|
||||
test('correctly ignores toString on errors', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
err.toString = () => undefined
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
test: 'this'
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('assign mixin()', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
return { hello: 'world' }
|
||||
}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
hello: 'world',
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('no err serializer', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
serializers: {}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('empty serializer', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
err () {}
|
||||
}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('assign mixin()', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
return { hello: 'world' }
|
||||
}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
hello: 'world',
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('no err serializer', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
serializers: {}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
err: {
|
||||
type: 'Error',
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('empty serializer', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
err () {}
|
||||
}
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly adds error information when nestedKey is used', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
err.toString = () => undefined
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
test: 'this',
|
||||
nestedKey: 'obj'
|
||||
}, stream)
|
||||
instance.fatal(err)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
obj: {
|
||||
err: {
|
||||
type: 'Error',
|
||||
stack: err.stack,
|
||||
message: err.message
|
||||
}
|
||||
},
|
||||
msg: err.message
|
||||
})
|
||||
})
|
||||
|
||||
test('correctly adds msg on error when nestedKey is used', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
err.toString = () => undefined
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
test: 'this',
|
||||
nestedKey: 'obj'
|
||||
}, stream)
|
||||
instance.fatal(err, 'msg message')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
obj: {
|
||||
err: {
|
||||
type: 'Error',
|
||||
stack: err.stack,
|
||||
message: err.message
|
||||
}
|
||||
},
|
||||
msg: 'msg message'
|
||||
})
|
||||
})
|
||||
|
||||
test('msg should take precedence over error message on mergingObject', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.error({ msg: 'my message', err })
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 50,
|
||||
err: {
|
||||
type: 'Error',
|
||||
stack: err.stack,
|
||||
message: err.message
|
||||
},
|
||||
msg: 'my message'
|
||||
})
|
||||
})
|
||||
|
||||
test('considers messageKey when giving msg precedence over error', async ({ same }) => {
|
||||
const err = new Error('myerror')
|
||||
const stream = sink()
|
||||
const instance = pino({ messageKey: 'message' }, stream)
|
||||
instance.error({ message: 'my message', err })
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 50,
|
||||
err: {
|
||||
type: 'Error',
|
||||
stack: err.stack,
|
||||
message: err.message
|
||||
},
|
||||
message: 'my message'
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const stdSerializers = require('pino-std-serializers')
|
||||
const pino = require('../')
|
||||
|
||||
test('set the errorKey with error serializer', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const errorKey = 'error'
|
||||
const instance = pino({
|
||||
errorKey,
|
||||
serializers: { [errorKey]: stdSerializers.err }
|
||||
}, stream)
|
||||
instance.error(new ReferenceError('test'))
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o[errorKey], 'object')
|
||||
equal(o[errorKey].type, 'ReferenceError')
|
||||
equal(o[errorKey].message, 'test')
|
||||
equal(typeof o[errorKey].stack, 'string')
|
||||
})
|
||||
|
||||
test('set the errorKey without error serializer', async ({ equal, same }) => {
|
||||
const stream = sink()
|
||||
const errorKey = 'error'
|
||||
const instance = pino({
|
||||
errorKey
|
||||
}, stream)
|
||||
instance.error(new ReferenceError('test'))
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o[errorKey], 'object')
|
||||
equal(o[errorKey].type, 'ReferenceError')
|
||||
equal(o[errorKey].message, 'test')
|
||||
equal(typeof o[errorKey].stack, 'string')
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
function testEscape (ch, key) {
|
||||
test('correctly escape ' + ch, async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
name: 'hello'
|
||||
}, stream)
|
||||
instance.fatal('this contains ' + key)
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
name: 'hello',
|
||||
msg: 'this contains ' + key
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
testEscape('\\n', '\n')
|
||||
testEscape('\\/', '/')
|
||||
testEscape('\\\\', '\\')
|
||||
testEscape('\\r', '\r')
|
||||
testEscape('\\t', '\t')
|
||||
testEscape('\\b', '\b')
|
||||
|
||||
const toEscape = [
|
||||
'\u0000', // NUL Null character
|
||||
'\u0001', // SOH Start of Heading
|
||||
'\u0002', // STX Start of Text
|
||||
'\u0003', // ETX End-of-text character
|
||||
'\u0004', // EOT End-of-transmission character
|
||||
'\u0005', // ENQ Enquiry character
|
||||
'\u0006', // ACK Acknowledge character
|
||||
'\u0007', // BEL Bell character
|
||||
'\u0008', // BS Backspace
|
||||
'\u0009', // HT Horizontal tab
|
||||
'\u000A', // LF Line feed
|
||||
'\u000B', // VT Vertical tab
|
||||
'\u000C', // FF Form feed
|
||||
'\u000D', // CR Carriage return
|
||||
'\u000E', // SO Shift Out
|
||||
'\u000F', // SI Shift In
|
||||
'\u0010', // DLE Data Link Escape
|
||||
'\u0011', // DC1 Device Control 1
|
||||
'\u0012', // DC2 Device Control 2
|
||||
'\u0013', // DC3 Device Control 3
|
||||
'\u0014', // DC4 Device Control 4
|
||||
'\u0015', // NAK Negative-acknowledge character
|
||||
'\u0016', // SYN Synchronous Idle
|
||||
'\u0017', // ETB End of Transmission Block
|
||||
'\u0018', // CAN Cancel character
|
||||
'\u0019', // EM End of Medium
|
||||
'\u001A', // SUB Substitute character
|
||||
'\u001B', // ESC Escape character
|
||||
'\u001C', // FS File Separator
|
||||
'\u001D', // GS Group Separator
|
||||
'\u001E', // RS Record Separator
|
||||
'\u001F' // US Unit Separator
|
||||
]
|
||||
|
||||
toEscape.forEach((key) => {
|
||||
testEscape(JSON.stringify(key), key)
|
||||
})
|
||||
|
||||
test('correctly escape `hello \\u001F world \\n \\u0022`', async ({ same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
name: 'hello'
|
||||
}, stream)
|
||||
instance.fatal('hello \u001F world \n \u0022')
|
||||
const result = await once(stream, 'data')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 60,
|
||||
name: 'hello',
|
||||
msg: 'hello \u001F world \n \u0022'
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import t from 'tap'
|
||||
import pino from '../../pino.js'
|
||||
import helper from '../helper.js'
|
||||
|
||||
const { sink, check, once } = helper
|
||||
|
||||
t.test('esm support', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info('hello world')
|
||||
check(equal, await once(stream, 'data'), 30, 'hello world')
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const t = require('tap')
|
||||
const semver = require('semver')
|
||||
|
||||
const { isYarnPnp } = require('../helper')
|
||||
|
||||
if (!semver.satisfies(process.versions.node, '^13.3.0 || ^12.10.0 || >= 14.0.0') || isYarnPnp) {
|
||||
t.skip('Skip esm because not supported by Node')
|
||||
} else {
|
||||
// Node v8 throw a `SyntaxError: Unexpected token import`
|
||||
// even if this branch is never touch in the code,
|
||||
// by using `eval` we can avoid this issue.
|
||||
// eslint-disable-next-line
|
||||
new Function('module', 'return import(module)')('./esm.mjs').catch((err) => {
|
||||
process.nextTick(() => {
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (!semver.satisfies(process.versions.node, '>= 14.13.0 || ^12.20.0') || isYarnPnp) {
|
||||
t.skip('Skip named exports because not supported by Node')
|
||||
} else {
|
||||
// Node v8 throw a `SyntaxError: Unexpected token import`
|
||||
// even if this branch is never touch in the code,
|
||||
// by using `eval` we can avoid this issue.
|
||||
// eslint-disable-next-line
|
||||
new Function('module', 'return import(module)')('./named-exports.mjs').catch((err) => {
|
||||
process.nextTick(() => {
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { tmpdir, hostname } from 'os'
|
||||
import t from 'tap'
|
||||
import { sink, check, once, watchFileCreated, file } from '../helper.js'
|
||||
import { pino, destination } from '../../pino.js'
|
||||
import { join } from 'path'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
t.test('named exports support', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info('hello world')
|
||||
check(equal, await once(stream, 'data'), 30, 'hello world')
|
||||
})
|
||||
|
||||
t.test('destination', async ({ same }) => {
|
||||
const tmp = file()
|
||||
const instance = pino(destination(tmp))
|
||||
instance.info('hello')
|
||||
await watchFileCreated(tmp)
|
||||
const result = JSON.parse(readFileSync(tmp).toString())
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid: process.pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { join } = require('path')
|
||||
const execa = require('execa')
|
||||
const writer = require('flush-write-stream')
|
||||
const { once } = require('./helper')
|
||||
|
||||
// https://github.com/pinojs/pino/issues/542
|
||||
test('pino.destination log everything when calling process.exit(0)', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'destination-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
not(actual.match(/hello/), null)
|
||||
not(actual.match(/world/), null)
|
||||
})
|
||||
|
||||
test('pino with no args log everything when calling process.exit(0)', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'default-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
not(actual.match(/hello/), null)
|
||||
not(actual.match(/world/), null)
|
||||
})
|
||||
|
||||
test('sync false logs everything when calling process.exit(0)', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
not(actual.match(/hello/), null)
|
||||
not(actual.match(/world/), null)
|
||||
})
|
||||
|
||||
test('sync false logs everything when calling flushSync', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'syncfalse-flush-exit.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
not(actual.match(/hello/), null)
|
||||
not(actual.match(/world/), null)
|
||||
})
|
||||
|
||||
test('transports exits gracefully when logging in exit', async ({ equal }) => {
|
||||
const child = execa(process.argv[0], [join(__dirname, 'fixtures', 'transport-with-on-exit.js')])
|
||||
child.stdout.resume()
|
||||
|
||||
const code = await once(child, 'close')
|
||||
|
||||
equal(code, 0)
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
|
||||
const pino = require('../../..')()
|
||||
|
||||
pino.info('hello world')
|
||||
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
|
||||
const pino = require('../../..')
|
||||
const logger = pino(pino.destination())
|
||||
|
||||
logger.info('hello world')
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
|
||||
const pino = require('../../..')
|
||||
const logger = pino(pino.destination({ sync: false }))
|
||||
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
logger.info('hello world')
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const { Writable } = require('stream')
|
||||
|
||||
module.exports = (options) => {
|
||||
const myTransportStream = new Writable({
|
||||
autoDestroy: true,
|
||||
write (chunk, enc, cb) {
|
||||
// apply a transform and send to stdout
|
||||
console.log(chunk.toString().toUpperCase())
|
||||
cb()
|
||||
}
|
||||
})
|
||||
return myTransportStream
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const logger = pino()
|
||||
logger.info('hello')
|
||||
logger.info('world')
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,8 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const logger = pino({}, pino.destination(1))
|
||||
logger.info('hello')
|
||||
logger.info('world')
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable no-eval */
|
||||
|
||||
eval(`
|
||||
const pino = require('../../../')
|
||||
|
||||
const logger = pino(
|
||||
pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
)
|
||||
|
||||
logger.info('done!')
|
||||
`)
|
||||
@@ -0,0 +1,3 @@
|
||||
const file1 = require("./file1.js")
|
||||
|
||||
file1()
|
||||
@@ -0,0 +1,3 @@
|
||||
const file12 = require("./file12.js")
|
||||
|
||||
file12()
|
||||
@@ -0,0 +1,5 @@
|
||||
const file2 = require("./file2.js")
|
||||
|
||||
module.exports = function () {
|
||||
file2()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file11 = require("./file11.js")
|
||||
|
||||
module.exports = function () {
|
||||
file11()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file12 = require("./file12.js")
|
||||
|
||||
module.exports = function () {
|
||||
file12()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file13 = require("./file13.js")
|
||||
|
||||
module.exports = function () {
|
||||
file13()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file14 = require("./file14.js")
|
||||
|
||||
module.exports = function () {
|
||||
file14()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const pino = require("../../../../");
|
||||
|
||||
module.exports = function() {
|
||||
const logger = pino(
|
||||
pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
)
|
||||
|
||||
logger.info('done!')
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file3 = require("./file3.js")
|
||||
|
||||
module.exports = function () {
|
||||
file3()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file4 = require("./file4.js")
|
||||
|
||||
module.exports = function () {
|
||||
file4()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file5 = require("./file5.js")
|
||||
|
||||
module.exports = function () {
|
||||
file5()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file6 = require("./file6.js")
|
||||
|
||||
module.exports = function () {
|
||||
file6()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file7 = require("./file7.js")
|
||||
|
||||
module.exports = function () {
|
||||
file7()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file8 = require("./file8.js")
|
||||
|
||||
module.exports = function () {
|
||||
file8()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file9 = require("./file9.js")
|
||||
|
||||
module.exports = function () {
|
||||
file9()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
const file10 = require("./file10.js")
|
||||
|
||||
module.exports = function () {
|
||||
file10()
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
const { Writable } = require('stream')
|
||||
|
||||
module.exports = () => {
|
||||
return new Writable({
|
||||
autoDestroy: true,
|
||||
write (chunk, enc, cb) {
|
||||
cb()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../../'))
|
||||
const log = pino({ prettyPrint: true })
|
||||
const obj = Object.create(null)
|
||||
Object.assign(obj, { foo: 'bar' })
|
||||
log.info(obj, 'hello')
|
||||
@@ -0,0 +1,11 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
|
||||
const write = process.stdout.write.bind(process.stdout)
|
||||
process.stdout.write = function (chunk) {
|
||||
write('hack ' + chunk)
|
||||
}
|
||||
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('../../'))()
|
||||
pino.info('me')
|
||||
@@ -0,0 +1,6 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const asyncLogger = pino(pino.destination({ sync: false })).child({ hello: 'world' })
|
||||
asyncLogger.info('h')
|
||||
@@ -0,0 +1,9 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const dest = pino.destination({ dest: 1, minLength: 4096, sync: false })
|
||||
const logger = pino({}, dest)
|
||||
logger.info('hello')
|
||||
logger.info('world')
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,10 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const dest = pino.destination({ dest: 1, minLength: 4096, sync: false })
|
||||
const logger = pino({}, dest)
|
||||
logger.info('hello')
|
||||
logger.info('world')
|
||||
dest.flushSync()
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,6 @@
|
||||
global.process = { __proto__: process, pid: 123456 }
|
||||
Date.now = function () { return 1459875739796 }
|
||||
require('os').hostname = function () { return 'abcdefghijklmnopqr' }
|
||||
const pino = require(require.resolve('./../../'))
|
||||
const asyncLogger = pino(pino.destination({ minLength: 4096, sync: false }))
|
||||
asyncLogger.info('h')
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const { once } = require('events')
|
||||
const { Transform } = require('stream')
|
||||
|
||||
async function run (opts) {
|
||||
if (!opts.destination) throw new Error('kaboom')
|
||||
const stream = fs.createWriteStream(opts.destination)
|
||||
await once(stream, 'open')
|
||||
const t = new Transform({
|
||||
transform (chunk, enc, cb) {
|
||||
setImmediate(cb, null, chunk.toString().toUpperCase())
|
||||
}
|
||||
})
|
||||
t.pipe(stream)
|
||||
return t
|
||||
}
|
||||
|
||||
module.exports = run
|
||||
@@ -0,0 +1,13 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const { once } = require('events')
|
||||
|
||||
async function run (opts) {
|
||||
if (!opts.destination) throw new Error('kaboom')
|
||||
const stream = fs.createWriteStream(opts.destination)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
|
||||
module.exports = run
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { once } from 'events'
|
||||
|
||||
export default async function run (opts) {
|
||||
const stream = createWriteStream(opts.destination)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
target: './to-file-transport-with-transform.js',
|
||||
options: {
|
||||
destination: process.argv[2]
|
||||
}
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('Hello')
|
||||
|
||||
logger.info('World')
|
||||
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('Hello')
|
||||
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
transport.on('ready', function () {
|
||||
logger.info('Hello')
|
||||
process.exit(0)
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const { join } = require('path')
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, 'transport-worker.js')
|
||||
})
|
||||
const logger = pino(transport)
|
||||
logger.info('Hello')
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: 'pino/file',
|
||||
options: {
|
||||
destination: process.argv[2]
|
||||
}
|
||||
}]
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
const toWrite = 1000000
|
||||
transport.on('ready', run)
|
||||
|
||||
let total = 0
|
||||
|
||||
function run () {
|
||||
if (total++ === 8) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < toWrite; i++) {
|
||||
logger.info(`hello ${i}`)
|
||||
}
|
||||
transport.once('drain', run)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
const pino = require('../..')
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '1' }
|
||||
})
|
||||
const logger = pino(transport)
|
||||
logger.info('Hello')
|
||||
@@ -0,0 +1,21 @@
|
||||
'use strict'
|
||||
|
||||
const build = require('pino-abstract-transport')
|
||||
const { pipeline, Transform } = require('stream')
|
||||
module.exports = (options) => {
|
||||
return build(function (source) {
|
||||
const myTransportStream = new Transform({
|
||||
autoDestroy: true,
|
||||
objectMode: true,
|
||||
transform (chunk, enc, cb) {
|
||||
chunk.service = 'pino'
|
||||
this.push(JSON.stringify(chunk))
|
||||
cb()
|
||||
}
|
||||
})
|
||||
pipeline(source, myTransportStream, () => {})
|
||||
return myTransportStream
|
||||
}, {
|
||||
enablePipelining: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict'
|
||||
|
||||
const build = require('pino-abstract-transport')
|
||||
const { pipeline, Transform } = require('stream')
|
||||
module.exports = () => {
|
||||
return build(function (source) {
|
||||
const myTransportStream = new Transform({
|
||||
autoDestroy: true,
|
||||
objectMode: true,
|
||||
transform (chunk, enc, cb) {
|
||||
const {
|
||||
time,
|
||||
level,
|
||||
[source.messageKey]: body,
|
||||
[source.errorKey]: error,
|
||||
...attributes
|
||||
} = chunk
|
||||
this.push(JSON.stringify({
|
||||
severityText: source.levels.labels[level],
|
||||
body,
|
||||
attributes,
|
||||
...(error && { error })
|
||||
}))
|
||||
cb()
|
||||
}
|
||||
})
|
||||
pipeline(source, myTransportStream, () => {})
|
||||
return myTransportStream
|
||||
}, {
|
||||
enablePipelining: true,
|
||||
expectPinoConfig: true
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
const pino = require('../..')
|
||||
const log = pino({
|
||||
transport: {
|
||||
target: 'pino/file',
|
||||
options: { destination: 1 }
|
||||
}
|
||||
})
|
||||
log.info('hello world!')
|
||||
process.on('exit', (code) => {
|
||||
log.info('Exiting peacefully')
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
|
||||
const { parentPort, workerData } = require('worker_threads')
|
||||
const { Writable } = require('stream')
|
||||
|
||||
module.exports = (options) => {
|
||||
const myTransportStream = new Writable({
|
||||
autoDestroy: true,
|
||||
write (chunk, enc, cb) {
|
||||
parentPort.postMessage({
|
||||
code: 'EVENT',
|
||||
name: 'workerData',
|
||||
args: [workerData]
|
||||
})
|
||||
cb()
|
||||
}
|
||||
})
|
||||
return myTransportStream
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const { Writable } = require('stream')
|
||||
const fs = require('fs')
|
||||
module.exports = (options) => {
|
||||
const myTransportStream = new Writable({
|
||||
autoDestroy: true,
|
||||
write (chunk, enc, cb) {
|
||||
// Bypass console.log() to avoid flakiness
|
||||
fs.writeSync(1, chunk.toString())
|
||||
cb()
|
||||
}
|
||||
})
|
||||
return myTransportStream
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
completelyUnrelatedProperty: 'Just a very incorrect transport worker implementation'
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const { once } = require('events')
|
||||
|
||||
async function run (opts) {
|
||||
const stream = fs.createWriteStream(opts.destination)
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
|
||||
module.exports = run
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "transport",
|
||||
"version": "0.0.1",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as fs from 'fs'
|
||||
import { once } from 'events'
|
||||
import { Transform } from 'stream'
|
||||
|
||||
async function run (opts: { destination?: fs.PathLike }): Promise<Transform> {
|
||||
if (!opts.destination) throw new Error('kaboom')
|
||||
const stream = fs.createWriteStream(opts.destination)
|
||||
await once(stream, 'open')
|
||||
const t = new Transform({
|
||||
transform (chunk, enc, cb) {
|
||||
setImmediate(cb, null, chunk.toString().toUpperCase())
|
||||
}
|
||||
})
|
||||
t.pipe(stream)
|
||||
return t
|
||||
}
|
||||
|
||||
export default run
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as fs from 'fs'
|
||||
import { once } from 'events'
|
||||
|
||||
async function run (opts: { destination?: fs.PathLike }): Promise<fs.WriteStream> {
|
||||
if (!opts.destination) throw new Error('kaboom')
|
||||
const stream = fs.createWriteStream(opts.destination, { encoding: 'utf8' })
|
||||
await once(stream, 'open')
|
||||
return stream
|
||||
}
|
||||
|
||||
export default run
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const execa = require('execa')
|
||||
const fs = require('fs')
|
||||
|
||||
const existsSync = fs.existsSync
|
||||
const stat = fs.promises.stat
|
||||
|
||||
// Hardcoded parameters
|
||||
const esVersions = ['es5', 'es6', 'es2017', 'esnext']
|
||||
const filesToTranspile = ['to-file-transport.ts']
|
||||
|
||||
async function transpile () {
|
||||
process.chdir(__dirname)
|
||||
|
||||
for (const sourceFileName of filesToTranspile) {
|
||||
const sourceStat = await stat(sourceFileName)
|
||||
|
||||
for (const esVersion of esVersions) {
|
||||
const intermediateFileName = sourceFileName.replace(/\.ts$/, '.js')
|
||||
const targetFileName = sourceFileName.replace(/\.ts$/, `.${esVersion}.cjs`)
|
||||
|
||||
const shouldTranspile = !existsSync(targetFileName) || (await stat(targetFileName)).mtimeMs < sourceStat.mtimeMs
|
||||
|
||||
if (shouldTranspile) {
|
||||
await execa('tsc', ['--target', esVersion, '--module', 'commonjs', sourceFileName])
|
||||
await execa('mv', [intermediateFileName, targetFileName])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transpile().catch(err => {
|
||||
process.exitCode = 1
|
||||
throw err
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
import pino from '../../..'
|
||||
import { join } from 'path'
|
||||
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, 'to-file-transport-with-transform.ts'),
|
||||
options: {
|
||||
destination: process.argv[2]
|
||||
}
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('Hello')
|
||||
logger.info('World')
|
||||
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,10 @@
|
||||
import pino from '../../..'
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
logger.info('Hello')
|
||||
|
||||
process.exit(0)
|
||||
@@ -0,0 +1,11 @@
|
||||
import pino from '../../..'
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file'
|
||||
})
|
||||
const logger = pino(transport)
|
||||
|
||||
transport.on('ready', function () {
|
||||
logger.info('Hello')
|
||||
process.exit(0)
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
import { join } from 'path'
|
||||
import pino from '../../..'
|
||||
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, 'transport-worker.ts')
|
||||
})
|
||||
const logger = pino(transport)
|
||||
logger.info('Hello')
|
||||
@@ -0,0 +1,8 @@
|
||||
import pino from '../../..'
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination: '1' }
|
||||
})
|
||||
const logger = pino(transport)
|
||||
logger.info('Hello')
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Writable } from 'stream'
|
||||
|
||||
export default (): Writable => {
|
||||
const myTransportStream = new Writable({
|
||||
autoDestroy: true,
|
||||
write (chunk, _enc, cb) {
|
||||
console.log(chunk.toString())
|
||||
cb()
|
||||
},
|
||||
defaultEncoding: 'utf8'
|
||||
})
|
||||
|
||||
return myTransportStream
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
'use strict'
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const { hostname } = require('os')
|
||||
const { join } = require('path')
|
||||
const { readFile } = require('fs').promises
|
||||
const { test } = require('tap')
|
||||
const { sink, once, watchFileCreated, file } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
test('level formatter', async ({ match }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info('hello world')
|
||||
match(await o, {
|
||||
log: {
|
||||
level: 'info'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('bindings formatter', async ({ match }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info('hello world')
|
||||
match(await o, {
|
||||
process: {
|
||||
pid: process.pid
|
||||
},
|
||||
host: {
|
||||
name: hostname()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('no bindings formatter', async ({ match, notOk }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
bindings (bindings) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info('hello world')
|
||||
const log = await o
|
||||
notOk(log.hasOwnProperty('pid'))
|
||||
notOk(log.hasOwnProperty('hostname'))
|
||||
match(log, { msg: 'hello world' })
|
||||
})
|
||||
|
||||
test('log formatter', async ({ match, equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
log (obj) {
|
||||
equal(obj.hasOwnProperty('msg'), false)
|
||||
return { hello: 'world', ...obj }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info({ foo: 'bar', nested: { object: true } }, 'hello world')
|
||||
match(await o, {
|
||||
hello: 'world',
|
||||
foo: 'bar',
|
||||
nested: { object: true }
|
||||
})
|
||||
})
|
||||
|
||||
test('Formatters combined', async ({ match }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
},
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
log (obj) {
|
||||
return { hello: 'world', ...obj }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info({ foo: 'bar', nested: { object: true } }, 'hello world')
|
||||
match(await o, {
|
||||
log: {
|
||||
level: 'info'
|
||||
},
|
||||
process: {
|
||||
pid: process.pid
|
||||
},
|
||||
host: {
|
||||
name: hostname()
|
||||
},
|
||||
hello: 'world',
|
||||
foo: 'bar',
|
||||
nested: { object: true }
|
||||
})
|
||||
})
|
||||
|
||||
test('Formatters in child logger', async ({ match }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
},
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
log (obj) {
|
||||
return { hello: 'world', ...obj }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const child = logger.child({
|
||||
foo: 'bar',
|
||||
nested: { object: true }
|
||||
}, {
|
||||
formatters: {
|
||||
bindings (bindings) {
|
||||
return { ...bindings, faz: 'baz' }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const o = once(stream, 'data')
|
||||
child.info('hello world')
|
||||
match(await o, {
|
||||
log: {
|
||||
level: 'info'
|
||||
},
|
||||
process: {
|
||||
pid: process.pid
|
||||
},
|
||||
host: {
|
||||
name: hostname()
|
||||
},
|
||||
hello: 'world',
|
||||
foo: 'bar',
|
||||
nested: { object: true },
|
||||
faz: 'baz'
|
||||
})
|
||||
})
|
||||
|
||||
test('Formatters without bindings in child logger', async ({ match }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label
|
||||
}
|
||||
}
|
||||
},
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
log (obj) {
|
||||
return { hello: 'world', ...obj }
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const child = logger.child({
|
||||
foo: 'bar',
|
||||
nested: { object: true }
|
||||
}, {
|
||||
formatters: {
|
||||
log (obj) {
|
||||
return { other: 'stuff', ...obj }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const o = once(stream, 'data')
|
||||
child.info('hello world')
|
||||
match(await o, {
|
||||
log: {
|
||||
level: 'info'
|
||||
},
|
||||
process: {
|
||||
pid: process.pid
|
||||
},
|
||||
host: {
|
||||
name: hostname()
|
||||
},
|
||||
foo: 'bar',
|
||||
other: 'stuff',
|
||||
nested: { object: true }
|
||||
})
|
||||
})
|
||||
|
||||
test('elastic common schema format', async ({ match, type }) => {
|
||||
const stream = sink()
|
||||
const ecs = {
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return {
|
||||
log: {
|
||||
level: label,
|
||||
logger: 'pino'
|
||||
}
|
||||
}
|
||||
},
|
||||
bindings (bindings) {
|
||||
return {
|
||||
process: {
|
||||
pid: bindings.pid
|
||||
},
|
||||
host: {
|
||||
name: bindings.hostname
|
||||
}
|
||||
}
|
||||
},
|
||||
log (obj) {
|
||||
return { ecs: { version: '1.4.0' }, ...obj }
|
||||
}
|
||||
},
|
||||
messageKey: 'message',
|
||||
timestamp: () => `,"@timestamp":"${new Date(Date.now()).toISOString()}"`
|
||||
}
|
||||
|
||||
const logger = pino({ ...ecs }, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info({ foo: 'bar' }, 'hello world')
|
||||
const log = await o
|
||||
type(log['@timestamp'], 'string')
|
||||
match(log, {
|
||||
log: { level: 'info', logger: 'pino' },
|
||||
process: { pid: process.pid },
|
||||
host: { name: hostname() },
|
||||
ecs: { version: '1.4.0' },
|
||||
foo: 'bar',
|
||||
message: 'hello world'
|
||||
})
|
||||
})
|
||||
|
||||
test('formatter with transport', async ({ match, equal }) => {
|
||||
const destination = file()
|
||||
const logger = pino({
|
||||
formatters: {
|
||||
log (obj) {
|
||||
equal(obj.hasOwnProperty('msg'), false)
|
||||
return { hello: 'world', ...obj }
|
||||
}
|
||||
},
|
||||
transport: {
|
||||
targets: [
|
||||
{
|
||||
target: join(__dirname, 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
logger.info({ foo: 'bar', nested: { object: true } }, 'hello world')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
match(result, {
|
||||
hello: 'world',
|
||||
foo: 'bar',
|
||||
nested: { object: true }
|
||||
})
|
||||
})
|
||||
|
||||
test('throws when custom level formatter is used with transport.targets', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({
|
||||
formatters: {
|
||||
level (label) {
|
||||
return label
|
||||
}
|
||||
},
|
||||
transport: {
|
||||
targets: [
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: 'foo.log' }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
Error('option.transport.targets do not allow custom level formatters'))
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PathLike } from 'fs'
|
||||
|
||||
export declare function watchFileCreated(filename: PathLike): Promise<void>
|
||||
export declare function watchForWrite(filename: PathLike, testString: string): Promise<void>
|
||||
@@ -0,0 +1,128 @@
|
||||
'use strict'
|
||||
|
||||
const crypto = require('crypto')
|
||||
const os = require('os')
|
||||
const writer = require('flush-write-stream')
|
||||
const split = require('split2')
|
||||
const { existsSync, readFileSync, statSync, unlinkSync } = require('fs')
|
||||
const pid = process.pid
|
||||
const hostname = os.hostname()
|
||||
const t = require('tap')
|
||||
const { join } = require('path')
|
||||
const { tmpdir } = os
|
||||
|
||||
const isWin = process.platform === 'win32'
|
||||
const isYarnPnp = process.versions.pnp !== undefined
|
||||
|
||||
function getPathToNull () {
|
||||
return isWin ? '\\\\.\\NUL' : '/dev/null'
|
||||
}
|
||||
|
||||
function once (emitter, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (name !== 'error') emitter.once('error', reject)
|
||||
emitter.once(name, (...args) => {
|
||||
emitter.removeListener('error', reject)
|
||||
resolve(...args)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function sink (func) {
|
||||
const result = split((data) => {
|
||||
try {
|
||||
return JSON.parse(data)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
console.log(data)
|
||||
}
|
||||
})
|
||||
if (func) result.pipe(writer.obj(func))
|
||||
return result
|
||||
}
|
||||
|
||||
function check (is, chunk, level, msg) {
|
||||
is(new Date(chunk.time) <= new Date(), true, 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
is(chunk.pid, pid)
|
||||
is(chunk.hostname, hostname)
|
||||
is(chunk.level, level)
|
||||
is(chunk.msg, msg)
|
||||
}
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
function watchFileCreated (filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const TIMEOUT = process.env.PINO_TEST_WAIT_WATCHFILE_TIMEOUT || 10000
|
||||
const INTERVAL = 100
|
||||
const threshold = TIMEOUT / INTERVAL
|
||||
let counter = 0
|
||||
const interval = setInterval(() => {
|
||||
const exists = existsSync(filename)
|
||||
// On some CI runs file is created but not filled
|
||||
if (exists && statSync(filename).size !== 0) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
} else if (counter <= threshold) {
|
||||
counter++
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
reject(new Error(
|
||||
`${filename} hasn't been created within ${TIMEOUT} ms. ` +
|
||||
(exists ? 'File exist, but still empty.' : 'File not yet created.')
|
||||
))
|
||||
}
|
||||
}, INTERVAL)
|
||||
})
|
||||
}
|
||||
|
||||
function watchForWrite (filename, testString) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const TIMEOUT = process.env.PINO_TEST_WAIT_WRITE_TIMEOUT || 10000
|
||||
const INTERVAL = 100
|
||||
const threshold = TIMEOUT / INTERVAL
|
||||
let counter = 0
|
||||
const interval = setInterval(() => {
|
||||
if (readFileSync(filename).includes(testString)) {
|
||||
clearInterval(interval)
|
||||
resolve()
|
||||
} else if (counter <= threshold) {
|
||||
counter++
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
reject(new Error(`'${testString}' hasn't been written to ${filename} within ${TIMEOUT} ms.`))
|
||||
}
|
||||
}, INTERVAL)
|
||||
})
|
||||
}
|
||||
|
||||
let files = []
|
||||
|
||||
function file () {
|
||||
const hash = crypto.randomBytes(12).toString('hex')
|
||||
const file = join(tmpdir(), `pino-${pid}-${hash}`)
|
||||
files.push(file)
|
||||
return file
|
||||
}
|
||||
|
||||
process.on('beforeExit', () => {
|
||||
if (files.length === 0) return
|
||||
t.comment('unlink files')
|
||||
for (const file of files) {
|
||||
try {
|
||||
t.comment(`unliking ${file}`)
|
||||
unlinkSync(file)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
files = []
|
||||
t.comment('unlink completed')
|
||||
})
|
||||
|
||||
module.exports = { getPathToNull, sink, check, once, sleep, watchFileCreated, watchForWrite, isWin, isYarnPnp, file }
|
||||
@@ -0,0 +1,97 @@
|
||||
'use strict'
|
||||
|
||||
const tap = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
tap.test('log method hook', t => {
|
||||
t.test('gets invoked', async t => {
|
||||
t.plan(8)
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
hooks: {
|
||||
logMethod (args, method, level) {
|
||||
t.type(args, Array)
|
||||
t.type(level, 'number')
|
||||
t.equal(args.length, 3)
|
||||
t.equal(level, this.levels.values.info)
|
||||
t.same(args, ['a', 'b', 'c'])
|
||||
|
||||
t.type(method, Function)
|
||||
t.equal(method.name, 'LOG')
|
||||
|
||||
method.apply(this, [args.join('-')])
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.info('a', 'b', 'c')
|
||||
t.match(await o, { msg: 'a-b-c' })
|
||||
})
|
||||
|
||||
t.test('fatal method invokes hook', async t => {
|
||||
t.plan(2)
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
hooks: {
|
||||
logMethod (args, method) {
|
||||
t.pass()
|
||||
method.apply(this, [args.join('-')])
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.fatal('a')
|
||||
t.match(await o, { msg: 'a' })
|
||||
})
|
||||
|
||||
t.test('children get the hook', async t => {
|
||||
t.plan(4)
|
||||
|
||||
const stream = sink()
|
||||
const root = pino({
|
||||
hooks: {
|
||||
logMethod (args, method) {
|
||||
t.pass()
|
||||
method.apply(this, [args.join('-')])
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
const child = root.child({ child: 'one' })
|
||||
const grandchild = child.child({ child: 'two' })
|
||||
|
||||
let o = once(stream, 'data')
|
||||
child.info('a', 'b')
|
||||
t.match(await o, { msg: 'a-b' })
|
||||
|
||||
o = once(stream, 'data')
|
||||
grandchild.info('c', 'd')
|
||||
t.match(await o, { msg: 'c-d' })
|
||||
})
|
||||
|
||||
t.test('get log level', async t => {
|
||||
t.plan(3)
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({
|
||||
hooks: {
|
||||
logMethod (args, method, level) {
|
||||
t.type(level, 'number')
|
||||
t.equal(level, this.levels.values.error)
|
||||
|
||||
method.apply(this, [args.join('-')])
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
const o = once(stream, 'data')
|
||||
logger.error('a')
|
||||
t.match(await o, { msg: 'a' })
|
||||
})
|
||||
|
||||
t.end()
|
||||
})
|
||||
@@ -0,0 +1,242 @@
|
||||
'use strict'
|
||||
|
||||
const http = require('http')
|
||||
const os = require('os')
|
||||
const semver = require('semver')
|
||||
const { test, skip } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('http request support', async ({ ok, same, error, teardown }) => {
|
||||
let originalReq
|
||||
const instance = pino(sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
same(chunk, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my request',
|
||||
req: {
|
||||
method: originalReq.method,
|
||||
url: originalReq.url,
|
||||
headers: originalReq.headers,
|
||||
remoteAddress: originalReq.socket.remoteAddress,
|
||||
remotePort: originalReq.socket.remotePort
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
originalReq = req
|
||||
instance.info(req, 'my request')
|
||||
res.end('hello')
|
||||
})
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
error(err)
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
|
||||
test('http request support via serializer', async ({ ok, same, error, teardown }) => {
|
||||
let originalReq
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
req: pino.stdSerializers.req
|
||||
}
|
||||
}, sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
same(chunk, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my request',
|
||||
req: {
|
||||
method: originalReq.method,
|
||||
url: originalReq.url,
|
||||
headers: originalReq.headers,
|
||||
remoteAddress: originalReq.socket.remoteAddress,
|
||||
remotePort: originalReq.socket.remotePort
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
const server = http.createServer(function (req, res) {
|
||||
originalReq = req
|
||||
instance.info({ req }, 'my request')
|
||||
res.end('hello')
|
||||
})
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
error(err)
|
||||
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
|
||||
// skipped because request connection is deprecated since v13, and request socket is always available
|
||||
skip('http request support via serializer without request connection', async ({ ok, same, error, teardown }) => {
|
||||
let originalReq
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
req: pino.stdSerializers.req
|
||||
}
|
||||
}, sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
const expected = {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my request',
|
||||
req: {
|
||||
method: originalReq.method,
|
||||
url: originalReq.url,
|
||||
headers: originalReq.headers
|
||||
}
|
||||
}
|
||||
if (semver.gte(process.version, '13.0.0')) {
|
||||
expected.req.remoteAddress = originalReq.socket.remoteAddress
|
||||
expected.req.remotePort = originalReq.socket.remotePort
|
||||
}
|
||||
same(chunk, expected)
|
||||
}))
|
||||
|
||||
const server = http.createServer(function (req, res) {
|
||||
originalReq = req
|
||||
delete req.connection
|
||||
instance.info({ req }, 'my request')
|
||||
res.end('hello')
|
||||
})
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
error(err)
|
||||
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
|
||||
test('http response support', async ({ ok, same, error, teardown }) => {
|
||||
let originalRes
|
||||
const instance = pino(sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
same(chunk, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my response',
|
||||
res: {
|
||||
statusCode: originalRes.statusCode,
|
||||
headers: originalRes.getHeaders()
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
const server = http.createServer(function (req, res) {
|
||||
originalRes = res
|
||||
res.end('hello')
|
||||
instance.info(res, 'my response')
|
||||
})
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
|
||||
error(err)
|
||||
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
|
||||
test('http response support via a serializer', async ({ ok, same, error, teardown }) => {
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
res: pino.stdSerializers.res
|
||||
}
|
||||
}, sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
same(chunk, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my response',
|
||||
res: {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'x-single': 'y',
|
||||
'x-multi': [1, 2]
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
const server = http.createServer(function (req, res) {
|
||||
res.setHeader('x-single', 'y')
|
||||
res.setHeader('x-multi', [1, 2])
|
||||
res.end('hello')
|
||||
instance.info({ res }, 'my response')
|
||||
})
|
||||
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
error(err)
|
||||
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
|
||||
test('http request support via serializer in a child', async ({ ok, same, error, teardown }) => {
|
||||
let originalReq
|
||||
const instance = pino({
|
||||
serializers: {
|
||||
req: pino.stdSerializers.req
|
||||
}
|
||||
}, sink((chunk, enc) => {
|
||||
ok(new Date(chunk.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete chunk.time
|
||||
same(chunk, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'my request',
|
||||
req: {
|
||||
method: originalReq.method,
|
||||
url: originalReq.url,
|
||||
headers: originalReq.headers,
|
||||
remoteAddress: originalReq.socket.remoteAddress,
|
||||
remotePort: originalReq.socket.remotePort
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
const server = http.createServer(function (req, res) {
|
||||
originalReq = req
|
||||
const child = instance.child({ req })
|
||||
child.info('my request')
|
||||
res.end('hello')
|
||||
})
|
||||
|
||||
server.unref()
|
||||
server.listen()
|
||||
const err = await once(server, 'listening')
|
||||
error(err)
|
||||
|
||||
const res = await once(http.get('http://localhost:' + server.address().port), 'response')
|
||||
res.resume()
|
||||
server.close()
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const t = require('tap')
|
||||
const test = t.test
|
||||
const pino = require('../..')()
|
||||
|
||||
test('should be the same as package.json', t => {
|
||||
t.plan(1)
|
||||
|
||||
const json = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json')).toString('utf8'))
|
||||
|
||||
t.equal(pino.version, json.version)
|
||||
})
|
||||
@@ -0,0 +1,185 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const pino = require('../')
|
||||
|
||||
const descLevels = {
|
||||
trace: 60,
|
||||
debug: 50,
|
||||
info: 40,
|
||||
warn: 30,
|
||||
error: 20,
|
||||
fatal: 10
|
||||
}
|
||||
|
||||
const ascLevels = {
|
||||
trace: 10,
|
||||
debug: 20,
|
||||
info: 30,
|
||||
warn: 40,
|
||||
error: 50,
|
||||
fatal: 60
|
||||
}
|
||||
|
||||
test('Default levels suite', ({ test, end }) => {
|
||||
test('can check if current level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug' })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if level enabled after level set', async ({ equal }) => {
|
||||
const log = pino()
|
||||
equal(false, log.isLevelEnabled('debug'))
|
||||
log.level = 'debug'
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if higher level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug' })
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
})
|
||||
|
||||
test('can check if lower level is disabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'error' })
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('ASC: can check if child has current level enabled', async ({ equal }) => {
|
||||
const log = pino().child({}, { level: 'debug' })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('can check if custom level is enabled', async ({ equal }) => {
|
||||
const log = pino({
|
||||
customLevels: { foo: 35 },
|
||||
level: 'debug'
|
||||
})
|
||||
equal(true, log.isLevelEnabled('foo'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('Ascending levels suite', ({ test, end }) => {
|
||||
const customLevels = ascLevels
|
||||
const levelComparison = 'ASC'
|
||||
|
||||
test('can check if current level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if level enabled after level set', async ({ equal }) => {
|
||||
const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(false, log.isLevelEnabled('debug'))
|
||||
log.level = 'debug'
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if higher level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
})
|
||||
|
||||
test('can check if lower level is disabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'error', customLevels, useOnlyCustomLevels: true })
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('can check if child has current level enabled', async ({ equal }) => {
|
||||
const log = pino().child({ levelComparison, customLevels, useOnlyCustomLevels: true }, { level: 'debug' })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('can check if custom level is enabled', async ({ equal }) => {
|
||||
const log = pino({
|
||||
levelComparison,
|
||||
useOnlyCustomLevels: true,
|
||||
customLevels: { foo: 35, ...customLevels },
|
||||
level: 'debug'
|
||||
})
|
||||
equal(true, log.isLevelEnabled('foo'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('Descending levels suite', ({ test, end }) => {
|
||||
const customLevels = descLevels
|
||||
const levelComparison = 'DESC'
|
||||
|
||||
test('can check if current level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if level enabled after level set', async ({ equal }) => {
|
||||
const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(false, log.isLevelEnabled('debug'))
|
||||
log.level = 'debug'
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('can check if higher level enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'debug', levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
})
|
||||
|
||||
test('can check if lower level is disabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'error', levelComparison, customLevels, useOnlyCustomLevels: true })
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('can check if child has current level enabled', async ({ equal }) => {
|
||||
const log = pino({ levelComparison, customLevels, useOnlyCustomLevels: true }).child({}, { level: 'debug' })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
test('can check if custom level is enabled', async ({ equal }) => {
|
||||
const log = pino({
|
||||
levelComparison,
|
||||
customLevels: { foo: 35, ...customLevels },
|
||||
useOnlyCustomLevels: true,
|
||||
level: 'debug'
|
||||
})
|
||||
equal(true, log.isLevelEnabled('foo'))
|
||||
equal(true, log.isLevelEnabled('error'))
|
||||
equal(false, log.isLevelEnabled('trace'))
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('Custom levels comparison', async ({ test, end }) => {
|
||||
test('Custom comparison returns true cause level is enabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'error', levelComparison: () => true })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('Custom comparison returns false cause level is disabled', async ({ equal }) => {
|
||||
const log = pino({ level: 'error', levelComparison: () => false })
|
||||
equal(false, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('Custom comparison returns true cause child level is enabled', async ({ equal }) => {
|
||||
const log = pino({ levelComparison: () => true }).child({ level: 'error' })
|
||||
equal(true, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
test('Custom comparison returns false cause child level is disabled', async ({ equal }) => {
|
||||
const log = pino({ levelComparison: () => false }).child({ level: 'error' })
|
||||
equal(false, log.isLevelEnabled('debug'))
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
/* global test */
|
||||
const pino = require('../../pino')
|
||||
|
||||
test('transport should work in jest', function () {
|
||||
pino({
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,706 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once, check } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const levelsLib = require('../lib/levels')
|
||||
|
||||
// Silence all warnings for this test
|
||||
process.removeAllListeners('warning')
|
||||
process.on('warning', () => {})
|
||||
|
||||
test('set the level by string', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
}, {
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}]
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = 'error'
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
})
|
||||
|
||||
test('the wrong level throws', async ({ throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => {
|
||||
instance.level = 'kaboom'
|
||||
})
|
||||
})
|
||||
|
||||
test('set the level by number', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
}, {
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}]
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
|
||||
instance.level = 50
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
})
|
||||
|
||||
test('exposes level string mappings', async ({ equal }) => {
|
||||
equal(pino.levels.values.error, 50)
|
||||
})
|
||||
|
||||
test('exposes level number mappings', async ({ equal }) => {
|
||||
equal(pino.levels.labels[50], 'error')
|
||||
})
|
||||
|
||||
test('returns level integer', async ({ equal }) => {
|
||||
const instance = pino({ level: 'error' })
|
||||
equal(instance.levelVal, 50)
|
||||
})
|
||||
|
||||
test('child returns level integer', async ({ equal }) => {
|
||||
const parent = pino({ level: 'error' })
|
||||
const child = parent.child({ foo: 'bar' })
|
||||
equal(child.levelVal, 50)
|
||||
})
|
||||
|
||||
test('set the level via exported pino function', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
}, {
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}]
|
||||
const stream = sink()
|
||||
const instance = pino({ level: 'error' }, stream)
|
||||
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
const result = await once(stream, 'data')
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
})
|
||||
|
||||
test('level-change event', async ({ equal }) => {
|
||||
const instance = pino()
|
||||
function handle (lvl, val, prevLvl, prevVal, logger) {
|
||||
equal(lvl, 'trace')
|
||||
equal(val, 10)
|
||||
equal(prevLvl, 'info')
|
||||
equal(prevVal, 30)
|
||||
equal(logger, instance)
|
||||
}
|
||||
instance.on('level-change', handle)
|
||||
instance.level = 'trace'
|
||||
instance.removeListener('level-change', handle)
|
||||
instance.level = 'info'
|
||||
|
||||
let count = 0
|
||||
|
||||
const l1 = () => count++
|
||||
const l2 = () => count++
|
||||
const l3 = () => count++
|
||||
instance.on('level-change', l1)
|
||||
instance.on('level-change', l2)
|
||||
instance.on('level-change', l3)
|
||||
|
||||
instance.level = 'trace'
|
||||
instance.removeListener('level-change', l3)
|
||||
instance.level = 'fatal'
|
||||
instance.removeListener('level-change', l1)
|
||||
instance.level = 'debug'
|
||||
instance.removeListener('level-change', l2)
|
||||
instance.level = 'info'
|
||||
|
||||
equal(count, 6)
|
||||
|
||||
instance.once('level-change', (lvl, val, prevLvl, prevVal, logger) => equal(logger, instance))
|
||||
instance.level = 'info'
|
||||
const child = instance.child({})
|
||||
instance.once('level-change', (lvl, val, prevLvl, prevVal, logger) => equal(logger, child))
|
||||
child.level = 'trace'
|
||||
})
|
||||
|
||||
test('enable', async ({ fail }) => {
|
||||
const instance = pino({
|
||||
level: 'trace',
|
||||
enabled: false
|
||||
}, sink((result, enc) => {
|
||||
fail('no data should be logged')
|
||||
}))
|
||||
|
||||
Object.keys(pino.levels.values).forEach((level) => {
|
||||
instance[level]('hello world')
|
||||
})
|
||||
})
|
||||
|
||||
test('silent level', async ({ fail }) => {
|
||||
const instance = pino({
|
||||
level: 'silent'
|
||||
}, sink((result, enc) => {
|
||||
fail('no data should be logged')
|
||||
}))
|
||||
|
||||
Object.keys(pino.levels.values).forEach((level) => {
|
||||
instance[level]('hello world')
|
||||
})
|
||||
})
|
||||
|
||||
test('set silent via Infinity', async ({ fail }) => {
|
||||
const instance = pino({
|
||||
level: Infinity
|
||||
}, sink((result, enc) => {
|
||||
fail('no data should be logged')
|
||||
}))
|
||||
|
||||
Object.keys(pino.levels.values).forEach((level) => {
|
||||
instance[level]('hello world')
|
||||
})
|
||||
})
|
||||
|
||||
test('exposed levels', async ({ same }) => {
|
||||
same(Object.keys(pino.levels.values), [
|
||||
'trace',
|
||||
'debug',
|
||||
'info',
|
||||
'warn',
|
||||
'error',
|
||||
'fatal'
|
||||
])
|
||||
})
|
||||
|
||||
test('exposed labels', async ({ same }) => {
|
||||
same(Object.keys(pino.levels.labels), [
|
||||
'10',
|
||||
'20',
|
||||
'30',
|
||||
'40',
|
||||
'50',
|
||||
'60'
|
||||
])
|
||||
})
|
||||
|
||||
test('setting level in child', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 50,
|
||||
msg: 'this is an error'
|
||||
}, {
|
||||
level: 60,
|
||||
msg: 'this is fatal'
|
||||
}]
|
||||
const instance = pino(sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
cb()
|
||||
})).child({ level: 30 })
|
||||
|
||||
instance.level = 'error'
|
||||
instance.info('hello world')
|
||||
instance.error('this is an error')
|
||||
instance.fatal('this is fatal')
|
||||
})
|
||||
|
||||
test('setting level by assigning a number to level', async ({ equal }) => {
|
||||
const instance = pino()
|
||||
equal(instance.levelVal, 30)
|
||||
equal(instance.level, 'info')
|
||||
instance.level = 50
|
||||
equal(instance.levelVal, 50)
|
||||
equal(instance.level, 'error')
|
||||
})
|
||||
|
||||
test('setting level by number to unknown value results in a throw', async ({ throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => { instance.level = 973 })
|
||||
})
|
||||
|
||||
test('setting level by assigning a known label to level', async ({ equal }) => {
|
||||
const instance = pino()
|
||||
equal(instance.levelVal, 30)
|
||||
equal(instance.level, 'info')
|
||||
instance.level = 'error'
|
||||
equal(instance.levelVal, 50)
|
||||
equal(instance.level, 'error')
|
||||
})
|
||||
|
||||
test('levelVal is read only', async ({ throws }) => {
|
||||
const instance = pino()
|
||||
throws(() => { instance.levelVal = 20 })
|
||||
})
|
||||
|
||||
test('produces labels when told to', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 'info',
|
||||
msg: 'hello world'
|
||||
}]
|
||||
const instance = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { level: label }
|
||||
}
|
||||
}
|
||||
}, sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
cb()
|
||||
}))
|
||||
|
||||
instance.info('hello world')
|
||||
})
|
||||
|
||||
test('resets levels from labels to numbers', async ({ equal }) => {
|
||||
const expected = [{
|
||||
level: 30,
|
||||
msg: 'hello world'
|
||||
}]
|
||||
pino({ useLevelLabels: true })
|
||||
const instance = pino({ useLevelLabels: false }, sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
cb()
|
||||
}))
|
||||
|
||||
instance.info('hello world')
|
||||
})
|
||||
|
||||
test('changes label naming when told to', async ({ equal }) => {
|
||||
const expected = [{
|
||||
priority: 30,
|
||||
msg: 'hello world'
|
||||
}]
|
||||
const instance = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { priority: number }
|
||||
}
|
||||
}
|
||||
}, sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
equal(result.priority, current.priority)
|
||||
equal(result.msg, current.msg)
|
||||
cb()
|
||||
}))
|
||||
|
||||
instance.info('hello world')
|
||||
})
|
||||
|
||||
test('children produce labels when told to', async ({ equal }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 'info',
|
||||
msg: 'child 1'
|
||||
},
|
||||
{
|
||||
level: 'info',
|
||||
msg: 'child 2'
|
||||
}
|
||||
]
|
||||
const instance = pino({
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { level: label }
|
||||
}
|
||||
}
|
||||
}, sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
cb()
|
||||
}))
|
||||
|
||||
const child1 = instance.child({ name: 'child1' })
|
||||
const child2 = child1.child({ name: 'child2' })
|
||||
|
||||
child1.info('child 1')
|
||||
child2.info('child 2')
|
||||
})
|
||||
|
||||
test('produces labels for custom levels', async ({ equal }) => {
|
||||
const expected = [
|
||||
{
|
||||
level: 'info',
|
||||
msg: 'hello world'
|
||||
},
|
||||
{
|
||||
level: 'foo',
|
||||
msg: 'foobar'
|
||||
}
|
||||
]
|
||||
const opts = {
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { level: label }
|
||||
}
|
||||
},
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}
|
||||
const instance = pino(opts, sink((result, enc, cb) => {
|
||||
const current = expected.shift()
|
||||
check(equal, result, current.level, current.msg)
|
||||
cb()
|
||||
}))
|
||||
|
||||
instance.info('hello world')
|
||||
instance.foo('foobar')
|
||||
})
|
||||
|
||||
test('setting levelKey does not affect labels when told to', async ({ equal }) => {
|
||||
const instance = pino(
|
||||
{
|
||||
formatters: {
|
||||
level (label, number) {
|
||||
return { priority: label }
|
||||
}
|
||||
}
|
||||
},
|
||||
sink((result, enc, cb) => {
|
||||
equal(result.priority, 'info')
|
||||
cb()
|
||||
})
|
||||
)
|
||||
|
||||
instance.info('hello world')
|
||||
})
|
||||
|
||||
test('throws when creating a default label that does not exist in logger levels', async ({ throws }) => {
|
||||
const defaultLevel = 'foo'
|
||||
throws(() => {
|
||||
pino({
|
||||
customLevels: {
|
||||
bar: 5
|
||||
},
|
||||
level: defaultLevel
|
||||
})
|
||||
}, `default level:${defaultLevel} must be included in custom levels`)
|
||||
})
|
||||
|
||||
test('throws when creating a default value that does not exist in logger levels', async ({ throws }) => {
|
||||
const defaultLevel = 15
|
||||
throws(() => {
|
||||
pino({
|
||||
customLevels: {
|
||||
bar: 5
|
||||
},
|
||||
level: defaultLevel
|
||||
})
|
||||
}, `default level:${defaultLevel} must be included in custom levels`)
|
||||
})
|
||||
|
||||
test('throws when creating a default value that does not exist in logger levels', async ({ equal, throws }) => {
|
||||
throws(() => {
|
||||
pino({
|
||||
customLevels: {
|
||||
foo: 5
|
||||
},
|
||||
useOnlyCustomLevels: true
|
||||
})
|
||||
}, 'default level:info must be included in custom levels')
|
||||
})
|
||||
|
||||
test('passes when creating a default value that exists in logger levels', async ({ equal, throws }) => {
|
||||
pino({
|
||||
level: 30
|
||||
})
|
||||
})
|
||||
|
||||
test('log null value when message is null', async ({ equal }) => {
|
||||
const expected = {
|
||||
msg: null,
|
||||
level: 30
|
||||
}
|
||||
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = 'info'
|
||||
instance.info(null)
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('formats when base param is null', async ({ equal }) => {
|
||||
const expected = {
|
||||
msg: 'a string',
|
||||
level: 30
|
||||
}
|
||||
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.level = 'info'
|
||||
instance.info(null, 'a %s', 'string')
|
||||
|
||||
const result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('fatal method sync-flushes the destination if sync flushing is available', async ({ pass, doesNotThrow, plan }) => {
|
||||
plan(2)
|
||||
const stream = sink()
|
||||
stream.flushSync = () => {
|
||||
pass('destination flushed')
|
||||
}
|
||||
const instance = pino(stream)
|
||||
instance.fatal('this is fatal')
|
||||
await once(stream, 'data')
|
||||
doesNotThrow(() => {
|
||||
stream.flushSync = undefined
|
||||
instance.fatal('this is fatal')
|
||||
})
|
||||
})
|
||||
|
||||
test('fatal method should call async when sync-flushing fails', ({ equal, fail, doesNotThrow, plan }) => {
|
||||
plan(2)
|
||||
const messages = [
|
||||
'this is fatal 1'
|
||||
]
|
||||
const stream = sink((result) => equal(result.msg, messages.shift()))
|
||||
stream.flushSync = () => { throw new Error('Error') }
|
||||
stream.flush = () => fail('flush should be called')
|
||||
|
||||
const instance = pino(stream)
|
||||
doesNotThrow(() => instance.fatal(messages[0]))
|
||||
})
|
||||
|
||||
test('calling silent method on logger instance', async ({ fail }) => {
|
||||
const instance = pino({ level: 'silent' }, sink((result, enc) => {
|
||||
fail('no data should be logged')
|
||||
}))
|
||||
instance.silent('hello world')
|
||||
})
|
||||
|
||||
test('calling silent method on child logger', async ({ fail }) => {
|
||||
const child = pino({ level: 'silent' }, sink((result, enc) => {
|
||||
fail('no data should be logged')
|
||||
})).child({})
|
||||
child.silent('hello world')
|
||||
})
|
||||
|
||||
test('changing level from info to silent and back to info', async ({ equal }) => {
|
||||
const expected = {
|
||||
level: 30,
|
||||
msg: 'hello world'
|
||||
}
|
||||
const stream = sink()
|
||||
const instance = pino({ level: 'info' }, stream)
|
||||
|
||||
instance.level = 'silent'
|
||||
instance.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
instance.level = 'info'
|
||||
instance.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('changing level from info to silent and back to info in child logger', async ({ equal }) => {
|
||||
const expected = {
|
||||
level: 30,
|
||||
msg: 'hello world'
|
||||
}
|
||||
const stream = sink()
|
||||
const child = pino({ level: 'info' }, stream).child({})
|
||||
|
||||
child.level = 'silent'
|
||||
child.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
child.level = 'info'
|
||||
child.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('changing level respects level comparison set to', async ({ test, end }) => {
|
||||
const ascLevels = {
|
||||
debug: 1,
|
||||
info: 2,
|
||||
warn: 3
|
||||
}
|
||||
|
||||
const descLevels = {
|
||||
debug: 3,
|
||||
info: 2,
|
||||
warn: 1
|
||||
}
|
||||
|
||||
const expected = {
|
||||
level: 2,
|
||||
msg: 'hello world'
|
||||
}
|
||||
|
||||
test('ASC in parent logger', async ({ equal }) => {
|
||||
const customLevels = ascLevels
|
||||
const levelComparison = 'ASC'
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream)
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('DESC in parent logger', async ({ equal }) => {
|
||||
const customLevels = descLevels
|
||||
const levelComparison = 'DESC'
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream)
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('custom function in parent logger', async ({ equal }) => {
|
||||
const customLevels = {
|
||||
info: 2,
|
||||
debug: 345,
|
||||
warn: 789
|
||||
}
|
||||
const levelComparison = (current, expected) => {
|
||||
if (expected === customLevels.warn) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream)
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('ASC in child logger', async ({ equal }) => {
|
||||
const customLevels = ascLevels
|
||||
const levelComparison = 'ASC'
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ })
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('DESC in parent logger', async ({ equal }) => {
|
||||
const customLevels = descLevels
|
||||
const levelComparison = 'DESC'
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ })
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
test('custom function in child logger', async ({ equal }) => {
|
||||
const customLevels = {
|
||||
info: 2,
|
||||
debug: 345,
|
||||
warn: 789
|
||||
}
|
||||
const levelComparison = (current, expected) => {
|
||||
if (expected === customLevels.warn) return false
|
||||
return true
|
||||
}
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream).child({ })
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
end()
|
||||
})
|
||||
|
||||
test('changing level respects level comparison DESC', async ({ equal }) => {
|
||||
const customLevels = {
|
||||
warn: 1,
|
||||
info: 2,
|
||||
debug: 3
|
||||
}
|
||||
|
||||
const levelComparison = 'DESC'
|
||||
|
||||
const expected = {
|
||||
level: 2,
|
||||
msg: 'hello world'
|
||||
}
|
||||
|
||||
const stream = sink()
|
||||
const logger = pino({ levelComparison, customLevels, useOnlyCustomLevels: true, level: 'info' }, stream)
|
||||
|
||||
logger.level = 'warn'
|
||||
logger.info('hello world')
|
||||
let result = stream.read()
|
||||
equal(result, null)
|
||||
|
||||
logger.level = 'debug'
|
||||
logger.info('hello world')
|
||||
result = await once(stream, 'data')
|
||||
check(equal, result, expected.level, expected.msg)
|
||||
})
|
||||
|
||||
// testing for potential loss of Pino constructor scope from serializers - an edge case with circular refs see: https://github.com/pinojs/pino/issues/833
|
||||
test('trying to get levels when `this` is no longer a Pino instance returns an empty string', async ({ equal }) => {
|
||||
const notPinoInstance = { some: 'object', getLevel: levelsLib.getLevel }
|
||||
const blankedLevelValue = notPinoInstance.getLevel()
|
||||
equal(blankedLevelValue, '')
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { test } = require('tap')
|
||||
const pino = require('../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('metadata works', async ({ ok, same, equal }) => {
|
||||
const now = Date.now()
|
||||
const instance = pino({}, {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
equal(instance, this.lastLogger)
|
||||
equal(30, this.lastLevel)
|
||||
equal('a msg', this.lastMsg)
|
||||
ok(Number(this.lastTime) >= now)
|
||||
same(this.lastObj, { hello: 'world' })
|
||||
const result = JSON.parse(chunk)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
hello: 'world',
|
||||
msg: 'a msg'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
instance.info({ hello: 'world' }, 'a msg')
|
||||
})
|
||||
|
||||
test('child loggers works', async ({ ok, same, equal }) => {
|
||||
const instance = pino({}, {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
equal(child, this.lastLogger)
|
||||
equal(30, this.lastLevel)
|
||||
equal('a msg', this.lastMsg)
|
||||
same(this.lastObj, { from: 'child' })
|
||||
const result = JSON.parse(chunk)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
hello: 'world',
|
||||
from: 'child',
|
||||
msg: 'a msg'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const child = instance.child({ hello: 'world' })
|
||||
child.info({ from: 'child' }, 'a msg')
|
||||
})
|
||||
|
||||
test('without object', async ({ ok, same, equal }) => {
|
||||
const instance = pino({}, {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
equal(instance, this.lastLogger)
|
||||
equal(30, this.lastLevel)
|
||||
equal('a msg', this.lastMsg)
|
||||
same({ }, this.lastObj)
|
||||
const result = JSON.parse(chunk)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'a msg'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
instance.info('a msg')
|
||||
})
|
||||
|
||||
test('without msg', async ({ ok, same, equal }) => {
|
||||
const instance = pino({}, {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
equal(instance, this.lastLogger)
|
||||
equal(30, this.lastLevel)
|
||||
equal(undefined, this.lastMsg)
|
||||
same({ hello: 'world' }, this.lastObj)
|
||||
const result = JSON.parse(chunk)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
hello: 'world'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
instance.info({ hello: 'world' })
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const level = 50
|
||||
const name = 'error'
|
||||
|
||||
test('default merge strategy', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
base: {},
|
||||
mixin () {
|
||||
return { tag: 'k8s' }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({
|
||||
tag: 'local'
|
||||
}, 'test')
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
level,
|
||||
msg: 'test',
|
||||
tag: 'local'
|
||||
})
|
||||
})
|
||||
|
||||
test('custom merge strategy with mixin priority', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
base: {},
|
||||
mixin () {
|
||||
return { tag: 'k8s' }
|
||||
},
|
||||
mixinMergeStrategy (mergeObject, mixinObject) {
|
||||
return Object.assign(mergeObject, mixinObject)
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({
|
||||
tag: 'local'
|
||||
}, 'test')
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
level,
|
||||
msg: 'test',
|
||||
tag: 'k8s'
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,218 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
const level = 50
|
||||
const name = 'error'
|
||||
|
||||
test('mixin object is included', async ({ ok, same }) => {
|
||||
let n = 0
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
return { hello: ++n }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]('test')
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg: 'test',
|
||||
hello: 1
|
||||
})
|
||||
})
|
||||
|
||||
test('mixin object is new every time', async ({ plan, ok, same }) => {
|
||||
plan(6)
|
||||
|
||||
let n = 0
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
return { hello: n }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
|
||||
while (++n < 4) {
|
||||
const msg = `test #${n}`
|
||||
stream.pause()
|
||||
instance[name](msg)
|
||||
stream.resume()
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
msg,
|
||||
hello: n
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('mixin object is not called if below log level', async ({ ok }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
ok(false, 'should not call mixin function')
|
||||
}
|
||||
}, stream)
|
||||
instance.level = 'error'
|
||||
instance.info('test')
|
||||
})
|
||||
|
||||
test('mixin object + logged object', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin () {
|
||||
return { foo: 1, bar: 2 }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({ bar: 3, baz: 4 })
|
||||
const result = await once(stream, 'data')
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than Date.now()')
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level,
|
||||
foo: 1,
|
||||
bar: 3,
|
||||
baz: 4
|
||||
})
|
||||
})
|
||||
|
||||
test('mixin not a function', async ({ throws }) => {
|
||||
const stream = sink()
|
||||
throws(function () {
|
||||
pino({ mixin: 'not a function' }, stream)
|
||||
})
|
||||
})
|
||||
|
||||
test('mixin can use context', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin (context) {
|
||||
ok(context !== null, 'context should be defined')
|
||||
ok(context !== undefined, 'context should be defined')
|
||||
same(context, {
|
||||
message: '123',
|
||||
stack: 'stack'
|
||||
})
|
||||
return Object.assign({
|
||||
error: context.message,
|
||||
stack: context.stack
|
||||
})
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({
|
||||
message: '123',
|
||||
stack: 'stack'
|
||||
}, 'test')
|
||||
})
|
||||
|
||||
test('mixin works without context', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin (context) {
|
||||
ok(context !== null, 'context is still defined w/o passing mergeObject')
|
||||
ok(context !== undefined, 'context is still defined w/o passing mergeObject')
|
||||
same(context, {})
|
||||
return {
|
||||
something: true
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]('test')
|
||||
})
|
||||
|
||||
test('mixin can use level number', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin (context, num) {
|
||||
ok(num !== null, 'level should be defined')
|
||||
ok(num !== undefined, 'level should be defined')
|
||||
same(num, level)
|
||||
return Object.assign({
|
||||
error: context.message,
|
||||
stack: context.stack
|
||||
})
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({
|
||||
message: '123',
|
||||
stack: 'stack'
|
||||
}, 'test')
|
||||
})
|
||||
|
||||
test('mixin receives logger as third parameter', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
mixin (context, num, logger) {
|
||||
ok(logger !== null, 'logger should be defined')
|
||||
ok(logger !== undefined, 'logger should be defined')
|
||||
same(logger, instance)
|
||||
return { ...context, num }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance[name]({
|
||||
message: '123'
|
||||
}, 'test')
|
||||
})
|
||||
|
||||
test('mixin receives child logger', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
let child = null
|
||||
const instance = pino({
|
||||
mixin (context, num, logger) {
|
||||
ok(logger !== null, 'logger should be defined')
|
||||
ok(logger !== undefined, 'logger should be defined')
|
||||
same(logger.expected, child.expected)
|
||||
return { ...context, num }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance.expected = false
|
||||
child = instance.child({})
|
||||
child.expected = true
|
||||
child[name]({
|
||||
message: '123'
|
||||
}, 'test')
|
||||
})
|
||||
|
||||
test('mixin receives logger even if child exists', async ({ ok, same }) => {
|
||||
const stream = sink()
|
||||
let child = null
|
||||
const instance = pino({
|
||||
mixin (context, num, logger) {
|
||||
ok(logger !== null, 'logger should be defined')
|
||||
ok(logger !== undefined, 'logger should be defined')
|
||||
same(logger.expected, instance.expected)
|
||||
return { ...context, num }
|
||||
}
|
||||
}, stream)
|
||||
instance.level = name
|
||||
instance.expected = false
|
||||
child = instance.child({})
|
||||
child.expected = true
|
||||
instance[name]({
|
||||
message: '123'
|
||||
}, 'test')
|
||||
})
|
||||
@@ -0,0 +1,673 @@
|
||||
'use strict'
|
||||
|
||||
const writeStream = require('flush-write-stream')
|
||||
const { readFileSync } = require('fs')
|
||||
const { join } = require('path')
|
||||
const test = require('tap').test
|
||||
const pino = require('../')
|
||||
const multistream = pino.multistream
|
||||
const proxyquire = require('proxyquire')
|
||||
const strip = require('strip-ansi')
|
||||
const { file, sink } = require('./helper')
|
||||
|
||||
test('sends to multiple streams using string levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'debug', stream },
|
||||
{ level: 'trace', stream },
|
||||
{ level: 'fatal', stream },
|
||||
{ level: 'silent', stream }
|
||||
]
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(streams))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 9)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sends to multiple streams using custom levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'debug', stream },
|
||||
{ level: 'trace', stream },
|
||||
{ level: 'fatal', stream },
|
||||
{ level: 'silent', stream }
|
||||
]
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(streams))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 9)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sends to multiple streams using optionally predefined levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const opts = {
|
||||
levels: {
|
||||
silent: Infinity,
|
||||
fatal: 60,
|
||||
error: 50,
|
||||
warn: 50,
|
||||
info: 30,
|
||||
debug: 20,
|
||||
trace: 10
|
||||
}
|
||||
}
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'trace', stream },
|
||||
{ level: 'debug', stream },
|
||||
{ level: 'info', stream },
|
||||
{ level: 'warn', stream },
|
||||
{ level: 'error', stream },
|
||||
{ level: 'fatal', stream },
|
||||
{ level: 'silent', stream }
|
||||
]
|
||||
const mstream = multistream(streams, opts)
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, mstream)
|
||||
log.trace('trace stream')
|
||||
log.debug('debug stream')
|
||||
log.info('info stream')
|
||||
log.warn('warn stream')
|
||||
log.error('error stream')
|
||||
log.fatal('fatal stream')
|
||||
log.silent('silent stream')
|
||||
t.equal(messageCount, 24)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('sends to multiple streams using number levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 20, stream },
|
||||
{ level: 60, stream }
|
||||
]
|
||||
const log = pino({
|
||||
level: 'debug'
|
||||
}, multistream(streams))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 6)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('level include higher levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const log = pino({}, multistream([{ level: 'info', stream }]))
|
||||
log.fatal('message')
|
||||
t.equal(messageCount, 1)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('supports multiple arguments', function (t) {
|
||||
const messages = []
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messages.push(JSON.parse(data))
|
||||
if (messages.length === 2) {
|
||||
const msg1 = messages[0]
|
||||
t.equal(msg1.msg, 'foo bar baz foobar')
|
||||
|
||||
const msg2 = messages[1]
|
||||
t.equal(msg2.msg, 'foo bar baz foobar barfoo foofoo')
|
||||
|
||||
t.end()
|
||||
}
|
||||
cb()
|
||||
})
|
||||
const log = pino({}, multistream({ stream }))
|
||||
log.info('%s %s %s %s', 'foo', 'bar', 'baz', 'foobar') // apply not invoked
|
||||
log.info('%s %s %s %s %s %s', 'foo', 'bar', 'baz', 'foobar', 'barfoo', 'foofoo') // apply invoked
|
||||
})
|
||||
|
||||
test('supports children', function (t) {
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
const input = JSON.parse(data)
|
||||
t.equal(input.msg, 'child stream')
|
||||
t.equal(input.child, 'one')
|
||||
t.end()
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream }
|
||||
]
|
||||
const log = pino({}, multistream(streams)).child({ child: 'one' })
|
||||
log.info('child stream')
|
||||
})
|
||||
|
||||
test('supports grandchildren', function (t) {
|
||||
const messages = []
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messages.push(JSON.parse(data))
|
||||
if (messages.length === 3) {
|
||||
const msg1 = messages[0]
|
||||
t.equal(msg1.msg, 'grandchild stream')
|
||||
t.equal(msg1.child, 'one')
|
||||
t.equal(msg1.grandchild, 'two')
|
||||
|
||||
const msg2 = messages[1]
|
||||
t.equal(msg2.msg, 'grandchild stream')
|
||||
t.equal(msg2.child, 'one')
|
||||
t.equal(msg2.grandchild, 'two')
|
||||
|
||||
const msg3 = messages[2]
|
||||
t.equal(msg3.msg, 'debug grandchild')
|
||||
t.equal(msg3.child, 'one')
|
||||
t.equal(msg3.grandchild, 'two')
|
||||
|
||||
t.end()
|
||||
}
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'debug', stream }
|
||||
]
|
||||
const log = pino({
|
||||
level: 'debug'
|
||||
}, multistream(streams)).child({ child: 'one' }).child({ grandchild: 'two' })
|
||||
log.info('grandchild stream')
|
||||
log.debug('debug grandchild')
|
||||
})
|
||||
|
||||
test('supports custom levels', function (t) {
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
t.equal(JSON.parse(data).msg, 'bar')
|
||||
t.end()
|
||||
})
|
||||
const log = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, multistream([{ level: 35, stream }]))
|
||||
log.foo('bar')
|
||||
})
|
||||
|
||||
test('supports pretty print', function (t) {
|
||||
t.plan(2)
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
t.not(strip(data.toString()).match(/INFO.*: pretty print/), null)
|
||||
cb()
|
||||
})
|
||||
|
||||
const safeBoom = proxyquire('pino-pretty/lib/utils/build-safe-sonic-boom.js', {
|
||||
'sonic-boom': function () {
|
||||
t.pass('sonic created')
|
||||
stream.flushSync = () => {}
|
||||
stream.flush = () => {}
|
||||
return stream
|
||||
}
|
||||
})
|
||||
const nested = proxyquire('pino-pretty/lib/utils/index.js', {
|
||||
'./build-safe-sonic-boom.js': safeBoom
|
||||
})
|
||||
const pretty = proxyquire('pino-pretty', {
|
||||
'./lib/utils/index.js': nested
|
||||
})
|
||||
|
||||
const log = pino({
|
||||
level: 'debug',
|
||||
name: 'helloName'
|
||||
}, multistream([
|
||||
{ stream: pretty() }
|
||||
]))
|
||||
|
||||
log.info('pretty print')
|
||||
})
|
||||
|
||||
test('emit propagates events to each stream', function (t) {
|
||||
t.plan(3)
|
||||
const handler = function (data) {
|
||||
t.equal(data.msg, 'world')
|
||||
}
|
||||
const streams = [sink(), sink(), sink()]
|
||||
streams.forEach(function (s) {
|
||||
s.once('hello', handler)
|
||||
})
|
||||
const stream = multistream(streams)
|
||||
stream.emit('hello', { msg: 'world' })
|
||||
})
|
||||
|
||||
test('children support custom levels', function (t) {
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
t.equal(JSON.parse(data).msg, 'bar')
|
||||
t.end()
|
||||
})
|
||||
const parent = pino({
|
||||
customLevels: {
|
||||
foo: 35
|
||||
}
|
||||
}, multistream([{ level: 35, stream }]))
|
||||
const child = parent.child({ child: 'yes' })
|
||||
child.foo('bar')
|
||||
})
|
||||
|
||||
test('levelVal overrides level', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'blabla', levelVal: 15, stream },
|
||||
{ level: 60, stream }
|
||||
]
|
||||
const log = pino({
|
||||
level: 'debug'
|
||||
}, multistream(streams))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 6)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('forwards metadata', function (t) {
|
||||
t.plan(4)
|
||||
const streams = [
|
||||
{
|
||||
stream: {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
t.equal(log, this.lastLogger)
|
||||
t.equal(30, this.lastLevel)
|
||||
t.same({ hello: 'world' }, this.lastObj)
|
||||
t.same('a msg', this.lastMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'debug'
|
||||
}, multistream(streams))
|
||||
|
||||
log.info({ hello: 'world' }, 'a msg')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('forward name', function (t) {
|
||||
t.plan(2)
|
||||
const streams = [
|
||||
{
|
||||
stream: {
|
||||
[Symbol.for('pino.metadata')]: true,
|
||||
write (chunk) {
|
||||
const line = JSON.parse(chunk)
|
||||
t.equal(line.name, 'helloName')
|
||||
t.equal(line.hello, 'world')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'debug',
|
||||
name: 'helloName'
|
||||
}, multistream(streams))
|
||||
|
||||
log.info({ hello: 'world' }, 'a msg')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('forward name with child', function (t) {
|
||||
t.plan(3)
|
||||
const streams = [
|
||||
{
|
||||
stream: {
|
||||
write (chunk) {
|
||||
const line = JSON.parse(chunk)
|
||||
t.equal(line.name, 'helloName')
|
||||
t.equal(line.hello, 'world')
|
||||
t.equal(line.component, 'aComponent')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'debug',
|
||||
name: 'helloName'
|
||||
}, multistream(streams)).child({ component: 'aComponent' })
|
||||
|
||||
log.info({ hello: 'world' }, 'a msg')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('clone generates a new multistream with all stream at the same level', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'debug', stream },
|
||||
{ level: 'trace', stream },
|
||||
{ level: 'fatal', stream }
|
||||
]
|
||||
const ms = multistream(streams)
|
||||
const clone = ms.clone(30)
|
||||
|
||||
t.not(clone, ms)
|
||||
|
||||
clone.streams.forEach((s, i) => {
|
||||
t.not(s, streams[i])
|
||||
t.equal(s.stream, streams[i].stream)
|
||||
t.equal(s.level, 30)
|
||||
})
|
||||
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, clone)
|
||||
|
||||
log.info('info stream')
|
||||
log.debug('debug message not counted')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 8)
|
||||
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('one stream', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream({ stream, level: 'fatal' }))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 1)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('dedupe', function (t) {
|
||||
let messageCount = 0
|
||||
const stream1 = writeStream(function (data, enc, cb) {
|
||||
messageCount -= 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const stream2 = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const streams = [
|
||||
{
|
||||
stream: stream1,
|
||||
level: 'info'
|
||||
},
|
||||
{
|
||||
stream: stream2,
|
||||
level: 'fatal'
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(streams, { dedupe: true }))
|
||||
log.info('info stream')
|
||||
log.fatal('fatal stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 1)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('dedupe when logs have different levels', function (t) {
|
||||
let messageCount = 0
|
||||
const stream1 = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const stream2 = writeStream(function (data, enc, cb) {
|
||||
messageCount += 2
|
||||
cb()
|
||||
})
|
||||
|
||||
const streams = [
|
||||
{
|
||||
stream: stream1,
|
||||
level: 'info'
|
||||
},
|
||||
{
|
||||
stream: stream2,
|
||||
level: 'error'
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(streams, { dedupe: true }))
|
||||
|
||||
log.info('info stream')
|
||||
log.warn('warn stream')
|
||||
log.error('error streams')
|
||||
log.fatal('fatal streams')
|
||||
t.equal(messageCount, 6)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('dedupe when some streams has the same level', function (t) {
|
||||
let messageCount = 0
|
||||
const stream1 = writeStream(function (data, enc, cb) {
|
||||
messageCount -= 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const stream2 = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const stream3 = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const streams = [
|
||||
{
|
||||
stream: stream1,
|
||||
level: 'info'
|
||||
},
|
||||
{
|
||||
stream: stream2,
|
||||
level: 'fatal'
|
||||
},
|
||||
{
|
||||
stream: stream3,
|
||||
level: 'fatal'
|
||||
}
|
||||
]
|
||||
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(streams, { dedupe: true }))
|
||||
log.info('info stream')
|
||||
log.fatal('fatal streams')
|
||||
log.fatal('fatal streams')
|
||||
t.equal(messageCount, 3)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('no stream', function (t) {
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream())
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('one stream', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream(stream))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 2)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('add a stream', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multistream().add(stream))
|
||||
log.info('info stream')
|
||||
log.debug('debug stream')
|
||||
log.fatal('fatal stream')
|
||||
t.equal(messageCount, 2)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('multistream.add throws if not a stream', function (t) {
|
||||
try {
|
||||
pino({
|
||||
level: 'trace'
|
||||
}, multistream().add({}))
|
||||
} catch (_) {
|
||||
t.end()
|
||||
}
|
||||
})
|
||||
|
||||
test('multistream throws if not a stream', function (t) {
|
||||
try {
|
||||
pino({
|
||||
level: 'trace'
|
||||
}, multistream({}))
|
||||
} catch (_) {
|
||||
t.end()
|
||||
}
|
||||
})
|
||||
|
||||
test('multistream.write should not throw if one stream fails', function (t) {
|
||||
let messageCount = 0
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
messageCount += 1
|
||||
cb()
|
||||
})
|
||||
const noopStream = pino.transport({
|
||||
target: join(__dirname, 'fixtures', 'noop-transport.js')
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
noopStream.on('error', function (err) {
|
||||
// something went wrong while writing to noop stream, ignoring!
|
||||
})
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
},
|
||||
multistream([
|
||||
{
|
||||
level: 'trace',
|
||||
stream
|
||||
},
|
||||
{
|
||||
level: 'debug',
|
||||
stream: noopStream
|
||||
}
|
||||
])
|
||||
)
|
||||
log.debug('0')
|
||||
noopStream.end()
|
||||
// noop stream is ending, should emit an error but not throw
|
||||
log.debug('1')
|
||||
log.debug('2')
|
||||
t.equal(messageCount, 3)
|
||||
t.end()
|
||||
})
|
||||
|
||||
test('flushSync', function (t) {
|
||||
const tmp = file()
|
||||
const destination = pino.destination({ dest: tmp, sync: false, minLength: 4096 })
|
||||
const stream = multistream([{ level: 'info', stream: destination }])
|
||||
const log = pino({ level: 'info' }, stream)
|
||||
destination.on('ready', () => {
|
||||
log.info('foo')
|
||||
log.info('bar')
|
||||
stream.flushSync()
|
||||
t.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 2)
|
||||
log.info('biz')
|
||||
stream.flushSync()
|
||||
t.equal(readFileSync(tmp, { encoding: 'utf-8' }).split('\n').length - 1, 3)
|
||||
t.end()
|
||||
})
|
||||
})
|
||||
|
||||
test('ends all streams', function (t) {
|
||||
t.plan(7)
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
t.pass('message')
|
||||
cb()
|
||||
})
|
||||
stream.flushSync = function () {
|
||||
t.pass('flushSync')
|
||||
}
|
||||
// stream2 has no flushSync
|
||||
const stream2 = writeStream(function (data, enc, cb) {
|
||||
t.pass('message2')
|
||||
cb()
|
||||
})
|
||||
const streams = [
|
||||
{ stream },
|
||||
{ level: 'debug', stream },
|
||||
{ level: 'trace', stream: stream2 },
|
||||
{ level: 'fatal', stream },
|
||||
{ level: 'silent', stream }
|
||||
]
|
||||
const multi = multistream(streams)
|
||||
const log = pino({
|
||||
level: 'trace'
|
||||
}, multi)
|
||||
log.info('info stream')
|
||||
multi.end()
|
||||
})
|
||||
@@ -0,0 +1,847 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
test('redact option – throws if not array', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: 'req.headers.cookie' })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact option – throws if array does not only contain strings', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: ['req.headers.cookie', {}] })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact option – throws if array contains an invalid path', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: ['req,headers.cookie'] })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact.paths option – throws if not array', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: { paths: 'req.headers.cookie' } })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact.paths option – throws if array does not only contain strings', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: { paths: ['req.headers.cookie', {}] } })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact.paths option – throws if array contains an invalid path', async ({ throws }) => {
|
||||
throws(() => {
|
||||
pino({ redact: { paths: ['req,headers.cookie'] } })
|
||||
})
|
||||
})
|
||||
|
||||
test('redact option – top level key', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['key'] }, stream)
|
||||
instance.info({
|
||||
key: { redact: 'me' }
|
||||
})
|
||||
const { key } = await once(stream, 'data')
|
||||
equal(key, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact option – top level key next level key', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['key', 'key.foo'] }, stream)
|
||||
instance.info({
|
||||
key: { redact: 'me' }
|
||||
})
|
||||
const { key } = await once(stream, 'data')
|
||||
equal(key, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact option – next level key then top level key', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['key.foo', 'key'] }, stream)
|
||||
instance.info({
|
||||
key: { redact: 'me' }
|
||||
})
|
||||
const { key } = await once(stream, 'data')
|
||||
equal(key, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact option – object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.headers.cookie'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact option – child object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.headers.cookie'] }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact option – interpolated object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.headers.cookie'] }, stream)
|
||||
|
||||
instance.info('test %j', {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(JSON.parse(msg.replace(/test /, '')).req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact.paths option – object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact.paths option – child object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact.paths option – interpolated object', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream)
|
||||
|
||||
instance.info('test %j', {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { msg } = await once(stream, 'data')
|
||||
equal(JSON.parse(msg.replace(/test /, '')).req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact.censor option – sets the redact value', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: 'test' } }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, 'test')
|
||||
})
|
||||
|
||||
test('redact.censor option – can be a function that accepts value and path arguments', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['topLevel'], censor: (value, path) => value + ' ' + path.join('.') } }, stream)
|
||||
instance.info({
|
||||
topLevel: 'test'
|
||||
})
|
||||
const { topLevel } = await once(stream, 'data')
|
||||
equal(topLevel, 'test topLevel')
|
||||
})
|
||||
|
||||
test('redact.censor option – can be a function that accepts value and path arguments (nested path)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: (value, path) => value + ' ' + path.join('.') } }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1; req.headers.cookie')
|
||||
})
|
||||
|
||||
test('redact.remove option – removes both key and value', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'], remove: true } }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal('cookie' in req.headers, false)
|
||||
})
|
||||
|
||||
test('redact.remove – top level key - object value', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['key'], remove: true } }, stream)
|
||||
instance.info({
|
||||
key: { redact: 'me' }
|
||||
})
|
||||
const o = await once(stream, 'data')
|
||||
equal('key' in o, false)
|
||||
})
|
||||
|
||||
test('redact.remove – top level key - number value', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['key'], remove: true } }, stream)
|
||||
instance.info({
|
||||
key: 1
|
||||
})
|
||||
const o = await once(stream, 'data')
|
||||
equal('key' in o, false)
|
||||
})
|
||||
|
||||
test('redact.remove – top level key - boolean value', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['key'], remove: true } }, stream)
|
||||
instance.info({
|
||||
key: false
|
||||
})
|
||||
const o = await once(stream, 'data')
|
||||
equal('key' in o, false)
|
||||
})
|
||||
|
||||
test('redact.remove – top level key in child logger', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const opts = { redact: { paths: ['key'], remove: true } }
|
||||
const instance = pino(opts, stream).child({ key: { redact: 'me' } })
|
||||
instance.info('test')
|
||||
const o = await once(stream, 'data')
|
||||
equal('key' in o, false)
|
||||
})
|
||||
|
||||
test('redact.paths preserves original object values after the log write', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.headers.cookie'] }, stream)
|
||||
const obj = {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.req.headers.cookie, '[Redacted]')
|
||||
equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;')
|
||||
})
|
||||
|
||||
test('redact.paths preserves original object values after the log write', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'] } }, stream)
|
||||
const obj = {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.req.headers.cookie, '[Redacted]')
|
||||
equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;')
|
||||
})
|
||||
|
||||
test('redact.censor preserves original object values after the log write', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'], censor: 'test' } }, stream)
|
||||
const obj = {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.req.headers.cookie, 'test')
|
||||
equal(obj.req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;')
|
||||
})
|
||||
|
||||
test('redact.remove preserves original object values after the log write', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['req.headers.cookie'], remove: true } }, stream)
|
||||
const obj = {
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal('cookie' in o.req.headers, false)
|
||||
equal('cookie' in obj.req.headers, true)
|
||||
})
|
||||
|
||||
test('redact – supports last position wildcard paths', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.headers.*'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
equal(req.headers.host, '[Redacted]')
|
||||
equal(req.headers.connection, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports first position wildcard paths', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['*.headers'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports first position wildcards before other paths', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['*.headers.cookie', 'req.id'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
equal(req.id, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports first position wildcards after other paths', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.id', '*.headers.cookie'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
equal(req.id, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports first position wildcards after top level keys', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['key', '*.headers.cookie'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports top level wildcard', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['*'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports top level wildcard with a censor function', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({
|
||||
redact: {
|
||||
paths: ['*'],
|
||||
censor: () => '[Redacted]'
|
||||
}
|
||||
}, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports top level wildcard and leading wildcard', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['*', '*.req'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redact – supports intermediate wildcard paths', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.*.cookie'] }, stream)
|
||||
instance.info({
|
||||
req: {
|
||||
id: 7915,
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
host: 'localhost:3000',
|
||||
connection: 'keep-alive',
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
},
|
||||
remoteAddress: '::ffff:127.0.0.1',
|
||||
remotePort: 58022
|
||||
}
|
||||
})
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redacts numbers at the top level', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['id'] }, stream)
|
||||
const obj = {
|
||||
id: 7915
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.id, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redacts booleans at the top level', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['maybe'] }, stream)
|
||||
const obj = {
|
||||
maybe: true
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.maybe, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redacts strings at the top level', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['s'] }, stream)
|
||||
const obj = {
|
||||
s: 's'
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.s, '[Redacted]')
|
||||
})
|
||||
|
||||
test('does not redact primitives if not objects', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['a.b'] }, stream)
|
||||
const obj = {
|
||||
a: 42
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.a, 42)
|
||||
})
|
||||
|
||||
test('redacts null at the top level', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['n'] }, stream)
|
||||
const obj = {
|
||||
n: null
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.n, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports bracket notation', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['a["b.b"]'] }, stream)
|
||||
const obj = {
|
||||
a: { 'b.b': 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.a['b.b'], '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports bracket notation with further nesting', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['a["b.b"].c'] }, stream)
|
||||
const obj = {
|
||||
a: { 'b.b': { c: 'd' } }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.a['b.b'].c, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports bracket notation with empty string as path segment', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['a[""].c'] }, stream)
|
||||
const obj = {
|
||||
a: { '': { c: 'd' } }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.a[''].c, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports leading bracket notation (single quote)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['[\'a.a\'].b'] }, stream)
|
||||
const obj = {
|
||||
'a.a': { b: 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o['a.a'].b, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports leading bracket notation (double quote)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['["a.a"].b'] }, stream)
|
||||
const obj = {
|
||||
'a.a': { b: 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o['a.a'].b, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports leading bracket notation (backtick quote)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['[`a.a`].b'] }, stream)
|
||||
const obj = {
|
||||
'a.a': { b: 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o['a.a'].b, '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports leading bracket notation (single-segment path)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['[`a.a`]'] }, stream)
|
||||
const obj = {
|
||||
'a.a': { b: 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o['a.a'], '[Redacted]')
|
||||
})
|
||||
|
||||
test('supports leading bracket notation (single-segment path, wildcard)', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['[*]'] }, stream)
|
||||
const obj = {
|
||||
'a.a': { b: 'c' }
|
||||
}
|
||||
instance.info(obj)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o['a.a'], '[Redacted]')
|
||||
})
|
||||
|
||||
test('child bindings are redacted using wildcard path', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['*.headers.cookie'] }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
}
|
||||
}
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
})
|
||||
|
||||
test('child bindings are redacted using wildcard and plain path keys', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
}
|
||||
}
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, '[Redacted]')
|
||||
equal(req.method, '[Redacted]')
|
||||
})
|
||||
|
||||
test('redacts boolean at the top level', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['msg'] }, stream)
|
||||
const obj = {
|
||||
s: 's'
|
||||
}
|
||||
instance.info(obj, true)
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.s, 's')
|
||||
equal(o.msg, '[Redacted]')
|
||||
})
|
||||
|
||||
test('child can customize redact', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
redact: ['req.url']
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;')
|
||||
equal(req.method, 'GET')
|
||||
equal(req.url, '[Redacted]')
|
||||
})
|
||||
|
||||
test('child can remove parent redact by array', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: ['req.method', '*.headers.cookie'] }, stream)
|
||||
instance.child({
|
||||
req: {
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
cookie: 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
redact: []
|
||||
}).info('message completed')
|
||||
const { req } = await once(stream, 'data')
|
||||
equal(req.headers.cookie, 'SESSID=298zf09hf012fh2; csrftoken=u32t4o3tb3gg43; _gat=1;')
|
||||
equal(req.method, 'GET')
|
||||
})
|
||||
|
||||
test('redact safe stringify', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ redact: { paths: ['that.secret'] } }, stream)
|
||||
|
||||
instance.info({
|
||||
that: {
|
||||
secret: 'please hide me',
|
||||
myBigInt: 123n
|
||||
},
|
||||
other: {
|
||||
mySecondBigInt: 222n
|
||||
}
|
||||
})
|
||||
const { that, other } = await once(stream, 'data')
|
||||
equal(that.secret, '[Redacted]')
|
||||
equal(that.myBigInt, 123)
|
||||
equal(other.mySecondBigInt, 222)
|
||||
})
|
||||
@@ -0,0 +1,253 @@
|
||||
'use strict'
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const stdSerializers = require('pino-std-serializers')
|
||||
const pino = require('../')
|
||||
|
||||
const parentSerializers = {
|
||||
test: () => 'parent'
|
||||
}
|
||||
|
||||
const childSerializers = {
|
||||
test: () => 'child'
|
||||
}
|
||||
|
||||
test('default err namespace error serializer', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino(stream)
|
||||
|
||||
parent.info({ err: ReferenceError('test') })
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o.err, 'object')
|
||||
equal(o.err.type, 'ReferenceError')
|
||||
equal(o.err.message, 'test')
|
||||
equal(typeof o.err.stack, 'string')
|
||||
})
|
||||
|
||||
test('custom serializer overrides default err namespace error serializer', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({
|
||||
serializers: {
|
||||
err: (e) => ({
|
||||
t: e.constructor.name,
|
||||
m: e.message,
|
||||
s: e.stack
|
||||
})
|
||||
}
|
||||
}, stream)
|
||||
|
||||
parent.info({ err: ReferenceError('test') })
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o.err, 'object')
|
||||
equal(o.err.t, 'ReferenceError')
|
||||
equal(o.err.m, 'test')
|
||||
equal(typeof o.err.s, 'string')
|
||||
})
|
||||
|
||||
test('custom serializer overrides default err namespace error serializer when nestedKey is on', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({
|
||||
nestedKey: 'obj',
|
||||
serializers: {
|
||||
err: (e) => {
|
||||
return {
|
||||
t: e.constructor.name,
|
||||
m: e.message,
|
||||
s: e.stack
|
||||
}
|
||||
}
|
||||
}
|
||||
}, stream)
|
||||
|
||||
parent.info({ err: ReferenceError('test') })
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o.obj.err, 'object')
|
||||
equal(o.obj.err.t, 'ReferenceError')
|
||||
equal(o.obj.err.m, 'test')
|
||||
equal(typeof o.obj.err.s, 'string')
|
||||
})
|
||||
|
||||
test('null overrides default err namespace error serializer', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({ serializers: { err: null } }, stream)
|
||||
|
||||
parent.info({ err: ReferenceError('test') })
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o.err, 'object')
|
||||
equal(typeof o.err.type, 'undefined')
|
||||
equal(typeof o.err.message, 'undefined')
|
||||
equal(typeof o.err.stack, 'undefined')
|
||||
})
|
||||
|
||||
test('undefined overrides default err namespace error serializer', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({ serializers: { err: undefined } }, stream)
|
||||
|
||||
parent.info({ err: ReferenceError('test') })
|
||||
const o = await once(stream, 'data')
|
||||
equal(typeof o.err, 'object')
|
||||
equal(typeof o.err.type, 'undefined')
|
||||
equal(typeof o.err.message, 'undefined')
|
||||
equal(typeof o.err.stack, 'undefined')
|
||||
})
|
||||
|
||||
test('serializers override values', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({ serializers: parentSerializers }, stream)
|
||||
parent.child({}, { serializers: childSerializers })
|
||||
|
||||
parent.fatal({ test: 'test' })
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.test, 'parent')
|
||||
})
|
||||
|
||||
test('child does not overwrite parent serializers', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({ serializers: parentSerializers }, stream)
|
||||
const child = parent.child({}, { serializers: childSerializers })
|
||||
|
||||
parent.fatal({ test: 'test' })
|
||||
|
||||
const o = once(stream, 'data')
|
||||
equal((await o).test, 'parent')
|
||||
const o2 = once(stream, 'data')
|
||||
child.fatal({ test: 'test' })
|
||||
equal((await o2).test, 'child')
|
||||
})
|
||||
|
||||
test('Symbol.for(\'pino.serializers\')', async ({ equal, same, not }) => {
|
||||
const stream = sink()
|
||||
const expected = Object.assign({
|
||||
err: stdSerializers.err
|
||||
}, parentSerializers)
|
||||
const parent = pino({ serializers: parentSerializers }, stream)
|
||||
const child = parent.child({ a: 'property' })
|
||||
|
||||
same(parent[Symbol.for('pino.serializers')], expected)
|
||||
same(child[Symbol.for('pino.serializers')], expected)
|
||||
equal(parent[Symbol.for('pino.serializers')], child[Symbol.for('pino.serializers')])
|
||||
|
||||
const child2 = parent.child({}, {
|
||||
serializers: {
|
||||
a
|
||||
}
|
||||
})
|
||||
|
||||
function a () {
|
||||
return 'hello'
|
||||
}
|
||||
|
||||
not(child2[Symbol.for('pino.serializers')], parentSerializers)
|
||||
equal(child2[Symbol.for('pino.serializers')].a, a)
|
||||
equal(child2[Symbol.for('pino.serializers')].test, parentSerializers.test)
|
||||
})
|
||||
|
||||
test('children inherit parent serializers', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({ serializers: parentSerializers }, stream)
|
||||
|
||||
const child = parent.child({ a: 'property' })
|
||||
child.fatal({ test: 'test' })
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.test, 'parent')
|
||||
})
|
||||
|
||||
test('children inherit parent Symbol serializers', async ({ equal, same, not }) => {
|
||||
const stream = sink()
|
||||
const symbolSerializers = {
|
||||
[Symbol.for('b')]: b
|
||||
}
|
||||
const expected = Object.assign({
|
||||
err: stdSerializers.err
|
||||
}, symbolSerializers)
|
||||
const parent = pino({ serializers: symbolSerializers }, stream)
|
||||
|
||||
same(parent[Symbol.for('pino.serializers')], expected)
|
||||
|
||||
const child = parent.child({}, {
|
||||
serializers: {
|
||||
[Symbol.for('a')]: a,
|
||||
a
|
||||
}
|
||||
})
|
||||
|
||||
function a () {
|
||||
return 'hello'
|
||||
}
|
||||
|
||||
function b () {
|
||||
return 'world'
|
||||
}
|
||||
|
||||
same(child[Symbol.for('pino.serializers')].a, a)
|
||||
same(child[Symbol.for('pino.serializers')][Symbol.for('b')], b)
|
||||
same(child[Symbol.for('pino.serializers')][Symbol.for('a')], a)
|
||||
})
|
||||
|
||||
test('children serializers get called', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({
|
||||
test: 'this'
|
||||
}, stream)
|
||||
|
||||
const child = parent.child({ a: 'property' }, { serializers: childSerializers })
|
||||
|
||||
child.fatal({ test: 'test' })
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.test, 'child')
|
||||
})
|
||||
|
||||
test('children serializers get called when inherited from parent', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const parent = pino({
|
||||
test: 'this',
|
||||
serializers: parentSerializers
|
||||
}, stream)
|
||||
|
||||
const child = parent.child({}, { serializers: { test: function () { return 'pass' } } })
|
||||
|
||||
child.fatal({ test: 'fail' })
|
||||
const o = await once(stream, 'data')
|
||||
equal(o.test, 'pass')
|
||||
})
|
||||
|
||||
test('non-overridden serializers are available in the children', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const pSerializers = {
|
||||
onlyParent: function () { return 'parent' },
|
||||
shared: function () { return 'parent' }
|
||||
}
|
||||
|
||||
const cSerializers = {
|
||||
shared: function () { return 'child' },
|
||||
onlyChild: function () { return 'child' }
|
||||
}
|
||||
|
||||
const parent = pino({ serializers: pSerializers }, stream)
|
||||
|
||||
const child = parent.child({}, { serializers: cSerializers })
|
||||
|
||||
const o = once(stream, 'data')
|
||||
child.fatal({ shared: 'test' })
|
||||
equal((await o).shared, 'child')
|
||||
const o2 = once(stream, 'data')
|
||||
child.fatal({ onlyParent: 'test' })
|
||||
equal((await o2).onlyParent, 'parent')
|
||||
const o3 = once(stream, 'data')
|
||||
child.fatal({ onlyChild: 'test' })
|
||||
equal((await o3).onlyChild, 'child')
|
||||
const o4 = once(stream, 'data')
|
||||
parent.fatal({ onlyChild: 'test' })
|
||||
equal((await o4).onlyChild, 'test')
|
||||
})
|
||||
|
||||
test('custom serializer for messageKey', async (t) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ serializers: { msg: () => '422' } }, stream)
|
||||
|
||||
const o = { num: NaN }
|
||||
instance.info(o, 42)
|
||||
|
||||
const { msg } = await once(stream, 'data')
|
||||
t.equal(msg, '422')
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { join } = require('path')
|
||||
const { fork } = require('child_process')
|
||||
const { once } = require('./helper')
|
||||
const writer = require('flush-write-stream')
|
||||
const pino = require('..')
|
||||
|
||||
test('do not use SonicBoom is someone tampered with process.stdout.write', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = fork(join(__dirname, 'fixtures', 'stdout-hack-protection.js'), { silent: true })
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
not(actual.match(/^hack/), null)
|
||||
})
|
||||
|
||||
test('do not use SonicBoom is someone has passed process.stdout to pino', async ({ equal }) => {
|
||||
const logger = pino(process.stdout)
|
||||
equal(logger[pino.symbols.streamSym], process.stdout)
|
||||
})
|
||||
|
||||
test('do not crash if process.stdout has no fd', async ({ teardown }) => {
|
||||
const fd = process.stdout.fd
|
||||
delete process.stdout.fd
|
||||
teardown(function () { process.stdout.fd = fd })
|
||||
pino()
|
||||
})
|
||||
|
||||
test('use fd=1 if process.stdout has no fd in pino.destination() (worker case)', async ({ teardown }) => {
|
||||
const fd = process.stdout.fd
|
||||
delete process.stdout.fd
|
||||
teardown(function () { process.stdout.fd = fd })
|
||||
pino.destination()
|
||||
})
|
||||
@@ -0,0 +1,191 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const {
|
||||
createWriteStream
|
||||
} = require('fs')
|
||||
const { readFile } = require('fs').promises
|
||||
const { join } = require('path')
|
||||
const { test } = require('tap')
|
||||
const { fork } = require('child_process')
|
||||
const writer = require('flush-write-stream')
|
||||
const {
|
||||
once,
|
||||
getPathToNull,
|
||||
file,
|
||||
watchFileCreated
|
||||
} = require('./helper')
|
||||
const { promisify } = require('util')
|
||||
|
||||
const sleep = promisify(setTimeout)
|
||||
|
||||
test('asynchronous logging', async ({
|
||||
equal,
|
||||
teardown
|
||||
}) => {
|
||||
const now = Date.now
|
||||
const hostname = os.hostname
|
||||
const proc = process
|
||||
global.process = {
|
||||
__proto__: process,
|
||||
pid: 123456
|
||||
}
|
||||
Date.now = () => 1459875739796
|
||||
os.hostname = () => 'abcdefghijklmnopqr'
|
||||
delete require.cache[require.resolve('../')]
|
||||
const pino = require('../')
|
||||
let expected = ''
|
||||
let actual = ''
|
||||
const normal = pino(writer((s, enc, cb) => {
|
||||
expected += s
|
||||
cb()
|
||||
}))
|
||||
|
||||
const dest = createWriteStream(getPathToNull())
|
||||
dest.write = (s) => {
|
||||
actual += s
|
||||
}
|
||||
const asyncLogger = pino(dest)
|
||||
|
||||
let i = 44
|
||||
while (i--) {
|
||||
normal.info('h')
|
||||
asyncLogger.info('h')
|
||||
}
|
||||
|
||||
const expected2 = expected.split('\n')[0]
|
||||
let actual2 = ''
|
||||
|
||||
const child = fork(join(__dirname, '/fixtures/syncfalse.js'), { silent: true })
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual2 += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
// Wait for the last write to be flushed
|
||||
await sleep(100)
|
||||
equal(actual, expected)
|
||||
equal(actual2.trim(), expected2)
|
||||
|
||||
teardown(() => {
|
||||
os.hostname = hostname
|
||||
Date.now = now
|
||||
global.process = proc
|
||||
})
|
||||
})
|
||||
|
||||
test('sync false with child', async ({
|
||||
equal,
|
||||
teardown
|
||||
}) => {
|
||||
const now = Date.now
|
||||
const hostname = os.hostname
|
||||
const proc = process
|
||||
global.process = {
|
||||
__proto__: process,
|
||||
pid: 123456
|
||||
}
|
||||
Date.now = function () {
|
||||
return 1459875739796
|
||||
}
|
||||
os.hostname = function () {
|
||||
return 'abcdefghijklmnopqr'
|
||||
}
|
||||
delete require.cache[require.resolve('../')]
|
||||
const pino = require('../')
|
||||
let expected = ''
|
||||
let actual = ''
|
||||
const normal = pino(writer((s, enc, cb) => {
|
||||
expected += s
|
||||
cb()
|
||||
})).child({ hello: 'world' })
|
||||
|
||||
const dest = createWriteStream(getPathToNull())
|
||||
dest.write = function (s) {
|
||||
actual += s
|
||||
}
|
||||
const asyncLogger = pino(dest).child({ hello: 'world' })
|
||||
|
||||
let i = 500
|
||||
while (i--) {
|
||||
normal.info('h')
|
||||
asyncLogger.info('h')
|
||||
}
|
||||
|
||||
asyncLogger.flush()
|
||||
|
||||
const expected2 = expected.split('\n')[0]
|
||||
let actual2 = ''
|
||||
|
||||
const child = fork(join(__dirname, '/fixtures/syncfalse-child.js'), { silent: true })
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual2 += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
equal(actual, expected)
|
||||
equal(actual2.trim(), expected2)
|
||||
|
||||
teardown(() => {
|
||||
os.hostname = hostname
|
||||
Date.now = now
|
||||
global.process = proc
|
||||
})
|
||||
})
|
||||
|
||||
test('flush does nothing with sync true (default)', async ({ equal }) => {
|
||||
const instance = require('..')()
|
||||
equal(instance.flush(), undefined)
|
||||
})
|
||||
|
||||
test('should still call flush callback even when does nothing with sync true (default)', (t) => {
|
||||
t.plan(3)
|
||||
const instance = require('..')()
|
||||
instance.flush((...args) => {
|
||||
t.ok('flush called')
|
||||
t.same(args, [])
|
||||
|
||||
// next tick to make flush not called more than once
|
||||
process.nextTick(() => {
|
||||
t.ok('flush next tick called')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('should call the flush callback when flushed the data for async logger', async (t) => {
|
||||
const outputPath = file()
|
||||
async function getOutputLogLines () {
|
||||
return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse)
|
||||
}
|
||||
|
||||
const pino = require('../')
|
||||
|
||||
const instance = pino({}, pino.destination({
|
||||
dest: outputPath,
|
||||
|
||||
// to make sure it does not flush on its own
|
||||
minLength: 4096
|
||||
}))
|
||||
const flushPromise = promisify(instance.flush).bind(instance)
|
||||
|
||||
instance.info('hello')
|
||||
await flushPromise()
|
||||
await watchFileCreated(outputPath)
|
||||
|
||||
const [firstFlushData] = await getOutputLogLines()
|
||||
|
||||
t.equal(firstFlushData.msg, 'hello')
|
||||
|
||||
// should not flush this as no data accumulated that's bigger than min length
|
||||
instance.info('world')
|
||||
|
||||
// Making sure data is not flushed yet
|
||||
const afterLogData = await getOutputLogLines()
|
||||
t.equal(afterLogData.length, 1)
|
||||
|
||||
await flushPromise()
|
||||
|
||||
// Making sure data is not flushed yet
|
||||
const afterSecondFlush = (await getOutputLogLines())[1]
|
||||
t.equal(afterSecondFlush.msg, 'world')
|
||||
})
|
||||
@@ -0,0 +1,121 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint no-prototype-builtins: 0 */
|
||||
|
||||
const { test } = require('tap')
|
||||
const { sink, once } = require('./helper')
|
||||
const pino = require('../')
|
||||
|
||||
test('pino exposes standard time functions', async ({ ok }) => {
|
||||
ok(pino.stdTimeFunctions)
|
||||
ok(pino.stdTimeFunctions.epochTime)
|
||||
ok(pino.stdTimeFunctions.unixTime)
|
||||
ok(pino.stdTimeFunctions.nullTime)
|
||||
ok(pino.stdTimeFunctions.isoTime)
|
||||
})
|
||||
|
||||
test('pino accepts external time functions', async ({ equal }) => {
|
||||
const opts = {
|
||||
timestamp: () => ',"time":"none"'
|
||||
}
|
||||
const stream = sink()
|
||||
const instance = pino(opts, stream)
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
equal(result.time, 'none')
|
||||
})
|
||||
|
||||
test('pino accepts external time functions with custom label', async ({ equal }) => {
|
||||
const opts = {
|
||||
timestamp: () => ',"custom-time-label":"none"'
|
||||
}
|
||||
const stream = sink()
|
||||
const instance = pino(opts, stream)
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('custom-time-label'), true)
|
||||
equal(result['custom-time-label'], 'none')
|
||||
})
|
||||
|
||||
test('inserts timestamp by default', async ({ ok, equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino(stream)
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than timestamp')
|
||||
equal(result.msg, 'foobar')
|
||||
})
|
||||
|
||||
test('omits timestamp when timestamp option is false', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ timestamp: false }, stream)
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), false)
|
||||
equal(result.msg, 'foobar')
|
||||
})
|
||||
|
||||
test('inserts timestamp when timestamp option is true', async ({ ok, equal }) => {
|
||||
const stream = sink()
|
||||
const instance = pino({ timestamp: true }, stream)
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than timestamp')
|
||||
equal(result.msg, 'foobar')
|
||||
})
|
||||
|
||||
test('child inserts timestamp by default', async ({ ok, equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino(stream)
|
||||
const instance = logger.child({ component: 'child' })
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
ok(new Date(result.time) <= new Date(), 'time is greater than timestamp')
|
||||
equal(result.msg, 'foobar')
|
||||
})
|
||||
|
||||
test('child omits timestamp with option', async ({ equal }) => {
|
||||
const stream = sink()
|
||||
const logger = pino({ timestamp: false }, stream)
|
||||
const instance = logger.child({ component: 'child' })
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), false)
|
||||
equal(result.msg, 'foobar')
|
||||
})
|
||||
|
||||
test('pino.stdTimeFunctions.unixTime returns seconds based timestamps', async ({ equal }) => {
|
||||
const opts = {
|
||||
timestamp: pino.stdTimeFunctions.unixTime
|
||||
}
|
||||
const stream = sink()
|
||||
const instance = pino(opts, stream)
|
||||
const now = Date.now
|
||||
Date.now = () => 1531069919686
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
equal(result.time, 1531069920)
|
||||
Date.now = now
|
||||
})
|
||||
|
||||
test('pino.stdTimeFunctions.isoTime returns ISO 8601 timestamps', async ({ equal }) => {
|
||||
const opts = {
|
||||
timestamp: pino.stdTimeFunctions.isoTime
|
||||
}
|
||||
const stream = sink()
|
||||
const instance = pino(opts, stream)
|
||||
const ms = 1531069919686
|
||||
const now = Date.now
|
||||
Date.now = () => ms
|
||||
const iso = new Date(ms).toISOString()
|
||||
instance.info('foobar')
|
||||
const result = await once(stream, 'data')
|
||||
equal(result.hasOwnProperty('time'), true)
|
||||
equal(result.time, iso)
|
||||
Date.now = now
|
||||
})
|
||||
@@ -0,0 +1,43 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const { join } = require('path')
|
||||
const { createReadStream } = require('fs')
|
||||
const { promisify } = require('util')
|
||||
const execa = require('execa')
|
||||
const split = require('split2')
|
||||
const stream = require('stream')
|
||||
const { file } = require('../helper')
|
||||
|
||||
const pipeline = promisify(stream.pipeline)
|
||||
const { Writable } = stream
|
||||
const sleep = promisify(setTimeout)
|
||||
|
||||
const skip = process.env.CI || process.env.CITGM
|
||||
|
||||
test('eight million lines', { skip }, async ({ equal, comment }) => {
|
||||
const destination = file()
|
||||
await execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-many-lines.js'), destination])
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
await execa('sync') // Wait for the file to be written to disk
|
||||
} catch {
|
||||
// Just a fallback, this should be unreachable
|
||||
}
|
||||
}
|
||||
await sleep(1000) // It seems that sync is not enough (even in POSIX systems)
|
||||
|
||||
const toWrite = 8 * 1000000
|
||||
let count = 0
|
||||
await pipeline(createReadStream(destination), split(), new Writable({
|
||||
write (chunk, enc, cb) {
|
||||
if (count % (toWrite / 10) === 0) {
|
||||
comment(`read ${count}`)
|
||||
}
|
||||
count++
|
||||
cb()
|
||||
}
|
||||
}))
|
||||
equal(count, toWrite)
|
||||
})
|
||||
@@ -0,0 +1,127 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { join } = require('path')
|
||||
const { readFile } = require('fs').promises
|
||||
const { watchFileCreated, file } = require('../helper')
|
||||
const { test } = require('tap')
|
||||
const pino = require('../../pino')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('pino.transport with destination overridden by bundler', async ({ same, teardown }) => {
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
foobar: join(__dirname, '..', 'fixtures', 'to-file-transport.js')
|
||||
}
|
||||
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: 'foobar',
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
|
||||
globalThis.__bundlerPathsOverrides = undefined
|
||||
})
|
||||
|
||||
test('pino.transport with worker destination overridden by bundler', async ({ same, teardown }) => {
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
'pino-worker': join(__dirname, '..', '..', 'lib/worker.js')
|
||||
}
|
||||
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
}
|
||||
]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
|
||||
globalThis.__bundlerPathsOverrides = undefined
|
||||
})
|
||||
|
||||
test('pino.transport with worker destination overridden by bundler and mjs transport', async ({ same, teardown }) => {
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
'pino-worker': join(__dirname, '..', '..', 'lib/worker.js')
|
||||
}
|
||||
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
targets: [
|
||||
{
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.es2017.cjs'),
|
||||
options: { destination }
|
||||
}
|
||||
]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
|
||||
globalThis.__bundlerPathsOverrides = undefined
|
||||
})
|
||||
|
||||
test('pino.transport with worker-pipeline destination overridden by bundler', async ({ same, teardown }) => {
|
||||
globalThis.__bundlerPathsOverrides = {
|
||||
'pino-pipeline-worker': join(__dirname, '..', '..', 'lib/worker-pipeline.js')
|
||||
}
|
||||
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
pipeline: [
|
||||
{
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
}
|
||||
]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
|
||||
globalThis.__bundlerPathsOverrides = undefined
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const { join } = require('path')
|
||||
const { test } = require('tap')
|
||||
const execa = require('execa')
|
||||
|
||||
test('when using a custom transport outside node_modules, the first file outside node_modules should be used', async function (t) {
|
||||
const evalApp = join(__dirname, '../', '/fixtures/eval/index.js')
|
||||
const { stdout } = await execa(process.argv[0], [evalApp])
|
||||
t.match(stdout, /done!/)
|
||||
})
|
||||
|
||||
test('when using a custom transport where some files in stacktrace are in the node_modules, the first file outside node_modules should be used', async function (t) {
|
||||
const evalApp = join(__dirname, '../', '/fixtures/eval/node_modules/2-files.js')
|
||||
const { stdout } = await execa(process.argv[0], [evalApp])
|
||||
t.match(stdout, /done!/)
|
||||
})
|
||||
|
||||
test('when using a custom transport where all files in stacktrace are in the node_modules, the first file inside node_modules should be used', async function (t) {
|
||||
const evalApp = join(__dirname, '../', '/fixtures/eval/node_modules/14-files.js')
|
||||
const { stdout } = await execa(process.argv[0], [evalApp])
|
||||
t.match(stdout, /done!/)
|
||||
})
|
||||
@@ -0,0 +1,624 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { join } = require('path')
|
||||
const { once } = require('events')
|
||||
const { readFile, writeFile } = require('fs').promises
|
||||
const { watchFileCreated, watchForWrite, file } = require('../helper')
|
||||
const { test } = require('tap')
|
||||
const pino = require('../../')
|
||||
const url = require('url')
|
||||
const strip = require('strip-ansi')
|
||||
const execa = require('execa')
|
||||
const writer = require('flush-write-stream')
|
||||
const rimraf = require('rimraf')
|
||||
const { promisify } = require('util')
|
||||
const { tmpdir } = os
|
||||
|
||||
const immediate = promisify(setImmediate)
|
||||
const pid = process.pid
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('pino.transport with file', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with file (no options + error handling)', async ({ equal }) => {
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js')
|
||||
})
|
||||
const [err] = await once(transport, 'error')
|
||||
equal(err.message, 'kaboom')
|
||||
})
|
||||
|
||||
test('pino.transport with file URL', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: url.pathToFileURL(join(__dirname, '..', 'fixtures', 'to-file-transport.js')).href,
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport errors if file does not exists', ({ plan, pass }) => {
|
||||
plan(1)
|
||||
const instance = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'non-existent-file'),
|
||||
worker: {
|
||||
stdin: true,
|
||||
stdout: true,
|
||||
stderr: true
|
||||
}
|
||||
})
|
||||
instance.on('error', function () {
|
||||
pass('error received')
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport errors if transport worker module does not export a function', ({ plan, equal }) => {
|
||||
// TODO: add case for non-pipelined single target (needs changes in thread-stream)
|
||||
plan(2)
|
||||
const manyTargetsInstance = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js')
|
||||
}, {
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js')
|
||||
}]
|
||||
})
|
||||
manyTargetsInstance.on('error', function (e) {
|
||||
equal(e.message, 'exported worker is not a function')
|
||||
})
|
||||
|
||||
const pipelinedInstance = pino.transport({
|
||||
pipeline: [{
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-wrong-export-type.js')
|
||||
}]
|
||||
})
|
||||
pipelinedInstance.on('error', function (e) {
|
||||
equal(e.message, 'exported worker is not a function')
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with esm', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.mjs'),
|
||||
options: { destination }
|
||||
})
|
||||
const instance = pino(transport)
|
||||
teardown(transport.end.bind(transport))
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with two files', async ({ same, teardown }) => {
|
||||
const dest1 = file()
|
||||
const dest2 = file()
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest1 }
|
||||
}, {
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest2 }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
const result1 = JSON.parse(await readFile(dest1))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with two files and custom levels', async ({ same, teardown }) => {
|
||||
const dest1 = file()
|
||||
const dest2 = file()
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest1 }
|
||||
}, {
|
||||
level: 'foo',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest2 }
|
||||
}],
|
||||
levels: { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60, foo: 25 }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
const result1 = JSON.parse(await readFile(dest1))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport without specifying default levels', async ({ same, teardown }) => {
|
||||
const dest = file()
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'foo',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest }
|
||||
}],
|
||||
levels: { foo: 25 }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await Promise.all([watchFileCreated(dest)])
|
||||
const result1 = JSON.parse(await readFile(dest))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with two files and dedupe', async ({ same, teardown }) => {
|
||||
const dest1 = file()
|
||||
const dest2 = file()
|
||||
const transport = pino.transport({
|
||||
dedupe: true,
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest1 }
|
||||
}, {
|
||||
level: 'error',
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: dest2 }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
instance.error('world')
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
const result1 = JSON.parse(await readFile(dest1))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 50,
|
||||
msg: 'world'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with an array including a pino-pretty destination', async ({ same, match, teardown }) => {
|
||||
const dest1 = file()
|
||||
const dest2 = file()
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: 'pino/file',
|
||||
options: {
|
||||
destination: dest1
|
||||
}
|
||||
}, {
|
||||
level: 'info',
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
destination: dest2
|
||||
}
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
const result1 = JSON.parse(await readFile(dest1))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const actual = (await readFile(dest2)).toString()
|
||||
match(strip(actual), /\[.*\] INFO.*hello/)
|
||||
})
|
||||
|
||||
test('no transport.end()', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
})
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('autoEnd = false', async ({ equal, same, teardown }) => {
|
||||
const destination = file()
|
||||
const count = process.listenerCount('exit')
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination },
|
||||
worker: { autoEnd: false }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
await once(transport, 'ready')
|
||||
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
|
||||
await watchFileCreated(destination)
|
||||
|
||||
equal(count, process.listenerCount('exit'))
|
||||
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with target and targets', async ({ fail, equal }) => {
|
||||
try {
|
||||
pino.transport({
|
||||
target: '/a/file',
|
||||
targets: [{
|
||||
target: '/a/file'
|
||||
}]
|
||||
})
|
||||
fail('must throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'only one of target or targets can be specified')
|
||||
}
|
||||
})
|
||||
|
||||
test('pino.transport with target pino/file', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with target pino/file and mkdir option', async ({ same, teardown }) => {
|
||||
const folder = join(tmpdir(), `pino-${process.pid}-mkdir-transport-file`)
|
||||
const destination = join(folder, 'log.txt')
|
||||
teardown(() => {
|
||||
try {
|
||||
rimraf.sync(folder)
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
})
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination, mkdir: true }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with target pino/file and append option', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
await writeFile(destination, JSON.stringify({ pid, hostname, time: Date.now(), level: 30, msg: 'hello' }))
|
||||
const transport = pino.transport({
|
||||
target: 'pino/file',
|
||||
options: { destination, append: false }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('goodbye')
|
||||
await watchForWrite(destination, '"goodbye"')
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'goodbye'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport should error with unknown target', async ({ fail, equal }) => {
|
||||
try {
|
||||
pino.transport({
|
||||
target: 'origin',
|
||||
caller: 'unknown-file.js'
|
||||
})
|
||||
fail('must throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'unable to determine transport target for "origin"')
|
||||
}
|
||||
})
|
||||
|
||||
test('pino.transport with target pino-pretty', async ({ match, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: 'pino-pretty',
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const actual = await readFile(destination, 'utf8')
|
||||
match(strip(actual), /\[.*\] INFO.*hello/)
|
||||
})
|
||||
|
||||
test('sets worker data informing the transport that pino will send its config', ({ match, plan, teardown }) => {
|
||||
plan(1)
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-worker-data.js')
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
transport.once('workerData', (workerData) => {
|
||||
match(workerData.workerData, { pinoWillSendConfig: true })
|
||||
})
|
||||
instance.info('hello')
|
||||
})
|
||||
|
||||
test('stdout in worker', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-main.js')])
|
||||
|
||||
for await (const chunk of child.stdout) {
|
||||
actual += chunk
|
||||
}
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit on ready', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-on-ready.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
await immediate()
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit before ready', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-immediately.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
await immediate()
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit before ready with async dest', async ({ not }) => {
|
||||
const destination = file()
|
||||
const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-exit-immediately-with-async-dest.js'), destination])
|
||||
|
||||
await once(child, 'exit')
|
||||
|
||||
const actual = await readFile(destination, 'utf8')
|
||||
not(strip(actual).match(/HELLO/), null)
|
||||
not(strip(actual).match(/WORLD/), null)
|
||||
})
|
||||
|
||||
test('string integer destination', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], [join(__dirname, '..', 'fixtures', 'transport-string-stdout.js')])
|
||||
|
||||
child.stdout.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
await immediate()
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('pino transport options with target', async ({ teardown, same }) => {
|
||||
const destination = file()
|
||||
const instance = pino({
|
||||
transport: {
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
}
|
||||
})
|
||||
const transportStream = instance[pino.symbols.streamSym]
|
||||
teardown(transportStream.end.bind(transportStream))
|
||||
instance.info('transport option test')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'transport option test'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino transport options with targets', async ({ teardown, same }) => {
|
||||
const dest1 = file()
|
||||
const dest2 = file()
|
||||
const instance = pino({
|
||||
transport: {
|
||||
targets: [
|
||||
{ target: 'pino/file', options: { destination: dest1 } },
|
||||
{ target: 'pino/file', options: { destination: dest2 } }
|
||||
]
|
||||
}
|
||||
})
|
||||
const transportStream = instance[pino.symbols.streamSym]
|
||||
teardown(transportStream.end.bind(transportStream))
|
||||
instance.info('transport option test')
|
||||
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
const result1 = JSON.parse(await readFile(dest1))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'transport option test'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'transport option test'
|
||||
})
|
||||
})
|
||||
|
||||
test('transport options with target and targets', async ({ fail, equal }) => {
|
||||
try {
|
||||
pino({
|
||||
transport: {
|
||||
target: {},
|
||||
targets: {}
|
||||
}
|
||||
})
|
||||
fail('must throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'only one of target or targets can be specified')
|
||||
}
|
||||
})
|
||||
|
||||
test('transport options with target and stream', async ({ fail, equal }) => {
|
||||
try {
|
||||
pino({
|
||||
transport: {
|
||||
target: {}
|
||||
}
|
||||
}, '/log/null')
|
||||
fail('must throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'only one of option.transport or stream can be specified')
|
||||
}
|
||||
})
|
||||
|
||||
test('transport options with stream', async ({ fail, equal, teardown }) => {
|
||||
try {
|
||||
const dest1 = file()
|
||||
const transportStream = pino.transport({ target: 'pino/file', options: { destination: dest1 } })
|
||||
teardown(transportStream.end.bind(transportStream))
|
||||
pino({
|
||||
transport: transportStream
|
||||
})
|
||||
fail('must throw')
|
||||
} catch (err) {
|
||||
equal(err.message, 'option.transport do not allow stream, please pass to option directly. e.g. pino(transport)')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,236 @@
|
||||
import * as os from 'os'
|
||||
import { join } from 'path'
|
||||
import { once } from 'events'
|
||||
import fs from 'fs'
|
||||
import { watchFileCreated } from '../helper'
|
||||
import { test } from 'tap'
|
||||
import pino from '../../'
|
||||
import * as url from 'url'
|
||||
import { default as strip } from 'strip-ansi'
|
||||
import execa from 'execa'
|
||||
import writer from 'flush-write-stream'
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// TODO: Implement .ts files loading support for Windows
|
||||
process.exit()
|
||||
}
|
||||
|
||||
const readFile = fs.promises.readFile
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('pino.transport with file', async ({ same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts'),
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with file (no options + error handling)', async ({ equal }) => {
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts')
|
||||
})
|
||||
const [err] = await once(transport, 'error')
|
||||
equal(err.message, 'kaboom')
|
||||
})
|
||||
|
||||
test('pino.transport with file URL', async ({ same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
target: url.pathToFileURL(join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts')).href,
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('pino.transport with two files', async ({ same, teardown }) => {
|
||||
const dest1 = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const dest2 = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts'),
|
||||
options: { destination: dest1 }
|
||||
}, {
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts'),
|
||||
options: { destination: dest2 }
|
||||
}]
|
||||
})
|
||||
|
||||
teardown(transport.end.bind(transport))
|
||||
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
|
||||
const result1 = JSON.parse(await readFile(dest1, { encoding: 'utf8' }))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2, { encoding: 'utf8' }))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('no transport.end()', async ({ same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts'),
|
||||
options: { destination }
|
||||
})
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('autoEnd = false', async ({ equal, same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const count = process.listenerCount('exit')
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', 'to-file-transport.ts'),
|
||||
options: { destination },
|
||||
worker: { autoEnd: false }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
await once(transport, 'ready')
|
||||
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
|
||||
await watchFileCreated(destination)
|
||||
|
||||
equal(count, process.listenerCount('exit'))
|
||||
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('stdout in worker', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], ['-r', 'ts-node/register', join(__dirname, '..', 'fixtures', 'ts', 'transport-main.ts')])
|
||||
|
||||
child.stdout?.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit on ready', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], ['-r', 'ts-node/register', join(__dirname, '..', 'fixtures', 'ts', 'transport-exit-on-ready.ts')])
|
||||
|
||||
child.stdout?.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit before ready', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], ['-r', 'ts-node/register', join(__dirname, '..', 'fixtures', 'ts', 'transport-exit-immediately.ts')])
|
||||
|
||||
child.stdout?.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
|
||||
test('log and exit before ready with async dest', async ({ not }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const child = execa(process.argv[0], ['-r', 'ts-node/register', join(__dirname, '..', 'fixtures', 'ts', 'transport-exit-immediately-with-async-dest.ts'), destination])
|
||||
|
||||
await once(child, 'exit')
|
||||
|
||||
const actual = await readFile(destination, { encoding: 'utf8' })
|
||||
|
||||
not(strip(actual).match(/HELLO/), null)
|
||||
not(strip(actual).match(/WORLD/), null)
|
||||
})
|
||||
|
||||
test('string integer destination', async ({ not }) => {
|
||||
let actual = ''
|
||||
const child = execa(process.argv[0], ['-r', 'ts-node/register', join(__dirname, '..', 'fixtures', 'ts', 'transport-string-stdout.ts')])
|
||||
|
||||
child.stdout?.pipe(writer((s, enc, cb) => {
|
||||
actual += s
|
||||
cb()
|
||||
}))
|
||||
await once(child, 'close')
|
||||
not(strip(actual).match(/Hello/), null)
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import * as os from 'os'
|
||||
import { join } from 'path'
|
||||
import { once } from 'events'
|
||||
import fs from 'fs'
|
||||
import { watchFileCreated } from '../helper'
|
||||
import { test } from 'tap'
|
||||
import pino from '../../'
|
||||
import * as url from 'url'
|
||||
import { default as strip } from 'strip-ansi'
|
||||
import execa from 'execa'
|
||||
import writer from 'flush-write-stream'
|
||||
|
||||
const readFile = fs.promises.readFile
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
// A subset of the test from core.test.js, we don't need all of them to check for compatibility
|
||||
function runTests(esVersion: string): void {
|
||||
test(`(ts -> ${esVersion}) pino.transport with file`, async ({ same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`),
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test(`(ts -> ${esVersion}) pino.transport with file URL`, async ({ same, teardown }) => {
|
||||
const destination = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
target: url.pathToFileURL(join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`)).href,
|
||||
options: { destination }
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination, { encoding: 'utf8' }))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test(`(ts -> ${esVersion}) pino.transport with two files`, async ({ same, teardown }) => {
|
||||
const dest1 = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const dest2 = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`),
|
||||
options: { destination: dest1 }
|
||||
}, {
|
||||
level: 'info',
|
||||
target: join(__dirname, '..', 'fixtures', 'ts', `to-file-transport.${esVersion}.cjs`),
|
||||
options: { destination: dest2 }
|
||||
}]
|
||||
})
|
||||
|
||||
teardown(transport.end.bind(transport))
|
||||
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
|
||||
await Promise.all([watchFileCreated(dest1), watchFileCreated(dest2)])
|
||||
|
||||
const result1 = JSON.parse(await readFile(dest1, { encoding: 'utf8' }))
|
||||
delete result1.time
|
||||
same(result1, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
const result2 = JSON.parse(await readFile(dest2, { encoding: 'utf8' }))
|
||||
delete result2.time
|
||||
same(result2, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
runTests('es5')
|
||||
runTests('es6')
|
||||
runTests('es2017')
|
||||
runTests('esnext')
|
||||
@@ -0,0 +1,239 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { join } = require('path')
|
||||
const { readFile, symlink, unlink, mkdir, writeFile } = require('fs').promises
|
||||
const { test } = require('tap')
|
||||
const { isWin, isYarnPnp, watchFileCreated, file } = require('../helper')
|
||||
const { once } = require('events')
|
||||
const execa = require('execa')
|
||||
const pino = require('../../')
|
||||
const rimraf = require('rimraf')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
async function installTransportModule (target) {
|
||||
if (isYarnPnp) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await uninstallTransportModule()
|
||||
} catch {}
|
||||
|
||||
if (!target) {
|
||||
target = join(__dirname, '..', '..')
|
||||
}
|
||||
|
||||
await symlink(
|
||||
join(__dirname, '..', 'fixtures', 'transport'),
|
||||
join(target, 'node_modules', 'transport')
|
||||
)
|
||||
}
|
||||
|
||||
async function uninstallTransportModule () {
|
||||
if (isYarnPnp) {
|
||||
return
|
||||
}
|
||||
await unlink(join(__dirname, '..', '..', 'node_modules', 'transport'))
|
||||
}
|
||||
|
||||
// TODO make this test pass on Windows
|
||||
test('pino.transport with package', { skip: isWin }, async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
|
||||
await installTransportModule()
|
||||
|
||||
const transport = pino.transport({
|
||||
target: 'transport',
|
||||
options: { destination }
|
||||
})
|
||||
|
||||
teardown(async () => {
|
||||
await uninstallTransportModule()
|
||||
transport.end()
|
||||
})
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
// TODO make this test pass on Windows
|
||||
test('pino.transport with package as a target', { skip: isWin }, async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
|
||||
await installTransportModule()
|
||||
|
||||
const transport = pino.transport({
|
||||
targets: [{
|
||||
target: 'transport',
|
||||
options: { destination }
|
||||
}]
|
||||
})
|
||||
teardown(async () => {
|
||||
await uninstallTransportModule()
|
||||
transport.end()
|
||||
})
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
// TODO make this test pass on Windows
|
||||
test('pino({ transport })', { skip: isWin || isYarnPnp }, async ({ same, teardown }) => {
|
||||
const folder = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
|
||||
teardown(() => {
|
||||
rimraf.sync(folder)
|
||||
})
|
||||
|
||||
const destination = join(folder, 'output')
|
||||
|
||||
await mkdir(join(folder, 'node_modules'), { recursive: true })
|
||||
|
||||
// Link pino
|
||||
await symlink(
|
||||
join(__dirname, '..', '..'),
|
||||
join(folder, 'node_modules', 'pino')
|
||||
)
|
||||
|
||||
await installTransportModule(folder)
|
||||
|
||||
const toRun = join(folder, 'index.js')
|
||||
|
||||
const toRunContent = `
|
||||
const pino = require('pino')
|
||||
const logger = pino({
|
||||
transport: {
|
||||
target: 'transport',
|
||||
options: { destination: '${destination}' }
|
||||
}
|
||||
})
|
||||
logger.info('hello')
|
||||
`
|
||||
|
||||
await writeFile(toRun, toRunContent)
|
||||
|
||||
const child = execa(process.argv[0], [toRun])
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid: child.pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
// TODO make this test pass on Windows
|
||||
test('pino({ transport }) from a wrapped dependency', { skip: isWin || isYarnPnp }, async ({ same, teardown }) => {
|
||||
const folder = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
|
||||
const wrappedFolder = join(
|
||||
os.tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
|
||||
const destination = join(folder, 'output')
|
||||
|
||||
await mkdir(join(folder, 'node_modules'), { recursive: true })
|
||||
await mkdir(join(wrappedFolder, 'node_modules'), { recursive: true })
|
||||
|
||||
teardown(() => {
|
||||
rimraf.sync(wrappedFolder)
|
||||
rimraf.sync(folder)
|
||||
})
|
||||
|
||||
// Link pino
|
||||
await symlink(
|
||||
join(__dirname, '..', '..'),
|
||||
join(wrappedFolder, 'node_modules', 'pino')
|
||||
)
|
||||
|
||||
// Link get-caller-file
|
||||
await symlink(
|
||||
join(__dirname, '..', '..', 'node_modules', 'get-caller-file'),
|
||||
join(wrappedFolder, 'node_modules', 'get-caller-file')
|
||||
)
|
||||
|
||||
// Link wrapped
|
||||
await symlink(
|
||||
wrappedFolder,
|
||||
join(folder, 'node_modules', 'wrapped')
|
||||
)
|
||||
|
||||
await installTransportModule(folder)
|
||||
|
||||
const pkgjsonContent = {
|
||||
name: 'pino'
|
||||
}
|
||||
|
||||
await writeFile(join(wrappedFolder, 'package.json'), JSON.stringify(pkgjsonContent))
|
||||
|
||||
const wrapped = join(wrappedFolder, 'index.js')
|
||||
|
||||
const wrappedContent = `
|
||||
const pino = require('pino')
|
||||
const getCaller = require('get-caller-file')
|
||||
|
||||
module.exports = function build () {
|
||||
const logger = pino({
|
||||
transport: {
|
||||
caller: getCaller(),
|
||||
target: 'transport',
|
||||
options: { destination: '${destination}' }
|
||||
}
|
||||
})
|
||||
return logger
|
||||
}
|
||||
`
|
||||
|
||||
await writeFile(wrapped, wrappedContent)
|
||||
|
||||
const toRun = join(folder, 'index.js')
|
||||
|
||||
const toRunContent = `
|
||||
const logger = require('wrapped')()
|
||||
logger.info('hello')
|
||||
`
|
||||
|
||||
await writeFile(toRun, toRunContent)
|
||||
|
||||
const child = execa(process.argv[0], [toRun])
|
||||
|
||||
await once(child, 'close')
|
||||
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid: child.pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { join } = require('path')
|
||||
const { readFile } = require('fs').promises
|
||||
const { watchFileCreated, file } = require('../helper')
|
||||
const { test } = require('tap')
|
||||
const pino = require('../../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('pino.transport with a pipeline', async ({ same, teardown }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-transform.js')
|
||||
}, {
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello',
|
||||
service: 'pino' // this property was added by the transform
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
const { doesNotThrow, test } = require('tap')
|
||||
const proxyquire = require('proxyquire')
|
||||
|
||||
test('pino.transport resolves targets in REPL', async ({ same }) => {
|
||||
// Arrange
|
||||
const transport = proxyquire('../../lib/transport', {
|
||||
'./caller': () => ['node:repl']
|
||||
})
|
||||
|
||||
// Act / Assert
|
||||
doesNotThrow(() => transport({ target: 'pino-pretty' }))
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const pino = require('../..')
|
||||
const { join } = require('path')
|
||||
const { test } = require('tap')
|
||||
const { readFile } = require('fs').promises
|
||||
const { watchFileCreated, file } = require('../helper')
|
||||
const { promisify } = require('util')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
test('thread-stream async flush', async ({ equal, same }) => {
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination }
|
||||
})
|
||||
const instance = pino(transport)
|
||||
instance.info('hello')
|
||||
|
||||
equal(instance.flush(), undefined)
|
||||
|
||||
await watchFileCreated(destination)
|
||||
const result = JSON.parse(await readFile(destination))
|
||||
delete result.time
|
||||
same(result, {
|
||||
pid,
|
||||
hostname,
|
||||
level: 30,
|
||||
msg: 'hello'
|
||||
})
|
||||
})
|
||||
|
||||
test('thread-stream async flush should call the passed callback', async (t) => {
|
||||
const outputPath = file()
|
||||
async function getOutputLogLines () {
|
||||
return (await readFile(outputPath)).toString().trim().split('\n').map(JSON.parse)
|
||||
}
|
||||
const transport = pino.transport({
|
||||
target: join(__dirname, '..', 'fixtures', 'to-file-transport.js'),
|
||||
options: { destination: outputPath }
|
||||
})
|
||||
const instance = pino(transport)
|
||||
const flushPromise = promisify(instance.flush).bind(instance)
|
||||
|
||||
instance.info('hello')
|
||||
await flushPromise()
|
||||
await watchFileCreated(outputPath)
|
||||
|
||||
const [firstFlushData] = await getOutputLogLines()
|
||||
|
||||
t.equal(firstFlushData.msg, 'hello')
|
||||
|
||||
// should not flush this as no data accumulated that's bigger than min length
|
||||
instance.info('world')
|
||||
|
||||
// Making sure data is not flushed yet
|
||||
const afterLogData = await getOutputLogLines()
|
||||
t.equal(afterLogData.length, 1)
|
||||
|
||||
await flushPromise()
|
||||
|
||||
// Making sure data is not flushed yet
|
||||
const afterSecondFlush = (await getOutputLogLines())[1]
|
||||
t.equal(afterSecondFlush.msg, 'world')
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
const { test } = require('tap')
|
||||
const proxyquire = require('proxyquire')
|
||||
const Writable = require('stream').Writable
|
||||
|
||||
test('file-target mocked', async function ({ equal, same, plan, pass }) {
|
||||
plan(1)
|
||||
let ret
|
||||
const fileTarget = proxyquire('../../file', {
|
||||
'./pino': {
|
||||
destination (opts) {
|
||||
same(opts, { dest: 1, sync: false })
|
||||
|
||||
ret = new Writable()
|
||||
ret.fd = opts.dest
|
||||
|
||||
process.nextTick(() => {
|
||||
ret.emit('ready')
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await fileTarget()
|
||||
})
|
||||
@@ -0,0 +1,167 @@
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const { join } = require('path')
|
||||
const { readFile } = require('fs').promises
|
||||
const writeStream = require('flush-write-stream')
|
||||
const { watchFileCreated, file } = require('../helper')
|
||||
const { test } = require('tap')
|
||||
const pino = require('../../')
|
||||
|
||||
const { pid } = process
|
||||
const hostname = os.hostname()
|
||||
|
||||
function serializeError (error) {
|
||||
return {
|
||||
type: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
}
|
||||
|
||||
function parseLogs (buffer) {
|
||||
return JSON.parse(`[${buffer.toString().replace(/}{/g, '},{')}]`)
|
||||
}
|
||||
|
||||
test('transport uses pino config', async ({ same, teardown, plan }) => {
|
||||
plan(1)
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js')
|
||||
}, {
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino({
|
||||
messageKey: 'customMessageKey',
|
||||
errorKey: 'customErrorKey',
|
||||
customLevels: { custom: 35 }
|
||||
}, transport)
|
||||
|
||||
const error = new Error('bar')
|
||||
instance.custom('foo')
|
||||
instance.error(error)
|
||||
await watchFileCreated(destination)
|
||||
const result = parseLogs(await readFile(destination))
|
||||
|
||||
same(result, [{
|
||||
severityText: 'custom',
|
||||
body: 'foo',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
}
|
||||
}, {
|
||||
severityText: 'error',
|
||||
body: 'bar',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
},
|
||||
error: serializeError(error)
|
||||
}])
|
||||
})
|
||||
|
||||
test('transport uses pino config without customizations', async ({ same, teardown, plan }) => {
|
||||
plan(1)
|
||||
const destination = file()
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js')
|
||||
}, {
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino(transport)
|
||||
|
||||
const error = new Error('qux')
|
||||
instance.info('baz')
|
||||
instance.error(error)
|
||||
await watchFileCreated(destination)
|
||||
const result = parseLogs(await readFile(destination))
|
||||
|
||||
same(result, [{
|
||||
severityText: 'info',
|
||||
body: 'baz',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
}
|
||||
}, {
|
||||
severityText: 'error',
|
||||
body: 'qux',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
},
|
||||
error: serializeError(error)
|
||||
}])
|
||||
})
|
||||
|
||||
test('transport uses pino config with multistream', async ({ same, teardown, plan }) => {
|
||||
plan(2)
|
||||
const destination = file()
|
||||
const messages = []
|
||||
const stream = writeStream(function (data, enc, cb) {
|
||||
const message = JSON.parse(data)
|
||||
delete message.time
|
||||
messages.push(message)
|
||||
cb()
|
||||
})
|
||||
const transport = pino.transport({
|
||||
pipeline: [{
|
||||
target: join(__dirname, '..', 'fixtures', 'transport-uses-pino-config.js')
|
||||
}, {
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
}]
|
||||
})
|
||||
teardown(transport.end.bind(transport))
|
||||
const instance = pino({
|
||||
messageKey: 'customMessageKey',
|
||||
errorKey: 'customErrorKey',
|
||||
customLevels: { custom: 35 }
|
||||
}, pino.multistream([transport, { stream }]))
|
||||
|
||||
const error = new Error('buzz')
|
||||
const serializedError = serializeError(error)
|
||||
instance.custom('fizz')
|
||||
instance.error(error)
|
||||
await watchFileCreated(destination)
|
||||
const result = parseLogs(await readFile(destination))
|
||||
|
||||
same(result, [{
|
||||
severityText: 'custom',
|
||||
body: 'fizz',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
}
|
||||
}, {
|
||||
severityText: 'error',
|
||||
body: 'buzz',
|
||||
attributes: {
|
||||
pid,
|
||||
hostname
|
||||
},
|
||||
error: serializedError
|
||||
}])
|
||||
|
||||
same(messages, [{
|
||||
level: 35,
|
||||
pid,
|
||||
hostname,
|
||||
customMessageKey: 'fizz'
|
||||
}, {
|
||||
level: 50,
|
||||
pid,
|
||||
hostname,
|
||||
customErrorKey: serializedError,
|
||||
customMessageKey: 'buzz'
|
||||
}])
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import { expectType } from "tsd";
|
||||
|
||||
import pino from '../../pino';
|
||||
import { pino as pinoNamed, P } from "../../pino";
|
||||
import * as pinoStar from "../../pino";
|
||||
import pinoCjsImport = require ("../../pino");
|
||||
const pinoCjs = require("../../pino");
|
||||
const { P: pinoCjsNamed } = require('pino')
|
||||
|
||||
const log = pino();
|
||||
expectType<P.LogFn>(log.info);
|
||||
expectType<P.LogFn>(log.error);
|
||||
|
||||
expectType<pino.Logger>(pinoNamed());
|
||||
expectType<P.Logger>(pinoNamed());
|
||||
expectType<pino.Logger>(pinoStar.default());
|
||||
expectType<pino.Logger>(pinoStar.pino());
|
||||
expectType<pino.Logger>(pinoCjsImport.default());
|
||||
expectType<pino.Logger>(pinoCjsImport.pino());
|
||||
expectType<any>(pinoCjsNamed());
|
||||
expectType<any>(pinoCjs());
|
||||
|
||||
const levelChangeEventListener: P.LevelChangeEventListener = (
|
||||
lvl: P.LevelWithSilent | string,
|
||||
val: number,
|
||||
prevLvl: P.LevelWithSilent | string,
|
||||
prevVal: number,
|
||||
) => {}
|
||||
expectType<P.LevelChangeEventListener>(levelChangeEventListener)
|
||||
@@ -0,0 +1,28 @@
|
||||
import { expectType } from 'tsd'
|
||||
|
||||
import { createWriteStream } from 'fs'
|
||||
|
||||
import pino, { multistream } from '../../pino'
|
||||
|
||||
const streams = [
|
||||
{ stream: process.stdout },
|
||||
{ stream: createWriteStream('') },
|
||||
{ level: 'error' as const, stream: process.stderr },
|
||||
{ level: 'fatal' as const, stream: process.stderr },
|
||||
]
|
||||
|
||||
expectType<pino.MultiStreamRes>(pino.multistream(process.stdout))
|
||||
expectType<pino.MultiStreamRes>(pino.multistream([createWriteStream('')]))
|
||||
expectType<pino.MultiStreamRes<'error'>>(pino.multistream({ level: 'error' as const, stream: process.stderr }))
|
||||
expectType<pino.MultiStreamRes<'fatal'>>(pino.multistream([{ level: 'fatal' as const, stream: createWriteStream('') }]))
|
||||
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams))
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, {}))
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, { levels: { 'info': 30 } }))
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams, { dedupe: true }))
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(pino.multistream(streams[0]).add(streams[1]))
|
||||
expectType<pino.MultiStreamRes<'error' | 'fatal'>>(multistream(streams))
|
||||
expectType<pino.MultiStreamRes<'error'>>(multistream(streams).clone('error'))
|
||||
|
||||
|
||||
expectType<pino.MultiStreamRes>(multistream(process.stdout));
|
||||
@@ -0,0 +1,35 @@
|
||||
import { expectType, expectAssignable } from 'tsd'
|
||||
import type { SonicBoom } from "sonic-boom";
|
||||
|
||||
import {
|
||||
destination,
|
||||
LevelMapping,
|
||||
levels,
|
||||
Logger,
|
||||
multistream,
|
||||
MultiStreamRes,
|
||||
SerializedError,
|
||||
stdSerializers,
|
||||
stdTimeFunctions,
|
||||
symbols,
|
||||
transport,
|
||||
version,
|
||||
} from "../../pino";
|
||||
import pino from "../../pino";
|
||||
|
||||
expectType<SonicBoom>(destination(""));
|
||||
expectType<LevelMapping>(levels);
|
||||
expectType<MultiStreamRes>(multistream(process.stdout));
|
||||
expectType<SerializedError>(stdSerializers.err({} as any));
|
||||
expectType<string>(stdTimeFunctions.isoTime());
|
||||
expectType<string>(version);
|
||||
|
||||
// Can't test against `unique symbol`, see https://github.com/SamVerschueren/tsd/issues/49
|
||||
expectAssignable<Symbol>(symbols.endSym);
|
||||
|
||||
// TODO: currently returns (aliased) `any`, waiting for strong typed `thread-stream`
|
||||
transport({
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
});
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { pino } from '../../pino'
|
||||
import { expectType } from "tsd";
|
||||
|
||||
// Single
|
||||
const transport = pino.transport({
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
})
|
||||
pino(transport)
|
||||
|
||||
expectType<pino.Logger>(pino({
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
}))
|
||||
|
||||
// Multiple
|
||||
const transports = pino.transport({targets: [
|
||||
{
|
||||
level: 'info',
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
},
|
||||
{
|
||||
level: 'trace',
|
||||
target: '#pino/file',
|
||||
options: { destination: './test.log' }
|
||||
}
|
||||
]})
|
||||
pino(transports)
|
||||
|
||||
expectType<pino.Logger>(pino({
|
||||
transport: {targets: [
|
||||
{
|
||||
level: 'info',
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
},
|
||||
{
|
||||
level: 'trace',
|
||||
target: '#pino/file',
|
||||
options: { destination: './test.log' }
|
||||
}
|
||||
]},
|
||||
}))
|
||||
|
||||
const transportsWithCustomLevels = pino.transport({targets: [
|
||||
{
|
||||
level: 'info',
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
},
|
||||
{
|
||||
level: 'foo',
|
||||
target: '#pino/file',
|
||||
options: { destination: './test.log' }
|
||||
}
|
||||
], levels: { foo: 35 }})
|
||||
pino(transports)
|
||||
|
||||
expectType<pino.Logger>(pino({
|
||||
transport: {targets: [
|
||||
{
|
||||
level: 'info',
|
||||
target: '#pino/pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
},
|
||||
{
|
||||
level: 'trace',
|
||||
target: '#pino/file',
|
||||
options: { destination: './test.log' }
|
||||
}
|
||||
], levels: { foo: 35 }
|
||||
},
|
||||
}))
|
||||
|
||||
const transportsWithoutOptions = pino.transport({
|
||||
targets: [
|
||||
{ target: '#pino/pretty' },
|
||||
{ target: '#pino/file' }
|
||||
], levels: { foo: 35 }
|
||||
})
|
||||
pino(transports)
|
||||
|
||||
expectType<pino.Logger>(pino({
|
||||
transport: {
|
||||
targets: [
|
||||
{ target: '#pino/pretty' },
|
||||
{ target: '#pino/file' }
|
||||
], levels: { foo: 35 }
|
||||
},
|
||||
}))
|
||||
|
||||
const pipelineTransport = pino.transport({
|
||||
pipeline: [{
|
||||
target: './my-transform.js'
|
||||
}, {
|
||||
// Use target: 'pino/file' to write to stdout
|
||||
// without any change.
|
||||
target: 'pino-pretty'
|
||||
}]
|
||||
})
|
||||
pino(pipelineTransport)
|
||||
|
||||
expectType<pino.Logger>(pino({
|
||||
transport: {
|
||||
pipeline: [{
|
||||
target: './my-transform.js'
|
||||
}, {
|
||||
// Use target: 'pino/file' to write to stdout
|
||||
// without any change.
|
||||
target: 'pino-pretty'
|
||||
}]
|
||||
}
|
||||
}))
|
||||
|
||||
type TransportConfig = {
|
||||
id: string
|
||||
}
|
||||
|
||||
// Custom transport params
|
||||
const customTransport = pino.transport<TransportConfig>({
|
||||
target: 'custom',
|
||||
options: { id: 'abc' }
|
||||
})
|
||||
pino(customTransport)
|
||||
|
||||
// Worker
|
||||
pino.transport({
|
||||
target: 'custom',
|
||||
worker: {
|
||||
argv: ['a', 'b'],
|
||||
stdin: false,
|
||||
stderr: true,
|
||||
stdout: false,
|
||||
autoEnd: true,
|
||||
},
|
||||
options: { id: 'abc' }
|
||||
})
|
||||
|
||||
// Dedupe
|
||||
pino.transport({
|
||||
targets: [],
|
||||
dedupe: true,
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { expectAssignable, expectType, expectNotAssignable } from "tsd";
|
||||
|
||||
import pino from "../../";
|
||||
import type {LevelWithSilent, Logger, LogFn, P, DestinationStreamWithMetadata, Level, LevelOrString, LevelWithSilentOrString, LoggerExtras } from "../../pino";
|
||||
|
||||
// NB: can also use `import * as pino`, but that form is callable as `pino()`
|
||||
// under `esModuleInterop: false` or `pino.default()` under `esModuleInterop: true`.
|
||||
const log = pino();
|
||||
expectAssignable<LoggerExtras>(log);
|
||||
expectType<Logger>(log);
|
||||
expectType<LogFn>(log.info);
|
||||
|
||||
expectType<P.Logger>(log);
|
||||
expectType<P.LogFn>(log.info);
|
||||
|
||||
expectType<Parameters<typeof log.isLevelEnabled>>([log.level]);
|
||||
|
||||
const level: Level = 'debug';
|
||||
expectAssignable<string>(level);
|
||||
expectAssignable<P.Level>(level);
|
||||
|
||||
const levelWithSilent: LevelWithSilent = 'silent';
|
||||
expectAssignable<string>(levelWithSilent);
|
||||
expectAssignable<P.LevelWithSilent>(levelWithSilent);
|
||||
|
||||
const levelOrString: LevelOrString = "myCustomLevel";
|
||||
expectAssignable<string>(levelOrString);
|
||||
expectNotAssignable<pino.Level>(levelOrString);
|
||||
expectNotAssignable<pino.LevelWithSilent>(levelOrString);
|
||||
expectAssignable<pino.LevelWithSilentOrString>(levelOrString);
|
||||
|
||||
const levelWithSilentOrString: LevelWithSilentOrString = "myCustomLevel";
|
||||
expectAssignable<string>(levelWithSilentOrString);
|
||||
expectNotAssignable<pino.Level>(levelWithSilentOrString);
|
||||
expectNotAssignable<pino.LevelWithSilent>(levelWithSilentOrString);
|
||||
expectAssignable<pino.LevelOrString>(levelWithSilentOrString);
|
||||
|
||||
function createStream(): DestinationStreamWithMetadata {
|
||||
return { write() {} };
|
||||
}
|
||||
|
||||
const stream = createStream();
|
||||
// Argh. TypeScript doesn't seem to narrow unless we assign the symbol like so, and tsd seems to
|
||||
// break without annotating the type explicitly
|
||||
const needsMetadata: typeof pino.symbols.needsMetadataGsym = pino.symbols.needsMetadataGsym;
|
||||
if (stream[needsMetadata]) {
|
||||
expectType<number>(stream.lastLevel);
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
import { IncomingMessage, ServerResponse } from "http";
|
||||
import { Socket } from "net";
|
||||
import { expectError, expectType } from 'tsd';
|
||||
import P, { pino } from "../../";
|
||||
import Logger = P.Logger;
|
||||
|
||||
const log = pino();
|
||||
const info = log.info;
|
||||
const error = log.error;
|
||||
|
||||
info("hello world");
|
||||
error("this is at error level");
|
||||
info("the answer is %d", 42);
|
||||
info({ obj: 42 }, "hello world");
|
||||
info({ obj: 42, b: 2 }, "hello world");
|
||||
info({ obj: { aa: "bbb" } }, "another");
|
||||
setImmediate(info, "after setImmediate");
|
||||
error(new Error("an error"));
|
||||
|
||||
const writeSym = pino.symbols.writeSym;
|
||||
|
||||
const testUniqSymbol = {
|
||||
[pino.symbols.needsMetadataGsym]: true,
|
||||
}[pino.symbols.needsMetadataGsym];
|
||||
|
||||
const log2: P.Logger = pino({
|
||||
name: "myapp",
|
||||
safe: true,
|
||||
serializers: {
|
||||
req: pino.stdSerializers.req,
|
||||
res: pino.stdSerializers.res,
|
||||
err: pino.stdSerializers.err,
|
||||
},
|
||||
});
|
||||
|
||||
pino({
|
||||
write(o) {},
|
||||
});
|
||||
|
||||
pino({
|
||||
mixin() {
|
||||
return { customName: "unknown", customId: 111 };
|
||||
},
|
||||
});
|
||||
|
||||
pino({
|
||||
mixin: () => ({ customName: "unknown", customId: 111 }),
|
||||
});
|
||||
|
||||
pino({
|
||||
mixin: (context: object) => ({ customName: "unknown", customId: 111 }),
|
||||
});
|
||||
|
||||
pino({
|
||||
mixin: (context: object, level: number) => ({ customName: "unknown", customId: 111 }),
|
||||
});
|
||||
|
||||
pino({
|
||||
redact: { paths: [], censor: "SECRET" },
|
||||
});
|
||||
|
||||
pino({
|
||||
redact: { paths: [], censor: () => "SECRET" },
|
||||
});
|
||||
|
||||
pino({
|
||||
redact: { paths: [], censor: (value) => value },
|
||||
});
|
||||
|
||||
pino({
|
||||
redact: { paths: [], censor: (value, path) => path.join() },
|
||||
});
|
||||
|
||||
pino({
|
||||
depthLimit: 1
|
||||
});
|
||||
|
||||
pino({
|
||||
edgeLimit: 1
|
||||
});
|
||||
|
||||
pino({
|
||||
browser: {
|
||||
write(o) {},
|
||||
},
|
||||
});
|
||||
|
||||
pino({
|
||||
browser: {
|
||||
write: {
|
||||
info(o) {},
|
||||
error(o) {},
|
||||
},
|
||||
serialize: true,
|
||||
asObject: true,
|
||||
transmit: {
|
||||
level: "fatal",
|
||||
send: (level, logEvent) => {
|
||||
level;
|
||||
logEvent.bindings;
|
||||
logEvent.level;
|
||||
logEvent.ts;
|
||||
logEvent.messages;
|
||||
},
|
||||
},
|
||||
disabled: false
|
||||
},
|
||||
});
|
||||
|
||||
pino({}, undefined);
|
||||
|
||||
pino({ base: null });
|
||||
if ("pino" in log) console.log(`pino version: ${log.pino}`);
|
||||
|
||||
expectType<void>(log.flush());
|
||||
log.flush((err?: Error) => undefined);
|
||||
log.child({ a: "property" }).info("hello child!");
|
||||
log.level = "error";
|
||||
log.info("nope");
|
||||
const child = log.child({ foo: "bar" });
|
||||
child.info("nope again");
|
||||
child.level = "info";
|
||||
child.info("hooray");
|
||||
log.info("nope nope nope");
|
||||
log.child({ foo: "bar" }, { level: "debug" }).debug("debug!");
|
||||
child.bindings();
|
||||
const customSerializers = {
|
||||
test() {
|
||||
return "this is my serializer";
|
||||
},
|
||||
};
|
||||
pino().child({}, { serializers: customSerializers }).info({ test: "should not show up" });
|
||||
const child2 = log.child({ father: true });
|
||||
const childChild = child2.child({ baby: true });
|
||||
const childRedacted = pino().child({}, { redact: ["path"] })
|
||||
childRedacted.info({
|
||||
msg: "logged with redacted properties",
|
||||
path: "Not shown",
|
||||
});
|
||||
const childAnotherRedacted = pino().child({}, {
|
||||
redact: {
|
||||
paths: ["anotherPath"],
|
||||
censor: "Not the log you\re looking for",
|
||||
}
|
||||
})
|
||||
childAnotherRedacted.info({
|
||||
msg: "another logged with redacted properties",
|
||||
anotherPath: "Not shown",
|
||||
});
|
||||
|
||||
log.level = "info";
|
||||
if (log.levelVal === 30) {
|
||||
console.log("logger level is `info`");
|
||||
}
|
||||
|
||||
const listener = (lvl: any, val: any, prevLvl: any, prevVal: any) => {
|
||||
console.log(lvl, val, prevLvl, prevVal);
|
||||
};
|
||||
log.on("level-change", (lvl, val, prevLvl, prevVal, logger) => {
|
||||
console.log(lvl, val, prevLvl, prevVal);
|
||||
});
|
||||
log.level = "trace";
|
||||
log.removeListener("level-change", listener);
|
||||
log.level = "info";
|
||||
|
||||
pino.levels.values.error === 50;
|
||||
pino.levels.labels[50] === "error";
|
||||
|
||||
const logstderr: pino.Logger = pino(process.stderr);
|
||||
logstderr.error("on stderr instead of stdout");
|
||||
|
||||
log.useLevelLabels = true;
|
||||
log.info("lol");
|
||||
log.level === "info";
|
||||
const isEnabled: boolean = log.isLevelEnabled("info");
|
||||
|
||||
const redacted = pino({
|
||||
redact: ["path"],
|
||||
});
|
||||
|
||||
redacted.info({
|
||||
msg: "logged with redacted properties",
|
||||
path: "Not shown",
|
||||
});
|
||||
|
||||
const anotherRedacted = pino({
|
||||
redact: {
|
||||
paths: ["anotherPath"],
|
||||
censor: "Not the log you\re looking for",
|
||||
},
|
||||
});
|
||||
|
||||
anotherRedacted.info({
|
||||
msg: "another logged with redacted properties",
|
||||
anotherPath: "Not shown",
|
||||
});
|
||||
|
||||
const withTimeFn = pino({
|
||||
timestamp: pino.stdTimeFunctions.isoTime,
|
||||
});
|
||||
|
||||
const withNestedKey = pino({
|
||||
nestedKey: "payload",
|
||||
});
|
||||
|
||||
const withHooks = pino({
|
||||
hooks: {
|
||||
logMethod(args, method, level) {
|
||||
expectType<pino.Logger>(this);
|
||||
return method.apply(this, args);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Properties/types imported from pino-std-serializers
|
||||
const wrappedErrSerializer = pino.stdSerializers.wrapErrorSerializer((err: pino.SerializedError) => {
|
||||
return { ...err, newProp: "foo" };
|
||||
});
|
||||
const wrappedReqSerializer = pino.stdSerializers.wrapRequestSerializer((req: pino.SerializedRequest) => {
|
||||
return { ...req, newProp: "foo" };
|
||||
});
|
||||
const wrappedResSerializer = pino.stdSerializers.wrapResponseSerializer((res: pino.SerializedResponse) => {
|
||||
return { ...res, newProp: "foo" };
|
||||
});
|
||||
|
||||
const socket = new Socket();
|
||||
const incomingMessage = new IncomingMessage(socket);
|
||||
const serverResponse = new ServerResponse(incomingMessage);
|
||||
|
||||
const mappedHttpRequest: { req: pino.SerializedRequest } = pino.stdSerializers.mapHttpRequest(incomingMessage);
|
||||
const mappedHttpResponse: { res: pino.SerializedResponse } = pino.stdSerializers.mapHttpResponse(serverResponse);
|
||||
|
||||
const serializedErr: pino.SerializedError = pino.stdSerializers.err(new Error());
|
||||
const serializedReq: pino.SerializedRequest = pino.stdSerializers.req(incomingMessage);
|
||||
const serializedRes: pino.SerializedResponse = pino.stdSerializers.res(serverResponse);
|
||||
|
||||
/**
|
||||
* Destination static method
|
||||
*/
|
||||
const destinationViaDefaultArgs = pino.destination();
|
||||
const destinationViaStrFileDescriptor = pino.destination("/log/path");
|
||||
const destinationViaNumFileDescriptor = pino.destination(2);
|
||||
const destinationViaStream = pino.destination(process.stdout);
|
||||
const destinationViaOptionsObject = pino.destination({ dest: "/log/path", sync: false });
|
||||
|
||||
pino(destinationViaDefaultArgs);
|
||||
pino({ name: "my-logger" }, destinationViaDefaultArgs);
|
||||
pino(destinationViaStrFileDescriptor);
|
||||
pino({ name: "my-logger" }, destinationViaStrFileDescriptor);
|
||||
pino(destinationViaNumFileDescriptor);
|
||||
pino({ name: "my-logger" }, destinationViaNumFileDescriptor);
|
||||
pino(destinationViaStream);
|
||||
pino({ name: "my-logger" }, destinationViaStream);
|
||||
pino(destinationViaOptionsObject);
|
||||
pino({ name: "my-logger" }, destinationViaOptionsObject);
|
||||
|
||||
try {
|
||||
throw new Error('Some error')
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
|
||||
interface StrictShape {
|
||||
activity: string;
|
||||
err?: unknown;
|
||||
}
|
||||
|
||||
info<StrictShape>({
|
||||
activity: "Required property",
|
||||
});
|
||||
|
||||
const logLine: pino.LogDescriptor = {
|
||||
level: 20,
|
||||
msg: "A log message",
|
||||
time: new Date().getTime(),
|
||||
aCustomProperty: true,
|
||||
};
|
||||
|
||||
interface CustomLogger extends pino.Logger {
|
||||
customMethod(msg: string, ...args: unknown[]): void;
|
||||
}
|
||||
|
||||
const serializerFunc: pino.SerializerFn = () => {}
|
||||
const writeFunc: pino.WriteFn = () => {}
|
||||
|
||||
interface CustomBaseLogger extends pino.BaseLogger {
|
||||
child(): CustomBaseLogger
|
||||
}
|
||||
|
||||
const customBaseLogger: CustomBaseLogger = {
|
||||
level: 'info',
|
||||
fatal() {},
|
||||
error() {},
|
||||
warn() {},
|
||||
info() {},
|
||||
debug() {},
|
||||
trace() {},
|
||||
silent() {},
|
||||
child() { return this }
|
||||
}
|
||||
|
||||
// custom levels
|
||||
const log3 = pino({ customLevels: { myLevel: 100 } })
|
||||
expectError(log3.log())
|
||||
log3.level = 'myLevel'
|
||||
log3.myLevel('')
|
||||
log3.child({}).myLevel('')
|
||||
|
||||
log3.on('level-change', (lvl, val, prevLvl, prevVal, instance) => {
|
||||
instance.myLevel('foo');
|
||||
});
|
||||
|
||||
const clog3 = log3.child({}, { customLevels: { childLevel: 120 } })
|
||||
// child inherit parent
|
||||
clog3.myLevel('')
|
||||
// child itself
|
||||
clog3.childLevel('')
|
||||
const cclog3 = clog3.child({}, { customLevels: { childLevel2: 130 } })
|
||||
// child inherit root
|
||||
cclog3.myLevel('')
|
||||
// child inherit parent
|
||||
cclog3.childLevel('')
|
||||
// child itself
|
||||
cclog3.childLevel2('')
|
||||
|
||||
const ccclog3 = clog3.child({})
|
||||
expectError(ccclog3.nonLevel(''))
|
||||
|
||||
const withChildCallback = pino({
|
||||
onChild: (child: Logger) => {}
|
||||
})
|
||||
withChildCallback.onChild = (child: Logger) => {}
|
||||
|
||||
pino({
|
||||
crlf: true,
|
||||
});
|
||||
|
||||
const customLevels = { foo: 99, bar: 42 }
|
||||
|
||||
const customLevelLogger = pino({ customLevels });
|
||||
|
||||
type CustomLevelLogger = typeof customLevelLogger
|
||||
type CustomLevelLoggerLevels = pino.Level | keyof typeof customLevels
|
||||
|
||||
const fn = (logger: Pick<CustomLevelLogger, CustomLevelLoggerLevels>) => {}
|
||||
|
||||
const customLevelChildLogger = customLevelLogger.child({ name: "child" })
|
||||
|
||||
fn(customLevelChildLogger); // missing foo typing
|
||||
|
||||
// unknown option
|
||||
expectError(
|
||||
pino({
|
||||
hello: 'world'
|
||||
})
|
||||
);
|
||||
|
||||
// unknown option
|
||||
expectError(
|
||||
pino({
|
||||
hello: 'world',
|
||||
customLevels: {
|
||||
'log': 30
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
function dangerous () {
|
||||
throw Error('foo')
|
||||
}
|
||||
|
||||
try {
|
||||
dangerous()
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
|
||||
try {
|
||||
dangerous()
|
||||
} catch (err) {
|
||||
log.error({ err })
|
||||
}
|
||||
|
||||
const bLogger = pino({
|
||||
customLevels: {
|
||||
log: 5,
|
||||
},
|
||||
level: 'log',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectType<Logger<'log'>>(pino({
|
||||
customLevels: {
|
||||
log: 5,
|
||||
},
|
||||
level: 'log',
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const parentLogger1 = pino({
|
||||
customLevels: { myLevel: 90 },
|
||||
onChild: (child) => { const a = child.myLevel; }
|
||||
}, process.stdout)
|
||||
parentLogger1.onChild = (child) => { child.myLevel(''); }
|
||||
|
||||
const childLogger1 = parentLogger1.child({});
|
||||
childLogger1.myLevel('');
|
||||
expectError(childLogger1.doesntExist(''));
|
||||
|
||||
const parentLogger2 = pino({}, process.stdin);
|
||||
expectError(parentLogger2.onChild = (child) => { const b = child.doesntExist; });
|
||||
|
||||
const childLogger2 = parentLogger2.child({});
|
||||
expectError(childLogger2.doesntExist);
|
||||
|
||||
expectError(pino({
|
||||
onChild: (child) => { const a = child.doesntExist; }
|
||||
}, process.stdout));
|
||||
|
||||
const pinoWithoutLevelsSorting = pino({});
|
||||
const pinoWithDescSortingLevels = pino({ levelComparison: 'DESC' });
|
||||
const pinoWithAscSortingLevels = pino({ levelComparison: 'ASC' });
|
||||
const pinoWithCustomSortingLevels = pino({ levelComparison: () => false });
|
||||
// with wrong level comparison direction
|
||||
expectError(pino({ levelComparison: 'SOME'}), process.stdout);
|
||||
// with wrong level comparison type
|
||||
expectError(pino({ levelComparison: 123}), process.stdout);
|
||||
// with wrong custom level comparison return type
|
||||
expectError(pino({ levelComparison: () => null }), process.stdout);
|
||||
expectError(pino({ levelComparison: () => 1 }), process.stdout);
|
||||
expectError(pino({ levelComparison: () => 'string' }), process.stdout);
|
||||
@@ -0,0 +1,78 @@
|
||||
import { StreamEntry, pino } from '../../pino'
|
||||
import { join } from 'path'
|
||||
import { tmpdir } from 'os'
|
||||
import pinoPretty from 'pino-pretty'
|
||||
|
||||
const destination = join(
|
||||
tmpdir(),
|
||||
'_' + Math.random().toString(36).substr(2, 9)
|
||||
)
|
||||
|
||||
// Single
|
||||
const transport = pino.transport({
|
||||
target: 'pino-pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
})
|
||||
const logger = pino(transport)
|
||||
logger.setBindings({ some: 'bindings' })
|
||||
logger.info('test2')
|
||||
logger.flush()
|
||||
|
||||
const transport2 = pino.transport({
|
||||
target: 'pino-pretty',
|
||||
})
|
||||
const logger2 = pino(transport2)
|
||||
logger2.info('test2')
|
||||
|
||||
|
||||
// Multiple
|
||||
|
||||
const transports = pino.transport({targets: [
|
||||
{
|
||||
level: 'info',
|
||||
target: 'pino-pretty',
|
||||
options: { some: 'options for', the: 'transport' }
|
||||
},
|
||||
{
|
||||
level: 'trace',
|
||||
target: 'pino/file',
|
||||
options: { destination }
|
||||
}
|
||||
]})
|
||||
const loggerMulti = pino(transports)
|
||||
loggerMulti.info('test2')
|
||||
|
||||
// custom levels
|
||||
|
||||
const customLevels = {
|
||||
debug : 1,
|
||||
info : 2,
|
||||
network : 3,
|
||||
error : 4,
|
||||
};
|
||||
|
||||
type CustomLevels = keyof typeof customLevels;
|
||||
|
||||
const pinoOpts = {
|
||||
level: 'debug',
|
||||
useOnlyCustomLevels: true,
|
||||
customLevels: customLevels,
|
||||
};
|
||||
|
||||
const multistreamOpts = {
|
||||
dedupe: true,
|
||||
levels: customLevels
|
||||
};
|
||||
|
||||
const streams: StreamEntry<CustomLevels>[] = [
|
||||
{ level : 'debug', stream : pinoPretty() },
|
||||
{ level : 'info', stream : pinoPretty() },
|
||||
{ level : 'network', stream : pinoPretty() },
|
||||
{ level : 'error', stream : pinoPretty() },
|
||||
];
|
||||
|
||||
const loggerCustomLevel = pino(pinoOpts, pino.multistream(streams, multistreamOpts));
|
||||
loggerCustomLevel.debug('test3')
|
||||
loggerCustomLevel.info('test4')
|
||||
loggerCustomLevel.error('test5')
|
||||
loggerCustomLevel.network('test6')
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"module": "commonjs",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
"exclude": [
|
||||
"./test/types/*.test-d.ts",
|
||||
"./*.d.ts"
|
||||
]
|
||||
}
|
||||