Compare commits

...

65 Commits

Author SHA1 Message Date
Rainer Killinger
aac8e584a9 1.1.1 2022-11-08 15:06:51 +01:00
Rainer Killinger
8d6ea040c1 fix: removing transformations in production 2022-11-08 15:04:53 +01:00
Rainer Killinger
e0f4ce134d docs: update changelog 2022-11-01 19:32:43 +01:00
Rainer Killinger
276e6d3b40 1.1.0 2022-11-01 19:32:42 +01:00
Rainer Killinger
ae0612c6d5 refactor: update dependencies 2022-11-01 19:19:10 +01:00
Rainer Killinger
8aef5b8d5b fix: compatibility with log aggregators 2022-11-01 19:15:32 +01:00
openstappsbot
107d94d499 refactor: update all 2022-10-24 07:16:13 +00:00
Rainer Killinger
7eb727ea27 docs: update changelog 2022-10-12 12:16:22 +02:00
Rainer Killinger
134c2e4c84 1.0.1 2022-10-12 12:16:21 +02:00
openstappsbot
60bc460841 refactor: update typescript-eslint monorepo to v5.40.0 2022-10-11 15:43:41 +00:00
openstappsbot
c8be14f9d8 refactor: update all 2022-10-11 07:22:17 +00:00
Rainer Killinger
e14b1248d5 ci: add cobertura coverage report 2022-08-17 11:32:18 +02:00
Rainer Killinger
734c6ec262 docs: update changelog 2022-08-17 10:17:40 +02:00
Rainer Killinger
6c51f777dd 1.0.0 2022-08-17 10:17:39 +02:00
Rainer Killinger
82a3651fb3 test: move to @testdeck/mocha 2022-08-17 10:13:26 +02:00
Rainer Killinger
e6e75db3e8 refactor: update to typescript 4.4.4 2022-08-17 10:01:47 +02:00
openstappsbot
eeda6ef26c refactor: update all 2022-08-16 07:14:31 +00:00
Rainer Killinger
73e331cd94 docs: update changelog 2022-05-27 16:07:21 +02:00
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
30 changed files with 5214 additions and 3679 deletions

View File

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

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
resources
openapi

3
.eslintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "@openstapps"
}

31
.gitignore vendored
View File

@@ -20,7 +20,7 @@ coverage
# nyc test coverage
.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
# Bower dependency directory (https://bower.io/)
@@ -29,14 +29,14 @@ bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
@@ -57,6 +57,29 @@ typings/
# dotenv environment variables file
.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
.idea
.vscode
@@ -65,4 +88,4 @@ typings/
lib
# ignore docs
docs
docs

View File

@@ -1,13 +1,12 @@
image: node:lts-alpine
image: registry.gitlab.com/openstapps/projectmanagement/node
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- lib
- node_modules
before_script:
- npm install
- npm ci
stages:
- build
@@ -18,14 +17,52 @@ build:
stage: build
script:
- npm run build
artifacts:
paths:
- lib
test:
variables:
FORCE_COLOR: '1' # see https://github.com/chalk/chalk/issues/203
stage: test
script:
- npm test
coverage: '/Statements[^:]*\:[^:]*\s+([\d\.]+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
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:
- coverage
- lib
pages:
stage: deploy
@@ -36,4 +73,4 @@ pages:
- /^v[0-9]+\.[0-9]+\.[0-9]+$/
artifacts:
paths:
- public
- public

View File

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

View File

