Compare commits
1 Commits
developmen
...
dev/NF/alt
Author | SHA1 | Date |
---|---|---|
Giorgio Regni | 5d0f03204f |
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"transform-es2015-destructuring",
|
||||||
|
"transform-es2015-modules-commonjs",
|
||||||
|
"transform-es2015-parameters"
|
||||||
|
]
|
||||||
|
}
|
14
.eslintrc
14
.eslintrc
|
@ -1 +1,13 @@
|
||||||
{ "extends": "scality" }
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": "airbnb",
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": [2,4],
|
||||||
|
"no-multi-spaces": [2, { exceptions: { "SwitchCase": true, "CallExpression": true } } ],
|
||||||
|
"strict": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
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
|
|
|
@ -1,5 +1,53 @@
|
||||||
# Contributing rules
|
# Contributing to WereLogs
|
||||||
|
|
||||||
Please follow the
|
Contributing to WereLogs can take multiple shapes:
|
||||||
[Contributing Guidelines](
|
* Reporting a bug
|
||||||
https://github.com/scality/Guidelines/blob/master/CONTRIBUTING.md).
|
* 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
|
||||||
|
|
|
@ -41,7 +41,6 @@ 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
|
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
|
encountered, and the code path the request went through. To address this, we
|
||||||
offer multiple features:
|
offer multiple features:
|
||||||
|
|
||||||
* [Request ID namespacing](###request-id-namespacing)
|
* [Request ID namespacing](###request-id-namespacing)
|
||||||
* [Request unit Logs](###request-unit-logs)
|
* [Request unit Logs](###request-unit-logs)
|
||||||
|
|
||||||
|
@ -88,3 +87,5 @@ 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
|
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
|
any logging resources for the log entries not considered 'useless' by a given
|
||||||
log level configuration.
|
log level configuration.
|
||||||
|
|
||||||
|
|
||||||
|
|
191
LICENSE
191
LICENSE
|
@ -1,191 +0,0 @@
|
||||||
|
|
||||||
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.
|
|
121
README.md
121
README.md
|
@ -1,81 +1,51 @@
|
||||||
# WereLogs
|
# WereLogs
|
||||||
|
|
||||||
[![Scality CI][badgepriv]](http://ci.ironmann.io/gh/scality/werelogs)
|
|
||||||
|
|
||||||
This repository provides a NodeJS Library that aims to be an efficient logging
|
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,
|
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
|
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
|
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.
|
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
|
## Installing the Library
|
||||||
|
|
||||||
In order to install WereLogs, you can use NPM with github's HTTP url, and save
|
In order to install WereLogs, you can use NPM with github's HTTP url, and save
|
||||||
it in your own package.json:
|
it in your own package.json:
|
||||||
|
```
|
||||||
```sh
|
|
||||||
$> npm i --save scality/werelogs
|
$> npm i --save scality/werelogs
|
||||||
```
|
```
|
||||||
|
|
||||||
As the repository is currently private, you will need to provide your username
|
As the repository is currently private, you will need to provide your username
|
||||||
and your password, or use the git+ssh protocol with a properly configured
|
and your password, or use the git+ssh protocol with a properly configured
|
||||||
environment, or use the git+https protocol with your username and cleartext
|
environment, or use the git+https protocol with your username and cleartext
|
||||||
password in the URL (which I absolutely don't recommend for security reasons).
|
password in the URL (which I absolutely don't recomment for security reasons).
|
||||||
|
|
||||||
## Using the Library
|
## Using the Library
|
||||||
|
|
||||||
Werelogs is a logging library that provides both per-request and per-module
|
As WereLogs is a per-request logging library and not a per-module one,
|
||||||
logging facilities, through the intermediary of the per-module Logger that
|
importing the library is not enough by itself (the module itself does not
|
||||||
is the default export.
|
provide logging methods). The module provides a method to instanciate a
|
||||||
|
request-specific logger object. That object is the one you want to use for any
|
||||||
|
logging operation related to your request, so you will have to pass it to
|
||||||
|
any function that requires it.
|
||||||
|
|
||||||
Werelogs may be configured only once throughout your application's lifetime,
|
```es6
|
||||||
through the configuration options available in the per-module logger
|
|
||||||
constructor.
|
|
||||||
|
|
||||||
The per-module Logger object is used to log relevant events for a given module.
|
|
||||||
|
|
||||||
The RequestLogger object is the one you want to use for any logging operation
|
|
||||||
related to an ongoing request, so you will have to pass it to any function that
|
|
||||||
requires it.
|
|
||||||
|
|
||||||
All logging methods (trace, debug, info, warn, error and fatal) follow the same
|
|
||||||
prototype and usage pattern. They can take up to two parameters, the first one,
|
|
||||||
mandatory, being a string message, and the second one, optional, being an
|
|
||||||
object used to provide additional information to be included in the log entry.
|
|
||||||
|
|
||||||
The RequestLogger also provides a way to include some attributes in the JSON by
|
|
||||||
default for all subsequent logging calls, by explicitly inputting them only
|
|
||||||
once for the whole request's lifetime through the method
|
|
||||||
`addDefaultFields`.
|
|
||||||
|
|
||||||
As the RequestLogger is a logger strongly associated to a request's processing
|
|
||||||
operations, it provides a builtin facility to log the elapsed time in ms of the
|
|
||||||
said processing of the request. This is done through a specific logging method,
|
|
||||||
`end` that returns a prepared logging object. Using this returned object with
|
|
||||||
the usual logging methods will automatically compute the elapsed time from the
|
|
||||||
instantiation of the RequestLogger to the moment it is called, by using an
|
|
||||||
internal hi-res time generated at the instantiation of the logger.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
import Logger from 'werelogs';
|
import Logger from 'werelogs';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Here, configure your WereLogs Logger at a global level
|
* Here, configure your WereLogs Logger at a global level
|
||||||
* It can be instantiated with a Name (for the module), and a config options
|
* It can be instanciated with a Name (for the module), and a config options
|
||||||
* Object.
|
* Object.
|
||||||
*
|
*
|
||||||
* This config options object contains a log level called 'level', a log
|
* This config options object contains a log level called 'level', a log
|
||||||
* dumping threshold called 'dump'. The only unnecessary
|
* dumping threshold called 'dump', and an array of stream called 'streams'.
|
||||||
* field is the 'level' of each individual stream, as werelogs is managing
|
* The 'streams' option shall follow bunyan's configuration needs, as werelogs
|
||||||
* that on its own.
|
* acts almost as a passthrough for this specific option. The only unnecessary
|
||||||
|
* field is the 'level' of each individual stream, as werelogs is managning
|
||||||
|
* 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
|
||||||
*
|
*
|
||||||
* All request loggers instantiated through this Logger will inherit its
|
* All request loggers instanciated through this Logger will inherit its
|
||||||
* configuration.
|
* configuration.
|
||||||
*/
|
*/
|
||||||
const log = new Logger(
|
const log = new Logger(
|
||||||
|
@ -83,6 +53,19 @@ const log = new Logger(
|
||||||
{
|
{
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
dump: 'error',
|
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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -94,28 +77,18 @@ const log = new Logger(
|
||||||
* Logger.
|
* Logger.
|
||||||
*/
|
*/
|
||||||
log.info('Application started.');
|
log.info('Application started.');
|
||||||
log.warn('Starting RequestLogging...', {'metadata': new Date()});
|
log.warn({'metadata': new Date()}, 'Starting RequestLogging...');
|
||||||
|
|
||||||
doSomething(reqLogger) {
|
doSomething(reqLogger) {
|
||||||
/*
|
|
||||||
* Let's add some kind of client-related data as default attributes first
|
|
||||||
*/
|
|
||||||
reqLogger.addDefaultFields({ clientIP: '127.0.0.1',
|
|
||||||
clientPort: '65535',
|
|
||||||
clientName: 'Todd'});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Then, you can log some data, either a string or an object, using one of
|
* Then, you can log some data, either a string or an object, using one of
|
||||||
* the logging methods: 'trace', 'debug', 'info', 'warn', 'error', or
|
* the logging methods: 'trace', 'debug', 'info', 'warn', 'error', or
|
||||||
* 'fatal'.
|
* 'fatal'.
|
||||||
*/
|
*/
|
||||||
reqLogger.info('This is a string log entry');
|
reqLogger.info('This is a string log entry');
|
||||||
// This example provides additional information to include into the JSON
|
reqLogger.info('This is a template string log entry with an included date:'
|
||||||
reqLogger.info('Placing bet...',
|
+ ` ${new Date().toISOString()}`);
|
||||||
{ date: new Date().toISOString(),
|
reqLogger.info({ status: 200, hasStuff: false });
|
||||||
odds: [1, 1000],
|
|
||||||
amount: 20000,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRequest() {
|
function processRequest() {
|
||||||
|
@ -128,26 +101,11 @@ function processRequest() {
|
||||||
*/
|
*/
|
||||||
const reqLogger = log.newRequestLogger();
|
const reqLogger = log.newRequestLogger();
|
||||||
|
|
||||||
/* you need to provide your logger instance to the code that requires it,
|
/* you need to provide your logger instance to the code that requires it, as
|
||||||
* as it is not a module-wide object instance */
|
* it is not a module-wide object instance */
|
||||||
doSomething(reqLogger, ...);
|
doSomething(reqLogger, ...);
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
/*
|
|
||||||
* Planning for some specific data to be included in the last logging
|
|
||||||
* request, you could use the addDefaultFields of the end()'s object:
|
|
||||||
*/
|
|
||||||
reqLogger.end().addDefaultFields({method: 'GET', client: client.getIP()})
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This call will generate a log entry with an added elapsed_ms
|
|
||||||
* field. This object can only be used once, as it should only be used for
|
|
||||||
* the last log entry associated to this specific RequestLogger.
|
|
||||||
* This call will be reusing potential data fields previously added through
|
|
||||||
* end().addDefaultFields().
|
|
||||||
*/
|
|
||||||
reqLogger.end().info('End of request.', { status: 200 });
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -157,3 +115,8 @@ 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
|
[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,
|
be able to find the issues, tagged with the releases they are impacting,
|
||||||
whether they're open or closed.
|
whether they're open or closed.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
The contributing rules for this project are defined in the associated
|
||||||
|
CONTRIBUTING.md file.
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
general:
|
||||||
|
artifacts:
|
||||||
|
- coverage/
|
||||||
|
machine:
|
||||||
|
node:
|
||||||
|
version: 4.1.0
|
||||||
|
test:
|
||||||
|
override:
|
||||||
|
- npm run lint_md
|
||||||
|
- npm run lint
|
||||||
|
- npm run coverage
|
|
@ -1,65 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
52
index.js
52
index.js
|
@ -1,51 +1 @@
|
||||||
const API = require('./lib/api.js');
|
module.exports = require('./target/lib/Logger.js').default;
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
191
lib/Config.js
191
lib/Config.js
|
@ -1,191 +0,0 @@
|
||||||
|
|
||||||
// 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
|
|
||||||
* operations go through this object. This is the way we chose for the
|
|
||||||
* configuration to be shared at a library-level, in order to set the
|
|
||||||
* configuration only once as a user of werelogs.
|
|
||||||
*/
|
|
||||||
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(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method resets the state of the Config Object, by setting only
|
|
||||||
* default values inside.
|
|
||||||
*
|
|
||||||
* @returns {Config} - this
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
return this.update({
|
|
||||||
level: 'info',
|
|
||||||
dump: 'error',
|
|
||||||
end: 'info',
|
|
||||||
streams: [{ level: 'trace', stream: process.stdout }],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is the central nervous system of this Configuration object.
|
|
||||||
* It supports updating the current configuration, and will re-generate
|
|
||||||
* a bunyan logger if the streams array changed.
|
|
||||||
*
|
|
||||||
* @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. 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
|
|
||||||
* a more detailed description of the streams array configuration.
|
|
||||||
*
|
|
||||||
* @returns {Config} - this
|
|
||||||
*/
|
|
||||||
update(config) {
|
|
||||||
if (!config) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkedConfig = config || {};
|
|
||||||
|
|
||||||
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 (Object.prototype.hasOwnProperty.call(checkedConfig, 'dump')) {
|
|
||||||
this.dumpThreshold = checkedConfig.dump;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'end')) {
|
|
||||||
LogLevel.throwIfInvalid(checkedConfig.end);
|
|
||||||
this.endLevel = checkedConfig.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(checkedConfig, 'streams')) {
|
|
||||||
if (!Array.isArray(checkedConfig.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.');
|
|
||||||
}
|
|
||||||
this.streams = checkedConfig.streams.map(stream => {
|
|
||||||
stream.level = 'trace'; // eslint-disable-line no-param-reassign
|
|
||||||
return stream;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.simpleLogger = new SimpleLogger('werelogs', this.streams);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is a simple getter to get access to the config's internal
|
|
||||||
* bunyan Logger. By using this function instead of keeping a reference to
|
|
||||||
* a bunyan Logger instance, we make it so that we can switch loggers
|
|
||||||
* on-the-fly for the loggers that rely on this.
|
|
||||||
*
|
|
||||||
* @returns {Bunyan.Logger} - A Bunyan logger to be used for logging
|
|
||||||
* operations by the user code.
|
|
||||||
*/
|
|
||||||
get logger() {
|
|
||||||
return this.simpleLogger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is a simple getter to get access to the config's internal
|
|
||||||
* logging level. By using this function instead of keeping a reference to
|
|
||||||
* it, we make it so that we can switch configuration on-the-fly for the
|
|
||||||
* loggers that rely on this.
|
|
||||||
*
|
|
||||||
* @returns {string} - The configured logging level
|
|
||||||
*/
|
|
||||||
get level() {
|
|
||||||
return this.logLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is a simple getter to get access to the config's internal
|
|
||||||
* dump Threshold. By using this function instead of keeping a reference to
|
|
||||||
* it, we make it so that we can switch configuration on-the-fly for the
|
|
||||||
* loggers that rely on this.
|
|
||||||
*
|
|
||||||
* @returns {string} - The configured dump threshold
|
|
||||||
*/
|
|
||||||
get dump() {
|
|
||||||
return this.dumpThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is a simple getter to get access to the config's internal
|
|
||||||
* end logging level. By using this function instead of keeping a reference
|
|
||||||
* to it, we make it so that we can switch configuration on-the-fly for the
|
|
||||||
* loggers that rely on this.
|
|
||||||
*
|
|
||||||
* @returns {string} - The configured 'end' logging level
|
|
||||||
*/
|
|
||||||
get end() {
|
|
||||||
return this.endLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Config;
|
|
|
@ -1,6 +1,3 @@
|
||||||
|
|
||||||
// eslint-disable-line strict
|
|
||||||
|
|
||||||
const logLevels = [
|
const logLevels = [
|
||||||
'trace',
|
'trace',
|
||||||
'debug',
|
'debug',
|
||||||
|
@ -15,19 +12,12 @@ const logLevels = [
|
||||||
*
|
*
|
||||||
* @function throwIfInvalid
|
* @function throwIfInvalid
|
||||||
*
|
*
|
||||||
* @param {string} level The logging level's name to be checked
|
* @param level {string} The logging level's name to be checked
|
||||||
*
|
*
|
||||||
* @throw {Error} A human-readable message that tells which log
|
* @throw {Error} A human-readable message that tells which log levels are
|
||||||
* levels are supported.
|
* supported.
|
||||||
* @throw {TypeError} A human-readable message indicating that the
|
|
||||||
* provided logging level was not a string
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
*/
|
||||||
function throwIfInvalid(level) {
|
export function throwIfInvalid(level) {
|
||||||
if (typeof level !== 'string') {
|
|
||||||
throw new TypeError('Logging level should be a string');
|
|
||||||
}
|
|
||||||
if (logLevels.indexOf(level) === -1) {
|
if (logLevels.indexOf(level) === -1) {
|
||||||
throw new RangeError(`Invalid logging level: ${level} is neither`
|
throw new RangeError(`Invalid logging level: ${level} is neither`
|
||||||
+ ` ${logLevels.join(', ')}.`);
|
+ ` ${logLevels.join(', ')}.`);
|
||||||
|
@ -42,20 +32,15 @@ function throwIfInvalid(level) {
|
||||||
*
|
*
|
||||||
* @function shouldLog
|
* @function shouldLog
|
||||||
*
|
*
|
||||||
* @param {string} level The level of the log entry for which to check
|
* @param level {string} The level of the log entry for which to check
|
||||||
* if it should log
|
* if it should log
|
||||||
* @param {string} floorLevel The configured logging level, acting as a floor
|
* @param floorLevel {string} The configured logging level, acting as a floor
|
||||||
* under which entries are not logged.
|
* under which entries are not logged.
|
||||||
*
|
*
|
||||||
* @return {boolean} true if the entry of log level 'level' should be output
|
* @return {boolean} true if the entry of log level 'level' should be output
|
||||||
* false if the entry of log level 'level' should not be
|
* false if the entry of log level 'level' should not be
|
||||||
* output
|
* output
|
||||||
*/
|
*/
|
||||||
function shouldLog(level, floorLevel) {
|
export function shouldLog(level, floorLevel) {
|
||||||
return logLevels.indexOf(level) >= logLevels.indexOf(floorLevel);
|
return logLevels.indexOf(level) >= logLevels.indexOf(floorLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
throwIfInvalid,
|
|
||||||
shouldLog,
|
|
||||||
};
|
|
||||||
|
|
224
lib/Logger.js
224
lib/Logger.js
|
@ -1,175 +1,143 @@
|
||||||
|
import bunyan from 'bunyan';
|
||||||
|
|
||||||
// eslint-disable-line strict
|
import * as LogLevel from './LogLevel.js';
|
||||||
|
import RequestLogger from './RequestLogger.js';
|
||||||
|
import { unserializeUids } from './Utils.js';
|
||||||
|
|
||||||
const LogLevel = require('./LogLevel.js');
|
export default class Logger {
|
||||||
const RequestLogger = require('./RequestLogger.js');
|
|
||||||
const { unserializeUids } = require('./Utils.js');
|
|
||||||
const Config = require('./Config.js');
|
|
||||||
|
|
||||||
class Logger {
|
|
||||||
/**
|
/**
|
||||||
* This is the constructor of the Logger class. It takes optional
|
* This is the constructor of the Logger class. It takes optional
|
||||||
* configuration parameters, that allow to modify its behavior.
|
* configuration parameters, that allow to modify its behavior.
|
||||||
*
|
*
|
||||||
* @param {Werelogs.Config} config - An instanciated Werelogs Config object
|
* @param name {string} The name of the Logger. It can be found later on in
|
||||||
|
* the log entries.
|
||||||
*
|
*
|
||||||
* @param {string} name - The name of the Logger. It can be found later on
|
* @param config {Object} A configuration object for werelogs. It may
|
||||||
* in the log entries.
|
* contain the following fields:
|
||||||
*
|
* - 'level', being the string name of the logging
|
||||||
* @returns {undefined}
|
* level
|
||||||
|
* - 'dump', being the string name of the logging
|
||||||
|
* dump Threshold
|
||||||
|
* - 'streams', being an array of Objects to be
|
||||||
|
* used by the underlying library bunyan as the
|
||||||
|
* 'streams' configuration field, for compatibility
|
||||||
|
* (and simplicity of werelog's code) purposes. As
|
||||||
|
* such, the streams are left almost untouched,
|
||||||
|
* except the level that is forced to 'trace', as
|
||||||
|
* werelogs is internally managning the logging
|
||||||
|
* level (due to the dump threshold mecanisms)
|
||||||
*/
|
*/
|
||||||
constructor(config, name) {
|
constructor(name, config = {}) {
|
||||||
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;
|
this.name = name;
|
||||||
|
|
||||||
|
if (config.hasOwnProperty('level')) {
|
||||||
|
LogLevel.throwIfInvalid(config.level);
|
||||||
|
this.logLevel = config.level;
|
||||||
|
} else {
|
||||||
|
this.logLevel = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasOwnProperty('dump')) {
|
||||||
|
LogLevel.throwIfInvalid(config.dump);
|
||||||
|
this.dumpThreshold = config.dump;
|
||||||
|
} else {
|
||||||
|
this.dumpThreshold = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasOwnProperty('streams')) {
|
||||||
|
if (!Array.isArray(config.streams)) {
|
||||||
|
throw new Error('WereLogs config.streams must be an Array of Writeable Streams.');
|
||||||
|
}
|
||||||
|
if (!config.streams.length) {
|
||||||
|
throw new Error('Werelogs config.streams must contain at least one stream.');
|
||||||
|
}
|
||||||
|
this.streams = config.streams.map((stream) => {
|
||||||
|
stream.level = 'trace';
|
||||||
|
return stream;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.streams = [{ level: 'trace', stream: process.stdout }];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bLogger = bunyan.createLogger({
|
||||||
|
name: this.name,
|
||||||
|
streams: this.streams,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setLevel(levelName) {
|
||||||
|
LogLevel.throwIfInvalid(levelName);
|
||||||
|
this.logLevel = levelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDumpThreshold(levelName) {
|
||||||
|
LogLevel.throwIfInvalid(levelName);
|
||||||
|
this.dumpThreshold = levelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method creates a Request Logger using an array of UIDs or an
|
* This method creates a Request Logger using an array of UIDs or an
|
||||||
* explicit UID to use as the origin request ID.
|
* explicit UID to use as the origin request ID.
|
||||||
*
|
*
|
||||||
* @param {(string|string[]|undefined)} uids - The uid List to set
|
* @param {string|array|undefined} the uid List to set
|
||||||
*
|
*
|
||||||
* @returns {RequestLogger} A Valid Request Logger
|
* @returns {RequestLogger} a Valid Request Logger
|
||||||
*/
|
*/
|
||||||
newRequestLogger(uids) {
|
newRequestLogger(uids) {
|
||||||
const rLog = new RequestLogger(this.config.logger,
|
return new RequestLogger(this.bLogger,
|
||||||
this.config.level, this.config.dump,
|
this.logLevel, this.dumpThreshold,
|
||||||
this.config.end, uids);
|
uids);
|
||||||
rLog.addDefaultFields({ name: this.name });
|
|
||||||
return rLog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method creates a Request Logger using a serialized list of UIDs to
|
* This method creates a Request Logger using a serialized list of UIDs to
|
||||||
* set the UID list into the newly created Request Logger..
|
* set the UID list into the newly created Request Logger..
|
||||||
*
|
*
|
||||||
* @param {string} serializedUids - The Serialized UID list
|
* @param {string} the Serialized UID list
|
||||||
*
|
*
|
||||||
* @returns {RequestLogger} A Valid Request Logger
|
* @returns {RequestLogger} a Valid Request Logger
|
||||||
*/
|
*/
|
||||||
newRequestLoggerFromSerializedUids(serializedUids) {
|
newRequestLoggerFromSerializedUids(serializedUids) {
|
||||||
const rLog = new RequestLogger(this.config.logger,
|
return new RequestLogger(this.bLogger,
|
||||||
this.config.level, this.config.dump,
|
this.logLevel, this.dumpThreshold,
|
||||||
this.config.end,
|
|
||||||
unserializeUids(serializedUids));
|
unserializeUids(serializedUids));
|
||||||
rLog.addDefaultFields({ name: this.name });
|
|
||||||
return rLog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_doLog(levelName, msg, data) {
|
/**
|
||||||
const sLogger = this.config.logger;
|
* Logging functions
|
||||||
const finalData = { name: this.name, time: Date.now() };
|
*
|
||||||
if (!LogLevel.shouldLog(levelName, this.config.level)) {
|
* For the module-level logging,
|
||||||
|
*/
|
||||||
|
_doLog(levelName, args) {
|
||||||
|
if (!LogLevel.shouldLog(levelName, this.logLevel)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data !== undefined && typeof data !== 'object') {
|
const logArgs = Array.prototype.splice.apply(args);
|
||||||
sLogger.fatal(
|
this.bLogger[levelName].apply(this.bLogger, logArgs);
|
||||||
{
|
|
||||||
callparams: [msg, data],
|
|
||||||
},
|
|
||||||
'Werelogs API was mis-used.'
|
|
||||||
+ ' This development error should be fixed ASAP.',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data) {
|
|
||||||
Object.keys(data).forEach(k => {
|
|
||||||
finalData[k] = data[k];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const args = [finalData, msg];
|
|
||||||
sLogger[levelName].apply(sLogger, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
trace() {
|
||||||
* Logging function to write a trace-level log entry.
|
this._doLog('trace', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
trace(msg, data) {
|
|
||||||
this._doLog('trace', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
debug() {
|
||||||
* Logging function to write a debug-level log entry.
|
this._doLog('debug', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
debug(msg, data) {
|
|
||||||
this._doLog('debug', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
info() {
|
||||||
* Logging function to write a info-level log entry.
|
this._doLog('info', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
info(msg, data) {
|
|
||||||
this._doLog('info', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
warn() {
|
||||||
* Logging function to write a warn-level log entry.
|
this._doLog('warn', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
warn(msg, data) {
|
|
||||||
this._doLog('warn', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
error() {
|
||||||
* Logging function to write a error-level log entry.
|
this._doLog('error', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
error(msg, data) {
|
|
||||||
this._doLog('error', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fatal() {
|
||||||
* Logging function to write a fatal-level log entry.
|
this._doLog('fatal', arguments);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
fatal(msg, data) {
|
|
||||||
this._doLog('fatal', msg, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Logger;
|
|
||||||
|
|
|
@ -1,203 +1,70 @@
|
||||||
|
import * as LogLevel from './LogLevel.js';
|
||||||
// eslint-disable-line strict
|
import { generateUid, serializeUids } from './Utils.js';
|
||||||
|
|
||||||
const LogLevel = require('./LogLevel.js');
|
|
||||||
const Utils = require('./Utils.js');
|
|
||||||
|
|
||||||
const { serializeUids, generateUid, objectCopy } = Utils;
|
|
||||||
|
|
||||||
function ensureUidValidity(uid) {
|
function ensureUidValidity(uid) {
|
||||||
if (uid.indexOf(':') !== -1) {
|
if (uid.indexOf(':') !== -1) {
|
||||||
throw new Error(`RequestLogger UID "${uid}" contains an illegal `
|
throw new Error(`RequestLogger UID "${uid}" contains an illegal character: ':'.`);
|
||||||
+ 'character: \':\'.');
|
|
||||||
}
|
}
|
||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EndLogger {
|
|
||||||
constructor(reqLogger) {
|
|
||||||
this.logger = reqLogger;
|
|
||||||
this.fields = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
augmentedLog(level, msg, data) {
|
|
||||||
// We can alter current instance, as it won't be usable after this
|
|
||||||
// call.
|
|
||||||
this.fields = objectCopy(this.fields, data || {});
|
|
||||||
return this.logger.log(level, msg, this.fields, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function allows the user to add default fields to include into all
|
|
||||||
* JSON log entries generated through this request logger. As this function
|
|
||||||
* attempt not to modify the provided fields object, it copies the field
|
|
||||||
* into a new object for safe keeping.
|
|
||||||
*
|
|
||||||
* @param {object} fields The dictionnary of additional fields to include
|
|
||||||
* by default for this instance of the
|
|
||||||
* RequestLogger.
|
|
||||||
*
|
|
||||||
* @returns {object} The previous set of default fields (can be
|
|
||||||
* safely ignored).
|
|
||||||
*/
|
|
||||||
addDefaultFields(fields) {
|
|
||||||
const oldFields = this.fields;
|
|
||||||
this.fields = objectCopy({}, this.fields, fields);
|
|
||||||
return oldFields;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a trace-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
trace(msg, data) {
|
|
||||||
this.augmentedLog('trace', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a debug-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
debug(msg, data) {
|
|
||||||
this.augmentedLog('debug', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a info-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
info(msg, data) {
|
|
||||||
this.augmentedLog('info', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a warn-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
warn(msg, data) {
|
|
||||||
this.augmentedLog('warn', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a error-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
error(msg, data) {
|
|
||||||
this.augmentedLog('error', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a fatal-level log entry as the last log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
fatal(msg, data) {
|
|
||||||
this.augmentedLog('fatal', msg, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WereLogs Request Logger. This class provides all information required
|
* WereLogs Request Logger. This class provides all information required
|
||||||
* to bufferise and dump log entries whenever it is relevant depending on
|
* to bufferise and dump log entries whenever it is relevant depending on
|
||||||
* the global log level; and is used to track the log events for one given
|
* the global log level; and is used to track the log events for one given
|
||||||
* request.
|
* request.
|
||||||
*/
|
*/
|
||||||
class RequestLogger {
|
export default class RequestLogger {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the WereLogs Request Logger.
|
* Constructor of the WereLogs Request Logger.
|
||||||
* This function takes a logger instance, a logging level, and a last
|
* This function takes a logger instance, a logging level, and a last
|
||||||
* parameter corresponding to the request UID.
|
* parameter corresponding to the request UID.
|
||||||
*
|
*
|
||||||
* @param { bunyan.Logger } logger - A bunyan logger instance by which all
|
* @param logger { bunyan.Logger } A bunyan logger instance by which all
|
||||||
* logging output will go through,
|
* logging output will go through,
|
||||||
* provided by the Werelogs main Logger.
|
* provided by the Werelogs main Logger.
|
||||||
* @param { string } logLevel - The floor logging level. All logging
|
* @param loglevel { string } The floor logging level. All logging
|
||||||
* requests equal or above this level
|
* requests equal or above this level will
|
||||||
* will be immediately output. Others
|
* be immediately output. Others will get
|
||||||
* will get bufferised.
|
* bufferised.
|
||||||
* @param { string } dumpThreshold - The floor dumping level. The full
|
* @param dumpThreshold { string } The floor dumping level. The full
|
||||||
* logging history (conditionned by the
|
* logging history (conditionned by the
|
||||||
* logging level) is dumped whenever a
|
* logging level) is dumped whenever a log
|
||||||
* log entry reaches this level. This
|
* entry reaches this level. This helps
|
||||||
* helps understanding any kind of error
|
* understanding any kind of error
|
||||||
* happenning in the system, and have a
|
* happenning in the system, and have a
|
||||||
* track of a whole request, at the point
|
* track of a whole request, at the point
|
||||||
* where it fails. dumpThreshold should
|
* where it fails. dumpThreshold should be
|
||||||
* be equal or greater than logLevel
|
* equal or greater than logLevel (geater
|
||||||
* (geater meaning on the more critical
|
* meaning on the more critical side of)
|
||||||
* side of)
|
* @param uids { string[] | string | undefined } An Unique Id in a String
|
||||||
* @param { string } endLevel - The logging level to use for the
|
* format or Unique ID List of the parent
|
||||||
* special finish logging call 'end()'
|
* request IDs. Any String of the UID shall
|
||||||
* @param {(string[] | string | undefined)} [uids] - An Unique ID in a
|
* not contain any colon, due to the
|
||||||
* String format or Unique ID List of the
|
* serialization format chosen for the UID
|
||||||
* parent request IDs. Any String of the
|
* List.
|
||||||
* UID shall not contain any colon, due
|
|
||||||
* to the serialization format chosen for
|
|
||||||
* the UID List.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
*/
|
||||||
constructor(logger, logLevel, dumpThreshold, endLevel, uids) {
|
constructor(logger, logLevel, dumpThreshold, uids = undefined) {
|
||||||
let uidList;
|
let uidList = undefined;
|
||||||
|
|
||||||
if (!LogLevel.shouldLog(dumpThreshold, logLevel)) {
|
if (!LogLevel.shouldLog(dumpThreshold, logLevel)) {
|
||||||
throw new Error('Logging Dump level should be equal or'
|
throw new Error('Logging Dump level should be equal or'
|
||||||
+ ' higher than logging filter level.');
|
+ ' higher than logging filter level.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uids !== undefined && Array.isArray(uids)) {
|
if (Array.isArray(uids)) {
|
||||||
uidList = uids.map(uid => ensureUidValidity(uid));
|
uidList = uids.map(uid => ensureUidValidity(uid));
|
||||||
uidList.push(generateUid());
|
uidList.push(generateUid());
|
||||||
} else if (uids !== undefined && typeof uids === 'string') {
|
} else if (typeof(uids) === 'string') {
|
||||||
uidList = [ ensureUidValidity(uids) ];
|
uidList = [ ensureUidValidity(uids) ];
|
||||||
}
|
}
|
||||||
this.uids = uidList || [ generateUid() ];
|
this.uids = uidList || [ generateUid() ];
|
||||||
|
|
||||||
this.entries = [];
|
this.entries = [];
|
||||||
this.fields = {};
|
|
||||||
this.logLevel = logLevel;
|
this.logLevel = logLevel;
|
||||||
this.dumpThreshold = dumpThreshold;
|
this.dumpThreshold = dumpThreshold;
|
||||||
this.endLevel = endLevel;
|
this.bLogger = logger;
|
||||||
this.endLogger = new EndLogger(this);
|
|
||||||
this.sLogger = logger;
|
|
||||||
|
|
||||||
this.startTime = process.hrtime();
|
|
||||||
this.elapsedTime = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,8 +75,8 @@ class RequestLogger {
|
||||||
* @note The call to the map function is to make sure we get a copy of the
|
* @note The call to the map function is to make sure we get a copy of the
|
||||||
* array instead of a reference to it.
|
* array instead of a reference to it.
|
||||||
*
|
*
|
||||||
* @returns {string[]} A copy of the internal array of UIDs. It shall
|
* @returns A copy of the internal array of UIDs. It shall never be empty,
|
||||||
* never be empty, null or undefined.
|
* null or undefined.
|
||||||
*/
|
*/
|
||||||
getUids() {
|
getUids() {
|
||||||
return this.uids.map(uid => uid);
|
return this.uids.map(uid => uid);
|
||||||
|
@ -229,159 +96,28 @@ class RequestLogger {
|
||||||
return serializeUids(this.uids);
|
return serializeUids(this.uids);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
trace(message) {
|
||||||
* This function allows the user to add default fields to include into all
|
return this.log('trace', message);
|
||||||
* JSON log entries generated through this request logger. As this function
|
|
||||||
* attempt not to modify the provided fields object, it copies the field
|
|
||||||
* into a new object for safe keeping.
|
|
||||||
*
|
|
||||||
* @param {object} fields The dictionnary of additional fields to include
|
|
||||||
* by default for this instance of the
|
|
||||||
* RequestLogger.
|
|
||||||
*
|
|
||||||
* @returns {object} The previous set of default fields (can be
|
|
||||||
* safely ignored).
|
|
||||||
*/
|
|
||||||
addDefaultFields(fields) {
|
|
||||||
const oldFields = this.fields;
|
|
||||||
this.fields = objectCopy({}, this.fields, fields);
|
|
||||||
return oldFields;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
debug(message) {
|
||||||
* Logging function to write a trace-level log entry.
|
return this.log('debug', message);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
trace(msg, data) {
|
|
||||||
return this.log('trace', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
info(message) {
|
||||||
* Logging function to write a debug-level log entry.
|
return this.log('info', message);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
debug(msg, data) {
|
|
||||||
return this.log('debug', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
warn(message) {
|
||||||
* Logging function to write a info-level log entry.
|
return this.log('warn', message);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
info(msg, data) {
|
|
||||||
return this.log('info', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
error(message) {
|
||||||
* Logging function to write a warn-level log entry.
|
return this.log('error', message);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
warn(msg, data) {
|
|
||||||
return this.log('warn', msg, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fatal(message) {
|
||||||
* Logging function to write a error-level log entry.
|
return this.log('fatal', message);
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
error(msg, data) {
|
|
||||||
return this.log('error', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging function to write a fatal-level log entry.
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
fatal(msg, data) {
|
|
||||||
return this.log('fatal', msg, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @version 1.1
|
|
||||||
* @method RequestLogger#end
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Function returning a wrapped RequestLogger to be used at the end of a
|
|
||||||
* given request. It will automatically include an elapsed_ms data field
|
|
||||||
* in the associated log entry.
|
|
||||||
*
|
|
||||||
* @returns {EndLogger} The wrapped RequestLogger fit to actually do the
|
|
||||||
* last logging operation, whatever logging level it
|
|
||||||
* will be.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @deprecated since version 1.1
|
|
||||||
*
|
|
||||||
* @version 1.0
|
|
||||||
* @method RequestLogger#end
|
|
||||||
*
|
|
||||||
* @description
|
|
||||||
* Logging function to write the last log entry for the given RequestLogger
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
end(msg, data) {
|
|
||||||
if (msg === undefined && data === undefined) {
|
|
||||||
return this.endLogger;
|
|
||||||
}
|
|
||||||
return this.log(this.endLevel, msg, data, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated since version 1.1
|
|
||||||
*
|
|
||||||
* Logging function to finish an elapsed counter in error level
|
|
||||||
*
|
|
||||||
* @param {string} msg - The message string to include in the log entry.
|
|
||||||
* @param {object} [data] - The object providing additional JSON fields
|
|
||||||
* for the log entry. This is how to provide
|
|
||||||
* metadata for a specific log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
errorEnd(msg, data) {
|
|
||||||
return this.log('error', msg, data, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -390,76 +126,30 @@ class RequestLogger {
|
||||||
* the dump threshold level, every past entry is dumped before this one,
|
* the dump threshold level, every past entry is dumped before this one,
|
||||||
* no matter what their log levels were and what is the configured log
|
* no matter what their log levels were and what is the configured log
|
||||||
* level.
|
* level.
|
||||||
* This function also takes care of properly passing the input parameters
|
|
||||||
* to bunyan, to comply with its API as much as is reasonable.
|
|
||||||
* In order to avoid dumping multiple times the same entries, whenever the
|
* In order to avoid dumping multiple times the same entries, whenever the
|
||||||
* dump threshold is reached, this function will be flushing its past
|
* dump threshold is reached, this function will be flushing its past
|
||||||
* history after the dump operation is done.
|
* history after the dump operation is done.
|
||||||
*
|
*
|
||||||
* If the upperlaying API happens to be misused, this function will log a
|
* @method
|
||||||
* fatal message including the parameters to the logging system, for the
|
* @name log
|
||||||
* developer to find out what he did wrong.
|
|
||||||
*
|
*
|
||||||
* @private
|
* @param level {string} The log level of the log entry. It is
|
||||||
*
|
|
||||||
* @param {string} level - The log level of the log entry. It is
|
|
||||||
* assumed that the level name has already
|
* assumed that the level name has already
|
||||||
* been checked and is valid.
|
* been checked and is valid.
|
||||||
* @param {string} msg - The message to be printed out in the logs
|
* @param message {string} The arguments sent to the function calling this
|
||||||
* @param {object} [logFields] - The additional JSON fields to include for
|
* utility one.
|
||||||
* this specific log entry.
|
|
||||||
* @param {boolean} isEnd - The flag to tell whether the log entry is
|
|
||||||
* the 'end' log entry that must provide
|
|
||||||
* additional fields or not (elapsedTime)
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
*/
|
||||||
log(level, msg, logFields, isEnd) {
|
log(level, msg) {
|
||||||
if (logFields !== undefined && typeof logFields !== 'object') {
|
|
||||||
this.log(
|
|
||||||
'fatal',
|
|
||||||
'Werelogs API was mis-used.'
|
|
||||||
+ ' This development error should be fixed ASAP.',
|
|
||||||
{
|
|
||||||
callparams: [msg, logFields],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fields = objectCopy({}, this.fields, logFields || {});
|
|
||||||
const endFlag = isEnd || false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
const logEntry = {
|
const logEntry = {
|
||||||
level,
|
level,
|
||||||
fields,
|
|
||||||
msg,
|
msg,
|
||||||
|
hrtime: process.hrtime(),
|
||||||
|
req_id: this.uids.join(':'),
|
||||||
};
|
};
|
||||||
this.entries.push(logEntry);
|
this.entries.push(logEntry);
|
||||||
|
|
||||||
if (LogLevel.shouldLog(level, this.dumpThreshold)) {
|
if (LogLevel.shouldLog(level, this.dumpThreshold)) {
|
||||||
this.entries.forEach(entry => {
|
this.entries.forEach((entry) => {
|
||||||
this.doLogIO(entry);
|
this.doLogIO(entry);
|
||||||
});
|
});
|
||||||
this.entries = [];
|
this.entries = [];
|
||||||
|
@ -468,45 +158,34 @@ class RequestLogger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function transmits a log entry to the configured bunyan logger
|
|
||||||
* instance. This way, we can rely on bunyan for actual logging operations,
|
|
||||||
* and logging multiplexing, instead of managing that ourselves.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {object} logEntry - The Logging entry to be passed to
|
|
||||||
* bunyan
|
|
||||||
* @param {string} logEntry.msg - The message to be logged
|
|
||||||
* @param {object} logEntry.fields - The data fields to associate to the
|
|
||||||
* log entry.
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
doLogIO(logEntry) {
|
doLogIO(logEntry) {
|
||||||
switch (logEntry.level) {
|
switch (logEntry.level) {
|
||||||
case 'trace':
|
case 'trace':
|
||||||
this.sLogger.trace(logEntry.fields, logEntry.msg);
|
this.bLogger.trace({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
case 'debug':
|
case 'debug':
|
||||||
this.sLogger.debug(logEntry.fields, logEntry.msg);
|
this.bLogger.debug({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
case 'info':
|
case 'info':
|
||||||
this.sLogger.info(logEntry.fields, logEntry.msg);
|
this.bLogger.info({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
case 'warn':
|
case 'warn':
|
||||||
this.sLogger.warn(logEntry.fields, logEntry.msg);
|
this.bLogger.warn({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
this.sLogger.error(logEntry.fields, logEntry.msg);
|
this.bLogger.error({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
case 'fatal':
|
case 'fatal':
|
||||||
this.sLogger.fatal(logEntry.fields, logEntry.msg);
|
this.bLogger.fatal({ hrtime: logEntry.hrtime,
|
||||||
|
req_id: logEntry.req_id }, logEntry.msg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unexpected log level: ${logEntry.level}`);
|
throw new Error(`Unexpected log level: ${logEntry.level}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = RequestLogger;
|
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
|
|
||||||
// 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;
|
|
83
lib/Utils.js
83
lib/Utils.js
|
@ -1,50 +1,26 @@
|
||||||
|
|
||||||
// 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.
|
* This function generates a string uid.
|
||||||
*
|
*
|
||||||
* The base algorithm is taken from here: http://jcward.com/UUID.js
|
* @return uid {string} An hexadecimal string representation of an unique id
|
||||||
* And is explained here: http://stackoverflow.com/a/21963136
|
* made of 80 bits.of entropy.
|
||||||
*
|
|
||||||
* @returns {string} An hexadecimal string representation of an unique
|
|
||||||
* id made of 80 bits.of entropy.
|
|
||||||
*/
|
*/
|
||||||
function generateUid() {
|
export function generateUid() {
|
||||||
const d0 = Math.random() * 0xffffffff | 0;
|
function s4() {
|
||||||
const d1 = Math.random() * 0xffffffff | 0;
|
return Math.floor((1 + Math.random()) * 0x10000)
|
||||||
const d2 = Math.random() * 0xffffffff | 0;
|
.toString(16)
|
||||||
return lut[d0 & 0xff]
|
.substring(1);
|
||||||
+ lut[d0 >> 8 & 0xff]
|
}
|
||||||
+ lut[d0 >> 16 & 0xff]
|
return s4() + s4() + s4() + s4() + s4();
|
||||||
+ 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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function serializes an array of UIDs into a format suitable for any
|
* This function serializes an array of UIDs into a format suitable for any
|
||||||
* text-based protocol or storage.
|
* text-based protocol or storage.
|
||||||
*
|
*
|
||||||
* @param {string[]} uidList - The array of string UIDs to serialize
|
* @param {Array[string]} The array of string UIDs
|
||||||
*
|
|
||||||
* @returns {string} The serialized UID array in a string form
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function serializeUids(uidList) {
|
export function serializeUids(uidList) {
|
||||||
return uidList.join(':');
|
return uidList.join(':');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,41 +28,10 @@ function serializeUids(uidList) {
|
||||||
* This function unserializes an array of UIDs from a string and returns the
|
* This function unserializes an array of UIDs from a string and returns the
|
||||||
* generated Array.
|
* generated Array.
|
||||||
*
|
*
|
||||||
* @param {string} stringdata - The string data of the serialized array of UIDs
|
* @param {string} The string data of the serialized array of UIDs
|
||||||
*
|
*
|
||||||
* @returns {string[]} - The unserialized array of string UIDs
|
* @returns {Array[string]} The unserialized array of string UIDs
|
||||||
*/
|
*/
|
||||||
function unserializeUids(stringdata) {
|
export function unserializeUids(stringdata) {
|
||||||
return stringdata.split(':');
|
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
72
lib/api.js
|
@ -1,72 +0,0 @@
|
||||||
|
|
||||||
// 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;
|
|
|
@ -1,106 +0,0 @@
|
||||||
/**
|
|
||||||
* @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,
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
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.
|
|
49
package.json
49
package.json
|
@ -1,18 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "werelogs",
|
"name": "werelogs",
|
||||||
"engines": {
|
"version": "0.0.1",
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"version": "8.1.5",
|
|
||||||
"description": "An efficient raw JSON logging library aimed at micro-services architectures.",
|
"description": "An efficient raw JSON logging library aimed at micro-services architectures.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"gendoc": "jsdoc $(git ls-files 'lib/*.js') -d doc",
|
"compile": "babel -d target/lib/ lib/ && babel -d target/tests/ tests/",
|
||||||
"lint": "eslint $(git ls-files '*.js')",
|
"lint": "eslint $(git ls-files '*.js')",
|
||||||
"lint_md": "markdownlint $(git ls-files '*.md')",
|
"lint_md": "mdlint $(git ls-files '*.md')",
|
||||||
"test": "mocha tests/unit/",
|
"postinstall": "npm run compile",
|
||||||
"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*)",
|
"prepublish": "npm run compile",
|
||||||
"coverage": "nyc ./node_modules/.bin/_mocha tests/unit"
|
"pretest": "npm run compile",
|
||||||
|
"test": "mocha target/tests/unit/",
|
||||||
|
"coverage": "babel-node ./node_modules/istanbul/lib/cli.js cover ./node_modules/.bin/_mocha tests/unit"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -26,27 +25,29 @@
|
||||||
"library",
|
"library",
|
||||||
"JSON"
|
"JSON"
|
||||||
],
|
],
|
||||||
"author": "Giorgio Regni",
|
"author": "David Pineau",
|
||||||
"license": "Apache-2.0",
|
"license": "ISC",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/scality/werelogs/issues"
|
"url": "https://github.com/scality/werelogs/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/scality/werelogs#readme",
|
"homepage": "https://github.com/scality/werelogs#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-safe-stringify": "^2.1.1",
|
"babel-core": "^6.1.21",
|
||||||
"safe-json-stringify": "^1.2.0"
|
"babel-plugin-transform-es2015-destructuring": "^6.1.18",
|
||||||
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.2.0",
|
||||||
|
"babel-plugin-transform-es2015-parameters": "^6.1.18",
|
||||||
|
"babel-cli": "^6.1.21",
|
||||||
|
"bunyan": "GiorgioRegni/node-bunyan"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.32.0",
|
"babel-cli": "^6.1.21",
|
||||||
"eslint-config-airbnb": "^18.2.1",
|
"babel-eslint": "^4.1.6",
|
||||||
"eslint-config-scality": "git+https://git.yourcmc.ru/vitalif/zenko-eslint-config-scality.git",
|
"eslint": "^1.10.1",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-config-airbnb": "^1.0.2",
|
||||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
"eslint-plugin-react": "^3.10.0",
|
||||||
"eslint-plugin-react": "^7.26.0",
|
"istanbul": "^1.0.0-alpha",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"istanbul-api": "==1.0.0-alpha.9",
|
||||||
"jsdoc": "^3.4.3",
|
"mdlint": "^0.1.0",
|
||||||
"markdownlint-cli": "^0.27.1",
|
"mocha": "^2.3.4"
|
||||||
"mocha": ">=3.1.2",
|
|
||||||
"nyc": "^15.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
124
tests/Utils.js
124
tests/Utils.js
|
@ -1,50 +1,61 @@
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
// eslint-disable-line strict
|
import * as LogLevel from '../lib/LogLevel.js';
|
||||||
|
|
||||||
const assert = require('assert');
|
export class DummyLogger {
|
||||||
|
|
||||||
const LogLevel = require('../lib/LogLevel.js');
|
|
||||||
|
|
||||||
class DummyLogger {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.ops = [];
|
this.ops = [];
|
||||||
this.counts = {
|
this.counts = {
|
||||||
trace: 0,
|
'trace': 0,
|
||||||
debug: 0,
|
'debug': 0,
|
||||||
info: 0,
|
'info': 0,
|
||||||
warn: 0,
|
'warn': 0,
|
||||||
error: 0,
|
'error': 0,
|
||||||
fatal: 0,
|
'fatal': 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
trace(obj, msg) {
|
trace(obj, ...params) {
|
||||||
this._doLog('trace', obj, msg);
|
this.ops.push(['trace',
|
||||||
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
|
this.counts.trace += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug(obj, msg) {
|
debug(obj, ...params) {
|
||||||
this._doLog('debug', obj, msg);
|
this.ops.push(['debug',
|
||||||
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
|
this.counts.debug += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
info(obj, msg) {
|
info(obj, ...params) {
|
||||||
this._doLog('info', obj, msg);
|
this.ops.push(['info',
|
||||||
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
|
this.counts.info += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(obj, msg) {
|
warn(obj, ...params) {
|
||||||
this._doLog('warn', obj, msg);
|
this.ops.push(['warn',
|
||||||
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
|
this.counts.warn += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
error(obj, msg) {
|
error(obj, ...params) {
|
||||||
this._doLog('error', obj, msg);
|
this.ops.push(['error',
|
||||||
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
|
this.counts.error += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fatal(obj, msg) {
|
fatal(obj, ...params) {
|
||||||
this._doLog('fatal', obj, msg);
|
this.ops.push(['fatal',
|
||||||
}
|
[obj, Array.prototype.splice.apply(params, [0])],
|
||||||
|
]);
|
||||||
_doLog(level, obj, msg) {
|
this.counts.fatal += 1;
|
||||||
this.ops.push([level, [obj, msg]]);
|
|
||||||
this.counts[level] += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,66 +68,41 @@ function computeBehavior(filterLevel, logLevel, testLevel) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
msg: `Expected ${logLevel} to be called ${value} times with `
|
'msg': `Expected ${logLevel} to be called ${value} times with filter level ${filterLevel}.`,
|
||||||
+ `filter level ${filterLevel}.`,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function genericFilterGenerator(filterLevel, testLevel, createLogger) {
|
export default function filterGenerator(filterLevel, testLevel, createLogger) {
|
||||||
return function testFilter(done) {
|
return function testFilter(done) {
|
||||||
let retObj;
|
let value;
|
||||||
|
let msg;
|
||||||
const dummyLogger = new DummyLogger();
|
const dummyLogger = new DummyLogger();
|
||||||
const logger = createLogger(dummyLogger, filterLevel);
|
const logger = createLogger(dummyLogger, filterLevel);
|
||||||
|
|
||||||
switch (testLevel) {
|
switch (testLevel) {
|
||||||
/* eslint-disable no-multi-spaces */
|
|
||||||
case 'trace': logger.trace('test trace'); break;
|
case 'trace': logger.trace('test trace'); break;
|
||||||
case 'debug': logger.debug('test debug'); break;
|
case 'debug': logger.debug('test debug'); break;
|
||||||
case 'info': logger.info('test info'); break;
|
case 'info': logger.info('test info'); break;
|
||||||
case 'warn': logger.warn('test warn'); break;
|
case 'warn': logger.warn('test warn'); break;
|
||||||
case 'error': logger.error('test error'); break;
|
case 'error': logger.error('test error'); break;
|
||||||
case 'fatal': logger.fatal('test fatal'); break;
|
case 'fatal': logger.fatal('test fatal'); break;
|
||||||
/* eslint-enable no-multi-spaces */
|
|
||||||
default:
|
default:
|
||||||
done(new Error('Unexpected testLevel name: ', testLevel));
|
done(new Error('Unexpected testLevel name: ', testLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
retObj = computeBehavior(filterLevel, 'trace', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'trace', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.trace, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.trace, value, msg);
|
||||||
retObj = computeBehavior(filterLevel, 'debug', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'debug', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.debug, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.debug, value, msg);
|
||||||
retObj = computeBehavior(filterLevel, 'info', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'info', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.info, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.info, value, msg);
|
||||||
retObj = computeBehavior(filterLevel, 'warn', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'warn', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.warn, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.warn, value, msg);
|
||||||
retObj = computeBehavior(filterLevel, 'error', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'error', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.error, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.error, value, msg);
|
||||||
retObj = computeBehavior(filterLevel, 'fatal', testLevel);
|
({ value, msg } = computeBehavior(filterLevel, 'fatal', testLevel));
|
||||||
assert.strictEqual(dummyLogger.counts.fatal, retObj.value, retObj.msg);
|
assert.strictEqual(dummyLogger.counts.fatal, value, msg);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function loggingMisuseGenerator(test, createLogger) {
|
|
||||||
return function generatedLogAPIMisuseTest(done) {
|
|
||||||
const dummyLogger = new DummyLogger();
|
|
||||||
const logger = createLogger(dummyLogger);
|
|
||||||
assert.doesNotThrow(
|
|
||||||
() => {
|
|
||||||
logger.info.apply(logger, test.args);
|
|
||||||
},
|
|
||||||
Error,
|
|
||||||
`Werelogs should not throw with ${test.desc}`,
|
|
||||||
);
|
|
||||||
assert(dummyLogger.ops[0][0], 'fatal',
|
|
||||||
'Expected the Module Logger to have logged a fatal message.');
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
DummyLogger,
|
|
||||||
genericFilterGenerator,
|
|
||||||
loggingMisuseGenerator,
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
|
|
||||||
// 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 werelogs.Logger('FT-test');
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFields(fields) {
|
|
||||||
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', () => {
|
|
||||||
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.',
|
|
||||||
);
|
|
||||||
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(parseLogEntry().message, msg);
|
|
||||||
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 },
|
|
||||||
};
|
|
||||||
logger.info(msg, fields);
|
|
||||||
assert.strictEqual(parseLogEntry().message, msg);
|
|
||||||
checkFields(fields);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Usage of the RequestLogger', () => {
|
|
||||||
afterEach(() => {
|
|
||||||
logBuffer.records = [];
|
|
||||||
});
|
|
||||||
it('Should be able to create a logger', done => {
|
|
||||||
assert.doesNotThrow(
|
|
||||||
() => createModuleLogger().newRequestLogger(),
|
|
||||||
Error,
|
|
||||||
'Werelogs threw an exception trying to create a ModuleLogger.',
|
|
||||||
);
|
|
||||||
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(parseLogEntry().message, msg);
|
|
||||||
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 },
|
|
||||||
};
|
|
||||||
logger.info(msg, fields);
|
|
||||||
assert.strictEqual(parseLogEntry().message, msg);
|
|
||||||
checkFields(fields);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,57 +0,0 @@
|
||||||
const assert = require('assert');
|
|
||||||
const { PassThrough } = require('stream');
|
|
||||||
|
|
||||||
const Werelogs = require('werelogs'); // eslint-disable-line
|
|
||||||
const modules = [
|
|
||||||
require('./module1.js'),
|
|
||||||
require('./module2.js'),
|
|
||||||
require('./module3.js'),
|
|
||||||
];
|
|
||||||
|
|
||||||
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: pass,
|
|
||||||
}],
|
|
||||||
});
|
|
||||||
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');
|
|
||||||
/* 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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
const Werelogs = require('werelogs').Logger; // eslint-disable-line
|
|
||||||
|
|
||||||
const log = new Werelogs('test-mod1');
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
log.info('Logging as info');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = test;
|
|
|
@ -1,9 +0,0 @@
|
||||||
const Werelogs = require('werelogs').Logger; // eslint-disable-line
|
|
||||||
|
|
||||||
const log = new Werelogs('test-mod2');
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
log.debug('Logging as debug');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = test;
|
|
|
@ -1,9 +0,0 @@
|
||||||
const Werelogs = require('werelogs').Logger; // eslint-disable-line
|
|
||||||
|
|
||||||
const log = new Werelogs('test-mod3');
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
log.error('Logging as error');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = test;
|
|
|
@ -1,58 +0,0 @@
|
||||||
/* eslint-disable max-len */
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const Config = require('../../lib/Config.js');
|
|
||||||
|
|
||||||
describe('Config', () => {
|
|
||||||
const config = new Config();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
config.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should work with default configuration', done => {
|
|
||||||
assert.doesNotThrow(
|
|
||||||
() => {
|
|
||||||
config.logger.info('test message');
|
|
||||||
},
|
|
||||||
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.');
|
|
||||||
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.');
|
|
||||||
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.');
|
|
||||||
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.');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,9 +1,6 @@
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
// eslint-disable-line strict
|
import * as LogLevel from '../../lib/LogLevel.js';
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const LogLevel = require('../../lib/LogLevel.js');
|
|
||||||
|
|
||||||
function generateValidThrowTest(level) {
|
function generateValidThrowTest(level) {
|
||||||
return function validTest(done) {
|
return function validTest(done) {
|
||||||
|
@ -13,23 +10,21 @@ function generateValidThrowTest(level) {
|
||||||
},
|
},
|
||||||
Error,
|
Error,
|
||||||
'Expected level to be valid and '
|
'Expected level to be valid and '
|
||||||
+ 'the function not to throw an Error.',
|
+ 'the function not to throw an Error.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('LogLevel', () => {
|
describe('LogLevel', () => {
|
||||||
describe('throwIfInvalid(level)', () => {
|
describe('throwIfInvalid(level)', () => {
|
||||||
it('should throw on invalid string', done => {
|
it('should throw on invalid string', (done) => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => {
|
() => {
|
||||||
LogLevel.throwIfInvalid('invalid');
|
LogLevel.throwIfInvalid('invalid');
|
||||||
},
|
},
|
||||||
RangeError,
|
RangeError,
|
||||||
'Expected function to throw an Error instance due to '
|
'Expected function to throw an Error instance due to '
|
||||||
+ 'invalid log level.',
|
+ 'invalid log level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -53,57 +48,51 @@ describe('LogLevel', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('shouldLog(level, floor)', () => {
|
describe('shouldLog(level, floor)', () => {
|
||||||
it('should return true on "trace" parameters', done => {
|
it('should return true on "trace" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('trace', 'trace'),
|
LogLevel.shouldLog('trace', 'trace'),
|
||||||
true,
|
true,
|
||||||
'Expected trace floor to allow logging trace level.',
|
'Expected trace floor to allow logging trace level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true on "debug" parameters', done => {
|
it('should return true on "debug" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('debug', 'debug'),
|
LogLevel.shouldLog('debug', 'debug'),
|
||||||
true,
|
true,
|
||||||
'Expected debug floor to allow logging debug level.',
|
'Expected debug floor to allow logging debug level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true on "info" parameters', done => {
|
it('should return true on "info" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('info', 'info'),
|
LogLevel.shouldLog('info', 'info'),
|
||||||
true,
|
true,
|
||||||
'Expected info floor to allow logging info level.',
|
'Expected info floor to allow logging info level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true on "warn" parameters', done => {
|
it('should return true on "warn" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('warn', 'warn'),
|
LogLevel.shouldLog('warn', 'warn'),
|
||||||
true,
|
true,
|
||||||
'Expected warn floor to allow logging warn level.',
|
'Expected warn floor to allow logging warn level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true on "error" parameters', done => {
|
it('should return true on "error" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('error', 'error'),
|
LogLevel.shouldLog('error', 'error'),
|
||||||
true,
|
true,
|
||||||
'Expected error floor to allow logging error level.',
|
'Expected error floor to allow logging error level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true on "fatal" parameters', done => {
|
it('should return true on "fatal" parameters', (done) => {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
LogLevel.shouldLog('fatal', 'fatal'),
|
LogLevel.shouldLog('fatal', 'fatal'),
|
||||||
true,
|
true,
|
||||||
'Expected fatal floor to allow logging fatal level.',
|
'Expected fatal floor to allow logging fatal level.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,160 +1,144 @@
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
// eslint-disable-line strict
|
import { default as genericFilterGenerator } from '../Utils.js';
|
||||||
|
|
||||||
const assert = require('assert');
|
import RequestLogger from '../../lib/RequestLogger.js';
|
||||||
|
import Logger from '../../lib/Logger.js';
|
||||||
|
|
||||||
const { genericFilterGenerator, loggingMisuseGenerator, DummyLogger } = require('../Utils');
|
/**
|
||||||
|
|
||||||
const Config = require('../../lib/Config.js');
|
|
||||||
const RequestLogger = require('../../lib/RequestLogger.js');
|
|
||||||
const Logger = require('../../lib/Logger.js');
|
|
||||||
|
|
||||||
const config = new Config();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This function is a thunk-function calling the Utils' filterGenerator with
|
* This function is a thunk-function calling the Utils' filterGenerator with
|
||||||
* the right createLogger function, while seemlessly passing through its
|
* the right createLogger function, while seemlessly passing through its
|
||||||
* arguments.
|
* arguments.
|
||||||
*/
|
*/
|
||||||
function filterGenerator(logLevel, callLevel) {
|
function filterGenerator(...params) {
|
||||||
function createModuleLogger(dummyLogger, filterLevel) {
|
function createModuleLogger(dummyLogger, filterLevel) {
|
||||||
|
const logger = new Logger('TestModuleLogger',
|
||||||
|
{
|
||||||
|
level: filterLevel,
|
||||||
|
dump: 'fatal',
|
||||||
|
});
|
||||||
/*
|
/*
|
||||||
* Here, patch the config by setting a specifically designed dummyLogger
|
* Here, patch the logger by setting a specificly designed dummyLogger
|
||||||
* for testing purposes that will help us collect runtime data.
|
* for testing purposes that will help us collect runtime data.
|
||||||
*/
|
*/
|
||||||
const testConfig = new Config({ level: filterLevel, dump: 'fatal' });
|
logger.bLogger = dummyLogger;
|
||||||
testConfig.simpleLogger = dummyLogger;
|
|
||||||
|
|
||||||
return new Logger(testConfig, 'TestModuleLogger');
|
|
||||||
}
|
|
||||||
|
|
||||||
return genericFilterGenerator(logLevel, callLevel, createModuleLogger);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFields(src, result) {
|
|
||||||
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('Logger is usable:', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
config.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Cannot be instanciated without parameters', done => {
|
|
||||||
assert.throws(
|
|
||||||
() => new Logger(),
|
|
||||||
TypeError,
|
|
||||||
'Logger Instanciation should not succeed without parameter.',
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Cannot be instanciated with only a config', done => {
|
|
||||||
assert.throws(
|
|
||||||
() => new Logger(config),
|
|
||||||
TypeError,
|
|
||||||
'Logger Instanciation should not be succeed without a name.',
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Cannot be instanciated with a bad config type', done => {
|
|
||||||
assert.throws(
|
|
||||||
() => new Logger({ level: 'info' }, 'WereLogsTest'),
|
|
||||||
TypeError,
|
|
||||||
'Logger Instanciation should not succeed with a bad config type.',
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Cannot be instanciated with only a name', done => {
|
|
||||||
assert.throws(
|
|
||||||
() => new Logger('WereLogsTest'),
|
|
||||||
TypeError,
|
|
||||||
'Logger Instanciation should not succeed with only a name.',
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
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.',
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Can create Per-Request Loggers from a Serialized UID Array', done => {
|
|
||||||
const logger = new Logger(config, 'test');
|
|
||||||
assert.doesNotThrow(
|
|
||||||
() => {
|
|
||||||
logger.newRequestLogger();
|
|
||||||
},
|
|
||||||
Error,
|
|
||||||
// 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']);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Uses the additional fields as expected', done => {
|
|
||||||
const dummyLogger = new DummyLogger();
|
|
||||||
config.simpleLogger = dummyLogger;
|
|
||||||
const logger = new Logger(config, 'test');
|
|
||||||
const fields = {
|
|
||||||
ip: '127.0.0.1',
|
|
||||||
method: 'GET',
|
|
||||||
count: 23,
|
|
||||||
};
|
|
||||||
logger.info('message', fields);
|
|
||||||
checkFields(fields, dummyLogger.ops[0][1][0]);
|
|
||||||
assert.strictEqual(dummyLogger.ops[0][1][1], 'message');
|
|
||||||
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', () => { }] }, // 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) {
|
|
||||||
config.simpleLogger = dummyLogger;
|
|
||||||
const logger = new Logger(config, 'test');
|
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < testValues.length; ++i) {
|
/*
|
||||||
const test = testValues[i];
|
* Array-ify the arguments object, and append the specificly-added argument
|
||||||
it(`Does not crash with ${test.desc}`,
|
* to it.
|
||||||
loggingMisuseGenerator(test, createMisusableLogger));
|
*/
|
||||||
|
const args = Array.prototype.splice.apply(params, [0]);
|
||||||
|
args.push(createModuleLogger);
|
||||||
|
|
||||||
|
return genericFilterGenerator.apply({}, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('WereLogs Logger is usable:', () => {
|
||||||
|
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) => {
|
||||||
|
assert.throws(
|
||||||
|
() => {
|
||||||
|
return new Logger('test', {level: 'invalidlevel'});
|
||||||
|
},
|
||||||
|
RangeError,
|
||||||
|
'WereLogs should not be instanciable without the proper logging levels.');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cannot be instanciated with invalid dump threshold level', (done) => {
|
||||||
|
assert.throws(
|
||||||
|
() => {
|
||||||
|
return new Logger('test', {level: 'trace', dump: 'invalidlevel'});
|
||||||
|
},
|
||||||
|
RangeError,
|
||||||
|
'WereLogs should not be instanciable without the proper dumping threshold levels.');
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cannot be instanciated with a non-Array in config.streams', (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.');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Cannot be instanciated with an empty Array in config.streams', (done) => {
|
||||||
|
assert.throws(
|
||||||
|
() => {
|
||||||
|
return new Logger('test', {streams: []});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'Werelogs should not be instanciable with an empty array for the streams option.');
|
||||||
|
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('Can create Per-Request Loggers', (done) => {
|
||||||
|
const logger = new Logger('test');
|
||||||
|
assert.doesNotThrow(
|
||||||
|
() => {
|
||||||
|
logger.newRequestLogger();
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'Werelogs should not throw when creating a request logger.');
|
||||||
|
const reqLogger = logger.newRequestLogger();
|
||||||
|
assert(reqLogger instanceof RequestLogger, 'RequestLogger');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can create Per-Request Loggers from a Serialized UID Array', (done) => {
|
||||||
|
const logger = new Logger('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');
|
||||||
|
assert(reqLogger instanceof RequestLogger, 'RequestLogger');
|
||||||
|
assert.deepStrictEqual(reqLogger.getUids().slice(0, -1), ['OneUID', 'SecondUID', 'TestUID', 'YouWinUID']);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable no-multi-spaces, max-len */
|
describe('Werelogs Module-level Logger can log as specified by the log level', () => {
|
||||||
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 trace level out', filterGenerator('trace', 'trace'));
|
||||||
it('Trace level does not filter debug level out', filterGenerator('trace', 'debug'));
|
it('Trace level does not filter debug level out', filterGenerator('trace', 'debug'));
|
||||||
it('Trace level does not filter info level out', filterGenerator('trace', 'info'));
|
it('Trace level does not filter info level out', filterGenerator('trace', 'info'));
|
||||||
|
@ -197,4 +181,3 @@ describe('Logger can log as specified by the log level', () => {
|
||||||
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
|
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
|
||||||
it('Fatal level does not filter fatal level out', filterGenerator('fatal', 'fatal'));
|
it('Fatal level does not filter fatal level out', filterGenerator('fatal', 'fatal'));
|
||||||
});
|
});
|
||||||
/* eslint-enable no-multi-spaces, max-len */
|
|
||||||
|
|
|
@ -1,41 +1,42 @@
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
// eslint-disable-line strict
|
import { default as genericFilterGenerator, DummyLogger } from '../Utils.js';
|
||||||
|
|
||||||
const assert = require('assert');
|
import RequestLogger from '../../lib/RequestLogger.js';
|
||||||
|
|
||||||
const { DummyLogger, genericFilterGenerator, loggingMisuseGenerator } = require('../Utils.js');
|
/**
|
||||||
|
|
||||||
const RequestLogger = require('../../lib/RequestLogger.js');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This function is a thunk-function calling the Utils' filterGenerator with
|
* This function is a thunk-function calling the Utils' filterGenerator with
|
||||||
* the right createLogger function, while seemlessly passing through its
|
* the right createLogger function, while seemlessly passing through its
|
||||||
* arguments.
|
* arguments.
|
||||||
*/
|
*/
|
||||||
function filterGenerator(logLevel, callLevel) {
|
function filterGenerator(...params) {
|
||||||
function createRequestLogger(dummyLogger, filterLevel) {
|
function createRequestLogger(dummyLogger, filterLevel) {
|
||||||
return new RequestLogger(dummyLogger, filterLevel, 'fatal', 'info');
|
return new RequestLogger(dummyLogger, filterLevel, 'fatal');
|
||||||
}
|
}
|
||||||
|
|
||||||
return genericFilterGenerator(logLevel, callLevel, createRequestLogger);
|
/*
|
||||||
|
* Array-ify the arguments object, and append the specificly-added argument
|
||||||
|
* to it.
|
||||||
|
*/
|
||||||
|
const args = Array.prototype.splice.apply(params, [0]);
|
||||||
|
args.push(createRequestLogger);
|
||||||
|
|
||||||
|
return genericFilterGenerator.apply({}, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
|
function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done) {
|
||||||
done) {
|
|
||||||
const dummyLogger = new DummyLogger();
|
const dummyLogger = new DummyLogger();
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'error', 'info');
|
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'error');
|
||||||
|
|
||||||
commandHistory.every((val, index) => {
|
commandHistory.every(function doLogWithLevel(val, index) {
|
||||||
switch (val) {
|
switch (val) {
|
||||||
/* eslint-disable no-multi-spaces */
|
|
||||||
case 'trace': reqLogger.trace(index); break;
|
case 'trace': reqLogger.trace(index); break;
|
||||||
case 'debug': reqLogger.debug(index); break;
|
case 'debug': reqLogger.debug(index); break;
|
||||||
case 'info': reqLogger.info(index); break;
|
case 'info': reqLogger.info(index); break;
|
||||||
case 'warn': reqLogger.warn(index); break;
|
case 'warn': reqLogger.warn(index); break;
|
||||||
case 'error': reqLogger.error(index); break;
|
case 'error': reqLogger.error(index); break;
|
||||||
case 'fatal': reqLogger.fatal(index); break;
|
case 'fatal': reqLogger.fatal(index); break;
|
||||||
/* eslint-enable no-multi-spaces */
|
|
||||||
default:
|
default:
|
||||||
done(new Error('Unexpected logging level name: ', val));
|
done(new Error('Unexpected logging level name: ', val));
|
||||||
}
|
}
|
||||||
|
@ -43,90 +44,92 @@ function runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
|
||||||
});
|
});
|
||||||
|
|
||||||
expectedHistory.every((val, index) => {
|
expectedHistory.every((val, index) => {
|
||||||
assert.strictEqual(dummyLogger.ops[index][0], val[0],
|
assert.strictEqual(dummyLogger.ops[index][0], val[0], 'Expected log entry levels to match.');
|
||||||
'Expected log entry levels to match.');
|
assert.strictEqual(dummyLogger.ops[index][1][1][0], val[1], 'Expected log entry values to match.');
|
||||||
assert.strictEqual(dummyLogger.ops[index][1][1], val[1],
|
|
||||||
'Expected log entry values to match.');
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
assert.deepEqual(dummyLogger.counts, expectedCounts);
|
assert.deepEqual(dummyLogger.counts, expectedCounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable no-multi-spaces, max-len */
|
|
||||||
describe('RequestLogger', () => {
|
describe('RequestLogger', () => {
|
||||||
describe('Object Instanciation', () => {
|
describe('Object Instanciation', () => {
|
||||||
describe('Logging Levels Initialization', () => {
|
describe('Logging Levels Initialization', () => {
|
||||||
it('Throws if LogLevel is higher than dumpThreshold', done => {
|
it('Throws if LogLevel is higher than dumpThreshold', (done) => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => new RequestLogger(undefined, 'fatal', 'debug', 'info'),
|
() => {
|
||||||
|
return new RequestLogger(undefined, 'fatal', 'debug');
|
||||||
|
},
|
||||||
Error,
|
Error,
|
||||||
'Dump level "debug" should not be valid with logging level "fatal".',
|
'Dump level "debug" should not be valid with logging level "fatal".');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Works with LogLevel lesser or equal to DumpLevel', done => {
|
it('Works with LogLevel lesser or equal to DumpLevel', (done) => {
|
||||||
assert.doesNotThrow(
|
assert.doesNotThrow(
|
||||||
() => new RequestLogger(undefined, 'debug', 'fatal', 'info'),
|
() => {
|
||||||
|
return new RequestLogger(undefined, 'debug', 'fatal');
|
||||||
|
},
|
||||||
Error,
|
Error,
|
||||||
'Dump level "fatal" should be valid with logging level "debug".',
|
'Dump level "fatal" should be valid with logging level "debug".');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('UID Initialization', () => {
|
describe('UID Initialization', () => {
|
||||||
it('defines an UID when none provided', done => {
|
it('defines an UID when none provided', (done) => {
|
||||||
const dummyLogger = new DummyLogger();
|
const dummyLogger = new DummyLogger();
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info');
|
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal');
|
||||||
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
||||||
assert.strictEqual(reqLogger.uids.length, 1, 'Expected uid list to contain one element.');
|
assert.strictEqual(reqLogger.uids.length, 1, 'Expected uid list to contain one element.');
|
||||||
done();
|
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 dummyLogger = new DummyLogger();
|
||||||
const uids = 'BasicUid';
|
const uids = 'BasicUid';
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info', uids);
|
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', uids);
|
||||||
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
||||||
assert.strictEqual(reqLogger.uids.length, 1, 'Expected uid list to contain one element.');
|
assert.strictEqual(reqLogger.uids.length, 1, 'Expected uid list to contain one element.');
|
||||||
assert.strictEqual(reqLogger.uids[0], uids, 'Expected uid list to only contain the value given as argument.');
|
assert.strictEqual(reqLogger.uids[0], uids, 'Expected uid list to only contain the value given as argument.');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when UID string provided contains a colon', done => {
|
it('throws when UID string provided contains a colon', (done) => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', 'pouet:tata'),
|
() => {
|
||||||
|
return new RequestLogger(undefined, 'debug', 'fatal', 'pouet:tata');
|
||||||
|
},
|
||||||
Error,
|
Error,
|
||||||
'UID string "pouet:tata" should be rejected by the RequestLogger constructor.',
|
'UID string "pouet:tata" should be rejected by the RequestLogger constructor.');
|
||||||
);
|
|
||||||
done();
|
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 dummyLogger = new DummyLogger();
|
||||||
const uids = ['oneuid', 'twouid', 'threeuids'];
|
const uids = ['oneuid', 'twouid', 'threeuids'];
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', 'info', uids);
|
const reqLogger = new RequestLogger(dummyLogger, 'debug', 'fatal', uids);
|
||||||
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
assert.strictEqual(Array.isArray(reqLogger.uids), true, 'Expected uid list to be an Array.');
|
||||||
assert.strictEqual(reqLogger.uids.length, 4, 'Expected uid list to contain four elements.');
|
assert.strictEqual(reqLogger.uids.length, 4, 'Expected uid list to contain four elements.');
|
||||||
assert.strictEqual(uids.indexOf(reqLogger.uids[3]), -1, 'Expected the last uid of the list to be the new one.');
|
assert.strictEqual(uids.indexOf(reqLogger.uids[3]), -1, 'Expected the last uid of the list to be the new one.');
|
||||||
done();
|
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(
|
assert.throws(
|
||||||
() => new RequestLogger(undefined, 'debug', 'fatal', 'info', ['OneUID', 'SecondUID', 'Test:DashUID']),
|
() => {
|
||||||
|
return new RequestLogger(undefined, 'debug', 'fatal', ['OneUID', 'SecondUID', 'Test:DashUID']);
|
||||||
|
},
|
||||||
Error,
|
Error,
|
||||||
'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.',
|
'UID string "Test:DashUID" should be rejected by the RequestLogger constructor.');
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getUids() method', () => {
|
describe('getUids() method', () => {
|
||||||
it('retrieves a list of string UID', done => {
|
it('retrieves a list of string UID', (done) => {
|
||||||
const dummyLogger = new DummyLogger();
|
const dummyLogger = new DummyLogger();
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
|
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error');
|
||||||
const uidlist = reqLogger.getUids();
|
const uidlist = reqLogger.getUids();
|
||||||
assert.strictEqual(Array.isArray(uidlist), true, 'Expected UID List to be an Array');
|
assert.strictEqual(Array.isArray(uidlist), true, 'Expected UID List to be an Array');
|
||||||
assert.strictEqual(typeof uidlist[0], 'string', 'Expected UID items to be strings');
|
assert.strictEqual(typeof uidlist[0], 'string', 'Expected UID items to be strings');
|
||||||
|
@ -134,28 +137,28 @@ describe('RequestLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Length of the UIDs array', () => {
|
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 dummyLogger = new DummyLogger();
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
|
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error');
|
||||||
const uidlist = reqLogger.getUids();
|
const uidlist = reqLogger.getUids();
|
||||||
assert.strictEqual(uidlist.length, 1, 'Expected only one item in UID Array');
|
assert.strictEqual(uidlist.length, 1, 'Expected only one item in UID Array');
|
||||||
done();
|
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 dummyLogger = new DummyLogger();
|
||||||
const myUid = 'ThisIsMyUid';
|
const myUid = 'ThisIsMyUid';
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info', myUid);
|
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', myUid);
|
||||||
const uidlist = reqLogger.getUids();
|
const uidlist = reqLogger.getUids();
|
||||||
assert.strictEqual(uidlist.length, 1, 'Expected only one item in UID Array');
|
assert.strictEqual(uidlist.length, 1, 'Expected only one item in UID Array');
|
||||||
assert.strictEqual(uidlist[0], myUid, 'Expected UID to match what was used to set it.');
|
assert.strictEqual(uidlist[0], myUid, 'Expected UID to match what was used to set it.');
|
||||||
done();
|
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 dummyLogger = new DummyLogger();
|
||||||
const myParentUidList = [ 'ThisIsMyOriginUid', 'ThisIsMySecondGenUid', 'ThisIsMyThirdGenUid' ];
|
const myParentUidList = [ 'ThisIsMyOriginUid', 'ThisIsMySecondGenUid', 'ThisIsMyThirdGenUid' ];
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info', myParentUidList);
|
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', myParentUidList);
|
||||||
const uidlist = reqLogger.getUids();
|
const uidlist = reqLogger.getUids();
|
||||||
assert.strictEqual(uidlist.length, myParentUidList.length + 1, 'Expected n+1 item in UID Array compared to set UID List array');
|
assert.strictEqual(uidlist.length, myParentUidList.length + 1, 'Expected n+1 item in UID Array compared to set UID List array');
|
||||||
assert.deepStrictEqual(uidlist.slice(0, -1), myParentUidList, 'Expected UID list[:-1] to match what was used to set it.');
|
assert.deepStrictEqual(uidlist.slice(0, -1), myParentUidList, 'Expected UID list[:-1] to match what was used to set it.');
|
||||||
|
@ -163,9 +166,9 @@ 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 dummyLogger = new DummyLogger();
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 'info');
|
const reqLogger = new RequestLogger(dummyLogger, 'info', 'error');
|
||||||
const uidlist = reqLogger.getUids();
|
const uidlist = reqLogger.getUids();
|
||||||
uidlist.push('Test');
|
uidlist.push('Test');
|
||||||
assert.notStrictEqual(uidlist.length, reqLogger.getUids().length, 'Expected different number of items in internals and local variable.');
|
assert.notStrictEqual(uidlist.length, reqLogger.getUids().length, 'Expected different number of items in internals and local variable.');
|
||||||
|
@ -174,35 +177,17 @@ describe('RequestLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getSerializedUids()', () => {
|
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 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 reqLogger = new RequestLogger(dummyLogger, 'info', 'error', 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.');
|
assert.strictEqual(reqLogger.getSerializedUids(), expectedString, 'Expected serialized UID List to match expected data.');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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', 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');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < testValues.length; ++i) {
|
|
||||||
const test = testValues[i];
|
|
||||||
it(`Does not crash with ${test.desc}`,
|
|
||||||
loggingMisuseGenerator(test, createMisusableRequestLogger));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Logging level dump filtering', () => {
|
describe('Logging level dump filtering', () => {
|
||||||
it('Trace level does not filter trace level out', filterGenerator('trace', 'trace'));
|
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 debug level out', filterGenerator('trace', 'debug'));
|
||||||
|
@ -246,248 +231,37 @@ describe('RequestLogger', () => {
|
||||||
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
|
it('Fatal level filters error level out', filterGenerator('fatal', 'error'));
|
||||||
it('Fatal level does not filter fatal level out', filterGenerator('fatal', 'fatal'));
|
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 = { ...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 = { ...refFields };
|
|
||||||
reqLogger.info('test', usedFields);
|
|
||||||
assert.deepStrictEqual(usedFields, refFields);
|
|
||||||
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 = { ...refFields };
|
|
||||||
reqLogger.error('test', usedFields);
|
|
||||||
assert.deepStrictEqual(usedFields, refFields);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Default Fields', () => {
|
|
||||||
it('should not modify the object passed as a parameter', done => {
|
|
||||||
const add1 = {
|
|
||||||
attr1: 0,
|
|
||||||
};
|
|
||||||
const add2 = {
|
|
||||||
attr2: 'string',
|
|
||||||
};
|
|
||||||
const dummyLogger = new DummyLogger();
|
|
||||||
const reqLogger = new RequestLogger(dummyLogger,
|
|
||||||
'info', 'fatal', 'info');
|
|
||||||
reqLogger.addDefaultFields(add1);
|
|
||||||
reqLogger.addDefaultFields(add2);
|
|
||||||
assert.deepStrictEqual(add1, { attr1: 0 });
|
|
||||||
assert.deepStrictEqual(add2, { attr2: 'string' });
|
|
||||||
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');
|
|
||||||
reqLogger.addDefaultFields(clientInfo);
|
|
||||||
reqLogger.info('test message');
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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');
|
|
||||||
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][0], 'error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const endLogging = {
|
|
||||||
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),
|
|
||||||
};
|
|
||||||
/* 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(dummyLogger.ops[0][0], level);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
/* eslint-enable max-len */
|
|
||||||
|
|
||||||
it('should be augmentable through addDefaultFields', done => {
|
|
||||||
const dummyLogger = new DummyLogger();
|
|
||||||
const reqLogger = new RequestLogger(dummyLogger, 'trace', 'fatal');
|
|
||||||
reqLogger.end().addDefaultFields({ endFlag: true });
|
|
||||||
// Someone could do multiple operations in the meantime before
|
|
||||||
// end() logging
|
|
||||||
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(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', () => {
|
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 commandHistory = ['info', 'error'];
|
||||||
const expectedHistory = [['info', 0], ['info', 0], ['error', 1]];
|
const expectedHistory = [['info', 0], ['info', 0], ['error', 1]];
|
||||||
const expectedCounts = {
|
const expectedCounts = { trace: 0, debug: 0, info: 2, warn: 0, error: 1, fatal: 0 };
|
||||||
trace: 0,
|
|
||||||
debug: 0,
|
|
||||||
info: 2,
|
|
||||||
warn: 0,
|
|
||||||
error: 1,
|
|
||||||
fatal: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
|
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
|
||||||
done);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Dumping Keeps logging history order', done => {
|
it('Dumping Keeps logging history order', (done) => {
|
||||||
const commandHistory = ['trace', 'info', 'debug', 'error'];
|
const commandHistory = ['trace', 'info', 'debug', 'error'];
|
||||||
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
|
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2], ['trace', 0], ['info', 1], ['debug', 2], ['error', 3]];
|
||||||
['trace', 0], ['info', 1], ['debug', 2],
|
const expectedCounts = { trace: 2, debug: 2, info: 2, warn: 0, error: 1, fatal: 0 };
|
||||||
['error', 3]];
|
|
||||||
const expectedCounts = {
|
|
||||||
trace: 2,
|
|
||||||
debug: 2,
|
|
||||||
info: 2,
|
|
||||||
warn: 0,
|
|
||||||
error: 1,
|
|
||||||
fatal: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts,
|
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
|
||||||
done);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Dumping multiple times does not re-dump already-dumped entries',
|
it('Dumping multiple times does not re-dump already-dumped entries', (done) => {
|
||||||
done => {
|
|
||||||
const commandHistory = ['trace', 'info', 'debug', 'error',
|
const commandHistory = ['trace', 'info', 'debug', 'error',
|
||||||
'warn', 'debug', 'fatal'];
|
'warn', 'debug', 'fatal'];
|
||||||
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
|
const expectedHistory = [['trace', 0], ['info', 1], ['debug', 2],
|
||||||
['trace', 0], ['info', 1], ['debug', 2],
|
['trace', 0], ['info', 1], ['debug', 2], ['error', 3],
|
||||||
['error', 3], ['warn', 4], ['debug', 5],
|
|
||||||
['warn', 4], ['debug', 5],
|
['warn', 4], ['debug', 5],
|
||||||
['fatal', 6]];
|
['warn', 4], ['debug', 5], ['fatal', 6]];
|
||||||
const expectedCounts = {
|
const expectedCounts = { trace: 2, debug: 4, info: 2, warn: 2, error: 1, fatal: 1 };
|
||||||
trace: 2,
|
|
||||||
debug: 4,
|
|
||||||
info: 2,
|
|
||||||
warn: 2,
|
|
||||||
error: 1,
|
|
||||||
fatal: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
runLoggingDumpTest(commandHistory, expectedHistory,
|
runLoggingDumpTest(commandHistory, expectedHistory, expectedCounts, done);
|
||||||
expectedCounts, done);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
|
|
||||||
// eslint-disable-line strict
|
|
||||||
|
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
const Utils = require('../../lib/Utils.js');
|
||||||
const {
|
const generateUid = Utils.generateUid;
|
||||||
generateUid, serializeUids, unserializeUids, objectCopy,
|
const serializeUids = Utils.serializeUids;
|
||||||
} = require('../../lib/Utils');
|
const unserializeUids = Utils.unserializeUids;
|
||||||
|
|
||||||
describe('Utils: generateUid', () => {
|
describe('Utils: generateUid', () => {
|
||||||
it('generates a string-typed ID', done => {
|
it('generates a string-typed ID', (done) => {
|
||||||
const uid = generateUid();
|
const uid = generateUid();
|
||||||
assert.strictEqual(typeof uid, 'string',
|
assert.strictEqual(typeof(uid), 'string', 'The generated ID is not a String (' + typeof(uid) + ')');
|
||||||
`The generated ID is not a String (${typeof uid})`);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it('generate roughly unique IDs', done => {
|
it('generate roughly unique IDs', (done) => {
|
||||||
const generated = {};
|
const generated = {};
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (let i = 0; i < 10000; ++i) {
|
for (let i = 0; i < 10000; ++i) {
|
||||||
|
@ -22,77 +18,25 @@ describe('Utils: generateUid', () => {
|
||||||
count = generated[uid] ? generated[uid] + 1 : 1;
|
count = generated[uid] ? generated[uid] + 1 : 1;
|
||||||
generated[uid] = count;
|
generated[uid] = count;
|
||||||
}
|
}
|
||||||
Object.keys(generated).every(uid => {
|
Object.keys(generated).every((uid) => {
|
||||||
assert.strictEqual(generated[uid], 1,
|
assert.strictEqual(generated[uid], 1, `Uid ${uid} was generated ${generated[uid]} times: It is not even remotely unique.`);
|
||||||
`Uid ${uid} was generated ${generated[uid]} `
|
|
||||||
+ 'times: It is not even remotely unique.');
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Utils: serializeUids', () => {
|
describe('Utils: serializeUids', () => {
|
||||||
it('serializes to the expected string data', done => {
|
it('serializes to the expected string data', (done) => {
|
||||||
const uidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
|
const uidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
|
||||||
const serializedUIDs = serializeUids(uidList);
|
const serializedUIDs = serializeUids(uidList);
|
||||||
assert.strictEqual(serializedUIDs, 'FirstUID:SecondUID:ThirdUID',
|
assert.strictEqual(serializedUIDs, 'FirstUID:SecondUID:ThirdUID', 'Serialized UID List should match expected value.');
|
||||||
'Serialized UID List should match expected value.');
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unserializes the expected number of UIDs', done => {
|
it('unserializes the expected number of UIDs', (done) => {
|
||||||
const refUidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
|
const refUidList = [ 'FirstUID', 'SecondUID', 'ThirdUID'];
|
||||||
const unserializedUIDs = unserializeUids('FirstUID:SecondUID:ThirdUID');
|
const unserializedUIDs = unserializeUids('FirstUID:SecondUID:ThirdUID');
|
||||||
assert.deepStrictEqual(unserializedUIDs, refUidList,
|
assert.deepStrictEqual(unserializedUIDs, refUidList, 'Unserialized UID List should match expected value.');
|
||||||
'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();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
#!/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');
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
#!/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');
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
#!/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
|
|
||||||
*/
|
|
|
@ -1,309 +0,0 @@
|
||||||
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}`));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in New Issue