Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e177c58312 | ||
|
|
0f05fdae7b | ||
|
|
0d2f45fe31 | ||
|
|
74465875cb | ||
|
|
182d41ba8e | ||
|
|
74409928f6 | ||
|
|
f30731f3b8 | ||
|
|
23c56e8d1c | ||
|
|
c2dc339a38 | ||
|
|
bb5442f808 | ||
|
|
49954b75a6 | ||
|
|
a5e2df99ce | ||
|
|
5a05a41e59 | ||
|
|
163a24646a | ||
|
|
c3368a3595 |
21 changed files with 2198 additions and 1517 deletions
31
README.md
31
README.md
|
|
@ -1,4 +1,33 @@
|
||||||
|
# esm-logging
|
||||||
|
|
||||||
|
> This README is a stub. Working on it. Currently stabilizing the build
|
||||||
|
environment after that I'll make it nice around here.
|
||||||
|
|
||||||
* [Logging Cookbook](doc/logging-cookbook.md)
|
A quasi-port of the Python standard library logging module to ECMAScript.
|
||||||
|
|
||||||
|
# Why?
|
||||||
|
|
||||||
|
First of, because logging is important. It is important for debugging purposes,
|
||||||
|
leading to faster and more resilient development, for traceability leading to
|
||||||
|
better security. Most logging libraries I've discovered didn't satisfy me,
|
||||||
|
introduced weird concepts and all in all just weren't great. Other programming
|
||||||
|
language ecosystems offer way nicer logging facilities. Take Rust for example,
|
||||||
|
or... Python! Python has PEP, giving it a very structured approach towards
|
||||||
|
implementing new features and that's also how its logging facilities came to be
|
||||||
|
([PEP 282](https://peps.python.org/pep-0282/)). Python's logging facilities are
|
||||||
|
implemented by the [logging]() module, which is part of the standard library and
|
||||||
|
has been since 2002. It was originally authored by Vinay Sajip
|
||||||
|
|
||||||
|
# Roadmap
|
||||||
|
|
||||||
|
- do a quasi-port of the logging module with minimal amount of adaption
|
||||||
|
- add documentation
|
||||||
|
- add support for asynchronous calls
|
||||||
|
- implement Open Cybersecurity Framework (OCSF) formatter
|
||||||
|
- implement (Browser) local storage handler as a replacement for file handler
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
For the time being, please check out my [CI
|
||||||
|
service](https://bitbucket.org/byteb4rb1e/esm-logging/pipelines), for an idea on
|
||||||
|
how to build this.
|
||||||
|
|
|
||||||
75
TODO
Normal file
75
TODO
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# TODO List for esm-logging
|
||||||
|
|
||||||
|
This is a poor-man's issue tracker. I am not primarily a GitHub user so don't
|
||||||
|
want to commit to their issue tracking feature, but my primary SVC service
|
||||||
|
provider (Bitbucket) only offers paid integration into their issue tracker
|
||||||
|
(Jira). I don't have the time (and patience) at the moment to analyze the best
|
||||||
|
approach, so this file will have to suffice.
|
||||||
|
|
||||||
|
It's a very simple concept: Track any issues (features, bugfixes, hotfixes) in
|
||||||
|
here, assign a sequential number to it and use that number when branching.
|
||||||
|
|
||||||
|
I will try to develop a format so that I can parse the file later on, should I
|
||||||
|
decide to migrate to a real issue tracker. It's probably going to be Bugzilla,
|
||||||
|
but for that my html-theme-ref project needs to stabilize first.
|
||||||
|
|
||||||
|
## Format Specification
|
||||||
|
|
||||||
|
The file uses Markdown conventions for formatting headers and other text block
|
||||||
|
entitities, but SHOULD NOT be considered a Markdown file. That's why it has no
|
||||||
|
definitive file extension.
|
||||||
|
|
||||||
|
Each issue entry follows a structured format for easier parsing and future
|
||||||
|
migration. Issues MUST be **appended** to this file and never moved, to
|
||||||
|
preserve Git diffing.
|
||||||
|
|
||||||
|
### Issue Format
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
ID: [ISSUE-NUMBER]
|
||||||
|
Type: [feature/bugfix/hotfix]
|
||||||
|
Title: [Short title]
|
||||||
|
Status: [open/in-progress/done]
|
||||||
|
Priority: [low/medium/high]
|
||||||
|
Created: [YYYY-MM-DD]
|
||||||
|
Description: [Detailed explanation]
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
- ISSUE-NUMBERs must be sequential
|
||||||
|
- truncation of description must be indentended so that every line starts at the
|
||||||
|
same column
|
||||||
|
- issues must be started with two LF
|
||||||
|
- issues must be terminated with two LF, then `---`
|
||||||
|
- issues may have a free-text field (epilog), which must be started with two LF.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
ID: 1
|
||||||
|
Type: feature
|
||||||
|
Title: string formatting utilities
|
||||||
|
Status: in-progress
|
||||||
|
Priority: high
|
||||||
|
Created: 2025-05-01
|
||||||
|
Description: implement utilities for formatting strings. The formatting should
|
||||||
|
be inspired by Python 3K PEP 3101 in addition to their standard
|
||||||
|
library utilities starting from ver. 3.7. Optimizations should
|
||||||
|
focus on V8 support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
ID: 2
|
||||||
|
Type: feature
|
||||||
|
Title: describe development workflow in CONTRIBUTING.md
|
||||||
|
Status: open
|
||||||
|
Priority: medium
|
||||||
|
Created: 2025-05-01
|
||||||
|
Description: It's a good idea to describe the development workflow, including
|
||||||
|
branching strategies earlier on, so that if someone is interested
|
||||||
|
in forking, they can pick up right away. It's not meant for
|
||||||
|
contributions though. I'm currently not interested in external
|
||||||
|
contributions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@ -26,6 +26,7 @@ definitions:
|
||||||
- build/debug/**/*
|
- build/debug/**/*
|
||||||
- build/debug/*
|
- build/debug/*
|
||||||
script:
|
script:
|
||||||
|
- make clean
|
||||||
- make build/debug CI=1
|
- make build/debug CI=1
|
||||||
- step: &build-release
|
- step: &build-release
|
||||||
name: Build (Release)
|
name: Build (Release)
|
||||||
|
|
@ -35,6 +36,7 @@ definitions:
|
||||||
- build/release/**/*
|
- build/release/**/*
|
||||||
- build/release/*
|
- build/release/*
|
||||||
script:
|
script:
|
||||||
|
- make clean
|
||||||
- make build/release CI=1
|
- make build/release CI=1
|
||||||
- step: &build-doc
|
- step: &build-doc
|
||||||
name: Build (Doc)
|
name: Build (Doc)
|
||||||
|
|
@ -44,6 +46,7 @@ definitions:
|
||||||
- build/doc/**/*
|
- build/doc/**/*
|
||||||
- build/doc/*
|
- build/doc/*
|
||||||
script:
|
script:
|
||||||
|
- make clean
|
||||||
- make build/doc CI=1
|
- make build/doc CI=1
|
||||||
- step: &dist
|
- step: &dist
|
||||||
name: Package
|
name: Package
|
||||||
|
|
@ -52,6 +55,7 @@ definitions:
|
||||||
artifacts:
|
artifacts:
|
||||||
- dist/*
|
- dist/*
|
||||||
script:
|
script:
|
||||||
|
- rm -rvf test-reports/
|
||||||
- make dist CI=1
|
- make dist CI=1
|
||||||
pipelines:
|
pipelines:
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
609
package-lock.json
generated
609
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "administratrix/esm-logging",
|
"name": "@administratrix/esm-logging",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "administratrix/esm-logging",
|
"name": "@administratrix/esm-logging",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"ts-jest": "^29.3.2",
|
"ts-jest": "^29.3.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typedoc": "^0.27.9",
|
"typedoc": "^0.28.3",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -543,15 +543,64 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@gerrit0/mini-shiki": {
|
"node_modules/@gerrit0/mini-shiki": {
|
||||||
"version": "1.27.2",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz",
|
"resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.3.0.tgz",
|
||||||
"integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==",
|
"integrity": "sha512-frvArO0+s5Viq68uSod5SieLPVM2cLpXoQ1e07lURwgADXpL/MOypM7jPz9otks0g2DIe2YedDAeVrDyYJZRxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/engine-oniguruma": "^1.27.2",
|
"@shikijs/engine-oniguruma": "^3.3.0",
|
||||||
"@shikijs/types": "^1.27.2",
|
"@shikijs/langs": "^3.3.0",
|
||||||
"@shikijs/vscode-textmate": "^10.0.1"
|
"@shikijs/themes": "^3.3.0",
|
||||||
|
"@shikijs/types": "^3.3.0",
|
||||||
|
"@shikijs/vscode-textmate": "^10.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/cliui": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^5.1.2",
|
||||||
|
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||||
|
"strip-ansi": "^7.0.1",
|
||||||
|
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||||
|
"wrap-ansi": "^8.1.0",
|
||||||
|
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@istanbuljs/load-nyc-config": {
|
"node_modules/@istanbuljs/load-nyc-config": {
|
||||||
|
|
@ -927,24 +976,44 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/engine-oniguruma": {
|
"node_modules/@shikijs/engine-oniguruma": {
|
||||||
"version": "1.29.2",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.3.0.tgz",
|
||||||
"integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==",
|
"integrity": "sha512-l0vIw+GxeNU7uGnsu6B+Crpeqf+WTQ2Va71cHb5ZYWEVEPdfYwY5kXwYqRJwHrxz9WH+pjSpXQz+TJgAsrkA5A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/types": "1.29.2",
|
"@shikijs/types": "3.3.0",
|
||||||
"@shikijs/vscode-textmate": "^10.0.1"
|
"@shikijs/vscode-textmate": "^10.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/langs": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-zt6Kf/7XpBQKSI9eqku+arLkAcDQ3NHJO6zFjiChI8w0Oz6Jjjay7pToottjQGjSDCFk++R85643WbyINcuL+g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@shikijs/themes": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-tXeCvLXBnqq34B0YZUEaAD1lD4lmN6TOHAhnHacj4Owh7Ptb/rf5XCDeROZt2rEOk5yuka3OOW2zLqClV7/SOg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@shikijs/types": "3.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@shikijs/types": {
|
"node_modules/@shikijs/types": {
|
||||||
"version": "1.29.2",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.3.0.tgz",
|
||||||
"integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==",
|
"integrity": "sha512-KPCGnHG6k06QG/2pnYGbFtFvpVJmC3uIpXrAiPrawETifujPBv0Se2oUxm5qYgjCvGJS9InKvjytOdN+bGuX+Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@shikijs/vscode-textmate": "^10.0.1",
|
"@shikijs/vscode-textmate": "^10.0.2",
|
||||||
"@types/hast": "^3.0.4"
|
"@types/hast": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1384,14 +1453,13 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0",
|
"balanced-match": "^1.0.0"
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
|
|
@ -1576,6 +1644,46 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/co": {
|
"node_modules/co": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||||
|
|
@ -1745,6 +1853,13 @@
|
||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eastasianwidth": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ejs": {
|
"node_modules/ejs": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||||
|
|
@ -1782,9 +1897,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|
@ -1869,6 +1984,13 @@
|
||||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/execa/node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/exit": {
|
"node_modules/exit": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||||
|
|
@ -1922,16 +2044,6 @@
|
||||||
"minimatch": "^5.0.1"
|
"minimatch": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/filelist/node_modules/brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/filelist/node_modules/minimatch": {
|
"node_modules/filelist/node_modules/minimatch": {
|
||||||
"version": "5.1.6",
|
"version": "5.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
|
|
@ -1972,12 +2084,22 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs.realpath": {
|
"node_modules/foreground-child": {
|
||||||
"version": "1.0.0",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-spawn": "^7.0.6",
|
||||||
|
"signal-exit": "^4.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
|
|
@ -2048,22 +2170,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "11.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
|
||||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"foreground-child": "^3.1.0",
|
||||||
"inflight": "^1.0.4",
|
"jackspeak": "^4.0.1",
|
||||||
"inherits": "2",
|
"minimatch": "^10.0.0",
|
||||||
"minimatch": "^3.1.1",
|
"minipass": "^7.1.2",
|
||||||
"once": "^1.3.0",
|
"package-json-from-dist": "^1.0.0",
|
||||||
"path-is-absolute": "^1.0.0"
|
"path-scurry": "^2.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "20 || >=22"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
|
@ -2156,25 +2280,6 @@
|
||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/inflight": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
|
||||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
|
||||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/is-arrayish": {
|
"node_modules/is-arrayish": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||||
|
|
@ -2332,6 +2437,22 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jackspeak": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jake": {
|
"node_modules/jake": {
|
||||||
"version": "10.9.2",
|
"version": "10.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz",
|
||||||
|
|
@ -2351,6 +2472,30 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jake/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jake/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest": {
|
"node_modules/jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||||
|
|
@ -3201,16 +3346,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "10.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^2.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minipass": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp": {
|
"node_modules/mkdirp": {
|
||||||
|
|
@ -3277,16 +3435,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/once": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
|
||||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/onetime": {
|
"node_modules/onetime": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||||
|
|
@ -3358,6 +3506,13 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/package-json-from-dist": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0"
|
||||||
|
},
|
||||||
"node_modules/parse-json": {
|
"node_modules/parse-json": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||||
|
|
@ -3387,16 +3542,6 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-is-absolute": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
|
|
@ -3414,6 +3559,33 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/path-scurry": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"minipass": "^7.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||||
|
"version": "11.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
||||||
|
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "20 || >=22"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
|
@ -3631,11 +3803,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "3.0.7",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/sisteransi": {
|
"node_modules/sisteransi": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
|
|
@ -3710,6 +3888,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eastasianwidth": "^0.2.0",
|
||||||
|
"emoji-regex": "^9.2.2",
|
||||||
|
"strip-ansi": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width-cjs": {
|
||||||
|
"name": "string-width",
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
|
@ -3724,6 +3921,42 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/string-width/node_modules/ansi-regex": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width/node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
|
@ -3737,6 +3970,20 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strip-ansi-cjs": {
|
||||||
|
"name": "strip-ansi",
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-bom": {
|
"node_modules/strip-bom": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
|
||||||
|
|
@ -3811,6 +4058,30 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/test-exclude/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tmpl": {
|
"node_modules/tmpl": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||||
|
|
@ -3975,38 +4246,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typedoc": {
|
"node_modules/typedoc": {
|
||||||
"version": "0.27.9",
|
"version": "0.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz",
|
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.3.tgz",
|
||||||
"integrity": "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw==",
|
"integrity": "sha512-5svOCTfXvVSh6zbZKSQluZhR8yN2tKpTeHZxlmWpE6N5vc3R8k/jhg9nnD6n5tN9/ObuQTojkONrOxFdUFUG9w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gerrit0/mini-shiki": "^1.24.0",
|
"@gerrit0/mini-shiki": "^3.2.2",
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"minimatch": "^9.0.5",
|
"minimatch": "^9.0.5",
|
||||||
"yaml": "^2.6.1"
|
"yaml": "^2.7.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"typedoc": "bin/typedoc"
|
"typedoc": "bin/typedoc"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18",
|
||||||
|
"pnpm": ">= 10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
|
"typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typedoc/node_modules/brace-expansion": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typedoc/node_modules/minimatch": {
|
"node_modules/typedoc/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
|
@ -4141,6 +4403,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^6.1.0",
|
||||||
|
"string-width": "^5.0.1",
|
||||||
|
"strip-ansi": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi-cjs": {
|
||||||
|
"name": "wrap-ansi",
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
|
@ -4158,12 +4439,69 @@
|
||||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||||
"version": "1.0.2",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi/node_modules/ansi-regex": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi/node_modules/ansi-styles": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi/node_modules/strip-ansi": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/write-file-atomic": {
|
"node_modules/write-file-atomic": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
|
|
@ -4179,6 +4517,13 @@
|
||||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/write-file-atomic/node_modules/signal-exit": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/xml": {
|
"node_modules/xml": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||||
|
|
@ -4245,6 +4590,28 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yn": {
|
"node_modules/yn": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "administratrix/esm-logging",
|
"name": "@administratrix/esm-logging",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "port of Python standard library logging module",
|
"description": "port of Python standard library logging module",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
|
@ -24,7 +24,12 @@
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"ts-jest": "^29.3.2",
|
"ts-jest": "^29.3.2",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typedoc": "^0.27.9",
|
"typedoc": "^0.28.3",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"jest": {
|
||||||
|
"glob": "^11.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
202
src/config.ts
Normal file
202
src/config.ts
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
import { MANAGER } from './manager';
|
||||||
|
import { ValueError } from './helper/error';
|
||||||
|
import { STYLES, Formatter } from './formatter';
|
||||||
|
import { StreamHandler, FileHandler, Handler } from './handler';
|
||||||
|
import { LogLevel } from './log-level';
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Configuration classes and functions
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* options for basic configuration of logging module
|
||||||
|
*/
|
||||||
|
export interface BasicConfigOptions {
|
||||||
|
/*
|
||||||
|
* Specifies that a FileHandler be created, using the specified filename,
|
||||||
|
* rather than a StreamHandler.
|
||||||
|
*/
|
||||||
|
filename?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the mode to open the file, if filename is specified (if
|
||||||
|
* filemode is unspecified, it defaults to 'a')
|
||||||
|
*/
|
||||||
|
filemode?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the specified format string for the handler.
|
||||||
|
*/
|
||||||
|
format?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the specified date/time format.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
datefmt?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a format string is specified, use this to specify the type of format
|
||||||
|
* string (possible values '%', '{', '$', for %-formatting,
|
||||||
|
* :meth:`str.format` and :class:`string.Template`- defaults to '%').
|
||||||
|
*
|
||||||
|
* TODO: switch to enum
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the root logger level to the specified level.
|
||||||
|
*/
|
||||||
|
level?: LogLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the specified stream to initialize the StreamHandler. Note that this
|
||||||
|
* argument is incompatible with 'filename' - if both are present, 'stream'
|
||||||
|
* is ignored.
|
||||||
|
*
|
||||||
|
* TODO:
|
||||||
|
*/
|
||||||
|
stream?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified, this should be an iterable of already created handlers,
|
||||||
|
* which will be added to the root logger. Any handler in the list which
|
||||||
|
* does not have a formatter assigned will be assigned the formatter created
|
||||||
|
* in this function.
|
||||||
|
*/
|
||||||
|
handlers?: Handler[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this keyword is specified as true, any existing handlers attached to
|
||||||
|
* the root logger are removed and closed, before carrying out the
|
||||||
|
* configuration as specified by the other arguments.
|
||||||
|
*/
|
||||||
|
force?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified together with a filename, this encoding is passed to the
|
||||||
|
* created FileHandler, causing it to be used when the file is opened.
|
||||||
|
*/
|
||||||
|
encoding?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If specified together with a filename, this value is
|
||||||
|
* passed to the created FileHandler, causing it to be used
|
||||||
|
* when the file is opened in text mode. If not specified,
|
||||||
|
* the default value is `backslashreplace`.
|
||||||
|
*/
|
||||||
|
errors?: string|null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do basic configuration for the logging system.
|
||||||
|
*
|
||||||
|
* This function does nothing if the root logger already has handlers
|
||||||
|
* configured, unless the keyword argument *force* is set to ``True``.
|
||||||
|
* It is a convenience method intended for use by simple scripts
|
||||||
|
* to do one-shot configuration of the logging package.
|
||||||
|
*
|
||||||
|
* The default behaviour is to create a StreamHandler which writes to
|
||||||
|
* sys.stderr, set a formatter using the BASIC_FORMAT format string, and
|
||||||
|
* add the handler to the root logger.
|
||||||
|
*
|
||||||
|
* A number of optional keyword arguments may be specified, which can alter
|
||||||
|
* the default behaviour.
|
||||||
|
*
|
||||||
|
* Note that you could specify a stream created using open(filename, mode)
|
||||||
|
* rather than passing the filename and mode in. However, it should be
|
||||||
|
* remembered that StreamHandler does not close its stream (since it may be
|
||||||
|
* using sys.stdout or sys.stderr), whereas FileHandler closes its stream
|
||||||
|
* when the handler is closed.
|
||||||
|
*
|
||||||
|
* TODO: refactor logic, there apparently is some redundancy in the original
|
||||||
|
* code
|
||||||
|
*/
|
||||||
|
export function basicConfig(options: BasicConfigOptions) {
|
||||||
|
const force = options.force ?? false;
|
||||||
|
var encoding = options.encoding ?? undefined;
|
||||||
|
var errors: string|undefined = options.errors ?? 'backslashreplace';
|
||||||
|
var handlers = options.handlers ?? [];
|
||||||
|
const filename = options.filename ?? null;
|
||||||
|
const stream = options.stream ?? null;
|
||||||
|
const filemode = options.filemode ?? 'a';
|
||||||
|
const dateformat = options.filemode ?? null;
|
||||||
|
const style = options.filemode ?? '%';
|
||||||
|
const level = options.level ?? null;
|
||||||
|
|
||||||
|
if (!Object.keys(STYLES).includes(style)) {
|
||||||
|
throw new ValueError(
|
||||||
|
`style must be one of: ${Object.keys(STYLES).join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
for (var i = 0; i < MANAGER.root.handlers.length; i += 1) {
|
||||||
|
let h: Handler = MANAGER.root.handlers[i];
|
||||||
|
MANAGER.root.removeHandler(h);
|
||||||
|
h.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlers.length == 0) {
|
||||||
|
if (handlers === null && stream && filename) {
|
||||||
|
throw new ValueError(
|
||||||
|
"'stream' and 'filename' should not be specified together"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (stream || filename) {
|
||||||
|
throw new ValueError(
|
||||||
|
"'stream' or 'filename' should not be specified together" +
|
||||||
|
"with 'handlers'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handlers === null) {
|
||||||
|
var h: Handler;
|
||||||
|
|
||||||
|
if (filename) {
|
||||||
|
if (filemode.match('b')) { errors = undefined }
|
||||||
|
else { encoding = 'utf-8' }
|
||||||
|
|
||||||
|
h = new FileHandler({
|
||||||
|
filename: filename,
|
||||||
|
filemode: filemode,
|
||||||
|
'encoding': encoding,
|
||||||
|
errors: errors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else { h = new StreamHandler(stream) }
|
||||||
|
|
||||||
|
handlers = [h];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < handlers.length; i += 1) {
|
||||||
|
let h = handlers[i];
|
||||||
|
|
||||||
|
if (h.formatter === null) {
|
||||||
|
h.formatter = new Formatter({
|
||||||
|
fmt: options.format ?? STYLES[style][1],
|
||||||
|
datefmt: dateformat,
|
||||||
|
style: style
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
MANAGER.root.addHandler(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level !== null) { MANAGER.root.setLevel(level) }
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
// runtime interface guard, please let me stay. 🥺
|
||||||
|
// the interface does not allow for additional members, but the
|
||||||
|
// runtime environment has no concept of interfaces. We can stick to
|
||||||
|
// the original implementation
|
||||||
|
const keys = Object.keys(options).join(', ');
|
||||||
|
|
||||||
|
throw new ValueError(`Unrecognised argument(s): ${keys}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/filter.ts
Normal file
114
src/filter.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { LogRecord } from './log-record';
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Filter classes and functions
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export type FilterCallable = (record: LogRecord) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter instances are used to perform arbitrary filtering of LogRecords.
|
||||||
|
*
|
||||||
|
* Loggers and Handlers can optionally use Filter instances to filter records as
|
||||||
|
* desired. The base filter class only allows events which are below a certain
|
||||||
|
* point in the logger hierarchy. For example, a filter initialized with "A.B"
|
||||||
|
* will allow events logged by loggers "A.B", initialized with the empty string,
|
||||||
|
* all events are passed.
|
||||||
|
*/
|
||||||
|
export class Filter {
|
||||||
|
public readonly scope: string;
|
||||||
|
public readonly slen: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize with the name of the logger which ,together with its children,
|
||||||
|
* will have its events allowed through the filter. If no name is specified,
|
||||||
|
* allow every event.
|
||||||
|
*
|
||||||
|
* @param name - name of logging scope
|
||||||
|
*/
|
||||||
|
constructor(scope: string) {
|
||||||
|
this.scope = scope ?? '';
|
||||||
|
this.slen = this.scope.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspect a record, if it should be logged.
|
||||||
|
*
|
||||||
|
* Returns true if the record should be logged, or false otherwise. If
|
||||||
|
* deemed appropriate, the record may be modified in-place.
|
||||||
|
*
|
||||||
|
* @param - scope of log record to inspect
|
||||||
|
* @param - log record to inspect
|
||||||
|
*/
|
||||||
|
filter(record: LogRecord): boolean {
|
||||||
|
if (this.slen == 0 || this.scope == record.scope) { return true }
|
||||||
|
else if (!record.scope.substring(0, this.slen)) { return false }
|
||||||
|
return (record.scope[this.slen] == '.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Filterer {
|
||||||
|
filters: Filter[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the specified filter to this handler.
|
||||||
|
*
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
addFilter(filter: Filter) {
|
||||||
|
if (!this.filters.includes(filter)) { this.filters.push(filter) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified filter from this handler.
|
||||||
|
*
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
removeFilter(filter: Filter) {
|
||||||
|
if (this.filters.includes(filter)) {
|
||||||
|
this.filters.splice(this.filters.indexOf(filter), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a record is loggable by consulting all the filters.
|
||||||
|
*
|
||||||
|
* The default is to allow the record to be logged; any filter can veto this
|
||||||
|
* by returning a false value.
|
||||||
|
* If a filter attached to a handler returns a log record instance, then
|
||||||
|
* that instance is used in place of the original log record in any further
|
||||||
|
* processing of the event by that handler.
|
||||||
|
* If a filter returns any other true value, the original log record is used
|
||||||
|
* in any further processing of the event by that handler.
|
||||||
|
*
|
||||||
|
* If none of the filters return false values, this method returns a log
|
||||||
|
* record.
|
||||||
|
*
|
||||||
|
* If any of the filters return a false value, this method returns a false
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* @param filter
|
||||||
|
*/
|
||||||
|
filter(record: LogRecord): LogRecord|null {
|
||||||
|
|
||||||
|
for (var i = 0; i < this.filters.length; i += 1) {
|
||||||
|
let result: boolean|LogRecord = false;
|
||||||
|
|
||||||
|
let filter = this.filters[i];
|
||||||
|
|
||||||
|
if (typeof (filter as Filter).filter == 'function') {
|
||||||
|
result = (filter as Filter).filter(record)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = (filter as unknown as FilterCallable)(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) { return null }
|
||||||
|
|
||||||
|
if ((result as any) instanceof LogRecord) { record = result as unknown as LogRecord }
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
202
src/formatter.ts
Normal file
202
src/formatter.ts
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
import { MyError, ValueError } from './helper/error';
|
||||||
|
import { LogRecord } from './log-record';
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Formatter classes and functions
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface PercentFormatterStyleOptions {
|
||||||
|
fmt?: string,
|
||||||
|
defaults: {[key: string]: any};
|
||||||
|
}
|
||||||
|
|
||||||
|
class PercentFormatterStyle {
|
||||||
|
public static defaultFormat = '%(message)s';
|
||||||
|
public static asctimeFormat = '%(asctime)s';
|
||||||
|
public static asctimeSearch = '%(asctime)';
|
||||||
|
public static validationPattern =
|
||||||
|
/%\(\w+\)[#0+ -]*(\*|\d+)?(\.(\*|\d+))?[diouxefgcrsa%]/;
|
||||||
|
|
||||||
|
private fmt: string;
|
||||||
|
private defaults: {[key: string]: any};
|
||||||
|
|
||||||
|
constructor(options: PercentFormatterStyleOptions) {
|
||||||
|
this.fmt = options.fmt ?? PercentFormatterStyle.defaultFormat;
|
||||||
|
this.defaults = options.defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
usesTime(): boolean {
|
||||||
|
return this.fmt.match(PercentFormatterStyle.asctimeFormat) ? true : false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the input format, ensure it matches the correct style
|
||||||
|
*/
|
||||||
|
validate() {
|
||||||
|
if (!PercentFormatterStyle.validationPattern.test(this.fmt)) {
|
||||||
|
throw new ValueError(
|
||||||
|
`Invalid format '${this.fmt}' for ` +
|
||||||
|
`'${PercentFormatterStyle.defaultFormat[0]}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _format(record: LogRecord): string {
|
||||||
|
var defaults = this.defaults;
|
||||||
|
var values: {[key: string]: any}|null;
|
||||||
|
if (defaults) { values = {...this.defaults, ...Object.entries(record)} }
|
||||||
|
else { values = Object.entries(record) }
|
||||||
|
//TODO: implement formatting
|
||||||
|
return 'would do some formatting';
|
||||||
|
}
|
||||||
|
|
||||||
|
format(record: LogRecord): string {
|
||||||
|
try {
|
||||||
|
return this._format(record)
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new ValueError(`formatting field not found in record: ${e}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BASIC_FORMAT = '%(level)s:%(name)s:%(message)s';
|
||||||
|
|
||||||
|
export const STYLES: {[key: string]: [{ new(options: PercentFormatterStyleOptions): PercentFormatterStyle}, string]} = {
|
||||||
|
'%': [PercentFormatterStyle, BASIC_FORMAT],
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormatterOptions {
|
||||||
|
fmt?: string
|
||||||
|
datefmt?: any
|
||||||
|
style?: string
|
||||||
|
validate?: boolean
|
||||||
|
defaults?: {[key: string]: any}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatter instances are used to convert a LogRecord to text.
|
||||||
|
*
|
||||||
|
* Formatters need to know how a LogRecord is constructed. They are
|
||||||
|
* responsible for converting a LogRecord to (usually) a string which can
|
||||||
|
* be interpreted by either a human or an external system. The base Formatter
|
||||||
|
* allows a formatting string to be specified. If none is supplied, the
|
||||||
|
* style-dependent default value, "%(message)s", "{message}", or
|
||||||
|
* "${message}", is used.
|
||||||
|
*
|
||||||
|
* The Formatter can be initialized with a format string which makes use of
|
||||||
|
* knowledge of the LogRecord attributes - e.g. the default value mentioned
|
||||||
|
* above makes use of the fact that the user's message and arguments are pre-
|
||||||
|
* formatted into a LogRecord's message attribute. Currently, the useful
|
||||||
|
* attributes in a LogRecord are described by:
|
||||||
|
*
|
||||||
|
* %(name)s Name of the logger (logging channel)
|
||||||
|
* %(levelno)s Numeric logging level for the message (DEBUG, INFO,
|
||||||
|
* WARNING, ERROR, CRITICAL)
|
||||||
|
* %(levelname)s Text logging level for the message ("DEBUG", "INFO",
|
||||||
|
* "WARNING", "ERROR", "CRITICAL")
|
||||||
|
* %(pathname)s Full pathname of the source file where the logging
|
||||||
|
* call was issued (if available)
|
||||||
|
* %(filename)s Filename portion of pathname
|
||||||
|
* %(module)s Module (name portion of filename)
|
||||||
|
* %(lineno)d Source line number where the logging call was issued
|
||||||
|
* (if available)
|
||||||
|
* %(funcName)s Function name
|
||||||
|
* %(created)f Time when the LogRecord was created (time.time_ns() / 1e9
|
||||||
|
* return value)
|
||||||
|
* %(asctime)s Textual time when the LogRecord was created
|
||||||
|
* %(msecs)d Millisecond portion of the creation time
|
||||||
|
* %(relativeCreated)d Time in milliseconds when the LogRecord was created,
|
||||||
|
* relative to the time the logging module was loaded
|
||||||
|
* (typically at application startup time)
|
||||||
|
* %(thread)d Thread ID (if available)
|
||||||
|
* %(threadName)s Thread name (if available)
|
||||||
|
* %(taskName)s Task name (if available)
|
||||||
|
* %(process)d Process ID (if available)
|
||||||
|
* %(message)s The result of record.getMessage(), computed just as
|
||||||
|
* the record is emitted
|
||||||
|
*/
|
||||||
|
export class Formatter {
|
||||||
|
public static defaultTimeFormat = '%Y-%M';
|
||||||
|
public static defaultMsecFormat = '%s,%30d';
|
||||||
|
|
||||||
|
protected style: any;
|
||||||
|
protected fmt: string;
|
||||||
|
protected datefmt: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the formatter with specified format strings.
|
||||||
|
*
|
||||||
|
* Initialize the formatter either with the specified format string, or a
|
||||||
|
* default as described above. Allow for specialized date formatting with
|
||||||
|
* the optional datefmt argument. If datefmt is omitted, you get an
|
||||||
|
* ISO8601-like (or RFC 3339-like) format.
|
||||||
|
*
|
||||||
|
* Use a style parameter of '%', '{' or '$' to specify that you want to
|
||||||
|
* use one of %-formatting, :meth:`str.format` (``{}``) formatting or
|
||||||
|
* :class:`string.Template` formatting in your format string.
|
||||||
|
*/
|
||||||
|
constructor(options?: FormatterOptions) {
|
||||||
|
options = options ?? {};
|
||||||
|
var style = options.style ?? '%';
|
||||||
|
var validate = options.validate ?? true;
|
||||||
|
|
||||||
|
if (!Object.keys(STYLES).includes(style ?? '')) {
|
||||||
|
throw new ValueError(`style must be one of: ${Object.keys(STYLES).join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.style = new STYLES[style][0]({
|
||||||
|
fmt: options.fmt,
|
||||||
|
defaults: options.defaults ?? {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validate) { this.style.validate() }
|
||||||
|
|
||||||
|
this.fmt = this.style.fmt;
|
||||||
|
|
||||||
|
this.datefmt = options.datefmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the creation time of the specified LogRecord as formatted text.
|
||||||
|
*
|
||||||
|
* This method should be called from format() by a formatter which
|
||||||
|
* wants to make use of a formatted time. This method can be overridden
|
||||||
|
* in formatters to provide for any specific requirement, but the
|
||||||
|
* basic behaviour is as follows: if datefmt (a string) is specified,
|
||||||
|
* it is used with time.strftime() to format the creation time of the
|
||||||
|
* record. Otherwise, an ISO8601-like (or RFC 3339-like) format is used.
|
||||||
|
* The resulting string is returned. This function uses a user-configurable
|
||||||
|
* function to convert the creation time to a tuple. By default,
|
||||||
|
* time.localtime() is used; to change this for a particular formatter
|
||||||
|
* instance, set the 'converter' attribute to a function with the same
|
||||||
|
* signature as time.localtime() or time.gmtime(). To change it for all
|
||||||
|
* formatters, for example if you want all logging times to be shown in GMT,
|
||||||
|
* set the 'converter' attribute in the Formatter class.
|
||||||
|
*/
|
||||||
|
formatTime(record: LogRecord, datefmt?: any): string {
|
||||||
|
|
||||||
|
//TODO: record.created
|
||||||
|
if (datefmt) {
|
||||||
|
//TODO: time.strftime
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//TODO: time.strftime
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'some time';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format and return the specified exception information as a string.
|
||||||
|
|
||||||
|
* This default implementation just uses
|
||||||
|
* traceback.print_exception()
|
||||||
|
*/
|
||||||
|
formatError(ei: MyError): string {
|
||||||
|
//TODO
|
||||||
|
return 'some error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_FORMATTER = new Formatter();
|
||||||
199
src/handler.ts
Normal file
199
src/handler.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
import * as stream from 'stream';
|
||||||
|
|
||||||
|
import { LogLevel, checkLevel, NOTSET } from './log-level';
|
||||||
|
import { LogRecord } from './log-record';
|
||||||
|
import { Formatter, DEFAULT_FORMATTER } from './formatter';
|
||||||
|
import { Filterer } from './filter';
|
||||||
|
import { NotImplementedError } from './helper/error';
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
const stream = require('stream');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const stream = require('./helper/stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Handler classes and functions
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type Handlers = {[key: string]: Handler};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map of handler names to handlers
|
||||||
|
*/
|
||||||
|
const HANDLERS: Handlers = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* added to allow handlers to be removed in reverse order of initialization
|
||||||
|
*/
|
||||||
|
const HANDLER_LIST: WeakRef<Handler>[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler to the internal cleanup list using a weak reference.
|
||||||
|
*
|
||||||
|
* @param handler -
|
||||||
|
*/
|
||||||
|
function addHandlerRef(handler: Handler) {
|
||||||
|
HANDLER_LIST.push(new WeakRef(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a handler with the specified *name*, or None if there isn't one with
|
||||||
|
* that name.
|
||||||
|
*/
|
||||||
|
export function getHandlerByName(name: string): Handler|null {
|
||||||
|
return HANDLERS[name] ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all known handler names as an immutable set
|
||||||
|
*/
|
||||||
|
export function getHandlerNames(): Handlers { return Object.freeze(HANDLERS) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler instances dispatch logging events to specific destinations.
|
||||||
|
*
|
||||||
|
* The base handler class. Acts as a placeholder which defines the Handler
|
||||||
|
* interface. Handlers can optionally use Formatter instances to format
|
||||||
|
* records as desired. By default, no formatter is specified; in this case,
|
||||||
|
* the 'raw' message as determined by record.message is logged.
|
||||||
|
*/
|
||||||
|
export class Handler extends Filterer {
|
||||||
|
|
||||||
|
protected _scope: string|null = null;
|
||||||
|
protected _formatter: Formatter|null = null;
|
||||||
|
protected _level: number;
|
||||||
|
protected _closed: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the instance - basically setting the formatter to None
|
||||||
|
* and the filter list to empty
|
||||||
|
*/
|
||||||
|
constructor(level?: LogLevel) {
|
||||||
|
super();
|
||||||
|
this._level = checkLevel(level ?? NOTSET);
|
||||||
|
// Add the handler to the global HANDLER_LIST (for cleanup on shutdown)
|
||||||
|
addHandlerRef(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get level(): number { return this._level }
|
||||||
|
set level(level: LogLevel|string) { this.level = checkLevel(level) }
|
||||||
|
|
||||||
|
get scope(): string|null { return this._scope }
|
||||||
|
set scope(scope: string) { this._scope = scope }
|
||||||
|
get closed(): boolean { return this._closed }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format the specified record.
|
||||||
|
*
|
||||||
|
* If a formatter is set, use it. Otherwise, use the default formatter for
|
||||||
|
* the module.
|
||||||
|
*/
|
||||||
|
format(record: LogRecord) {
|
||||||
|
var fmt: Formatter|null = null;
|
||||||
|
|
||||||
|
if (this.formatter) { fmt = this.formatter }
|
||||||
|
else { fmt = DEFAULT_FORMATTER }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do whatever it takes to actually log the specified logging record.
|
||||||
|
*
|
||||||
|
* This version is intended to be implemented by subclasses and so raises a
|
||||||
|
* NotImplementedError.
|
||||||
|
*/
|
||||||
|
emit(record: LogRecord) {
|
||||||
|
throw new NotImplementedError(
|
||||||
|
'emit must be implemented by Handler subclass'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditionally emit the specfied logging record.
|
||||||
|
*
|
||||||
|
* Emission depends on filters which may have been added to the handler.
|
||||||
|
* Wrap the actual emission of the record with acquisition/release of the
|
||||||
|
* I/O thread lock.
|
||||||
|
*/
|
||||||
|
handle(record: LogRecord) {
|
||||||
|
var rv = this.filter(record);
|
||||||
|
if ((rv as any) instanceof LogRecord) {
|
||||||
|
record = rv as unknown as LogRecord
|
||||||
|
}
|
||||||
|
if (rv) {
|
||||||
|
//locking here
|
||||||
|
this.emit(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tidy up any resources used by the handler
|
||||||
|
*
|
||||||
|
* This version removes the handler from an internal map of handlers, which
|
||||||
|
* is used for handler lookup by scope. Subclasses should ensure that this
|
||||||
|
* gets called from overriden close() methods.
|
||||||
|
*/
|
||||||
|
close() {
|
||||||
|
this._closed = true;
|
||||||
|
|
||||||
|
if (this.scope && Object.keys(HANDLERS).includes(this.scope)) {
|
||||||
|
delete HANDLERS[this.scope]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle errors which occur during an emit() call.
|
||||||
|
*
|
||||||
|
* This method should be called from handlers when an exception is
|
||||||
|
* encountered during an emit() call. If raiseExceptions is false,
|
||||||
|
* exceptions get silently ignored. This is what is mostly wanted
|
||||||
|
* for a logging system - most users will not care about errors in
|
||||||
|
* the logging system, they are more interested in application errors.
|
||||||
|
* You could, however, replace this with a custom handler if you wish.
|
||||||
|
* The record which was being processed is passed in to this method.
|
||||||
|
*/
|
||||||
|
handleError(record: LogRecord) {
|
||||||
|
throw new NotImplementedError(
|
||||||
|
'still need to find portable way for stacktracing...'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
set formatter(fmt: Formatter) { this._formatter = fmt }
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileHandlerOptions {
|
||||||
|
filename: string
|
||||||
|
filemode?: string
|
||||||
|
encoding?: string
|
||||||
|
errors?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler class which writes logging records, appropriately formatted,
|
||||||
|
to a stream. Note that this class does not close the stream, as
|
||||||
|
sys.stdout or sys.stderr may be used.
|
||||||
|
*/
|
||||||
|
export class StreamHandler extends Handler {
|
||||||
|
constructor(stream?: stream.Writable) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileHandler extends StreamHandler {
|
||||||
|
constructor(options: FileHandlerOptions) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is like a StreamHandler using sys.stderr, but always uses
|
||||||
|
* whatever sys.stderr is currently set to rather than the value of
|
||||||
|
* sys.stderr at handler construction time.
|
||||||
|
*/
|
||||||
|
export class StderrHandler extends Handler {
|
||||||
|
/**
|
||||||
|
* Initialize the handler.
|
||||||
|
*/
|
||||||
|
constructor(level: LogLevel) { super(level) }
|
||||||
|
}
|
||||||
1317
src/index.ts
1317
src/index.ts
File diff suppressed because it is too large
Load diff
118
src/log-level.ts
Normal file
118
src/log-level.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*---------------------------------------------------------------------------
|
||||||
|
Level related stuff
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Default levels and level names, these can be replaced with any positive set
|
||||||
|
of values having corresponding names. There is a pseudo-level, NOTSET, which
|
||||||
|
is only really there as a lower limit for user-defined levels. Handlers and
|
||||||
|
loggers are initialized with NOTSET so that they will log all messages, even
|
||||||
|
at user-defined levels.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type LogLevel = number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An indication that something unexpected happened, or that a problem might
|
||||||
|
* occur in the near future (e.g. ‘disk space low’). The software is still
|
||||||
|
* working as expected.
|
||||||
|
*/
|
||||||
|
export const CRITICAL = 50;
|
||||||
|
export const FATAL = CRITICAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to a more serious problem, the software has not been able to perform some
|
||||||
|
* function.
|
||||||
|
*/
|
||||||
|
export const ERROR = 40;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An indication that something unexpected happened, or that a problem might
|
||||||
|
* occur in the near future (e.g. ‘disk space low’). The software is still
|
||||||
|
* working as expected.
|
||||||
|
*/
|
||||||
|
export const WARNING = 30;
|
||||||
|
export const WARN = WARNING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation that things are working as expected.
|
||||||
|
*/
|
||||||
|
export const INFO = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detailed information, typically only of interest to a developer trying to
|
||||||
|
* diagnose a problem.
|
||||||
|
*/
|
||||||
|
export const DEBUG = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set on a logger, indicates that ancestor loggers are to be consulted to
|
||||||
|
* determine the effective level. If that still resolves to NOTSET, then all
|
||||||
|
* events are logged. When set on a handler, all events are handled.
|
||||||
|
*/
|
||||||
|
export const NOTSET = 0;
|
||||||
|
|
||||||
|
const LEVELTONAME: {[key: number]: string} = {
|
||||||
|
[CRITICAL]: 'CRITICAL',
|
||||||
|
[ERROR]: 'ERROR',
|
||||||
|
[WARNING]: 'WARNING',
|
||||||
|
[INFO]: 'INFO',
|
||||||
|
[DEBUG]: 'DEBUG',
|
||||||
|
[NOTSET]: 'NOTSET'
|
||||||
|
}
|
||||||
|
|
||||||
|
const NAMETOLEVEL: {[key: string]: number} = {
|
||||||
|
CRITICAL: CRITICAL,
|
||||||
|
ERROR: ERROR,
|
||||||
|
WARNING: WARNING,
|
||||||
|
INFO: INFO,
|
||||||
|
DEBUG: DEBUG,
|
||||||
|
NOTSET: NOTSET,
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLevelNamesMapping() {
|
||||||
|
return Object.assign({}, NAMETOLEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the textual or numeric representation of logging level 'level'
|
||||||
|
*
|
||||||
|
* @param level
|
||||||
|
*/
|
||||||
|
export function getLevelName(level: string|number): string|number {
|
||||||
|
var result: string|number = LEVELTONAME[level as number];
|
||||||
|
if (result !== undefined) { return result }
|
||||||
|
result = NAMETOLEVEL[level as string];
|
||||||
|
if (result !== undefined) { return result }
|
||||||
|
return `Level ${level}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate 'levelName' with 'level'
|
||||||
|
*
|
||||||
|
* @param level
|
||||||
|
* @param levelName
|
||||||
|
*/
|
||||||
|
export function addLevelName(level: number, levelName: string) {
|
||||||
|
LEVELTONAME[level] = levelName;
|
||||||
|
NAMETOLEVEL[levelName] = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkLevel(level: number|string): number {
|
||||||
|
var rv: number;
|
||||||
|
|
||||||
|
if (typeof level == 'number') { rv = level }
|
||||||
|
|
||||||
|
else if (typeof level == 'string') {
|
||||||
|
if (!Object.keys(NAMETOLEVEL).includes(level as string)) {
|
||||||
|
throw new Error(`Unknown level: ${level}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = NAMETOLEVEL[level]
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
throw new Error(`Level not a number or valid string: ${level}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
77
src/log-record.ts
Normal file
77
src/log-record.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { getLevelName, LogLevel } from './log-level';
|
||||||
|
import { MillisecondsSinceUnixEpoch } from './helper/datetime';
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// The logging record
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* options for instantiating a new log record
|
||||||
|
*/
|
||||||
|
export interface LogRecordOptions {
|
||||||
|
/**
|
||||||
|
* The numeric level of the logging event (such as 10 for DEBUG, 20 for
|
||||||
|
* INFO, etc). Note that this is converted to two attributes of the
|
||||||
|
* LogRecord: levelno for the numeric value and levelname for the
|
||||||
|
* corresponding level name.
|
||||||
|
*/
|
||||||
|
level: number,
|
||||||
|
file?: string,
|
||||||
|
/**
|
||||||
|
* The line number in the source file where the logging call was made.
|
||||||
|
*/
|
||||||
|
lno?: number,
|
||||||
|
/**
|
||||||
|
* The event description message, which can be a %-format string with
|
||||||
|
* placeholders for variable data, or an arbitrary object (see Using
|
||||||
|
* arbitrary objects as messages).
|
||||||
|
*/
|
||||||
|
msg: string,
|
||||||
|
/**
|
||||||
|
* Variable data to merge into the msg argument to obtain the event
|
||||||
|
* description.
|
||||||
|
*/
|
||||||
|
args?: any[],
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogRecordFactory = { (name: string, options: LogRecordOptions): LogRecord };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LogRecord instances are created every time something is logged. They contain
|
||||||
|
* all the information pertinent to the event being logged. The main
|
||||||
|
* information parssed in is msg and args, which are combined using str(msg) %
|
||||||
|
* args to create the message field of the record. The record also includes
|
||||||
|
* information such as when the record was created, the source line where the
|
||||||
|
* logging call was made, and any exception information to be logged.
|
||||||
|
*/
|
||||||
|
export class LogRecord {
|
||||||
|
public readonly levelno: LogLevel;
|
||||||
|
public readonly levelname: string|LogLevel;
|
||||||
|
public readonly scope: string;
|
||||||
|
|
||||||
|
public readonly created: MillisecondsSinceUnixEpoch = Date.now();
|
||||||
|
|
||||||
|
constructor(scope: string, options: LogRecordOptions) {
|
||||||
|
this.levelno = options.level;
|
||||||
|
this.levelname = getLevelName(options.level);
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export var logRecordFactory = (scope: string, options: LogRecordOptions) => {
|
||||||
|
return new LogRecord(scope, options)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define which class use when instantiating log records.
|
||||||
|
*
|
||||||
|
* @param factory - A callable which will be called to instantiate a log record.
|
||||||
|
* Pass a clojure, if your factory is a class already.
|
||||||
|
*/
|
||||||
|
export function setLogRecordFactory(factory: LogRecordFactory) {
|
||||||
|
logRecordFactory = factory
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogRecordFactory(): LogRecordFactory {
|
||||||
|
return logRecordFactory
|
||||||
|
}
|
||||||
324
src/logger.ts
Normal file
324
src/logger.ts
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
import {
|
||||||
|
LogLevel,
|
||||||
|
DEBUG,
|
||||||
|
NOTSET,
|
||||||
|
WARNING,
|
||||||
|
checkLevel,
|
||||||
|
} from './log-level';
|
||||||
|
import {
|
||||||
|
LogRecord,
|
||||||
|
logRecordFactory,
|
||||||
|
LogRecordOptions,
|
||||||
|
} from './log-record';
|
||||||
|
import { Handler, StderrHandler } from './handler';
|
||||||
|
import {
|
||||||
|
NotImplementedError,
|
||||||
|
KeyError,
|
||||||
|
ValueError,
|
||||||
|
StackTrace,
|
||||||
|
} from './helper/error';
|
||||||
|
import { Manager } from './manager';
|
||||||
|
import { Filterer } from './filter';
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Logger classes and functions
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export type ExecutionInfo = [string, Error, StackTrace];
|
||||||
|
|
||||||
|
export var throwErrors: boolean = true;
|
||||||
|
|
||||||
|
export const DEFAULT_LAST_RESORT = new StderrHandler(WARNING);
|
||||||
|
|
||||||
|
export var lastResort = DEFAULT_LAST_RESORT;
|
||||||
|
|
||||||
|
export type LoggerClass = { new(): Logger };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* context of a logging event/trigger
|
||||||
|
*/
|
||||||
|
export interface LogOptions{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
excInfo: ExecutionInfo|Error|null,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
extra: {[key: string]: any}|null,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
stackInfo: boolean,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
stackLevel: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_LOG_OPTIONS: LogOptions = Object.freeze({
|
||||||
|
excInfo: null,
|
||||||
|
extra: null,
|
||||||
|
stackInfo: false,
|
||||||
|
stackLevel: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of the logger class represent a single logging channel. A 'logging
|
||||||
|
* channel' indicates an area of an application. Exactly how an 'area' is
|
||||||
|
* defined is up to the application developer. Since an application can have any
|
||||||
|
* number of areas, logging channels are identified by a unique string.
|
||||||
|
* Application areas can be nested (e.g. an area of input process might include
|
||||||
|
* sub-areas "read CSV file", "read XLS files" and "read Gnumeric files"). To
|
||||||
|
* cater for this natural nesting, channel ames are organized into a namespace
|
||||||
|
* hierarchy where levels are separated by periods, much like the Java or Python
|
||||||
|
* package namespace. So in the instance given above, channel names might be
|
||||||
|
* "input" for the upper level, and "input.csv", "input.xls" and "input.gnu" for
|
||||||
|
* the sub-levels.
|
||||||
|
* There is no arbitrary limit to the depth of nesting.
|
||||||
|
*/
|
||||||
|
export class Logger extends Filterer {
|
||||||
|
public readonly scope: string;
|
||||||
|
public _level: number;
|
||||||
|
private _manager: Manager|null = null;
|
||||||
|
public readonly parent: Logger|null = null;
|
||||||
|
public readonly propagate: boolean = true;
|
||||||
|
public readonly handlers: Handler[] = [];
|
||||||
|
public readonly disabled: boolean = false;
|
||||||
|
private cache: {[key: number]: boolean} = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the logger with a name and an optional level
|
||||||
|
*
|
||||||
|
* @param scope -
|
||||||
|
* @param level -
|
||||||
|
* @param manager -
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
scope: string,
|
||||||
|
level?: LogLevel,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.scope = scope;
|
||||||
|
this._level = checkLevel(level ?? NOTSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get level() { return this._level }
|
||||||
|
|
||||||
|
public set level(level: LogLevel) { this._level = checkLevel(level) }
|
||||||
|
|
||||||
|
public set manager(manager: Manager) {
|
||||||
|
if (this.manager) {
|
||||||
|
throw new ValueError('logger can only be assigned to manager once');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLevel(level: LogLevel) {
|
||||||
|
this.level = checkLevel(level);
|
||||||
|
|
||||||
|
//this.manager.clearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the effective level for this logger.
|
||||||
|
*
|
||||||
|
* Loop through this logger and its parents in the logger hierarchy, looking
|
||||||
|
* for a non-zero logging level. Return the first one found.
|
||||||
|
*/
|
||||||
|
public getEffectiveLevel() {
|
||||||
|
var logger: Logger|null = this;
|
||||||
|
|
||||||
|
while (logger) {
|
||||||
|
if (logger.level) { return logger.level }
|
||||||
|
logger = logger.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NOTSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this logger enabled for level 'level'?
|
||||||
|
*/
|
||||||
|
public isEnabledFor(level: LogLevel): boolean {
|
||||||
|
if (this.disabled) { return false }
|
||||||
|
|
||||||
|
if (this.cache[level] === undefined && this.manager && this.manager.disable < level) {
|
||||||
|
return this.cache[level] = (
|
||||||
|
level >= this.getEffectiveLevel()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cache[level] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log 'msg % args' with severity 'DEBUG'
|
||||||
|
*
|
||||||
|
* To pass exception information, use the keyword argument exc_info with
|
||||||
|
* a true value, e.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* logger.debug("Houston, we have a thorny problem", { exc_info: true })
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public debug(msg: string, options?: LogOptions) {
|
||||||
|
if (this.isEnabledFor(DEBUG)) { this._log(DEBUG, msg, options) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory method which can be overriden in subclasses to create
|
||||||
|
* specialized LogRecords.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected makeRecord(
|
||||||
|
name: string,
|
||||||
|
level: LogLevel,
|
||||||
|
msg: string,
|
||||||
|
options: LogOptions,
|
||||||
|
): LogRecord {
|
||||||
|
|
||||||
|
var recordOptions: LogRecordOptions = {
|
||||||
|
level: level,
|
||||||
|
msg: msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
var rv = logRecordFactory(name, recordOptions);
|
||||||
|
|
||||||
|
if (options.extra !== null) {
|
||||||
|
Object.entries(options.extra!).forEach((item) => {
|
||||||
|
|
||||||
|
var [k, v] = item;
|
||||||
|
|
||||||
|
if (['message', 'asctime'].includes(k as string) ||
|
||||||
|
(rv as {[key: string]: any}).keys().includes(k as string)) {
|
||||||
|
throw new KeyError('attempt to overwrite ${k} in LogRecord')
|
||||||
|
}
|
||||||
|
|
||||||
|
(rv as any)[k] = options.extra![k as string] as any
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Low-level logging routine which creates a LogRecord and then calls the
|
||||||
|
* handlers of this logger to handle the record.
|
||||||
|
*/
|
||||||
|
protected _log(level: LogLevel, msg: string, options?: LogOptions) {
|
||||||
|
options = options ?? DEFAULT_LOG_OPTIONS;
|
||||||
|
options = { ...DEFAULT_LOG_OPTIONS, ...options };
|
||||||
|
|
||||||
|
var sinfo=null;
|
||||||
|
|
||||||
|
if (options!.excInfo !== null) {
|
||||||
|
if (options!.excInfo instanceof Error) {
|
||||||
|
var excInfo: ExecutionInfo = [
|
||||||
|
typeof options!.excInfo,
|
||||||
|
options!.excInfo,
|
||||||
|
options!.excInfo.stack!
|
||||||
|
]
|
||||||
|
}
|
||||||
|
else if (!(options!.excInfo instanceof Array)) {
|
||||||
|
throw new NotImplementedError("would try to get the callee stack from the system. Probably will use stacktrace.js as this needs to be implemented browser-specific.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var record = this.makeRecord(this.scope, level, msg, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call the handlers for the specified record.
|
||||||
|
*
|
||||||
|
* This method is used for unpickled records received from a socket, as well
|
||||||
|
* as those created locally. Logger-level filtering is applied.
|
||||||
|
*/
|
||||||
|
protected handle(scope: string, record: LogRecord) {
|
||||||
|
if (this.disabled) { return }
|
||||||
|
var maybeRecord = this.filter(record);
|
||||||
|
if (!maybeRecord) { return }
|
||||||
|
if ((maybeRecord as any) instanceof LogRecord) { record = maybeRecord }
|
||||||
|
this.callHandlers(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass a record to all relevant handlers.
|
||||||
|
*
|
||||||
|
* Loop through all handlers for this logger and its parents n the logger
|
||||||
|
* hierarchy. If no handler was found, output a one-off error message to
|
||||||
|
* sys.stderr. Stop searching up the hierarchy whenever a logger with the
|
||||||
|
* "propagate" attribute set to zero is found - that will be the last logger
|
||||||
|
* whose handlers are called.
|
||||||
|
*/
|
||||||
|
protected callHandlers(record: LogRecord) {
|
||||||
|
var c: Logger|null = this;
|
||||||
|
var found = 0;
|
||||||
|
|
||||||
|
while (c) {
|
||||||
|
for (var i = 0; i < c.handlers.length; i += 1) {
|
||||||
|
let hdlr = c.handlers[i];
|
||||||
|
|
||||||
|
found = found + 1;
|
||||||
|
|
||||||
|
if (record.levelno >= hdlr.level) { hdlr.handle(record) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c.propagate) { c = null }
|
||||||
|
else { c = c.parent }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found == 0) {
|
||||||
|
if (lastResort) {
|
||||||
|
if (record.levelno >= lastResort.level) {
|
||||||
|
lastResort.handle(record)
|
||||||
|
}
|
||||||
|
else if (throwErrors && (this.manager && !this.manager.emittedNoHandlerWarning)) {
|
||||||
|
console.error(
|
||||||
|
`No handlers could be found for logger ${this.scope}`
|
||||||
|
);
|
||||||
|
|
||||||
|
this.manager.emittedNoHandlerWarning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
for (var property in this.cache) delete this.cache[property];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified handler from this logger.
|
||||||
|
*/
|
||||||
|
public addHandler(hdlr: Handler) {
|
||||||
|
const i = this.handlers.indexOf(hdlr);
|
||||||
|
if (i === -1) { this.handlers.push(hdlr) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified handler from this logger.
|
||||||
|
*/
|
||||||
|
public removeHandler(hdlr: Handler) {
|
||||||
|
const i = this.handlers.indexOf(hdlr);
|
||||||
|
if (i !== -1) { delete this.handlers[i] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A root logger is not that different to any other logger, except that it must
|
||||||
|
* have a logging level and there is only one instance of in a manager's
|
||||||
|
* hierarchy.
|
||||||
|
*/
|
||||||
|
export class RootLogger extends Logger {
|
||||||
|
|
||||||
|
constructor(level: LogLevel) {
|
||||||
|
super('root', level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* root logger (singleton)
|
||||||
|
*/
|
||||||
|
export const ROOT = new RootLogger(WARNING);
|
||||||
127
src/manager.ts
Normal file
127
src/manager.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
import {
|
||||||
|
Logger,
|
||||||
|
LoggerClass,
|
||||||
|
RootLogger,
|
||||||
|
ROOT,
|
||||||
|
} from './logger';
|
||||||
|
import { LogRecordFactory } from './log-record';
|
||||||
|
import {
|
||||||
|
LogLevel,
|
||||||
|
NOTSET,
|
||||||
|
checkLevel,
|
||||||
|
} from './log-level'
|
||||||
|
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Manager classes and functions
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var loggerClass = Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder instance
|
||||||
|
*/
|
||||||
|
class Placeholder {
|
||||||
|
protected loggers: Logger[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize with the specified logger being a child of this placeholder.
|
||||||
|
*/
|
||||||
|
constructor(logger: Logger) { this.push(logger) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add the specified logger as a child of this placeholder
|
||||||
|
*/
|
||||||
|
public push(logger: Logger) {
|
||||||
|
if (!this.loggers.includes(logger)) { this.loggers.push(logger) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There is [under normal circumstances] just one Manager intance, which holds
|
||||||
|
* the hierarchy of loggers.
|
||||||
|
*/
|
||||||
|
export class Manager {
|
||||||
|
public readonly root: RootLogger;
|
||||||
|
protected _disable: number = 0;
|
||||||
|
public emittedNoHandlerWarning: boolean = false;
|
||||||
|
protected loggers: {[key: string]: Logger} = {};
|
||||||
|
protected _loggerClass: LoggerClass|null = null;
|
||||||
|
protected _logRecordFactory: LogRecordFactory|null = null;
|
||||||
|
|
||||||
|
public get disable(): number { return this._disable }
|
||||||
|
|
||||||
|
public set disable(level: LogLevel) { this._disable = checkLevel(level) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the manager with the root node of the logger hierarchy
|
||||||
|
*/
|
||||||
|
constructor(root: RootLogger) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a logger with the specified name (scope name), creating it, if it
|
||||||
|
* does not yet exist. This name is a dot-separated hierarchical name, such
|
||||||
|
* as "a", "a.b", "a.b.c" or similar.
|
||||||
|
*
|
||||||
|
* If a PlaceHolder existed for the specified name [i.e. the logger didn't
|
||||||
|
* exist but a child of it did], replace it with the created logger and fix
|
||||||
|
* up the parent/child references which pointed to the placeholder to now
|
||||||
|
* point to the logger.
|
||||||
|
*/
|
||||||
|
getLogger(scope: string) {
|
||||||
|
var rv: null|Logger = null;
|
||||||
|
|
||||||
|
if (typeof scope != 'string') {
|
||||||
|
|
||||||
|
rv = this.loggers[scope];
|
||||||
|
|
||||||
|
if (rv instanceof Placeholder) {
|
||||||
|
var ph = rv;
|
||||||
|
rv = new (this._loggerClass ?? loggerClass)(scope, NOTSET);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rv = new (this._loggerClass ?? loggerClass)(scope, NOTSET);
|
||||||
|
this.loggers[scope] = rv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the class to be used when instantiating a logger with this Manager.
|
||||||
|
*/
|
||||||
|
set loggerClass(class_: LoggerClass) {
|
||||||
|
if (class_ !== Logger) {
|
||||||
|
if (!(class_.prototype instanceof Logger)) {
|
||||||
|
throw new TypeError("logger not derived from logging.Logger: ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loggerClass = class_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the factory to be used when instantiating a log record with this
|
||||||
|
* Manager.
|
||||||
|
*/
|
||||||
|
set logRecordFactory(factory: LogRecordFactory) {
|
||||||
|
this._logRecordFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear the cache for all loggers in loggerDict
|
||||||
|
*/
|
||||||
|
public clear() {
|
||||||
|
Object.values(this.loggers).forEach((logger) => {
|
||||||
|
logger.clear()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log manager (singleton)
|
||||||
|
*/
|
||||||
|
export const MANAGER = new Manager(ROOT);
|
||||||
11
src/polyfill/regexp.ts
Normal file
11
src/polyfill/regexp.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Polyfill for `RegExp.escape`, ensuring compatibility with environments
|
||||||
|
* that do not yet support this method.
|
||||||
|
*
|
||||||
|
* @see src/types/regexp.d.ts For the TypeScript type declaration.
|
||||||
|
*/
|
||||||
|
if (!RegExp.escape) {
|
||||||
|
RegExp.escape = function (str: string): string {
|
||||||
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
};
|
||||||
|
}
|
||||||
8
src/types/regexp.d.ts
vendored
Normal file
8
src/types/regexp.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
interface RegExpConstructor {
|
||||||
|
/**
|
||||||
|
* @see
|
||||||
|
* {@link https://tc39.es/proposal-regex-escaping/#sec-regexp.escape
|
||||||
|
* | ECMAScript Stage 4 Draft}
|
||||||
|
*/
|
||||||
|
escape?(str: string): string;
|
||||||
|
}
|
||||||
26
src/util/regexp.ts
Normal file
26
src/util/regexp.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
export type SubstitutionCallable = (match: RegExpExecArray|null) => string;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the string obtained by replacing the leftmost non-overlapping
|
||||||
|
* occurrences of the pattern in `input` by the `substitution`. `substitution`
|
||||||
|
* can be either a string or a callable; if a string, backslash escapes in it
|
||||||
|
* are processed. If it is a callable, it's passed the `RegExpExecArray` object
|
||||||
|
* and must return a substitution string to be used.
|
||||||
|
*/
|
||||||
|
export function substitute(
|
||||||
|
pattern: RegExp,
|
||||||
|
input: string,
|
||||||
|
substitution: string|SubstitutionCallable,
|
||||||
|
): string {
|
||||||
|
return input.replace(pattern, (match, ...groups) => {
|
||||||
|
const execArray = pattern.exec(match);
|
||||||
|
|
||||||
|
if (typeof substitution === "function") return substitution(execArray);
|
||||||
|
|
||||||
|
return substitution.replace(
|
||||||
|
/\\(\d+)/g,
|
||||||
|
(_, index) => execArray?.[Number(index)] ?? ''
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
100
src/util/string.ts
Normal file
100
src/util/string.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* TODO: Monitor ECMAScript Stage 4 Draft adoption.
|
||||||
|
* Once officially standardized, remove the polyfill.
|
||||||
|
*
|
||||||
|
* @see src/types/regexp.d.ts for more information.
|
||||||
|
*/
|
||||||
|
import '../polyfill/regexp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constants used for ctype-style character classification.
|
||||||
|
*
|
||||||
|
* Includes:
|
||||||
|
* - `WHITESPACE`: Common whitespace characters.
|
||||||
|
* - `ASCII_LOWERCASE`: Lowercase ASCII letters.
|
||||||
|
* - `ASCII_UPPERCASE`: Uppercase ASCII letters.
|
||||||
|
* - `ASCII_LETTERS`: Combined uppercase and lowercase letters.
|
||||||
|
* - `DIGITS`: Numeric digits (0-9).
|
||||||
|
* - `HEXDIGITS`: Hexadecimal digits (0-9, a-f, A-F).
|
||||||
|
* - `OCTDIGITS`: Octal digits (0-7).
|
||||||
|
* - `PUNCTUATION`: A regex pattern for common punctuation characters.
|
||||||
|
* - `PRINTABLE`: All printable ASCII characters.
|
||||||
|
*/
|
||||||
|
export const WHITESPACE = ' \t\n\r\v\f';
|
||||||
|
export const ASCII_LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
export const ASCII_UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
export const ASCII_LETTERS = ASCII_LOWERCASE + ASCII_UPPERCASE;
|
||||||
|
export const DIGITS = '0123456789';
|
||||||
|
export const HEXDIGITS = DIGITS + 'abcdef' + 'abcdef';
|
||||||
|
export const OCTDIGITS = '01234567';
|
||||||
|
export const PUNCTUATION = new RegExp("!\"#$%&'()*+,-./:;<=>?@[]^_`{|}~");
|
||||||
|
export const PRINTABLE = DIGITS + ASCII_LETTERS + PUNCTUATION + WHITESPACE;
|
||||||
|
|
||||||
|
|
||||||
|
export interface TemplateOptions {
|
||||||
|
/**
|
||||||
|
* @remarks
|
||||||
|
* overrides of the default template options must ensure the delimiter is
|
||||||
|
* escaped through the `RegExp.escape` method. This is due to performance
|
||||||
|
* reasons as to not require escaping on every `Template` construction.
|
||||||
|
*/
|
||||||
|
delimiter: string,
|
||||||
|
/**
|
||||||
|
* TODO: write block comment
|
||||||
|
*/
|
||||||
|
bracedIdPattern?: string
|
||||||
|
/**
|
||||||
|
* @see
|
||||||
|
* {@link https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-constructor}
|
||||||
|
*/
|
||||||
|
flags: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const DEFAULT_TEMPLATE_OPTIONS: TemplateOptions = {
|
||||||
|
delimiter: RegExp.escape!('$'),
|
||||||
|
flags: 'i',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string class for supporting $-substitutions
|
||||||
|
*/
|
||||||
|
export class Template {
|
||||||
|
/**
|
||||||
|
* '[a-z]' matches to non-ASCII letters when used with IGNORECASE, but
|
||||||
|
* without the ASCII flag. We can't add re.ASCII to flags because of
|
||||||
|
* backward compatibility. So we use the ?a local flag and [a-z] pattern.
|
||||||
|
* See https://bugs.python.org/issue31672
|
||||||
|
*/
|
||||||
|
protected static idPattern: string = '[_a-z][_a-z0-9]*';
|
||||||
|
protected readonly pattern: RegExp;
|
||||||
|
protected readonly template: string;
|
||||||
|
|
||||||
|
static createPattern(
|
||||||
|
delimiter: string,
|
||||||
|
bracedIdPattern?: string,
|
||||||
|
): string {
|
||||||
|
var pattern: string;
|
||||||
|
pattern = `${delimiter}(?:`;
|
||||||
|
pattern += `(?<escaped>${delimiter})`;
|
||||||
|
pattern += `|(?<named>${Template.idPattern})`;
|
||||||
|
pattern += `|{{(?<braced>${bracedIdPattern ?? Template.idPattern})}}`;
|
||||||
|
pattern += '|(?<invalid>)';
|
||||||
|
pattern += ')';
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(template: string, options?: TemplateOptions) {
|
||||||
|
options = options ?? DEFAULT_TEMPLATE_OPTIONS;
|
||||||
|
|
||||||
|
this.template = template;
|
||||||
|
|
||||||
|
this.pattern = new RegExp(
|
||||||
|
Template.createPattern(options.delimiter, options.bracedIdPattern),
|
||||||
|
options.flags
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
73
tests/log-level.test.ts
Normal file
73
tests/log-level.test.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import {expect, jest, test} from '@jest/globals';
|
||||||
|
import * as log_level from '../src/log-level';
|
||||||
|
|
||||||
|
describe('Logger', () => {
|
||||||
|
it('can be instantiated', () => {
|
||||||
|
//const logger = new log_level.Logger('test', 0);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getLevelName', () => {
|
||||||
|
it('numeric to textual representation of built-ins', () => {
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.CRITICAL)
|
||||||
|
).toBe('CRITICAL');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.FATAL)
|
||||||
|
).toBe('CRITICAL');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.ERROR)
|
||||||
|
).toBe('ERROR');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.WARNING)
|
||||||
|
).toBe('WARNING');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.WARN)
|
||||||
|
).toBe('WARNING');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.INFO)
|
||||||
|
).toBe('INFO');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.DEBUG)
|
||||||
|
).toBe('DEBUG');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName(log_level.NOTSET)
|
||||||
|
).toBe('NOTSET');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('textual to numeric representation of built-ins', () => {
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('CRITICAL')
|
||||||
|
).toBe(log_level.CRITICAL);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('FATAL')
|
||||||
|
).toBe(`Level FATAL`);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('ERROR')
|
||||||
|
).toBe(log_level.ERROR);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('WARNING')
|
||||||
|
).toBe(log_level.WARNING);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('WARN')
|
||||||
|
).toBe('Level WARN');
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('INFO')
|
||||||
|
).toBe(log_level.INFO);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('DEBUG')
|
||||||
|
).toBe(log_level.DEBUG);
|
||||||
|
expect(
|
||||||
|
log_level.getLevelName('NOTSET')
|
||||||
|
).toBe(log_level.NOTSET);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('addLevelName', () => {
|
||||||
|
it('numeric to textual representation of built-ins', () => {
|
||||||
|
log_level.addLevelName(80, 'FOOBAR');
|
||||||
|
expect(log_level.getLevelName(80)).toBe('FOOBAR');
|
||||||
|
expect(log_level.getLevelName('FOOBAR')).toBe(80);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import {expect, jest, test} from '@jest/globals';
|
|
||||||
|
|
||||||
describe('Logger', () => {
|
|
||||||
it('can be instantiated', () => {
|
|
||||||
//const logger = new logging.Logger('test', 0);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getLevelName', () => {
|
|
||||||
var logging: any;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// there are a couple of singletons, which I'm not yet sure if they need
|
|
||||||
// to be reloaded for every test case
|
|
||||||
logging = require('../src');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('numeric to textual representation of built-ins', () => {
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.CRITICAL)
|
|
||||||
).toBe('CRITICAL');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.FATAL)
|
|
||||||
).toBe('CRITICAL');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.ERROR)
|
|
||||||
).toBe('ERROR');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.WARNING)
|
|
||||||
).toBe('WARNING');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.WARN)
|
|
||||||
).toBe('WARNING');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.INFO)
|
|
||||||
).toBe('INFO');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.DEBUG)
|
|
||||||
).toBe('DEBUG');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName(logging.NOTSET)
|
|
||||||
).toBe('NOTSET');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('textual to numeric representation of built-ins', () => {
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('CRITICAL')
|
|
||||||
).toBe(logging.CRITICAL);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('FATAL')
|
|
||||||
).toBe(`Level FATAL`);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('ERROR')
|
|
||||||
).toBe(logging.ERROR);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('WARNING')
|
|
||||||
).toBe(logging.WARNING);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('WARN')
|
|
||||||
).toBe('Level WARN');
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('INFO')
|
|
||||||
).toBe(logging.INFO);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('DEBUG')
|
|
||||||
).toBe(logging.DEBUG);
|
|
||||||
expect(
|
|
||||||
logging.getLevelName('NOTSET')
|
|
||||||
).toBe(logging.NOTSET);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('addLevelName', () => {
|
|
||||||
var logging: any;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
logging = require('../src');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('numeric to textual representation of built-ins', () => {
|
|
||||||
logging.addLevelName(80, 'FOOBAR');
|
|
||||||
expect(logging.getLevelName(80)).toBe('FOOBAR');
|
|
||||||
expect(logging.getLevelName('FOOBAR')).toBe(80);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
"lib": ["esnext.weakref", "dom"]
|
"lib": ["esnext.weakref", "dom"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"src/**/*.ts",
|
||||||
|
"src/types/**/*.d.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue