Compare commits

...

47 Commits

Author SHA1 Message Date
Rainer Killinger
3ae31f0b2d 0.8.1 2022-05-27 16:07:20 +02:00
Rainer Killinger
15286dd717 refactor: update dependencies 2022-05-27 16:06:38 +02:00
Rainer Killinger
f75e532d44 docs: update changelog 2021-12-14 12:37:03 +01:00
Rainer Killinger
70ce8ea0d6 0.8.0 2021-12-14 12:37:02 +01:00
Rainer Killinger
3c3a571dca refactor: update dependencies 2021-12-14 12:26:04 +01:00
openstappsbot
a11a24225b refactor: update all 2021-08-02 07:09:50 +00:00
Rainer Killinger
3c582cebc6 docs: update changelog 2021-05-18 20:11:55 +02:00
Rainer Killinger
e486d25537 0.7.0 2021-05-18 20:11:50 +02:00
openstappsbot
308965a2fa refactor: update all 2021-05-13 07:10:59 +00:00
Rainer Killinger
78e7d7456b docs: update changelog 2021-04-06 16:30:31 +02:00
Rainer Killinger
822823ea1a 0.6.0 2021-04-06 16:30:30 +02:00
Rainer Killinger
2df53fcd95 refactor: bump version to match npmjs release 2021-04-06 16:30:10 +02:00
Rainer Killinger
e6bc84adf5 refactor: update @openstapps dependencies 2021-04-06 16:26:42 +02:00
Rainer Killinger
30d2108634 refactor: update @types/node for node version 14 2021-04-06 16:05:12 +02:00
Rainer Killinger
9de064756a refactor: update dependencies 2020-03-23 16:32:46 +01:00
Rainer Killinger
edc6e6fad5 feat: add EXIT log level 2020-03-23 16:32:41 +01:00
Rainer Killinger
3fb99ad896 test: add test for log level exclusiveness 2020-03-10 12:49:04 +01:00
Rainer Killinger
100b921488 fix: log level exclusiveness calculation 2020-03-10 12:49:04 +01:00
Karl-Philipp Wulfert
6a1499725c docs: update changelog 2019-07-12 10:55:45 +02:00
Karl-Philipp Wulfert
37e25800dc 0.4.0 2019-07-12 10:55:44 +02:00
Karl-Philipp Wulfert
ddbe00d2a5 feat: add transformations
Fixes #9
2019-07-11 12:53:40 +02:00
Karl-Philipp Wulfert
d05fd8a2a5 docs: update changelog 2019-06-05 11:45:02 +02:00
Karl-Philipp Wulfert
b2053c116e 0.3.1 2019-06-05 11:45:02 +02:00
Karl-Philipp Wulfert
d2cb99f70f fix: correct meta information for imports 2019-06-05 11:43:49 +02:00
Karl-Philipp Wulfert
1e6e951dea docs: update changelog 2019-06-04 15:31:04 +02:00
Karl-Philipp Wulfert
2485b68b4a 0.3.0 2019-06-04 15:31:03 +02:00
Karl-Philipp Wulfert
c30e83086a test: adjust tests 2019-06-03 13:20:44 +02:00
Karl-Philipp Wulfert
15a61f6932 refactor: adjust code to updated dependencies 2019-06-03 13:20:44 +02:00
Karl-Philipp Wulfert
c29420805e build: update dependencies 2019-06-03 13:20:43 +02:00
Karl-Philipp Wulfert
59af9607b6 docs: update changelog 2019-05-27 16:29:33 +02:00
Karl-Philipp Wulfert
22b1b86b40 0.2.1 2019-05-27 16:29:30 +02:00
Karl-Philipp Wulfert
39a1f9ae8f ci: fix automatic publish 2019-05-27 16:29:10 +02:00
Karl-Philipp Wulfert
d5f33d45e2 docs: update changelog 2019-05-27 13:23:33 +02:00
Karl-Philipp Wulfert
305c158a76 0.2.0 2019-05-27 13:23:29 +02:00
Karl-Philipp Wulfert
d9c4ce48dc feat: add automatic publishing
Fixes #6
2019-05-23 13:53:10 +02:00
Karl-Philipp Wulfert
fb9791767d docs: update changelog 2019-05-07 10:37:51 +02:00
Karl-Philipp Wulfert
5b5080dcc7 0.1.0 2019-05-07 10:37:47 +02:00
Karl-Philipp Wulfert
3ba2b0bf3c build: update dependencies and adjust configuration 2019-04-30 14:40:51 +02:00
Karl-Philipp Wulfert
766205049b test: adjust tests
Fixes #1
2019-04-30 14:40:46 +02:00
Karl-Philipp Wulfert
1ae3beb347 refactor: rebuild logger to be static
Fixes #4
2019-04-30 14:40:38 +02:00
Karl-Philipp Wulfert
7ef29ef56d build: adjust scripts 2019-04-30 14:40:38 +02:00
Karl-Philipp Wulfert
f8f94dc026 ci: add audit stage 2019-04-30 14:40:38 +02:00
Karl-Philipp Wulfert
370658fa67 ci: use correct image for ci 2019-04-30 14:40:38 +02:00
Karl-Philipp Wulfert
f63be608b8 build: update dependencies
Fixes #5
2019-04-30 14:40:32 +02:00
Karl-Philipp Wulfert
2b54af6c7e build: replace circular-json with flatted
Fixes #2
2019-02-14 13:20:36 +01:00
Anselm Stordeur
f26153d970 build: add prepublishOnly script 2019-01-16 19:40:00 +01:00
Anselm Stordeur
42713f061a docs: update changelog 2019-01-16 19:28:34 +01:00
28 changed files with 4354 additions and 3394 deletions

View File

@@ -1,4 +1,3 @@
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
# editorconfig.org # editorconfig.org
root = true root = true
@@ -7,7 +6,6 @@ root = true
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true

29
.gitignore vendored
View File

@@ -20,7 +20,7 @@ coverage
# nyc test coverage # nyc test coverage
.nyc_output .nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt .grunt
# Bower dependency directory (https://bower.io/) # Bower dependency directory (https://bower.io/)
@@ -29,14 +29,14 @@ bower_components
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
node_modules/ node_modules/
jspm_packages/ jspm_packages/
# Typescript v1 declaration files # TypeScript v1 declaration files
typings/ typings/
# Optional npm cache directory # Optional npm cache directory
@@ -57,6 +57,29 @@ typings/
# dotenv environment variables file # dotenv environment variables file
.env .env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
#DynamoDB Local files
.dynamodb/
########## end of https://github.com/github/gitignore/blob/master/Node.gitignore
# ignore ide files # ignore ide files
.idea .idea
.vscode .vscode

View File

@@ -1,9 +1,8 @@
image: node:lts-alpine image: registry.gitlab.com/openstapps/projectmanagement/node
cache: cache:
key: ${CI_COMMIT_REF_SLUG} key: ${CI_COMMIT_REF_SLUG}
paths: paths:
- lib
- node_modules - node_modules
before_script: before_script:
@@ -18,8 +17,13 @@ build:
stage: build stage: build
script: script:
- npm run build - npm run build
artifacts:
paths:
- lib
test: test:
variables:
FORCE_COLOR: '1' # see https://github.com/chalk/chalk/issues/203
stage: test stage: test
script: script:
- npm test - npm test
@@ -27,6 +31,36 @@ test:
paths: paths:
- coverage - coverage
audit:
stage: test
script:
- npm audit
except:
- schedules
allow_failure: true
scheduled-audit:
stage: test
script:
- npm audit
only:
- schedules
package:
dependencies:
- build
tags:
- secrecy
stage: deploy
script:
- echo "//registry.npmjs.org/:_authToken=$NPM_AUTH_TOKEN" > ~/.npmrc
- npm publish
only:
- /^v[0-9]+.[0-9]+.[0-9]+$/
artifacts:
paths:
- lib
pages: pages:
stage: deploy stage: deploy
script: script:

View File

