Compare commits

...

185 Commits

Author SHA1 Message Date
Vitaliy Filippov 2ac9e086a9 Include error stack traces 2024-08-04 01:31:08 +03:00
Vitaliy Filippov cd2bf582ff Change git dependency URLs 2024-07-21 17:38:16 +03:00
Vitaliy Filippov ad1e403afa Remove yarn lock 2024-07-21 15:41:41 +03:00
Mickael Bourgois 4131abec82
WRLGS-11: Bump package.json version 8.1.5 2024-06-28 12:24:59 +02:00
Mickael Bourgois a35b4fc605
Merge remote-tracking branch 'origin/w/8.0/improvement/WRLGS-11-stderr-timestamp' into w/8.1/improvement/WRLGS-11-stderr-timestamp 2024-06-28 12:24:30 +02:00
Mickael Bourgois cff3114315
WRLGS-11: Bump package.json version 8.0.1 2024-06-28 12:18:02 +02:00
Mickael Bourgois a2cd55da7c
Merge remote-tracking branch 'origin/improvement/WRLGS-11-stderr-timestamp' into w/8.0/improvement/WRLGS-11-stderr-timestamp 2024-06-28 12:17:32 +02:00
Mickael Bourgois 4984e5be3c
WRLGS-11: Fix and bump package version
All tags from 7.4.10 and 7.10.1.0 to 7.10.4.10
are all the same commit 23dfe7c
2024-06-28 12:01:28 +02:00
Mickael Bourgois b40a74ea39
WRLGS-11: Fix coverage test in latest branch
nyc coverage seems to slow down the process on first run
The slow seems to vary between branches (due to nyc version)
2024-06-28 12:01:27 +02:00
Mickael Bourgois 68df3de3e9
WRLGS-11: update dev deps for nyc coverage 2024-06-26 21:06:46 +02:00
Mickael Bourgois da2224fbf7
WRLGS-11: Test stderrUtils 2024-06-26 20:12:16 +02:00
Mickael Bourgois dd2d71175d
WRLGS-11: Generic function for timestamp in stderr 2024-06-26 19:46:37 +02:00
bert-e 60cc71b318 Merge branch 'dependabot/npm_and_yarn/semver-5.7.2' into q/8.1 2024-05-13 06:43:07 +00:00
Francois Ferrand 4a5237f128
Merge branch 'development/8.1' into dependabot/npm_and_yarn/semver-5.7.2 2024-05-13 08:42:26 +02:00
Francois Ferrand b05142e679
Merge branch 'development/8.1' into dependabot/npm_and_yarn/word-wrap-1.2.4 2024-05-13 08:42:12 +02:00
dependabot[bot] 10af7a165a
chore(deps): bump @babel/traverse from 7.15.4 to 7.24.5
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.15.4 to 7.24.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.24.5/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-10 14:48:44 +00:00
bert-e f727fb69c2 Merge branch 'dependabot/npm_and_yarn/lodash-4.17.21' into q/8.1 2024-05-10 14:47:29 +00:00
bert-e 2e9b1d99b7 Merge branch 'dependabot/npm_and_yarn/json5-1.0.2' into q/8.1 2024-05-10 14:47:17 +00:00
Francois Ferrand c6796f8f9b
Merge branch 'development/8.1' into dependabot/npm_and_yarn/lodash-4.17.21 2024-05-10 16:46:47 +02:00
Francois Ferrand 0141f8df72
Merge branch 'development/8.1' into dependabot/npm_and_yarn/ansi-regex-3.0.1 2024-05-10 16:46:27 +02:00
Francois Ferrand 62bf231ad9
Merge branch 'development/8.1' into dependabot/npm_and_yarn/json5-1.0.2 2024-05-10 16:46:24 +02:00
Francois Ferrand 6bfa25e714
Bump gha actions
- checkout@v4
- setup-node@v4
- Cleanup branch selection rules

Issue: WRLGS-9
2024-05-10 15:05:38 +02:00
Jonathan Gramain d6bec11df0 WRLGS-8 bump werelogs version to 8.1.4 2024-02-26 15:15:49 -08:00
Jonathan Gramain b8a9afa248 impr: WRLGS-8 replace asserts by error logs
When RequestLogger.end() was called more than once for a particular
RequestLogger, an assert used to be raised, now an error log is dumped
instead which doesn't raise an exception and allows the worker to
continue processing further requests.

In this case an assert is not warranted because:

- the issue is not critical and doesn't prevent the process from
  continuing normal processing

- the issue could be triggered from unfrequent code paths that are
  hard to find by just reading the code.

