我已经使用winston,morgan和winston-daily-rotate-file,express-http-context实现了节点js的记录器。因此,每天,当任何http request(morgan)或开发人员定义的记录器消息写入该文件时,都会写入一个新的日志文件。从节点js。下面的格式将如何记录
时间戳||级别||文件名|| traceId || statusCode || logMessage
一切都很好,但是当我使用笑话(从初学到笑话)编写测试用例时。我无法覆盖两行。这是完整的代码和文件夹结构。
对于每个请求,我都设置一个traceId,该ID将在customFormat方法上提取,然后我返回customFormat消息,这是我无法讲的两行。
//index.js
const app = require('express')();
const cors = require('cors')
const morgan = require('morgan') // HTTP request logger middleware
const logger = require('./lib/logger')(module) //Logger
const uuid = require('uuid')
const httpContext = require('express-http-context')
const config = require('./config').get(process.env.APP_ENV)
// Use any third party middleware that does not need access to the context here
// app.use(some3rdParty.middleware);
app.use(httpContext.middleware);
// all code from here on has access to the same context for each request
// Run the context for each request.
// Assigning a unique identifier to each request
app.use((req, res, next) => {
httpContext.set('traceId', uuid.v1());
next();
});
// using morgan with winston(logger)
app.use(morgan('combined', {
stream: {
write: (message) => logger[config.logLevel](message)
}
}))
app.use(cors());
app.listen(4000, () => {
console.log('Server running on port 4000');
});
// lib/logger/index.js
const appRoot = require('app-root-path');
const {
createLogger,
format,
transports
} = require('winston');
require('winston-daily-rotate-file');
const {
combine,
label,
} = format;
const config = require('../../config').get(process.env.APP_ENV);
const loggerHelper = require('./helpers')
// Custom settings for each transport
const options = {
dailyRotateFile: {
filename: `${appRoot}/logs/TPS-UI-%DATE%.log`,
datePattern: 'YYYY-MM-DD',
prepend: true,
level: config.logLevel,
timestamp: new Date(),
localTime: true,
}
}
// Instantiate a Winston Logger with the settings
const logger = moduleObj => createLogger({
format: combine(
label({
label: loggerHelper.getFileName(moduleObj)
}),
format.timestamp(),
loggerHelper.customFormat()
),
transports: [
new transports.DailyRotateFile(options.dailyRotateFile),
],
exitOnError: false, // do not exit on handled exceptions
});
module.exports = logger
// lib/logger/helpers/index.js
const loggerHelper = require('../../../helper');
const httpContext = require('express-http-context');
const {
format: {
printf
}
} = require('winston')
/**
* @method checkMessageProp
* @param {message} can be object if developer defined, else it will be string
* if its a network request (morgan requests)
* @returns a fixed format how the status code and message should show
*/
const returnLogMessage = (message) => {
const {
statusCode,
logMsg,
maskedData
} = message;
switch (typeof message) {
case 'object':
let statusCodeToBeLogged = statusCode ? statusCode : "Status code not defined",
logMessageToBeLogged = logMsg ? logMsg : "Log message not defined",
return `${statusCodeToBeLogged} || ${logMessageToBeLogged}`
case 'string':
if (message) {
const messageSplit = message.split('"');
let statusCodeToBeLogged = messageSplit[2].trim().split(" ")[0],
logMessageToBeLogged = messageSplit[1]
return `${statusCodeToBeLogged} || ${logMessageToBeLogged}`;
}
return 'Status Code Not Defined || Log Message Not Defined';
default:
return message;
}
};
/**
* @method getFileName
* @param {moduleObj} the module realted object passed from the require of logger file
* @returns the file name where the logger was invoked
*/
const getFileName = (moduleObj) => {
if (Object.keys(moduleObj).length > 0) {
const tempFileNameParts = moduleObj.filename.split("/");
const fileName = tempFileNameParts.slice(Math.max(tempFileNameParts.length - 2, 1)).join('/');
return fileName;
}
return 'Module not passed while requiring the logger';
};
/**
* @method customFormat
* @param {log} the log passed by the developer or based on network requests
* @returns a customFormat how it should be logged to the log files
*/
const customFormat = () => {
return printf((log) => {
const traceId = httpContext.get('traceId');
return `${new Date(log.timestamp)} || ${log.level.toUpperCase()} || ${log.label} || ${traceId} || ${returnLogMessage(log.message)} `;
})
}
module.exports = {
returnLogMessage,
getFileName,
customFormat
}
// lib/logger/__test__/logger.test.js
jest.mock('winston');
const logger = require('..');
const winston = require('winston');
describe('Given the logger method is called', () => {
let loggerObject;
const mockModuleObject = {
filename: 'server/index.js'
};
beforeEach(() => {
loggerObject = logger(mockModuleObject);
});
test('it should return a object returned by createLogger', () => {
expect(loggerObject).toEqual(winston.mockLoggerObject);
});
test('it should call combine, format, printf and timestamp method of winston', () => {
expect(winston.mockPrintf).toHaveBeenCalled();
expect(winston.mockLabel).toHaveBeenCalled();
expect(winston.mockCombine).toHaveBeenCalled();
});
test('expect Dailytransports to be called', () => {
// how to check the daily transport has been called
expect(winston.mockDailyTransport).toHaveBeenCalled();
});
});
// lib/logger/__test__/helpers/helper.test.js
jest.mock('winston')
jest.mock('express-http-context')
const helper = require('../../helpers/')
const httpContext = require('express-http-context')
const {
format: {
printf
}
} = jest.requireActual('winston')
describe('Given the helper methods for logger should call a d take different scenarios', () => {
let mockMessageObj, mockMessageString, mockMessageStringEmpty, mockMessageNumber;
beforeAll(() => {
mockMessageObj = {
statusCode: 200,
logMsg: "Testing log"
}
mockMessageString = `::1 - - [31/Jan/2019:11:26:54 +0000]
"GET /graphql HTTP/1.1" 404 146 "-" "Mozilla/5.0 (X11; Linux x86_64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"`
mockMessageStringEmpty = ""
mockMessageNumber = 12345
})
test('returnLogMessage and getFileName methods should exist', () => {
expect(helper.returnLogMessage).toBeDefined()
expect(helper.getFileName).toBeDefined()
})
// returnLogMessage Method Test Cases
test('should return a string when passes object', () => {
expect(helper.returnLogMessage(mockMessageObj)).toEqual('200 || Testing log || userId : tne:1***:***2354')
})
test('should return a string when passes string', () => {
expect(helper.returnLogMessage(mockMessageString)).toEqual('404 || GET /graphql HTTP/1.1')
})
test('should return default string when passes string as undefined', () => {
expect(helper.returnLogMessage(mockMessageStringEmpty)).toEqual('Status Code Not Defined || Log Message Not Defined || Mask Data Not Defined')
})
test('should return the actual default message if the type is nor object neither string', () => {
expect(helper.returnLogMessage(mockMessageNumber)).toEqual(12345)
})
test('should return default message for status code', () => {
let tempMockMessageObjStatusCode = { ...mockMessageObj
}
tempMockMessageObjStatusCode["statusCode"] = ""
expect(helper.returnLogMessage(tempMockMessageObjStatusCode)).toEqual('Status code not defined || Testing log || userId : tne:1***:***2354')
})
test('should return default message for log msg', () => {
let tempMockMessageObjLogMsg = { ...mockMessageObj
}
tempMockMessageObjLogMsg["logMsg"] = ""
expect(helper.returnLogMessage(tempMockMessageObjLogMsg)).toEqual('200 || Log message not defined || userId : tne:1***:***2354')
})
test('should return default message for masked data for undefined', () => {
let tempMockMessageObjMaskData = { ...mockMessageObj
}
tempMockMessageObjMaskData["maskedData"] = ""
expect(helper.returnLogMessage(tempMockMessageObjMaskData)).toEqual('200 || Testing log || Masked data not defined')
})
test('should return default message for masked data for empty object', () => {
let tempMockMessageObjMaskData = { ...mockMessageObj
}
tempMockMessageObjMaskData["maskedData"] = {}
expect(helper.returnLogMessage(tempMockMessageObjMaskData)).toEqual('200 || Testing log || Masked data not defined')
})
// getFileName Method Test Cases
test('should return default label when module is not passed', () => {
expect(helper.getFileName({})).toEqual('Module not passed while requiring the logger')
})
})
// this one how can i test the custom format method
describe('custom format', () => {
test('should call the printF function inside customFormat function', () => {
})
})
// __mocks/winston.js
const winston = jest.genMockFromModule('winston');
const mockLoggerObject = {
error: jest.fn(),
info: jest.fn(),
};
const mockLabel = jest.fn();
const mocktimestamp = jest.fn();
const mockPrintf = jest.fn();
const mockCombine = jest.fn();
const mockDailyTransport = jest.fn();
const mockTransports = {
DailyRotateFile: mockDailyTransport
};
const mockCreateLogger = jest.fn().mockReturnValue(mockLoggerObject);
const mockFormat = {
label: mockLabel,
timestamp: mocktimestamp,
printf: mockPrintf,
combine: mockCombine,
};
winston.createLogger = mockCreateLogger;
winston.transports = mockTransports;
winston.mockLoggerObject = mockLoggerObject;
winston.format = mockFormat;
winston.mockLabel = mockLabel;
winston.mocktimestamp = mocktimestamp;
winston.mockPrintf = mockPrintf;
winston.mockDailyTransport = mockDailyTransport;
winston.mockCombine = mockCombine;
module.exports = winston;
//__mocks/express-http-context
const httpContext = jest.genMockFromModule('express-http-context');
const mockGet = jest.fn();
httpContext.get = mockGet;
module.exports = httpContext;
我无法检查的两个测试用例或抛出错误的一个是
// helper.test.js
describe('custom format', () => {
test('should call the printF function inside customFormat function', () => {
// how can i coverage the line what should be written here
})
})
// logger.test.js
test('expect Dailytransports to be called', () => {
expect(winston.mockDailyTransport).toHaveBeenCalled();
});