@@ -1,10 +1,9 @@
# Ignore all files/folders by default # Ignore all files/folders by default
# See https://stackoverflow.com/a/29932318 # See https://stackoverflow.com/a/29932318
/* /*
# Except these files/folders
# Execept this files/folders
!docs
!lib !lib
lib/tsconfig.tsbuildinfo
!LICENSE !LICENSE
!package.json !package.json
!package-lock.json !package-lock.json

View File

@@ -1,3 +1,73 @@
# [0.8.0](https://gitlab.com/openstapps/logger/compare/v0.7.0...v0.8.0) (2021-12-14)
# [0.7.0](https://gitlab.com/openstapps/logger/compare/v0.6.0...v0.7.0) (2021-05-18)
# [0.6.0](https://gitlab.com/openstapps/logger/compare/v0.4.0...v0.6.0) (2021-04-06)
### Bug Fixes
* log level exclusiveness calculation ([100b921](https://gitlab.com/openstapps/logger/commit/100b921488c3b8846678bf72befb787ca51bd1c7))
### Features
* add EXIT log level ([edc6e6f](https://gitlab.com/openstapps/logger/commit/edc6e6fad5ece784059e406dcedd6d76fe07f74b))
# [0.4.0](https://gitlab.com/openstapps/logger/compare/v0.3.1...v0.4.0) (2019-07-12)
### Features
* add transformations ([ddbe00d](https://gitlab.com/openstapps/logger/commit/ddbe00d2a51430348fcf4e4e29807cc43a37cf49)), closes [#9](https://gitlab.com/openstapps/logger/issues/9)
## [0.3.1](https://gitlab.com/openstapps/logger/compare/v0.3.0...v0.3.1) (2019-06-05)
### Bug Fixes
* correct meta information for imports ([d2cb99f](https://gitlab.com/openstapps/logger/commit/d2cb99f70f713029550d8e535abfa65961486006))
# [0.3.0](https://gitlab.com/openstapps/logger/compare/v0.2.1...v0.3.0) (2019-06-04)
## [0.2.1](https://gitlab.com/openstapps/logger/compare/v0.2.0...v0.2.1) (2019-05-27)
# [0.2.0](https://gitlab.com/openstapps/logger/compare/v0.1.0...v0.2.0) (2019-05-27)
### Features
* add automatic publishing ([d9c4ce4](https://gitlab.com/openstapps/logger/commit/d9c4ce48dc980f85d43be47a23b5eda73f9f21b1)), closes [#6](https://gitlab.com/openstapps/logger/issues/6)
# [0.1.0](https://gitlab.com/openstapps/logger/compare/v0.0.5...v0.1.0) (2019-05-07)
## [0.0.5](https://gitlab.com/openstapps/logger/compare/v0.0.4...v0.0.5) (2019-01-16)
### Features
* allow env variables to overwrite smtp config ([3d82c94](https://gitlab.com/openstapps/logger/commit/3d82c94577ad2013124fab64fc13ce24df579d21)), closes [#3](https://gitlab.com/openstapps/logger/issues/3)
## [0.0.4](https://gitlab.com/openstapps/logger/compare/v0.0.3...v0.0.4) (2018-11-28) ## [0.0.4](https://gitlab.com/openstapps/logger/compare/v0.0.3...v0.0.4) (2018-11-28)
@@ -10,12 +80,12 @@
## [0.0.1](https://gitlab.com/openstapps/logger/compare/911d71c...v0.0.1) (2018-11-28) ## [0.0.1](https://gitlab.com/openstapps/logger/compare/911d71cf3b453dfe302c33d275cb840086288e8e...v0.0.1) (2018-11-28)
### Features ### Features
* add logger ([911d71c](https://gitlab.com/openstapps/logger/commit/911d71c)) * add logger ([911d71c](https://gitlab.com/openstapps/logger/commit/911d71cf3b453dfe302c33d275cb840086288e8e))

View File

@@ -3,11 +3,12 @@
[![pipeline status](https://gitlab.com/openstapps/logger/badges/master/pipeline.svg)](https://gitlab.com/openstapps/logger/commits/master) [![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) [![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. Logs are only printed if their log level is equal or higher than the defined log level.
## Log Levels ## Log Levels
Available log levels are: Available log levels are:
- 1 - INFO - 1 - INFO
- 2 - LOG - 2 - LOG
@@ -24,16 +25,21 @@ For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything that is `OK
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set
`ALLOW_NO_TRANSPORT` to `true`. `ALLOW_NO_TRANSPORT` to `true`.
Additionally setting the environment variable `STAPPS_EXIT_LEVEL` which works in the same manner as `STAPPS_LOG_LEVEL` will terminate your process after logging at the selected level(s) (usefull for integration tests). It will be ignored in afore mentioned productive environments.
## SMTP ## SMTP
This class also provides a simple implementation of an SMTP transport which can be used as a 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 `TransportWithVerification` for the logger. You can use this to transport errors of the logger or to transport mails
of your own monitoring solution. of your own monitoring solution.
### Usage ### Usage
You can instatiate it with a config or it will check for a config in the environment variables. Environment variables 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. can overwrite the actual config values.
Environment variables are: Environment variables are:
* SMTP_AUTH_USER: SMTP username * SMTP_AUTH_USER: SMTP username
* SMTP_AUTH_PASSWORD: SMTP password * SMTP_AUTH_PASSWORD: SMTP password
* SMTP_HOST: SMTP host * SMTP_HOST: SMTP host
@@ -43,3 +49,15 @@ Environment variables are:
* SMTP_SENDER_MAIL: sender of the mail * SMTP_SENDER_MAIL: sender of the mail
* SMTP_SENDER_NAME: name of the sender * SMTP_SENDER_NAME: name of the sender
* SMTP_SECURE: `true` to enable tls * 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

5567
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@openstapps/logger", "name": "@openstapps/logger",
"version": "0.0.5", "version": "0.8.1",
"description": "A cli logger with colors, loglevels and the possibility to use a transport system for errors", "description": "A cli logger with colors, loglevels and the possibility to use a transport system for errors",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -8,61 +8,78 @@
}, },
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"build": "npm run tslint && npm run compile && npm run documentation", "build": "npm run tslint && npm run compile",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m 'docs: update changelog'",
"compile": "tsc", "check-configuration": "openstapps-configuration",
"documentation": "typedoc --includeDeclarations --excludeExternals --mode modules --out docs src", "compile": "rimraf lib && tsc && prepend lib/cli.js '#!/usr/bin/env node\n'",
"prepareOnly": "npm run build", "documentation": "typedoc --name \"@openstapps/logger\" --includeVersion --out docs --readme README.md --listInvalidSymbolLinks --entryPointStrategy expand src",
"test": "nyc mocha --require ts-node/register --ui mocha-typescript 'test/*.ts'", "postversion": "npm run changelog",
"tslint": "tslint 'src/**/*.ts'" "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/**/*.spec.ts'",
"tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'"
}, },
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>", "author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
"contributors": [ "contributors": [
"Anselm Stordeur <anselmstordeur@gmail.com>", "Anselm Stordeur <anselmstordeur@gmail.com>",
"Jovan Krunic <jovan.krunic@gmail.com>" "Jovan Krunic <jovan.krunic@gmail.com>",
"Rainer Killinger <mail-openstapps@killinger.co>"
], ],
"typings": "./lib/Logger.d.ts", "typings": "./lib/logger.d.ts",
"main": "./lib/Logger.js", "main": "./lib/logger.js",
"nyc": { "nyc": {
"all": true,
"branches": 95,
"check-coverage": true, "check-coverage": true,
"per-file": true, "exclude": [
"lines": 0, "src/smtp.ts",
"statements": 0, "src/cli.ts"
"functions": 0,
"branches": 0,
"include": [
"src"
], ],
"extension": [ "extension": [
".ts" ".ts"
], ],
"functions": 95,
"include": [
"src"
],
"lines": 95,
"per-file": true,
"reporter": [ "reporter": [
"html", "html",
"text-summary" "text-summary"
], ],
"all": true "require": [
"ts-node/register"
],
"statements": 95
}, },
"devDependencies": { "devDependencies": {
"@openstapps/configuration": "0.5.0", "@openstapps/configuration": "0.29.0",
"@types/chai": "4.1.7", "@types/chai": "4.3.1",
"@types/mocha": "5.2.5", "@types/chai-as-promised": "7.1.5",
"@types/sinon": "7.0.3", "@types/chai-spies": "1.0.3",
"chai": "4.2.0", "@types/mocha": "9.1.1",
"conventional-changelog-cli": "2.0.11", "chai": "4.3.6",
"mocha": "5.2.0", "chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"conventional-changelog-cli": "2.2.2",
"mocha": "9.1.3",
"mocha-typescript": "1.1.17", "mocha-typescript": "1.1.17",
"nyc": "13.1.0", "nyc": "15.1.0",
"sinon": "7.2.2", "prepend-file-cli": "1.0.6",
"ts-node": "7.0.1", "rimraf": "3.0.2",
"tslint": "5.12.0", "ts-node": "10.8.0",
"typedoc": "0.14.0", "tslint": "6.1.3",
"typescript": "3.2.2" "typedoc": "0.22.15",
"typescript": "4.4.4"
}, },
"dependencies": { "dependencies": {
"@types/circular-json": "0.4.0", "@types/node": "14.18.18",
"@types/node": "10.12.18", "@types/nodemailer": "6.4.4",
"@types/nodemailer": "4.6.5", "chalk": "4.1.2",
"circular-json": "0.5.9", "flatted": "3.2.5",
"nodemailer": "5.1.1" "moment": "2.29.3",
"nodemailer": "6.7.5"
} }
} }

