diff --git a/package-lock.json b/package-lock.json index f4c80e2..542c164 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,6 @@ { "name": "node-serverless-helpers", + "version": "3.0.3", "version": "3.0.4", "lockfileVersion": 1, "requires": true, diff --git a/readme.md b/readme.md index d133391..8fcdc24 100644 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/src/config.ts b/src/config.ts index f2579b7..ad31d6b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,5 @@ import { all as merge } from 'deepmerge'; +import {log} from './debug'; export interface ApiConfigCorsOptions { origin: string; @@ -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; diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000..c34e73e --- /dev/null +++ b/src/debug.ts @@ -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); + } + } +}; diff --git a/src/handling/api/api.ts b/src/handling/api/api.ts index 45aa5fc..78acff3 100644 --- a/src/handling/api/api.ts +++ b/src/handling/api/api.ts @@ -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; } @@ -37,6 +41,7 @@ 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); }; @@ -44,8 +49,8 @@ const singleHeaders = (event: ApiHandlerEvent, headers: OutgoingHttpHeaders): Si 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; } @@ -72,9 +77,12 @@ 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, @@ -82,7 +90,6 @@ const format = (event: ApiHandlerEvent, response: Response, content: any): APIGa body: '', }; } - return { headers, multiValueHeaders, @@ -94,19 +101,21 @@ 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'); } @@ -114,19 +123,26 @@ const formatError = (event: APIGatewayProxyEvent, response: Response, err: any): export const apiHandler = (next: ApiHandler): APIGatewayProxyHandler => { return async (event: APIGatewayEvent, context: Context): Promise => { + 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('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('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('ApiGateway', [event, err, result]); - return result; } }; diff --git a/src/handling/index.ts b/src/handling/index.ts index 2a544a9..e8600de 100644 --- a/src/handling/index.ts +++ b/src/handling/index.ts @@ -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; let callInit = true; @@ -13,14 +14,17 @@ const isApi = (event: any): false | ((next: ApiHandler) => APIGatewayProxyHandle export type DefaultHandler = (event: any, context: any) => Promise; 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 => { 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) { @@ -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(); }; }; diff --git a/src/handling/middleware.ts b/src/handling/middleware.ts index c0fa313..0498238 100644 --- a/src/handling/middleware.ts +++ b/src/handling/middleware.ts @@ -1,4 +1,5 @@ import { ApiAfterMiddleware, ApiBeforeMiddleware, ApiErrorHandler } from './api'; +import {log} from '../debug'; export type DefaultBeforeMiddleware = (event: any, context: any) => Promise; export type BeforeMiddleware = ApiBeforeMiddleware | DefaultAfterMiddleware; @@ -30,10 +31,11 @@ export const callBeforeMiddleware = async any>( type: HandlingType, args: Parameters, ): Promise => { + 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); } @@ -43,10 +45,11 @@ export const callAfterMiddleware = async any>( type: HandlingType, args: Parameters, ): Promise => { + 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); } @@ -56,10 +59,11 @@ export const callErrorHandlers = async any>( type: HandlingType, args: Parameters, ): Promise => { + 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); } @@ -68,6 +72,10 @@ export const callErrorHandlers = async 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 { @@ -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 { @@ -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 { diff --git a/src/init.ts b/src/init.ts index 1d9d812..cfc2278 100644 --- a/src/init.ts +++ b/src/init.ts @@ -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); };