├── .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 | --------------------------------------------------------------------------------