UNPKG

@infotorg/winston-log-formats

Version:

Infotorg log formats for Winston logger

602 lines (550 loc) 20.3 kB
const { AxiosError } = require('axios'); const axiosLogFormat = require('../src/axios'); describe('Tests "axios" Log format', () => { // Sat Feb 18 2023 16:49:18 GMT+0100 (Central European Standard Time) const requestStartedAt = 1676735337786; const xTrackId = 'your-track-id'; test('it should be a function', () => { expect(typeof axiosLogFormat === 'function').toBe(true); }); test('it should return Format wrapper object', () => { expect(typeof axiosLogFormat() === 'object').toBe(true); }); test('it should NOT add axios meta information for non axios request/response/error info object', () => { const info = axiosLogFormat().transform({ message: 'Not axios request/response' }); expect(info).toStrictEqual({ message: 'Not axios request/response' }); }); describe('For Request', () => { const axiosRequestConfig = Object.freeze({ url: '/', method: 'post', data: { product: 'someProduct', data: { name: 'John', surname: 'Doe' }, }, timeout: 0, headers: { common: { Accept: 'application/json, text/plain, */*' }, 'X-Session': '1621448702.23937#Y8dbR/s1Wi3Wep830IUp8IoZDzz', 'Content-type': 'application/json', delete: {}, get: {}, head: {}, post: { 'Content-Type': 'application/x-www-form-urlencoded' }, put: { 'Content-Type': 'application/x-www-form-urlencoded' }, patch: { 'Content-Type': 'application/x-www-form-urlencoded' }, }, baseURL: 'https://example.com', isAxiosRequest: true, // This is a custom properties which added by axios request interceptor requestStartedAt, xTrackId: 'your-track-id', }); const expectedMeta = { req: { baseURL: 'https://example.com', data: { data: { surname: 'Doe', name: 'John' }, product: 'someProduct' }, method: 'post', requestStartedAt, xTrackId, timeout: 0, url: '/', headers: { 'Content-type': 'application/json', 'X-Session': '1621448702.23937#Y8dbR/s1Wi3Wep830IUp8IoZDzz', common: { Accept: 'application/json, text/plain, */*' }, delete: {}, get: {}, head: {}, patch: { 'Content-Type': 'application/x-www-form-urlencoded' }, post: { 'Content-Type': 'application/x-www-form-urlencoded' }, put: { 'Content-Type': 'application/x-www-form-urlencoded' }, }, }, }; const level = 'debug'; const message = 'POST https://example.com/'; const description = 'Axios request'; test('it should NOT add axios information when the format is disabled in opts', () => { const info = axiosLogFormat().transform({ level, message: axiosRequestConfig }, { enabled: false }); expect(info.level).toBe(level); expect(info.message).toStrictEqual(axiosRequestConfig); expect(info.meta).toBeUndefined(); }); test('it should NOT add axios request meta information if "meta" is disabled in opts', () => { const info = axiosLogFormat().transform({ level, message: axiosRequestConfig }, { meta: false }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({}); }); test('it should add axios request meta information in the info object', () => { const info = axiosLogFormat().transform({ level, message: axiosRequestConfig }, { meta: true }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info.meta).toStrictEqual(expectedMeta); }); test('it should add axios request meta information without headers in the info object', () => { const info = axiosLogFormat().transform({ level, message: axiosRequestConfig }, { headers: false, meta: true }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.meta).toStrictEqual({ req: { baseURL: 'https://example.com', data: { data: { surname: 'Doe', name: 'John' }, product: 'someProduct' }, method: 'post', requestStartedAt, xTrackId, timeout: 0, url: '/', }, }); }); test('it should add axios request meta information in the info object at the custom "axios" meta key', () => { const metaKey = 'axios'; const info = axiosLogFormat().transform({ level, message: axiosRequestConfig }, { meta: true, metaKey }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info[metaKey]).toStrictEqual(expectedMeta); }); test('it should add axios request meta information in the info object with a custom request description', () => { const requestDescription = 'HTTP client request'; const info = axiosLogFormat().transform( { level, message: axiosRequestConfig }, { meta: true, requestDescription } ); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(requestDescription); expect(info.meta).toStrictEqual(expectedMeta); }); }); describe('For Response', () => { let res; const level = 'debug'; const message = 'POST https://example.com/ 200 OK 100ms'; const description = 'Axios response'; beforeEach(() => { res = Object.freeze({ status: 200, statusText: 'OK', headers: { 'content-length': '3513', 'content-type': 'application/json', date: 'Mon Feb 06 2023 13:35:00 GMT', connection: 'close', }, config: { url: '/', method: 'post', data: '{"product":"someProduct","data":{"name":"John","surname":"Doe"}}', headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'X-Session': '1621448702.23937#Y8dbR/s1Wi3Wep830IUp8IoZDzz', 'User-Agent': 'axios/0.21.1', 'Content-Length': 76, }, baseURL: 'https://example.com', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', requestStartedAt, }, responseTime: 100, // This property is added by axios response interceptor isAxiosResponse: true, }); }); test('it should add axios response meta information in the info object', () => { const info = axiosLogFormat().transform({ level, message: res }, { meta: true, headers: true }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({ req: { baseURL: 'https://example.com', data: '{"product":"someProduct","data":{"name":"John","surname":"Doe"}}', headers: { Accept: 'application/json, text/plain, */*', 'Content-Length': 76, 'Content-Type': 'application/json', 'User-Agent': 'axios/0.21.1', 'X-Session': '1621448702.23937#Y8dbR/s1Wi3Wep830IUp8IoZDzz', }, method: 'post', requestStartedAt, url: '/', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }, res: { headers: { connection: 'close', 'content-length': '3513', 'content-type': 'application/json', date: 'Mon Feb 06 2023 13:35:00 GMT', }, responseTime: 100, status: 200, statusText: 'OK', }, }); }); test('it should add axios response meta information in the info object at the custom "axios" meta key', () => { const metaKey = 'axios'; const info = axiosLogFormat().transform({ level, message: res }, { meta: true, metaKey, headers: true }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info[metaKey]).toStrictEqual({ req: { baseURL: 'https://example.com', data: '{"product":"someProduct","data":{"name":"John","surname":"Doe"}}', headers: { Accept: 'application/json, text/plain, */*', 'Content-Length': 76, 'Content-Type': 'application/json', 'User-Agent': 'axios/0.21.1', 'X-Session': '1621448702.23937#Y8dbR/s1Wi3Wep830IUp8IoZDzz', }, method: 'post', requestStartedAt, url: '/', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }, res: { headers: { connection: 'close', 'content-length': '3513', 'content-type': 'application/json', date: 'Mon Feb 06 2023 13:35:00 GMT', }, responseTime: 100, status: 200, statusText: 'OK', }, }); }); test('it should NOT add axios response meta information in the info object if "meta" is disabled in opts', () => { const info = axiosLogFormat().transform({ level, message: res }, { meta: false }); expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({}); }); test('it should pass through other properties in the axios format', () => { const info = axiosLogFormat().transform( { message: res, requestId: '1234-request-id', timestamp: '2023-02-07 17:53:36.730' }, { headers: false, meta: true } ); expect(info.message).toBe(message); expect(info.requestId).toBe('1234-request-id'); expect(info.timestamp).toBe('2023-02-07 17:53:36.730'); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({ req: { baseURL: 'https://example.com', data: '{"product":"someProduct","data":{"name":"John","surname":"Doe"}}', method: 'post', requestStartedAt, url: '/', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }, res: { responseTime: 100, status: 200, statusText: 'OK', }, }); }); }); describe('For HTTP "404 Not Found" Response Error', () => { let axiosError; const message = 'Request failed with status code 404'; const description = 'Axios error'; const level = 'warn'; beforeEach(() => { const config = { timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3', }, method: 'get', url: 'http://localhost:1337/uploads/non-existent.svg', requestStartedAt, data: undefined, }; const request = { method: 'GET', path: '/uploads/non-existent.svg', host: 'localhost', protocol: 'http:', }; const response = { status: 404, statusText: 'Not Found', headers: { 'content-type': 'application/json; charset=utf-8', 'content-length': '94', date: 'Mon, 06 Feb 2023 15:48:16 GMT', connection: 'close', }, config, data: { data: null, error: { status: 404, name: 'NotFoundError', message: 'Not Found', details: {} }, }, responseTime: 100, }; axiosError = Object.assign(new AxiosError(message, '', config, request, response), { level }); }); test('it should add axios error response meta information in the info object for response', () => { const info = axiosLogFormat().transform(axiosError, { meta: true, headers: true }); expect(info.level).toBe(level); expect(info.message).toMatch(/GET http:\/\/localhost:1337\/uploads\/non-existent\.svg 404 Not Found (\d+)ms/); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({ req: { data: undefined, headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3' }, method: 'get', requestStartedAt, timeout: 0, url: 'http://localhost:1337/uploads/non-existent.svg', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }, res: { data: { data: null, error: { details: {}, message: 'Not Found', name: 'NotFoundError', status: 404 }, }, headers: { connection: 'close', 'content-length': '94', 'content-type': 'application/json; charset=utf-8', date: 'Mon, 06 Feb 2023 15:48:16 GMT', }, responseTime: 100, status: 404, statusText: 'Not Found', }, }); }); test('it should pass through other properties in the axios format', () => { const info = axiosLogFormat().transform( Object.assign(axiosError, { requestTraceId: 'test1234-request-id', timestamp: '2023-02-07 17:53:36.730', }), { meta: true, headers: true } ); expect(info.level).toBe(level); expect(info.message).toMatch(/GET http:\/\/localhost:1337\/uploads\/non-existent\.svg 404 Not Found (\d+)ms/); expect(info.requestTraceId).toBe('test1234-request-id'); expect(info.timestamp).toBe('2023-02-07 17:53:36.730'); expect(info.description).toBe(description); expect(info.meta).toStrictEqual({ req: { data: undefined, headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3' }, method: 'get', requestStartedAt, timeout: 0, url: 'http://localhost:1337/uploads/non-existent.svg', xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', }, res: { data: { data: null, error: { details: {}, message: 'Not Found', name: 'NotFoundError', status: 404 }, }, headers: { connection: 'close', 'content-length': '94', 'content-type': 'application/json; charset=utf-8', date: 'Mon, 06 Feb 2023 15:48:16 GMT', }, responseTime: 100, status: 404, statusText: 'Not Found', }, }); }); }); describe('For Network Errors', () => { const level = 'error'; test('it should add axios "ECONNREFUSED" error meta information the info object', () => { // Axios error in JSON format // 1. Arrange data for generating AxiosError const code = 'ECONNREFUSED'; const errno = -61; const message = 'connect ECONNREFUSED 127.0.0.1:3333'; const config = { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3', }, method: 'get', url: 'http://localhost:3333', requestStartedAt, }; const request = { method: 'GET', path: '/', host: 'localhost', protocol: 'http:', }; const axiosError = Object.assign(new AxiosError(message, code, config, request), { level, errno, syscall: 'connect', hostname: 'localhost', address: '127.0.0.1', port: 3333, }); // 2. Act const info = axiosLogFormat().transform(axiosError, { meta: true, headers: true }); // 3. Assert expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe('Axios error'); expect(info.meta).toStrictEqual({ req: { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3' }, method: 'get', requestStartedAt, url: 'http://localhost:3333', }, res: { errno, code, syscall: 'connect', hostname: 'localhost', address: '127.0.0.1', port: 3333, status: null, }, }); }); test('it should add axios "ENOTFOUND" error meta information the info object', () => { // 1. Arrange data for generating AxiosError const code = 'ENOTFOUND'; const errno = -3008; const message = 'getaddrinfo ENOTFOUND non-existent-domain-for-sure.com'; const config = { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3', }, method: 'get', url: 'http://non-existent-domain-for-sure.com/', requestStartedAt, data: undefined, }; const request = { method: 'GET', path: '/', host: 'non-existent-domain-for-sure.com', protocol: 'http:', }; const axiosError = Object.assign(new AxiosError(message, code, config, request), { level, errno, syscall: 'getaddrinfo', hostname: 'non-existent-domain-for-sure.com', address: '1.2.3.4', port: 80, status: null, }); // 2. Act const info = axiosLogFormat().transform(axiosError, { meta: true, headers: true, stack: false }); // 3. Assert expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe('Axios error'); expect(info.meta).toStrictEqual({ req: { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3' }, method: 'get', requestStartedAt, url: 'http://non-existent-domain-for-sure.com/', }, res: { code, errno, syscall: 'getaddrinfo', hostname: 'non-existent-domain-for-sure.com', address: '1.2.3.4', port: 80, status: null, }, }); }); test('it should add axios "ENOTFOUND" error meta information with stack the info object', () => { // 1. Arrange data for generating AxiosError const code = 'ENOTFOUND'; const errno = -3008; const message = 'getaddrinfo ENOTFOUND non-existent-domain-for-sure.com'; const config = { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3', }, method: 'get', url: 'http://non-existent-domain-for-sure.com/', requestStartedAt, data: undefined, }; const request = { method: 'GET', path: '/', host: 'non-existent-domain-for-sure.com', protocol: 'http:', }; const axiosError = Object.assign(new AxiosError(message, code, config, request), { level, errno, syscall: 'getaddrinfo', hostname: 'non-existent-domain-for-sure.com', address: '1.2.3.4', port: 80, status: null, }); // 2. Act const info = axiosLogFormat().transform(axiosError, { meta: true, headers: true, stack: true }); // 3. Assert expect(info.level).toBe(level); expect(info.message).toBe(message); expect(info.description).toBe('Axios error'); expect(info.meta).toStrictEqual({ req: { headers: { Accept: 'application/json, text/plain, */*', 'User-Agent': 'axios/1.3.3' }, method: 'get', requestStartedAt, url: 'http://non-existent-domain-for-sure.com/', }, res: { errno, code, stack: expect.stringMatching(/Error: getaddrinfo ENOTFOUND non-existent-domain-for-sure.com/), syscall: 'getaddrinfo', hostname: 'non-existent-domain-for-sure.com', address: '1.2.3.4', port: 80, status: null, }, }); }); }); });