diff --git a/.codacy.yaml b/.codacy.yaml new file mode 100644 index 0000000..0d4ee05 --- /dev/null +++ b/.codacy.yaml @@ -0,0 +1,6 @@ +--- +engines: + duplication: + exclude_paths: + - "src/**/*.spec.ts" + - "src/spec/**" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..183627a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +max_line_length = 120 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +ij_continuation_indent_size = 4 +ij_any_indent_case_from_switch = false + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..a7080d5 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,34 @@ +module.exports = { + root: true, + ignorePatterns: ['node_modules/', 'dist/', 'target/', 'd.ts/', '*.d.ts'], + extends: [ + '@run-z', + ], + overrides: [ + { + files: ['*.js', '*.cjs', '*.mjs'], + env: { + node: true, + }, + }, + { + files: ['*.ts'], + extends: [ + '@run-z/eslint-config/typescript', + ], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + }, + }, + { + files: ['*.spec.ts'], + extends: [ + '@run-z/eslint-config/jest', + ], + parserOptions: { + project: './tsconfig.spec.json', + }, + }, + ], +}; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..64e182b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,49 @@ +# This is a basic workflow to help you get started with Actions + +name: Build + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Cache + uses: actions/cache@v2 + env: + cache-name: cache-node-modules-v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package.json') }} + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' + - name: Install pnpm + run: npm i -g pnpm + - name: Install dependencies + run: pnpm install + + - name: Build the project + run: pnpm ci:all + + - name: Upload coverage report + uses: codacy/codacy-coverage-reporter-action@v1 + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + coverage-reports: target/coverage/lcov.info diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e23cf98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Node.js modules +node_modules + +# IntelliJ IDEA files +.idea +*.iml + +# Logs +*.log + +# Yarn cache +/.yarn +!.yarn/releases +!.yarn/plugins +.pnp.* + +# Lock files as this is part of Yarn worktree +yarn.lock +package-lock.json + +# Package archive +*.tgz + +# Intermediate files +/target + +# Distribution directory +/dist + +# Type definitions +*.d.ts +*.d.ts.map diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..a9ebe63 --- /dev/null +++ b/.npmignore @@ -0,0 +1,37 @@ +# Node.js modules +node_modules + +# IntelliJ IDEA files +.idea +*.iml + +# Intermediate files +/target + +# Logs +*.log + +# Package archive +*.tgz + +# Source files +/src + +# Build scripts +/build + +# Build configurations +/.* +/*.cjs +/*.js +/*.json + +# Package lock +/yarn.lock + +# Include distribution dir +!/dist + +# Include type definitions +!*.d.ts +!*.d.ts.map diff --git a/.remarkrc b/.remarkrc new file mode 100644 index 0000000..8385851 --- /dev/null +++ b/.remarkrc @@ -0,0 +1,10 @@ +{ + "configuration": { + "list-item-indent": "space" + }, + "plugins": [ + "remark-preset-lint-recommended", + ["remark-lint-list-item-indent", "space"], + ["remark-lint-no-shortcut-reference-link", false] + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..973d1f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ruslan Lopatin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7164a36 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +Logger API +========== + +[![NPM][npm-image]][npm-url] +[![Build Status][build-status-img]][build-status-link] +[![Code Quality][quality-img]][quality-link] +[![Coverage][coverage-img]][coverage-link] +[![GitHub Project][github-image]][github-url] +[![API Documentation][api-docs-image]][API documentation] + +[npm-image]: https://img.shields.io/npm/v/@proc7ts/logger.svg?logo=npm +[npm-url]: https://www.npmjs.com/package/@proc7ts/logger +[build-status-img]: https://github.com/proc7ts/logger/workflows/Build/badge.svg +[build-status-link]: https://github.com/proc7ts/logger/actions?query=workflow:Build +[quality-img]: https://app.codacy.com/project/badge/Grade/4e45ef3c83a3497fbe8f7fe3341e023c +[quality-link]: https://www.codacy.com/gh/proc7ts/logger/dashboard?utm_source=github.com&utm_medium=referral&utm_content=proc7ts/logger&utm_campaign=Badge_Grade +[coverage-img]: https://app.codacy.com/project/badge/Coverage/4e45ef3c83a3497fbe8f7fe3341e023c +[coverage-link]: https://www.codacy.com/gh/proc7ts/logger/dashboard?utm_source=github.com&utm_medium=referral&utm_content=proc7ts/logger&utm_campaign=Badge_Coverage +[github-image]: https://img.shields.io/static/v1?logo=github&label=GitHub&message=project&color=informational +[github-url]: https://github.com/proc7ts/logger +[api-docs-image]: https://img.shields.io/static/v1?logo=typescript&label=API&message=docs&color=informational +[API documentation]: https://proc7ts.github.io/logger/ + + +Logger +------ +[Logger]: #logger + +`Logger` interface declares methods corresponding to generic logger levels: + +- `error(...args)` - Logs error. +- `warn(...args)` - Logs warning. +- `info(...args)` - Logs informational message. +- `debug(...args)` - Logs debug message. + +Each method accepts arbitrary number of arguments. + + +Console Logger +-------------- + +`consoleLogger` is a [Logger] instance that logs to [console]. + +Note that the first parameter isn't treated in any special way. I.e. it is not a [format string]. + +[console]: https://developer.mozilla.org/en-US/docs/Web/API/Console +[format string]: https://developer.mozilla.org/en-US/docs/Web/API/Console#using_string_substitutions + + +Silent Logger +------------- + +`silentLogger` is a [Logger] instance the never logs anything. diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5eb13e9 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,41 @@ +export default { + preset: 'ts-jest/presets/default-esm', + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/*.spec.ts', + '!src/**/index.ts', + '!**/node_modules/**', + ], + coverageDirectory: 'target/coverage', + coverageThreshold: { + global: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, + extensionsToTreatAsEsm: ['.ts'], + reporters: [ + 'default', + [ + 'jest-junit', + { + suiteName: '@proc7ts/logger', + outputDirectory: './target/test-results', + classNameTemplate: '{classname}: {title}', + titleTemplate: '{classname}: {title}', + ancestorSeparator: ' › ', + usePathForSuiteName: 'true', + }, + ], + ], + testEnvironment: 'node', + globals: { + 'ts-jest': { + tsconfig: 'tsconfig.spec.json', + useESM: true, + }, + }, +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..34e4996 --- /dev/null +++ b/package.json @@ -0,0 +1,62 @@ +{ + "name": "@proc7ts/logger", + "version": "1.0.0", + "description": "Simple logger API", + "keywords": [ + "log", + "logger", + "logger-api" + ], + "homepage": "https://github.com/proc7ts/logger", + "repository": { + "type": "git", + "url": "ssh://git@github.com:proc7ts/logger.git" + }, + "license": "MIT", + "author": "Ruslan Lopatin ", + "bugs": { + "url": "https://github.com/proc7ts/logger/issues" + }, + "type": "module", + "main": "./dist/logger.js", + "types": "./index.d.ts", + "exports": "./dist/logger.js", + "devDependencies": { + "@jest/globals": "^27.0.3", + "@rollup/plugin-node-resolve": "^13.0.0", + "@run-z/eslint-config": "^1.3.0", + "@run-z/rollup-helpers": "^1.1.1", + "@typescript-eslint/eslint-plugin": "^4.26.0", + "@typescript-eslint/parser": "^4.26.0", + "eslint": "^7.27.0", + "eslint-plugin-jest": "^24.3.6", + "gh-pages": "^3.2.0", + "jest": "^27.0.3", + "jest-junit": "^12.1.0", + "jest-mock": "^27.0.3", + "remark-cli": "^9.0.0", + "remark-preset-lint-recommended": "^5.0.0", + "rollup": "^2.50.5", + "rollup-plugin-flat-dts": "^1.2.2", + "rollup-plugin-sourcemaps": "^0.6.3", + "rollup-plugin-typescript2": "^0.30.0", + "run-z": "^1.8.0", + "shx": "^0.3.3", + "ts-jest": "^27.0.2", + "tslib": "^2.2.0", + "typedoc": "^0.20.36", + "typescript": "^4.3.2" + }, + "scripts": { + "all": "run-z build,lint,test", + "build": "run-z +z --then rollup -c", + "ci:all": "run-z all +test/--ci/--runInBand", + "clean": "run-z +z --then shx rm -rf 'index.d.ts?(.map)' dist target", + "doc": "run-z +z --then typedoc", + "doc:publish": "run-z doc --then gh-pages --dist target/typedoc --dotfiles", + "lint": "run-z + lint:md --and eslint .", + "lint:md": "run-z +z --then remark .", + "test": "run-z +z env:NODE_OPTIONS='--experimental-vm-modules --no-warnings' --then jest", + "z": "run-z +cmd:rollup,+cmd:typedoc,+cmd:eslint,+cmd:remark,+cmd:jest" + } +} diff --git a/rollup.config.mjs b/rollup.config.mjs new file mode 100644 index 0000000..16bbdac --- /dev/null +++ b/rollup.config.mjs @@ -0,0 +1,38 @@ +import nodeResolve from '@rollup/plugin-node-resolve'; +import { externalModules } from '@run-z/rollup-helpers'; +import flatDts from 'rollup-plugin-flat-dts'; +import sourcemaps from 'rollup-plugin-sourcemaps'; +import ts from 'rollup-plugin-typescript2'; +import typescript from 'typescript'; + +export default { + input: { + logger: './src/index.ts', + }, + plugins: [ + ts({ + typescript, + tsconfig: 'tsconfig.main.json', + cacheRoot: 'target/.rts2_cache', + }), + nodeResolve(), + sourcemaps(), + ], + external: externalModules(), + output: { + dir: '.', + format: 'esm', + sourcemap: true, + entryFileNames: 'dist/[name].js', + plugins: [ + flatDts({ + tsconfig: 'tsconfig.main.json', + lib: ['ES2019'], + compilerOptions: { + declarationMap: true, + }, + internal: ['**/impl/**', '**/*.impl'], + }), + ], + }, +}; diff --git a/src/console-logger.ts b/src/console-logger.ts new file mode 100644 index 0000000..6fe8dc2 --- /dev/null +++ b/src/console-logger.ts @@ -0,0 +1,38 @@ +import { Logger } from './logger'; + +const consoleLogger$log = (log: (...args: unknown[]) => void) => (...args: unknown[]) => { + if (!args.length) { + log(); + } else { + // Avoid formatting. + log('%O', ...args); + } + }; + +const consoleLogger$error = (/*#__PURE__*/ consoleLogger$log((...args) => console.error(...args))); +const consoleLogger$warn = (/*#__PURE__*/ consoleLogger$log((...args) => console.warn(...args))); +const consoleLogger$info = (/*#__PURE__*/ consoleLogger$log((...args) => console.info(...args))); +const consoleLogger$debug = (/*#__PURE__*/ consoleLogger$log((...args) => console.debug(...args))); + +/** + * Logger instance that logs to console. + */ +export const consoleLogger: Logger = { + + get error() { + return consoleLogger$error; + }, + + get warn() { + return consoleLogger$warn; + }, + + get info() { + return consoleLogger$info; + }, + + get debug() { + return consoleLogger$debug; + }, + +}; diff --git a/src/console.logger.spec.ts b/src/console.logger.spec.ts new file mode 100644 index 0000000..3ca146e --- /dev/null +++ b/src/console.logger.spec.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { SpyInstance } from 'jest-mock'; +import { consoleLogger } from './console-logger'; + +describe('consoleLogger', () => { + + let logSpy: SpyInstance; + + afterEach(() => { + logSpy.mockRestore(); + }); + + describe('error', () => { + + beforeEach(() => { + logSpy = jest.spyOn(console, 'error').mockImplementation(() => { /* do not log */ }); + }); + + it('logs to console', () => { + consoleLogger.error('message', 1, 2, 3); + expect(logSpy).toHaveBeenCalledWith('%O', 'message', 1, 2, 3); + }); + it('logs empty message to console', () => { + consoleLogger.error(); + expect(logSpy).toHaveBeenCalledWith(); + }); + }); + + describe('warn', () => { + + beforeEach(() => { + logSpy = jest.spyOn(console, 'warn').mockImplementation(() => { /* do not log */ }); + }); + + it('logs to console', () => { + consoleLogger.warn('message', 1, 2, 3); + expect(logSpy).toHaveBeenCalledWith('%O', 'message', 1, 2, 3); + }); + it('logs empty message to console', () => { + consoleLogger.warn(); + expect(logSpy).toHaveBeenCalledWith(); + }); + }); + + describe('info', () => { + + beforeEach(() => { + logSpy = jest.spyOn(console, 'info').mockImplementation(() => { /* do not log */ }); + }); + + it('logs to console', () => { + consoleLogger.info('message', 1, 2, 3); + expect(logSpy).toHaveBeenCalledWith('%O', 'message', 1, 2, 3); + }); + it('logs empty message to console', () => { + consoleLogger.info(); + expect(logSpy).toHaveBeenCalledWith(); + }); + }); + + describe('debug', () => { + + beforeEach(() => { + logSpy = jest.spyOn(console, 'debug').mockImplementation(() => { /* do not log */ }); + }); + + it('logs to console', () => { + consoleLogger.debug('message', 1, 2, 3); + expect(logSpy).toHaveBeenCalledWith('%O', 'message', 1, 2, 3); + }); + it('logs empty message to console', () => { + consoleLogger.debug(); + expect(logSpy).toHaveBeenCalledWith(); + }); + }); + +}); diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1f2f61a --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +/** + * @packageDocumentation + * @module Module @proc7ts/logger + */ +export * from './console-logger'; +export * from './logger'; +export * from './silent-logger'; diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..8bf9966 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,34 @@ +/** + * Basic logger interface. + */ +export interface Logger { + + /** + * Logs error. + * + * @param args - Log message and arguments. + */ + error(...args: any[]): void; + + /** + * Logs warning. + * + * @param args - Log message and arguments. + */ + warn(...args: any[]): void; + + /** + * Logs informational message. + * + * @param args - Log message and arguments. + */ + info(...args: any[]): void; + + /** + * Logs debug message. + * + * @param args - Log message and arguments. + */ + debug(...args: any[]): void; + +} diff --git a/src/silent-logger.spec.ts b/src/silent-logger.spec.ts new file mode 100644 index 0000000..2fedf62 --- /dev/null +++ b/src/silent-logger.spec.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from '@jest/globals'; +import { silentLogger } from './silent-logger'; + +describe('silentLogger', () => { + describe('error', () => { + it('logs nothing', () => { + // noinspection JSVoidFunctionReturnValueUsed + expect(silentLogger.error('message')).toBeUndefined(); + }); + }); + + describe('warn', () => { + it('logs nothing', () => { + expect(silentLogger.warn).toBe(silentLogger.error); + }); + }); + + describe('info', () => { + it('logs nothing', () => { + expect(silentLogger.info).toBe(silentLogger.error); + }); + }); + + describe('debug', () => { + it('logs nothing', () => { + expect(silentLogger.debug).toBe(silentLogger.error); + }); + }); +}); diff --git a/src/silent-logger.ts b/src/silent-logger.ts new file mode 100644 index 0000000..bc2577f --- /dev/null +++ b/src/silent-logger.ts @@ -0,0 +1,26 @@ +import { Logger } from './logger'; + +const silentLogger$log = (..._args: unknown[]): void => { /* Do not log */ }; + +/** + * Logger instance that suppresses logging, i.e. never logs anything. + */ +export const silentLogger: Logger = { + + get error() { + return silentLogger$log; + }, + + get warn() { + return silentLogger$log; + }, + + get info() { + return silentLogger$log; + }, + + get debug() { + return silentLogger$log; + }, + +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..72a01b2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "moduleResolution": "Node", + "module": "ES2015", + "target": "ES2019", + "strict": true, + "experimentalDecorators": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "noEmitHelpers": true, + "lib": [ + "DOM", + "ES2019" + ], + "types": [], + "outDir": "target/js", + "sourceMap": true, + "newLine": "LF" + }, + "include": [ + "src/**/*" + ] +} diff --git a/tsconfig.main.json b/tsconfig.main.json new file mode 100644 index 0000000..930e183 --- /dev/null +++ b/tsconfig.main.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "files": [ + "./src/index.ts" + ], + "include": [] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..2d23de1 --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "esModuleInterop": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "importHelpers": false, + "noEmitHelpers": false + } +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..26bfe09 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,6 @@ +{ + "entryPoints": ["src/index.ts"], + "name": "Amend", + "out": "./target/typedoc", + "tsconfig": "tsconfig.main.json" +}