Skip to content

Commit

Permalink
feat: add pretty error print log
Browse files Browse the repository at this point in the history
resolves #1925
  • Loading branch information
BrunnerLivio committed Jul 30, 2022
1 parent d9147d8 commit 818504e
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 310 deletions.
5 changes: 5 additions & 0 deletions lib/health-check/error-logger/error-logger.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { HealthIndicatorResult } from '../../health-indicator';

export interface ErrorLogger {
getErrorMessage(message: string, causes: HealthIndicatorResult): string;
}
24 changes: 24 additions & 0 deletions lib/health-check/error-logger/error-logger.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Provider } from '@nestjs/common';
import { ErrorLogStyle } from '../../terminus-options.interface';
import { ErrorLogger } from './error-logger.interface';
import { JsonErrorLogger } from './json-error-logger.service';
import { PrettyErrorLogger } from './pretty-error-logger.service';

export const ERROR_LOGGER = 'TERMINUS_ERROR_LOGGER';

export function getErrorLoggerProvider(
errorLogStyle?: ErrorLogStyle,
): Provider<ErrorLogger> {
switch (errorLogStyle) {
case 'pretty':
return {
provide: ERROR_LOGGER,
useClass: PrettyErrorLogger,
};
default:
return {
provide: ERROR_LOGGER,
useClass: JsonErrorLogger,
};
}
}
9 changes: 9 additions & 0 deletions lib/health-check/error-logger/error-loggers.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Type } from '@nestjs/common';
import { ErrorLogger } from './error-logger.interface';
import { JsonErrorLogger } from './json-error-logger.service';
import { PrettyErrorLogger } from './pretty-error-logger.service';

export const ERROR_LOGGERS: Type<ErrorLogger>[] = [
JsonErrorLogger,
PrettyErrorLogger,
];
9 changes: 9 additions & 0 deletions lib/health-check/error-logger/json-error-logger.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Injectable } from '@nestjs/common';
import { ErrorLogger } from './error-logger.interface';

@Injectable()
export class JsonErrorLogger implements ErrorLogger {
getErrorMessage(message: string, causes: any) {
return `${message} ${JSON.stringify(causes)}`;
}
}
159 changes: 159 additions & 0 deletions lib/health-check/error-logger/pretty-error-logger.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { Test } from '@nestjs/testing';
import { PrettyErrorLogger } from './pretty-error-logger.service';

const GREEN = '\x1b[0m\x1b[32m';
const RED = '\x1b[0m\x1b[31m';
const STOP_COLOR = '\x1b[0m';

describe('PrettyErrorLogger', () => {
let prettyErrorLogger: PrettyErrorLogger;

beforeEach(async () => {
const module = Test.createTestingModule({
providers: [PrettyErrorLogger],
});
const context = await module.compile();

prettyErrorLogger = context.get(PrettyErrorLogger);
});

it('should print one "up" results', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ────────┐
│ │
│ status: up │
│ │
└────────────────┘${STOP_COLOR}
`);
});

it('should print one "down" results', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'down',
},
});

expect(message).toBe(`message
${RED}┌ ❌ dog ──────────┐
│ │
│ status: down │
│ │
└──────────────────┘${STOP_COLOR}
`);
});

it('should print "up" and "down" results', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
},
pug: {
status: 'down',
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ────────┐
│ │
│ status: up │
│ │
└────────────────┘${STOP_COLOR}
${RED}┌ ❌ pug ──────────┐
│ │
│ status: down │
│ │
└──────────────────┘${STOP_COLOR}
`);
});

it('should print object details', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
foo: 'bar',
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ────────┐
│ │
│ status: up │
│ foo: bar │
│ │
└────────────────┘${STOP_COLOR}
`);
});

it('should print nested object details', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
foo: {
bar: 'baz',
},
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ──────────┐
│ │
│ status: up │
│ foo: │
│ - bar: baz │
│ │
└──────────────────┘${STOP_COLOR}
`);
});