View File

@@ -1,351 +0,0 @@
/*
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
*/
import {stringify} from 'circular-json';
import {Transport, TransportWithVerification} from './Transport';
/**
* Logger with colors, loglevel and transport
*
* 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`.
*
* Log levels in that order are:
* ```
* 1 - INFO
* 2 - LOG
* 4 - WARN
* 8 - ERROR
* 16 - OK
* ```
*/
export class Logger {
/*
* Reset = "\x1b[0m"
* Bright = "\x1b[1m"
* Dim = "\x1b[2m"
* Underscore = "\x1b[4m"
* Blink = "\x1b[5m"
* Reverse = "\x1b[7m"
* Hidden = "\x1b[8m"
*
* FgBlack = "\x1b[30m"
* FgRed = "\x1b[31m"
* FgGreen = "\x1b[32m"
* FgYellow = "\x1b[33m"
* FgBlue = "\x1b[34m"
* FgMagenta = "\x1b[35m"
* FgCyan = "\x1b[36m"
* FgWhite = "\x1b[37m"
*
* BgBlack = "\x1b[40m"
* BgRed = "\x1b[41m"
* BgGreen = "\x1b[42m"
* BgYellow = "\x1b[43m"
* BgBlue = "\x1b[44m"
* BgMagenta = "\x1b[45m"
* BgCyan = "\x1b[46m"
* BgWhite = "\x1b[47m"
*/
/**
* Prefix for cyan color
*/
private cyan = '\x1b[36m';
/**
* Prefix for green color
*/
private green = '\x1b[32m';
/**
* Set to true if this code is executed by Node.js
*/
private isNode: boolean;
/**
* Set to true if the environment for a productive environment is given
*
* In Node.js this means that `NODE_ENV` is set to `production`
*/
private isProductiveEnvironment: boolean;
/**
* Log levels
*/
private logLevels: { [logLevel: string]: 1 | 2 | 4 | 8 | 16 } = {
'INFO': 1,
'LOG': 2,
'WARN': 4,
// tslint:disable-next-line:object-literal-sort-keys
'ERROR': 8,
'OK': 16,
};
/**
* Prefix for red color
*/
private red = '\x1b[31m';
/**
* Suffix to end a color
*/
private reset = '\x1b[0m';
/**
* Transport for errors
*
* For example `@stapps/smtp-transport`
*/
private transport?: Transport;
/**
* Prefix for white color
*/
private white = '\x1b[37m';
/**
* Prefix for yellow color
*/
private yellow = '\x1b[33m';
/**
* Checks if this code is executed in Node.js
*/
public static isNodeEnvironment(): boolean {
// Only Node.js has a process variable that is of [[Class]] process
return Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
}
/**
* Checks if a productive environment is given
*/
public static isProductiveEnvironment(): boolean {
return Logger.isNodeEnvironment &&
(typeof process.env.NODE_ENV === 'string' && process.env.NODE_ENV === 'production');
}
/**
* Instatiate an instance of logger
* @param transport A transport instance that can be used for error transport
*/
constructor(transport?: Transport) {
// node environment -> maybe we run a service which needs monitoring
if (Logger.isNodeEnvironment()) {
this.isNode = true;
// check if we are in productive environment -> then we need to run a transport
if (Logger.isProductiveEnvironment()) {
this.isProductiveEnvironment = true;
if (typeof transport === 'undefined') {
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error('Productive environment doesn\'t set an transport agent for error notifications');
} else {
this.warn('Productive environment doesn\'t set an transport agent for error notifications');
}
} else {
this.transport = transport;
// we expect an transport for error notifications
if (this.isTransportWithVerification(transport) && !transport.isVerified()) {
transport.verify().then((success) => {
if (typeof success === 'string') {
this.ok(success);
} else {
this.ok('Successfully verified transport for error notification');
}
}).catch((err) => {
throw err;
});
}
}
} else {
this.isProductiveEnvironment = false;
}
} else {
this.isProductiveEnvironment = false;
this.isNode = false;
}
}
/**
* Check if intended log level is allowed in environment log level
*
* @param logLevel
* @returns {boolean}
*/
private checkLogLevel(logLevel: 1 | 2 | 4 | 8 | 16) {
const requiredLogLevel = this.getLogLevel();
if (requiredLogLevel > 31 || requiredLogLevel < 0) {
throw new Error('Log level is out of range.');
}
// tslint:disable-next-line:no-bitwise
return requiredLogLevel & logLevel;
}
/**
* Return log level from environment
* @returns {number}
*/
private getLogLevel(): number {
if (this.isNode && typeof process.env.STAPPS_LOG_LEVEL === 'string') {
// Node.js environment exists
return parseInt(process.env.STAPPS_LOG_LEVEL, 10);
} else if (typeof window !== 'undefined' && typeof (window as any).STAPPS_LOG_LEVEL === 'number') {
// browser environment exists
return (window as any).STAPPS_LOG_LEVEL;
}
// Log everything
return 31;
}
private isTransportWithVerification(transport: Transport): transport is TransportWithVerification {
return typeof (transport as any).verify === 'function';
}
/**
* Stringify a list of arguments
*
* @param args {any[]} Arguments to stringify
* @returns {string} Stringified arguments
*/
private stringifyArguments(..._args: any[]): string {
const result: string[] = [];
/* tslint:disable:prefer-for-of */
for (let idx = 0; idx < arguments.length; idx++) {
/* tslint:enable */
const argument = arguments[idx];
const type = typeof argument;
if (['string', 'number'].indexOf(type) !== -1) {
result.push(argument);
} else if (argument instanceof Error) {
result.push(argument.message);
if (typeof argument.stack !== 'undefined') {
result.push(argument.stack);
}
} else {
result.push(stringify(argument, null, 2));
}
}
return result.join(', ');
}
/**
* Log with level ERROR
*
* @param args {any} Arguments to log
*/
public error(...args: any[]): void {
if (this.checkLogLevel(this.logLevels.ERROR)) {
if (this.isNode) {
/* tslint:disable-next-line:no-console */
console.error(this.red + '[ERROR] ' + this.stringifyArguments(...args) + this.reset);
if (this.isProductiveEnvironment) {
if (typeof this.transport === 'undefined') {
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error('Error couldn\'t be tranported. Please set an transport or set ALLOW_NO_TRANSPORT=true');
}
} else {
this.transport.send('Error', this.stringifyArguments(...args)).catch((err) => {
throw err;
});
}
}
} else {
/* tslint:disable-next-line:no-console */
console.log('[ERROR] ' + this.stringifyArguments(...args));
}
}
}
/**
* Log with level INFO
*
* @param args {any} Arguments to log
*/
public info(...args: any[]): void {
if (this.checkLogLevel(this.logLevels.INFO)) {
if (this.isNode) {
/* tslint:disable-next-line:no-console */
console.info(this.cyan + '[INFO] ' + this.stringifyArguments(...args) + this.reset);
} else {
/* tslint:disable-next-line:no-console */
console.info('[INFO] ' + this.stringifyArguments(...args));
}
}
}
/**
* Log with level LOG
*
* @param args {any} Arguments to log
*/
public log(...args: any[]): void {
if (this.checkLogLevel(this.logLevels.LOG)) {
if (this.isNode) {
/* tslint:disable-next-line:no-console */
console.log(this.white + '[LOG] ' + this.stringifyArguments(...args) + this.reset);
} else {
/* tslint:disable-next-line:no-console */
console.log('[LOG] ' + this.stringifyArguments(...args));
}
}
}
/**
* Log with level OK
*
* @param args {any} Arguments to log
*/
public ok(...args: any[]): void {
if (this.checkLogLevel(this.logLevels.OK)) {
if (this.isNode) {
/* tslint:disable-next-line:no-console */
console.log(this.green + '[OK] ' + this.stringifyArguments(...args) + this.reset);
} else {
/* tslint:disable-next-line:no-console */
console.log('[OK] ' + this.stringifyArguments(...args));
}
}
}
/**
* Log with level WARN
*
* @param args {any} Arguments to log
*/
public warn(...args: any[]): void {
if (this.checkLogLevel(this.logLevels.WARN)) {
if (this.isNode) {
/* tslint:disable-next-line:no-console */
console.warn(this.yellow + '[WARN] ' + this.stringifyArguments(...args) + this.reset);
} else {
/* tslint:disable-next-line:no-console */
console.warn('[WARN] ' + this.stringifyArguments(...args));
}
}
}
}

