├── .github ├── FUNDING.yml └── workflows │ ├── deno-test.yml │ └── jsr-publish.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── _build_npm.ts ├── _updateMod.ts ├── changelog.md ├── date.ts ├── deno.jsonc ├── deno.lock ├── deps.ts ├── dev_deps.ts ├── eol.ts ├── example ├── consoleLogger.ts ├── fileAndconsoleLogger.ts ├── fileLogger.rotate.ts └── fileLogger.ts ├── interface.ts ├── logger.ts ├── mod.ts ├── screenshots ├── consoleLogger.png ├── fileLogger.png └── fileLogger.rotate.png ├── stdout.ts ├── test └── test.ts ├── types.ts ├── writable.ts └── writer.ts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: urielch 2 | -------------------------------------------------------------------------------- /.github/workflows/deno-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow will install Deno then run `deno lint` and `deno test`. 7 | # For more information see: https://github.com/denoland/setup-deno 8 | 9 | name: Deno Test 10 | 11 | on: 12 | push: 13 | branches: ["master"] 14 | pull_request: 15 | branches: ["master"] 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - name: Setup repo 26 | uses: actions/checkout@v4 27 | 28 | - name: Setup Deno 29 | uses: denoland/setup-deno@v2 30 | with: 31 | deno-version: v2.x 32 | 33 | # Uncomment this step to verify the use of 'deno fmt' on each commit. 34 | - name: Verify formatting 35 | run: deno fmt --check 36 | 37 | - name: Run linter 38 | run: deno lint 39 | 40 | - name: Run tests 41 | run: deno test -A 42 | -------------------------------------------------------------------------------- /.github/workflows/jsr-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to JSR 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | 11 | permissions: 12 | contents: read 13 | id-token: write 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Publish package 19 | run: npx jsr publish 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | deps 107 | npm -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.unstable": false 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zfx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deno-logger 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/@denodnt/logger.svg?style=flat)](https://www.npmjs.org/package/@denodnt/logger) 4 | [![JSR Version](https://jsr.io/badges/@deno-library/logger)](https://jsr.io/@deno-library/logger) 5 | 6 | Deno / NodeJS colorful logger colorful logger 7 | 8 | For Deno usage refer to [deno-logger doc](https://deno.land/x/logger) 9 | 10 | ## Useage 11 | 12 | ### console logger 13 | 14 | ```js 15 | import { Logger } from "jsr:@deno-library/logger"; 16 | // or 17 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 18 | 19 | const logger = new Logger(); 20 | 21 | logger.debug("i am from consoleLogger", "debug"); 22 | logger.info("i am from consoleLogger", { name: "zfx" }); 23 | logger.log("i am from consoleLogger", "hello"); 24 | logger.warn("i am from consoleLogger", 1, "any"); 25 | logger.error("i am from consoleLogger", new Error("test")); 26 | ``` 27 | 28 | ### file and console logger 29 | 30 | ```js 31 | import { Logger } from "jsr:@deno-library/logger"; 32 | // or 33 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 34 | 35 | const logger = new Logger(); 36 | 37 | // console only 38 | logger.debug("i am from consoleLogger", "debug"); 39 | logger.info("i am from consoleLogger", { name: "zfx" }); 40 | logger.log("i am from consoleLogger", "hello"); 41 | logger.warn("i am from consoleLogger", 1, "any"); 42 | logger.error("i am from consoleLogger", new Error("test")); 43 | 44 | await logger.initFileLogger("../log"); 45 | 46 | // file and console 47 | logger.debug("i am from consoleLogger", "debug"); 48 | logger.info("i am from fileLogger", { name: "zfx" }); 49 | logger.log("i am from consoleLogger", "hello"); 50 | logger.warn("i am from fileLogger", 1, "any"); 51 | logger.error("i am from fileLogger", new Error("test")); 52 | ``` 53 | 54 | ### file logger only 55 | 56 | ```js 57 | import { Logger } from "jsr:@deno-library/logger"; 58 | // or 59 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 60 | 61 | const logger = new Logger(); 62 | await logger.initFileLogger("../log"); 63 | logger.disableConsole(); 64 | 65 | // file only 66 | logger.info(["i am from fileLogger", 1], { name: "zfx" }); 67 | ``` 68 | 69 | ### file logger optional parameter 70 | 71 | interface 72 | 73 | ```ts 74 | interface fileLoggerOptions { 75 | rotate?: boolean; // cut by day 76 | maxBytes?: number; 77 | // Only available if maxBytes is provided, Otherwise you will get an error 78 | maxBackupCount?: number; 79 | } 80 | ``` 81 | 82 | example 83 | 84 | ```js 85 | import { Logger } from "jsr:@deno-library/logger"; 86 | // or 87 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 88 | 89 | const logger = new Logger(); 90 | 91 | // cut by day 92 | // filename is [date]_[type].log 93 | // example 2020-05-25_warn.log, 2020-05-25_info.log, 2020-05-25_error.log 94 | await logger.initFileLogger("../log", { 95 | rotate: true, 96 | }); 97 | 98 | // maxBytes 99 | // filename is [type].log.[timestamp] 100 | // example: info.log.1590374415956 101 | await logger.initFileLogger("../log", { 102 | maxBytes: 10 * 1024, 103 | }); 104 | 105 | // rotate and maxBytes 106 | // filename is [date]_[type].log.[timestamp] 107 | // example: 2020-05-25_info.log.1590374415956 108 | await logger.initFileLogger("../log", { 109 | rotate: true, 110 | maxBytes: 10 * 1024, 111 | }); 112 | 113 | // maxBytes and maxBackupCount 114 | // filename is [type].log.[n] 115 | // example info.log.1, info.log.2 ... 116 | // when reach maxBackupCount, the [type].log.[maxBackupCount-1] will be overwrite 117 | // detail: 118 | // `maxBytes` specifies the maximum size 119 | // in bytes that the log file can grow to before rolling over to a new one. If the 120 | // size of the new log message plus the current log file size exceeds `maxBytes` 121 | // then a roll over is triggered. When a roll over occurs, before the log message 122 | // is written, the log file is renamed and appended with `.1`. If a `.1` version 123 | // already existed, it would have been renamed `.2` first and so on. The maximum 124 | // number of log files to keep is specified by `maxBackupCount`. After the renames 125 | // are complete the log message is written to the original, now blank, file. 126 | // 127 | // Example: Given `log.txt`, `log.txt.1`, `log.txt.2` and `log.txt.3`, a 128 | // `maxBackupCount` of 3 and a new log message which would cause `log.txt` to 129 | // exceed `maxBytes`, then `log.txt.2` would be renamed to `log.txt.3` (thereby 130 | // discarding the original contents of `log.txt.3` since 3 is the maximum number of 131 | // backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt` would 132 | // be renamed to `log.txt.1` and finally `log.txt` would be created from scratch 133 | // where the new log message would be written. 134 | await logger.initFileLogger("../log", { 135 | maxBytes: 10 * 1024, 136 | maxBackupCount: 10, 137 | }); 138 | 139 | // rotate and maxBytes and maxBackupCount 140 | // filename is [date]_[type].log.[n] 141 | // example 2020-05-25_info.log.1, 2020-05-25_info.log.2 142 | // when reach maxBackupCount, the [type].log.[maxBackupCount-1] will be overwrite 143 | await logger.initFileLogger("../log", { 144 | rotate: true, 145 | maxBytes: 10 * 1024, 146 | maxBackupCount: 10, 147 | }); 148 | 149 | // rotate and maxBackupCount 150 | // maxBackupCount will be ignored 151 | await logger.initFileLogger("../log", { 152 | rotate: true, 153 | maxBackupCount: 10, 154 | }); 155 | ``` 156 | 157 | The following conditions will throw an error 158 | 159 | ```ts 160 | // maxBackupCount 161 | // get error => maxBackupCount must work with maxBytes 162 | await logger.initFileLogger("../log", { 163 | maxBackupCount: 10, 164 | }); 165 | // rotate and maxBackupCount 166 | // get error => maxBackupCount must work with maxBytes 167 | await logger.initFileLogger("../log", { 168 | rotate: true, 169 | maxBackupCount: 10, 170 | }); 171 | ``` 172 | 173 | ## disableConsole and enableConsole 174 | 175 | ```js 176 | import { Logger } from "jsr:@deno-library/logger"; 177 | // or 178 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 179 | 180 | const logger = new Logger(); 181 | 182 | // console 183 | logger.info("console enabled, you can see me"); 184 | logger.disableConsole(); 185 | // no message is logged 186 | logger.info("console disabled"); 187 | logger.enableConsole(); 188 | // console 189 | logger.info("console enabled, you can see me"); 190 | ``` 191 | 192 | ## disableFile and enableFile 193 | 194 | ```js 195 | import { Logger } from "jsr:@deno-library/logger"; 196 | // or 197 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 198 | 199 | const logger = new Logger(); 200 | await logger.initFileLogger("../log"); 201 | 202 | logger.disableFile(); 203 | // not log to file 204 | logger.info("file disbaled"); 205 | logger.enableFile(); 206 | // log to file 207 | logger.info("file enabled, you can see me"); 208 | ``` 209 | 210 | ## disable and enable 211 | 212 | - disable disable write to file and terminal, don't care if it is currently 213 | writing to a file or terminal, but hope to restore the currently configuration 214 | later 215 | - enable restore previous log configuration: file, terminal or both 216 | 217 | example: 218 | 219 | 1. fileLogger => disable => enable => fileLogger 220 | 2. consoleLogger => disable => enable => consoleLogger 221 | 3. fileLogger, consoleLogger => disable => enable => fileLogger, consoleLogger 222 | 223 | ```js 224 | import { Logger } from "jsr:@deno-library/logger"; 225 | // or 226 | // import Logger from "https://deno.land/x/logger@v1.1.7/logger.ts"; 227 | 228 | const logger = new Logger(); 229 | await logger.initFileLogger("../log"); 230 | 231 | logger.disable(); 232 | logger.enable(); 233 | ``` 234 | 235 | ## test 236 | 237 | ```bash 238 | deno test --allow-read --allow-write 239 | ``` 240 | 241 | ## Dual-Stack / Triple-Stack integration 242 | 243 | A dual-stack project is generaly a npm project that can be import as commonJS 244 | module or as ESM module; So if a project can also be import as a Deno module, 245 | it's a triple-stack one. 246 | 247 | To convert your Deno project to a dual-stack npm project, you should use 248 | [deno/dnt](https://deno.land/x/dnt), then create a `_build_npm.ts` or 249 | `scripts/build_npm.ts` that looks like: 250 | 251 | ```ts 252 | import { build, emptyDir } from "@deno/dnt"; 253 | 254 | // grap the next version number as you want 255 | const version: Deno.args[0]; 256 | await emptyDir("./npm"); 257 | await build({ 258 | entryPoints: ["./mod.ts"], 259 | outDir: "./npm", 260 | shims: { 261 | deno: true, 262 | }, 263 | compilerOptions: { 264 | lib: ["dom", "esnext"], 265 | }, 266 | package: { 267 | name: "pkg-name", 268 | version: version, 269 | // ... package stuff 270 | }, 271 | // map your favorite deno logger to its npm port. 272 | mappings: { 273 | "https://deno.land/x/logger@v1.1.7/logger.ts": { 274 | name: "@denodnt/logger", 275 | version: "1.1.7", 276 | peerDependency: false, 277 | }, 278 | }, 279 | }); 280 | ``` 281 | 282 | ## Screenshots 283 | 284 | consoleLogger\ 285 | ![consoleLogger](https://github.com/deno-library/logger/blob/master/screenshots/consoleLogger.png?raw=true) 286 | 287 | fileLogger\ 288 | ![fileLogger](https://github.com/deno-library/logger/blob/master/screenshots/fileLogger.png?raw=true) 289 | 290 | cut logs by day\ 291 | ![CutByDay](https://github.com/deno-library/logger/blob/master/screenshots/fileLogger.rotate.png?raw=true) 292 | 293 | More screenshots in the `screenshots` folder. 294 | 295 | ## Logger interface 296 | 297 | ```ts 298 | interface fileLoggerOptions { 299 | rotate?: boolean; // cut by day 300 | maxBytes?: number; // the maximum size in bytes that the log file can grow to before rolling over to a new one 301 | maxBackupCount?: number; // maxBackupCount must work with maxBytes 302 | } 303 | 304 | interface LoggerInerface { 305 | constructor(); 306 | 307 | debug(...args: unknown[]): void; 308 | info(...args: unknown[]): void; 309 | log(...args: unknown[]): void; 310 | warn(...args: unknown[]): void; 311 | error(...args: unknown[]): void; 312 | 313 | initFileLogger(dir: string, options: fileLoggerOptions = {}): Promise; 314 | 315 | disableConsole(): void; 316 | enableConsole(): void; 317 | 318 | disableFile(): void; 319 | enableFile(): void; 320 | 321 | disable(): void; 322 | enable(): void; 323 | } 324 | ``` 325 | -------------------------------------------------------------------------------- /_build_npm.ts: -------------------------------------------------------------------------------- 1 | // dnt deps can not be moved to dev_deps.ts 2 | import { build, emptyDir } from "@deno/dnt"; 3 | import type { PackageJson } from "@deno/dnt"; 4 | import * as pc from "jsr:@std/fmt@1.0.3/colors"; 5 | 6 | export async function buildDnt() { 7 | let version = Deno.args[0]; 8 | const GITHUB_REF = Deno.env.get("GITHUB_REF"); 9 | const PKG_VERSION = Deno.env.get("PKG_VERSION"); 10 | 11 | if (!version) { 12 | if (PKG_VERSION) { 13 | console.log(`NPM_VERSION values is "${pc.green(PKG_VERSION)}"`); 14 | version = PKG_VERSION; 15 | } else if (GITHUB_REF) { 16 | // drop the ref/tag/ and the v prefix 17 | version = GITHUB_REF.replace(/^.+\/[vV]?/g, ""); 18 | console.log( 19 | `GITHUB_REF values is ${ 20 | pc.green( 21 | GITHUB_REF, 22 | ) 23 | } will be used as version: "${pc.green(version)}"`, 24 | ); 25 | } 26 | } 27 | 28 | if (!version) { 29 | console.error("Missing version number"); 30 | console.error("usage: deno run -A _build_npm.ts v0.0.0"); 31 | Deno.exit(-1); 32 | } 33 | // allow only semver string 34 | if (!version.match(/[\d]+\.[\d]+\.[\d]+/)) { 35 | console.error( 36 | `version number ${ 37 | pc.green(version) 38 | } do not match Semantic Versioning syntax ${ 39 | pc.green("major.minor.path") 40 | }`, 41 | ); 42 | Deno.exit(-1); 43 | } 44 | 45 | const packageJson: PackageJson = { 46 | name: "@denodnt/logger", 47 | author: "zfx", 48 | license: "MIT", 49 | funding: "https://github.com/deno-library/logger?sponsor=1", 50 | contributors: [ 51 | "fuxing Zhang (https://github.com/fuxingZhang)", 52 | "Uriel Chemouni (https://uriel.ovh/)", 53 | ], 54 | description: "deno logger available for deno and NPM", 55 | keywords: ["logger", "deno", "rotate"], 56 | private: false, 57 | homepage: "https://github.com/deno-library/logger", 58 | version, 59 | repository: { 60 | type: "git", 61 | url: "git+https://github.com/deno-library/logger", 62 | }, 63 | bugs: { 64 | url: "https://github.com/deno-library/logger/issues", 65 | }, 66 | }; 67 | 68 | await emptyDir("./npm"); 69 | await build({ 70 | entryPoints: ["./mod.ts"], 71 | outDir: "./npm", 72 | test: true, 73 | shims: { 74 | deno: true, 75 | }, 76 | compilerOptions: {}, 77 | package: packageJson, 78 | }); 79 | // post build steps 80 | Deno.copyFileSync("LICENSE", "npm/LICENSE"); 81 | let readme = Deno.readTextFileSync("README.md"); 82 | readme = readme.replaceAll( 83 | /https:\/\/deno.land\/x\/logger@v[0-9.]+\/(logger|mod)\.ts/g, 84 | "@denodnt/logger", 85 | ); 86 | Deno.writeTextFileSync("npm/README.md", readme); 87 | } 88 | 89 | if (import.meta.main) { 90 | buildDnt(); 91 | } 92 | -------------------------------------------------------------------------------- /_updateMod.ts: -------------------------------------------------------------------------------- 1 | const readmeContentOriginal = Deno.readTextFileSync("README.md"); 2 | const jsonStr = Deno.readTextFileSync("deno.jsonc"); 3 | const jsonConf = JSON.parse(jsonStr) as { version: string }; 4 | 5 | let readmeContent = readmeContentOriginal.replaceAll( 6 | /https:\/\/deno.land\/x\/logger@v[\d.]+\/logger.ts/g, 7 | `https://deno.land/x/logger@v${jsonConf.version}/logger.ts`, 8 | ).replaceAll( 9 | /version: "[\d.]+"/g, 10 | `version: "${jsonConf.version}"`, 11 | ); 12 | 13 | // update current version in readme 14 | if (readmeContent !== readmeContentOriginal) { 15 | Deno.writeTextFileSync("README.md", readmeContent); 16 | } 17 | 18 | readmeContent = readmeContent.replaceAll( 19 | /https:\/\/deno.land\/x\/logger@v[\d.]+\/logger.ts/g, 20 | `jsr:@deno-library/logger@${jsonConf.version}`, 21 | ); 22 | 23 | // Write the updated content back to the mod.ts file 24 | Deno.writeTextFileSync( 25 | "mod.ts", 26 | `/** 27 | * ${readmeContent} 28 | * @module 29 | */ 30 | 31 | /** 32 | * The Logger class 33 | */ 34 | export { default as Logger } from "./logger.ts"; 35 | /** 36 | * The Logger class default instance 37 | */ 38 | export { default as default } from "./logger.ts"; 39 | `, 40 | ); 41 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### v1.1.7 - 2024.11.11 4 | 5 | - [Update the used deno API](https://github.com/deno-library/logger/pull/17) 6 | - move from `deno-lib/logger` to `deno-library/logger` 7 | 8 | ### v1.1.6 - 2024.05.17 9 | 10 | - use JSR publish 11 | - add docs 12 | 13 | ### v1.1.1 - 2023.05.19 14 | 15 | - New build / deployement script 16 | - Update doc 17 | 18 | ### v1.1.0 - 2023.05.04 19 | 20 | Remove useless parameter and optimize fileLogger 21 | 22 | ```diff 23 | interface fileLoggerOptions { 24 | rotate?: boolean; // cut by day 25 | - now?: boolean; // print datetime or not 26 | maxBytes?: number, // the maximum size in bytes that the log file can grow to before rolling over to a new one 27 | maxBackupCount?: number // maxBackupCount must work with maxBytes 28 | } 29 | ``` 30 | 31 | ### v1.0.3 - 2023.05.04 32 | 33 | [update code to support modern Deno](https://github.com/deno-library/logger/pull/4). 34 | -------------------------------------------------------------------------------- /date.ts: -------------------------------------------------------------------------------- 1 | // const format = n => n > 9 ? n : `0${n}`; 2 | const format = (n: number) => n.toString().padStart(2, "0"); 3 | 4 | /** 5 | * Dater extends Date to provide more formating methods. 6 | */ 7 | export default class Dater extends Date { 8 | /** 9 | * Get date string as yyyy-mm-dd hh:mm:ss 10 | * @param separator 11 | * @returns 12 | */ 13 | override toLocaleString(separator = "-"): string { 14 | const dateString = this.toLocaleDateString(separator); 15 | const timeString = this.toLocaleTimeString(); 16 | return `${dateString} ${timeString}`; 17 | } 18 | 19 | /** 20 | * Get date string as yyyy-mm-dd 21 | * @param separator 22 | * @returns 23 | */ 24 | override toLocaleDateString(separator = "-"): string { 25 | const year = format(this.getFullYear()); 26 | const month = format(this.getMonth() + 1); 27 | const day = format(this.getDate()); 28 | return `${year}${separator}${month}${separator}${day}`; 29 | } 30 | 31 | /** 32 | * Get time string as hh:mm:ss 33 | * @returns 34 | */ 35 | override toLocaleTimeString(): string { 36 | const hour = format(this.getHours()); 37 | const minute = format(this.getMinutes()); 38 | const second = format(this.getSeconds()); 39 | return `${hour}:${minute}:${second}`; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@deno-library/logger", 3 | "version": "1.2.0", 4 | "license": "MIT", 5 | "exports": "./mod.ts", 6 | "tasks": { 7 | "regen": "deno run -A _updateMod.ts" 8 | }, 9 | "fmt": { 10 | "exclude": ["npm/"] 11 | }, 12 | "lint": { 13 | "exclude": ["npm/"] 14 | }, 15 | "test": { 16 | "exclude": ["npm/"] 17 | }, 18 | "publish": { 19 | "include": ["*.ts", "README.md", "LICENSE", "changelog.md", "deno.jsonc"], 20 | "exclude": ["_*.ts"] 21 | }, 22 | "imports": { 23 | "@deno/dnt": "jsr:@deno/dnt@^0.41.3" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4", 3 | "specifiers": { 4 | "jsr:@david/code-block-writer@^13.0.2": "13.0.3", 5 | "jsr:@deno/cache-dir@~0.10.3": "0.10.3", 6 | "jsr:@deno/dnt@~0.41.3": "0.41.3", 7 | "jsr:@deno/graph@~0.73.1": "0.73.1", 8 | "jsr:@std/assert@0.223": "0.223.0", 9 | "jsr:@std/assert@0.226": "0.226.0", 10 | "jsr:@std/bytes@0.223": "0.223.0", 11 | "jsr:@std/fmt@0.223": "0.223.0", 12 | "jsr:@std/fmt@1": "1.0.2", 13 | "jsr:@std/fmt@1.0.3": "1.0.3", 14 | "jsr:@std/fs@0.223": "0.223.0", 15 | "jsr:@std/fs@1": "1.0.4", 16 | "jsr:@std/fs@^1.0.6": "1.0.6", 17 | "jsr:@std/fs@~0.229.3": "0.229.3", 18 | "jsr:@std/io@0.223": "0.223.0", 19 | "jsr:@std/path@0.223": "0.223.0", 20 | "jsr:@std/path@1": "1.0.6", 21 | "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", 22 | "jsr:@std/path@^1.0.6": "1.0.6", 23 | "jsr:@std/path@^1.0.8": "1.0.8", 24 | "jsr:@std/path@~0.225.2": "0.225.2", 25 | "jsr:@ts-morph/bootstrap@0.24": "0.24.0", 26 | "jsr:@ts-morph/common@0.24": "0.24.0", 27 | "npm:@types/node@*": "22.5.4" 28 | }, 29 | "jsr": { 30 | "@david/code-block-writer@13.0.3": { 31 | "integrity": "f98c77d320f5957899a61bfb7a9bead7c6d83ad1515daee92dbacc861e13bb7f" 32 | }, 33 | "@deno/cache-dir@0.10.3": { 34 | "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", 35 | "dependencies": [ 36 | "jsr:@deno/graph", 37 | "jsr:@std/fmt@0.223", 38 | "jsr:@std/fs@0.223", 39 | "jsr:@std/io", 40 | "jsr:@std/path@0.223" 41 | ] 42 | }, 43 | "@deno/dnt@0.41.3": { 44 | "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", 45 | "dependencies": [ 46 | "jsr:@david/code-block-writer", 47 | "jsr:@deno/cache-dir", 48 | "jsr:@std/fmt@1", 49 | "jsr:@std/fs@1", 50 | "jsr:@std/path@1", 51 | "jsr:@ts-morph/bootstrap" 52 | ] 53 | }, 54 | "@deno/graph@0.73.1": { 55 | "integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41" 56 | }, 57 | "@std/assert@0.223.0": { 58 | "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" 59 | }, 60 | "@std/assert@0.226.0": { 61 | "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" 62 | }, 63 | "@std/bytes@0.223.0": { 64 | "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" 65 | }, 66 | "@std/fmt@0.223.0": { 67 | "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" 68 | }, 69 | "@std/fmt@1.0.2": { 70 | "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" 71 | }, 72 | "@std/fmt@1.0.3": { 73 | "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" 74 | }, 75 | "@std/fs@0.223.0": { 76 | "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" 77 | }, 78 | "@std/fs@0.229.3": { 79 | "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", 80 | "dependencies": [ 81 | "jsr:@std/path@1.0.0-rc.1" 82 | ] 83 | }, 84 | "@std/fs@1.0.4": { 85 | "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", 86 | "dependencies": [ 87 | "jsr:@std/path@^1.0.6" 88 | ] 89 | }, 90 | "@std/fs@1.0.6": { 91 | "integrity": "42b56e1e41b75583a21d5a37f6a6a27de9f510bcd36c0c85791d685ca0b85fa2", 92 | "dependencies": [ 93 | "jsr:@std/path@^1.0.8" 94 | ] 95 | }, 96 | "@std/io@0.223.0": { 97 | "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", 98 | "dependencies": [ 99 | "jsr:@std/assert@0.223", 100 | "jsr:@std/bytes" 101 | ] 102 | }, 103 | "@std/path@0.223.0": { 104 | "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", 105 | "dependencies": [ 106 | "jsr:@std/assert@0.223" 107 | ] 108 | }, 109 | "@std/path@0.225.2": { 110 | "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", 111 | "dependencies": [ 112 | "jsr:@std/assert@0.226" 113 | ] 114 | }, 115 | "@std/path@1.0.0-rc.1": { 116 | "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" 117 | }, 118 | "@std/path@1.0.6": { 119 | "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" 120 | }, 121 | "@std/path@1.0.8": { 122 | "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" 123 | }, 124 | "@ts-morph/bootstrap@0.24.0": { 125 | "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", 126 | "dependencies": [ 127 | "jsr:@ts-morph/common" 128 | ] 129 | }, 130 | "@ts-morph/common@0.24.0": { 131 | "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", 132 | "dependencies": [ 133 | "jsr:@std/fs@~0.229.3", 134 | "jsr:@std/path@~0.225.2" 135 | ] 136 | } 137 | }, 138 | "npm": { 139 | "@types/node@22.5.4": { 140 | "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", 141 | "dependencies": [ 142 | "undici-types" 143 | ] 144 | }, 145 | "undici-types@6.19.8": { 146 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" 147 | } 148 | }, 149 | "workspace": { 150 | "dependencies": [ 151 | "jsr:@deno/dnt@~0.41.3" 152 | ] 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | cyan, 3 | gray, 4 | green, 5 | red, 6 | stripAnsiCode, 7 | yellow, 8 | } from "jsr:@std/fmt@1.0.3/colors"; 9 | 10 | /** 11 | * use a local copy of UNSTABLE { type Writer, writeAll } from "jsr:@std/io@0.225.0/write-all"; 12 | */ 13 | export interface Writer { 14 | /** Writes `p.byteLength` bytes from `p` to the underlying data stream. It 15 | * resolves to the number of bytes written from `p` (`0` <= `n` <= 16 | * `p.byteLength`) or reject with the error encountered that caused the 17 | * write to stop early. `write()` must reject with a non-null error if 18 | * would resolve to `n` < `p.byteLength`. `write()` must not modify the 19 | * slice data, even temporarily. 20 | * 21 | * Implementations should not retain a reference to `p`. 22 | */ 23 | write(p: Uint8Array): Promise; 24 | } 25 | 26 | export async function writeAll(writer: Writer, data: Uint8Array) { 27 | let nwritten = 0; 28 | while (nwritten < data.length) { 29 | nwritten += await writer.write(data.subarray(nwritten)); 30 | } 31 | } 32 | export { exists } from "jsr:@std/fs@^1.0.6"; 33 | 34 | export { stat } from "node:fs/promises"; 35 | -------------------------------------------------------------------------------- /dev_deps.ts: -------------------------------------------------------------------------------- 1 | export { assert, assertEquals, assertRejects } from "jsr:@std/assert@1.0.7"; 2 | -------------------------------------------------------------------------------- /eol.ts: -------------------------------------------------------------------------------- 1 | const os = Deno.build.os; 2 | const eol = os === "windows" ? "\r\n" : "\n"; 3 | /** 4 | * OS specific end of line character 5 | */ 6 | export default eol; 7 | -------------------------------------------------------------------------------- /example/consoleLogger.ts: -------------------------------------------------------------------------------- 1 | import Logger from "../logger.ts"; 2 | 3 | const logger = new Logger(); 4 | 5 | // console only 6 | logger.info("i am from consoleLogger", { name: "zfx" }); 7 | logger.warn("i am from consoleLogger", 1, "any"); 8 | logger.error("i am from consoleLogger", new Error("test")); 9 | -------------------------------------------------------------------------------- /example/fileAndconsoleLogger.ts: -------------------------------------------------------------------------------- 1 | import Logger from "../logger.ts"; 2 | 3 | const logger = new Logger(); 4 | 5 | // console only 6 | logger.info("i am from consoleLogger", { name: "zfx" }); 7 | logger.warn("i am from consoleLogger", 1, "any"); 8 | logger.error("i am from consoleLogger", new Error("test")); 9 | 10 | await logger.initFileLogger("./log"); 11 | 12 | // file and console 13 | logger.info("i am from fileLogger", { name: "zfx" }); 14 | logger.warn("i am from fileLogger", 1, "any"); 15 | logger.error("i am from fileLogger", new Error("test")); 16 | -------------------------------------------------------------------------------- /example/fileLogger.rotate.ts: -------------------------------------------------------------------------------- 1 | import Logger from "../logger.ts"; 2 | 3 | const logger = new Logger(); 4 | await logger.initFileLogger("./log", { 5 | rotate: true, 6 | }); 7 | logger.disableConsole(); 8 | 9 | // file only 10 | logger.info(["i am from fileLogger", 1], { name: "info" }); 11 | logger.warn({ name: "warn" }, "i am from fileLogger", [1, [1, 2]]); 12 | logger.error("i am from fileLogger", [1, 2], { name: "error" }); 13 | -------------------------------------------------------------------------------- /example/fileLogger.ts: -------------------------------------------------------------------------------- 1 | import Logger from "../logger.ts"; 2 | 3 | const logger = new Logger(); 4 | await logger.initFileLogger("./log"); 5 | logger.disableConsole(); 6 | 7 | // file only 8 | logger.info(["i am from fileLogger", 1], { name: "info" }); 9 | logger.warn({ name: "warn" }, "i am from fileLogger", [1, [1, 2]]); 10 | logger.error("i am from fileLogger", [1, 2], { name: "error" }); 11 | -------------------------------------------------------------------------------- /interface.ts: -------------------------------------------------------------------------------- 1 | import type Types from "./types.ts"; 2 | 3 | export interface WriterConstructor { 4 | maxBytes?: number; 5 | maxBackupCount?: number; 6 | } 7 | 8 | export interface WriterWrite { 9 | path: string; 10 | msg: Uint8Array; 11 | type: Types; 12 | } 13 | 14 | export interface fileLoggerOptions extends WriterConstructor { 15 | rotate?: boolean; 16 | filename?: string; 17 | } 18 | 19 | export interface LoggerWriteOptions { 20 | dir: string; 21 | type: Types; 22 | args: unknown[]; 23 | } 24 | -------------------------------------------------------------------------------- /logger.ts: -------------------------------------------------------------------------------- 1 | import stdout from "./stdout.ts"; 2 | import Writer from "./writer.ts"; 3 | import eol from "./eol.ts"; 4 | import Dater from "./date.ts"; 5 | import { cyan, exists, green, red, stripAnsiCode, yellow } from "./deps.ts"; 6 | import type { fileLoggerOptions, LoggerWriteOptions } from "./interface.ts"; 7 | import Types from "./types.ts"; 8 | 9 | const { inspect } = Deno; 10 | 11 | const noop = async () => {}; 12 | 13 | export type LoggerType = "debug" | "info" | "log" | "warn" | "error"; 14 | 15 | /** 16 | * Logger class 17 | */ 18 | export default class Logger { 19 | private stdout = stdout; 20 | private encoder = new TextEncoder(); 21 | private writer?: Writer; 22 | private rotate = false; 23 | private dir?: string; 24 | private filename?: string; 25 | 26 | #debug = this.debug; 27 | #info = this.info; 28 | #log = this.log; 29 | #warn = this.warn; 30 | #error = this.error; 31 | #write = this.write; 32 | 33 | private format(...args: unknown[]): Uint8Array { 34 | const msg = args.map((arg) => typeof arg === "string" ? arg : inspect(arg)) 35 | .join(" "); 36 | // const msg = args.map(arg => inspect(arg, { 37 | // showHidden: true, 38 | // depth: 4, 39 | // colors: true, 40 | // indentLevel: 2 41 | // })).join(''); 42 | 43 | return this.encoder.encode(stripAnsiCode(msg) + eol); 44 | } 45 | 46 | /** 47 | * Log message with debug level 48 | * @param args data to log 49 | */ 50 | async debug(...args: unknown[]): Promise { 51 | args = [this.getDebug(), ...args]; 52 | this.stdout(...args); 53 | if (this.dir) { 54 | await this.write({ 55 | dir: this.dir, 56 | type: Types.DEBUG, 57 | args, 58 | }); 59 | } 60 | } 61 | /** 62 | * Log message with info level 63 | * @param args data to log 64 | */ 65 | async info(...args: unknown[]): Promise { 66 | args = [this.getInfo(), ...args]; 67 | this.stdout(...args); 68 | if (this.dir) { 69 | await this.write({ 70 | dir: this.dir, 71 | type: Types.INFO, 72 | args, 73 | }); 74 | } 75 | } 76 | 77 | /** 78 | * Log message with info level 79 | * @param args data to log 80 | */ 81 | async log(...args: unknown[]): Promise { 82 | args = [this.getLog(), ...args]; 83 | this.stdout(...args); 84 | if (this.dir) { 85 | await this.write({ 86 | dir: this.dir, 87 | type: Types.LOG, 88 | args, 89 | }); 90 | } 91 | } 92 | 93 | /** 94 | * Log message with warning level 95 | * @param args data to log 96 | */ 97 | async warn(...args: unknown[]): Promise { 98 | args = [this.getWarn(), ...args]; 99 | this.stdout(...args); 100 | if (this.dir) { 101 | await this.write({ 102 | dir: this.dir, 103 | type: Types.WARN, 104 | args, 105 | }); 106 | } 107 | } 108 | 109 | /** 110 | * Log message with error level 111 | * @param args data to log 112 | */ 113 | async error(...args: unknown[]): Promise { 114 | args = [this.getError(), ...args]; 115 | this.stdout(...args); 116 | if (this.dir) { 117 | await this.write({ 118 | dir: this.dir, 119 | type: Types.ERROR, 120 | args, 121 | }); 122 | } 123 | } 124 | 125 | private write({ dir, type, args }: LoggerWriteOptions): Promise { 126 | const date = this.getDate(); 127 | const filename = this.filename || 128 | (this.rotate === true ? `${date}_${type}` : type); 129 | const path = `${dir}/${filename}.log`; 130 | const msg = this.format(...args); 131 | return this.writer!.write({ path, msg, type }); 132 | } 133 | 134 | /** 135 | * init file logger 136 | * @param dir 137 | * @param options 138 | */ 139 | async initFileLogger( 140 | dir: string, 141 | options: fileLoggerOptions = {}, 142 | ): Promise { 143 | const exist = await exists(dir, { isDirectory: true }); 144 | if (!exist) { 145 | stdout(`${this.getWarn()} Log folder does not exist`); 146 | try { 147 | Deno.mkdirSync(dir, { recursive: true }); 148 | stdout(`${this.getInfo()} Log folder create success`); 149 | } catch (error) { 150 | stdout(`${this.getError()} Log folder create failed: ` + error); 151 | } 152 | } 153 | const { rotate, maxBytes, maxBackupCount } = options; 154 | if (rotate === true) this.rotate = true; 155 | this.dir = dir; 156 | this.filename = options?.filename; 157 | this.writer = new Writer({ 158 | maxBytes, 159 | maxBackupCount, 160 | }); 161 | } 162 | 163 | /** 164 | * disable a specific type of logger 165 | * @param type Level of logger to disable 166 | */ 167 | disable(type?: LoggerType): void { 168 | if (!type) { 169 | this.debug = noop; 170 | this.info = noop; 171 | this.log = noop; 172 | this.warn = noop; 173 | this.error = noop; 174 | return; 175 | } 176 | if (type === "debug") { 177 | this.debug = noop; 178 | return; 179 | } 180 | if (type === "info") { 181 | this.info = noop; 182 | return; 183 | } 184 | if (type === "log") { 185 | this.log = noop; 186 | return; 187 | } 188 | if (type === "warn") { 189 | this.warn = noop; 190 | return; 191 | } 192 | if (type === "error") { 193 | this.error = noop; 194 | return; 195 | } 196 | } 197 | 198 | /** 199 | * Enable a specific type of logger 200 | * @param type Level of logger to enable 201 | */ 202 | enable(type?: LoggerType): void { 203 | if (!type) { 204 | this.debug = this.#debug; 205 | this.info = this.#info; 206 | this.log = this.#log; 207 | this.warn = this.#warn; 208 | this.error = this.#error; 209 | } 210 | if (type === "debug") { 211 | this.debug = this.#debug; 212 | return; 213 | } 214 | if (type === "info") { 215 | this.info = this.#info; 216 | return; 217 | } 218 | if (type === "log") { 219 | this.log = this.#log; 220 | return; 221 | } 222 | if (type === "warn") { 223 | this.warn = this.#warn; 224 | return; 225 | } 226 | if (type === "error") { 227 | this.error = this.#error; 228 | return; 229 | } 230 | } 231 | 232 | /** 233 | * Disable console logger 234 | */ 235 | disableConsole(): void { 236 | this.stdout = noop; 237 | } 238 | 239 | /** 240 | * Enable console logger 241 | */ 242 | enableConsole(): void { 243 | this.stdout = stdout; 244 | } 245 | 246 | /** 247 | * Disable file logger 248 | */ 249 | disableFile(): void { 250 | this.write = noop; 251 | } 252 | 253 | /** 254 | * Enable file logger 255 | */ 256 | enableFile(): void { 257 | this.write = this.#write; 258 | } 259 | 260 | private getDebug(): string { 261 | return green(this.getNow() + cyan(` Debug:`)); 262 | } 263 | 264 | private getInfo(): string { 265 | return green(this.getNow() + green(` Info:`)); 266 | } 267 | 268 | private getLog(): string { 269 | return green(`${this.getNow()} Log:`); 270 | } 271 | 272 | private getWarn(): string { 273 | return green(this.getNow()) + yellow(` Warn:`); 274 | } 275 | 276 | private getError(): string { 277 | return green(this.getNow()) + red(` Error:`); 278 | } 279 | 280 | private getNow(): string { 281 | return new Dater().toLocaleString(); 282 | } 283 | 284 | private getDate(): string { 285 | return new Dater().toLocaleDateString(); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # deno-logger 3 | 4 | [![NPM Version](https://img.shields.io/npm/v/@denodnt/logger.svg?style=flat)](https://www.npmjs.org/package/@denodnt/logger) 5 | [![JSR Version](https://jsr.io/badges/@deno-library/logger)](https://jsr.io/@deno-library/logger) 6 | 7 | Deno / NodeJS colorful logger colorful logger 8 | 9 | For Deno usage refer to [deno-logger doc](https://deno.land/x/logger) 10 | 11 | ## Useage 12 | 13 | ### console logger 14 | 15 | ```js 16 | import { Logger } from "jsr:@deno-library/logger"; 17 | 18 | const logger = new Logger(); 19 | 20 | logger.debug("i am from consoleLogger", "debug"); 21 | logger.info("i am from consoleLogger", { name: "zfx" }); 22 | logger.log("i am from consoleLogger", "hello"); 23 | logger.warn("i am from consoleLogger", 1, "any"); 24 | logger.error("i am from consoleLogger", new Error("test")); 25 | ``` 26 | 27 | ### file and console logger 28 | 29 | ```js 30 | import { Logger } from "jsr:@deno-library/logger"; 31 | 32 | const logger = new Logger(); 33 | 34 | // console only 35 | logger.debug("i am from consoleLogger", "debug"); 36 | logger.info("i am from consoleLogger", { name: "zfx" }); 37 | logger.log("i am from consoleLogger", "hello"); 38 | logger.warn("i am from consoleLogger", 1, "any"); 39 | logger.error("i am from consoleLogger", new Error("test")); 40 | 41 | await logger.initFileLogger("../log"); 42 | 43 | // file and console 44 | logger.debug("i am from consoleLogger", "debug"); 45 | logger.info("i am from fileLogger", { name: "zfx" }); 46 | logger.log("i am from consoleLogger", "hello"); 47 | logger.warn("i am from fileLogger", 1, "any"); 48 | logger.error("i am from fileLogger", new Error("test")); 49 | ``` 50 | 51 | ### file logger only 52 | 53 | ```js 54 | import { Logger } from "jsr:@deno-library/logger"; 55 | 56 | const logger = new Logger(); 57 | await logger.initFileLogger("../log"); 58 | logger.disableConsole(); 59 | 60 | // file only 61 | logger.info(["i am from fileLogger", 1], { name: "zfx" }); 62 | ``` 63 | 64 | ### file logger optional parameter 65 | 66 | interface 67 | 68 | ```ts 69 | interface fileLoggerOptions { 70 | rotate?: boolean; // cut by day 71 | maxBytes?: number; 72 | // Only available if maxBytes is provided, Otherwise you will get an error 73 | maxBackupCount?: number; 74 | } 75 | ``` 76 | 77 | example 78 | 79 | ```js 80 | import { Logger } from "jsr:@deno-library/logger"; 81 | const logger = new Logger(); 82 | 83 | // cut by day 84 | // filename is [date]_[type].log 85 | // example 2020-05-25_warn.log, 2020-05-25_info.log, 2020-05-25_error.log 86 | await logger.initFileLogger("../log", { 87 | rotate: true, 88 | }); 89 | 90 | // maxBytes 91 | // filename is [type].log.[timestamp] 92 | // example: info.log.1590374415956 93 | await logger.initFileLogger("../log", { 94 | maxBytes: 10 * 1024, 95 | }); 96 | 97 | // rotate and maxBytes 98 | // filename is [date]_[type].log.[timestamp] 99 | // example: 2020-05-25_info.log.1590374415956 100 | await logger.initFileLogger("../log", { 101 | rotate: true, 102 | maxBytes: 10 * 1024, 103 | }); 104 | 105 | // maxBytes and maxBackupCount 106 | // filename is [type].log.[n] 107 | // example info.log.1, info.log.2 ... 108 | // when reach maxBackupCount, the [type].log.[maxBackupCount-1] will be overwrite 109 | // detail: 110 | // `maxBytes` specifies the maximum size 111 | // in bytes that the log file can grow to before rolling over to a new one. If the 112 | // size of the new log message plus the current log file size exceeds `maxBytes` 113 | // then a roll over is triggered. When a roll over occurs, before the log message 114 | // is written, the log file is renamed and appended with `.1`. If a `.1` version 115 | // already existed, it would have been renamed `.2` first and so on. The maximum 116 | // number of log files to keep is specified by `maxBackupCount`. After the renames 117 | // are complete the log message is written to the original, now blank, file. 118 | // 119 | // Example: Given `log.txt`, `log.txt.1`, `log.txt.2` and `log.txt.3`, a 120 | // `maxBackupCount` of 3 and a new log message which would cause `log.txt` to 121 | // exceed `maxBytes`, then `log.txt.2` would be renamed to `log.txt.3` (thereby 122 | // discarding the original contents of `log.txt.3` since 3 is the maximum number of 123 | // backups to keep), `log.txt.1` would be renamed to `log.txt.2`, `log.txt` would 124 | // be renamed to `log.txt.1` and finally `log.txt` would be created from scratch 125 | // where the new log message would be written. 126 | await logger.initFileLogger("../log", { 127 | maxBytes: 10 * 1024, 128 | maxBackupCount: 10, 129 | }); 130 | 131 | // rotate and maxBytes and maxBackupCount 132 | // filename is [date]_[type].log.[n] 133 | // example 2020-05-25_info.log.1, 2020-05-25_info.log.2 134 | // when reach maxBackupCount, the [type].log.[maxBackupCount-1] will be overwrite 135 | await logger.initFileLogger("../log", { 136 | rotate: true, 137 | maxBytes: 10 * 1024, 138 | maxBackupCount: 10, 139 | }); 140 | 141 | // rotate and maxBackupCount 142 | // maxBackupCount will be ignored 143 | await logger.initFileLogger("../log", { 144 | rotate: true, 145 | maxBackupCount: 10, 146 | }); 147 | ``` 148 | 149 | The following conditions will throw an error 150 | 151 | ```ts 152 | // maxBackupCount 153 | // get error => maxBackupCount must work with maxBytes 154 | await logger.initFileLogger("../log", { 155 | maxBackupCount: 10, 156 | }); 157 | // rotate and maxBackupCount 158 | // get error => maxBackupCount must work with maxBytes 159 | await logger.initFileLogger("../log", { 160 | rotate: true, 161 | maxBackupCount: 10, 162 | }); 163 | ``` 164 | 165 | ## disableConsole and enableConsole 166 | 167 | ```js 168 | import { Logger } from "jsr:@deno-library/logger"; 169 | 170 | const logger = new Logger(); 171 | 172 | // console 173 | logger.info("console enabled, you can see me"); 174 | logger.disableConsole(); 175 | // no message is logged 176 | logger.info("console disabled"); 177 | logger.enableConsole(); 178 | // console 179 | logger.info("console enabled, you can see me"); 180 | ``` 181 | 182 | ## disableFile and enableFile 183 | 184 | ```js 185 | import { Logger } from "jsr:@deno-library/logger"; 186 | 187 | const logger = new Logger(); 188 | await logger.initFileLogger("../log"); 189 | 190 | logger.disableFile(); 191 | // not log to file 192 | logger.info("file disbaled"); 193 | logger.enableFile(); 194 | // log to file 195 | logger.info("file enabled, you can see me"); 196 | ``` 197 | 198 | ## disable and enable 199 | 200 | - disable disable write to file and terminal, don't care if it is currently 201 | writing to a file or terminal, but hope to restore the currently configuration 202 | later 203 | - enable restore previous log configuration: file, terminal or both 204 | 205 | example: 206 | 207 | 1. fileLogger => disable => enable => fileLogger 208 | 2. consoleLogger => disable => enable => consoleLogger 209 | 3. fileLogger, consoleLogger => disable => enable => fileLogger, consoleLogger 210 | 211 | ```js 212 | import { Logger } from "jsr:@deno-library/logger"; 213 | 214 | const logger = new Logger(); 215 | await logger.initFileLogger("../log"); 216 | 217 | logger.disable(); 218 | logger.enable(); 219 | ``` 220 | 221 | ## test 222 | 223 | ```bash 224 | deno test --allow-read --allow-write 225 | ``` 226 | 227 | ## Dual-Stack / Triple-Stack integration 228 | 229 | A dual-stack project is generaly a npm project that can be import as commonJS 230 | module or as ESM module; So if a project can also be import as a Deno module, 231 | it's a triple-stack one. 232 | 233 | To convert your Deno project to a dual-stack npm project, you should use 234 | [deno/dnt](https://deno.land/x/dnt), then create a `_build_npm.ts` or 235 | `scripts/build_npm.ts` that looks like: 236 | 237 | ```ts 238 | import { build, emptyDir } from "@deno/dnt"; 239 | 240 | // grap the next version number as you want 241 | const version: Deno.args[0]; 242 | await emptyDir("./npm"); 243 | await build({ 244 | entryPoints: ["./mod.ts"], 245 | outDir: "./npm", 246 | shims: { 247 | deno: true, 248 | }, 249 | compilerOptions: { 250 | lib: ["dom", "esnext"], 251 | }, 252 | package: { 253 | name: "pkg-name", 254 | version: version, 255 | // ... package stuff 256 | }, 257 | // map your favorite deno logger to its npm port. 258 | mappings: { 259 | "jsr:@deno-library/logger@1.1.7/logger": { 260 | name: "@denodnt/logger", 261 | version: "1.1.7", 262 | peerDependency: false, 263 | }, 264 | }, 265 | }); 266 | ``` 267 | 268 | ## Screenshots 269 | 270 | consoleLogger\ 271 | ![consoleLogger](https://github.com/deno-library/logger/blob/master/screenshots/consoleLogger.png?raw=true) 272 | 273 | fileLogger\ 274 | ![fileLogger](https://github.com/deno-library/logger/blob/master/screenshots/fileLogger.png?raw=true) 275 | 276 | cut logs by day\ 277 | ![CutByDay](https://github.com/deno-library/logger/blob/master/screenshots/fileLogger.rotate.png?raw=true) 278 | 279 | More screenshots in the `screenshots` folder. 280 | 281 | ## Logger interface 282 | 283 | ```ts 284 | interface fileLoggerOptions { 285 | rotate?: boolean; // cut by day 286 | maxBytes?: number; // the maximum size in bytes that the log file can grow to before rolling over to a new one 287 | maxBackupCount?: number; // maxBackupCount must work with maxBytes 288 | } 289 | 290 | interface LoggerInerface { 291 | constructor(); 292 | 293 | debug(...args: unknown[]): void; 294 | info(...args: unknown[]): void; 295 | log(...args: unknown[]): void; 296 | warn(...args: unknown[]): void; 297 | error(...args: unknown[]): void; 298 | 299 | initFileLogger(dir: string, options: fileLoggerOptions = {}): Promise; 300 | 301 | disableConsole(): void; 302 | enableConsole(): void; 303 | 304 | disableFile(): void; 305 | enableFile(): void; 306 | 307 | disable(): void; 308 | enable(): void; 309 | } 310 | ``` 311 | 312 | ## Contributors 313 | 314 | 315 | 316 | 317 | Thanks for their contributions! 318 | * @module 319 | */ 320 | 321 | /** 322 | * The Logger class 323 | */ 324 | export { default as Logger, type LoggerType } from "./logger.ts"; 325 | /** 326 | * The Logger class default instance 327 | */ 328 | export { default as default } from "./logger.ts"; 329 | -------------------------------------------------------------------------------- /screenshots/consoleLogger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deno-library/logger/7511f8a79c5da84cf2d2b2654defc034c9ee7216/screenshots/consoleLogger.png -------------------------------------------------------------------------------- /screenshots/fileLogger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deno-library/logger/7511f8a79c5da84cf2d2b2654defc034c9ee7216/screenshots/fileLogger.png -------------------------------------------------------------------------------- /screenshots/fileLogger.rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deno-library/logger/7511f8a79c5da84cf2d2b2654defc034c9ee7216/screenshots/fileLogger.rotate.png -------------------------------------------------------------------------------- /stdout.ts: -------------------------------------------------------------------------------- 1 | const isTTY = Deno.stdout.isTerminal(); 2 | import { writeAll } from "./deps.ts"; 3 | 4 | /** 5 | * Deno stdout log function 6 | * @param msg 7 | */ 8 | export async function stdout(msg: Uint8Array): Promise { 9 | await writeAll(Deno.stdout, msg); 10 | } 11 | 12 | /** 13 | * default log function 14 | * @param args 15 | */ 16 | export default function log(...args: unknown[]): void { 17 | console.log(...args); 18 | } 19 | /** 20 | * No operation function 21 | */ 22 | function noop(): void {} 23 | 24 | /** 25 | * Only output to stdout when the terminal is a TTY 26 | */ 27 | export const stdoutOnlyTty = isTTY ? stdout : noop; 28 | /** 29 | * Only output to console when the terminal is a TTY 30 | */ 31 | export const logOnlyTty = isTTY ? log : noop; 32 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | const { test } = Deno; 2 | import { assert, assertRejects } from "../dev_deps.ts"; 3 | import Logger from "../logger.ts"; 4 | 5 | const maxBytesError = "maxBytes cannot be less than 1"; 6 | test(maxBytesError, async function () { 7 | await assertRejects( 8 | async () => { 9 | const logger = new Logger(); 10 | logger.disableConsole(); 11 | await logger.initFileLogger("../log", { 12 | maxBytes: 0, 13 | }); 14 | }, 15 | Error, 16 | maxBytesError, 17 | ); 18 | }); 19 | 20 | const workWithError = "maxBackupCount must work with maxBytes"; 21 | test(workWithError, async function () { 22 | await assertRejects( 23 | async () => { 24 | const logger = new Logger(); 25 | logger.disableConsole(); 26 | await logger.initFileLogger("../log", { 27 | maxBackupCount: 10, 28 | }); 29 | }, 30 | Error, 31 | workWithError, 32 | ); 33 | }); 34 | 35 | const lessError = "maxBackupCount cannot be less than 1"; 36 | test(lessError, async function () { 37 | await assertRejects( 38 | async () => { 39 | const logger = new Logger(); 40 | logger.disableConsole(); 41 | await logger.initFileLogger("../log", { 42 | maxBytes: 10 * 2014, 43 | maxBackupCount: 0, 44 | }); 45 | assert(false); 46 | }, 47 | Error, 48 | lessError, 49 | ); 50 | }); 51 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types of messages that can be logged. 3 | */ 4 | export const enum Types { 5 | DEBUG = "debug", 6 | INFO = "info", 7 | LOG = "log", 8 | WARN = "warn", 9 | ERROR = "error", 10 | } 11 | 12 | export default Types; 13 | -------------------------------------------------------------------------------- /writable.ts: -------------------------------------------------------------------------------- 1 | import { writeAll } from "./deps.ts"; 2 | 3 | /** 4 | * Writable class 5 | */ 6 | export default class Writable { 7 | protected file!: Deno.FsFile; 8 | private path: string; 9 | currentSize = 0; 10 | 11 | /** 12 | * Writable constructor 13 | * @param path 14 | */ 15 | constructor(path: string) { 16 | this.path = path; 17 | } 18 | 19 | /** 20 | * Setup writable file 21 | */ 22 | async setup(): Promise { 23 | this.file = await Deno.open(this.path, { 24 | create: true, 25 | append: true, 26 | write: true, 27 | }); 28 | this.currentSize = (await Deno.stat(this.path)).size; 29 | } 30 | 31 | /** 32 | * Write message to file 33 | * @param msg 34 | */ 35 | async write(msg: Uint8Array): Promise { 36 | await writeAll(this.file, msg); 37 | this.currentSize += msg.byteLength; 38 | } 39 | 40 | /** 41 | * Close file 42 | */ 43 | close(): void { 44 | this.file.close(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /writer.ts: -------------------------------------------------------------------------------- 1 | import Writable from "./writable.ts"; 2 | import { exists } from "./deps.ts"; 3 | import type { WriterConstructor, WriterWrite } from "./interface.ts"; 4 | import Types from "./types.ts"; 5 | 6 | /** 7 | * Writer class 8 | */ 9 | export default class Writer { 10 | private maxBytes?: number; 11 | private maxBackupCount?: number; 12 | private pathWriterMap = new Map(); 13 | 14 | private [Types.DEBUG]: string = ""; 15 | private [Types.INFO]: string = ""; 16 | private [Types.LOG]: string = ""; 17 | private [Types.WARN]: string = ""; 18 | private [Types.ERROR]: string = ""; 19 | 20 | /** 21 | * Writer constructor 22 | * @param param0 23 | * @returns 24 | */ 25 | constructor({ maxBytes, maxBackupCount }: WriterConstructor) { 26 | if (maxBytes !== undefined && maxBytes <= 0) { 27 | throw new Error("maxBytes cannot be less than 1"); 28 | } 29 | this.maxBytes = maxBytes; 30 | 31 | if (maxBackupCount === undefined) return; 32 | if (!maxBytes) { 33 | throw new Error("maxBackupCount must work with maxBytes"); 34 | } 35 | if (maxBackupCount <= 0) { 36 | throw new Error("maxBackupCount cannot be less than 1"); 37 | } 38 | this.maxBackupCount = maxBackupCount; 39 | } 40 | 41 | private async newWriter(path: string) { 42 | const writer = new Writable(path); 43 | await writer.setup(); 44 | this.pathWriterMap.set(path, writer); 45 | return writer; 46 | } 47 | 48 | /** 49 | * Write message to file 50 | * @param param0 51 | * @returns 52 | */ 53 | async write({ path, msg, type }: WriterWrite): Promise { 54 | const msgByteLength = msg.byteLength; 55 | 56 | if (this.pathWriterMap.has(path)) { 57 | const writer = this.pathWriterMap.get(path); 58 | const currentSize = writer.currentSize; 59 | const size = currentSize + msgByteLength; 60 | if (this.maxBytes && size > this.maxBytes) { 61 | writer.close(); 62 | this.rotateLogFiles(path); 63 | const _writer = await this.newWriter(path); 64 | await _writer.write(msg); 65 | } else { 66 | await writer.write(msg); 67 | } 68 | return; 69 | } 70 | 71 | const typePath = this[type]; 72 | if (typePath) { 73 | const pathWriter = this.pathWriterMap.get(typePath); 74 | if (pathWriter && pathWriter.close) pathWriter.close(); 75 | this.pathWriterMap.delete(typePath); 76 | } 77 | this[type] = path; 78 | 79 | const writer = await this.newWriter(path); 80 | await writer.write(msg); 81 | } 82 | 83 | /** 84 | * Rotate log files 85 | * @param path 86 | */ 87 | async rotateLogFiles(path: string): Promise { 88 | if (this.maxBackupCount) { 89 | for (let i = this.maxBackupCount - 1; i >= 0; i--) { 90 | const source = path + (i === 0 ? "" : "." + i); 91 | const dest = path + "." + (i + 1); 92 | const exist = await exists(source); 93 | if (exist) { 94 | await Deno.rename(source, dest); 95 | } 96 | } 97 | } else { 98 | const dest = path + "." + Date.now(); 99 | await Deno.rename(path, dest); 100 | } 101 | } 102 | } 103 | --------------------------------------------------------------------------------