Skip to content

Commit

Permalink
feat(loggers): allow to ignore urls by regex (#3592)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmenko authored Aug 19, 2024
1 parent d0b9f57 commit d1c89d2
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 35 deletions.
15 changes: 15 additions & 0 deletions .changeset/loud-files-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@commercetools-backend/loggers': minor
---

In the access logger the option `ignoreUrls` supports both strings and regular expressions.

For strings, the value is matched as-is. You can now use the regular expression to match a certain path structure, for example to ignore requests to load assets.

```ts
const access = createAccessLoggerMiddleware({
level: 'info',
json: true,
ignoreUrls: ['/', '/health', /^\/static\/(.*)/],
});
```
54 changes: 54 additions & 0 deletions packages-backend/loggers/src/helpers.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { parseIps, createAccessLogSkipper } from './helpers';

describe('parseIps', () => {
describe.each`
header | expectedIps
${undefined} | ${[]}
${'1.1.1.1'} | ${['1.1.1.1']}
${'1.1.1.1,2.2.2.2'} | ${['1.1.1.1', '2.2.2.2']}
${['1.1.1.1']} | ${['1.1.1.1']}
${['1.1.1.1', '2.2.2.2']} | ${['1.1.1.1', '2.2.2.2']}
`(`given the header x-forwarded-for "$header"`, ({ header, expectedIps }) => {
it(`it should extract list of IPs as ${expectedIps}`, () => {
expect(
parseIps(
// @ts-ignore: this should be a Request object
{ headers: { 'x-forwarded-for': header } }
)
).toEqual(expectedIps);
});
});
});

describe('createAccessLogSkipper', () => {
describe.each`
originalUrl | ignoreUrl | shouldSkip
${'/'} | ${'/'} | ${true}
${'/favicon.ico'} | ${'/favicon.ico'} | ${true}
${'/favicon.ico'} | ${/favicon/} | ${true}
${'/static/favicon.ico'} | ${'/favicon.ico'} | ${false}
${'/static/favicon.ico'} | ${/^\/static\/(.*)/} | ${true}
${'/static/chunks/webpack-123.js'} | ${'/static'} | ${false}
${'/static/chunks/webpack-123.js'} | ${/^\/static\/chunks\/(.*)/} | ${true}
`(`given "$originalUrl"`, ({ originalUrl, ignoreUrl, shouldSkip }) => {
it(`when ignoring "${ignoreUrl}" it should result in skip: ${shouldSkip}`, () => {
const skip = createAccessLogSkipper({ ignoreUrls: [ignoreUrl] });
expect(
skip(
// @ts-ignore: this should be a Request object
{ originalUrl }
)
).toEqual(shouldSkip);
});
});

it('should skip if option "silent" is true', () => {
const skip = createAccessLogSkipper({ silent: true });
expect(
skip(
// @ts-ignore: this should be a Request object
{}
)
).toEqual(true);
});
});
51 changes: 51 additions & 0 deletions packages-backend/loggers/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Request } from 'express';
import type { TAccessLoggerOptions } from './types';

const parseIps = (request: Request) => {
const forwardedFor = request.headers['x-forwarded-for'];

if (!forwardedFor) {
return [];
}

const remoteAddresses = Array.isArray(forwardedFor)
? forwardedFor
: forwardedFor.split(',');

return remoteAddresses;
};

const createAccessLogSkipper =
(options: TAccessLoggerOptions) => (request: Request) => {
if (Boolean(options.silent)) {
return true;
}
const hasMatchingIgnoreUrl = (options.ignoreUrls ?? []).some(
(uriPathOrRegex) => {
if (typeof uriPathOrRegex === 'string') {
return request.originalUrl === uriPathOrRegex;
}
return request.originalUrl.match(uriPathOrRegex);
}
);
return hasMatchingIgnoreUrl;
};

const mapRequestMetadata = (request: Request) => {
try {
const remoteAddress = request.socket?.remoteAddress;
const proxyIps = parseIps(request);
const [clientIp] = proxyIps;
return {
clientIp: clientIp ?? remoteAddress,
proxyIps,
hostname: request.socket ? request.hostname : undefined,
...(remoteAddress ? { remoteAddress } : {}),
};
} catch (error) {
console.error(`Failed to parse request metadata`, error);
return {};
}
};

export { parseIps, createAccessLogSkipper, mapRequestMetadata };
38 changes: 4 additions & 34 deletions packages-backend/loggers/src/middlewares/create-access-logger.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,19 @@
import type { Request } from 'express';
import expressWinston from 'express-winston';
import winston from 'winston';
import { createAccessLogSkipper, mapRequestMetadata } from '../helpers';
import type { TAccessLoggerOptions } from '../types';

type TAccessLoggerMiddleware = ReturnType<typeof expressWinston.logger>;

const parseIps = (request: Request) => {
const forwardedFor = request.headers['x-forwarded-for'];

if (!forwardedFor) {
return [];
}

const remoteAddresses = Array.isArray(forwardedFor)
? forwardedFor
: forwardedFor.split(',');

return remoteAddresses;
};

const createAccessLoggerMiddleware = (
options: TAccessLoggerOptions = {}
): TAccessLoggerMiddleware => {
const ignoreUrls = options.ignoreUrls ?? [];
const formatters = winston.format.combine(
winston.format.timestamp(),
...(options.formatters ?? []),
options.json ? winston.format.json() : winston.format.cli()
);
const skip = createAccessLogSkipper(options);

return expressWinston.logger({
level: options.level ?? 'info',
Expand All @@ -36,24 +22,8 @@ const createAccessLoggerMiddleware = (
meta: true,
expressFormat: true, // Use default morgan access log formatting
colorize: !options.json,
skip: (req) =>
Boolean(options.silent) || ignoreUrls.includes(req.originalUrl),
dynamicMeta: (req) => {
try {
const remoteAddress = req.socket?.remoteAddress;
const proxyIps = parseIps(req);
const [clientIp] = proxyIps;
return {
clientIp: clientIp ?? remoteAddress,
proxyIps,
hostname: req.socket ? req.hostname : undefined,
...(remoteAddress ? { remoteAddress } : {}),
};
} catch (error) {
console.error(`Failed to parse request metadata`, error);
return {};
}
},
skip,
dynamicMeta: mapRequestMetadata,
});
};

Expand Down
2 changes: 1 addition & 1 deletion packages-backend/loggers/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export type TLoggerOptions = {
};

export type TAccessLoggerOptions = TLoggerOptions & {
ignoreUrls?: string[];
ignoreUrls?: (string | RegExp)[];
};

0 comments on commit d1c89d2

Please sign in to comment.