View File

@@ -12,8 +12,9 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Transport, VerifiableTransport} from './transport';
/** /**
* A recursive partial object * A recursive partial object
* *
* Copied from https://stackoverflow.com/a/51365037 * Copied from https://stackoverflow.com/a/51365037
@@ -26,24 +27,64 @@ export type RecursivePartial<T> = {
/** /**
* Deletes all properties that are undefined from an object * Deletes all properties that are undefined from an object
* @param obj *
* @param obj Object to delete undefined properties from
*/ */
export function deleteUndefinedProperties(obj: any) { export function deleteUndefinedProperties(obj: unknown): unknown {
// return atomic data types and arrays (recursion anchor) // return atomic data types and arrays (recursion anchor)
if (typeof obj !== 'object' || Array.isArray(obj)) { if (typeof obj !== 'object' || Array.isArray(obj)) {
return obj; return obj;
} }
// check each key // check each key
Object.keys(obj).forEach((key) => { for (const key in obj) {
if (typeof obj[key] === 'undefined') { /* istanbul ignore if */
if (!obj.hasOwnProperty(key)) {
continue;
}
const indexedObj = obj as { [k: string]: unknown; };
if (typeof indexedObj[key] === 'undefined') {
// delete undefined keys // delete undefined keys
delete obj[key]; delete indexedObj[key];
} else { } else {
// check recursive // check recursive
obj[key] = deleteUndefinedProperties(obj[key]); indexedObj[key] = deleteUndefinedProperties(indexedObj[key]);
}
} }
});
return obj; return obj;
} }
/**
* Checks if environment is Node.js
*/
export function isNodeEnvironment(): boolean {
return Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]';
}
/**
* Checks if environment is productive
*/
export function isProductiveEnvironment(): boolean {
return typeof process.env === 'object'
&& typeof process.env.NODE_ENV !== 'undefined'
&& process.env.NODE_ENV === 'production';
}
/**
* Checks if environment is Node.js and productive
*/
export function isProductiveNodeEnvironment(): boolean {
return isNodeEnvironment() && isProductiveEnvironment();
}
/**
* Check if a transport is a verifiable transport
*
* @param transport Transport to check
*/
export function isTransportWithVerification(transport: Transport): transport is VerifiableTransport {
return transport instanceof VerifiableTransport;
}

345
src/logger.ts Normal file
View File

