Compare commits

..

18 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
22 changed files with 1690 additions and 1115 deletions

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"
}

View File

@@ -6,7 +6,7 @@ cache:
- node_modules - node_modules
before_script: before_script:
- npm install - npm ci
stages: stages:
- build - build
@@ -27,9 +27,12 @@ test:
stage: test stage: test
script: script:
- npm test - npm test
coverage: '/Statements[^:]*\:[^:]*\s+([\d\.]+)%/'
artifacts: artifacts:
paths: reports:
- coverage coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
audit: audit:
stage: test stage: test

View File

@@ -1,3 +1,24 @@
# [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.8.0](https://gitlab.com/openstapps/logger/compare/v0.7.0...v0.8.0) (2021-12-14)

View File

@@ -22,8 +22,7 @@ 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`. 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 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. 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.
@@ -52,7 +51,7 @@ Environment variables are:
## Transformations ## Transformations
By default the logger will only add the log level to the message. 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 can change this behavior by setting other Transformers via `Logger.setTransformations`. If you do so, mind the order of the transformers.

2384
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.8.1", "version": "1.1.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,7 +8,7 @@
}, },
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"scripts": { "scripts": {
"build": "npm run tslint && npm run compile", "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'", "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", "check-configuration": "openstapps-configuration",
"compile": "rimraf lib && tsc && prepend lib/cli.js '#!/usr/bin/env node\n'", "compile": "rimraf lib && tsc && prepend lib/cli.js '#!/usr/bin/env node\n'",
@@ -17,8 +17,8 @@
"prepublishOnly": "npm ci && npm run build", "prepublishOnly": "npm ci && npm run build",
"preversion": "npm run prepublishOnly", "preversion": "npm run prepublishOnly",
"push": "git push && git push origin \"v$npm_package_version\"", "push": "git push && git push origin \"v$npm_package_version\"",
"test": "nyc mocha --require ts-node/register --ui mocha-typescript 'test/**/*.spec.ts'", "test": "nyc mocha 'test/**/*.spec.ts'",
"tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'" "lint": "eslint --ext .ts src/"
}, },
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>", "author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
"contributors": [ "contributors": [
@@ -28,6 +28,43 @@
], ],
"typings": "./lib/logger.d.ts", "typings": "./lib/logger.d.ts",
"main": "./lib/logger.js", "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": { "nyc": {
"all": true, "all": true,
"branches": 95, "branches": 95,
@@ -46,6 +83,7 @@
"lines": 95, "lines": 95,
"per-file": true, "per-file": true,
"reporter": [ "reporter": [
"cobertura",
"html", "html",
"text-summary" "text-summary"
], ],
@@ -53,33 +91,5 @@
"ts-node/register" "ts-node/register"
], ],
"statements": 95 "statements": 95
},
"devDependencies": {
"@openstapps/configuration": "0.29.0",
"@types/chai": "4.3.1",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
"@types/mocha": "9.1.1",
"chai": "4.3.6",
"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",
"nyc": "15.1.0",
"prepend-file-cli": "1.0.6",
"rimraf": "3.0.2",
"ts-node": "10.8.0",
"tslint": "6.1.3",
"typedoc": "0.22.15",
"typescript": "4.4.4"
},
"dependencies": {
"@types/node": "14.18.18",
"@types/nodemailer": "6.4.4",
"chalk": "4.1.2",
"flatted": "3.2.5",
"moment": "2.29.3",
"nodemailer": "6.7.5"
} }
} }

View File

@@ -20,41 +20,43 @@ import {Transport, VerifiableTransport} from './transport';
* Copied from https://stackoverflow.com/a/51365037 * Copied from https://stackoverflow.com/a/51365037
*/ */
export type RecursivePartial<T> = { export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U> ? [P in keyof T]?: T[P] extends Array<infer U>
Array<RecursivePartial<U>> : ? Array<RecursivePartial<U>>
T[P] extends object ? RecursivePartial<T[P]> : T[P]; : T[P] extends object
? RecursivePartial<T[P]>
: T[P];
}; };
/** /**
* Deletes all properties that are undefined from an object * Deletes all properties that are undefined from an object
* *
* @param obj Object to delete undefined properties from * @param object Object to delete undefined properties from
*/ */
export function deleteUndefinedProperties(obj: unknown): unknown { export function deleteUndefinedProperties(object: 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 object !== 'object' || Array.isArray(object)) {
return obj; return object;
} }
// check each key // check each key
for (const key in obj) { for (const key in object) {
/* istanbul ignore if */ /* istanbul ignore if */
if (!obj.hasOwnProperty(key)) { if (!object.hasOwnProperty(key)) {
continue; continue;
} }
const indexedObj = obj as { [k: string]: unknown; }; const indexedObject = object as {[k: string]: unknown};
if (typeof indexedObj[key] === 'undefined') { if (typeof indexedObject[key] === 'undefined') {
// delete undefined keys // delete undefined keys
delete indexedObj[key]; delete indexedObject[key];
} else { } else {
// check recursive // check recursive
indexedObj[key] = deleteUndefinedProperties(indexedObj[key]); indexedObject[key] = deleteUndefinedProperties(indexedObject[key]);
} }
} }
return obj; return object;
} }
/** /**
@@ -68,9 +70,11 @@ export function isNodeEnvironment(): boolean {
* Checks if environment is productive * Checks if environment is productive
*/ */
export function isProductiveEnvironment(): boolean { export function isProductiveEnvironment(): boolean {
return typeof process.env === 'object' return (
&& typeof process.env.NODE_ENV !== 'undefined' typeof process.env === 'object' &&
&& process.env.NODE_ENV === 'production'; typeof process.env.NODE_ENV !== 'undefined' &&
process.env.NODE_ENV === 'production'
);
} }
/** /**

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {stringify} from 'flatted'; import {stringify} from 'flatted';
import {isNodeEnvironment, isProductiveNodeEnvironment} from './common'; import {isNodeEnvironment, isProductiveEnvironment, isProductiveNodeEnvironment} from './common';
import {Transformation} from './transformation'; import {Transformation} from './transformation';
import {AddLogLevel} from './transformations/add-log-level'; import {AddLogLevel} from './transformations/add-log-level';
import {Transport} from './transport'; import {Transport} from './transport';
@@ -24,7 +24,7 @@ import {Transport} from './transport';
* @param something Something to check * @param something Something to check
*/ */
// tslint:disable-next-line:completed-docs // tslint:disable-next-line:completed-docs
function hasStAppsLogLevel(something: object): something is { STAPPS_LOG_LEVEL: number; } { function hasStAppsLogLevel(something: object): something is {STAPPS_LOG_LEVEL: number} {
return 'STAPPS_LOG_LEVEL' in something; return 'STAPPS_LOG_LEVEL' in something;
} }
@@ -34,7 +34,7 @@ function hasStAppsLogLevel(something: object): something is { STAPPS_LOG_LEVEL:
* @param something Something to check * @param something Something to check
*/ */
// tslint:disable-next-line:completed-docs // tslint:disable-next-line:completed-docs
function hasStAppsExitLevel(something: object): something is { STAPPS_EXIT_LEVEL: number; } { function hasStAppsExitLevel(something: object): something is {STAPPS_EXIT_LEVEL: number} {
return 'STAPPS_EXIT_LEVEL' in something; return 'STAPPS_EXIT_LEVEL' in something;
} }
@@ -72,13 +72,7 @@ export class Logger {
/** /**
* Log levels * Log levels
*/ */
private static readonly logLevels: LogLevel[] = [ private static readonly logLevels: LogLevel[] = ['INFO', 'LOG', 'WARN', 'ERROR', 'OK'];
'INFO',
'LOG',
'WARN',
'ERROR',
'OK',
];
/** /**
* Log level sum, equivalent to all log levels enabled * Log level sum, equivalent to all log levels enabled
@@ -88,9 +82,7 @@ export class Logger {
/** /**
* Transformers for log output * Transformers for log output
*/ */
private static transformations?: Transformation[] = [ private static transformations?: Transformation[] = [new AddLogLevel()];
new AddLogLevel(),
];
/** /**
* Transport for errors * Transport for errors
@@ -99,11 +91,16 @@ export class Logger {
/** /**
* Apply transformations to an output * Apply transformations to an output
* Will strip newlines in production environment
* *
* @param logLevel Log level of the output * @param logLevel Log level of the output
* @param output Output to apply transformations to * @param output Output to apply transformations to
*/ */
private static applyTransformers(logLevel: LogLevel, output: string): string { 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) { if (!Array.isArray(Logger.transformations) || Logger.transformations.length === 0) {
return output; return output;
} }
@@ -147,8 +144,10 @@ export class Logger {
if (isProductiveNodeEnvironment()) { if (isProductiveNodeEnvironment()) {
return; return;
} }
// tslint:disable-next-line: no-console // eslint-disable-next-line no-console
console.error(Logger.applyTransformers('ERROR', `exiting as of used exit level ${Logger.getLevel('EXIT')} !`)); console.error(
Logger.applyTransformers('ERROR', `exiting as of used exit level ${Logger.getLevel('EXIT')} !`),
);
process.exit(-1); process.exit(-1);
} }
@@ -171,7 +170,7 @@ export class Logger {
if (isNodeEnvironment() && typeof environmentLevel !== 'undefined') { if (isNodeEnvironment() && typeof environmentLevel !== 'undefined') {
// Node.js environment exists // Node.js environment exists
return parseInt(environmentLevel, 10); return Number.parseInt(environmentLevel, 10);
} }
// Fallback to log everything, or not exiting // Fallback to log everything, or not exiting
@@ -195,23 +194,25 @@ export class Logger {
/** /**
* Log an error * Log an error
* *
* @param args Arguments to log * @param arguments_ Arguments to log
*/ */
public static async error(...args: unknown[]): Promise<string | void> { public static async error(...arguments_: unknown[]): Promise<string | void> {
if (!Logger.checkLogLevel('ERROR')) { if (!Logger.checkLogLevel('ERROR')) {
return; return;
} }
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...args))); console.error(Logger.applyTransformers('ERROR', Logger.stringifyArguments(...arguments_)));
if (isProductiveNodeEnvironment()) { if (isProductiveNodeEnvironment()) {
if (typeof Logger.transport !== 'undefined') { if (typeof Logger.transport !== 'undefined') {
return Logger.transport.send('Error', Logger.stringifyArguments(...args)); return Logger.transport.send('Error', Logger.stringifyArguments(...arguments_));
} }
if (process.env.ALLOW_NO_TRANSPORT !== 'true') { 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'.`); throw new Error(
`Error couldn't be transported! Please set a transport or set ALLOW_NO_TRANSPORT='true'.`,
);
} }
} }
if (Logger.checkExitLevel('ERROR')) { if (Logger.checkExitLevel('ERROR')) {
@@ -222,15 +223,15 @@ export class Logger {
/** /**
* Log an information * Log an information
* *
* @param args Arguments to log * @param arguments_ Arguments to log
*/ */
public static info(...args: unknown[]): void { public static info(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('INFO')) { if (!Logger.checkLogLevel('INFO')) {
return; return;
} }
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...args))); console.info(Logger.applyTransformers('INFO', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('INFO')) { if (Logger.checkExitLevel('INFO')) {
Logger.exit(); Logger.exit();
} }
@@ -252,15 +253,15 @@ export class Logger {
/** /**
* Log something * Log something
* *
* @param args Arguments to log * @param arguments_ Arguments to log
*/ */
public static log(...args: unknown[]): void { public static log(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('LOG')) { if (!Logger.checkLogLevel('LOG')) {
return; return;
} }
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...args))); console.log(Logger.applyTransformers('LOG', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('LOG')) { if (Logger.checkExitLevel('LOG')) {
Logger.exit(); Logger.exit();
} }
@@ -269,15 +270,15 @@ export class Logger {
/** /**
* Log something successful * Log something successful
* *
* @param args Arguments to log * @param arguments_ Arguments to log
*/ */
public static ok(...args: unknown[]): void { public static ok(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('OK')) { if (!Logger.checkLogLevel('OK')) {
return; return;
} }
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...args))); console.log(Logger.applyTransformers('OK', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('OK')) { if (Logger.checkExitLevel('OK')) {
Logger.exit(); Logger.exit();
} }
@@ -289,7 +290,10 @@ export class Logger {
* @param transformations List of transformations * @param transformations List of transformations
*/ */
public static setTransformations(transformations: Transformation[]) { public static setTransformations(transformations: Transformation[]) {
Logger.transformations = transformations; const transforms = transformations.filter(transform =>
isProductiveEnvironment() ? transform.useInProduction === true : true,
);
Logger.transformations = transforms;
} }
/** /**
@@ -304,12 +308,12 @@ export class Logger {
/** /**
* Stringify a list of arguments * Stringify a list of arguments
* *
* @param args Arguments to stringify * @param arguments_ Arguments to stringify
*/ */
public static stringifyArguments(...args: unknown[]): string { public static stringifyArguments(...arguments_: unknown[]): string {
const result: string[] = []; const result: string[] = [];
args.forEach((argument) => { for (const argument of arguments_) {
if (typeof argument === 'string' || typeof argument === 'number') { if (typeof argument === 'string' || typeof argument === 'number') {
result.push(argument.toString()); result.push(argument.toString());
} else if (argument instanceof Error) { } else if (argument instanceof Error) {
@@ -318,10 +322,9 @@ export class Logger {
result.push(argument.stack); result.push(argument.stack);
} }
} else { } else {
// tslint:disable-next-line:no-magic-numbers result.push(stringify(argument, undefined, 2));
result.push(stringify(argument, null, 2));
} }
}); }
return result.join(', '); return result.join(', ');
} }
@@ -329,15 +332,15 @@ export class Logger {
/** /**
* Log a warning * Log a warning
* *
* @param args Arguments to log * @param arguments_ Arguments to log
*/ */
public static warn(...args: unknown[]): void { public static warn(...arguments_: unknown[]): void {
if (!Logger.checkLogLevel('WARN')) { if (!Logger.checkLogLevel('WARN')) {
return; return;
} }
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...args))); console.warn(Logger.applyTransformers('WARN', Logger.stringifyArguments(...arguments_)));
if (Logger.checkExitLevel('WARN')) { if (Logger.checkExitLevel('WARN')) {
Logger.exit(); Logger.exit();
} }

View File

@@ -138,8 +138,8 @@ export class SMTP extends VerifiableTransport {
if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT === 'true') {
try { try {
SMTP._instance = new SMTP(config); SMTP._instance = new SMTP(config);
} catch (err) { } catch {
/* tslint:disable-next-line:no-console */ // eslint-disable-next-line no-console
console.warn('SMTP config failed.'); console.warn('SMTP config failed.');
return; return;
@@ -163,7 +163,9 @@ export class SMTP extends VerifiableTransport {
*/ */
public static isValidEmailAddress(address: string): boolean { public static isValidEmailAddress(address: string): boolean {
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
return /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(address); return /^(([^<>()\[\].,;:\s@"]+(\.[^<>()\[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(
address,
);
} }
/** /**
@@ -184,18 +186,18 @@ export class SMTP extends VerifiableTransport {
*/ */
private constructor(smtpConfig?: 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 environmentConfig: 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 !== 'undefined') ? (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 !== 'undefined') ? parseInt(process.env.SMTP_PORT, 10) : undefined, port:
recipients: (typeof process.env.SMTP_RECIPIENTS !== 'undefined') ? typeof process.env.SMTP_PORT !== 'undefined' ? Number.parseInt(process.env.SMTP_PORT, 10) : undefined,
(process.env.SMTP_RECIPIENTS).split(',') : 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, 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,
@@ -205,49 +207,49 @@ export class SMTP extends VerifiableTransport {
const config = { const config = {
...smtpConfig, ...smtpConfig,
// 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) as object), ...(deleteUndefinedProperties(environmentConfig) as object),
} as SMTPConfig; } as SMTPConfig;
if (typeof config.host === 'undefined') { if (typeof config.host === 'undefined') {
throw new Error( throw new TypeError(
'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 === 'undefined' || isNaN(config.port)) { if (typeof config.port === 'undefined' || Number.isNaN(config.port)) {
throw new Error( throw new TypeError(
'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 TypeError(
'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 === 'undefined') { if (typeof config.auth.user === 'undefined') {
throw new Error( throw new TypeError(
'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 === 'undefined') { if (typeof config.auth.password === 'undefined') {
throw new Error( throw new TypeError(
'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 === 0) {
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 === 'undefined') { if (typeof config.sender.mail === 'undefined') {
throw new Error( throw new TypeError(
'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).',
); );
} }
@@ -304,7 +306,7 @@ export class SMTP extends VerifiableTransport {
return 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,
subject: subject, subject: subject,
text: message, text: message,
to: this.recipients, to: this.recipients,
@@ -341,20 +343,19 @@ export class SMTP extends VerifiableTransport {
* @returns true if the transport is valid * @returns true if the transport is valid
*/ */
public async verify(): Promise<boolean> { public async verify(): Promise<boolean> {
let verificationSuccessfull = false; let verificationSuccessfull = false;
try { try {
verificationSuccessfull = await this.transportAgent.verify(); verificationSuccessfull = await this.transportAgent.verify();
} catch (err) { } catch (error) {
if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') { if (!isProductiveEnvironment() || process.env.ALLOW_NO_TRANSPORT !== 'true') {
throw err; throw error;
} }
/* tslint:disable-next-line:no-console */ // eslint-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 as Error).message, (error as Error).message,
); );
} }
@@ -362,12 +363,12 @@ export class SMTP extends VerifiableTransport {
if (!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`',
); );
} }
/* tslint:disable-next-line:no-console */ // eslint-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;

View File

@@ -18,6 +18,11 @@ import {LogLevel} from './logger';
* A transformer for log output * A transformer for log output
*/ */
export interface Transformation { export interface Transformation {
/**
* Indicates if this transformation is stripped in production environments
*/
useInProduction: boolean;
/** /**
* Transform an output * Transform an output
* *

View File

@@ -19,6 +19,11 @@ import {Transformation} from '../transformation';
* Transformation that adds the log level to output * Transformation that adds the log level to output
*/ */
export class AddLogLevel implements Transformation { export class AddLogLevel implements Transformation {
/**
* Keep this transformation in production environments
*/
useInProduction = true;
/** /**
* Add log level to output * Add log level to output
* *

View File

@@ -12,7 +12,7 @@
* 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 chalk, {Chalk} from 'chalk'; import chalk from 'chalk';
import {LogLevel} from '../logger'; import {LogLevel} from '../logger';
import {Transformation} from '../transformation'; import {Transformation} from '../transformation';
@@ -20,18 +20,25 @@ import {Transformation} from '../transformation';
* Transformation that colorizes log output * Transformation that colorizes log output
*/ */
export class Colorize implements Transformation { export class Colorize implements Transformation {
/**
* Skip this transformation in production environments
*/
useInProduction = false;
/** /**
* Instantiate a new colorize transformation * Instantiate a new colorize transformation
* *
* @param logLevelToColor Map from log level to color transformation to apply * @param logLevelToColor Map from log level to color transformation to apply
*/ */
constructor(private readonly logLevelToColor: { [k in LogLevel]: Chalk; } = { constructor(
ERROR: chalk.bold.red, private readonly logLevelToColor: {[k in LogLevel]: chalk.Chalk} = {
INFO: chalk.cyan, ERROR: chalk.bold.red,
LOG: chalk.white, INFO: chalk.cyan,
OK: chalk.bold.green, LOG: chalk.white,
WARN: chalk.yellow, OK: chalk.bold.green,
}) { WARN: chalk.yellow,
},
) {
// noop // noop
} }

View File

@@ -14,20 +14,15 @@
*/ */
import {LogLevel} from '../logger'; import {LogLevel} from '../logger';
import {Transformation} from '../transformation'; import {Transformation} from '../transformation';
/** /**
* Transformation that adds a timestamp to output * Transformation that adds a timestamp to output
*/ */
export class Timestamp implements Transformation { export class Timestamp implements Transformation {
/** /**
* Instantiate a new timestamp transformation * Keep this transformation in production environments
*
* @see https://momentjs.com/docs/#/displaying/format/
*
* @param format Format for timestamps
*/ */
constructor(private readonly format = 'LLLL') { useInProduction = true;
// noop
}
/** /**
* Add timestamp to output * Add timestamp to output
@@ -36,9 +31,6 @@ export class Timestamp implements Transformation {
* @param output Output to add timestamp to * @param output Output to add timestamp to
*/ */
transform(_logLevel: LogLevel, output: string): string { transform(_logLevel: LogLevel, output: string): string {
const moment = require('moment'); return `[${new Date().toISOString()}] ${output}`;
const now = moment();
return `[${now.format(this.format)}] ${output}`;
} }
} }

View File

@@ -13,7 +13,7 @@
* 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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import { import {
deleteUndefinedProperties, deleteUndefinedProperties,
isNodeEnvironment, isNodeEnvironment,

View File

@@ -16,9 +16,10 @@ import chai from 'chai';
import {expect} from 'chai'; import {expect} from 'chai';
import chaiAsPromised from 'chai-as-promised'; import chaiAsPromised from 'chai-as-promised';
import chaiSpies from 'chai-spies'; import chaiSpies from 'chai-spies';
import {suite} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {Logger} from '../src/logger'; import {Logger} from '../src/logger';
import {AddLogLevel} from '../src/transformations/add-log-level'; import {AddLogLevel} from '../src/transformations/add-log-level';
import {Colorize} from '../src/transformations/colorize';
import {DummyTransport} from './dummyTransport'; import {DummyTransport} from './dummyTransport';
chai.should(); chai.should();
@@ -236,6 +237,34 @@ export class LoggerSpec {
process.env.NODE_ENV = nodeEnv; 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 @test
log() { log() {
const spy = LoggerSpec.sandbox.on(console, 'log', () => { const spy = LoggerSpec.sandbox.on(console, 'log', () => {
@@ -443,7 +472,7 @@ export class LoggerSpec {
const stub = LoggerSpec.sandbox.on(console, 'log', () => { const stub = LoggerSpec.sandbox.on(console, 'log', () => {
// noop // noop
}); });
const applyTransformationsSpy = LoggerSpec.sandbox.on(Logger, 'applyTransformations'); const applyTransformationsSpy = LoggerSpec.sandbox.on(new Logger(), 'applyTransformations');
Logger.log('Foobar'); Logger.log('Foobar');

View File

@@ -13,7 +13,7 @@
* 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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {SMTP} from '../src/smtp'; import {SMTP} from '../src/smtp';
@suite() @suite()

View File

@@ -13,7 +13,7 @@
* 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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {AddLogLevel} from '../../src/transformations/add-log-level'; import {AddLogLevel} from '../../src/transformations/add-log-level';
@suite() @suite()

View File

@@ -13,7 +13,7 @@
* 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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {Colorize} from '../../src/transformations/colorize'; import {Colorize} from '../../src/transformations/colorize';
@suite() @suite()

View File

@@ -13,23 +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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {Timestamp} from '../../src/transformations/timestamp'; import {Timestamp} from '../../src/transformations/timestamp';
import moment = require('moment');
@suite() @suite()
export class ColorizeSpec { export class TimeStampSpec {
@test @test
'default'() { 'default'() {
const transformation = new Timestamp(); const transformation = new Timestamp();
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('LLLL')}] Foobar`); expect(transformation.transform('ERROR', 'Foobar')).to.be.contain(`Z`);
}
@test
'different format'() {
const transformation = new Timestamp('DD.MM.YYYY');
expect(transformation.transform('ERROR', 'Foobar')).to.be.equal(`[${moment().format('DD.MM.YYYY')}] Foobar`);
} }
} }

View File

@@ -13,7 +13,7 @@
* 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 {suite, test} from 'mocha-typescript'; import {suite, test} from '@testdeck/mocha';
import {isTransportWithVerification} from '../src/common'; import {isTransportWithVerification} from '../src/common';
import {DummyTransport, VerifiableDummyTransport} from './dummyTransport'; import {DummyTransport, VerifiableDummyTransport} from './dummyTransport';

View File

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