Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ const app = new ExpressKit(nodekit, {
app.run();
```

## Self telemetry

By default, self telemetry sends the original request URL. Applications with large or
high-cardinality query strings can strip query parameters before sending stats:

```typescript
const config: Partial<AppConfig> = {
appTelemetryChEnableSelfStats: true,
appTelemetryChSelfStatsStripQueryParams: true,
};
```

## CSP

`config.ts`
Expand Down
13 changes: 12 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
} from './types';
import {prepareCSRFMiddleware} from './csrf';

function stripQueryString(url: string) {
const queryIndex = url.indexOf('?');

return queryIndex === -1 ? url : url.slice(0, queryIndex);
}

// Methods are lowercased to use it in `expressApp[method]`
function isAllowedMethod(method: string): method is Lowercase<HttpMethod> | 'mount' {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -21,7 +27,7 @@
}

function wrapMiddleware(fn: AppMiddleware, i?: number): AppMiddleware {
const result: AppMiddleware = async (req, res, next) => {

Check warning on line 30 in src/router.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Expected to return a value at the end of async arrow function
const reqCtx = req.ctx;
// Skip creating child context if parent is already ended (e.g. client disconnected).
// Optional chaining for backward compatibility with nodekit < 2.5.0 (no abortSignal).
Expand All @@ -45,7 +51,7 @@
ctx.fail(error);
req.ctx = reqCtx;
next(error);
return;

Check warning on line 54 in src/router.ts

View workflow job for this annotation

GitHub Actions / Verify Files

Async arrow function expected a return value
}
}
};
Expand Down Expand Up @@ -132,6 +138,11 @@
const disableSelfStats = Boolean(req.routeInfo.disableSelfStats);

if (!disableSelfStats) {
// Stripping removes all query parameter values, so redaction is unnecessary in this mode.
const requestUrl = ctx.config.appTelemetryChSelfStatsStripQueryParams
? stripQueryString(req.originalUrl)
: ctx.utils.redactSensitiveQueryParams(req.originalUrl);

req.originalContext.stats({
service: 'self',
action: req.routeInfo.handlerName || UNNAMED_CONTROLLER,
Expand All @@ -140,7 +151,7 @@
requestId: req.originalContext.get(REQUEST_ID_PARAM_NAME) || '',
requestTime: req.originalContext.getTime(), // We have to use req.originalContext here to get full time
requestMethod: req.method,
requestUrl: ctx.utils.redactSensitiveQueryParams(req.originalUrl),
requestUrl,
traceId: req.originalContext.getTraceId() || '',
userId: req.originalContext.get(USER_ID_PARAM_NAME) || '',
});
Expand Down
27 changes: 25 additions & 2 deletions src/tests/self-stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ describe('self stats telemetry', () => {
const agent = request.agent(app.express);
const requestId = Math.random().toString();

await agent.get('/ping-self-stats').set(DEFAULT_REQUEST_ID_HEADER, requestId).expect(200);
await agent
.get('/ping-self-stats?filter=value')
.set(DEFAULT_REQUEST_ID_HEADER, requestId)
.expect(200);

// last self stats data
const stat = stats.mock.calls?.pop() || {};
Expand All @@ -67,12 +70,32 @@ describe('self stats telemetry', () => {
responseStatus: 200,
requestId,
requestMethod: 'GET',
requestUrl: '/ping-self-stats',
requestUrl: '/ping-self-stats?filter=value',
traceId: '',
},
]);
});

it('self stats telemetry strips query string when enabled', async () => {
const {app, stats} = setupApp({
config: {
appTelemetryChSelfStatsStripQueryParams: true,
},
});

const agent = request.agent(app.express);

await agent.get('/ping-self-stats?filter=value&filter=other').expect(200);

const stat = stats.mock.calls?.pop() || {};

expect(stat).toMatchObject([
{
requestUrl: '/ping-self-stats',
},
]);
});

it('self stats telemetry skipped', async () => {
const {app, stats} = setupApp();

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ declare module '@gravity-ui/nodekit' {
appAfterAuthMiddleware?: AppMiddleware[];

appTelemetryChEnableSelfStats?: boolean;
appTelemetryChSelfStatsStripQueryParams?: boolean;

appLoggingOmitIdInMessages?: boolean;

Expand Down
Loading