n-event-logger
log all your API service calls and function calls with a single line of code
quickstart
import { withLogger, withServiceLogger, eventLogger } from '@financial-times/n-event-logger';
// enhancer function names might be changed to the following after alpha
import { autoLog, autoLogAll, eventLogger } from '@financial-times/n-event-logger';
auto log different status of a function call (commonly an action under an operation)
const data = await withLogger(yourCallFunction)(params, meta);
const result = withLogger(someOtherFunction)(params, meta);
enhance all methods in api service module ensure that it would be logged wherever used
/*-- some-api-service --*/
export default withServiceLogger{ CallA, CallB };
/*-- some-controller-or-middleware --*/
import APIService from 'some-api-service';
await APIService.CallA(params, meta);
await APIService.CallB(params, meta);
more strcutured operation/action log
const meta = { transactionId, userId, operation };
const event = eventLogger(meta);
try {
event.action('someAction').success();
event.success();
} catch(e) {
event.failure(e);
}
set key names of fields to be muted in .env to reduce log for development
LOGGER_MUTE_FIELDS=transactionId, userId
before/after
// before
try {
logger.info(meta, { action: 'yourCallFunction' }, params);
const data = await yourCallFunction(params, metaSubset);
logger.info(meta, { action: 'yourCallFunction' }, params, { result: 'success' });
} catch(e) {
// some error handling and parsing...
logger.info(meta, { action: 'yourCallFunction' }, params, { result: 'failure' }, parsedError);
}
// after: 7+ lines (depends on error parsing) => 1 line
const data = await withLogger(yourCallFunction)(params, meta);
const meta = { transactionId, userId, operation };
// before
logger.info(meta);
try {
logger.info(meta, { action: 'someAction', result: 'success' });
logger.info(meta, { result: 'success' });
} catch(e) {
// some error hanldling and parsing
logger.error(meta, { result: 'failure' }, parsedError);
}
// after: some less key strokes
const event = eventLogger(meta);
try {
event.action('someAction').success();
event.success();
} catch(e) {
event.failure(e);
}
install
npm install @financial-times/n-event-logger
gotcha
function args format standard
One opinionated pre-requisite is to have the callFunction input format as (params, meta) so that the callFunction can be invoked correctly with extra meta for logger
exception/error
out-of-box parse support for the following standard types of errors
- Fetch Response Error
- Fetch (Network) Error
- Node Native Error Objects
if your custom Error types are extended from native Error class, the logger might not be able to pick up custom information fields well, just use an object not constructed by Error
usage
enhance a single API service call
/*-- api-service.js --*/
import { withLogger } from '@financial-times/n-event-logger';
export const callSomeAPIService = (params, meta) => {
const options = {
headers: {
/* some meta data specific headers... */
}
};
/* maybe some code before fetch... */
return fetch(url, options)
.then(/* some code for response... */)
.catch(/* some error hanlding... */);
}
// this would record the name of the function 'callSomeAPIService' as action in the logger automatically
export const enhancedCallSomeAPIService = (params, meta) => withLogger(callSomeAPIService)(params, meta);
/*
currently async/await is needed for the logger to work correctly, update coming soon
*/
/*-- middleware/controller.js --*/
import { enhancedCallSomeAPIService } from '../api-service';
const someOperationFunction = async (req, res, next) => {
const meta = {
/* some fields for headers */
transactionId: req.transactionId,
/* extra fields for loggers */
operation: 'someOperation',
userId: req.userId,
};
try{
await enhancedCallSomeAPIService(params, meta);
} catch (e) {
next(e);
}
}
info: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, action: callSomeAPIService
info: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, action: callSomeAPIService, status: success
depends on the error status code, it would log as warn for 4XX, and error for 5XX
warn: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, action: callSomeAPIService, status: failure, message: some error message
error: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, action: callSomeAPIService, status: failure, message: some error message
enhance a whole series of API service call
/*-- api-service.js --*/
import { withServiceLogger } from '@financial-times/n-event-logger';
export const apiServiceCallA = (params, meta) => {}
export const apiServiceCallB = (params, meta) => {}
// helper to enhance all API service call functions as object methods
export default withServiceLogger{
apiServiceCallA,
apiServiceCallB
};
/*-- middleware/controller.js --*/
import SomeAPIService from '../some-api-service';
const someOperationFunction = async (req, res, next) => {
const meta = {
transactionId: req.transactionId,
userId: req.userId,
operation: 'someOperation',
};
try{
await someAPIService.apiServiceCallA(params, meta);
await someAPIService.apiServiceCallB(params, meta);
} catch (e) {
next(e);
}
}
log your operation in structure with loggerEvent
/*-- middleware/controller.js --*/
import { loggerEvent } from '@financial-times/n-event-logger';
import SomeAPIService from '../some-api-service';
const someOperationFunction = async (req, res, next) => {
const meta = {
transactionId: req.transactionId,
userId: req.userId,
operation: 'someOperation',
};
const event = loggerEvent(meta);
try{
const a = await someAPIService.apiServiceCallA(params, meta);
const b = await someAPIService.apiServiceCallB(params, meta);
/* some other code... */
const c = someFunction(a, b);
event.success({ c });
/* some other code... */
} catch (e) {
event.failure(e);
next(e);
}
}
info: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation
... some more process info log from various actions
info: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, status: success, data: { "c": 'values are stringified JSON' }
info: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation
... some more process info log from various actions
error: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, action: apiServiceCallB, result: failure, message: "some message"
error: transactionId: xxxx-xxx, userId: xxxx-xxx, operatoin: someOperation, status: failure, message: "error message"
logs help you track down exactly which function call leads to the operation failure with what params in call in what context
auto log non-api-service function with withLogger enhancer
import { loggerEvent, withLogger } from '@financial-times/n-event-logger';
const someFunction = (a, b) => {
try {
/* some code... */
return c;
} catch(e) {
/* some error handling... */
// function needs to throw error or return Promise.reject() for failure status to be logged
throw e;
}
}
const someOperationFunction = async (req, res, next) => {
const meta = {
transactionId: req.transactionId,
userId: req.userId,
operation: 'someOperation',
};
const event = loggerEvent(meta);
try{
const a = await someAPIService.apiServiceCallA(params, meta);
const b = await someAPIService.apiServiceCallB(params, meta);
/* some other code... */
// await is needed for the result status logger to work correctly
const c = await withLogger(meta)(someFunction)(a, b);
event.success({ c });
/* some other code... */
} catch (e) {
event.failure(e);
next(e);
}
}
track some non-api-service function on the fly
/*-- middleware/controller.js --*/
const someOperationFunction = async (req, res, next) => {
const meta = {
transactionId: req.transactionId,
userId: req.userId,
operation: 'someOperation',
};
const event = loggerEvent(meta);
try{
const a = await someAPIService.apiServiceCallA(params, meta);
const b = await someAPIService.apiServiceCallB(params, meta);
/* some other code... */
if(someCondition && someCheck){
event.action('someAction').success();
} else {
event.action('someAction').failure();
throw ;
}
/* some other code... */
event.success();
/* some other code... */
} catch (e) {
event.failure(e);
next(e);
}
}
test stub
import * as nEventLogger from '@financial-times/n-event-logger';
//example using sinon sandbox, will look into provide testStub as a module based on sinon/jest
const stubLoggerEvent = meta => ({
// add more stubs to methods if you want
start: () => null,
success: () => null,
failure: () => null,
action: () => stubLoggerEvent(meta)
});
sandbox.stub(nEventLogger, 'loggerEvent').callsFake(stubLoggerEvent);
sandbox.stub(nEventLogger, 'withLogger').callsFake(
callFunction => (params, meta) => callFunction(params, meta)
);
sandbox.stub(nEventLogger, 'withServiceLogger').callsFake(service => service);
development
make install
-
yarn test --watch
to automatically run test on changing src -
yarn watch
to automatically correct code format on saving src
todos
- middleware/controller one-line enhancer
- consider integrating
.addContext()
fromn-logger