it('should print array details', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
foo: ['bar', 'baz'],
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ────────┐
│ │
│ status: up │
│ foo: │
│ - 0: bar │
│ - 1: baz │
│ │
└────────────────┘${STOP_COLOR}
`);
});

it('should print symbol details', () => {
const message = prettyErrorLogger.getErrorMessage('message', {
dog: {
status: 'up',
foo: Symbol('TEST'),
},
});

expect(message).toBe(`message
${GREEN}┌ ✅ dog ───────────────┐
│ │
│ status: up │
│ foo: Symbol(TEST) │
│ │
└───────────────────────┘${STOP_COLOR}
`);
});
});
72 changes: 72 additions & 0 deletions lib/health-check/error-logger/pretty-error-logger.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Injectable } from '@nestjs/common';
import { HealthIndicatorResult } from '../../health-indicator';
import { ErrorLogger } from './error-logger.interface';
import * as boxen from 'boxen';

const GREEN = '\x1b[0m\x1b[32m';
const RED = '\x1b[0m\x1b[31m';
const STOP_COLOR = '\x1b[0m';

@Injectable()
export class PrettyErrorLogger implements ErrorLogger {
private printIndent(level: number) {
if (level === 0) {
return '';
}
return `${' '.repeat(level * 2)}- `;
}

private printIndicatorSummary(result: any, level = 0) {
const messages: string[] = [];
for (const [key, value] of Object.entries(result)) {
if (typeof value === 'object' && value !== null) {
messages.push(
`${this.printIndent(level)}${key}:\n${this.printIndicatorSummary(
value,
level + 1,
)}`,
);
} else {
const val = (value as any)?.toString
? (value as any).toString()
: value;
messages.push(`${this.printIndent(level)}${key}: ${val}`);
}
}
return messages.join('\n');
}

private printSummary(result: HealthIndicatorResult) {
let message = '';

for (const [key, value] of Object.entries(result)) {
const summary = this.printIndicatorSummary(value);

if (value.status === 'up') {
message +=
GREEN +
(boxen as any)(summary, {
padding: 1,
title: `✅ ${key}`,
}) +
STOP_COLOR +
'\n';
}
if (value.status === 'down') {
message +=
RED +
(boxen as any)(summary, {
padding: 1,
title: `❌ ${key}`,
}) +
STOP_COLOR +
'\n';
}
}
return message;
}

getErrorMessage(message: string, causes: HealthIndicatorResult) {
return `${message}\n\n${this.printSummary(causes)}`;
}
}
10 changes: 10 additions & 0 deletions lib/health-check/health-check.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { Test } from '@nestjs/testing';
import { HealthCheckService } from './health-check.service';
import { HealthCheckExecutor } from './health-check-executor.service';
import { ERROR_LOGGER } from './error-logger/error-logger.provider';
import { ErrorLogger } from './error-logger/error-logger.interface';

const healthCheckExecutorMock: Partial<HealthCheckExecutor> = {
execute: jest.fn(),
};

const errorLoggerMock: ErrorLogger = {
getErrorMessage: jest.fn(),
};

describe('HealthCheckService', () => {
let healthCheckExecutor: HealthCheckExecutor;
let healthCheckService: HealthCheckService;
Expand All @@ -18,6 +24,10 @@ describe('HealthCheckService', () => {
provide: HealthCheckExecutor,
useValue: healthCheckExecutorMock,
},
{
provide: ERROR_LOGGER,
useValue: errorLoggerMock,
},
],
});
const context = await module.compile();
Expand Down
29 changes: 17 additions & 12 deletions lib/health-check/health-check.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import {
Injectable,
ServiceUnavailableException,
Logger,
Inject,
} from '@nestjs/common';
import { HealthIndicatorFunction } from '../health-indicator';
import { ErrorLogger } from './error-logger/error-logger.interface';
import { ERROR_LOGGER } from './error-logger/error-logger.provider';
import { HealthCheckExecutor } from './health-check-executor.service';
import { HealthCheckResult } from './health-check-result.interface';

Expand All @@ -13,20 +16,14 @@ import { HealthCheckResult } from './health-check-result.interface';
*/
@Injectable()
export class HealthCheckService {
constructor(private healthCheckExecutor: HealthCheckExecutor) {}
constructor(
private readonly healthCheckExecutor: HealthCheckExecutor,
@Inject(ERROR_LOGGER)
private readonly errorLogger: ErrorLogger,
) {}

private readonly logger = new Logger(HealthCheckService.name);

/**
* Logs an error message of terminus
* @param message The log message
* @param error The error which was thrown
*/
private logError(message: string, causes: any) {
message = `${message} ${JSON.stringify(causes)}`;
this.logger.error(message);
}

/**
* Checks the given health indicators
*
Expand All @@ -47,7 +44,15 @@ export class HealthCheckService {
if (result.status === 'ok') {
return result;
}
this.logError('Health Check has failed!', result.error);

if (result.error) {
const msg = this.errorLogger.getErrorMessage(
'Health Check has failed!',
result.details,
);
this.logger.error(msg);
}

throw new ServiceUnavailableException(result);
}
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { TerminusModule } from './terminus.module';
export { TerminusModuleOptions } from './terminus-options.interface';
export * from './health-indicator';
export * from './errors';
export {
Expand Down
5 changes: 5 additions & 0 deletions lib/terminus-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type ErrorLogStyle = 'pretty' | 'json';

export interface TerminusModuleOptions {
errorLogStyle: ErrorLogStyle;
}
Loading

0 comments on commit 818504e

Please sign in to comment.