Skip to content

Commit fb9b8a1

Browse files
authored
Merge pull request #15 from topcoder-platform/add-timing-logging
Add timing logging for every call on mcp
2 parents 6e610fb + 67255d9 commit fb9b8a1

File tree

9 files changed

+71
-0
lines changed

9 files changed

+71
-0
lines changed

src/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ToolsModule } from './mcp/tools/tools.module';
66
import { GlobalProvidersModule } from './shared/global/globalProviders.module';
77
import { ResourcesModule } from './mcp/resources/resources.module';
88
import { randomUUID } from 'crypto';
9+
import { TimingInterceptorMiddleware } from './shared/global/timingInterceptor';
910

1011
@Module({
1112
imports: [
@@ -28,5 +29,6 @@ import { randomUUID } from 'crypto';
2829
export class AppModule implements NestModule {
2930
configure(consumer: MiddlewareConsumer) {
3031
consumer.apply(TokenValidatorMiddleware).forRoutes('*');
32+
consumer.apply(TimingInterceptorMiddleware).forRoutes('*');
3133
}
3234
}

src/mcp/resources/swagger/challenges.resource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { Resource } from '@tc/mcp-nest';
33
import axios from 'axios';
44
import { Logger } from 'src/shared/global';
5+
import { LogTime } from 'src/shared/global/logTime.decorator';
56

67
const SPEC_URL =
78
'https://raw.githubusercontent.com/topcoder-platform/challenge-api-v6/refs/heads/develop/docs/swagger.yaml';
@@ -16,6 +17,7 @@ export class ChallengesApiSwaggerResource {
1617
description: 'Swagger documentation for the Challenges V6 API',
1718
mimeType: 'text/yaml',
1819
})
20+
@LogTime('ChallengesApiSwaggerResource')
1921
async getChallengesApiSwagger() {
2022
this.logger.debug('Fetching Challenges V6 API Swagger');
2123
// Fetch the content from the URI and return it.

src/mcp/resources/swagger/identity.resource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { Resource } from '@tc/mcp-nest';
33
import axios from 'axios';
44
import { Logger } from 'src/shared/global';
5+
import { LogTime } from 'src/shared/global/logTime.decorator';
56

67
const SPEC_URL =
78
'https://raw.githubusercontent.com/topcoder-platform/identity-api-v6/refs/heads/develop/doc/swagger.yaml';
@@ -16,6 +17,7 @@ export class IdentityApiSwaggerResource {
1617
description: 'Swagger documentation for the Identity V6 API',
1718
mimeType: 'text/yaml',
1819
})
20+
@LogTime('IdentityApiSwaggerResource')
1921
async getIdentityApiSwagger() {
2022
this.logger.debug('Fetching Identity V6 API Swagger');
2123
// Fetch the content from the URI and return it.

src/mcp/resources/swagger/member.resource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { Resource } from '@tc/mcp-nest';
33
import axios from 'axios';
44
import { Logger } from 'src/shared/global';
5+
import { LogTime } from 'src/shared/global/logTime.decorator';
56

67
const SPEC_URL =
78
'https://raw.githubusercontent.com/topcoder-platform/member-api-v6/refs/heads/develop/docs/swagger.yaml';
@@ -16,6 +17,7 @@ export class MemberApiSwaggerResource {
1617
description: 'Swagger documentation for the Member V6 API',
1718
mimeType: 'text/yaml',
1819
})
20+
@LogTime('MemberApiSwaggerResource')
1921
async getMemberApiSwagger() {
2022
this.logger.debug('Fetching Member V6 API Swagger');
2123
// Fetch the content from the URI and return it.

src/mcp/resources/swagger/review.resource.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
22
import { Resource } from '@tc/mcp-nest';
33
import axios from 'axios';
44
import { Logger } from 'src/shared/global';
5+
import { LogTime } from 'src/shared/global/logTime.decorator';
56

67
const SPEC_URL = 'https://api.topcoder-dev.com/v6/review/api-docs-yaml';
78

@@ -15,6 +16,7 @@ export class ReviewApiSwaggerResource {
1516
description: 'Swagger documentation for the Review V6 API',
1617
mimeType: 'text/yaml',
1718
})
19+
@LogTime('ReviewApiSwaggerResource')
1820
async getReviewApiSwagger() {
1921
this.logger.debug('Fetching Review V6 API Swagger');
2022
// Fetch the content from the URI and return it.

src/mcp/tools/challenges/queryChallenges.tool.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { QUERY_CHALLENGES_TOOL_PARAMETERS } from './queryChallenges.parameters';
55
import { TopcoderChallengesService } from 'src/shared/topcoder/challenges.service';
66
import { Logger } from 'src/shared/global';
77
import { QUERY_CHALLENGES_TOOL_OUTPUT_SCHEMA } from './queryChallenges.output';
8+
import { LogTime } from 'src/shared/global/logTime.decorator';
89

910
@Injectable()
1011
export class QueryChallengesTool {
@@ -131,6 +132,7 @@ export class QueryChallengesTool {
131132
readOnlyHint: true,
132133
},
133134
})
135+
@LogTime('ChallengesTool')
134136
async queryChallenges(params) {
135137
return this._queryChallenges(params);
136138
}

src/mcp/tools/skills/querySkills.tool.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Logger } from 'src/shared/global';
55
import { QUERY_SKILLS_TOOL_PARAMETERS } from './querySkills.parameters';
66
import { QUERY_SKILLS_TOOL_OUTPUT_SCHEMA } from './querySkills.output';
77
import { TopcoderSkillsService } from 'src/shared/topcoder/skills.service';
8+
import { LogTime } from 'src/shared/global/logTime.decorator';
89

910
@Injectable()
1011
export class QuerySkillsTool {
@@ -125,6 +126,7 @@ export class QuerySkillsTool {
125126
readOnlyHint: true,
126127
},
127128
})
129+
@LogTime('SkillsTool')
128130
async querySkills(params) {
129131
return this._querySkills(params);
130132
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Logger } from '@nestjs/common';
2+
3+
export function LogTime(label?: string) {
4+
const logger = new Logger('ExecutionTime');
5+
6+
return function (
7+
target: any,
8+
propertyKey: string,
9+
descriptor: PropertyDescriptor,
10+
) {
11+
const originalMethod = descriptor.value;
12+
13+
descriptor.value = async function (...args: any[]) {
14+
const start = Date.now();
15+
16+
try {
17+
const result = await originalMethod.apply(this, args);
18+
const ms = Date.now() - start;
19+
logger.log(`${label || propertyKey} executed in ${ms}ms`);
20+
return result;
21+
} catch (error) {
22+
const ms = Date.now() - start;
23+
logger.error(
24+
`${label || propertyKey} failed after ${ms}ms – ${error.message}`,
25+
);
26+
throw error;
27+
}
28+
};
29+
30+
return descriptor;
31+
};
32+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
2+
3+
import { Request, Response, NextFunction } from 'express';
4+
5+
@Injectable()
6+
export class TimingInterceptorMiddleware implements NestMiddleware {
7+
private logger = new Logger('TimingInterceptor');
8+
9+
use(request: Request, response: Response, next: NextFunction): void {
10+
const { method, originalUrl: url } = request;
11+
const start = Date.now();
12+
const mcpMethod = request.body?.method;
13+
14+
response.on('close', () => {
15+
const { statusCode } = response;
16+
const duration = Date.now() - start;
17+
18+
this.logger.log(
19+
`${method} ${mcpMethod ? `{${mcpMethod}} ` : ''}${url} ${statusCode} took ${duration}ms`
20+
);
21+
});
22+
23+
next();
24+
}
25+
}

0 commit comments

Comments
 (0)