@@ -0,0 +1,345 @@
/*
* Copyright (C) 2019-2020 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 <https://www.gnu.org/licenses/>.
*/
import {stringify} from 'flatted';
import {isNodeEnvironment, isProductiveNodeEnvironment} from './common';
import {Transformation} from './transformation';
import {AddLogLevel} from './transformations/add-log-level';
import {Transport} from './transport';
/**
* Check if something has property STAPPS_LOG_LEVEL
*
* @param something Something to check
*/
// tslint:disable-next-line:completed-docs
function hasStAppsLogLevel(something: object): something is { STAPPS_LOG_LEVEL: number; } {
return 'STAPPS_LOG_LEVEL' in something;
}
/**
* Check if something has property STAPPS_EXIT_LEVEL
*
* @param something Something to check
*/
// tslint:disable-next-line:completed-docs
function hasStAppsExitLevel(something: object): something is { STAPPS_EXIT_LEVEL: number; } {
return 'STAPPS_EXIT_LEVEL' in something;
}
/**
* A level descriptor for either log or exit level
*/
export type Level = 'LOG' | 'EXIT';
/**
* 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`.
*
* Log levels in that order are:
* ```
* INFO: 1
* LOG: 2
* WARN: 4
* ERROR: 8
* OK: 16
* ```
*/
export class Logger {
/**
* Base of binary system
*/
private static readonly binaryBase = 2;
/**
* Log levels
*/
private static readonly logLevels: LogLevel[] = [
'INFO',
'LOG',
'WARN',
'ERROR',
'OK',
];
/**
* Log level sum, equivalent to all log levels enabled
*/
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 exit level is allowed in environment exit level
*
* @param exitLevel Log level to check
*/
private static checkExitLevel(exitLevel: LogLevel): boolean {
if (Logger.getLevel('EXIT') === 0) {
return false;
}
// tslint:disable-next-line:no-bitwise
return (Logger.getLevel('EXIT') & Logger.logLevelNumber(exitLevel)) === Logger.logLevelNumber(exitLevel);
}
/**
* Check if intended log level is allowed in environment log level
*
* @param logLevel Log level to check
*/
private static checkLogLevel(logLevel: LogLevel): boolean {
// tslint:disable-next-line:no-bitwise
return (Logger.getLevel('LOG') & Logger.logLevelNumber(logLevel)) === Logger.logLevelNumber(logLevel);
}
/**
* Notify about exit and end process
*/
private static exit(): void {
if (isProductiveNodeEnvironment()) {
return;
}
// tslint:disable-next-line: no-console
console.error(Logger.applyTransformers('ERROR', `exiting as of used exit level ${Logger.getLevel('EXIT')} !`));
process.exit(-1);
}
/**
* Return log level from environment
*/
private static getLevel(level: Level): number {
if (typeof window !== 'undefined') {
// browser environment exists
if (hasStAppsLogLevel(window)) {
return window.STAPPS_LOG_LEVEL;
}
if (hasStAppsExitLevel(window)) {
return window.STAPPS_EXIT_LEVEL;
}
}
const environmentLevel = level === 'LOG' ? process.env.STAPPS_LOG_LEVEL : process.env.STAPPS_EXIT_LEVEL;
if (isNodeEnvironment() && typeof environmentLevel !== 'undefined') {
// Node.js environment exists
return parseInt(environmentLevel, 10);
}
// Fallback to log everything, or not exiting
switch (level) {
case 'LOG':
return Logger.logLevelSum;
case 'EXIT':
return 0;
}
}
/**
* Get number of specific log level
*
* @param logLevel Log level to check
*/
private static logLevelNumber(logLevel: LogLevel): number {
return Math.pow(Logger.binaryBase, Logger.logLevels.indexOf(logLevel));
}
/**
* Log an error
*
* @param args Arguments to log
*/
public static async error(...args: unknown[]): Promise<string | void> {
if (!Logger.checkLogLevel('ERROR')) {
return;
}
/* tslint:disable-next-line:no-console */
console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...args)));
if (isProductiveNodeEnvironment()) {
if (typeof Logger.transport !== 'undefined') {
return Logger.transport.send('Error', Logger.stringifyArguments(...args));
}
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error(`Error couldn't be transported! Please set a transport or set ALLOW_NO_TRANSPORT='true'.`);
}
}
if (Logger.checkExitLevel('ERROR')) {
Logger.exit();
}
}
/**
* Log an information
*
* @param args Arguments to log
*/
public static info(...args: unknown[]): void {
if (!Logger.checkLogLevel('INFO')) {
return;
}
/* tslint:disable-next-line:no-console */
console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...args)));
if (Logger.checkExitLevel('INFO')) {
Logger.exit();
}
}
/**
* Check if the logger is initialized correctly
*/
public static initialized(): void {
if (isProductiveNodeEnvironment() && typeof Logger.transport === 'undefined') {
if (process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error(`Productive environment doesn't set a transport for error notifications.`);
}
Logger.warn(`Productive environment doesn't set a transport for error notifications.`);
}
}
/**
* Log something
*
* @param args Arguments to log
*/
public static log(...args: unknown[]): void {
if (!Logger.checkLogLevel('LOG')) {
return;
}
/* tslint:disable-next-line:no-console */
console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...args)));
if (Logger.checkExitLevel('LOG')) {
Logger.exit();
}
}
/**
* Log something successful
*
* @param args Arguments to log
*/
public static ok(...args: unknown[]): void {
if (!Logger.checkLogLevel('OK')) {
return;
}
/* tslint:disable-next-line:no-console */
console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...args)));
if (Logger.checkExitLevel('OK')) {
Logger.exit();
}
}
/**
* Set transformations for log output
*
* @param transformations List of transformations
*/
public static setTransformations(transformations: Transformation[]) {
Logger.transformations = transformations;
}
/**
* Set a transport
*
* @param transport Transport to set
*/
public static setTransport(transport?: Transport) {
Logger.transport = transport;
}
/**
* Stringify a list of arguments
*
* @param args Arguments to stringify
*/
public static stringifyArguments(...args: unknown[]): string {
const result: string[] = [];
args.forEach((argument) => {
if (typeof argument === 'string' || typeof argument === 'number') {
result.push(argument.toString());
} else if (argument instanceof Error) {
result.push(argument.message);
if (typeof argument.stack !== 'undefined') {
result.push(argument.stack);
}
} else {
// tslint:disable-next-line:no-magic-numbers
result.push(stringify(argument, null, 2));
}
});
return result.join(', ');
}
/**
* Log a warning
*
* @param args Arguments to log
*/
public static warn(...args: unknown[]): void {
if (!Logger.checkLogLevel('WARN')) {
return;
}
/* tslint:disable-next-line:no-console */
console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...args)));
if (Logger.checkExitLevel('WARN')) {
Logger.exit();
}
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 StApps * Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it * 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 * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -14,25 +14,57 @@
*/ */
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import {MailOptions} from 'nodemailer/lib/sendmail-transport'; import {MailOptions} from 'nodemailer/lib/sendmail-transport';
import {deleteUndefinedProperties, RecursivePartial} from './common'; import {deleteUndefinedProperties, isProductiveEnvironment, RecursivePartial} from './common';
import {Logger} from './Logger'; import {VerifiableTransport} from './transport';
import {TransportWithVerification} from './Transport';
/** /**
* A configuration of the transport used to send mails via SMTP * A configuration of the transport used to send mails via SMTP
*/ */
export interface SMTPConfig { export interface SMTPConfig {
/**
* Auth configuration
*/
auth: { auth: {
/**
* Password for login
*/
password: string; password: string;
/**
* User for login
*/
user: string; user: string;
}; };
/**
* List of "carbon copy" recipients
*/
cc?: string[]; cc?: string[];
/**
* SMTP host server
*/
host: string; host: string;
/**
* SMTP port
*/
port: number; port: number;
/**
* List of recipients
*/
recipients: string[]; recipients: string[];
/**
* Whether or not to establish a secure connection
*/
secure?: boolean; secure?: boolean;
/**
* Sender configuration
*/
sender: { sender: {
/**
* Mail of sender
*/
mail: string; mail: string;
/**
* Name of sender
*/
name?: string; name?: string;
}; };
} }
@@ -40,32 +72,40 @@ export interface SMTPConfig {
/** /**
* An implementation of mail transport via SMTP * An implementation of mail transport via SMTP
*/ */
export class SMTP extends TransportWithVerification { export class SMTP extends VerifiableTransport {
/**
* Singleton instance
*/
private static _instance: SMTP; private static _instance: SMTP;
/** /**
* List of all mail addresses to send in cc * List of all mail addresses to send in cc
*/ */
private cc: string[]; private readonly cc: string[];
/** /**
* Who is using this service * Who is using this service
*/ */
private from: { private readonly from: {
/**
* Mail of sender
*/
mail: string; mail: string;
/**
* Name of sender
*/
name?: string; name?: string;
}; };
/** /**
* List of all mail addresses to send to * List of all mail addresses to send to
*/ */
private recipients: string[]; private readonly recipients: string[];
/** /**
* Connection to SMTP server * Connection to SMTP server
*/ */
private transportAgent: nodemailer.Transporter; private readonly transportAgent: nodemailer.Transporter;
/** /**
* Set to true if the transport was verified * Set to true if the transport was verified
@@ -85,30 +125,31 @@ export class SMTP extends TransportWithVerification {
* SMTP_SENDER_MAIL: sender of the mail * SMTP_SENDER_MAIL: sender of the mail
* SMTP_SENDER_NAME: name of the sender * SMTP_SENDER_NAME: name of the sender
* SMTP_SECURE: `true` to enable tls * SMTP_SECURE: `true` to enable tls
* @param config {SMTPConfig} *
* @return {Transport} * @param config SMTP config for instance
*/ */
public static getInstance(config?: SMTPConfig): SMTP | undefined { public static getInstance(config?: SMTPConfig): SMTP | undefined {
// if an instance of SMTP already exists // if an instance of SMTP already exists
if (this._instance) { if (typeof SMTP._instance !== 'undefined') {
return this._instance; return SMTP._instance;
} }
// monitoring is not required -> SMTP init can fail // monitoring is not required -> SMTP init can fail
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') {
try { try {
this._instance = new this(config); SMTP._instance = new SMTP(config);
} catch (err) { } catch (err) {
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.warn('SMTP config failed.'); console.warn('SMTP config failed.');
return; return;
} }
} else { } else {
// monitoring is required -> SMTP will throw error if config is invalid // monitoring is required -> SMTP will throw error if config is invalid
this._instance = new this(config); SMTP._instance = new SMTP(config);
} }
return this._instance; return SMTP._instance;
} }
/** /**
@@ -118,100 +159,94 @@ export class SMTP extends TransportWithVerification {
* For more information please consider reading * For more information please consider reading
* https://stackoverflow.com/a/2071250 * https://stackoverflow.com/a/2071250
* *
* @param {string} address * @param address Address to validate
* @return {boolean}
*/ */
public static isValidEmailAddress(address: string): boolean { public static isValidEmailAddress(address: string): boolean {
if (typeof address !== 'string') {
return false;
}
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
const regex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; return /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(address);
return regex.test(address);
} }
/** /**
* Checks a list of mail addresses for validity. * Checks a list of mail addresses for validity
* @param {string[]} recipients *
* @return {string[]} * @param recipients List of recipients to check
*/ */
public static isValidRecipientsList(recipients: string[] | undefined): boolean { public static isValidRecipientsList(recipients: string[] | undefined): boolean {
return Array.isArray(recipients) && recipients.length > 0 && recipients.every(this.isValidEmailAddress); return Array.isArray(recipients) && recipients.length > 0 && recipients.every(SMTP.isValidEmailAddress);
} }
/** /**
* Creates an SMTP connection. WARNING: This class is supposed to be used as a singleton. You should never * Creates an SMTP connection.
* call `new SMTP()` *
* @param {SMTPConfig} config * WARNING: This class is supposed to be used as a singleton. You should never call `new SMTP()`
*
* @param smtpConfig SMTP config
*/ */
private constructor(config?: SMTPConfig) { private constructor(smtpConfig?: SMTPConfig) {
// create a partial config from environment variables that can overwrite the given config // create a partial config from environment variables that can overwrite the given config
const envConfig: RecursivePartial<SMTPConfig> = { const envConfig: RecursivePartial<SMTPConfig> = {
auth: { auth: {
password: process.env.SMTP_AUTH_PASSWORD, password: process.env.SMTP_AUTH_PASSWORD,
user: process.env.SMTP_AUTH_USER, user: process.env.SMTP_AUTH_USER,
}, },
cc: ((typeof process.env.SMTP_CC === 'string') ? (process.env.SMTP_CC as string).split(',') : []), cc: ((typeof process.env.SMTP_CC !== 'undefined') ? (process.env.SMTP_CC as string).split(',') : []),
host: process.env.SMTP_HOST, host: process.env.SMTP_HOST,
port: (typeof process.env.SMTP_PORT === 'string') ? parseInt(process.env.SMTP_PORT, 10) : undefined, port: (typeof process.env.SMTP_PORT !== 'undefined') ? parseInt(process.env.SMTP_PORT, 10) : undefined,
recipients: (typeof process.env.SMTP_RECIPIENTS === 'string') ? recipients: (typeof process.env.SMTP_RECIPIENTS !== 'undefined') ?
(process.env.SMTP_RECIPIENTS).split(',') : (process.env.SMTP_RECIPIENTS).split(',') :
[], [],
secure: (typeof process.env.SMTP_SECURE === 'string') ? (process.env.SMTP_SECURE === 'true') : false, secure: (typeof process.env.SMTP_SECURE !== 'undefined') ? (process.env.SMTP_SECURE === 'true') : false,
sender: { sender: {
mail: process.env.SMTP_SENDER_MAIL, mail: process.env.SMTP_SENDER_MAIL,
name: process.env.SMTP_SENDER_NAME, name: process.env.SMTP_SENDER_NAME,
}, },
}; };
// @ts-ignore const config = {
config = { ...smtpConfig,
...config,
// deleting undefined properties so the actual config doesn't get overwritten by undefined values // deleting undefined properties so the actual config doesn't get overwritten by undefined values
...deleteUndefinedProperties(envConfig), ...(deleteUndefinedProperties(envConfig) as object),
}; } as SMTPConfig;
if (typeof config!.host !== 'string') { if (typeof config.host === 'undefined') {
throw new Error( throw new Error(
'SMTP configuration needs a host. Add it to the config or use environment variables (SMTP_HOST).', 'SMTP configuration needs a host. Add it to the config or use environment variables (SMTP_HOST).',
); );
} }
if (typeof config!.port !== 'number' || isNaN(config!.port)) { if (typeof config.port === 'undefined' || isNaN(config.port)) {
throw new Error( throw new Error(
'SMTP configuration needs a port. Add it to the config or use environment variables (SMTP_PORT).', 'SMTP configuration needs a port. Add it to the config or use environment variables (SMTP_PORT).',
); );
} }
if (typeof config!.auth !== 'object') { if (typeof config.auth !== 'object') {
throw new Error( throw new Error(
'SMTP configuration needs an auth object.' + 'SMTP configuration needs an auth object.' +
'Add it to the config or use environment variables (SMTP_AUTH_USER, SMTP_AUTH_PASSWORD).', 'Add it to the config or use environment variables (SMTP_AUTH_USER, SMTP_AUTH_PASSWORD).',
); );
} }
if (typeof config!.auth.user !== 'string') { if (typeof config.auth.user === 'undefined') {
throw new Error( throw new Error(
'SMTP auth configuration needs a user. Add it to the config or use environment variables (SMTP_AUTH_USER).', 'SMTP auth configuration needs a user. Add it to the config or use environment variables (SMTP_AUTH_USER).',
); );
} }
if (typeof config!.auth.password !== 'string') { if (typeof config.auth.password === 'undefined') {
throw new Error( throw new Error(
'SMTP auth configuration needs a password.' + 'SMTP auth configuration needs a password.' +
'Add it to the config or use environment variables (SMTP_AUTH_PASSWORD).', 'Add it to the config or use environment variables (SMTP_AUTH_PASSWORD).',
); );
} }
if (Array.isArray(config!.recipients) && config!.recipients.length < 1) { if (Array.isArray(config.recipients) && config.recipients.length < 1) {
throw new Error( throw new Error(
'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).', 'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).',
); );
} }
if (typeof config!.sender.mail !== 'string') { if (typeof config.sender.mail === 'undefined') {
throw new Error( throw new Error(
'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).', 'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).',
); );
@@ -219,15 +254,15 @@ export class SMTP extends TransportWithVerification {
super(); super();
if (SMTP.isValidRecipientsList(config!.recipients)) { if (SMTP.isValidRecipientsList(config.recipients)) {
this.recipients = config!.recipients; this.recipients = config.recipients;
} else { } else {
throw new Error('Invalid recipients found'); throw new Error('Invalid recipients found');
} }
if (typeof config!.cc !== 'undefined') { if (typeof config.cc !== 'undefined') {
if (SMTP.isValidRecipientsList(config!.cc)) { if (SMTP.isValidRecipientsList(config.cc)) {
this.cc = config!.cc!; this.cc = config.cc;
} else { } else {
throw new Error('Invalid cc recipients found'); throw new Error('Invalid cc recipients found');
} }
@@ -235,25 +270,24 @@ export class SMTP extends TransportWithVerification {
this.cc = []; this.cc = [];
} }
this.from = config!.sender; this.from = config.sender;
this.verified = false; this.verified = false;
// creating transport with configuration // creating transport with configuration
this.transportAgent = nodemailer.createTransport({ this.transportAgent = nodemailer.createTransport({
auth: { auth: {
pass: config!.auth.password, pass: config.auth.password,
user: config!.auth.user, user: config.auth.user,
}, },
host: config!.host, host: config.host,
port: config!.port, port: config.port,
secure: typeof config!.secure === 'boolean' ? config!.secure : false, secure: typeof config.secure !== 'undefined' ? config.secure : false,
}); });
} }
/** /**
* Check if instance was verified at least once * Check if instance was verified at least once
* @returns {boolean}
*/ */
public isVerified(): boolean { public isVerified(): boolean {
return this.verified; return this.verified;
@@ -262,11 +296,12 @@ export class SMTP extends TransportWithVerification {
/** /**
* Sends a preconfigured mail with recipients and sender configured on * Sends a preconfigured mail with recipients and sender configured on
* creation of the class (set by environment or on creation of this class) * creation of the class (set by environment or on creation of this class)
* @param {string} subject *
* @param {string} message * @param subject Subject of the mail
* @param message message of the mail
*/ */
public async send(subject: string, message: string): Promise<string> { public async send(subject: string, message: string): Promise<string> {
return await this.sendMail({ return this.sendMail({
cc: this.cc, cc: this.cc,
// use an address block if name is available, mail otherwise // use an address block if name is available, mail otherwise
from: (typeof this.from.name !== 'string') ? `${this.from.name} <${this.from.mail}>` : this.from.mail, from: (typeof this.from.name !== 'string') ? `${this.from.name} <${this.from.mail}>` : this.from.mail,
@@ -278,7 +313,8 @@ export class SMTP extends TransportWithVerification {
/** /**
* Sends a mail object * Sends a mail object
* @param {MailOptions} mail *
* @param mail Mail to send
*/ */
public async sendMail(mail: MailOptions): Promise<string> { public async sendMail(mail: MailOptions): Promise<string> {
// info is the response of the smtp server // info is the response of the smtp server
@@ -306,35 +342,36 @@ export class SMTP extends TransportWithVerification {
*/ */
public async verify(): Promise<boolean> { public async verify(): Promise<boolean> {
let verificationSuccessfull: boolean = false; let verificationSuccessfull = false;
try { try {
verificationSuccessfull = await this.transportAgent.verify(); verificationSuccessfull = await this.transportAgent.verify();
} catch (err) { } catch (err) {
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw err; throw err;
} else { }
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.warn( console.warn(
'SMTP verification error was ignored, because tranport failures are allowed: ', 'SMTP verification error was ignored, because tranport failures are allowed: ',
err.message, (err as Error).message,
); );
} }
}
if (!verificationSuccessfull) { if (!verificationSuccessfull) {
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error( throw new Error(
'Verification of SMTP transport failed.' + 'Verification of SMTP transport failed.' +
'If you want to ignore this error set' + 'If you want to ignore this error set' +
'`NODE_ENV=dev` or `ALLOW_NO_TRANSPORT=true`', '`NODE_ENV=dev` or `ALLOW_NO_TRANSPORT=true`',
); );
} else { }
/* tslint:disable-next-line:no-console */ /* tslint:disable-next-line:no-console */
console.warn('SMTP verification error was ignored, because tranport failures are allowed.'); console.warn('SMTP verification error was ignored, because tranport failures are allowed.');
} }
}
this.verified = verificationSuccessfull; this.verified = verificationSuccessfull;
return verificationSuccessfull; return verificationSuccessfull;
} }
} }

28
src/transformation.ts Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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}`;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2019-2020 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 <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
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 moment = require('moment');
const now = moment();
return `[${now.format(this.format)}] ${output}`;
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 StApps * Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it * 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 * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -14,34 +14,33 @@
*/ */
/** /**
* An abstract wrapper for a transport system like for example smtp * An abstract wrapper for a transport system like for example SMTP
*/ */
export abstract class Transport { export abstract class Transport {
/** /**
* Send message with subject. A message can be a mail or something completly different. Depending on what Transport * Send message with subject
* is implemented *
* @param {string} subject * A message can be a mail or something completely different depending on what transport is implemented.
* @param {string} message *
* @param subject Subject of the message
* @param message Message to send
*/ */
abstract send(subject: string, message: string): Promise<string>; abstract send(subject: string, message: string): Promise<string>;
} }
/** /**
* A transport wrapper of transport which can be veriefied * A transport wrapper of transport which can be verified
*/ */
export abstract class TransportWithVerification extends Transport { export abstract class VerifiableTransport extends Transport {
/** /**
* Checks if the transport was verified at least once * Checks if the transport was verified at least once
* @returns {boolean}
*/ */
abstract isVerified(): boolean; abstract isVerified(): boolean;
/** /**
* Verifies transport (check connection, authentication, ...) * Verifies transport
*
* Check connection, authentication, ...
*/ */
abstract verify(): Promise<boolean>; abstract verify(): Promise<boolean>;
} }

View File

@@ -1,227 +0,0 @@
/*
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript';
import * as sinon from 'sinon';
import {Logger} from '../src/Logger';
const logger1 = new Logger();
@suite(timeout(2000), slow(1000))
export class LoggerSpec {
@test
error1(done: () => void) {
let
stub;
stub = sinon.stub(console, 'error');
logger1.error('Foobar');
stub.restore();
expect(stub.args[0][0]).contains('[ERROR] Foobar');
done();
}
@test
error2(done: () => void) {
const e = new Error();
const stub = sinon.stub(console, 'error');
logger1.error(e);
stub.restore();
expect(stub.args[0][0]).contains('Error').contains('at').contains(process.cwd());
done();
}
@test
info(done: () => void) {
let
stub;
stub = sinon.stub(console, 'info');
logger1.info('Foobar');
stub.restore();
expect(stub.args[0][0]).contains('[INFO] Foobar');
done();
}
@test
log(done: () => void) {
let
stub;
stub = sinon.stub(console, 'log');
logger1.log('Foobar');
stub.restore();
expect(stub.args[0][0]).contains('[LOG] Foobar');
done();
}
@test
logLevel1(done: () => void) {
let logStub;
let warnStub;
process.env.STAPPS_LOG_LEVEL = '8';
logStub = sinon.stub(console, 'log');
warnStub = sinon.stub(console, 'warn');
logger1.ok('foo');
logger1.warn('bar');
logStub.restore();
/* tslint:disable:no-unused-expression */
expect(logStub.called).to.be.false;
expect(warnStub.called).to.be.false;
/* tslint:enable */
delete process.env.STAPPS_LOG_LEVEL;
logger1.warn('bar');
/* tslint:disable:no-unused-expression */
expect(warnStub.called).to.be.true;
/* tslint:enable */
warnStub.restore();
done();
}
@test
logLevel2(done: () => void) {
let logStub;
let warnStub;
process.env.STAPPS_LOG_LEVEL = '12';
logStub = sinon.stub(console, 'log');
warnStub = sinon.stub(console, 'warn');
logger1.ok('foo');
logger1.warn('bar');
logStub.restore();
/* tslint:disable:no-unused-expression */
expect(logStub.called).to.be.false;
expect(warnStub.called).to.be.true;
/* tslint:enable */
delete process.env.STAPPS_LOG_LEVEL;
logger1.warn('bar');
/* tslint:disable:no-unused-expression */
expect(warnStub.called).to.be.true;
/* tslint:enable */
warnStub.restore();
done();
}
@test
logLevel3(done: () => void) {
let logStub;
let warnStub;
process.env.STAPPS_LOG_LEVEL = '20';
logStub = sinon.stub(console, 'log');
warnStub = sinon.stub(console, 'warn');
logger1.ok('foo');
logger1.warn('bar');
logStub.restore();
/* tslint:disable:no-unused-expression */
expect(logStub.called).to.be.true;
expect(warnStub.called).to.be.true;
/* tslint:enable */
delete process.env.STAPPS_LOG_LEVEL;
logger1.warn('bar');
/* tslint:disable:no-unused-expression */
expect(warnStub.called).to.be.true;
/* tslint:enable */
warnStub.restore();
done();
}
@test
ok(done: () => void) {
let
stub;
stub = sinon.stub(console, 'log');
logger1.ok('Foobar');
stub.restore();
expect(stub.args[0][0]).contains('[OK] Foobar');
done();
}
@test
stringifyArguments(done: () => void) {
let
stub;
stub = sinon.stub(console, 'log');
logger1.log('foo', 'bar');
stub.restore();
expect(stub.args[0][0]).contains('[LOG]').and.contains('foo').and.contains('bar');
done();
}
@test
warn(done: () => void) {
let
stub;
stub = sinon.stub(console, 'warn');
logger1.warn('Foobar');
stub.restore();
expect(stub.args[0][0]).contains('[WARN] Foobar');
done();
}
}

View File

@@ -13,12 +13,16 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {expect} from 'chai'; import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript'; import {suite, test} from 'mocha-typescript';
import {deleteUndefinedProperties} from '../src/common'; import {
deleteUndefinedProperties,
isNodeEnvironment,
isProductiveEnvironment,
isProductiveNodeEnvironment,
} from '../src/common';
@suite(timeout(2000), slow(1000)) @suite()
export class CommonSpec { export class CommonSpec {
/* tslint:disable:member-ordering */
@test @test
deleteUndefinedProperties1() { deleteUndefinedProperties1() {
expect(deleteUndefinedProperties( expect(deleteUndefinedProperties(
@@ -67,4 +71,48 @@ export class CommonSpec {
}, },
); );
} }
@test
isNodeEnvironment() {
expect(isNodeEnvironment()).to.be.equal(true);
const savedProcess = process;
// @ts-ignore
process = undefined;
expect(isNodeEnvironment()).to.be.equal(false);
process = savedProcess;
}
@test
isProductiveEnvironment() {
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = '';
expect(isProductiveEnvironment()).to.be.equal(false);
process.env.NODE_ENV = 'production';
expect(isProductiveEnvironment()).to.be.equal(true);
process.env.NODE_ENV = nodeEnv;
}
@test
isProductiveNodeEnvironment() {
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = '';
expect(isProductiveNodeEnvironment()).to.be.equal(false);
process.env.NODE_ENV = 'production';
expect(isProductiveNodeEnvironment()).to.be.equal(true);
process.env.NODE_ENV = nodeEnv;
}
} }

35
test/dummyTransport.ts Normal file
View File

@@ -0,0 +1,35 @@
import {Transport, VerifiableTransport} from '../src/transport';
export class DummyTransport extends Transport {
send(subject: string, message: string): Promise<string> {
return new Promise((resolve, reject) => {
if (0 === 0) {
resolve(subject);
}
reject(message);
});
}
}
export class VerifiableDummyTransport extends VerifiableTransport {
isVerified(): boolean {
return false;
}
send(subject: string, message: string): Promise<string> {
return new Promise((resolve, reject) => {
if (0 === 0) {
resolve(subject);
}
reject(message);
});
}
verify(): Promise<boolean> {
return new Promise((resolve) => {
resolve(true);
});
}
}

453
test/logger.spec.ts Normal file
View File

@@ -0,0 +1,453 @@
/*
* Copyright (C) 2018, 2020 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 <https://www.gnu.org/licenses/>.
*/
import chai from 'chai';
import {expect} from 'chai';
import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies';
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()
export class LoggerSpec {
static sandbox: ChaiSpies.Sandbox;
static before() {
LoggerSpec.sandbox = chai.spy.sandbox();
}
before() {
Logger.setTransformations([
new AddLogLevel(),
]);
}
after() {
LoggerSpec.sandbox.restore();
}
@test
async 'default log level'() {
expect((Logger as any).getLevel('LOG')).to.be.equal(31);
expect((Logger as any).getLevel('EXIT')).to.be.equal(0);
}
@test
async error() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
// noop
});
await Logger.error('Foobar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[ERROR]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
@test
async 'error in productive environment'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
// noop
});
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
await Logger.error('Foobar').should.be.rejectedWith(Error);
expect(spy).to.have.been.called.exactly(1);
process.env.ALLOW_NO_TRANSPORT = 'true';
await Logger.error('Foobar').should.not.be.rejectedWith(Error);
expect(spy).to.have.been.called.exactly(2);
delete process.env.ALLOW_NO_TRANSPORT;
Logger.setTransport(new DummyTransport());
await Logger.error('Foobar').should.not.be.rejectedWith(Error);
expect(spy).to.have.been.called.exactly(3);
Logger.setTransport();
process.env.NODE_ENV = nodeEnv;
}
@test
async 'error without output'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
// noop
});
process.env.STAPPS_LOG_LEVEL = '0';
await Logger.error('Foobar');
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
@test
async 'error with Error'() {
const spy = LoggerSpec.sandbox.on(console, 'error', () => {
// noop
});
await Logger.error(new Error());
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('Error');
expect(spy.__spy.calls[0][0]).to.contain(process.cwd());
}
@test
info() {
const spy = LoggerSpec.sandbox.on(console, 'info', () => {
// noop
});
Logger.info('Foobar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[INFO]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
@test
async exits() {
const infoSpy = LoggerSpec.sandbox.on(console, 'info', () => {
// noop
});
const logSpy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
const warnSpy = LoggerSpec.sandbox.on(console, 'warn', () => {
// noop
});
const errorSpy = LoggerSpec.sandbox.on(console, 'error', () => {
// noop
});
const processSpy = LoggerSpec.sandbox.on(process, 'exit', () => {
// noop
});
const exitLevel = process.env.STAPPS_EXIT_LEVEL;
process.env.STAPPS_EXIT_LEVEL = '31';
Logger.info('Foobar');
Logger.log('Foobar');
Logger.warn('Foobar');
Logger.ok('Foobar');
await Logger.error('Foobar');
expect(infoSpy).to.have.been.called.once;
expect(infoSpy.__spy.calls[0][0]).to.contain('[INFO]');
expect(infoSpy.__spy.calls[0][0]).to.contain('Foobar');
expect(logSpy).to.have.been.called.twice;
expect(warnSpy).to.have.been.called.once;
expect(warnSpy.__spy.calls[0][0]).to.contain('[WARN]');
expect(warnSpy.__spy.calls[0][0]).to.contain('Foobar');
expect(errorSpy).to.have.been.called.exactly(6);
expect(processSpy).to.have.been.called.exactly(5);
process.env.STAPPS_EXIT_LEVEL = exitLevel;
}
@test
'info without output'() {
const spy = LoggerSpec.sandbox.on(console, 'info', () => {
// noop
});
process.env.STAPPS_LOG_LEVEL = '0';
Logger.info('Foobar');
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called;
}
@test
initialized() {
Logger.setTransport(new DummyTransport());
expect(() => {
Logger.initialized();
}).not.to.throw();
Logger.setTransport();
}
@test
'initialized in productive environment'() {
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
Logger.setTransport(new DummyTransport());
expect(() => {
Logger.initialized();
}).not.to.throw();
Logger.setTransport();
expect(() => {
Logger.initialized();
}).to.throw();
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
// noop
});
process.env.ALLOW_NO_TRANSPORT = 'true';
expect(() => {
Logger.initialized();
}).not.to.throw();
delete process.env.ALLOW_NO_TRANSPORT;
expect(spy).to.have.been.called();
process.env.NODE_ENV = nodeEnv;
}
@test
log() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.log('Foobar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[LOG]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
@test
'log without output'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
process.env.STAPPS_LOG_LEVEL = '0';
Logger.log('Foobar');
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).to.not.have.been.called();
}
@test
ok() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.ok('Foobar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[OK]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
@test
'ok without output'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
process.env.STAPPS_LOG_LEVEL = '0';
Logger.ok('Foobar');
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
@test
setTransport() {
expect(() => {
Logger.setTransport(new DummyTransport());
Logger.setTransport();
}).not.to.throw();
}
@test
'stringify'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.log('foo', 'bar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('foo');
expect(spy.__spy.calls[0][0]).to.contain('bar');
}
@test
'stringify object'() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.log({
foo: 'bar',
});
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('foo');
expect(spy.__spy.calls[0][0]).to.contain('bar');
}
@test
warn() {
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
// noop
});
Logger.warn('Foobar');
expect(spy).to.have.been.called();
expect(spy.__spy.calls[0][0]).to.contain('[WARN]');
expect(spy.__spy.calls[0][0]).to.contain('Foobar');
}
@test
'warn without output'() {
const spy = LoggerSpec.sandbox.on(console, 'warn', () => {
// noop
});
process.env.STAPPS_LOG_LEVEL = '0';
Logger.warn('Foobar');
delete process.env.STAPPS_LOG_LEVEL;
expect(spy).not.to.have.been.called();
}
@test
'log level exclusiveness'() {
const warnSpy = LoggerSpec.sandbox.on(console, 'warn', () => {
// noop WARN
});
const infoSpy = LoggerSpec.sandbox.on(console, 'info', () => {
// noop INFO
});
const okSpy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop OK
});
// only warn and info = warn + info = 4 + 1 = 5
process.env.STAPPS_LOG_LEVEL = '5';
Logger.warn('Foo');
Logger.info('Bar');
Logger.ok('Baz');
expect(warnSpy).to.have.been.called();
expect(warnSpy.__spy.calls[0][0]).to.contain('[WARN]');
expect(warnSpy.__spy.calls[0][0]).to.contain('Foo');
expect(infoSpy).to.have.been.called();
expect(infoSpy.__spy.calls[0][0]).to.contain('[INFO]');
expect(infoSpy.__spy.calls[0][0]).to.contain('Bar');
expect(okSpy).to.not.have.been.called();
delete process.env.STAPPS_LOG_LEVEL;
}
@test
getExitLevel() {
const savedProcess = process;
// @ts-ignore
process = undefined;
(global as any).window = {
STAPPS_EXIT_LEVEL: 0,
};
const stub = LoggerSpec.sandbox.on(console, 'info', () => {
// noop
});
Logger.info('Foobar');
process = savedProcess;
delete (global as any).window;
expect(stub).not.to.have.been.called();
}
@test
getLogLevel() {
const savedProcess = process;
// @ts-ignore
process = undefined;
(global as any).window = {
STAPPS_LOG_LEVEL: 0,
};
const stub = LoggerSpec.sandbox.on(console, 'info', () => {
// noop
});
Logger.info('Foobar');
process = savedProcess;
delete (global as any).window;
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;
}
}

View File

@@ -13,12 +13,10 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {expect} from 'chai'; import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript'; import {suite, test} from 'mocha-typescript';
import {SMTP} from '../src/SMTP'; import {SMTP} from '../src/smtp';
// tslint:disable:no-unused-expression @suite()
@suite(timeout(2000), slow(1000))
export class SMTPSpec { export class SMTPSpec {
/* tslint:disable:member-ordering */ /* tslint:disable:member-ordering */
@test @test
@@ -105,5 +103,6 @@ export class SMTPSpec {
mailValidation17() { mailValidation17() {
expect(SMTP.isValidEmailAddress('stordeur@campus')).to.be.false; expect(SMTP.isValidEmailAddress('stordeur@campus')).to.be.false;
} }
/* tslint:enable:member-ordering */ /* tslint:enable:member-ordering */
} }

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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');
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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');
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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`);
}
}

31
test/transport.spec.ts Normal file
View File

@@ -0,0 +1,31 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {suite, test} from 'mocha-typescript';
import {isTransportWithVerification} from '../src/common';
import {DummyTransport, VerifiableDummyTransport} from './dummyTransport';
@suite()
export class TransportSpec {
@test
isNotTransportWithVerification() {
return expect(isTransportWithVerification(new DummyTransport())).to.be.false;
}
@test
isTransportWithVerification() {
return expect(isTransportWithVerification(new VerifiableDummyTransport())).to.be.true;
}
}

View File

@@ -1,3 +1,6 @@
{ {
"extends": "./node_modules/@openstapps/configuration/tslint.json" "extends": "./node_modules/@openstapps/configuration/tslint.json",
"rules": {
"no-redundant-jsdoc": false
}
} }