@@ -1,3 +1,94 @@
# [1.1.0](https://gitlab.com/openstapps/logger/compare/v1.0.1...v1.1.0) (2022-11-01)
### Bug Fixes
* compatibility with log aggregators ([8aef5b8](https://gitlab.com/openstapps/logger/commit/8aef5b8d5b5fe4ed4ff7f17fd679ebdf83381001))
## [1.0.1](https://gitlab.com/openstapps/logger/compare/v1.0.0...v1.0.1) (2022-10-12)
# [1.0.0](https://gitlab.com/openstapps/logger/compare/v0.8.1...v1.0.0) (2022-08-17)
## [0.8.1](https://gitlab.com/openstapps/logger/compare/v0.8.0...v0.8.1) (2022-05-27)
# [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)
@@ -10,12 +101,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
* 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)
[![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
@@ -21,19 +22,23 @@ To select your desired log levels add the corresponding numbers and set the valu
For example `STAPPS_LOG_LEVEL=17` is 16 + 1 and would log everything that is `OK` or `INFO`.
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set
`ALLOW_NO_TRANSPORT` to `true`.
If you want to use logger in production (`NODE_ENV=production`) and allow all transports to fail set `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
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 +48,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. It will replace newlines with spaces and might skip some of your choosen Transformers when in production (`NODE_ENV=production`) for compatibility reasons with existing log aggregators and analyzers.
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

6445
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/logger",
"version": "0.0.5",
"version": "1.1.1",
"description": "A cli logger with colors, loglevels and the possibility to use a transport system for errors",
"repository": {
"type": "git",
@@ -8,61 +8,88 @@
},
"license": "GPL-3.0-only",
"scripts": {
"build": "npm run tslint && npm run compile && npm run documentation",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"compile": "tsc",
"documentation": "typedoc --includeDeclarations --excludeExternals --mode modules --out docs src",
"prepareOnly": "npm run build",
"test": "nyc mocha --require ts-node/register --ui mocha-typescript 'test/*.ts'",
"tslint": "tslint 'src/**/*.ts'"
"build": "npm run lint && npm run compile",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m 'docs: update changelog'",
"check-configuration": "openstapps-configuration",
"compile": "rimraf lib && tsc && prepend lib/cli.js '#!/usr/bin/env node\n'",
"documentation": "typedoc --name \"@openstapps/logger\" --includeVersion --out docs --readme README.md --listInvalidSymbolLinks --entryPointStrategy expand src",
"postversion": "npm run changelog",
"prepublishOnly": "npm ci && npm run build",
"preversion": "npm run prepublishOnly",
"push": "git push && git push origin \"v$npm_package_version\"",
"test": "nyc mocha 'test/**/*.spec.ts'",
"lint": "eslint --ext .ts src/"
},
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
"contributors": [
"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",
"main": "./lib/Logger.js",
"typings": "./lib/logger.d.ts",
"main": "./lib/logger.js",
"dependencies": {
"@types/node": "14.18.32",
"@types/nodemailer": "6.4.6",
"chalk": "4.1.2",
"flatted": "3.2.7",
"moment": "2.29.4",
"nodemailer": "6.8.0"
},
"devDependencies": {
"@openstapps/configuration": "0.33.0",
"@openstapps/eslint-config": "1.1.0",
"@testdeck/mocha": "0.3.0",
"@types/chai": "4.3.3",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
"@types/mocha": "10.0.0",
"@typescript-eslint/eslint-plugin": "5.42.0",
"@typescript-eslint/parser": "5.42.0",
"chai": "4.3.6",
"chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"conventional-changelog-cli": "2.2.2",
"eslint": "8.26.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-jsdoc": "39.4.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-unicorn": "43.0.2",
"mocha": "10.1.0",
"nyc": "15.1.0",
"prepend-file-cli": "1.0.6",
"prettier": "2.7.1",
"rimraf": "3.0.2",
"ts-node": "10.9.1",
"tslint": "6.1.3",
"typedoc": "0.22.18",
"typescript": "4.4.4"
},
"nyc": {
"all": true,
"branches": 95,
"check-coverage": true,
"per-file": true,
"lines": 0,
"statements": 0,
"functions": 0,
"branches": 0,
"include": [
"src"
"exclude": [
"src/smtp.ts",
"src/cli.ts"
],
"extension": [
".ts"
],
"functions": 95,
"include": [
"src"
],
"lines": 95,
"per-file": true,
"reporter": [
"cobertura",
"html",
"text-summary"
],
"all": true
},
"devDependencies": {
"@openstapps/configuration": "0.5.0",
"@types/chai": "4.1.7",
"@types/mocha": "5.2.5",
"@types/sinon": "7.0.3",
"chai": "4.2.0",
"conventional-changelog-cli": "2.0.11",
"mocha": "5.2.0",
"mocha-typescript": "1.1.17",
"nyc": "13.1.0",
"sinon": "7.2.2",
"ts-node": "7.0.1",
"tslint": "5.12.0",
"typedoc": "0.14.0",
"typescript": "3.2.2"
},
"dependencies": {
"@types/circular-json": "0.4.0",
"@types/node": "10.12.18",
"@types/nodemailer": "4.6.5",
"circular-json": "0.5.9",
"nodemailer": "5.1.1"
"require": [
"ts-node/register"
],
"statements": 95
}
}

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,38 +12,83 @@
* 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 {Transport, VerifiableTransport} from './transport';
/**
* A recursive partial object
*
* Copied from https://stackoverflow.com/a/51365037
*/
/**
* A recursive partial object
*
* Copied from https://stackoverflow.com/a/51365037
*/
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U> ?
Array<RecursivePartial<U>> :
T[P] extends object ? RecursivePartial<T[P]> : T[P];
[P in keyof T]?: T[P] extends Array<infer U>
? Array<RecursivePartial<U>>
: T[P] extends object
? RecursivePartial<T[P]>
: T[P];
};
/**
* Deletes all properties that are undefined from an object
* @param obj
*
* @param object Object to delete undefined properties from
*/
export function deleteUndefinedProperties(obj: any) {
export function deleteUndefinedProperties(object: unknown): unknown {
// return atomic data types and arrays (recursion anchor)
if (typeof obj !== 'object' || Array.isArray(obj)) {
return obj;
if (typeof object !== 'object' || Array.isArray(object)) {
return object;
}
// check each key
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'undefined') {
for (const key in object) {
/* istanbul ignore if */
if (!object.hasOwnProperty(key)) {
continue;
}
const indexedObject = object as {[k: string]: unknown};
if (typeof indexedObject[key] === 'undefined') {
// delete undefined keys
delete obj[key];
delete indexedObject[key];
} else {
// check recursive
obj[key] = deleteUndefinedProperties(obj[key]);
indexedObject[key] = deleteUndefinedProperties(indexedObject[key]);
}
});
}
return obj;
return object;
}
/**
* 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;
}

348
src/logger.ts Normal file
View File

@@ -0,0 +1,348 @@
/*
* 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, isProductiveEnvironment, 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
* Will strip newlines in production environment
*
* @param logLevel Log level of the output
* @param output Output to apply transformations to
*/
private static applyTransformers(logLevel: LogLevel, output: string): string {
if (isProductiveEnvironment()) {
output = output.replace(/[\n\r]/g, ' ');
}
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;
}
// eslint-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 Number.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 arguments_ Arguments to log
*/
public static async error(...arguments_: unknown[]): Promise<string | void> {
if (!Logger.checkLogLevel('ERROR')) {
return;
}
// eslint-disable-next-line no-console
console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...arguments_)));
if (isProductiveNodeEnvironment()) {
if (typeof Logger.transport !== 'undefined') {
return Logger.transport.send('Error', Logger.stringifyArguments(...arguments_));
}
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 arguments_ Arguments to log
*/
public static info(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('INFO')) {
return;
}
// eslint-disable-next-line no-console
console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...arguments_)));
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 arguments_ Arguments to log
*/
public static log(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('LOG')) {
return;
}
// eslint-disable-next-line no-console
console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('LOG')) {
Logger.exit();
}
}
/**
* Log something successful
*
* @param arguments_ Arguments to log
*/
public static ok(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('OK')) {
return;
}
// eslint-disable-next-line no-console
console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('OK')) {
Logger.exit();
}
}
/**
* Set transformations for log output
*
* @param transformations List of transformations
*/
public static setTransformations(transformations: Transformation[]) {
const transforms = transformations.filter(transform =>
isProductiveEnvironment() ? transform.useInProduction === true : true,
);
Logger.transformations = transforms;
}
/**
* Set a transport
*
* @param transport Transport to set
*/
public static setTransport(transport?: Transport) {
Logger.transport = transport;
}
/**
* Stringify a list of arguments
*
* @param arguments_ Arguments to stringify
*/
public static stringifyArguments(...arguments_: unknown[]): string {
const result: string[] = [];
for (const argument of arguments_) {
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 {
result.push(stringify(argument, undefined, 2));
}
}
return result.join(', ');
}
/**
* Log a warning
*
* @param arguments_ Arguments to log
*/
public static warn(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('WARN')) {
return;
}
// eslint-disable-next-line no-console
console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...arguments_)));
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
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -14,25 +14,57 @@
*/
import * as nodemailer from 'nodemailer';
import {MailOptions} from 'nodemailer/lib/sendmail-transport';
import {deleteUndefinedProperties, RecursivePartial} from './common';
import {Logger} from './Logger';
import {TransportWithVerification} from './Transport';
import {deleteUndefinedProperties, isProductiveEnvironment, RecursivePartial} from './common';
import {VerifiableTransport} from './transport';
/**
* A configuration of the transport used to send mails via SMTP
*/
export interface SMTPConfig {
/**
* Auth configuration
*/
auth: {
/**
* Password for login
*/
password: string;
/**
* User for login
*/
user: string;
};
/**
* List of "carbon copy" recipients
*/
cc?: string[];
/**
* SMTP host server
*/
host: string;
/**
* SMTP port
*/
port: number;
/**
* List of recipients
*/
recipients: string[];
/**
* Whether or not to establish a secure connection
*/
secure?: boolean;
/**
* Sender configuration
*/
sender: {
/**
* Mail of sender
*/
mail: string;
/**
* Name of sender
*/
name?: string;
};
}
@@ -40,32 +72,40 @@ export interface SMTPConfig {
/**
* An implementation of mail transport via SMTP
*/
export class SMTP extends TransportWithVerification {
export class SMTP extends VerifiableTransport {
/**
* Singleton instance
*/
private static _instance: SMTP;
/**
* List of all mail addresses to send in cc
*/
private cc: string[];
private readonly cc: string[];
/**
* Who is using this service
*/
private from: {
private readonly from: {
/**
* Mail of sender
*/
mail: string;
/**
* Name of sender
*/
name?: string;
};
/**
* List of all mail addresses to send to
*/
private recipients: string[];
private readonly recipients: string[];
/**
* Connection to SMTP server
*/
private transportAgent: nodemailer.Transporter;
private readonly transportAgent: nodemailer.Transporter;
/**
* 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_NAME: name of the sender
* SMTP_SECURE: `true` to enable tls
* @param config {SMTPConfig}
* @return {Transport}
*
* @param config SMTP config for instance
*/
public static getInstance(config?: SMTPConfig): SMTP | undefined {
// if an instance of SMTP already exists
if (this._instance) {
return this._instance;
if (typeof SMTP._instance !== 'undefined') {
return SMTP._instance;
}
// 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 {
this._instance = new this(config);
} catch (err) {
/* tslint:disable-next-line:no-console */
SMTP._instance = new SMTP(config);
} catch {
// eslint-disable-next-line no-console
console.warn('SMTP config failed.');
return;
}
} else {
// 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,116 +159,112 @@ export class SMTP extends TransportWithVerification {
* For more information please consider reading
* https://stackoverflow.com/a/2071250
*
* @param {string} address
* @return {boolean}
* @param address Address to validate
*/
public static isValidEmailAddress(address: string): boolean {
if (typeof address !== 'string') {
return false;
}
// tslint:disable-next-line:max-line-length
const regex = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
return regex.test(address);
return /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(
address,
);
}
/**
* Checks a list of mail addresses for validity.
* @param {string[]} recipients
* @return {string[]}
* Checks a list of mail addresses for validity
*
* @param recipients List of recipients to check
*/
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
* call `new SMTP()`
* @param {SMTPConfig} config
* Creates an SMTP connection.
*
* 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
const envConfig: RecursivePartial<SMTPConfig> = {
const environmentConfig: RecursivePartial<SMTPConfig> = {
auth: {
password: process.env.SMTP_AUTH_PASSWORD,
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,
port: (typeof process.env.SMTP_PORT === 'string') ? parseInt(process.env.SMTP_PORT, 10) : undefined,
recipients: (typeof process.env.SMTP_RECIPIENTS === 'string') ?
(process.env.SMTP_RECIPIENTS).split(',') :
[],
secure: (typeof process.env.SMTP_SECURE === 'string') ? (process.env.SMTP_SECURE === 'true') : false,
port:
typeof process.env.SMTP_PORT !== 'undefined' ? Number.parseInt(process.env.SMTP_PORT, 10) : undefined,
recipients:
typeof process.env.SMTP_RECIPIENTS !== 'undefined' ? process.env.SMTP_RECIPIENTS.split(',') : [],
secure: typeof process.env.SMTP_SECURE !== 'undefined' ? process.env.SMTP_SECURE === 'true' : false,
sender: {
mail: process.env.SMTP_SENDER_MAIL,
name: process.env.SMTP_SENDER_NAME,
},
};
// @ts-ignore
config = {
...config,
const config = {
...smtpConfig,
// deleting undefined properties so the actual config doesn't get overwritten by undefined values
...deleteUndefinedProperties(envConfig),
};
...(deleteUndefinedProperties(environmentConfig) as object),
} as SMTPConfig;
if (typeof config!.host !== 'string') {
throw new Error(
if (typeof config.host === 'undefined') {
throw new TypeError(
'SMTP configuration needs a host. Add it to the config or use environment variables (SMTP_HOST).',
);
}
if (typeof config!.port !== 'number' || isNaN(config!.port)) {
throw new Error(
if (typeof config.port === 'undefined' || Number.isNaN(config.port)) {
throw new TypeError(
'SMTP configuration needs a port. Add it to the config or use environment variables (SMTP_PORT).',
);
}
if (typeof config!.auth !== 'object') {
throw new Error(
if (typeof config.auth !== 'object') {
throw new TypeError(
'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') {
throw new Error(
if (typeof config.auth.user === 'undefined') {
throw new TypeError(
'SMTP auth configuration needs a user. Add it to the config or use environment variables (SMTP_AUTH_USER).',
);
}
if (typeof config!.auth.password !== 'string') {
throw new Error(
if (typeof config.auth.password === 'undefined') {
throw new TypeError(
'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 === 0) {
throw new Error(
'SMTP configuration needs recipients. Add it to the config or use environment variables (SMTP_RECIPIENTS).',
);
}
if (typeof config!.sender.mail !== 'string') {
throw new Error(
if (typeof config.sender.mail === 'undefined') {
throw new TypeError(
'SMTP configuration needs a sender. Add it to the config or use environment variables (SMTP_SENDER_MAIL).',
);
}
super();
if (SMTP.isValidRecipientsList(config!.recipients)) {
this.recipients = config!.recipients;
if (SMTP.isValidRecipientsList(config.recipients)) {
this.recipients = config.recipients;
} else {
throw new Error('Invalid recipients found');
}
if (typeof config!.cc !== 'undefined') {
if (SMTP.isValidRecipientsList(config!.cc)) {
this.cc = config!.cc!;
if (typeof config.cc !== 'undefined') {
if (SMTP.isValidRecipientsList(config.cc)) {
this.cc = config.cc;
} else {
throw new Error('Invalid cc recipients found');
}
@@ -235,25 +272,24 @@ export class SMTP extends TransportWithVerification {
this.cc = [];
}
this.from = config!.sender;
this.from = config.sender;
this.verified = false;
// creating transport with configuration
this.transportAgent = nodemailer.createTransport({
auth: {
pass: config!.auth.password,
user: config!.auth.user,
pass: config.auth.password,
user: config.auth.user,
},
host: config!.host,
port: config!.port,
secure: typeof config!.secure === 'boolean' ? config!.secure : false,
host: config.host,
port: config.port,
secure: typeof config.secure !== 'undefined' ? config.secure : false,
});
}
/**
* Check if instance was verified at least once
* @returns {boolean}
*/
public isVerified(): boolean {
return this.verified;
@@ -262,14 +298,15 @@ export class SMTP extends TransportWithVerification {
/**
* Sends a preconfigured mail with recipients and sender configured on
* 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> {
return await this.sendMail({
return this.sendMail({
cc: this.cc,
// 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,
subject: subject,
text: message,
to: this.recipients,
@@ -278,7 +315,8 @@ export class SMTP extends TransportWithVerification {
/**
* Sends a mail object
* @param {MailOptions} mail
*
* @param mail Mail to send
*/
public async sendMail(mail: MailOptions): Promise<string> {
// info is the response of the smtp server
@@ -305,36 +343,36 @@ export class SMTP extends TransportWithVerification {
* @returns true if the transport is valid
*/
public async verify(): Promise<boolean> {
let verificationSuccessfull: boolean = false;
let verificationSuccessfull = false;
try {
verificationSuccessfull = await this.transportAgent.verify();
} catch (err) {
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw err;
} else {
/* tslint:disable-next-line:no-console */
console.warn(
'SMTP verification error was ignored, because tranport failures are allowed: ',
err.message,
);
} catch (error) {
if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw error;
}
// eslint-disable-next-line no-console
console.warn(
'SMTP verification error was ignored, because tranport failures are allowed:',
(error as Error).message,
);
}
if (!verificationSuccessfull) {
if (!Logger.isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw new Error(
'Verification of SMTP transport failed.' +
'If you want to ignore this error set' +
'`NODE_ENV=dev` or `ALLOW_NO_TRANSPORT=true`',
'If you want to ignore this error set' +
'`NODE_ENV=dev` or `ALLOW_NO_TRANSPORT=true`',
);
} else {
/* tslint:disable-next-line:no-console */
console.warn('SMTP verification error was ignored, because tranport failures are allowed.');
}
// eslint-disable-next-line no-console
console.warn('SMTP verification error was ignored, because tranport failures are allowed.');
}
this.verified = verificationSuccessfull;
return verificationSuccessfull;
}
}

33
src/transformation.ts Normal file
View File

@@ -0,0 +1,33 @@
/*
* 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 {
/**
* Indicates if this transformation is stripped in production environments
*/
useInProduction: boolean;
/**
* 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,37 @@
/*
* 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 {
/**
* Keep this transformation in production environments
*/
useInProduction = true;
/**
* 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,54 @@
/*
* 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 from 'chalk';
import {LogLevel} from '../logger';
import {Transformation} from '../transformation';
/**
* Transformation that colorizes log output
*/
export class Colorize implements Transformation {
/**
* Skip this transformation in production environments
*/
useInProduction = false;
/**
* Instantiate a new colorize transformation
*
* @param logLevelToColor Map from log level to color transformation to apply
*/
constructor(
private readonly logLevelToColor: {[k in LogLevel]: chalk.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,36 @@
/*
* 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 {
/**
* Keep this transformation in production environments
*/
useInProduction = true;
/**
* 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 {
return `[${new Date().toISOString()}] ${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
* under the terms of the GNU General Public License as published by the Free
* 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 {
/**
* Send message with subject. A message can be a mail or something completly different. Depending on what Transport
* is implemented
* @param {string} subject
* @param {string} message
* Send message with subject
*
* A message can be a mail or something completely different depending on what transport is implemented.
*
* @param subject Subject of the message
* @param message Message to send
*/
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
* @returns {boolean}
*/
abstract isVerified(): boolean;
/**
* Verifies transport (check connection, authentication, ...)
* Verifies transport
*
* Check connection, authentication, ...
*/
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/>.
*/
import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript';
import {deleteUndefinedProperties} from '../src/common';
import {suite, test} from '@testdeck/mocha';
import {
deleteUndefinedProperties,
isNodeEnvironment,
isProductiveEnvironment,
isProductiveNodeEnvironment,
} from '../src/common';
@suite(timeout(2000), slow(1000))
@suite()
export class CommonSpec {
/* tslint:disable:member-ordering */
@test
deleteUndefinedProperties1() {
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);
});
}
}

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

@@ -0,0 +1,482 @@
/*
* 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, test} from '@testdeck/mocha';
import {Logger} from '../src/logger';
import {AddLogLevel} from '../src/transformations/add-log-level';
import {Colorize} from '../src/transformations/colorize';
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
'is compatible with log aggregation in productive environment'() {
Logger.setTransformations([new AddLogLevel(), new Colorize()]);
let spy = LoggerSpec.sandbox.on(console, 'log', () => {
// noop
});
Logger.log('Foo\nbar');
expect(spy).to.have.been.called.once;
expect(spy.__spy.calls[0][0]).to.equal('\u001B[37m[LOG] Foo\u001B[39m\n\u001B[37mbar\u001B[39m');
const nodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
process.env.ALLOW_NO_TRANSPORT = 'true';
Logger.setTransformations([new AddLogLevel(), new Colorize()]);
Logger.log('Foo\nbar');
expect(spy).to.have.been.called.twice;
expect(spy.__spy.calls[1][0]).to.equal('[LOG] Foo bar');
process.env.NODE_ENV = nodeEnv;
delete process.env.ALLOW_NO_TRANSPORT;
}
@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(new 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/>.
*/
import {expect} from 'chai';
import {slow, suite, test, timeout} from 'mocha-typescript';
import {SMTP} from '../src/SMTP';
import {suite, test} from '@testdeck/mocha';
import {SMTP} from '../src/smtp';
// tslint:disable:no-unused-expression
@suite(timeout(2000), slow(1000))
@suite()
export class SMTPSpec {
/* tslint:disable:member-ordering */
@test
@@ -105,5 +103,6 @@ export class SMTPSpec {
mailValidation17() {
expect(SMTP.isValidEmailAddress('stordeur@campus')).to.be.false;
}
/* 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 '@testdeck/mocha';
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 '@testdeck/mocha';
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,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 '@testdeck/mocha';
import {Timestamp} from '../../src/transformations/timestamp';
@suite()
export class TimeStampSpec {
@test
'default'() {
const transformation = new Timestamp();
expect(transformation.transform('ERROR', 'Foobar')).to.be.contain(`Z`);
}
}

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 '@testdeck/mocha';
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,3 @@
{
"extends": "./node_modules/@openstapps/configuration/tsconfig.json"
}
}

View File

@@ -1,3 +0,0 @@
{
"extends": "./node_modules/@openstapps/configuration/tslint.json"
}