From ddbe00d2a51430348fcf4e4e29807cc43a37cf49 Mon Sep 17 00:00:00 2001 From: Karl-Philipp Wulfert Date: Wed, 10 Jul 2019 16:12:21 +0200 Subject: [PATCH] feat: add transformations Fixes #9 --- .gitlab-ci.yml | 2 + README.md | 18 +- package-lock.json | 182 +++++---------------- package.json | 20 ++- src/common.ts | 3 +- src/logger.ts | 62 +++++-- src/transformation.ts | 28 ++++ src/transformations/add-log-level.ts | 32 ++++ src/transformations/colorize.ts | 47 ++++++ src/transformations/timestamp.ts | 45 +++++ test/common.spec.ts | 4 +- test/logger.spec.ts | 48 +++++- test/smtp.spec.ts | 5 +- test/transformations/add-log-level.spec.ts | 27 +++ test/transformations/colorize.spec.ts | 28 ++++ test/transformations/timestamp.spec.ts | 35 ++++ test/transport.spec.ts | 4 +- 17 files changed, 415 insertions(+), 175 deletions(-) create mode 100644 src/transformation.ts create mode 100644 src/transformations/add-log-level.ts create mode 100644 src/transformations/colorize.ts create mode 100644 src/transformations/timestamp.ts create mode 100755 test/transformations/add-log-level.spec.ts create mode 100755 test/transformations/colorize.spec.ts create mode 100755 test/transformations/timestamp.spec.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05b09cfb..2b8a6843 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,6 +22,8 @@ build: - lib test: + variables: + FORCE_COLOR: '1' # see https://github.com/chalk/chalk/issues/203 stage: test script: - npm test diff --git a/README.md b/README.md index a6cb9777..c66d19b4 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![pipeline status](https://gitlab.com/openstapps/logger/badges/master/pipeline.svg)](https://gitlab.com/openstapps/logger/commits/master) [![coverage report](https://gitlab.com/openstapps/logger/badges/master/coverage.svg)](https://gitlab.com/openstapps/logger/commits/master) -This is a simple logger for TypeScript projects with colors for console output. +This is a simple logger for TypeScript projects with transformations for the log output. Logs are only printed if their log level is equal or higher than the defined log level. ## Log Levels + Available log levels are: - 1 - INFO - 2 - LOG @@ -25,15 +26,18 @@ If you want to use logger in production (`NODE_ENV=production`) and allow all tr `ALLOW_NO_TRANSPORT` to `true`. ## SMTP + This class also provides a simple implementation of an SMTP transport which can be used as a `TransportWithVerification` for the logger. You can use this to transport errors of the logger or to transport mails of your own monitoring solution. ### Usage + You can instatiate it with a config or it will check for a config in the environment variables. Environment variables can overwrite the actual config values. Environment variables are: + * SMTP_AUTH_USER: SMTP username * SMTP_AUTH_PASSWORD: SMTP password * SMTP_HOST: SMTP host @@ -43,3 +47,15 @@ Environment variables are: * SMTP_SENDER_MAIL: sender of the mail * SMTP_SENDER_NAME: name of the sender * SMTP_SECURE: `true` to enable tls + +## Transformations + +By default the logger will only add the log level to the message. + +You can change this behavior by setting other Transformers via `Logger.setTransformations`. If you do so, mind the order of the transformers. + +You may choose from the following: + +* `AddLogLevel`, which prepends the output with the log level +* `Colorize`, which colorizes the output according to the log level +* `Timestamp`, which prepends a timestamp to the output diff --git a/package-lock.json b/package-lock.json index 555e832d..7bb86c1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,9 +73,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.4.tgz", + "integrity": "sha512-Na84uwyImZZc3FKf4aUF1tysApzwf3p2yuFBIyBfbzT5glzKTdvYI4KVW4kcgjrzoGUjC7w3YyCHcJKaRxsr2Q==", "dev": true, "requires": { "regenerator-runtime": "^0.13.2" @@ -132,9 +132,9 @@ } }, "@openstapps/configuration": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@openstapps/configuration/-/configuration-0.18.0.tgz", - "integrity": "sha512-Ufi3jzCozVqCymNeaeRzuOHO2Yd5qXJ10uF4xNHk6Q4LFD9NAMMBkYbawkjmecZoNR+Llqs4AnwSxIkuEAxcxA==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@openstapps/configuration/-/configuration-0.21.0.tgz", + "integrity": "sha512-0PR8es12HJqNOoTHc4XwfNgS3WDpsrhXvkyty8sid6mcyeaOMmhoj680LwJlqcNsRP9DqqPc+s0JB8zKd0NOig==", "dev": true, "requires": { "@types/node": "10.14.7", @@ -142,8 +142,8 @@ "@types/yaml": "1.0.2", "chalk": "2.4.2", "commander": "2.20.0", - "semver": "6.1.0", - "tslint": "5.16.0", + "semver": "6.1.1", + "tslint": "5.17.0", "tslint-eslint-rules": "5.4.0", "yaml": "1.6.0" }, @@ -155,15 +155,15 @@ "dev": true }, "semver": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.0.tgz", - "integrity": "sha512-kCqEOOHoBcFs/2Ccuk4Xarm/KiWRSLEX9CAZF8xkJ6ZPlIoTZ8V5f7J16vYLJqDbR7KrxTJpR2lqjIEm2Qx9cQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", "dev": true }, "tslint": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", - "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", + "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -172,7 +172,7 @@ "commander": "^2.12.1", "diff": "^3.2.0", "glob": "^7.1.1", - "js-yaml": "^3.13.0", + "js-yaml": "^3.13.1", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "resolve": "^1.3.2", @@ -191,42 +191,6 @@ } } }, - "@sinonjs/commons": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", - "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", - "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.0.2", - "array-from": "^2.1.1", - "lodash": "^4.17.11" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, "@types/chai": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", @@ -314,9 +278,9 @@ "dev": true }, "@types/node": { - "version": "10.14.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.8.tgz", - "integrity": "sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==" + "version": "10.14.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.12.tgz", + "integrity": "sha512-QcAKpaO6nhHLlxWBvpc4WeLrTvPqlHOvaj0s5GriKkA1zq+bsFBPpfYCvQhLqLgYlIko8A9YrPdaMHCo5mBcpg==" }, "@types/nodemailer": { "version": "6.2.0", @@ -342,12 +306,6 @@ "@types/node": "*" } }, - "@types/sinon": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.0.12.tgz", - "integrity": "sha512-fo0MWpVPSUrnZZhp9wyu+hhI3VJ9+Jhs+PWrokBTg3d2ryNPDOAWF1csIhQuYWBTn7KdZzXpRgpX2o6cwOlPWg==", - "dev": true - }, "@types/yaml": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.0.2.tgz", @@ -426,12 +384,6 @@ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "dev": true }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", @@ -1132,9 +1084,9 @@ } }, "flatted": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.0.tgz", - "integrity": "sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==" }, "foreground-child": { "version": "1.5.6", @@ -1935,12 +1887,6 @@ "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", "dev": true }, - "just-extend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", - "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", - "dev": true - }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -2024,12 +1970,6 @@ "chalk": "^2.0.1" } }, - "lolex": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", - "integrity": "sha512-UHuOBZ5jjsKuzbB/gRNNW8Vg8f00Emgskdq2kvZxgBJCS0aqquAuXai/SkWORlKeZEiNQWZjFZOqIUcH9LqKCw==", - "dev": true - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -2435,6 +2375,11 @@ "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", "dev": true }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -2459,27 +2404,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nise": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.4.10.tgz", - "integrity": "sha512-sa0RRbj53dovjc7wombHmVli9ZihXbXCQ2uH3TNm03DyvOSIQbxg+pbqDKrk2oxMK1rtLGVlKxcB9rrc6X5YjA==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.1.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^2.3.2", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "2.7.5", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.5.tgz", - "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==", - "dev": true - } - } - }, "node-environment-flags": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", @@ -2952,15 +2876,6 @@ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -3279,21 +3194,6 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "sinon": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", - "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.1", - "diff": "^3.5.0", - "lolex": "^4.0.1", - "nise": "^1.4.10", - "supports-color": "^5.5.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3645,9 +3545,9 @@ "dev": true }, "ts-node": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.2.0.tgz", - "integrity": "sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", + "integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==", "dev": true, "requires": { "arg": "^4.1.0", @@ -3666,15 +3566,15 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "dev": true }, "tslint": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.17.0.tgz", - "integrity": "sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.18.0.tgz", + "integrity": "sha512-Q3kXkuDEijQ37nXZZLKErssQVnwCV/+23gFEMROi8IlbaBG6tXqLPQJ5Wjcyt/yHPKBC+hD5SzuGaMora+ZS6w==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -3710,9 +3610,9 @@ "dev": true }, "tsutils": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.13.0.tgz", - "integrity": "sha512-wRtEjVU8Su72sDIDoqno5Scwt8x4eaF0teKO3m4hu8K1QFPnIZMM88CLafs2tapUeWnY9SwwO3bWeOt2uauBcg==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.14.0.tgz", + "integrity": "sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -3775,9 +3675,9 @@ "dev": true }, "typescript": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", - "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 488db539..3d19f376 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "prepublishOnly": "npm ci && npm run build", "preversion": "npm run prepublishOnly", "push": "git push && git push origin \"v$npm_package_version\"", - "test": "nyc mocha --require ts-node/register --ui mocha-typescript 'test/*.ts'", + "test": "nyc mocha --require ts-node/register --ui mocha-typescript 'test/**/*.spec.ts'", "tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'" }, "author": "Karl-Philipp Wulfert ", @@ -48,15 +48,17 @@ "html", "text-summary" ], + "require": [ + "ts-node/register" + ], "statements": 95 }, "devDependencies": { - "@openstapps/configuration": "0.18.0", + "@openstapps/configuration": "0.21.0", "@types/chai": "4.1.7", "@types/chai-as-promised": "7.1.0", "@types/chai-spies": "1.0.0", "@types/mocha": "5.2.7", - "@types/sinon": "7.0.12", "chai": "4.2.0", "chai-as-promised": "7.1.1", "chai-spies": "1.0.0", @@ -66,17 +68,17 @@ "nyc": "14.1.1", "prepend-file-cli": "1.0.6", "rimraf": "2.6.3", - "sinon": "7.3.2", - "ts-node": "8.2.0", - "tslint": "5.17.0", + "ts-node": "8.3.0", + "tslint": "5.18.0", "typedoc": "0.14.2", - "typescript": "3.5.1" + "typescript": "3.5.3" }, "dependencies": { - "@types/node": "10.14.8", + "@types/node": "10.14.12", "@types/nodemailer": "6.2.0", "chalk": "2.4.2", - "flatted": "2.0.0", + "flatted": "2.0.1", + "moment": "2.24.0", "nodemailer": "6.2.1" } } diff --git a/src/common.ts b/src/common.ts index 130e07f4..bed167f9 100644 --- a/src/common.ts +++ b/src/common.ts @@ -12,14 +12,13 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ +import {Transport, VerifiableTransport} from './transport'; /** * A recursive partial object * * Copied from https://stackoverflow.com/a/51365037 */ -import {Transport, VerifiableTransport} from './transport'; - export type RecursivePartial = { [P in keyof T]?: T[P] extends Array ? Array> : diff --git a/src/logger.ts b/src/logger.ts index a6d7a605..89b60367 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -12,9 +12,10 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -import chalk from 'chalk'; import {stringify} from 'flatted'; import {isNodeEnvironment, isProductiveNodeEnvironment} from './common'; +import {Transformation} from './transformation'; +import {AddLogLevel} from './transformations/add-log-level'; import {Transport} from './transport'; /** @@ -28,7 +29,12 @@ function hasStAppsLogLevel(something: object): something is { STAPPS_LOG_LEVEL: } /** - * Logger with colors, loglevel and transport + * A log level + */ +export type LogLevel = 'INFO' | 'LOG' | 'WARN' | 'ERROR' | 'OK'; + +/** + * A logger with transports and transformations * * Log level can be defined by setting the environment variable STAPPS_LOG_LEVEL to a valid log level. Log levels are * set in a binary way. For example STAPPS_LOG_LEVEL=12 does result in logs only for `Logger.warn` and `Logger.error`. @@ -51,7 +57,7 @@ export class Logger { /** * Log levels */ - private static readonly logLevels = [ + private static readonly logLevels: LogLevel[] = [ 'INFO', 'LOG', 'WARN', @@ -64,17 +70,43 @@ export class Logger { */ private static readonly logLevelSum = Math.pow(Logger.binaryBase, Logger.logLevels.length) - 1; + /** + * Transformers for log output + */ + private static transformations?: Transformation[] = [ + new AddLogLevel(), + ]; + /** * Transport for errors */ private static transport?: Transport; + /** + * Apply transformations to an output + * + * @param logLevel Log level of the output + * @param output Output to apply transformations to + */ + private static applyTransformers(logLevel: LogLevel, output: string): string { + if (!Array.isArray(Logger.transformations) || Logger.transformations.length === 0) { + return output; + } + + let transformedOutput = output; + for (const transformation of Logger.transformations) { + transformedOutput = transformation.transform(logLevel, transformedOutput); + } + + return transformedOutput; + } + /** * Check if intended log level is allowed in environment log level * * @param logLevel Log level to check */ - private static checkLogLevel(logLevel: string): boolean { + private static checkLogLevel(logLevel: LogLevel): boolean { const logLevelNumber = Math.pow(Logger.binaryBase, Logger.logLevels.indexOf(logLevel) + 1) - 1; // tslint:disable-next-line:no-bitwise @@ -110,7 +142,7 @@ export class Logger { } /* tslint:disable-next-line:no-console */ - console.error(chalk.bold.red(`[ERROR] ${Logger.stringifyArguments(...args)}`)); + console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...args))); if (isProductiveNodeEnvironment()) { if (typeof Logger.transport !== 'undefined') { @@ -134,7 +166,7 @@ export class Logger { } /* tslint:disable-next-line:no-console */ - console.info(chalk.cyan(`[INFO] ${Logger.stringifyArguments(...args)}`)); + console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...args))); } /** @@ -146,8 +178,7 @@ export class Logger { throw new Error(`Productive environment doesn't set a transport for error notifications.`); } - /* tslint:disable-next-line:no-console */ - console.warn(chalk.yellow(`Productive environment doesn't set a transport for error notifications.`)); + Logger.warn(`Productive environment doesn't set a transport for error notifications.`); } } @@ -162,7 +193,7 @@ export class Logger { } /* tslint:disable-next-line:no-console */ - console.log(chalk.white(`[LOG] ${Logger.stringifyArguments(...args)}`)); + console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...args))); } /** @@ -176,7 +207,16 @@ export class Logger { } /* tslint:disable-next-line:no-console */ - console.log(chalk.bold.green(`[OK] ${Logger.stringifyArguments(...args)}`)); + console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...args))); + } + + /** + * Set transformations for log output + * + * @param transformations List of transformations + */ + public static setTransformations(transformations: Transformation[]) { + Logger.transformations = transformations; } /** @@ -224,6 +264,6 @@ export class Logger { } /* tslint:disable-next-line:no-console */ - console.warn(chalk.yellow(`[WARN] ${Logger.stringifyArguments(...args)}`)); + console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...args))); } } diff --git a/src/transformation.ts b/src/transformation.ts new file mode 100644 index 00000000..5d0cf460 --- /dev/null +++ b/src/transformation.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {LogLevel} from './logger'; + +/** + * A transformer for log output + */ +export interface Transformation { + /** + * Transform an output + * + * @param logLevel Log level to transform output for + * @param output Output to transform + */ + transform(logLevel: LogLevel, output: string): string; +} diff --git a/src/transformations/add-log-level.ts b/src/transformations/add-log-level.ts new file mode 100644 index 00000000..02ded89b --- /dev/null +++ b/src/transformations/add-log-level.ts @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {LogLevel} from '../logger'; +import {Transformation} from '../transformation'; + +/** + * Transformation that adds the log level to output + */ +export class AddLogLevel implements Transformation { + /** + * Add log level to output + * + * @param logLevel Log level to add to output + * @param output Output to colorize + */ + // tslint:disable-next-line:prefer-function-over-method + transform(logLevel: LogLevel, output: string): string { + return `[${logLevel}] ${output}`; + } +} diff --git a/src/transformations/colorize.ts b/src/transformations/colorize.ts new file mode 100644 index 00000000..fdf1b140 --- /dev/null +++ b/src/transformations/colorize.ts @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import chalk, {Chalk} from 'chalk'; +import {LogLevel} from '../logger'; +import {Transformation} from '../transformation'; + +/** + * Transformation that colorizes log output + */ +export class Colorize implements Transformation { + /** + * Instantiate a new colorize transformation + * + * @param logLevelToColor Map from log level to color transformation to apply + */ + constructor(private readonly logLevelToColor: { [k in LogLevel]: Chalk; } = { + ERROR: chalk.bold.red, + INFO: chalk.cyan, + LOG: chalk.white, + OK: chalk.bold.green, + WARN: chalk.yellow, + }) { + // noop + } + + /** + * Colorize log output + * + * @param logLevel Log level to choose color for + * @param output Output to colorize + */ + transform(logLevel: LogLevel, output: string): string { + return this.logLevelToColor[logLevel](output); + } +} diff --git a/src/transformations/timestamp.ts b/src/transformations/timestamp.ts new file mode 100644 index 00000000..11cc6508 --- /dev/null +++ b/src/transformations/timestamp.ts @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import * as moment from 'moment'; +import {LogLevel} from '../logger'; +import {Transformation} from '../transformation'; + +/** + * Transformation that adds a timestamp to output + */ +export class Timestamp implements Transformation { + /** + * Instantiate a new timestamp transformation + * + * @see https://momentjs.com/docs/#/displaying/format/ + * + * @param format Format for timestamps + */ + constructor(private readonly format = 'LLLL') { + // noop + } + + /** + * Add timestamp to output + * + * @param _logLevel Log level to add timestamp to output for + * @param output Output to add timestamp to + */ + transform(_logLevel: LogLevel, output: string): string { + const now = moment(); + + return `[${now.format(this.format)}] ${output}`; + } +} diff --git a/test/common.spec.ts b/test/common.spec.ts index a3d2697c..cc282adb 100644 --- a/test/common.spec.ts +++ b/test/common.spec.ts @@ -13,7 +13,7 @@ * this program. If not, see . */ import {expect} from 'chai'; -import {slow, suite, test, timeout} from 'mocha-typescript'; +import {suite, test} from 'mocha-typescript'; import { deleteUndefinedProperties, isNodeEnvironment, @@ -21,7 +21,7 @@ import { isProductiveNodeEnvironment, } from '../src/common'; -@suite(timeout(2000), slow(1000)) +@suite() export class CommonSpec { @test deleteUndefinedProperties1() { diff --git a/test/logger.spec.ts b/test/logger.spec.ts index a85aeeaa..2f6b41e3 100644 --- a/test/logger.spec.ts +++ b/test/logger.spec.ts @@ -16,23 +16,30 @@ import * as chai from 'chai'; import {expect} from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as chaiSpies from 'chai-spies'; -import {slow, suite, timeout} from 'mocha-typescript'; +import {suite} from 'mocha-typescript'; import {Logger} from '../src/logger'; +import {AddLogLevel} from '../src/transformations/add-log-level'; import {DummyTransport} from './dummyTransport'; chai.should(); chai.use(chaiSpies); chai.use(chaiAsPromised); -@suite(timeout(2000), slow(1000)) +@suite() export class LoggerSpec { - static sandbox: any; + static sandbox: ChaiSpies.Sandbox; static before() { LoggerSpec.sandbox = chai.spy.sandbox(); } before() { + Logger.setTransformations([ + new AddLogLevel(), + ]); + } + + after() { LoggerSpec.sandbox.restore(); } @@ -44,6 +51,7 @@ export class LoggerSpec { @test async error() { const spy = LoggerSpec.sandbox.on(console, 'error', () => { + // noop }); await Logger.error('Foobar'); @@ -56,6 +64,7 @@ export class LoggerSpec { @test async 'error in productive environment'() { const spy = LoggerSpec.sandbox.on(console, 'error', () => { + // noop }); const nodeEnv = process.env.NODE_ENV; @@ -84,6 +93,7 @@ export class LoggerSpec { @test async 'error without output'() { const spy = LoggerSpec.sandbox.on(console, 'error', () => { + // noop }); process.env.STAPPS_LOG_LEVEL = '0'; @@ -98,6 +108,7 @@ export class LoggerSpec { @test async 'error with Error'() { const spy = LoggerSpec.sandbox.on(console, 'error', () => { + // noop }); await Logger.error(new Error()); @@ -110,6 +121,7 @@ export class LoggerSpec { @test info() { const spy = LoggerSpec.sandbox.on(console, 'info', () => { + // noop }); Logger.info('Foobar'); @@ -122,6 +134,7 @@ export class LoggerSpec { @test 'info without output'() { const spy = LoggerSpec.sandbox.on(console, 'info', () => { + // noop }); process.env.STAPPS_LOG_LEVEL = '0'; @@ -162,6 +175,7 @@ export class LoggerSpec { }).to.throw(); const spy = LoggerSpec.sandbox.on(console, 'warn', () => { + // noop }); process.env.ALLOW_NO_TRANSPORT = 'true'; @@ -180,6 +194,7 @@ export class LoggerSpec { @test log() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); Logger.log('Foobar'); @@ -192,6 +207,7 @@ export class LoggerSpec { @test 'log without output'() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); process.env.STAPPS_LOG_LEVEL = '0'; @@ -206,6 +222,7 @@ export class LoggerSpec { @test ok() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); Logger.ok('Foobar'); @@ -218,6 +235,7 @@ export class LoggerSpec { @test 'ok without output'() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); process.env.STAPPS_LOG_LEVEL = '0'; @@ -226,7 +244,7 @@ export class LoggerSpec { delete process.env.STAPPS_LOG_LEVEL; - expect(spy).not.to.have.been.called() + expect(spy).not.to.have.been.called(); } @test @@ -240,6 +258,7 @@ export class LoggerSpec { @test 'stringify'() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); Logger.log('foo', 'bar'); @@ -252,10 +271,11 @@ export class LoggerSpec { @test 'stringify object'() { const spy = LoggerSpec.sandbox.on(console, 'log', () => { + // noop }); Logger.log({ - foo: 'bar' + foo: 'bar', }); expect(spy).to.have.been.called(); @@ -266,6 +286,7 @@ export class LoggerSpec { @test warn() { const spy = LoggerSpec.sandbox.on(console, 'warn', () => { + // noop }); Logger.warn('Foobar'); @@ -278,6 +299,7 @@ export class LoggerSpec { @test 'warn without output'() { const spy = LoggerSpec.sandbox.on(console, 'warn', () => { + // noop }); process.env.STAPPS_LOG_LEVEL = '0'; @@ -301,6 +323,7 @@ export class LoggerSpec { }; const stub = LoggerSpec.sandbox.on(console, 'info', () => { + // noop }); Logger.info('Foobar'); @@ -311,4 +334,19 @@ export class LoggerSpec { expect(stub).not.to.have.been.called(); } + + @test + 'output without transformations'() { + Logger.setTransformations([]); + + const stub = LoggerSpec.sandbox.on(console, 'log', () => { + // noop + }); + const applyTransformationsSpy = LoggerSpec.sandbox.on(Logger, 'applyTransformations'); + + Logger.log('Foobar'); + + expect(stub).to.have.been.called.with('Foobar'); + expect(applyTransformationsSpy).not.to.have.been.called; + } } diff --git a/test/smtp.spec.ts b/test/smtp.spec.ts index bc6e6e23..8d15130c 100644 --- a/test/smtp.spec.ts +++ b/test/smtp.spec.ts @@ -13,10 +13,10 @@ * this program. If not, see . */ import {expect} from 'chai'; -import {slow, suite, test, timeout} from 'mocha-typescript'; +import {suite, test} from 'mocha-typescript'; import {SMTP} from '../src/smtp'; -@suite(timeout(2000), slow(1000)) +@suite() export class SMTPSpec { /* tslint:disable:member-ordering */ @test @@ -103,5 +103,6 @@ export class SMTPSpec { mailValidation17() { expect(SMTP.isValidEmailAddress('stordeur@campus')).to.be.false; } + /* tslint:enable:member-ordering */ } diff --git a/test/transformations/add-log-level.spec.ts b/test/transformations/add-log-level.spec.ts new file mode 100755 index 00000000..159be000 --- /dev/null +++ b/test/transformations/add-log-level.spec.ts @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {expect} from 'chai'; +import {suite, test} from 'mocha-typescript'; +import {AddLogLevel} from '../../src/transformations/add-log-level'; + +@suite() +export class AddLogLevelSpec { + @test + 'transform'() { + const transformation = new AddLogLevel(); + + expect(transformation.transform('ERROR', 'Foobar')).to.be.equal('[ERROR] Foobar'); + } +} diff --git a/test/transformations/colorize.spec.ts b/test/transformations/colorize.spec.ts new file mode 100755 index 00000000..988962e5 --- /dev/null +++ b/test/transformations/colorize.spec.ts @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {expect} from 'chai'; +import {suite, test} from 'mocha-typescript'; +import {Colorize} from '../../src/transformations/colorize'; + +@suite() +export class ColorizeSpec { + @test + 'transform'() { + const transformation = new Colorize(); + + expect(transformation.transform('ERROR', 'Foobar')).to.be.equal('\u001b[1m\u001b[31mFoobar\u001b[39m\u001b[22m'); + expect(transformation.transform('LOG', 'Foobar')).to.be.equal('\u001b[37mFoobar\u001b[39m'); + } +} diff --git a/test/transformations/timestamp.spec.ts b/test/transformations/timestamp.spec.ts new file mode 100755 index 00000000..fd5b0b42 --- /dev/null +++ b/test/transformations/timestamp.spec.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2019 StApps + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ +import {expect} from 'chai'; +import {suite, test} from 'mocha-typescript'; +import {Timestamp} from '../../src/transformations/timestamp'; +import moment = require('moment'); + +@suite() +export class ColorizeSpec { + @test + 'default'() { + const transformation = new Timestamp(); + + expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('LLLL')}] Foobar`); + } + + @test + 'different format'() { + const transformation = new Timestamp('DD.MM.YYYY'); + + expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('DD.MM.YYYY')}] Foobar`); + } +} diff --git a/test/transport.spec.ts b/test/transport.spec.ts index c79fd96f..e75f9d2f 100644 --- a/test/transport.spec.ts +++ b/test/transport.spec.ts @@ -13,11 +13,11 @@ * this program. If not, see . */ import {expect} from 'chai'; -import {slow, suite, test, timeout} from 'mocha-typescript'; +import {suite, test} from 'mocha-typescript'; import {isTransportWithVerification} from '../src/common'; import {DummyTransport, VerifiableDummyTransport} from './dummyTransport'; -@suite(timeout(2000), slow(1000)) +@suite() export class TransportSpec { @test isNotTransportWithVerification() {