By logging an error instead, we have a notification of the issue in
the process logs, and it helps to spot the root cause when
investigating because the 'error' log triggers a dump of the full
request trace logs.
2024-02-26 15:15:49 -08:00
dependabot[bot] 41fe5a3d85
chore(deps): bump word-wrap from 1.2.3 to 1.2.4
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-19 04:27:23 +00:00
dependabot[bot] 5eb29b505f
chore(deps): bump semver from 5.7.1 to 5.7.2
Bumps [semver](https://github.com/npm/node-semver) from 5.7.1 to 5.7.2.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v5.7.2/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v5.7.1...v5.7.2)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-12 15:26:09 +00:00
dependabot[bot] 9b37eb51ce
chore(deps): bump lodash from 4.17.15 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

---
updated-dependencies:
- dependency-name: lodash
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-14 10:37:24 +00:00
dependabot[bot] b65a5a7f77
chore(deps): bump ansi-regex from 3.0.0 to 3.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-14 10:37:23 +00:00
dependabot[bot] bede37872e
chore(deps): bump json5 from 1.0.1 to 1.0.2
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-14 10:37:23 +00:00
Dimitrios Vasilas e1574238a6 impr(WRLGS-6): Bump version to 8.1.3 2023-02-20 14:17:57 +01:00
Dimitrios Vasilas 0af401348d impr(WRLGS-6): export RequestLogger class 2023-02-20 14:16:55 +01:00
williamlardier c995252869
WRLGS-5: bump project version 2022-11-09 16:37:25 +01:00
williamlardier d81ae13c64
WRLGS-5: improve logging efficiency and stringify 2022-11-09 16:37:21 +01:00
williamlardier f4b2841b96
WRLGS-5: add new dependency for fast parsing 2022-11-09 16:36:34 +01:00
Taylor McKinnon 240be22c47 fix name 2022-10-06 13:11:04 -07:00
Taylor McKinnon 69d7f992db impr(WRLGS-4): Bump version to 8.1.1 2022-10-06 12:06:39 -07:00
Taylor McKinnon 111ee41271 impr(WRLGS-4): Improve exported type information 2022-10-06 10:23:20 -07:00
bert-e 77ced685d5 Merge branch 'w/7.10/feature/WRLGS-2/revert-minimum-node-version' into tmp/octopus/w/8.0/feature/WRLGS-2/revert-minimum-node-version 2021-09-29 16:27:31 +00:00
bert-e 5664f19d03 Merge branch 'w/8.0/feature/WRLGS-2/revert-minimum-node-version' into tmp/octopus/w/8.1/feature/WRLGS-2/revert-minimum-node-version 2021-09-29 16:27:31 +00:00
Jorge Duarte Rodríguez 23dfe7c60b Merge remote-tracking branch 'origin/development/7.4' into feature/WRLGS-2/revert-minimum-node-version 2021-09-29 18:26:48 +02:00
Jorge Duarte Rodríguez b9fe283527 Merge branch 'feature/WRLGS-2/revert-minimum-node-version' of github.com:scality/werelogs into feature/WRLGS-2/revert-minimum-node-version 2021-09-29 18:14:20 +02:00
Jorge Duarte Rodríguez e861a16952 chore: corrects ft_test target 2021-09-29 18:12:52 +02:00
Jorge Duarte Rodríguez 7f2b4a04a1 chore: downgrade tools to support legacy node 10 production services 2021-09-29 18:12:52 +02:00
Jorge Duarte Rodríguez dbbcf18381 chore: downgrade version to prevent issues at production 2021-09-29 18:12:52 +02:00
Jorge Duarte Rodríguez 6bd64feb8d chore: related to cb416491b9 2021-09-29 18:12:52 +02:00
Jorge Duarte Rodríguez a468197199 docs: updates README 2021-09-29 18:12:52 +02:00
megahaxor c1e7df3b41 docs: updates README with @miniscruff suggestions
Co-authored-by: Ronnie <halfpint1170@gmail.com>
2021-09-29 18:12:52 +02:00
megahaxor f5b089a53d chore: fixes version issue at eve/main.yml
Co-authored-by: Thomas Carmet <thomas.carmet@scality.com>
2021-09-29 18:12:52 +02:00
Jorge Duarte Rodríguez 639ce6395f chore: corrects ft_test target 2021-09-29 18:04:25 +02:00
Jorge Duarte Rodríguez 5df3cdc4f4 chore: downgrade tools to support legacy node 10 production services 2021-09-29 17:31:40 +02:00
bert-e 994af9ed1e Merge branch 'w/8.0/feature/PIPDEP-1062/github-actions-migration' into tmp/octopus/w/8.1/feature/PIPDEP-1062/github-actions-migration 2021-09-29 15:24:44 +00:00
Thomas Carmet d22f2b161e
Merge pull request #136 from scality/feature/bump-version
Update package.json version
2021-09-29 08:23:16 -07:00
Jorge Duarte Rodríguez 92686a0c46 chore: downgrade version to prevent issues at production 2021-09-29 17:15:27 +02:00
Thomas Carmet b65ddff863
Update package.json version 2021-09-29 08:14:38 -07:00
bert-e fcabd0bb0f Merge branch 'w/7.10/feature/PIPDEP-1062/github-actions-migration' into tmp/octopus/w/8.0/feature/PIPDEP-1062/github-actions-migration 2021-09-29 14:57:50 +00:00
Erwan Bernard fcd3ca1a8b [PIPDEP-1062] Merge 7.4 2021-09-29 16:16:00 +02:00
Erwan Bernard 0dd3d8319d [PIPDEP-1062] Github Actions migration 2021-09-29 10:27:56 +02:00
bert-e 8c483f34ed Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 23:46:09 +00:00
Jorge Duarte Rodríguez 12339cf82a chore: related to cb416491b9 2021-09-29 01:45:58 +02:00
bert-e 069f9d56ff Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 23:19:59 +00:00
Jorge Duarte Rodríguez a37209985d docs: updates README 2021-09-29 01:19:48 +02:00
bert-e c4d677d1bd Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 23:18:49 +00:00
megahaxor 4823c7b0d7
docs: updates README with @miniscruff suggestions
Co-authored-by: Ronnie <halfpint1170@gmail.com>
2021-09-29 01:18:44 +02:00
bert-e c3ddb20954 Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 23:09:22 +00:00
Jorge Duarte Rodríguez e8e40bfa3b Merge branch 'feature/WRLGS-1/node16Support' of github.com:scality/werelogs into feature/WRLGS-1/node16Support 2021-09-29 01:09:08 +02:00
Jorge Duarte Rodríguez cb416491b9 chore: complies with comments of !129 by @naren-rajendran 2021-09-29 01:08:33 +02:00
bert-e 97313361ad Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 22:27:52 +00:00
megahaxor c73c979f80
chore: fixes version issue at eve/main.yml
Co-authored-by: Thomas Carmet <thomas.carmet@scality.com>
2021-09-29 00:27:47 +02:00
bert-e 08487ac7a1 Merge branch 'w/7.10/feature/WRLGS-1/node16Support' into tmp/octopus/w/8.0/feature/WRLGS-1/node16Support 2021-09-28 22:23:08 +00:00
Jorge Duarte Rodríguez 613dbd59cd chore: updates lockfile with new package versions 2021-09-29 00:05:37 +02:00
Jorge Duarte Rodríguez bfdb4c3a51 chore: removes lockfiles from gitignore to ensure proper operation of CI services 2021-09-28 23:40:04 +02:00
Jorge Duarte Rodríguez 73648838aa Revert "feat(node16Support): removes lockfile"
This reverts commit a2ca4eabce.
2021-09-28 23:38:32 +02:00
Jorge Duarte Rodríguez d2753efbb6 chore: deletes unused circle cfg 2021-09-28 23:36:13 +02:00
Jorge Duarte Rodríguez 9afaddb158 chore: replaces istanbul for nyc and upgrades mocha 2021-09-28 23:35:29 +02:00
Jorge Duarte Rodríguez 73c9d01c47 chore: temporarily disables linter until some issues are solved 2021-09-28 23:35:02 +02:00
Jorge Duarte Rodríguez 6bd84ef7aa chore: adds gitignore file 2021-09-28 23:34:17 +02:00
Jorge Duarte Rodríguez c61c0a3c30
chore: linting 2021-09-28 02:10:00 +02:00
Jorge Duarte Rodríguez 8d8c7c853e
chore: linting 2021-09-28 02:08:22 +02:00
Jorge Duarte Rodríguez 2a24625861
feat(node16Support): upgrades safe-json-stringify 2021-09-28 01:59:37 +02:00
Jorge Duarte Rodríguez 9895875bd6
feat(node16Support): fixes ft_test target 2021-09-28 01:23:50 +02:00
Jorge Duarte Rodríguez cb20040ce9
feat(node16Support): adds markdownlint-cli support 2021-09-28 00:24:32 +02:00
Jorge Duarte Rodríguez d3c4944ea1
feat(node16Support): removes old mdlint pkg 2021-09-28 00:22:06 +02:00
Jorge Duarte Rodríguez cc5c5f2abb
feat(node16Support): fixes and upgrades CI settings 2021-09-28 00:13:38 +02:00
Jorge Duarte Rodríguez 6166beabdd
feat(node16Support): updates and installs missing eslint packages 2021-09-27 19:18:25 +02:00
Jorge Duarte Rodríguez 73c20becc8
feat(node16Support): updates eslint packages and engines.node cfg val 2021-09-27 19:13:22 +02:00
Jorge Duarte Rodríguez a2ca4eabce
feat(node16Support): removes lockfile 2021-09-27 18:26:54 +02:00
bert-e 22bca9c217 Merge branch 'w/7.5/improvement/S3C-2363-install-yarn-frozen-lockfile' into tmp/octopus/w/8.0/improvement/S3C-2363-install-yarn-frozen-lockfile 2019-08-08 18:03:34 +00:00
Katherine Laue 0a4c57658f improvement/S3C-2363 install yarn frozen lockfile 2019-08-08 10:59:47 -07:00
bert-e 16b767fd15 Merge branch 'w/7.5/improvement/S3C-2363-install-yarn' into tmp/octopus/w/8.0/improvement/S3C-2363-install-yarn 2019-08-01 18:29:10 +00:00
Katherine Laue 02272b75af improvement/S3C-2363 migrate package manager to yarn 2019-08-01 11:25:59 -07:00
Katherine Laue 351a2a339e Merge remote-tracking branch 'origin/w/7.5/improvement/S3C-2292-upgrade-nodejs' into w/8.0/improvement/S3C-2292-upgrade-nodejs 2019-06-28 14:03:48 -07:00
Katherine Laue 4e0d97cf69 improvement: S3C-2292 Upgrade node version 2019-06-28 13:12:12 -07:00
David Pineau 1a6e052fb2 Merge remote-tracking branch 'origin/development/7.4' into development/8.0 2018-06-28 18:30:33 +02:00
David Pineau 0ff7ec82f0 Merge remote-tracking branch 'origin/development/6.4' into development/7.4 2018-06-28 18:27:28 +02:00
David Pineau 40b92c545d [Workflow] First branching: Use commit hashs instead of tags for dependencies 2018-06-28 18:23:18 +02:00
David Pineau 96ed486230 Merge remote-tracking branch 'origin/development/6.4' into development/7.4 2018-06-27 18:13:12 +02:00
David Pineau 315fb1b018 [Workflow] Use tags instead of branches for dependencies 2018-06-27 18:11:30 +02:00
David Pineau d71cb98f7e [Workflow] Use tags instead of branches for dependencies 2018-06-27 18:10:59 +02:00
Rahul Padigela 74b121bef4 chore: update version and dependencies 2018-05-30 16:38:38 -07:00
Rahul Padigela 224e312f86
Merge pull request #111 from scality/fwdport/7.4-beta-master
Fwdport/7.4 beta master
2018-04-20 19:38:54 -07:00
Rahul Padigela 8fd2c05ee8 chore: update dependencies 2018-04-20 14:09:43 -07:00
Rahul Padigela 12f3978a44 Merge remote-tracking branch 'origin/rel/7.4-beta' into fwdport/7.4-beta-master 2018-04-20 14:06:07 -07:00
Rahul Padigela 673fdad7d1
Merge pull request #110 from scality/fwdport/7.4-7.4-beta
Fwdport/7.4 7.4 beta
2018-04-19 11:34:59 -07:00
Rahul Padigela cd1dde085f Merge remote-tracking branch 'origin/rel/7.4' into fwdport/7.4-7.4-beta 2018-04-19 11:10:53 -07:00
alexandre-merle 288ee773f8
Merge pull request #109 from scality/feature/update-node-version
feature: update node version
2018-03-26 13:25:11 +02:00
Alexandre Merle c92b2a7255 feature: update node version
Update node version to 6.13.1
2018-03-26 12:57:21 +02:00
Rahul Padigela 2e868922ef ft: update version number 2018-03-14 13:28:47 -07:00
Rahul Padigela 5ebaedefe1 ft: update package.json dependencies 2018-03-14 13:10:58 -07:00
David Pineau 2c730376fa
Merge pull request #108 from scality/feature/S3C-1245-deps
S3C-1245 update dependencies
2018-02-05 14:42:54 +01:00
Anne Harper 9c60ac2a6d S3C-1245 update dependencies 2018-02-05 14:36:06 +01:00
Anne Harper 62090c963b S3C-1245 update dependencies 2018-02-05 12:18:18 +01:00
ironman-machine c8d50822a7 merge #107 2017-11-21 21:25:22 +00:00
David Pineau 8c22e6aa90 Merge pull request #106 from scality/fix/dependencies
fix(deps): use the 7.2 dependencies
2017-11-21 14:53:25 +01:00
Thibault Riviere df33184f7c fix(deps): use the 7.2 dependencies 2017-11-20 12:11:18 +01:00
ironman-machine ea2168a79a merge #104 2017-09-19 04:35:42 +00:00
philipyoo a2ce1c1e3d chore: reflect new lint changes
fix no-prototype-builtins
fix space-unary-ops
fix import/no-unresolved
fix indent-legacy
2017-09-18 16:31:10 -07:00
ironman-machine c00576a071 merge #103 2017-09-11 23:18:11 +00:00
Rached Ben Mustapha da254c4245 fix: allow upgrading node version 2017-09-11 12:20:04 -07:00
alexandre-merle 9b6f2b6dbd Merge pull request #102 from scality/fwd/7.0-to-master
Fwd/7.0 to master
2017-08-09 17:02:38 +02:00
Alexandre Merle 976647c993 Merge remote-tracking branch 'origin/rel/7.0' into fwd/7.0-to-master 2017-08-09 15:55:56 +02:00
alexandre-merle d3f913d8d9 Merge pull request #101 from scality/fix/rel/7.0
fix 7.0
2017-08-09 14:20:08 +02:00
Alexandre Merle 66b5d3ab28 fix 7.0 2017-08-09 14:02:06 +02:00
David Pineau f102a78655 Merge pull request #99 from scality/S3C-703/Fix-werelogs-design
S3C-703: Fix werelogs design
2017-08-08 20:06:27 +02:00
David Pineau 5ad3da938f Remove obsolete config bits from Functional tests
Now that the Backwards Compatibility has been proven through minimal update of
the functional tests; we can clean-up the leftovers bits of deadcode left
around to minimize the diff.

This removes the obsolete configurations formerly provided to the constructor
of the `Logger` class.
2017-08-07 11:03:12 +02:00
David Pineau 829964a199 Make SimpleLogger default consistent with Config defaults 2017-08-07 11:03:12 +02:00
David Pineau 683ecde9fe Provide a new Werelogs API
The former API presented a defect that required updating the design.
Indeed, due to the update() behavior of the instanciation of any new
werelogs Logger, a dependency could potentially override the original
configuration, making logs hard to control.

This design offers a new layer on top of the previous one, for the
following benefits:

 - Backwards compatibility on the handling of the Loggers:

   * A `Logger` object can still quickly be instanciated, without updating the
     constructor call (Proved by the minimal update on functional tests)

   * Configuration is still shared within a global-like scope: the Werelogs
     pre-instanciated `API` (encompassing Loggers spawned from it, and their
     children objects)

 - Configuration is no longer global, nor updated through the instanciation of
   a `Logger`, avoiding the raised issue

 - Provide a convenience API for the users that may not want to
   handle the API instance directly (usually not necessary) to still be able to
   isntanciate Loggers directly, but also to still be able to reconfigure the
   logging levels and streams

Additionally:

 * The config documentation has been updated to be more consistent

 * Unit tests of Logger have been cleaned-up to remove any concern of Config
   handling, as it is the sole and exclusive responsibility of the Config
   object

 * Functional tests have been updated relatively to the way to re-configure the
   logging system, in a way that shows that instanciating a Logger is not broken
2017-08-07 11:03:12 +02:00
ThibaultRiviere c260957231 Merge pull request #98 from scality/S3C-654/check-validity-level
S3C-654: check validity level vs. dump level
2017-07-21 15:02:18 +02:00
Lam Pham-Sy 7c8a333fc3 Update log level
Check validity of log level vs. log dump level
2017-07-20 21:17:57 +02:00
ironman-machine ac016eb00f merge #94 2017-05-05 11:09:33 +00:00
Lam Pham-Sy 7d13f81d81 lib/Utils.js: enhance copying objects 2017-05-05 11:50:55 +02:00
ironman-machine 4f30bfcdc9 merge #96 2017-05-04 16:51:15 +00:00
Mathieu Cassagne f09a12ed3a SimpleLogger: Call os.hostname() only once
We called os.hostname() each time we log something.
This avoid it.
2017-05-04 17:11:11 +02:00
ironman-machine e5592a6d2f merge #95 2017-04-28 17:05:13 +00:00
Mathieu Cassagne a54cbf1ec4 generateUid: Faster implementation
The way we used to generate uid for a request is not the fastest one.
This one is really faster and generate uid of the same look.
The base algorithm is taken from here: http://jcward.com/UUID.js
2017-04-28 14:39:32 +02:00
Rahul Padigela 5564f8954c Merge pull request #92 from scality/ft/node-v6
FT: Node v6
2017-02-28 11:03:52 -08:00
Rahul Padigela 3b2e67d53b Merge pull request #93 from scality/ft/fix-deps
FT: Fix deps
2017-02-21 20:14:25 -08:00
Alexandre Merle 52d55e6144 FT: Fix deps
Fix dependencies
2017-02-21 04:43:33 +01:00
Alexandre Merle 74033d2c68 FT: Node v6
Switch to node v6
2017-02-15 13:05:04 +01:00
David Pineau cb81bf7e5f Merge pull request #90 from scality/Forward-rel/6.4-to-master
Forward rel/6.4 to master
2017-01-19 13:19:41 +01:00
David Pineau d56bf0e56d Merge remote-tracking branch 'origin/rel/6.4' into Forward-rel/6.4-to-master 2017-01-19 12:15:49 +01:00
David Pineau f6e67043bd Merge pull request #89 from scality/bump/rel/6.4
[package](BUMP): bump deps version to rel/6.4
2017-01-19 11:55:27 +01:00
David Pineau 7ac41e0371 [package](BUMP): bump deps version to rel/6.4 2017-01-19 11:21:40 +01:00
David Pineau e9ee36e89b Merge pull request #84 from scality/fwdport-6.3-to-master
Fwdport 6.3 to master
2016-12-12 17:55:44 +01:00
alexandremerle 6262eaf3e7 Merge remote-tracking branch 'origin/rel/6.3' into fwdport-6.3-to-master 2016-12-12 15:07:34 +01:00
alexandremerle 25f8b513ec Update package.json (6.3) 2016-12-12 15:06:48 +01:00
Rahul Padigela 659d908f89 Merge pull request #83 from scality/FIX/module-logger-time
Include time in module logger
2016-11-16 18:44:27 -08:00
Rached Ben Mustapha f621cbf59c Include time in module logger 2016-11-16 17:58:37 -08:00
Rahul Padigela a64f2bad04 Merge pull request #81 from scality/linter
Fix warnings and set warnings as hard errors
2016-10-04 12:00:36 -07:00
Guillaume Gomez bb67ce5b22 Fix markdown files as well 2016-09-22 18:15:54 +02:00
Guillaume Gomez 64fabfef6b Fix warnings and set warnings as hard errors 2016-09-22 17:09:07 +02:00
Michael Zapata f6ae7c8e7e Merge pull request #76 from scality/ft/type_definition
feat(typescript): add definition file for TS users
2016-08-11 11:37:14 +02:00
Michael Zapata c22f4c783e
feat(typescript): add definition file for TS users 2016-08-11 11:15:03 +02:00
Michael Zapata 9019fc11b7 Merge pull request #77 from scality/rf/use_new_werelogs_api
refactor(werelogs): export using ES6 standards
2016-08-11 10:51:46 +02:00
Michael Zapata bc49b1104d
refactor(werelogs): export using ES6 standards
Update `index.js` to provide a `Logger` field to match expected
behaviour under ES6 or TypeScript

BREAKING CHANGE
2016-08-03 10:59:22 +02:00
David Pineau 1681574df3 Bump minor version 2016-06-11 06:59:19 +02:00
David Pineau 9ec20cc2d9 Merge pull request #71 from scality/fwdport/1.0
Fwdport/1.0
2016-06-10 18:57:32 +02:00
David Pineau 9b3467283b Merge branch rel/1.0 into fwdport/1.0 2016-06-10 18:51:47 +02:00
David Pineau df7878a7d8 Merge pull request #73 from scality/ft/contributing
Add Contribution rules
2016-06-08 21:02:09 +02:00
David Pineau b1e4735421 Add Contribution rules 2016-06-08 20:59:07 +02:00
David Pineau 9cfd4db6dc Merge pull request #72 from scality/fix/UpdateBadge
Update Badges with both public and internal
2016-06-08 20:43:28 +02:00
David Pineau a66f0624ea Update Badges with both public and internal 2016-06-08 20:36:24 +02:00
David Pineau 8029bfde6b Merge pull request #70 from scality/ft/license
Set OSS License and 1.0.0 version for open-sourcing
2016-06-08 19:36:10 +02:00
David Pineau 31c9c86e6d Update OSS License 2016-06-08 19:33:24 +02:00
Rahul Padigela 96b2d0c206 Merge pull request #68 from scality/dev/perf/replaceObjAssign
perf: replace Object.assign with vanilla looping
2016-06-07 15:32:13 -07:00
Rahul Padigela ca79cbe546 perf: remove redundant copying of fields 2016-06-07 10:42:32 -07:00
Rahul Padigela 86d6047a4f perf: replace Object.assign with vanilla looping
Object.assign is currently not very performant, replacing it with
standard looping.
2016-06-07 10:42:32 -07:00
Rahul Padigela 7c8b1c1edf Merge pull request #67 from scality/dev/FT/safeJSONStringify
FT: safe JSON.stringify
2016-06-07 10:41:37 -07:00
Rahul Padigela 76023748f7 FT: safe JSON.stringify
Protects from JSON.stringfy throwing an exception when the object
being stringified has circular references.
Fix #66
2016-06-06 11:13:23 -07:00
Michael Zapata 5990ed27f7 Merge pull request #65 from scality/fwdport
Fwdport
2016-06-01 13:21:07 +02:00
Michael Zapata d9cef07a4d
Merge remote-tracking branch 'origin/rel/1.0' into fwdport 2016-06-01 13:17:37 +02:00
Rahul Padigela 6b3cef1a38 Merge pull request #62 from scality/dev/FT/Werelogs2.0
FT/Werelogs 2.0
2016-05-31 19:36:07 -07:00
Rahul Padigela b1a38119be perf: use Date.now() for timestamp generation
V8 profiling revealed that under load new Date() consumes a ton of
cpu ticks, using Date.now() is ~33% faster. The idea is filebeat or
logstash component would parse this long int timestamp into ISO format.
2016-05-31 17:14:46 -07:00
Rahul Padigela 6052b902d3 FT: STDOUT logging
- Introduces SimpleLogger interface which writes the logs in JSON
to STDOUT
- Removes bunyan, bunyan-logstash modules which are no longer used.

Fix #60
2016-05-31 17:14:40 -07:00
David Pineau 4b75eb4fb7 Merge pull request #59 from scality/fwdport/1.0
Fwdport branch rel/1.0 into master
2016-05-11 20:50:28 +02:00
David Pineau 83718c0fa0 Merge branch 'rel/1.0' into master 2016-05-11 20:44:21 +02:00
Rahul Padigela 398e370c49 Merge pull request #53 from scality/dev/FT/addTimeField
FT: add time field
2016-05-02 18:56:41 -07:00
Rahul Padigela 8a5decdc45 FT: add time field
Normally node-bunyan automatically adds this field if it's omitted,
but it uses lodash to generate the timestamp, which in turn uses
a costly regex to figure out if the date method is available in an
env. That particular call was consuming a lot of cpu ticks, sitting
up top on the profiler log.
2016-05-02 18:52:32 -07:00
Rahul Padigela 2130baffd8 Merge pull request #54 from scality/dev/perf/removeHrtime
perf: remove hrtime field
2016-05-02 18:48:55 -07:00
Rahul Padigela 1a86fe03eb perf: remove hrtime field
Stringifying hrtime proved to be expensive as it was consuming a
lot of cpu ticks in the profiler log.
2016-05-02 18:46:33 -07:00
Rahul Padigela dad0ec7a8f Merge pull request #56 from scality/dev/CLEANUP/removeBabelEslint
cleanup: remove babel-eslint dep
2016-04-29 10:36:03 -07:00
Rahul Padigela 4ddb13c2b5 cleanup: remove babel-eslint dep
npm run lint fails currently as babel-eslint dependency has been
removed.
Fixes #55
2016-04-28 12:17:36 -07:00
jmunoznaranjo c87bf36fbf Merge pull request #52 from scality/fwdport
Fwdport
2016-04-01 10:40:52 +02:00
Juan Muñoz edf0b957a5 Merge remote-tracking branch 'origin/rel/1.0' into fwdport 2016-03-31 12:54:20 +02:00
David Pineau 6f0a0dfe74 Merge pull request #49 from scality/rel/1.0
Forward-port rel/1.0 changes to master
2016-03-18 10:33:09 +01:00
David Pineau 35f2e5f151 Merge pull request #47 from scality/rel/1.0
Forward-port rel/1.0 changes to master
2016-03-14 11:23:11 +01:00
34 changed files with 1808 additions and 681 deletions

View File

@ -1,14 +1 @@
{
"parser": "babel-eslint",
"extends": "airbnb",
"env": {
"node": true,
"mocha": true
},
"rules": {
"indent": [2,4],
"no-multi-spaces": [2, { exceptions: { "SwitchCase": true, "CallExpression": true } } ],
"valid-jsdoc": 2,
"strict": 0
}
}
{ "extends": "scality" }

31
.github/workflows/tests.yaml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Tests
on:
push:
branches-ignore:
- development/**
- q/*/**
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkokut
uses: actions/checkout@v4
- name: Install deps
run: sudo apt-get update -q
- uses: actions/setup-node@v4
with:
node-version: '16'
- name: Install Yarn
run: npm install -g yarn
- name: install dependencies
run: yarn install --frozen-lockfile
- name: run lint
run: echo "linter is disabled temporarily ()" || yarn run --silent lint -- --max-warnings 0
- name: run lint_md
run: yarn --silent lint_md
- name: run test
run: yarn test
- name: run coverage
run: yarn coverage

View File

@ -1,53 +1,5 @@
# Contributing to WereLogs
# Contributing rules
Contributing to WereLogs can take multiple shapes:
* Reporting a bug
* Requesting/Suggesting/Discussing a feature
* Providing Code:
- a bug fix
- a new feature
- improving the documentation
Each of these shapes of contribution must comply to a given set of rules, to
facilitate cooperation.
## Discussing the project
This project is an opensource project, provided to you by other developers, and
they too, are human. Thus, please try to explain your point of view when
discussing a feature or bug, with examples, ascii art drawings, in an organized
discourse. This should help everyone understand your point better, as well as
it creates a context and cordial exchange between people. At any moment, try to
keep your calm, and avoid flaming, as it never helps any discussion, and surely
repulses some of the other contributors.
Summarizing:
* Keep calm and stay cordial
* Explain your point, using the help of samples of code, ascii art diagrams
etc.
## Reporting an Issue
In order to report a bug, here are a few rules that can help both the
reporter as well as the developer of the project:
* A bug report's name must shortly describe the user-side effect of the bug
* A bug report's body must provide the following informations:
- Describe in detail the effect of the bug in an expected usage situation
- Provide information about the setup in which werelogs is used, if you feel
it is relevant.
Some additional useful information for a bug report can be:
- Running environment
- A sample of code that triggers the bug (REALLY helpful and appreciated)
- Describe any potential work-around found for the bug
## Requesting or Suggesting a feature
In order for any feature suggestion to be clear, please comply to the following
basic rules:
* Issue title must be clear about the feature (a clear and semantic name might
help) and what value it adds to the project
* The description of the suggestion must provide insight into:
- What the feature provides
- At least one use-case of the feature (to show the value of it for the
project)
- If thought of, one suggestion as to how technically implement the feature
Please follow the
[Contributing Guidelines](
https://github.com/scality/Guidelines/blob/master/CONTRIBUTING.md).

View File

@ -41,8 +41,9 @@ as it means that whenever the errors happen, depending on your log level, you
might have already lost quite a bit of priceless information about the error
encountered, and the code path the request went through. To address this, we
offer multiple features:
* [Request ID namespacing](###request-id-namespacing)
* [Request unit Logs](###request-unit-logs)
* [Request ID namespacing](###request-id-namespacing)
* [Request unit Logs](###request-unit-logs)
### Request ID namespacing
@ -87,5 +88,3 @@ error (or higher level logging operation) is logged before the log context is
freed, then the full set of buffered logging messages is freed, not taking
any logging resources for the log entries not considered 'useless' by a given
log level configuration.

191
LICENSE Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2016 Scality
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,16 +1,25 @@
# WereLogs
[![Scality CI][badgepriv]](http://ci.ironmann.io/gh/scality/werelogs)
This repository provides a NodeJS Library that aims to be an efficient logging
library, reducing as much as possible the need to compute anything in NodeJS,
and focusing on a simple I/O scheme. The goal here is to make the most of
NodeJS's strengths, but relying on its I/O capacities, and avoiding any form of
computation that is known to not be advantageous in Node.
## Contributing
In order to contribute, please follow the
[Contributing Guidelines](
https://github.com/scality/Guidelines/blob/master/CONTRIBUTING.md).
## Installing the Library
In order to install WereLogs, you can use NPM with github's HTTP url, and save
it in your own package.json:
```
```sh
$> npm i --save scality/werelogs
```
@ -62,13 +71,9 @@ import Logger from 'werelogs';
* Object.
*
* This config options object contains a log level called 'level', a log
* dumping threshold called 'dump', and an array of stream called 'streams'.
* The 'streams' option shall follow bunyan's configuration needs, as werelogs
* acts almost as a passthrough for this specific option. The only unnecessary
* dumping threshold called 'dump'. The only unnecessary
* field is the 'level' of each individual stream, as werelogs is managing
* that on its own. For the reference about how to configure bunyan's streams,
* please refer to its repository's readme:
* https://github.com/trentm/node-bunyan
* that on its own.
*
* All request loggers instantiated through this Logger will inherit its
* configuration.
@ -78,19 +83,6 @@ const log = new Logger(
{
level: 'debug',
dump: 'error',
streams: [
// A simple, usual logging stream
{ stream: process.stdout},
// A more complex logging stream provided by a bunyan plugin
// that will upload the logs directly to an ELK's logstash service
{
type: 'raw',
stream: require('bunyan-logstash').createStream({
host: '127.0.0.1',
port: 5505,
}),
}
],
}
);
@ -165,8 +157,3 @@ In order to find out the known issues, it is advised to take a look at the
[project's github page](http://github.com/scality/werelogs). There, you should
be able to find the issues, tagged with the releases they are impacting,
whether they're open or closed.
## Contributing
The contributing rules for this project are defined in the associated
CONTRIBUTING.md file.

View File

@ -1,23 +0,0 @@
general:
branches:
ignore:
- /^ultron\/.*/ # Ignore ultron/* branches
artifacts:
- coverage/
- doc/
machine:
node:
version: 4.1.0
test:
override:
- npm run lint_md
- npm run lint
- npm run gendoc
- npm run coverage
# running ft_test packs werelogs and installs it + deps into
# tests/functional. Pack is like publishing werelogs in a local tgz
# archive that can be installed.
# This step shall ensure that no issue is encountered when installing
# the package, and allows to functionally test werelogs.
- npm run ft_test

65
index.d.ts vendored Normal file
View File

@ -0,0 +1,65 @@
interface WerelogsConfigOptions {
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
dump?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
streams?: object[];
}
declare class WerelogsConfig {
constructor(config?: WerelogsConfigOptions);
reset(): WerelogsConfig;
update(config: WerelogsConfig): WerelogsConfig;
logger: any;
level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
dump: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
end: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
}
interface LogDictionary {
httpMethod?: string;
httpURL?: string;
[field: string]: any;
}
declare module 'werelogs' {
export class RequestLogger {
constructor(
logger: any,
logLevel: string,
dumpThreshold: string,
endLevel: string,
uids?: string|Array<string>
);
getUids(): Array<string>;
getSerializedUids(): string;
addDefaultFields(fields: LogDictionary): LogDictionary;
trace(msg: string, data?: LogDictionary): void;
debug(msg: string, data?: LogDictionary): void;
info(msg: string, data?: LogDictionary): void;
warn(msg: string, data?: LogDictionary): void;
error(msg: string, data?: LogDictionary): void;
fatal(msg: string, data?: LogDictionary): void;
end(msg: string, data?: LogDictionary): void;
errorEnd(msg: string, data?:LogDictionary): void;
}
export class Logger {
name: string;
constructor(name: string);
newRequestLogger(uids?: string|Array<string>): RequestLogger;
newRequestLoggerFromSerializedUids(uids: string): RequestLogger;
trace(msg: string, data?: LogDictionary): void;
debug(msg: string, data?: LogDictionary): void;
info(msg: string, data?: LogDictionary): void;
warn(msg: string, data?: LogDictionary): void;
error(msg: string, data?: LogDictionary): void;
fatal(msg: string, data?: LogDictionary): void;
}
export function configure(config: WerelogsConfigOptions): void;
export class API {
constructor(config: WerelogsConfigOptions);
reconfigure(config: WerelogsConfigOptions): void;
Logger: Logger;
}
}

View File

@ -1 +1,51 @@
module.exports = require('./lib/Logger.js');
const API = require('./lib/api.js');
const stderrUtils = require('./lib/stderrUtils');
/*
* For convenience purposes, we provide an already instanciated API; so that
* old uses of the imported Logger class can be kept as-is. For quick logging,
* this also provides a hassle-free way to log using werelogs.
*/
const werelogs = new API();
module.exports = {
Logger: werelogs.Logger,
configure: werelogs.reconfigure.bind(werelogs),
Werelogs: API,
/**
* Timestamp logs going to stderr
*
* @example <caption>Simplest usage</caption>
* ```
* const { stderrUtils } = require('werelogs');
* stderrUtils.catchAndTimestampStderr();
* ```
*
* @example <caption>Manage process exit</caption>
* ```
* const { stderrUtils } = require('werelogs');
* // set exitCode to null to keep process running on uncaughtException
* stderrUtils.catchAndTimestampStderr(undefined, null);
* // application init
* process.on('uncaughtException', (err) => {
* // custom handling, close connections, files
* this.worker.kill(); // or process.exit(1);
* });
* // Note you could use prependListener to execute your callback first
* // and then let stderrUtils exit the process.
* ```
*
* @example <caption>Custom listener</caption>
* ```
* const { stderrUtils } = require('werelogs');
* stderrUtils.catchAndTimestampWarning();
* // application init
* process.on('uncaughtException', (err, origin) => {
* stderrUtils.printErrorWithTimestamp(err, origin);
* // close and stop everything
* process.exit(1);
* });
* ```
*/
stderrUtils,
};

View File

@ -1,9 +1,8 @@
'use strict';
const bunyan = require('bunyan');
// eslint-disable-line strict
const LogLevel = require('./LogLevel.js');
const SimpleLogger = require('./SimpleLogger.js');
/**
* This class is the central point of the once-only configuration system for
* werelogs. It is instanciated once to be exported, and all configuration
@ -16,10 +15,31 @@ class Config {
* This is the default constructor of the Config Object, and the only way
* to instanciate it (with default parameters).
*
* @param {object} conf - A configuration object for werelogs.
* @param {string} conf.level - The string name of the logging level
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {string} conf.dump - The string name of the log dumping
* level ('trace', 'debug', 'info',
* 'warn', 'error' and 'fatal' in order
* of importance.)
* @param {object[]} conf.streams - The array of streams into which to
* log. This is an Array of objects
* which have a field named 'stream',
* which is writeable.
*
* @returns {undefined}
*/
constructor() {
this.reset();
constructor(conf) {
this.logLevel = 'info';
this.dumpThreshold = 'error';
this.endLevel = 'info';
this.streams = [{ level: 'trace', stream: process.stdout }];
this.simpleLogger = new SimpleLogger('werelogs', this.streams);
if (conf) {
this.update(conf);
}
}
/**
@ -51,12 +71,10 @@ class Config {
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {object[]} config.streams - The array of streams into which to log.
* Their configuration is directly
* related to the expected bunyan
* streams array, for compatibility
* purposes (except that the 'level'
* field is not accounted for)
* @param {object[]} config.streams - The array of streams into which to
* log. This is an Array of objects
* which have a field named 'stream',
* which is writeable.
*
* @see [Bunyan's documentation]{@link
* https://github.com/trentm/node-bunyan/blob/master/README.md#streams} for
@ -71,37 +89,50 @@ class Config {
const checkedConfig = config || {};
if (checkedConfig.hasOwnProperty('level')) {
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'level')) {
LogLevel.throwIfInvalid(checkedConfig.level);
}
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'dump')) {
LogLevel.throwIfInvalid(checkedConfig.dump);
}
// for check log level vs. log dump level
const newLogLevel = checkedConfig.level || this.logLevel;
const newLogDumpLevel = checkedConfig.dump || this.dumpThreshold;
if (newLogDumpLevel
&& !LogLevel.shouldLog(newLogDumpLevel, newLogLevel)) {
throw new Error(
'Logging level should be at most logging dump level',
);
}
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'level')) {
this.logLevel = checkedConfig.level;
}
if (checkedConfig.hasOwnProperty('dump')) {
LogLevel.throwIfInvalid(checkedConfig.dump);
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'dump')) {
this.dumpThreshold = checkedConfig.dump;
}
if (checkedConfig.hasOwnProperty('end')) {
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'end')) {
LogLevel.throwIfInvalid(checkedConfig.end);
this.endLevel = checkedConfig.end;
}
if (checkedConfig.hasOwnProperty('streams')) {
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'streams')) {
if (!Array.isArray(checkedConfig.streams)) {
throw new Error('WereLogs config.streams must be an Array of Writeable Streams.');
throw new TypeError('WereLogs config.streams must be an Array '
+ 'of Writeable Streams.');
}
if (!checkedConfig.streams.length) {
throw new Error('Werelogs config.streams must contain at least one stream.');
throw new Error('Werelogs config.streams must contain at '
+ 'least one stream.');
}
this.streams = checkedConfig.streams.map((stream) => {
stream.level = 'trace';
this.streams = checkedConfig.streams.map(stream => {
stream.level = 'trace'; // eslint-disable-line no-param-reassign
return stream;
});
this.bLogger = bunyan.createLogger({
name: 'werelogs',
streams: this.streams,
});
this.simpleLogger = new SimpleLogger('werelogs', this.streams);
}
return this;
@ -117,7 +148,7 @@ class Config {
* operations by the user code.
*/
get logger() {
return this.bLogger;
return this.simpleLogger;
}
/**
@ -157,4 +188,4 @@ class Config {
}
}
module.exports = new Config();
module.exports = Config;

View File

@ -1,4 +1,5 @@
'use strict';
// eslint-disable-line strict
const logLevels = [
'trace',
@ -18,10 +19,15 @@ const logLevels = [
*
* @throw {Error} A human-readable message that tells which log
* levels are supported.
* @throw {TypeError} A human-readable message indicating that the
* provided logging level was not a string
*
* @returns {undefined}
*/
function throwIfInvalid(level) {
if (typeof level !== 'string') {
throw new TypeError('Logging level should be a string');
}
if (logLevels.indexOf(level) === -1) {
throw new RangeError(`Invalid logging level: ${level} is neither`
+ ` ${logLevels.join(', ')}.`);

View File

@ -1,52 +1,32 @@
'use strict';
// eslint-disable-line strict
const LogLevel = require('./LogLevel.js');
const RequestLogger = require('./RequestLogger.js');
const unserializeUids = require('./Utils.js').unserializeUids;
const { unserializeUids } = require('./Utils.js');
const Config = require('./Config.js');
class Logger {
/**
* This is the constructor of the Logger class. It takes optional
* configuration parameters, that allow to modify its behavior.
*
* @param {string} name - The name of the Logger. It can be found later on in
* the log entries.
* @param {Werelogs.Config} config - An instanciated Werelogs Config object
*
* @param {object} config - A configuration object for werelogs.
* @param {string} config.level - The string name of the logging level
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {string} config.dump - The string name of the log dumping level
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {object[]} config.streams - The array of streams into which to log.
* Their configuration is directly
* related to the expected bunyan
* streams array, for compatibility
* purposes (except that the 'level'
* field is not accounted for)
*
* @see [Bunyan's documentation]{@link
* https://github.com/trentm/node-bunyan/blob/master/README.md#streams} for
* a more detailed description of the streams array configuration.
* @param {string} name - The name of the Logger. It can be found later on
* in the log entries.
*
* @returns {undefined}
*/
constructor(name, config) {
constructor(config, name) {
if (!(config instanceof Config)) {
throw new TypeError('Invalid parameter Type for "config".');
}
if (!(typeof name === 'string' || name instanceof String)) {
throw new TypeError('Invalid parameter Type for "name".');
}
this.config = config;
this.name = name;
Config.update(config);
}
setLevel(levelName) {
Config.update({ level: levelName });
}
setDumpThreshold(levelName) {
Config.update({ dump: levelName });
}
/**
@ -58,10 +38,10 @@ class Logger {
* @returns {RequestLogger} A Valid Request Logger
*/
newRequestLogger(uids) {
const rLog = new RequestLogger(Config.logger,
Config.level, Config.dump, Config.end,
uids);
rLog.addDefaultFields({name: this.name});
const rLog = new RequestLogger(this.config.logger,
this.config.level, this.config.dump,
this.config.end, uids);
rLog.addDefaultFields({ name: this.name });
return rLog;
}
@ -74,35 +54,37 @@ class Logger {
* @returns {RequestLogger} A Valid Request Logger
*/
newRequestLoggerFromSerializedUids(serializedUids) {
const rLog = new RequestLogger(Config.logger,
Config.level, Config.dump, Config.end,
unserializeUids(serializedUids));
rLog.addDefaultFields({name: this.name});
const rLog = new RequestLogger(this.config.logger,
this.config.level, this.config.dump,
this.config.end,
unserializeUids(serializedUids));
rLog.addDefaultFields({ name: this.name });
return rLog;
}
_doLog(levelName, msg, data) {
const bLogger = Config.logger;
const finalData = { name: this.name };
if (!LogLevel.shouldLog(levelName, Config.level)) {
const sLogger = this.config.logger;
const finalData = { name: this.name, time: Date.now() };
if (!LogLevel.shouldLog(levelName, this.config.level)) {
return;
}
if (data !== undefined && typeof data !== 'object') {
bLogger.fatal(
sLogger.fatal(
{
'callparams': [ msg, data ],
callparams: [msg, data],
},
'Werelogs API was mis-used.'
+ ' This development error should be fixed ASAP.');
+ ' This development error should be fixed ASAP.',
);
return;
}
if (data) {
Object.keys(data).forEach((k) => {
Object.keys(data).forEach(k => {
finalData[k] = data[k];
});
}
const args = [ finalData, msg ];
bLogger[levelName].apply(bLogger, args);
const args = [finalData, msg];
sLogger[levelName].apply(sLogger, args);
}
/**

View File

@ -1,15 +1,15 @@
'use strict';
const assert = require('assert');
// eslint-disable-line strict
const LogLevel = require('./LogLevel.js');
const Utils = require('./Utils.js');
const serializeUids = Utils.serializeUids;
const generateUid = Utils.generateUid;
const { serializeUids, generateUid, objectCopy } = Utils;
function ensureUidValidity(uid) {
if (uid.indexOf(':') !== -1) {
throw new Error(`RequestLogger UID "${uid}" contains an illegal character: ':'.`);
throw new Error(`RequestLogger UID "${uid}" contains an illegal `
+ 'character: \':\'.');
}
return uid;
}
@ -21,12 +21,9 @@ class EndLogger {
}
augmentedLog(level, msg, data) {
assert.strictEqual(this.logger.elapsedTime, null, 'The logger\'s'
+ 'end() wrapper should not be called more than'
+ ' once.');
// We can alter current instance, as it won't be usable after this
// call.
this.fields = Object.assign(this.fields, data || {});
this.fields = objectCopy(this.fields, data || {});
return this.logger.log(level, msg, this.fields, true);
}
@ -45,7 +42,7 @@ class EndLogger {
*/
addDefaultFields(fields) {
const oldFields = this.fields;
this.fields = Object.assign({}, this.fields, fields);
this.fields = objectCopy({}, this.fields, fields);
return oldFields;
}
@ -141,7 +138,6 @@ class EndLogger {
* request.
*/
class RequestLogger {
/**
* Constructor of the WereLogs Request Logger.
* This function takes a logger instance, a logging level, and a last
@ -177,7 +173,7 @@ class RequestLogger {
* @returns {undefined}
*/
constructor(logger, logLevel, dumpThreshold, endLevel, uids) {
let uidList = undefined;
let uidList;
if (!LogLevel.shouldLog(dumpThreshold, logLevel)) {
throw new Error('Logging Dump level should be equal or'
@ -187,10 +183,10 @@ class RequestLogger {
if (uids !== undefined && Array.isArray(uids)) {
uidList = uids.map(uid => ensureUidValidity(uid));
uidList.push(generateUid());
} else if (uids !== undefined && typeof(uids) === 'string') {
uidList = [ ensureUidValidity(uids) ];
} else if (uids !== undefined && typeof uids === 'string') {
uidList = [ensureUidValidity(uids)];
}
this.uids = uidList || [ generateUid() ];
this.uids = uidList || [generateUid()];
this.entries = [];
this.fields = {};
@ -198,7 +194,7 @@ class RequestLogger {
this.dumpThreshold = dumpThreshold;
this.endLevel = endLevel;
this.endLogger = new EndLogger(this);
this.bLogger = logger;
this.sLogger = logger;
this.startTime = process.hrtime();
this.elapsedTime = null;
@ -248,7 +244,7 @@ class RequestLogger {
*/
addDefaultFields(fields) {
const oldFields = this.fields;
this.fields = Object.assign({}, this.fields, fields);
this.fields = objectCopy({}, this.fields, fields);
return oldFields;
}
@ -369,8 +365,6 @@ class RequestLogger {
if (msg === undefined && data === undefined) {
return this.endLogger;
}
assert.strictEqual(this.elapsedTime, null, 'The "end()" logging method '
+ 'should not be called more than once.');
return this.log(this.endLevel, msg, data, true);
}
@ -387,8 +381,6 @@ class RequestLogger {
* @returns {undefined}
*/
errorEnd(msg, data) {
assert.strictEqual(this.elapsedTime, null, 'The "end()" logging method '
+ 'should not be called more than once.');
return this.log('error', msg, data, true);
}
@ -429,24 +421,33 @@ class RequestLogger {
'Werelogs API was mis-used.'
+ ' This development error should be fixed ASAP.',
{
'callparams': [ msg, logFields ],
});
callparams: [msg, logFields],
},
);
return;
}
const fields = Object.assign({}, logFields || {});
const fields = objectCopy({}, this.fields, logFields || {});
const endFlag = isEnd || false;
const hr = process.hrtime();
/*
* Here, Stringify hrtime for it to stay within a one-liner when piping
* the output through bunyan's cli tool.
* Then prepend the fields to the argument Array we're preparing for
* bunyan
*/
fields.hrtime = JSON.stringify(hr);
* using Date.now() as it's faster than new Date(). logstash component
* uses this field to generate ISO 8601 timestamp
*/
if (fields.time === undefined) {
fields.time = Date.now();
}
// eslint-disable-next-line camelcase
fields.req_id = serializeUids(this.uids);
if (endFlag) {
if (this.elapsedTime !== null) {
// reset elapsedTime to avoid an infinite recursion
// while logging the error
this.elapsedTime = null;
this.error('RequestLogger.end() has been called more than once');
}
this.elapsedTime = process.hrtime(this.startTime);
// eslint-disable-next-line camelcase
fields.elapsed_ms = this.elapsedTime[0] * 1000
+ this.elapsedTime[1] / 1000000;
}
@ -458,7 +459,7 @@ class RequestLogger {
this.entries.push(logEntry);
if (LogLevel.shouldLog(level, this.dumpThreshold)) {
this.entries.forEach((entry) => {
this.entries.forEach(entry => {
this.doLogIO(entry);
});
this.entries = [];
@ -483,26 +484,24 @@ class RequestLogger {
* @returns {undefined}
*/
doLogIO(logEntry) {
const fields = Object.assign({}, this.fields, logEntry.fields);
switch (logEntry.level) {
case 'trace':
this.bLogger.trace(fields, logEntry.msg);
this.sLogger.trace(logEntry.fields, logEntry.msg);
break;
case 'debug':
this.bLogger.debug(fields, logEntry.msg);
this.sLogger.debug(logEntry.fields, logEntry.msg);
break;
case 'info':
this.bLogger.info(fields, logEntry.msg);
this.sLogger.info(logEntry.fields, logEntry.msg);
break;
case 'warn':
this.bLogger.warn(fields, logEntry.msg);
this.sLogger.warn(logEntry.fields, logEntry.msg);
break;
case 'error':
this.bLogger.error(fields, logEntry.msg);
this.sLogger.error(logEntry.fields, logEntry.msg);
break;
case 'fatal':
this.bLogger.fatal(fields, logEntry.msg);
this.sLogger.fatal(logEntry.fields, logEntry.msg);
break;
default:
throw new Error(`Unexpected log level: ${logEntry.level}`);

112
lib/SimpleLogger.js Normal file
View File

@ -0,0 +1,112 @@
// eslint-disable-line strict
const os = require('os');
const safeJSONStringify = require('safe-json-stringify');
const fastJSONStringify = require('fast-safe-stringify')
function errorStackReplacer(key, value) {
if (value instanceof Error) {
return value.stack;
}
return value;
}
/*
* This function safely stringifies JSON. If an exception occcurs (due to
* circular references, exceptions thrown from object getters etc.), the module
* safe-json-stringify is used to remove the offending property and return a
* stringified object. This approach is ideal as it does not tax every log entry
* to use the module and has been tested using Cosbench to verify that it doesn't
* introduce any regression in performance.
*/
function safeStringify(obj) {
let str;
try {
// Try to stringify the object (fast version)
str = fastJSONStringify(obj, errorStackReplacer);
} catch (e) {
// fallback to remove circular object references or other exceptions
// eslint-disable-next-line no-param-reassign
obj.unsafeJSON = true;
return safeJSONStringify(obj, errorStackReplacer);
}
return str;
}
function isWriteableStream(s) {
if (!s.stream) {
return false;
}
// duck typing to check if the obect is a writeable stream
return s.stream.writable;
}
class SimpleLogger {
constructor(name, streams) {
this.name = name;
this.streams = [{ level: 'trace', stream: process.stdout }];
if (streams) {
if (!Array.isArray(streams)) {
throw new Error('Invalid streams. streams must be an array'
+ ' list of writeable streams');
}
/*
* This is for backwards compatibility. current config in projects
* create a bunyan-logstash stream which is not a compatible
* writeable stream. Any non-writable streams will be ignored.
* This will be changed to throw an error if non-writable stream is
* encountered.
*/
this.streams = streams.filter(isWriteableStream);
}
this.hostname = os.hostname();
}
log(level, fields, message) {
let logFields;
let logMsg;
if (message === undefined && typeof fields === 'string') {
logMsg = fields;
logFields = {};
} else {
logMsg = message;
logFields = fields || {};
}
// TODO - Protect these fields from being overwritten
logFields.level = level;
logFields.message = logMsg;
logFields.hostname = this.hostname;
logFields.pid = process.pid;
const safeString = safeStringify(logFields);
this.streams.forEach(s => s.stream
.write(`${safeString}\n`));
}
info(fields, message) {
this.log('info', fields, message);
}
debug(fields, message) {
this.log('debug', fields, message);
}
trace(fields, message) {
this.log('trace', fields, message);
}
warn(fields, message) {
this.log('warn', fields, message);
}
error(fields, message) {
this.log('error', fields, message);
}
fatal(fields, message) {
this.log('fatal', fields, message);
}
}
module.exports = SimpleLogger;

View File

@ -1,18 +1,38 @@
'use strict';
// eslint-disable-line strict
/**
* @constant
* @type {String[]} - The lookup table to generate the UID
*/
const lut = [];
for (let i = 0; i < 256; i++) {
lut[i] = (i < 16 ? '0' : '') + (i).toString(16);
}
/**
* This function generates a string uid.
*
* The base algorithm is taken from here: http://jcward.com/UUID.js
* And is explained here: http://stackoverflow.com/a/21963136
*
* @returns {string} An hexadecimal string representation of an unique
* id made of 80 bits.of entropy.
*/
function generateUid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4() + s4() + s4() + s4();
const d0 = Math.random() * 0xffffffff | 0;
const d1 = Math.random() * 0xffffffff | 0;
const d2 = Math.random() * 0xffffffff | 0;
return lut[d0 & 0xff]
+ lut[d0 >> 8 & 0xff]
+ lut[d0 >> 16 & 0xff]
+ lut[d1 & 0xff]
+ lut[d1 >> 8 & 0xff]
+ lut[d1 >> 16 & 0x0f | 0x40]
+ lut[d2 & 0x3f | 0x80]
+ lut[d2 >> 8 & 0xff]
+ lut[d2 >> 16 & 0xff]
+ lut[d2 >> 24 & 0xff];
}
/**
@ -32,16 +52,41 @@ function serializeUids(uidList) {
* This function unserializes an array of UIDs from a string and returns the
* generated Array.
*
* @param {string} stringdata - The string data of the serialized array of UIDs
* @param {string} stringdata - The string data of the serialized array of UIDs
*
* @returns {string[]} The unserialized array of string UIDs
* @returns {string[]} - The unserialized array of string UIDs
*/
function unserializeUids(stringdata) {
return stringdata.split(':');
}
/**
* This function copies the properties from the source object to the target
* object.
*
* @param {...object} target - object to be copied to
* @returns {object} - target object
*/
function objectCopy(target) {
const result = target;
/* eslint-disable prefer-rest-params */
const nb = arguments.length;
for (let i = 1; i < nb; i++) {
const source = arguments[i];
const keys = Object.keys(source);
const keysNb = keys.length;
for (let j = 0; j < keysNb; j++) {
const key = keys[j];
result[key] = source[key];
}
}
/* eslint-enable prefer-rest-params */
return result;
}
module.exports = {
generateUid,
serializeUids,
unserializeUids,
objectCopy,
};

72
lib/api.js Normal file
View File

@ -0,0 +1,72 @@
// eslint-disable-line strict
const Config = require('./Config.js');
const Logger = require('./Logger.js');
class API {
/**
* This is the constructor of the Logger class. It takes optional
* configuration parameters, that allow to modify its behavior.
*
* @param {object} config - A configuration object for werelogs.
* @param {string} config.level - The name of the logging level ('trace',
* 'debug', 'info', 'warn', 'error' and
* 'fatal' in order of importance.)
* @param {string} config.dump - The name of the log dumping level
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {object[]} config.streams - The streams into which to log. This
* is an Array of objects which have a
* field named 'stream', which is
* writeable.
*/
constructor(config) {
this.config = new Config(config);
this.preboundLogger = Logger.bind(null, this.config);
}
/**
* This is a thunk function that allows reconfiguring the streams and log
* levels of all Logger and future RequestLogger objects. Note that
* existing RequestLogger will live their lifespan retaining the old
* configuration.
* If the provided configuration is erroneous, the function may throw
* exceptions depending on the detected configuration error. Please see the
* Config class's documentation about that.
*
* @throws {TypeError} - One of the provided arguments is not of the
* expected type
* @throws {RangeError} - The logging level provided is not part of the
* supported logging levels
* @throws {Error} - A human-readable message providing details about
* a logic error due to the input parameters
* provided.
*
* @param {object} config - A configuration object for werelogs.
* @param {string} config.level - The name of the logging level ('trace',
* 'debug', 'info', 'warn', 'error' and
* 'fatal' in order of importance.)
* @param {string} config.dump - The name of the log dumping level
* ('trace', 'debug', 'info', 'warn',
* 'error' and 'fatal' in order of
* importance.)
* @param {object[]} config.streams - The streams into which to log. This
* is an Array of objects which have a
* field named 'stream', which is
* writeable.
*
* @returns {undefined}
*
*/
reconfigure(config) {
this.config.update(config);
}
get Logger() {
return this.preboundLogger;
}
}
module.exports = API;

106
lib/stderrUtils.js Normal file
View File

@ -0,0 +1,106 @@
/**
* @returns {string} a timestamp in ISO format YYYY-MM-DDThh:mm:ss.sssZ
*/
const defaultTimestamp = () => new Date().toISOString();
/**
* Prints on stderr a timestamp, the origin and the error
*
* If no other instructions are needed on uncaughtException,
* consider using `catchAndTimestampStderr` directly.
*
* @example
* process.on('uncaughtException', (err, origin) => {
* printErrorWithTimestamp(err, origin);
* // server.close();
* // file.close();
* process.nextTick(() => process.exit(1));
* });
* // Don't forget to timestamp warning
* catchAndTimestampWarning();
* @param {Error} err see process event uncaughtException
* @param {uncaughtException|unhandledRejection} origin see process event
* @param {string} [date=`defaultTimestamp()`] Date to print
* @returns {boolean} see process.stderr.write
*/
function printErrorWithTimestamp(
err, origin, date = defaultTimestamp(),
) {
return process.stderr.write(`${date}: ${origin}:\n${err.stack}\n`);
}
/**
* Prefer using `catchAndTimestampStderr` instead of this function.
*
* Adds listener for uncaughtException to print with timestamp.
*
* If you want to manage the end of the process, you can set exitCode to null.
* Or use `printErrorWithTimestamp` in your own uncaughtException listener.
*
* @param {Function} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @param {*} [exitCode=1] On uncaughtException, if not null, `process.exit`
* will be called with this value
* @returns {undefined}
*/
function catchAndTimestampUncaughtException(
dateFct = defaultTimestamp, exitCode = 1,
) {
process.on('uncaughtException', (err, origin) => {
printErrorWithTimestamp(err, origin, dateFct());
if (exitCode !== null) {
process.nextTick(() => process.exit(exitCode));
}
});
}
/**
* Forces the use of `--trace-warnings` and adds a date in warning.detail
* The warning will be printed by the default `onWarning`
*
* @param {string} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @returns {undefined}
*/
function catchAndTimestampWarning(dateFct = defaultTimestamp) {
process.traceProcessWarnings = true;
// must be executed first, before the default `onWarning`
process.prependListener('warning', warning => {
if (warning.detail) {
// eslint-disable-next-line no-param-reassign
warning.detail += `\nAbove Warning Date: ${dateFct()}`;
} else {
// eslint-disable-next-line no-param-reassign
warning.detail = `Above Warning Date: ${dateFct()}`;
}
});
}
/**
* Adds listener for uncaughtException and warning to print them with timestamp.
*
* If you want to manage the end of the process, you can set exitCode to null.
* Or use `printErrorWithTimestamp` in your own uncaughtException listener.
*
* @example
* const { stderrUtils } = require('werelogs');
* // first instruction in your index.js or entrypoint
* stderrUtils.catchAndTimestampStderr();
*
* @param {Function} [dateFct=`defaultTimestamp`] Fct returning a formatted date
* @param {*} [exitCode=1] On uncaughtException, if not null, `process.exit`
* will be called with this value
* @returns {undefined}
*/
function catchAndTimestampStderr(
dateFct = defaultTimestamp, exitCode = 1,
) {
catchAndTimestampUncaughtException(dateFct, exitCode);
catchAndTimestampWarning(dateFct);
}
module.exports = {
defaultTimestamp,
printErrorWithTimestamp,
catchAndTimestampUncaughtException,
catchAndTimestampWarning,
catchAndTimestampStderr,
};

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 E-conomic
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,15 +1,18 @@
{
"name": "werelogs",
"version": "1.0.1-beta2",
"engines": {
"node": ">=10"
},
"version": "8.1.5",
"description": "An efficient raw JSON logging library aimed at micro-services architectures.",
"main": "index.js",
"scripts": {
"gendoc": "jsdoc $(git ls-files 'lib/*.js') -d doc",
"lint": "eslint $(git ls-files '*.js')",
"lint_md": "mdlint $(git ls-files '*.md')",
"lint_md": "markdownlint $(git ls-files '*.md')",
"test": "mocha tests/unit/",
"ft_test": "rm -rf tests/functional/node_modules && npm pack && cp -R node_modules werelogs-*.tgz tests/functional && cd tests/functional && npm install werelogs-*.tgz && ./node_modules/.bin/mocha . multi-modules/ && cd -",
"coverage": "istanbul cover ./node_modules/.bin/_mocha tests/unit"
"ft_test": "(npm pack && cp werelogs-*.tgz tests/functional && cd tests/functional && cp -R ../../node_modules/ node_modules/ && npm install werelogs-*.tgz && ./node_modules/.bin/mocha . multi-modules/ && rm -rf tests/functional/node_modules tests/functional/werelogs-*.tgz tests/functional/*lock*)",
"coverage": "nyc ./node_modules/.bin/_mocha tests/unit"
},
"repository": {
"type": "git",
@ -23,23 +26,27 @@
"library",
"JSON"
],
"author": "David Pineau",
"license": "ISC",
"author": "Giorgio Regni",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/scality/werelogs/issues"
},
"homepage": "https://github.com/scality/werelogs#readme",
"dependencies": {
"bunyan": "^1.5.1"
"fast-safe-stringify": "^2.1.1",
"safe-json-stringify": "^1.2.0"
},
"devDependencies": {
"eslint": "^1.10.1",
"eslint-config-airbnb": "^1.0.2",
"eslint-plugin-react": "^3.10.0",
"istanbul": "^1.0.0-alpha",
"istanbul-api": "==1.0.0-alpha.9",
"jsdoc": "^3.4.0",
"mdlint": "^0.1.0",
"mocha": "^2.3.4"
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-scality": "git+https://git.yourcmc.ru/vitalif/zenko-eslint-config-scality.git",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.26.0",
"eslint-plugin-react-hooks": "^4.2.0",
"jsdoc": "^3.4.3",
"markdownlint-cli": "^0.27.1",
"mocha": ">=3.1.2",
"nyc": "^15.1.0"
}
}

View File

@ -1,51 +1,50 @@
'use strict';
// eslint-disable-line strict
const assert = require('assert');
const LogLevel = require('../lib/LogLevel.js');
class DummyLogger {
constructor() {
this.ops = [];
this.counts = {
'trace': 0,
'debug': 0,
'info': 0,
'warn': 0,
'error': 0,
'fatal': 0,
trace: 0,
debug: 0,
info: 0,
warn: 0,
error: 0,
fatal: 0,
};
}
trace(obj, msg) {
this.ops.push(['trace', [obj, msg]]);
this.counts.trace += 1;
this._doLog('trace', obj, msg);
}
debug(obj, msg) {
this.ops.push(['debug', [obj, msg]]);
this.counts.debug += 1;
this._doLog('debug', obj, msg);
}
info(obj, msg) {
this.ops.push(['info', [obj, msg]]);
this.counts.info += 1;
this._doLog('info', obj, msg);
}
warn(obj, msg) {
this.ops.push(['warn', [obj, msg]]);
this.counts.warn += 1;
this._doLog('warn', obj, msg);
}
error(obj, msg) {
this.ops.push(['error', [obj, msg]]);
this.counts.error += 1;
this._doLog('error', obj, msg);
}
fatal(obj, msg) {
this.ops.push(['fatal', [obj, msg]]);
this.counts.fatal += 1;
this._doLog('fatal', obj, msg);
}
_doLog(level, obj, msg) {
this.ops.push([level, [obj, msg]]);
this.counts[level] += 1;
}
}
@ -58,7 +57,8 @@ function computeBehavior(filterLevel, logLevel, testLevel) {
return {
value,
'msg': `Expected ${logLevel} to be called ${value} times with filter level ${filterLevel}.`,
msg: `Expected ${logLevel} to be called ${value} times with `
+ `filter level ${filterLevel}.`,
};
}
@ -69,12 +69,14 @@ function genericFilterGenerator(filterLevel, testLevel, createLogger) {
const logger = createLogger(dummyLogger, filterLevel);
switch (testLevel) {
/* eslint-disable no-multi-spaces */
case 'trace': logger.trace('test trace'); break;
case 'debug': logger.debug('test debug'); break;
case 'info': logger.info('test info'); break;
case 'warn': logger.warn('test warn'); break;
case 'error': logger.error('test error'); break;
case 'fatal': logger.fatal('test fatal'); break;
/* eslint-enable no-multi-spaces */
default:
done(new Error('Unexpected testLevel name: ', testLevel));
}
@ -105,9 +107,10 @@ function loggingMisuseGenerator(test, createLogger) {
logger.info.apply(logger, test.args);
},
Error,
'Werelogs should not throw with ' + test.desc);
`Werelogs should not throw with ${test.desc}`,
);
assert(dummyLogger.ops[0][0], 'fatal',
'Expected the Module Logger to have logged a fatal message.');
'Expected the Module Logger to have logged a fatal message.');
done();
};
}

View File

@ -1,84 +1,116 @@
const assert = require('assert');
const bunyan = require('bunyan');
const Logger = require('werelogs');
const logBuffer = new bunyan.RingBuffer({ limit: 1 });
// eslint-disable-line strict
const assert = require('assert');
const { PassThrough } = require('stream');
const pass = new PassThrough();
const werelogs = require('werelogs'); // eslint-disable-line
// With PassThrough, SimpleLogger can use it as Writeable stream and all the
// data being written can be read into a variable
const logBuffer = {
records: [],
};
pass.on('data', data => {
logBuffer.records.push(data.toString());
});
werelogs.configure({
level: 'info',
dump: 'error',
streams: [{
stream: pass,
type: 'raw',
}],
});
function createModuleLogger() {
return new Logger('FT-test', {
level: 'info',
dump: 'error',
streams: [{
type: 'raw',
stream: logBuffer,
}],
});
return new werelogs.Logger('FT-test');
}
function checkFields(fields) {
Object.keys(fields).forEach((k) => {
if (fields.hasOwnProperty(k)) {
assert.deepStrictEqual(logBuffer.records[0][k], fields[k]);
const record = JSON.parse(logBuffer.records[0].trim());
Object.keys(fields).forEach(k => {
if (Object.prototype.hasOwnProperty.call(fields, k)) {
assert.deepStrictEqual(record[k], fields[k]);
}
});
}
function parseLogEntry() {
return JSON.parse(logBuffer.records[0].trim());
}
describe('Werelogs is usable as a dependency', () => {
describe('Usage of the ModuleLogger', () => {
it('Should be able to create a logger', (done) => {
afterEach(() => {
logBuffer.records = [];
});
it('Should be able to create a logger', done => {
assert.doesNotThrow(
createModuleLogger,
Error,
'Werelogs threw an exception trying to create a ModuleLogger.'
'Werelogs threw an exception trying to create a ModuleLogger.',
);
done();
});
it('Should be able to log a simple message', (done) => {
it('Should be able to log a simple message', done => {
const logger = createModuleLogger();
const msg = 'This is a simple message';
logger.info(msg);
assert.strictEqual(logBuffer.records[0].msg, msg);
assert.strictEqual(parseLogEntry().message, msg);
done();
});
it('Should be able to log a message and additional fields', (done) => {
it('Should be able to log a message and additional fields', done => {
const logger = createModuleLogger();
const msg = 'This is a message with added fields';
const fields = { errorCode: 9, description: 'TestError', options: { dump: false } };
const fields = {
errorCode: 9,
description: 'TestError',
options: { dump: false },
};
logger.info(msg, fields);
assert.strictEqual(logBuffer.records[0].msg, msg);
assert.strictEqual(parseLogEntry().message, msg);
checkFields(fields);
done();
});
});
describe('Usage of the RequestLogger', () => {
it('Should be able to create a logger', (done) => {
afterEach(() => {
logBuffer.records = [];
});
it('Should be able to create a logger', done => {
assert.doesNotThrow(
() => {
return createModuleLogger().newRequestLogger();
},
() => createModuleLogger().newRequestLogger(),
Error,
'Werelogs threw an exception trying to create a ModuleLogger.'
'Werelogs threw an exception trying to create a ModuleLogger.',
);
done();
});
it('Should be able to log a simple message', (done) => {
it('Should be able to log a simple message', done => {
const logger = createModuleLogger().newRequestLogger();
const msg = 'This is a simple message';
logger.info(msg);
assert.strictEqual(logBuffer.records[0].msg, msg);
assert.strictEqual(parseLogEntry().message, msg);
done();
});
it('Should be able to log a message and additional fields', (done) => {
it('Should be able to log a message and additional fields', done => {
const logger = createModuleLogger().newRequestLogger();
const msg = 'This is a message with added fields';
const fields = { errorCode: 9, description: 'TestError', options: { dump: false } };
const fields = {
errorCode: 9,
description: 'TestError',
options: { dump: false },
};
logger.info(msg, fields);
assert.strictEqual(logBuffer.records[0].msg, msg);
assert.strictEqual(parseLogEntry().message, msg);
checkFields(fields);
done();
});

View File

@ -1,46 +1,57 @@
const assert = require('assert');
const { PassThrough } = require('stream');
const bunyan = require('bunyan');
const Werelogs = require('werelogs');
const Werelogs = require('werelogs'); // eslint-disable-line
const modules = [
require('./module1.js'),
require('./module2.js'),
require('./module3.js'),
];
describe('Config is shared and unique within one program', () => {
it('should find all log entries in the RingBuffer with the right module name', (done) => {
const rBuffer = new bunyan.RingBuffer({ limit: 15 });
const log = new Werelogs('test-index', {
const pass = new PassThrough();
const logBuffer = {
records: [],
};
pass.on('data', data => {
logBuffer.records.push(JSON.parse(data.toString().trim()));
});
describe('Config is shared and unique within one API', () => {
it('should find all log entries in the RingBuffer with the right '
+ 'module name', done => {
Werelogs.configure({
level: 'debug',
dump: 'fatal',
streams: [{
type: 'raw',
stream: rBuffer,
stream: pass,
}],
});
modules.forEach((mod) => { mod(); });
const log = new Werelogs.Logger('test-index');
modules.forEach(mod => { mod(); });
log.warn('Logging as warn');
const rLog = log.newRequestLogger();
rLog.info('Logging request as info');
assert.deepStrictEqual(rBuffer.records.length, 5, 'Expected to see 5 log entries in the ring buffer.');
assert.deepStrictEqual(rBuffer.records[0].msg, 'Logging as info');
assert.deepStrictEqual(rBuffer.records[0].name, 'test-mod1');
assert.deepStrictEqual(rBuffer.records[0].level, 30);
assert.deepStrictEqual(rBuffer.records[1].msg, 'Logging as debug');
assert.deepStrictEqual(rBuffer.records[1].name, 'test-mod2');
assert.deepStrictEqual(rBuffer.records[1].level, 20);
assert.deepStrictEqual(rBuffer.records[2].msg, 'Logging as error');
assert.deepStrictEqual(rBuffer.records[2].name, 'test-mod3');
assert.deepStrictEqual(rBuffer.records[2].level, 50);
assert.deepStrictEqual(rBuffer.records[3].msg, 'Logging as warn');
assert.deepStrictEqual(rBuffer.records[3].name, 'test-index');
assert.deepStrictEqual(rBuffer.records[3].level, 40);
assert.deepStrictEqual(rBuffer.records[4].msg, 'Logging request as info');
assert.deepStrictEqual(rBuffer.records[4].name, 'test-index');
assert.deepStrictEqual(rBuffer.records[4].level, 30);
assert.notStrictEqual(rBuffer.records[4].req_id, undefined);
/* eslint-disable max-len */
assert.deepStrictEqual(logBuffer.records.length, 5, 'Expected to see 5 log entries in the ring buffer.');
assert.deepStrictEqual(logBuffer.records[0].message, 'Logging as info');
assert.deepStrictEqual(logBuffer.records[0].name, 'test-mod1');
assert.deepStrictEqual(logBuffer.records[0].level, 'info');
assert.deepStrictEqual(logBuffer.records[1].message, 'Logging as debug');
assert.deepStrictEqual(logBuffer.records[1].name, 'test-mod2');
assert.deepStrictEqual(logBuffer.records[1].level, 'debug');
assert.deepStrictEqual(logBuffer.records[2].message, 'Logging as error');
assert.deepStrictEqual(logBuffer.records[2].name, 'test-mod3');
assert.deepStrictEqual(logBuffer.records[2].level, 'error');
assert.deepStrictEqual(logBuffer.records[3].message, 'Logging as warn');
assert.deepStrictEqual(logBuffer.records[3].name, 'test-index');
assert.deepStrictEqual(logBuffer.records[3].level, 'warn');
assert.deepStrictEqual(logBuffer.records[4].message, 'Logging request as info');
assert.deepStrictEqual(logBuffer.records[4].name, 'test-index');
assert.deepStrictEqual(logBuffer.records[4].level, 'info');
assert.notStrictEqual(logBuffer.records[4].req_id, undefined);
/* eslint-enable max-len */
done();
});
});

View File

@ -1,4 +1,4 @@
const Werelogs = require('werelogs');
const Werelogs = require('werelogs').Logger; // eslint-disable-line
const log = new Werelogs('test-mod1');

View File

@ -1,4 +1,4 @@
const Werelogs = require('werelogs');
const Werelogs = require('werelogs').Logger; // eslint-disable-line
const log = new Werelogs('test-mod2');

View File

@ -1,4 +1,4 @@
const Werelogs = require('werelogs');
const Werelogs = require('werelogs').Logger; // eslint-disable-line
const log = new Werelogs('test-mod3');

View File

@ -1,64 +1,58 @@
/* eslint-disable max-len */
const assert = require('assert');
const bunyan = require('bunyan');
const Config = require('../../lib/Config.js');
const logBuffer = new bunyan.RingBuffer({ limit: 1 });
describe('Config', () => {
const config = new Config();
beforeEach(() => {
Config.reset();
config.reset();
});
it('should work with default configuration', (done) => {
it('should work with default configuration', done => {
assert.doesNotThrow(
() => {
Config.logger.info('test message');
config.logger.info('test message');
},
Error);
Error,
);
done();
});
it('log level should be updateable', (done) => {
Config.update({ level: 'debug' });
assert.strictEqual(Config.level, 'debug', 'Expected Config\'s log level to be updated.');
it('log level should be updateable', done => {
config.update({ level: 'debug' });
assert.strictEqual(config.level, 'debug', 'Expected config\'s log level to be updated.');
done();
});
it('dump threshold should be updateable', (done) => {
const origDump = Config.dump;
assert.notStrictEqual(origDump, 'warn', 'Expected original Config.dump to differ from value to update.');
Config.update({ dump: 'warn' });
assert.strictEqual(Config.dump, 'warn', 'Expected Config\'s dump threshold to be updated.');
it('dump threshold should be updateable', done => {
const origDump = config.dump;
assert.notStrictEqual(origDump, 'warn', 'Expected original config.dump to differ from value to update.');
config.update({ dump: 'warn' });
assert.strictEqual(config.dump, 'warn', 'Expected config\'s dump threshold to be updated.');
done();
});
it('end logging level should be updateable', (done) => {
const origEnd = Config.end;
assert.notStrictEqual(origEnd, 'trace', 'Expected original Config.end to differ from value to update.');
Config.update({ end: 'trace' });
assert.strictEqual(Config.end, 'trace', 'Expected Config\'s end log level to be updated.');
it('end logging level should be updateable', done => {
const origEnd = config.end;
assert.notStrictEqual(origEnd, 'trace', 'Expected original config.end to differ from value to update.');
config.update({ end: 'trace' });
assert.strictEqual(config.end, 'trace', 'Expected config\'s end log level to be updated.');
done();
});
it('streams should be updateable', (done) => {
const origStreams = Config.streams;
const origLogger = Config.logger;
Config.update({ streams: [{ type: 'raw', stream: logBuffer }] });
assert.notStrictEqual(origStreams, Config.streams, 'Expected Config\'s streams to have been updated.');
assert.notStrictEqual(origLogger, Config.Logger, 'Expected Config\'s logger to have been replaced by update.');
done();
});
it('should not be modified by an empty config object', (done) => {
const origLevel = Config.level;
const origDump = Config.dump;
const origLogger = Config.logger;
const origStreams = Config.streams;
Config.update({});
assert.deepStrictEqual(origLevel, Config.level, 'Expected logging level not to have changed.');
assert.deepStrictEqual(origDump, Config.dump, 'Expected dump threshold not to have changed.');
assert.strictEqual(origLogger, Config.logger, 'Expected logger not to have changed.');
assert.deepStrictEqual(origStreams, Config.streams, 'Expected streams not to have changed.');
it('should not be modified by an empty config object', done => {
const origLevel = config.level;
const origDump = config.dump;
const origLogger = config.logger;
const origStreams = config.streams;
config.update({});
assert.deepStrictEqual(origLevel, config.level, 'Expected logging level not to have changed.');
assert.deepStrictEqual(origDump, config.dump, 'Expected dump threshold not to have changed.');
assert.strictEqual(origLogger, config.logger, 'Expected logger not to have changed.');
assert.deepStrictEqual(origStreams, config.streams, 'Expected streams not to have changed.');
done();
});
});

View File

@ -1,4 +1,5 @@
'use strict';
// eslint-disable-line strict
const assert = require('assert');
@ -12,89 +13,97 @@ function generateValidThrowTest(level) {
},
Error,
'Expected level to be valid and '
+ 'the function not to throw an Error.');
+ 'the function not to throw an Error.',
);
done();
};
}
describe('LogLevel', () => {
describe('throwIfInvalid(level)', () => {
it('should throw on invalid string', (done) => {
it('should throw on invalid string', done => {
assert.throws(
() => {
LogLevel.throwIfInvalid('invalid');
},
RangeError,
'Expected function to throw an Error instance due to '
+ 'invalid log level.');
+ 'invalid log level.',
);
done();
});
it('should not throw on "trace" level',
generateValidThrowTest('trace'));
generateValidThrowTest('trace'));
it('should not throw on "debug" level',
generateValidThrowTest('debug'));
generateValidThrowTest('debug'));
it('should not throw on "info" level',
generateValidThrowTest('info'));
generateValidThrowTest('info'));
it('should not throw on "warn" level',
generateValidThrowTest('warn'));
generateValidThrowTest('warn'));
it('should not throw on "error" level',
generateValidThrowTest('error'));
generateValidThrowTest('error'));
it('should not throw on "fatal" level',
generateValidThrowTest('fatal'));
generateValidThrowTest('fatal'));
});
describe('shouldLog(level, floor)', () => {
it('should return true on "trace" parameters', (done) => {
it('should return true on "trace" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('trace', 'trace'),
true,
'Expected trace floor to allow logging trace level.');
'Expected trace floor to allow logging trace level.',
);
done();
});
it('should return true on "debug" parameters', (done) => {
it('should return true on "debug" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('debug', 'debug'),
true,
'Expected debug floor to allow logging debug level.');
'Expected debug floor to allow logging debug level.',
);
done();
});
it('should return true on "info" parameters', (done) => {
it('should return true on "info" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('info', 'info'),
true,
'Expected info floor to allow logging info level.');
'Expected info floor to allow logging info level.',
);
done();
});
it('should return true on "warn" parameters', (done) => {
it('should return true on "warn" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('warn', 'warn'),
true,
'Expected warn floor to allow logging warn level.');
'Expected warn floor to allow logging warn level.',
);
done();
});
it('should return true on "error" parameters', (done) => {
it('should return true on "error" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('error', 'error'),
true,
'Expected error floor to allow logging error level.');
'Expected error floor to allow logging error level.',
);
done();
});
it('should return true on "fatal" parameters', (done) => {
it('should return true on "fatal" parameters', done => {
assert.strictEqual(
LogLevel.shouldLog('fatal', 'fatal'),
true,
'Expected fatal floor to allow logging fatal level.');
'Expected fatal floor to allow logging fatal level.',
);
done();
});
});

View File

@ -1,15 +1,15 @@
'use strict';
// eslint-disable-line strict
const assert = require('assert');
const Utils = require('../Utils.js');
const genericFilterGenerator = Utils.genericFilterGenerator;
const loggingMisuseGenerator = Utils.loggingMisuseGenerator;
const DummyLogger = Utils.DummyLogger;
const { genericFilterGenerator, loggingMisuseGenerator, DummyLogger } = require('../Utils');
const Config = require('../../lib/Config.js');
const RequestLogger = require('../../lib/RequestLogger.js');
const Logger = require('../../index.js');
const Logger = require('../../lib/Logger.js');
const config = new Config();
/*
* This function is a thunk-function calling the Utils' filterGenerator with
@ -18,163 +18,107 @@ const Logger = require('../../index.js');
*/
function filterGenerator(logLevel, callLevel) {
function createModuleLogger(dummyLogger, filterLevel) {
const logger = new Logger('TestModuleLogger',
{
level: filterLevel,
dump: 'fatal',
});
/*
* Here, patch the Config by setting a specificly designed dummyLogger
* Here, patch the config by setting a specifically designed dummyLogger
* for testing purposes that will help us collect runtime data.
*/
Config.bLogger = dummyLogger;
const testConfig = new Config({ level: filterLevel, dump: 'fatal' });
testConfig.simpleLogger = dummyLogger;
return logger;
return new Logger(testConfig, 'TestModuleLogger');
}
return genericFilterGenerator(logLevel, callLevel, createModuleLogger);
}
function checkFields(src, result) {
Object.keys(src).forEach((k) => {
if (src.hasOwnProperty(k)) {
Object.keys(src).forEach(k => {
if (Object.prototype.hasOwnProperty.call(src, k)) {
assert.deepStrictEqual(result[k], src[k]);
}
});
assert.ok(Object.prototype.hasOwnProperty.call(result, 'time'));
// Time field should be current give or take 1s
assert.ok((Date.now() - result.time) < 1000);
}
describe('WereLogs Logger is usable:', () => {
describe('Logger is usable:', () => {
beforeEach(() => {
Config.reset();
config.reset();
});
it('Can be instanciated with only a name', (done) => {
assert.doesNotThrow(
() => {
return new Logger('WereLogsTest');
},
Error,
'WereLogs Instanciation should not throw any kind of error.');
done();
});
it('Cannot be instanciated with invalid log level', (done) => {
it('Cannot be instanciated without parameters', done => {
assert.throws(
() => {
return new Logger('test', {level: 'invalidlevel'});
},
RangeError,
'WereLogs should not be instanciable without the proper logging levels.');
() => new Logger(),
TypeError,
'Logger Instanciation should not succeed without parameter.',
);
done();
});
it('Cannot be instanciated with invalid dump threshold level', (done) => {
it('Cannot be instanciated with only a config', done => {
assert.throws(
() => {
return new Logger('test', {level: 'trace', dump: 'invalidlevel'});
},
RangeError,
'WereLogs should not be instanciable without the proper dumping threshold levels.');
() => new Logger(config),
TypeError,
'Logger Instanciation should not be succeed without a name.',
);
done();
});
it('Cannot be instanciated with a non-Array in config.streams', (done) => {
it('Cannot be instanciated with a bad config type', done => {
assert.throws(
() => {
return new Logger('test', {streams: process.stdout});
},
Error,
'Werelogs should not be instanciable with a stream option that is not an array.');
() => new Logger({ level: 'info' }, 'WereLogsTest'),
TypeError,
'Logger Instanciation should not succeed with a bad config type.',
);
done();
});
it('Cannot be instanciated with an empty Array in config.streams', (done) => {
it('Cannot be instanciated with only a name', done => {
assert.throws(
() => {
return new Logger('test', {streams: []});
},
Error,
'Werelogs should not be instanciable with an empty array for the streams option.');
() => new Logger('WereLogsTest'),
TypeError,
'Logger Instanciation should not succeed with only a name.',
);
done();
});
it('Cannot set logging level to invalid level at runtime', (done) => {
const logger = new Logger('test');
assert.throws(
() => {
logger.setLevel('invalidLevel');
},
RangeError,
'WereLogs should not be able to set log level to an invalid level.');
done();
});
it('Can set logging level at runtime', (done) => {
const logger = new Logger('test');
assert.doesNotThrow(
() => {
logger.setLevel('fatal');
},
RangeError,
'WereLogs should be able to set log level at runtime.');
done();
});
it('Cannot set dump threshold to invalid level at runtime', (done) => {
const logger = new Logger('test');
assert.throws(
() => {
logger.setDumpThreshold('invalidLevel');
},
RangeError,
'WereLogs should not be able to set dump threshold to an invalid level.');
done();
});
it('Can set dump threshold at runtime', (done) => {
const logger = new Logger('test');
assert.doesNotThrow(
() => {
logger.setDumpThreshold('fatal');
},
RangeError,
'WereLogs should be able to set dump threshold at runtime.');
done();
});
it('Can create Per-Request Loggers', (done) => {
const logger = new Logger('test');
it('Can create Per-Request Loggers', done => {
const logger = new Logger(config, 'test');
assert.doesNotThrow(
() => {
logger.newRequestLogger();
},
Error,
'Werelogs should not throw when creating a request logger.');
const reqLogger = logger.newRequestLogger();
assert(reqLogger instanceof RequestLogger, 'RequestLogger');
'Werelogs should not throw when creating a request logger.',
);
done();
});
it('Can create Per-Request Loggers from a Serialized UID Array', (done) => {
const logger = new Logger('test');
it('Can create Per-Request Loggers from a Serialized UID Array', done => {
const logger = new Logger(config, 'test');
assert.doesNotThrow(
() => {
logger.newRequestLogger();
},
Error,
'Werelogs should not throw when creating a request logger from a Serialized UID Array.');
const reqLogger = logger.newRequestLoggerFromSerializedUids('OneUID:SecondUID:TestUID:YouWinUID');
// eslint-disable-next-line max-len
'Werelogs should not throw when creating a request logger from a Serialized UID Array.',
);
const reqLogger = logger.newRequestLoggerFromSerializedUids(
'OneUID:SecondUID:TestUID:YouWinUID',
);
assert(reqLogger instanceof RequestLogger, 'RequestLogger');
assert.deepStrictEqual(reqLogger.getUids().slice(0, -1), ['OneUID', 'SecondUID', 'TestUID', 'YouWinUID']);
assert.deepStrictEqual(reqLogger.getUids().slice(0, -1),
['OneUID', 'SecondUID', 'TestUID', 'YouWinUID']);
done();
});
it('Uses the additional fields as expected', (done) => {
it('Uses the additional fields as expected', done => {
const dummyLogger = new DummyLogger();
const logger = new Logger('test');
Config.bLogger = dummyLogger;
config.simpleLogger = dummyLogger;
const logger = new Logger(config, 'test');
const fields = {
ip: '127.0.0.1',
method: 'GET',
@ -186,28 +130,31 @@ describe('WereLogs Logger is usable:', () => {
done();
});
/* eslint-disable max-len */
describe('Does not crash and logs a fatal message when mis-using its logging API', () => {
const testValues = [
{ desc: 'a string as second argument', args: [ 'test', 'second-param-string' ] },
{ desc: 'a function as second argument', args: [ 'test', () => { return; } ] },
{ desc: 'a Number as second argument', args: [ 'test', 1 ] },
{ desc: 'more than 2 arguments', args: [ 'test', 2, 3, 4 ] },
{ desc: 'a string as second argument', args: ['test', 'second-param-string'] },
{ desc: 'a function as second argument', args: ['test', () => { }] }, // eslint-disable-line arrow-body-style
{ desc: 'a Number as second argument', args: ['test', 1] },
{ desc: 'more than 2 arguments', args: ['test', 2, 3, 4] },
];
/* eslint-enable max-len */
function createMisusableLogger(dummyLogger) {
const logger = new Logger('test');
Config.bLogger = dummyLogger;
config.simpleLogger = dummyLogger;
const logger = new Logger(config, 'test');
return logger;
}
for (let i = 0; i < testValues.length; ++i) {
const test = testValues[i];
it('Does not crash with ' + test.desc,
loggingMisuseGenerator(test, createMisusableLogger));
it(`Does not crash with ${test.desc}`,
loggingMisuseGenerator(test, createMisusableLogger));
}
});
});
describe('Werelogs Module-level Logger can log as specified by the log level', () => {
/* eslint-disable no-multi-spaces, max-len */
describe('Logger can log as specified by the log level', () => {
it('Trace level does not filter trace level out', filterGenerator('trace', 'trace'));
it('Trace level does not filter debug level out', filterGenerator('trace', 'debug'));
it('Trace level does not filter info level out', filterGenerator('trace', 'info'));
@ -250,3 +197,4 @@ describe('Werelogs Module-level Logger can log as specified by the log level', (
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
it('Fatal level does not filter fatal level out', filterGenerator('fatal', 'fatal'));
});
/* eslint-enable no-multi-spaces, max-len */

View File

@ -1,11 +1,9 @@
'use strict';
// eslint-disable-line strict
const assert = require('assert');
const Utils = require('../Utils.js');
const DummyLogger = Utils.DummyLogger;
const genericFilterGenerator = Utils.genericFilterGenerator;
const loggingMisuseGenerator = Utils.loggingMisuseGenerator;
const { DummyLogger, genericFilterGenerator, loggingMisuseGenerator } = require('../Utils.js');
const RequestLogger = require('../../lib/RequestLogger.js');
@ -23,18 +21,21 @@ function filterGenerator(logLevel, callLevel) {
}
function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done) {
function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
done) {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'error', 'info');
commandHistory.every(function doLogWithLevel(val, index) {
commandHistory.every((val, index) => {
switch (val) {
/* eslint-disable no-multi-spaces */
case 'trace': reqLogger.trace(index); break;
case 'debug': reqLogger.debug(index); break;
case 'info': reqLogger.info(index); break;
case 'warn': reqLogger.warn(index); break;
case 'error': reqLogger.error(index); break;
case 'fatal': reqLogger.fatal(index); break;
/* eslint-enable no-multi-spaces */
default:
done(new Error('Unexpected logging level name: ', val));
}
@ -42,40 +43,40 @@ function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, don
});
expectedHistory.every((val, index) => {
assert.strictEqual(dummyLogger.ops[index][0], val[0], 'Expected log entry levels to match.');
assert.strictEqual(dummyLogger.ops[index][1][1], val[1], 'Expected log entry values to match.');
assert.strictEqual(dummyLogger.ops[index][0], val[0],
'Expected log entry levels to match.');
assert.strictEqual(dummyLogger.ops[index][1][1], val[1],
'Expected log entry values to match.');
return true;
});
assert.deepEqual(dummyLogger.counts, expectedCounts);
}
/* eslint-disable no-multi-spaces, max-len */
describe('RequestLogger', () => {
describe('Object Instanciation', () => {
describe('Logging Levels Initialization', () => {
it('Throws if LogLevel is higher than dumpThreshold', (done) => {
it('Throws if LogLevel is higher than dumpThreshold', done => {
assert.throws(
() => {
return new RequestLogger(undefined, 'fatal', 'debug', 'info');
},
() => new RequestLogger(undefined, 'fatal', 'debug', 'info'),
Error,
'Dump level "debug" should not be valid with logging level "fatal".');
'Dump level "debug" should not be valid with logging level "fatal".',
);
done();
});
it('Works with LogLevel lesser or equal to DumpLevel', (done) => {
it('Works with LogLevel lesser or equal to DumpLevel', done => {
assert.doesNotThrow(
() => {
return new RequestLogger(undefined, 'debug', 'fatal', 'info');
},
() => new RequestLogger(undefined, 'debug', 'fatal', 'info'),
Error,
'Dump level "fatal" should be valid with logging level "debug".');
'Dump level "fatal" should be valid with logging level "debug".',
);
done();
});
});
describe('UID Initialization', () => {
it('defines an UID when none provided', (done) => {
it('defines an UID when none provided', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info');
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
@ -83,7 +84,7 @@ describe('RequestLogger', () => {
done();
});
it('creates an UID array out of the provided UID string', (done) => {
it('creates an UID array out of the provided UID string', done => {
const dummyLogger = new DummyLogger();
const uids = 'BasicUid';
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info', uids);
@ -93,17 +94,16 @@ describe('RequestLogger', () => {
done();
});
it('throws when UID string provided contains a colon', (done) => {
it('throws when UID string provided contains a colon', done => {
assert.throws(
() => {
return new RequestLogger(undefined, 'debug', 'fatal', 'info', 'pouet:tata');
},
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', 'pouet:tata'),
Error,
'UID string "pouet:tata" should be rejected by the RequestLogger constructor.');
'UID string "pouet:tata" should be rejected by the RequestLogger constructor.',
);
done();
});
it('expands the UID array when one is provided', (done) => {
it('expands the UID array when one is provided', done => {
const dummyLogger = new DummyLogger();
const uids = ['oneuid', 'twouid', 'threeuids'];
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info', uids);
@ -113,19 +113,18 @@ describe('RequestLogger', () => {
done();
});
it('throws when UID string Array provided contains an UID that contains a colon', (done) => {
it('throws when UID string Array provided contains an UID that contains a colon', done => {
assert.throws(
() => {
return new RequestLogger(undefined, 'debug', 'fatal', 'info', ['OneUID', 'SecondUID', 'Test:DashUID']);
},
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', ['OneUID', 'SecondUID', 'Test:DashUID']),
Error,
'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.');
'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.',
);
done();
});
});
describe('getUids() method', () => {
it('retrieves a list of string UID', (done) => {
it('retrieves a list of string UID', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
const uidlist = reqLogger.getUids();
@ -135,7 +134,7 @@ describe('RequestLogger', () => {
});
describe('Length of the UIDs array', () => {
it('default constructor yields a one-item UID list', (done) => {
it('default constructor yields a one-item UID list', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
const uidlist = reqLogger.getUids();
@ -143,7 +142,7 @@ describe('RequestLogger', () => {
done();
});
it('manually-set UID constructor yields a one-item UID list', (done) => {
it('manually-set UID constructor yields a one-item UID list', done => {
const dummyLogger = new DummyLogger();
const myUid = 'ThisIsMyUid';
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info', myUid);
@ -153,9 +152,9 @@ describe('RequestLogger', () => {
done();
});
it('manually-set parent UID List constructor yields a n+1 item UID list', (done) => {
it('manually-set parent UID List constructor yields a n+1 item UID list', done => {
const dummyLogger = new DummyLogger();
const myParentUidList = [ 'ThisIsMyOriginUid', 'ThisIsMySecondGenUid', 'ThisIsMyThirdGenUid' ];
const myParentUidList = ['ThisIsMyOriginUid', 'ThisIsMySecondGenUid', 'ThisIsMyThirdGenUid'];
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info', myParentUidList);
const uidlist = reqLogger.getUids();
assert.strictEqual(uidlist.length, myParentUidList.length + 1, 'Expected n+1 item in UID Array compared to set UID List array');
@ -164,7 +163,7 @@ describe('RequestLogger', () => {
});
});
it('internal data cannot be set through returned UID List', (done) => {
it('internal data cannot be set through returned UID List', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
const uidlist = reqLogger.getUids();
@ -175,11 +174,11 @@ describe('RequestLogger', () => {
});
describe('getSerializedUids()', () => {
it('Should return a properly serialized UID Array', (done) => {
it('Should return a properly serialized UID Array', done => {
const dummyLogger = new DummyLogger();
const uidList = [ 'FirstUID', 'SecondUID', 'ThirdUID', 'TestUID' ];
const uidList = ['FirstUID', 'SecondUID', 'ThirdUID', 'TestUID'];
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info', uidList);
const expectedString = 'FirstUID:SecondUID:ThirdUID:TestUID:' + reqLogger.getUids()[4];
const expectedString = `FirstUID:SecondUID:ThirdUID:TestUID:${reqLogger.getUids()[4]}`;
assert.strictEqual(reqLogger.getSerializedUids(), expectedString, 'Expected serialized UID List to match expected data.');
done();
});
@ -188,10 +187,10 @@ describe('RequestLogger', () => {
describe('Does not crash when mis-using its logging API', () => {
const testValues = [
{ desc: 'a string as second argument', args: [ 'test', 'second-param-string' ] },
{ desc: 'a function as second argument', args: [ 'test', () => { return; } ] },
{ desc: 'a Number as second argument', args: [ 'test', 1 ] },
{ desc: 'more than 2 arguments', args: [ 'test', 2, 3, 4 ] },
{ desc: 'a string as second argument', args: ['test', 'second-param-string'] },
{ desc: 'a function as second argument', args: ['test', function f() { }] },
{ desc: 'a Number as second argument', args: ['test', 1] },
{ desc: 'more than 2 arguments', args: ['test', 2, 3, 4] },
];
function createMisusableRequestLogger(dummyLogger) {
return new RequestLogger(dummyLogger, 'info', 'error', 'info');
@ -199,8 +198,8 @@ describe('RequestLogger', () => {
for (let i = 0; i < testValues.length; ++i) {
const test = testValues[i];
it('Does not crash with ' + test.desc,
loggingMisuseGenerator(test, createMisusableRequestLogger));
it(`Does not crash with ${test.desc}`,
loggingMisuseGenerator(test, createMisusableRequestLogger));
}
});
@ -247,33 +246,39 @@ describe('RequestLogger', () => {
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
it('Fatal level does not filter fatal level out', filterGenerator('fatal', 'fatal'));
});
/* eslint-enable no-multi-spaces, max-len */
describe('Logging API regression testing', () => {
it('Should not alter the input fields when not actually logging', (done) => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const refFields = { 'hits': 45, 'count': 32 };
const usedFields = Object.assign({}, refFields);
reqLogger.debug('test', usedFields);
assert.deepStrictEqual(usedFields, refFields);
done();
});
it('Should not alter the input fields when not actually logging',
done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields };
reqLogger.debug('test', usedFields);
assert.deepStrictEqual(usedFields, refFields);
done();
});
it('Should not alter the input fields when actually logging', (done) => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const refFields = { 'hits': 45, 'count': 32 };
const usedFields = Object.assign({}, refFields);
reqLogger.info('test', usedFields);
assert.deepStrictEqual(usedFields, refFields);
done();
});
it('Should not alter the input fields when actually logging',
done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields };
reqLogger.info('test', usedFields);
assert.deepStrictEqual(usedFields, refFields);
done();
});
it('Should not alter the input fields when dumping', (done) => {
it('Should not alter the input fields when dumping', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const refFields = { 'hits': 45, 'count': 32 };
const usedFields = Object.assign({}, refFields);
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
const refFields = { hits: 45, count: 32 };
const usedFields = { ...refFields };
reqLogger.error('test', usedFields);
assert.deepStrictEqual(usedFields, refFields);
done();
@ -281,7 +286,7 @@ describe('RequestLogger', () => {
});
describe('Default Fields', () => {
it('should not modify the object passed as a parameter', (done) => {
it('should not modify the object passed as a parameter', done => {
const add1 = {
attr1: 0,
};
@ -289,7 +294,8 @@ describe('RequestLogger', () => {
attr2: 'string',
};
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
reqLogger.addDefaultFields(add1);
reqLogger.addDefaultFields(add2);
assert.deepStrictEqual(add1, { attr1: 0 });
@ -297,129 +303,192 @@ describe('RequestLogger', () => {
done();
});
it('should add one added default field to the log entries', (done) => {
it('should add one added default field to the log entries', done => {
const clientInfo = {
clientIP: '127.0.0.1',
};
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
reqLogger.addDefaultFields(clientInfo);
reqLogger.info('test message');
assert.strictEqual(clientInfo.clientIP, dummyLogger.ops[0][1][0].clientIP);
assert.strictEqual(clientInfo.clientIP,
dummyLogger.ops[0][1][0].clientIP);
done();
});
it('should add multiple added default fields to the log entries', (done) => {
const clientInfo = {
clientIP: '127.0.0.1',
clientPort: '1337',
};
const requestInfo = {
object: '/tata/self.txt',
creator: 'Joddy',
};
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
reqLogger.addDefaultFields(clientInfo);
reqLogger.addDefaultFields(requestInfo);
reqLogger.info('test message');
assert.strictEqual(clientInfo.clientIP, dummyLogger.ops[0][1][0].clientIP);
assert.strictEqual(clientInfo.clientPort, dummyLogger.ops[0][1][0].clientPort);
assert.strictEqual(requestInfo.object, dummyLogger.ops[0][1][0].object);
assert.strictEqual(requestInfo.creator, dummyLogger.ops[0][1][0].creator);
done();
});
it('should add multiple added default fields to the log entries',
done => {
const clientInfo = {
clientIP: '127.0.0.1',
clientPort: '1337',
};
const requestInfo = {
object: '/tata/self.txt',
creator: 'Joddy',
};
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
reqLogger.addDefaultFields(clientInfo);
reqLogger.addDefaultFields(requestInfo);
reqLogger.info('test message');
assert.strictEqual(clientInfo.clientIP,
dummyLogger.ops[0][1][0].clientIP);
assert.strictEqual(clientInfo.clientPort,
dummyLogger.ops[0][1][0].clientPort);
assert.strictEqual(requestInfo.object,
dummyLogger.ops[0][1][0].object);
assert.strictEqual(requestInfo.creator,
dummyLogger.ops[0][1][0].creator);
done();
});
});
describe('Automatic Elapsed Time computation', () => {
describe('Deprecated API:', () => {
it('should include an "elapsed_ms" field in the last log entry', (done) => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
reqLogger.end('Last message');
assert.strictEqual(dummyLogger.ops[0][1][1], 'Last message');
assert.notStrictEqual(dummyLogger.ops[0][1][0].elapsed_ms, undefined);
assert.strictEqual(typeof(dummyLogger.ops[0][1][0].elapsed_ms), 'number');
done();
});
it('should include an "elapsed_ms" field in the last log entry',
done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
reqLogger.end('Last message');
assert.strictEqual(dummyLogger.ops[0][1][1], 'Last message');
assert.notStrictEqual(dummyLogger.ops[0][1][0].elapsed_ms,
undefined);
assert.strictEqual(typeof dummyLogger.ops[0][1][0]
.elapsed_ms, 'number');
done();
});
// eslint-disable-next-line max-len
it('should include an "elapsed_ms" field in the last log entry and be error level', () => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'info', 'fatal', 'info');
const reqLogger = new RequestLogger(dummyLogger,
'info', 'fatal', 'info');
reqLogger.errorEnd('Last message failed');
assert.strictEqual(dummyLogger.ops[0][1][1], 'Last message failed');
assert.notStrictEqual(dummyLogger.ops[0][1][0].elapsed_ms, undefined);
assert.strictEqual(typeof(dummyLogger.ops[0][1][0].elapsed_ms), 'number');
assert.strictEqual(dummyLogger.ops[0][1][1],
'Last message failed');
assert.notStrictEqual(dummyLogger.ops[0][1][0].elapsed_ms,
undefined);
assert.strictEqual(typeof dummyLogger.ops[0][1][0].elapsed_ms,
'number');
assert.strictEqual(dummyLogger.ops[0][0], 'error');
});
});
const endLogging = {
trace: (endLogger) => { return endLogger.trace.bind(endLogger); },
debug: (endLogger) => { return endLogger.debug.bind(endLogger); },
info: (endLogger) => { return endLogger.info.bind(endLogger); },
warn: (endLogger) => { return endLogger.warn.bind(endLogger); },
error: (endLogger) => { return endLogger.error.bind(endLogger); },
fatal: (endLogger) => { return endLogger.fatal.bind(endLogger); },
trace: endLogger => endLogger.trace.bind(endLogger),
debug: endLogger => endLogger.debug.bind(endLogger),
info: endLogger => endLogger.info.bind(endLogger),
warn: endLogger => endLogger.warn.bind(endLogger),
error: endLogger => endLogger.error.bind(endLogger),
fatal: endLogger => endLogger.fatal.bind(endLogger),
};
Object.keys(endLogging).forEach((level) => {
it(`should include an "elapsed_ms" field in the last log entry with level ${level}`, (done) => {
/* eslint-disable max-len */
Object.keys(endLogging).forEach(level => {
it(`should include an "elapsed_ms" field in the last log entry with level ${level}`, done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'fatal');
endLogging[level](reqLogger.end())('Last message');
assert.strictEqual(dummyLogger.ops[0][1][1], 'Last message');
assert.notStrictEqual(dummyLogger.ops[0][1][0].elapsed_ms, undefined);
assert.strictEqual(typeof(dummyLogger.ops[0][1][0].elapsed_ms), 'number');
assert.strictEqual(typeof dummyLogger.ops[0][1][0].elapsed_ms, 'number');
assert.strictEqual(dummyLogger.ops[0][0], level);
done();
});
});
/* eslint-enable max-len */
it('should be augmentable through addDefaultFields', (done) => {
it('should be augmentable through addDefaultFields', done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'fatal');
reqLogger.end().addDefaultFields({endFlag: true});
reqLogger.end().addDefaultFields({ endFlag: true });
// Someone could do multiple operations in the meantime before
// end() logging
reqLogger.end().error('Test Augmented END', {endValue: 42});
reqLogger.end().error('Test Augmented END', { endValue: 42 });
assert.strictEqual(dummyLogger.ops[0][1][1], 'Test Augmented END');
assert.strictEqual(typeof(dummyLogger.ops[0][1][0].elapsed_ms), 'number');
assert.strictEqual(typeof dummyLogger.ops[0][1][0].elapsed_ms,
'number');
assert.strictEqual(dummyLogger.ops[0][1][0].endFlag, true);
assert.strictEqual(dummyLogger.ops[0][1][0].endValue, 42);
done();
});
it('should log an error in addition to request logs when end() called more than once',
done => {
const dummyLogger = new DummyLogger();
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'fatal');
reqLogger.end().info('after first call to end()');
reqLogger.end().debug('after second call to end()');
assert.strictEqual(dummyLogger.ops.length, 3);
assert.strictEqual(dummyLogger.ops[0][0], 'info');
assert.strictEqual(dummyLogger.ops[0][1][1], 'after first call to end()');
assert.strictEqual(dummyLogger.ops[1][0], 'error');
assert.strictEqual(dummyLogger.ops[2][0], 'debug');
assert.strictEqual(dummyLogger.ops[2][1][1], 'after second call to end()');
done();
});
});
describe('Log History dumped when logging floor level reached', () => {
it('Dumping duplicates log entries', (done) => {
it('Dumping duplicates log entries', done => {
const commandHistory = ['info', 'error'];
const expectedHistory = [['info', 0], ['info', 0], ['error', 1]];
const expectedCounts = { trace: 0, debug: 0, info: 2, warn: 0, error: 1, fatal: 0 };
const expectedCounts = {
trace: 0,
debug: 0,
info: 2,
warn: 0,
error: 1,
fatal: 0,
};
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
done);
done();
});
it('Dumping Keeps logging history order', (done) => {
it('Dumping Keeps logging history order', done => {
const commandHistory = ['trace', 'info', 'debug', 'error'];
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2], ['trace', 0], ['info', 1], ['debug', 2], ['error', 3]];
const expectedCounts = { trace: 2, debug: 2, info: 2, warn: 0, error: 1, fatal: 0 };
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
done();
});
it('Dumping multiple times does not re-dump already-dumped entries', (done) => {
const commandHistory = ['trace', 'info', 'debug', 'error',
'warn', 'debug', 'fatal'];
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
['trace', 0], ['info', 1], ['debug', 2], ['error', 3],
['warn', 4], ['debug', 5],
['warn', 4], ['debug', 5], ['fatal', 6]];
const expectedCounts = { trace: 2, debug: 4, info: 2, warn: 2, error: 1, fatal: 1 };
['trace', 0], ['info', 1], ['debug', 2],
['error', 3]];
const expectedCounts = {
trace: 2,
debug: 2,
info: 2,
warn: 0,
error: 1,
fatal: 0,
};
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
done);
done();
});
it('Dumping multiple times does not re-dump already-dumped entries',
done => {
const commandHistory = ['trace', 'info', 'debug', 'error',
'warn', 'debug', 'fatal'];
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
['trace', 0], ['info', 1], ['debug', 2],
['error', 3], ['warn', 4], ['debug', 5],
['warn', 4], ['debug', 5],
['fatal', 6]];
const expectedCounts = {
trace: 2,
debug: 4,
info: 2,
warn: 2,
error: 1,
fatal: 1,
};
runLoggingDumpTest(commandHistory, expectedHistory,
expectedCounts, done);
done();
});
});
});

View File

@ -1,18 +1,20 @@
'use strict';
// eslint-disable-line strict
const assert = require('assert');
const Utils = require('../../lib/Utils.js');
const generateUid = Utils.generateUid;
const serializeUids = Utils.serializeUids;
const unserializeUids = Utils.unserializeUids;
const {
generateUid, serializeUids, unserializeUids, objectCopy,
} = require('../../lib/Utils');
describe('Utils: generateUid', () => {
it('generates a string-typed ID', (done) => {
it('generates a string-typed ID', done => {
const uid = generateUid();
assert.strictEqual(typeof(uid), 'string', 'The generated ID is not a String (' + typeof(uid) + ')');
assert.strictEqual(typeof uid, 'string',
`The generated ID is not a String (${typeof uid})`);
done();
});
it('generate roughly unique IDs', (done) => {
it('generate roughly unique IDs', done => {
const generated = {};
let count = 0;
for (let i = 0; i < 10000; ++i) {
@ -20,25 +22,77 @@ describe('Utils: generateUid', () => {
count = generated[uid] ? generated[uid] + 1 : 1;
generated[uid] = count;
}
Object.keys(generated).every((uid) => {
assert.strictEqual(generated[uid], 1, `Uid ${uid} was generated ${generated[uid]} times: It is not even remotely unique.`);
Object.keys(generated).every(uid => {
assert.strictEqual(generated[uid], 1,
`Uid ${uid} was generated ${generated[uid]} `
+ 'times: It is not even remotely unique.');
return {};
});
done();
});
});
describe('Utils: serializeUids', () => {
it('serializes to the expected string data', (done) => {
const uidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
it('serializes to the expected string data', done => {
const uidList = ['FirstUID', 'SecondUID', 'ThirdUID'];
const serializedUIDs = serializeUids(uidList);
assert.strictEqual(serializedUIDs, 'FirstUID:SecondUID:ThirdUID', 'Serialized UID List should match expected value.');
assert.strictEqual(serializedUIDs, 'FirstUID:SecondUID:ThirdUID',
'Serialized UID List should match expected value.');
done();
});
it('unserializes the expected number of UIDs', (done) => {
const refUidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
it('unserializes the expected number of UIDs', done => {
const refUidList = ['FirstUID', 'SecondUID', 'ThirdUID'];
const unserializedUIDs = unserializeUids('FirstUID:SecondUID:ThirdUID');
assert.deepStrictEqual(unserializedUIDs, refUidList, 'Unserialized UID List should match expected value.');
assert.deepStrictEqual(unserializedUIDs, refUidList,
'Unserialized UID List should match expected value.');
done();
});
});
describe('Utils: objectCopy', () => {
it('copies all the properties from source to target object', done => {
const target = { foo: 'bar' };
const source = { id: 1, name: 'demo', value: { a: 1, b: 2, c: 3 } };
const result = {
foo: 'bar',
id: 1,
name: 'demo',
value: { a: 1, b: 2, c: 3 },
};
objectCopy(target, source);
assert.deepStrictEqual(target, result,
'target should have the same properties as source');
done();
});
it('copies all the properties from multiple sources to target object',
done => {
const target = { foo: 'bar' };
const source1 = {
id: 1,
name: 'demo1',
value: { a: 1, b: 2, c: 3 },
};
// eslint-disable-next-line camelcase
const source2 = {
req_id: 2,
method: 'test',
err: { code: 'error', msg: 'test' },
};
const result = {
foo: 'bar',
id: 1,
name: 'demo1',
value: { a: 1, b: 2, c: 3 },
// eslint-disable-next-line camelcase
req_id: 2,
method: 'test',
err: { code: 'error', msg: 'test' },
};
objectCopy(target, source1, source2);
assert.deepStrictEqual(target, result,
'target should have the same properties as source');
done();
});
});

View File

@ -0,0 +1,17 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const exitCode = fromStr(fromStr(process.argv[3], null), undefined);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampStderr(
date ? () => date : undefined,
exitCode,
);
process.emitWarning('TestWarningMessage');
// This will print warning after printing error before exit
throw new Error('TestingError');

View File

@ -0,0 +1,23 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const exitCode = fromStr(fromStr(process.argv[3], null), undefined);
const promise = fromStr(process.argv[4], true);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampUncaughtException(
date ? () => date : undefined,
exitCode,
);
// Executed if process does not exit, process is in undefined behavior (bad)
// eslint-disable-next-line no-console
setTimeout(() => console.log('EXECUTED AFTER UNCAUGHT EXCEPTION'), 1);
if (promise === true) {
Promise.reject();
} else {
throw new Error('TestingError');
}

View File

@ -0,0 +1,38 @@
#!/usr/bin/env node
// Convert string args into primitive value
const fromStr = (str, primitive) => (str === `${primitive}` ? primitive : str);
const date = fromStr(process.argv[2], undefined);
const name = fromStr(process.argv[3], undefined);
const code = fromStr(process.argv[4], undefined);
const detail = fromStr(process.argv[5], undefined);
const { stderrUtils } = require('../../../../index');
stderrUtils.catchAndTimestampWarning(
date ? () => date : undefined,
);
const warning = new Error('TestWarningMessage');
if (name) warning.name = name;
if (code) warning.code = code;
if (detail) warning.detail = detail;
process.emitWarning(warning);
/*
Examples:
(node:203831) Error: TestWarningMessage
at Object.<anonymous> (catchWarning.js:15:17)
...
at node:internal/main/run_main_module:22:47
Above Warning Date: 2024-06-26T16:32:55.505Z
(node:205151) [TEST01] CUSTOM: TestWarningMessage
at Object.<anonymous> (catchWarning.js:15:17)
...
at node:internal/main/run_main_module:22:47
Some additional detail
Above Warning Date: Tue, 31 Dec 2024 10:20:30 GMT
*/

309
tests/unit/stderrUtils.js Normal file
View File

@ -0,0 +1,309 @@
const assert = require('assert');
const { execFile } = require('child_process');
const stderrUtils = require('../../lib/stderrUtils');
/** Simple regex for ISO YYYY-MM-DDThh:mm:ss.sssZ */
// eslint-disable-next-line max-len
const defaultDateRegex = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)/;
// eslint-disable-next-line valid-jsdoc
/** another format: Tue, 31 Dec 2024 10:20:30 GMT */
const customDate = () => new Date('2024-12-31T10:20:30.444Z').toUTCString();
describe('stderrUtils', () => {
const errStackRegex = /Error: TestingError\n(?:.*\sat\s.*\n)+/;
describe('defaultTimestamp', () => {
it('should match ISO format', () => {
assert.match(stderrUtils.defaultTimestamp(), defaultDateRegex);
});
});
describe('printErrorWithTimestamp', () => {
let stderrText;
const originalStderrWrite = process.stderr.write;
const mockedStderrWrite = text => { stderrText = text; return true; };
const err = new Error('TestingError');
const origin = 'uncaughtException';
beforeEach(() => {
stderrText = undefined;
process.stderr.write = mockedStderrWrite;
});
afterEach(() => {
process.stderr.write = originalStderrWrite;
stderrText = undefined;
});
it(
'should write to stderr with current date, origin and stacktrace',
() => {
const written = stderrUtils
.printErrorWithTimestamp(err, origin);
assert.strictEqual(written, true);
const [firstLine, errStack] = stderrText.split(':\n');
const [errDate, errOrigin] = firstLine.split(': ');
assert.match(errDate, defaultDateRegex);
assert.strictEqual(errOrigin, origin);
assert.strictEqual(errStack, `${err.stack}\n`);
},
);
it(
'should write to stderr with custom date, origin and stacktrace',
() => {
const written = stderrUtils
.printErrorWithTimestamp(err, origin, customDate());
assert.strictEqual(written, true);
const [firstLine, errStack] = stderrText.split(':\n');
const [errDate, errOrigin] = firstLine.split(': ');
assert.strictEqual(errDate, customDate());
assert.strictEqual(errOrigin, origin);
assert.strictEqual(errStack, `${err.stack}\n`);
},
);
});
const execOptions = {
cwd: __dirname,
// Subprocess should always stop alone
// But just in case, kill subprocess after 500ms.
// Leave enough time for `nyc` that runs slower.
timeout: 500,
};
// Execute in another process to notice the process exit
// Therefore, looks more like a functional test
const timeoutHint = (ms, retries) =>
`Test fixture process timed out after ${ms}ms with ${retries} retries.\n` +
'Due to nyc coverage first run slowing down process.\nIncrease execOptions.timeout to fix';
describe('catchAndTimestampUncaughtException', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with custom exitCode 42', exitCode: 42 },
{ desc: 'without exit on uncaught exception', exitCode: null },
{ desc: 'for unhandled promise', promise: true },
].forEach(({
desc, date, exitCode, promise,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
let errStack;
let errDate;
let errOrigin;
before('run process catchUncaughtException', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchUncaughtException.js',
[`${date}`, `${exitCode}`, `${promise}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
let firstLine;
[firstLine, errStack] = stderr.split(':\n');
[errDate, errOrigin] = firstLine.split(': ');
done();
},
);
});
if (exitCode === null) {
it('should not be an error (or timeout)',
() => assert.ifError(err));
it('should have stdout (printed after uncaught exception)',
() => assert.match(stdout,
/^.*EXECUTED AFTER UNCAUGHT EXCEPTION(?:.|\n)*$/));
} else {
it('should be an error',
() => assert.ok(err));
it(`should have exitCode ${exitCode || 1}`,
() => assert.strictEqual(err.code, exitCode || 1));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
}
it('should have stderr',
() => assert.ok(stderr));
it('should have date in stderr first line',
() => (date
? assert.strictEqual(errDate, date)
: assert.match(errDate, defaultDateRegex)));
it('should have origin in stderr first line',
() => (promise === true
? assert.strictEqual(errOrigin, 'unhandledRejection')
: assert.strictEqual(errOrigin, 'uncaughtException')));
if (!promise) {
it('should have stack trace on stderr',
() => assert.match(errStack, errStackRegex));
}
}));
});
describe('catchAndTimestampWarning (also tests node onWarning)', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with deprecation warning', name: 'DeprecationWarning' },
{
desc: 'with custom warning',
name: 'CUSTOM',
code: 'TEST01',
detail: 'Some additional detail',
},
].forEach(({
desc, date, name, code, detail,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
before('run process catchWarning', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchWarning.js',
[`${date}`, `${name}`, `${code}`, `${detail}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
done();
},
);
});
it('should not be an error (or timeout)',
() => assert.ifError(err));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
it('should have stderr',
() => assert.ok(stderr));
it('should have message on stderr first line, then stack trace',
() => assert.match(stderr,
/^.*TestWarningMessage\n(?:\s+at\s.*\n)+/));
if (code) {
it('should have code on stderr first line',
() => assert.match(stderr, new RegExp(`^.*[${code}]`)));
}
if (name) {
it('should have name on stderr first line',
() => assert.match(stderr, new RegExp(`^.*${name}:`)));
}
if (detail) {
it('should have detail on stderr',
() => assert.match(stderr, new RegExp(`.*${detail}.*`)));
}
it(`should have ${date ? 'custom' : 'default'} date on stderr`,
() => assert.match(stderr, new RegExp(
`\nAbove Warning Date: ${
date || defaultDateRegex.source}\n`,
)));
}));
});
describe('catchAndTimestampStderr', () => {
[
{ desc: 'with default date' },
{ desc: 'with custom date', date: customDate() },
{ desc: 'with exit code', exitCode: 42 },
].forEach(({
desc, date, exitCode,
}) => describe(desc, () => {
/** for before all hook that doesn't support this.retries */
let retries = 4;
let err;
let stdout;
let stderr;
before('run process catchStderr', function beforeAllHook(done) {
execFile(
'./fixtures/stderrUtils/catchStderr.js',
[`${date}`, `${exitCode}`],
execOptions,
(subErr, subStdout, subStderr) => {
if (subErr?.killed) {
retries--;
if (retries <= 0) {
assert.fail(timeoutHint(execOptions.timeout, retries));
}
execOptions.timeout *= 2;
return beforeAllHook(done);
}
err = subErr;
stdout = subStdout;
stderr = subStderr;
done();
},
);
});
it('should be an error',
() => assert.ok(err));
it(`should have exitCode ${exitCode || 1}`,
() => assert.strictEqual(err.code, exitCode || 1));
it('should have empty stdout',
() => assert.strictEqual(stdout, ''));
it('should have stderr',
() => assert.ok(stderr));
// 2024-06-26T15:04:55.364Z: uncaughtException:
// Error: TestingError
// at Object.<anonymous> (catchStderr.js:16:7)
// at node:internal/main/run_main_module:22:47
it('should have error date, origin and stacktrace in stderr',
() => assert.match(stderr,
new RegExp(`${date || defaultDateRegex.source
}: uncaughtException:\n${errStackRegex.source}`)));
// (node:171245) Warning: TestWarningMessage
// at Object.<anonymous> (catchStderr.js:14:9)
// at node:internal/main/run_main_module:22:47
// Above Warning Date: 2024-06-26T15:04:55.365Z
it('should have warning with stacktrace in stderr', () => {
const trace = 'Warning: TestWarningMessage\n(?:\\s+at\\s.*\n)+';
const detail = `(?:.|\n)*?(?<=\n)Above Warning Date: ${
date || defaultDateRegex.source}\n`;
assert.match(stderr,
new RegExp(`${trace}${detail}`));
});
}));
});
});