├── .gitignore
├── CHANGELOG.md
├── babel.config.js
├── rollup.config.js
├── tsconfig.json
├── package.json
├── LICENSE
├── docs
└── Development.md
├── README.md
├── src
├── globals.ts
├── locationParams.ts
└── loggers.ts
├── CONTRIBUTING.md
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | lib/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.0
2 |
3 | * Initial release
4 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', {targets: {node: 'current'}}],
4 | '@babel/preset-typescript',
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import typescript from '@rollup/plugin-typescript';
3 | import commonjs from '@rollup/plugin-commonjs';
4 |
5 | export default {
6 | input: 'src/loggers.ts',
7 | output: {
8 | format: 'cjs',
9 | dir: 'lib',
10 | exports: 'named',
11 | sourcemap: true,
12 | strict: true
13 | },
14 | external: ['process'],
15 | plugins: [
16 | typescript(),
17 | resolve(),
18 | commonjs()
19 | ]
20 | };
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "src/**/*.ts"
4 | ],
5 | "exclude": [
6 | "node_modules"
7 | ],
8 | "compilerOptions": {
9 | "baseUrl": ".",
10 | "module": "ES2020",
11 | "moduleResolution": "Node",
12 | "outDir": "lib/",
13 | "declaration": true,
14 | "allowJs": true,
15 | "lib": ["ES2017", "DOM"],
16 | "target": "ES2015",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "allowSyntheticDefaultImports": true,
20 | "skipLibCheck": true,
21 | "strictBindCallApply": true,
22 | "strictFunctionTypes": true,
23 | "strictNullChecks": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "loggers-js",
3 | "version": "1.0.0",
4 | "main": "lib/loggers.js",
5 | "types": "lib/loggers.d.ts",
6 | "license": "MIT",
7 | "files": [
8 | "lib/",
9 | "README.md"
10 | ],
11 | "scripts": {
12 | "prepublishOnly": "tsc",
13 | "build": "rollup -c"
14 | },
15 | "devDependencies": {
16 | "@rollup/plugin-commonjs": "^21.0.0",
17 | "@rollup/plugin-node-resolve": "^13.0.5",
18 | "@rollup/plugin-typescript": "^8.2.5",
19 | "@types/node": "^16.10.2",
20 | "rollup": "^2.58.0",
21 | "tslib": "^2.3.1",
22 | "typescript": "^4.4.3"
23 | },
24 | "dependencies": {
25 | "lodash": "^4.17.21",
26 | "underscore.string": "^3.3.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 Wolfram Research Inc.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/docs/Development.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | Please read the [Contributing Agreement](../CONTRIBUTING.md) first. By contributing to this repository, you agree to the licensing terms herein.
4 |
5 | To install all required dependencies for development of this library, run:
6 |
7 | yarn install
8 |
9 | ## Test
10 |
11 | To run the tests:
12 |
13 | yarn test
14 |
15 | ## Releasing a new version
16 |
17 | To release a new version, log in to npm using
18 |
19 | yarn login
20 |
21 | as an owner of this package.
22 |
23 | Check out the `master` branch and make sure there are no uncommitted changes:
24 |
25 | git checkout master
26 |
27 | Then run
28 |
29 | yarn publish
30 |
31 | which asks for the new package version, updates `package.json` accordingly, runs a build, creates a Git tag, and publishes the package.
32 |
33 | If publishing fails due to missing authentication even though you have run `yarn login`, you might have to delete `~/.npmrc` and log in again (see [this Yarn issue](https://github.com/yarnpkg/yarn/issues/4709)).
34 |
35 | If [two-factor authentication](https://docs.npmjs.com/configuring-two-factor-authentication) is enabled for your account, you will be asked for a one-time password during the publishing process.
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Logger.js
2 |
3 | Just another simple JavaScript logging framework.
4 |
5 | Supports different named loggers (which can be enabled or disabled individually) and different logging levels.
6 |
7 | Loggers can be configured through a global variable `LOGGERS`, through a URL query parameter `?loggers={name}={loglevel}`, or programmaticaly.
8 |
9 | ## Installation
10 |
11 | Assuming you are using a package manager such as [npm](https://www.npmjs.com/get-npm) or [Yarn](https://yarnpkg.com/en/), just install this package from the npm repository:
12 |
13 | npm install loggers-js
14 |
15 | Then you can import `getLogger` in your JavaScript code:
16 |
17 | import {getLogger} from 'loggers-js';
18 |
19 | ## Example
20 |
21 | const logger = getLogger('mylogger');
22 | logger.enable();
23 | logger.setLevel('info');
24 | logger.info('Hello world!');
25 |
26 | ## Contributing
27 |
28 | Everyone is welcome to contribute. Please read the [Contributing agreement](CONTRIBUTING.md) and the [Development guide](./docs/Development.md) for more information, including how to run the tests.
29 |
30 | ## Versioning
31 |
32 | We use [semantic versioning](https://semver.org/) for this library and its API.
33 |
34 | See the [changelog](CHANGELOG.md) for details about the changes in each release.
35 |
--------------------------------------------------------------------------------
/src/globals.ts:
--------------------------------------------------------------------------------
1 | function emptyFunction() {}
2 |
3 | let globalScope;
4 | let hasWindow = false;
5 | if (typeof window !== 'undefined') {
6 | globalScope = window;
7 | hasWindow = true;
8 | } else { // @ts-ignore
9 | if (typeof global !== 'undefined') {
10 | // @ts-ignore
11 | globalScope = global;
12 | } else if (typeof self !== 'undefined') {
13 | globalScope = self;
14 | } else {
15 | // cf. http://www.2ality.com/2014/05/this.html
16 | // and http://speakingjs.com/es5/ch23.html#_indirect_eval_evaluates_in_global_scope
17 | globalScope = eval.call(null, 'this'); // eslint-disable-line no-eval
18 | }
19 | }
20 | // Assign to a constant to avoid exporting a mutable variable (which ESLint doesn't like).
21 | const globalScopeConst = globalScope;
22 |
23 | export default globalScopeConst;
24 |
25 | export const topWindow = hasWindow ? window.top : null;
26 |
27 | export const location = hasWindow
28 | ? window.location
29 | : {
30 | href: '',
31 | protocol: '',
32 | host: '',
33 | hostname: '',
34 | port: '',
35 | pathname: '',
36 | search: '',
37 | hash: '',
38 | username: '',
39 | password: '',
40 | origin: '',
41 | assign: emptyFunction,
42 | reload: emptyFunction,
43 | replace: emptyFunction,
44 | toString: () => ''
45 | };
46 |
47 | export const now =
48 | globalScope && globalScope.performance && globalScope.performance.now ? () => performance.now() : () => Date.now();
49 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Wolfram®
2 |
3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub.
4 |
5 | ## Licensing of Contributions
6 |
7 | By contributing to Wolfram, you agree and affirm that:
8 |
9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and
10 |
11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later.
12 |
13 | Please see [LICENSE](LICENSE) for licensing conditions pertaining
14 | to individual repositories.
15 |
16 |
17 | ## Bug reports
18 |
19 | ### Security Bugs
20 |
21 | Please **DO NOT** file a public issue regarding a security issue.
22 | Rather, send your report privately to security@wolfram.com. Security
23 | reports are appreciated and we will credit you for it. We do not offer
24 | a security bounty, but the forecast in your neighborhood will be cloudy
25 | with a chance of Wolfram schwag!
26 |
27 | ### General Bugs
28 |
29 | Please use the repository issues page to submit general bug issues.
30 |
31 | Please do not duplicate issues.
32 |
33 | Please do send a complete and well-written report to us. Note: **the
34 | thoroughness of your report will positively correlate to our willingness
35 | and ability to address it**.
36 |
37 | When reporting issues, always include:
38 |
39 | * Your version of *Mathematica*® or the Wolfram Language™.
40 | * Your version of the Wolfram Cloud™.
41 | * Your operating system.
42 | * Your web browser, including version number.
43 |
--------------------------------------------------------------------------------
/src/locationParams.ts:
--------------------------------------------------------------------------------
1 | import {topWindow, location} from './globals';
2 |
3 | type Params = Record;
4 | const re = /([^&=]+)=?([^&]*)/g;
5 | function decode(str) {
6 | return decodeURIComponent(str.replace(/\+/g, ' '));
7 | }
8 |
9 | /**
10 | * Parses a parameter string of the form key1=value1&key2=value2&...
11 | */
12 | function parseParams(query: string | null | undefined): Params {
13 | const params: Params = {};
14 | if (query) {
15 | // eslint-disable-next-line no-constant-condition
16 | while (true) {
17 | const e = re.exec(query);
18 | if (!e) {
19 | break;
20 | }
21 | const k = decode(e[1]);
22 | const v = decode(e[2]);
23 | const currentValue = params[k];
24 | if (currentValue !== undefined) {
25 | let arr: string[];
26 | if (!Array.isArray(currentValue)) {
27 | arr = params[k] = [currentValue];
28 | } else {
29 | arr = currentValue;
30 | }
31 | arr.push(v);
32 | } else {
33 | params[k] = v;
34 | }
35 | }
36 | }
37 | return params;
38 | }
39 |
40 | export function getLocationParams(): Params {
41 | let params: Params | null = null;
42 |
43 | if (topWindow) {
44 | try {
45 | params = parseParams(topWindow.location.search.substr(1));
46 | } catch (e) {
47 | // If we can't access the top window's location, fall back to the default
48 | // of using the current window's location.
49 | }
50 | }
51 | if (!params) {
52 | params = parseParams(location.search.substr(1));
53 | }
54 | return params;
55 | }
56 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@rollup/plugin-commonjs@^21.0.0":
6 | version "21.0.0"
7 | resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.0.tgz#b9e4342855ea20b5528f4587b9a90f642196a502"
8 | integrity sha512-XDQimjHl0kNotAV5lLo34XoygaI0teqiKGJ100B3iCU8+15YscJPeqk2KqkqD3NIe1H8ZTUo5lYjUFZyEgASTw==
9 | dependencies:
10 | "@rollup/pluginutils" "^3.1.0"
11 | commondir "^1.0.1"
12 | estree-walker "^2.0.1"
13 | glob "^7.1.6"
14 | is-reference "^1.2.1"
15 | magic-string "^0.25.7"
16 | resolve "^1.17.0"
17 |
18 | "@rollup/plugin-node-resolve@^13.0.5":
19 | version "13.0.5"
20 | resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.5.tgz#016abe58796a4ff544d6beac7818921e3d3777fc"
21 | integrity sha512-mVaw6uxtvuGx/XCI4qBQXsDZJUfyx5vp39iE0J/7Hd6wDhEbjHr6aES7Nr9yWbuE0BY+oKp6N7Bq6jX5NCGNmQ==
22 | dependencies:
23 | "@rollup/pluginutils" "^3.1.0"
24 | "@types/resolve" "1.17.1"
25 | builtin-modules "^3.1.0"
26 | deepmerge "^4.2.2"
27 | is-module "^1.0.0"
28 | resolve "^1.19.0"
29 |
30 | "@rollup/plugin-typescript@^8.2.5":
31 | version "8.2.5"
32 | resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz#e0319761b2b5105615e5a0c371ae05bc2984b7de"
33 | integrity sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==
34 | dependencies:
35 | "@rollup/pluginutils" "^3.1.0"
36 | resolve "^1.17.0"
37 |
38 | "@rollup/pluginutils@^3.1.0":
39 | version "3.1.0"
40 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
41 | integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
42 | dependencies:
43 | "@types/estree" "0.0.39"
44 | estree-walker "^1.0.1"
45 | picomatch "^2.2.2"
46 |
47 | "@types/estree@*":
48 | version "0.0.50"
49 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
50 | integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
51 |
52 | "@types/estree@0.0.39":
53 | version "0.0.39"
54 | resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
55 | integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
56 |
57 | "@types/node@*", "@types/node@^16.10.2":
58 | version "16.10.2"
59 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.2.tgz#5764ca9aa94470adb4e1185fe2e9f19458992b2e"
60 | integrity sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==
61 |
62 | "@types/resolve@1.17.1":
63 | version "1.17.1"
64 | resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
65 | integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
66 | dependencies:
67 | "@types/node" "*"
68 |
69 | balanced-match@^1.0.0:
70 | version "1.0.2"
71 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
72 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
73 |
74 | brace-expansion@^1.1.7:
75 | version "1.1.11"
76 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
77 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
78 | dependencies:
79 | balanced-match "^1.0.0"
80 | concat-map "0.0.1"
81 |
82 | builtin-modules@^3.1.0:
83 | version "3.2.0"
84 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887"
85 | integrity sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==
86 |
87 | commondir@^1.0.1:
88 | version "1.0.1"
89 | resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
90 | integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
91 |
92 | concat-map@0.0.1:
93 | version "0.0.1"
94 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
95 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
96 |
97 | deepmerge@^4.2.2:
98 | version "4.2.2"
99 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
100 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
101 |
102 | estree-walker@^1.0.1:
103 | version "1.0.1"
104 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
105 | integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
106 |
107 | estree-walker@^2.0.1:
108 | version "2.0.2"
109 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
110 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
111 |
112 | fs.realpath@^1.0.0:
113 | version "1.0.0"
114 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
115 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
116 |
117 | fsevents@~2.3.2:
118 | version "2.3.2"
119 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
120 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
121 |
122 | function-bind@^1.1.1:
123 | version "1.1.1"
124 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
125 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
126 |
127 | glob@^7.1.6:
128 | version "7.2.0"
129 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
130 | integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
131 | dependencies:
132 | fs.realpath "^1.0.0"
133 | inflight "^1.0.4"
134 | inherits "2"
135 | minimatch "^3.0.4"
136 | once "^1.3.0"
137 | path-is-absolute "^1.0.0"
138 |
139 | has@^1.0.3:
140 | version "1.0.3"
141 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
142 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
143 | dependencies:
144 | function-bind "^1.1.1"
145 |
146 | inflight@^1.0.4:
147 | version "1.0.6"
148 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
149 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
150 | dependencies:
151 | once "^1.3.0"
152 | wrappy "1"
153 |
154 | inherits@2:
155 | version "2.0.4"
156 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
157 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
158 |
159 | is-core-module@^2.2.0:
160 | version "2.7.0"
161 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3"
162 | integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==
163 | dependencies:
164 | has "^1.0.3"
165 |
166 | is-module@^1.0.0:
167 | version "1.0.0"
168 | resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
169 | integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
170 |
171 | is-reference@^1.2.1:
172 | version "1.2.1"
173 | resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
174 | integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
175 | dependencies:
176 | "@types/estree" "*"
177 |
178 | lodash@^4.17.21:
179 | version "4.17.21"
180 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
181 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
182 |
183 | magic-string@^0.25.7:
184 | version "0.25.7"
185 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
186 | integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
187 | dependencies:
188 | sourcemap-codec "^1.4.4"
189 |
190 | minimatch@^3.0.4:
191 | version "3.0.4"
192 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
193 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
194 | dependencies:
195 | brace-expansion "^1.1.7"
196 |
197 | once@^1.3.0:
198 | version "1.4.0"
199 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
200 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
201 | dependencies:
202 | wrappy "1"
203 |
204 | path-is-absolute@^1.0.0:
205 | version "1.0.1"
206 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
207 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
208 |
209 | path-parse@^1.0.6:
210 | version "1.0.7"
211 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
212 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
213 |
214 | picomatch@^2.2.2:
215 | version "2.3.0"
216 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
217 | integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
218 |
219 | resolve@^1.17.0, resolve@^1.19.0:
220 | version "1.20.0"
221 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
222 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
223 | dependencies:
224 | is-core-module "^2.2.0"
225 | path-parse "^1.0.6"
226 |
227 | rollup@^2.58.0:
228 | version "2.58.0"
229 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.58.0.tgz#a643983365e7bf7f5b7c62a8331b983b7c4c67fb"
230 | integrity sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw==
231 | optionalDependencies:
232 | fsevents "~2.3.2"
233 |
234 | sourcemap-codec@^1.4.4:
235 | version "1.4.8"
236 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
237 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
238 |
239 | sprintf-js@^1.0.3:
240 | version "1.1.2"
241 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
242 | integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
243 |
244 | tslib@^2.3.1:
245 | version "2.3.1"
246 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
247 | integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
248 |
249 | typescript@^4.4.3:
250 | version "4.4.3"
251 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
252 | integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
253 |
254 | underscore.string@^3.3.5:
255 | version "3.3.5"
256 | resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.5.tgz#fc2ad255b8bd309e239cbc5816fd23a9b7ea4023"
257 | integrity sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==
258 | dependencies:
259 | sprintf-js "^1.0.3"
260 | util-deprecate "^1.0.2"
261 |
262 | util-deprecate@^1.0.2:
263 | version "1.0.2"
264 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
265 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
266 |
267 | wrappy@1:
268 | version "1.0.2"
269 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
270 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
271 |
--------------------------------------------------------------------------------
/src/loggers.ts:
--------------------------------------------------------------------------------
1 | import {each, includes, map, toArray, without} from 'lodash';
2 | import {contains as stringContains, trim} from 'underscore.string';
3 | import process from 'process';
4 | import globals, {now} from './globals';
5 |
6 | import {getLocationParams} from './locationParams';
7 |
8 | const IS_SERVER = process.env.IS_SERVER === 'true';
9 | const TESTING = process.env.TESTING === 'true';
10 | const DEBUG = process.env.NODE_ENV !== 'production';
11 |
12 | const levelNames = ['off', 'log', 'error', 'warn', 'info', 'debug', 'trace'];
13 |
14 | const LEVEL_TO_NUMBER = {};
15 | each(levelNames, (name, index) => {
16 | LEVEL_TO_NUMBER[name] = index + 1;
17 | });
18 |
19 | const CONSOLE_METHODS_BY_LEVEL = {
20 | error: console.error,
21 | warn: console.warn,
22 | info: console.info,
23 | debug: console.debug,
24 | log: console.log
25 | };
26 |
27 | function logToStdOut(msg) {
28 | process.stdout.write(`${msg}\n`, 'utf8');
29 | }
30 |
31 | function logToStdErr(msg) {
32 | process.stderr.write(`${msg}\n`, 'utf8');
33 | }
34 |
35 | if (globals.IS_SERVER) {
36 | CONSOLE_METHODS_BY_LEVEL.error = logToStdErr;
37 | CONSOLE_METHODS_BY_LEVEL.warn = logToStdOut;
38 | CONSOLE_METHODS_BY_LEVEL.info = logToStdOut;
39 | CONSOLE_METHODS_BY_LEVEL.debug = logToStdOut;
40 | CONSOLE_METHODS_BY_LEVEL.log = logToStdOut;
41 | }
42 |
43 | export function setOutputFunction(log: (...args: any) => void) {
44 | CONSOLE_METHODS_BY_LEVEL.error = CONSOLE_METHODS_BY_LEVEL.warn = CONSOLE_METHODS_BY_LEVEL.info = CONSOLE_METHODS_BY_LEVEL.debug = CONSOLE_METHODS_BY_LEVEL.log = log;
45 |
46 | // Also change the actual console methods, so that e.g. React warnings don't write
47 | // to the console directly (which can mess up the StdInOutCommunication between Java and JS).
48 | // Keep the original methods as "original*" properties just in case.
49 | const consoleChanged = console as any;
50 | consoleChanged.originalError = consoleChanged.originalError || console.error;
51 | consoleChanged.originalWarn = consoleChanged.originalWarn || console.warn;
52 | consoleChanged.originalInfo = consoleChanged.originalInfo || console.info;
53 | consoleChanged.originalDebug = consoleChanged.originalDebug || console.debug;
54 | consoleChanged.originalLog = consoleChanged.originalLog || console.log;
55 | console.error = console.warn = console.info = console.debug = console.log = log;
56 | }
57 |
58 | const loggers = {};
59 |
60 | /**
61 | * The global logging level, applied to all loggers.
62 | */
63 | let logLevel;
64 |
65 | function levelToNumber(value) {
66 | if (typeof value === 'string') {
67 | return LEVEL_TO_NUMBER[value];
68 | }
69 | return value;
70 | }
71 |
72 | function levelFromNumber(value) {
73 | return levelNames[value - 1];
74 | }
75 |
76 | if (includes(['LOCAL', 'LOCAL8080', 'DEVEL'], globals.serverCategory)) {
77 | // On devel and localhost, set the logging level to "info".
78 | // Individual loggers still have to be enabled for messages to actually show up.
79 | logLevel = levelToNumber('info');
80 | } else {
81 | // Otherwise, set the global logging level to "off" by default.
82 | logLevel = levelToNumber('off');
83 | }
84 |
85 | /**
86 | * List of loggers which are enabled as soon as they are created.
87 | *
88 | * This can be configured in the deploy properties as js.loggers, from where it gets propagated as a global variable
89 | * jsLoggers in initData.jsp.
90 | *
91 | * Note that, currently, globals.jsLoggers is empty during server-side rendering. To enable loggers and set log levels
92 | * for server-side rendering, modify enableDebugLoggers in javascript/notebook/serverRendering.js.
93 | */
94 | let enabledLoggers: string[] = map((globals.jsLoggers || '').split(','), s => {
95 | return trim(s);
96 | });
97 |
98 | const enabledLoggerLevels = {};
99 |
100 | /**
101 | * Boolean to determine whether to include the timestamp when logging.
102 | *
103 | * This is set by the js.loggers.timestamp deploy property.
104 | *
105 | * The default/fallback value should be true.
106 | */
107 | const timestampEnabled: boolean = (globals.jsLoggersTimestamp || 'true').trim().toLowerCase() !== 'false';
108 |
109 | export function logRenderError(...args) {
110 | if (IS_SERVER) {
111 | const parts = args.map(arg => {
112 | if (arg) {
113 | if (arg.msg) {
114 | return arg.msg;
115 | } else if (arg instanceof Error) {
116 | return `${arg.toString()} @ ${arg.stack}`;
117 | } else {
118 | return arg.toString();
119 | }
120 | } else {
121 | return JSON.stringify(arg);
122 | }
123 | });
124 | CONSOLE_METHODS_BY_LEVEL.warn.call(console, parts.join(' '));
125 | }
126 | }
127 |
128 | type Options = {level?: string | number};
129 |
130 | class Logger {
131 | name: string;
132 | enabled: boolean;
133 | level: number;
134 | pendingAsyncCalls: any;
135 | logLevel: number | null;
136 | indentation: number;
137 |
138 | /**
139 | * Creates a new logger instance.
140 | * @param {string} name
141 | * @param {{level: number|string}} options Additional options for the level.
142 | * Can include a level which will be used for all logs of this logger ("debug" by default).
143 | * @constructor
144 | * @global
145 | */
146 | constructor(name, options: Options = {}) {
147 | this.name = name || '';
148 | this.enabled = includes(enabledLoggers, name);
149 | this.level = levelToNumber(options.level || 'log');
150 | this.pendingAsyncCalls = [];
151 | this.logLevel = enabledLoggerLevels[name] || null;
152 | this.indentation = 0;
153 | }
154 |
155 | /**
156 | * Enables this logger.
157 | * @memberof Logger#
158 | */
159 | enable(level = null) {
160 | this.enabled = true;
161 | if (level !== null) {
162 | this.setLogLevel(level);
163 | }
164 | }
165 |
166 | /**
167 | * Disables this logger.
168 | * @memberof Logger#
169 | */
170 | disable() {
171 | this.enabled = false;
172 | }
173 |
174 | isEnabled() {
175 | return this.enabled;
176 | }
177 |
178 | hasLevel(level) {
179 | const levelNumber = levelToNumber(level);
180 | const effectiveLogLevel = this.logLevel !== null ? this.logLevel : logLevel;
181 | return this.enabled && effectiveLogLevel >= levelNumber;
182 | }
183 |
184 | /**
185 | * Logs a message, if this logger is enabled and an appropriate logging level is set.
186 | * Works the same way as console.log.
187 | * @param {number|string} level Level at which to log the message.
188 | * It will only actually be printed if the global logging level is set to the same level or higher.
189 | * @param {...*} args Arbitrary number of arguments to log.
190 | * Can be strings or any other objects.
191 | * @memberof Logger#
192 | *
193 | * TODO: Make multiple args work correctly when server-side rendering. Currently, `logger.log('myObj:', {val: 1});`
194 | * produces `myObj:` in ServerRendering.log. When fixing that, make sure styling arguments (such as
195 | * `logger.log('%cfoo', 'color: red');`) are handled correctly.
196 | */
197 | logLeveled(level, ...args) {
198 | if (this.enabled) {
199 | const levelNumber = levelToNumber(level);
200 | const effectiveLogLevel = this.logLevel !== null ? this.logLevel : logLevel;
201 | if (effectiveLogLevel >= levelNumber) {
202 | // Determine the console method to use depending on the logging level.
203 | const method =
204 | CONSOLE_METHODS_BY_LEVEL[levelFromNumber(levelNumber)] ||
205 | CONSOLE_METHODS_BY_LEVEL.log ||
206 | console.log;
207 | method.apply(console, this._transformArgs(args));
208 | }
209 | }
210 | }
211 |
212 | /**
213 | * Logs at the default level of this logger.
214 | * @param {...*} args Arbitrary number of arguments to log.
215 | * @memberof Logger#
216 | */
217 | log(...args) {
218 | if (DEBUG) {
219 | this.logLeveled(this.level, ...args);
220 | }
221 | }
222 |
223 | /**
224 | * Logs at the error level.
225 | * @param {...*} args Arbitrary number of arguments to log.
226 | * @memberof Logger#
227 | */
228 | error(...args) {
229 | if (DEBUG) {
230 | this.logLeveled('error', ...args);
231 | }
232 | if (IS_SERVER) {
233 | logRenderError(`Error logged [${this.name}]:`, ...args);
234 | }
235 | }
236 | /**
237 | * Logs at the warn level.
238 | * @param {...*} args Arbitrary number of arguments to log.
239 | * @memberof Logger#
240 | */
241 | warn(...args) {
242 | if (DEBUG) {
243 | this.logLeveled('warn', ...args);
244 | }
245 | }
246 | /**
247 | * Logs at the info level.
248 | * @param {...*} args Arbitrary number of arguments to log.
249 | * @memberof Logger#
250 | */
251 | info(...args) {
252 | if (DEBUG) {
253 | this.logLeveled('info', ...args);
254 | }
255 | }
256 | /**
257 | * Logs at the debug level.
258 | * @param {...*} args Arbitrary number of arguments to log.
259 | * @memberof Logger#
260 | */
261 | debug(...args) {
262 | if (DEBUG) {
263 | this.logLeveled('debug', ...args);
264 | }
265 | }
266 |
267 | /**
268 | * Outputs a stack trace, if the logging level is "trace".
269 | * @memberof Logger#
270 | */
271 | trace() {
272 | if (this.enabled && logLevel >= levelToNumber('trace')) {
273 | console.trace();
274 | }
275 | }
276 |
277 | traceAsyncCall(data) {
278 | const effectiveLogLevel = this.logLevel !== null ? this.logLevel : logLevel;
279 | if (this.enabled && effectiveLogLevel >= levelToNumber('trace')) {
280 | const token = {data, time: now()};
281 | this.pendingAsyncCalls.push(token);
282 | return token;
283 | }
284 | return null;
285 | }
286 |
287 | traceAsyncCallEnd(token) {
288 | if (token) {
289 | const a = this.pendingAsyncCalls;
290 | let i;
291 | for (i = a.length - 1; i >= 0; --i) {
292 | if (a[i] === token) {
293 | a.splice(i, 1);
294 | break;
295 | }
296 | }
297 | }
298 | }
299 |
300 | beginBlock() {
301 | ++this.indentation;
302 | }
303 | endBlock() {
304 | --this.indentation;
305 | }
306 |
307 | group(...args) {
308 | if (this.enabled) {
309 | console.group(...this._transformArgs(args));
310 | }
311 | }
312 | groupCollapsed(...args) {
313 | if (this.enabled) {
314 | console.groupCollapsed(...this._transformArgs(args));
315 | }
316 | }
317 | groupEnd() {
318 | if (this.enabled) {
319 | console.groupEnd();
320 | }
321 | }
322 |
323 | // TODO: Make time/timeEnd work with ServerRendering.log. (Currently, they just output `%s: %sms`.) It probably
324 | // doesn't make sense to just disable them, since they are still useful when using a remote debugger (such as
325 | // chrome://inspect).
326 | time(...args) {
327 | if (this.enabled) {
328 | console.time(...args);
329 | }
330 | }
331 | timeEnd(...args) {
332 | if (this.enabled) {
333 | console.timeEnd(...args);
334 | }
335 | }
336 |
337 | setLogLevel(level) {
338 | this.logLevel = levelToNumber(level);
339 | }
340 |
341 | _transformArgs(args) {
342 | if (!timestampEnabled) {
343 | return args;
344 | }
345 |
346 | const result = toArray(args);
347 | const date = new Date();
348 | let prefix = date.toISOString(); // 2011-10-05T14:48:00.000Z
349 | if (this.name) {
350 | prefix += ` [${this.name}]`;
351 | }
352 | for (let i = 0; i < this.indentation; ++i) {
353 | prefix += ' ';
354 | }
355 | if (result && result.length) {
356 | result[0] = `${prefix} ${result[0]}`;
357 | }
358 |
359 | return map(result, item => {
360 | if (item && item.isExpr) {
361 | return item.toString();
362 | } else {
363 | return item;
364 | }
365 | });
366 | }
367 | }
368 |
369 | const params = getLocationParams();
370 | const loggersSetting = params.loggers || globals.LOGGERS;
371 | if (loggersSetting) {
372 | each(loggersSetting.split(','), logger => {
373 | let level = null;
374 | let loggerName = logger;
375 | if (stringContains(logger, '=')) {
376 | const parts = logger.split('=');
377 | loggerName = parts[0];
378 | level = parts[1];
379 | }
380 | if (loggerName.startsWith('!')) {
381 | enabledLoggers = without(enabledLoggers, loggerName.substr(1));
382 | } else {
383 | enabledLoggers.push(loggerName);
384 | enabledLoggerLevels[loggerName] = levelToNumber(level);
385 | }
386 | });
387 | }
388 | if (params.loglevel) {
389 | logLevel = levelToNumber(params.loglevel);
390 | }
391 |
392 | /**
393 | * Initializes a logger.
394 | * @param {string} name
395 | * @param {?{level: number}} options
396 | * @returns {Logger}
397 | */
398 | export function get(name: string, options: Options = {}) {
399 | let logger = loggers[name];
400 | if (!logger) {
401 | logger = loggers[name] = new Logger(name, options || {});
402 | }
403 | return logger;
404 | }
405 |
406 | /**
407 | * Alias for `get`. (Deprecated. Use `get` instead.)
408 | */
409 | export function create(name: string, options: Options = {}) {
410 | return get(name, options);
411 | }
412 |
413 | export function getLogger(name: string, options: Options = {}) {
414 | return get(name, options);
415 | }
416 |
417 | /**
418 | * Set the logging level, which affects all loggers.
419 | * Possible values are "off", "log", "error", "warn", "info", "debug", "trace".
420 | * @param {number|string} value
421 | * @alias logger.setLevel
422 | */
423 | export function setLevel(value) {
424 | logLevel = levelToNumber(value);
425 | }
426 |
427 | export function getLevel() {
428 | return logLevel;
429 | }
430 |
431 | /**
432 | * Overrides console.log with the input function. Returns a function that should be called to restore console.log to its
433 | * previous definition.
434 | *
435 | * Note that console.log actually can be used inside customLogFunc.
436 | *
437 | * This should rarely be used. One example use case: overriding the console.log statements in a compiled asm.js module.
438 | */
439 | export function overrideConsoleLog(customLogFunc: (...args: any) => void): () => void {
440 | // Determine the object that contains console. An if-statement is used because IS_SERVER wasn't getting set when
441 | // using a ternary operator for some reason.
442 | let consoleParent;
443 | if (IS_SERVER || TESTING) {
444 | consoleParent = global;
445 | } else {
446 | consoleParent = window;
447 | }
448 |
449 | const originalConsoleLog = consoleParent.console.log;
450 |
451 | function restoreConsoleLog() {
452 | consoleParent.console.log = originalConsoleLog;
453 | }
454 |
455 | function newConsoleLog(...args: any) {
456 | restoreConsoleLog();
457 | customLogFunc(...args);
458 | consoleParent.console.log = newConsoleLog;
459 | }
460 |
461 | consoleParent.console.log = newConsoleLog;
462 | return restoreConsoleLog;
463 | }
464 |
465 | /**
466 | * Loggers interface exposed for debugging purposes.
467 | */
468 | globals._loggers = loggers;
469 |
--------------------------------------------------------------------------------