Skip to content

Commit

Permalink
feat(debug): add logs in verbose mode
Browse files Browse the repository at this point in the history
  • Loading branch information
MarioArnt authored Feb 3, 2020
1 parent 7c0cc1b commit f48736b
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 14 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ The important part is the `handle` function that does 3 things.
event comes from, and add useful middlewares to it.
3. Run your function, and wrap the result to the expected format.

## Debug

To print useful debug logs `export NODE_SLS_HELPERS_DEBUG=*`.

## TODO

- Global logging system
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { all as merge } from 'deepmerge';
import {log} from './debug';

export interface ApiConfigCorsOptions {
origin: string;
Expand Down Expand Up @@ -26,7 +27,9 @@ let internalConfig: ConfigOptions = {
};

export const config = (options: ConfigOptions): void => {
log.debug('[CONFIG] Updating config with', options);
internalConfig = merge([internalConfig, options]) as ConfigOptions;
log.debug('[CONFIG] Used config', internalConfig);
};

export const getConfig = (): ConfigOptions => internalConfig;
7 changes: 7 additions & 0 deletions src/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const log = {
debug: (msg: any, ...args: any[]) => {
if (process.env.NODE_SLS_HELPERS_DEBUG) {
console.debug('[SLS_HELPERS]', msg, ...args);
}
}
};
34 changes: 25 additions & 9 deletions src/handling/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ import {
Response,
SingleValueHeaders,
} from './types';
import {log} from '../../debug';


const normalize = (event: APIGatewayProxyEvent): ApiHandlerEvent => {
log.debug('[API] Normalizing event');
const clonedEvent = Object.assign(event);
if (event.body) {
try {
log.debug('[API] Parsing request body from JSON');
clonedEvent.body = JSON.parse(clonedEvent.body);
} catch (e) {
console.error(e);
const error = new Error('Bad Request');
log.debug('[API] ERROR: Only JSON Accepted');
const error = new Error('Only JSON payloads are accepted');
error.name = 'BadRequestError';
throw error;
}
Expand All @@ -37,15 +41,16 @@ const normalize = (event: APIGatewayProxyEvent): ApiHandlerEvent => {
};

const httpMethodToStatus = (method: string, statusCode?: number): number => {
log.debug('[API] Setting status code for method', method, statusCode || (method === 'POST' ? 201 : 200));
return statusCode || (method === 'POST' ? 201 : 200);
};

const singleHeaders = (event: ApiHandlerEvent, headers: OutgoingHttpHeaders): SingleValueHeaders => {
const finalHeaders = Object.keys(headers)
.filter((k: string) => ['boolean', 'string', 'number'].indexOf(typeof headers[k]) > -1)
.reduce((p: SingleValueHeaders, k: string) => Object.assign(p, {[k]: headers[k]}), {});

const cors = getConfig().api.cors as ApiConfigCorsOptions;
log.debug('[API] Reading CORS config', cors);
if (cors) {
finalHeaders['Access-Control-Allow-Origin'] = cors.origin || event.headers.origin;
}
Expand All @@ -72,17 +77,19 @@ const multipleHeaders = (event: ApiHandlerEvent, headers: OutgoingHttpHeaders):
};

const format = (event: ApiHandlerEvent, response: Response, content: any): APIGatewayProxyResult => {
log.debug('[API] Formatting response');
log.debug('[API] Setting headers');
const headers = singleHeaders(event, response.headers);
const multiValueHeaders = multipleHeaders(event, response.headers);
if (content === null || content === undefined || content === '') {
log.debug('[API] No content returning 204');
return {
headers,
multiValueHeaders,
statusCode: event.httpMethod === 'POST' ? 201 : 204,
body: '',
};
}

return {
headers,
multiValueHeaders,
Expand All @@ -94,39 +101,48 @@ const format = (event: ApiHandlerEvent, response: Response, content: any): APIGa
};

const formatError = (event: APIGatewayProxyEvent, response: Response, err: any): APIGatewayProxyResult => {
log.debug('[API] Error name', err.name);
switch (err.name) {
case 'ValidationError':
console.info(err);
log.debug('[API] 422 - Validation error');
response.statusCode = 422;
return format(event, response, {data: err.details});
case 'BadRequestError':
log.debug('[API] 400 - Bad request');
response.statusCode = 400;
return format(event, response, err.details ? {data: err.details} : 'Bad Request');
case 'ForbiddenError':
response.statusCode = 403;
return format(event, response, err.details ? {data: err.details} : 'Forbidden');
default:
console.error(err);
log.debug('[API] 500 - Generic error');
response.statusCode = err.statusCode || 500;
return format(event, response, err.body || 'Internal Server Error');
}
};

export const apiHandler = (next: ApiHandler): APIGatewayProxyHandler => {
return async (event: APIGatewayEvent, context: Context): Promise<APIGatewayProxyResult> => {
log.debug('[API] Initializing response');
const response = new Response();
try {
log.debug('[API] Normalizing event', event);
const normalizedEvent = await normalize(event);
log.debug('[API] Normalized event', normalizedEvent);
log.debug('[API] Calling before middleware');
await callBeforeMiddleware<ApiBeforeMiddleware>('ApiGateway', [normalizedEvent, context]);

log.debug('[API] Run business logic code');
const result = format(normalizedEvent, response, await next(normalizedEvent, response, context));
log.debug('[API] Formatted result', result);
log.debug('[API] Calling after middleware');
await callAfterMiddleware<ApiAfterMiddleware>('ApiGateway', [normalizedEvent, result]);

return result;
} catch (err) {
log.debug('[API] Error happened !', err);
const result = formatError(event, response, err);
log.debug('[API] Formatted', result);
log.debug('[API] Calling error middleware');
await callErrorHandlers<ApiErrorHandler>('ApiGateway', [event, err, result]);

return result;
}
};
Expand Down
9 changes: 7 additions & 2 deletions src/handling/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { APIGatewayProxyHandler, Callback, Context, Handler } from 'aws-lambda';

import { runInitializers } from '../init';
import { apiHandler, ApiHandler } from './api';
import {log} from '../debug';

let initPromise: Promise<any>;
let callInit = true;
Expand All @@ -13,14 +14,17 @@ const isApi = (event: any): false | ((next: ApiHandler) => APIGatewayProxyHandle
export type DefaultHandler = (event: any, context: any) => Promise<any>;

export const handle = (next: ApiHandler | DefaultHandler, shouldThrowOnUnhandled = true): Handler => {
log.debug('[HANDLE] Handling event with function', next.name);
if (callInit) {
log.debug('[HANDLE] Calling initializers');
callInit = false;
initPromise = runInitializers();
}

return async (event: any, context: Context, callback: Callback): Promise<any> => {
await initPromise;

log.debug('[HANDLE] Initializers ran successfully');
log.debug('[HANDLE] Is API Gateway handler', event.pathParameters !== undefined);
for (const check of [isApi]) {
const result = check(event);
if (result) {
Expand All @@ -29,9 +33,10 @@ export const handle = (next: ApiHandler | DefaultHandler, shouldThrowOnUnhandled
}

if (!shouldThrowOnUnhandled) {
log.debug('[HANDLE] Using default handler');
return (next as DefaultHandler)(event, context);
}

log.debug('[HANDLE] Unhandled event !');
throwUnhandledEvent();
};
};
Expand Down
22 changes: 19 additions & 3 deletions src/handling/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiAfterMiddleware, ApiBeforeMiddleware, ApiErrorHandler } from './api';
import {log} from '../debug';

export type DefaultBeforeMiddleware = (event: any, context: any) => Promise<void>;
export type BeforeMiddleware = ApiBeforeMiddleware | DefaultAfterMiddleware;
Expand Down Expand Up @@ -30,10 +31,11 @@ export const callBeforeMiddleware = async <T extends (...args: any[]) => any>(
type: HandlingType,
args: Parameters<T>,
): Promise<void> => {
log.debug('[MIDDLEWARE][BEFORE] Running generic middleware', middlewareList.__ALWAYS__.before.map(f => f.name));
for (const middleware of middlewareList.__ALWAYS__.before) {
await middleware.apply({}, args);
}

log.debug('[MIDDLEWARE][BEFORE] Running API gateway middleware', middlewareList.ApiGateway.before.map(f => f.name));
for (const middleware of middlewareList.ApiGateway.before) {
await middleware.apply({}, args);
}
Expand All @@ -43,10 +45,11 @@ export const callAfterMiddleware = async <T extends (...args: any[]) => any>(
type: HandlingType,
args: Parameters<T>,
): Promise<void> => {
log.debug('[MIDDLEWARE][AFTER] Running generic middleware', middlewareList.__ALWAYS__.after.map(f => f.name));
for (const middleware of middlewareList.ApiGateway.after) {
await middleware.apply({}, args);
}

log.debug('[MIDDLEWARE][AFTER] Running API gateway middleware', middlewareList.ApiGateway.after.map(f => f.name));
for (const middleware of middlewareList.__ALWAYS__.after) {
await middleware.apply({}, args);
}
Expand All @@ -56,10 +59,11 @@ export const callErrorHandlers = async <T extends (...args: any[]) => any>(
type: HandlingType,
args: Parameters<T>,
): Promise<void> => {
log.debug('[MIDDLEWARE][ERROR] Running generic middleware', middlewareList.__ALWAYS__.errors.map(f => f.name));
for (const middleware of middlewareList.ApiGateway.errors) {
await middleware.apply({}, args);
}

log.debug('[MIDDLEWARE][ERROR] Running API gateway middleware', middlewareList.ApiGateway.errors.map(f => f.name));
for (const middleware of middlewareList.__ALWAYS__.errors) {
await middleware.apply({}, args);
}
Expand All @@ -68,6 +72,10 @@ export const callErrorHandlers = async <T extends (...args: any[]) => any>(
export function before(type: 'ApiGateway', middleware: ApiBeforeMiddleware[]): void;
export function before(middleware: DefaultBeforeMiddleware[]): void;
export function before(typeOrMiddleware: HandlingType | BeforeMiddleware[], middleware: BeforeMiddleware[] = []): void {
log.debug('[MIDDLEWARE][BEFORE] Registering middleware', {
type: Array.isArray(typeOrMiddleware) ? '__ALWAYS__' : typeOrMiddleware,
middleware: Array.isArray(typeOrMiddleware) ? typeOrMiddleware.map(f => f.name) : middleware.map(f => f.name),
});
if (!isSendingType(typeOrMiddleware)) {
middlewareList['__ALWAYS__'].before = typeOrMiddleware;
} else {
Expand All @@ -78,6 +86,10 @@ export function before(typeOrMiddleware: HandlingType | BeforeMiddleware[], midd
export function after(type: 'ApiGateway', middleware: ApiAfterMiddleware[]): void;
export function after(middleware: DefaultAfterMiddleware[]): void;
export function after(typeOrMiddleware: HandlingType | AfterMiddleware[], middleware: AfterMiddleware[] = []): void {
log.debug('[MIDDLEWARE][AFTER] Registering middleware', {
type: Array.isArray(typeOrMiddleware) ? '__ALWAYS__' : typeOrMiddleware,
middleware: Array.isArray(typeOrMiddleware) ? typeOrMiddleware.map(f => f.name) : middleware.map(f => f.name),
});
if (!isSendingType(typeOrMiddleware)) {
middlewareList['__ALWAYS__'].after = typeOrMiddleware;
} else {
Expand All @@ -88,6 +100,10 @@ export function after(typeOrMiddleware: HandlingType | AfterMiddleware[], middle
export function handleError(type: 'ApiGateway', middleware: ApiErrorHandler[]): void;
export function handleError(middleware: DefaultErrorHandler[]): void;
export function handleError(typeOrMiddleware: HandlingType | ErrorHandler[], middleware: ErrorHandler[] = []): void {
log.debug('[MIDDLEWARE][ERROR] Registering middleware', {
type: Array.isArray(typeOrMiddleware) ? '__ALWAYS__' : typeOrMiddleware,
middleware: Array.isArray(typeOrMiddleware) ? typeOrMiddleware.map(f => f.name) : middleware.map(f => f.name),
});
if (!isSendingType(typeOrMiddleware)) {
middlewareList['__ALWAYS__'].errors = typeOrMiddleware;
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/init.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {log} from './debug';

const globalInitializers: Function[] = [];

export const runInitializers = async () => {
log.debug(`[INIT] Running ${globalInitializers.length} initializer`);
return Promise.all(globalInitializers.map(initializer => initializer()));
};

export const init = (initializer: Function) => {
log.debug('[INIT] Adding initializer', initializer.name);
globalInitializers.push(initializer);
};

0 comments on commit f48736b

Please sign in to comment.