Skip to content

Commit e1a653b

Browse files
committed
feat: implement logging interceptor for telemetry data collection; add TelemetryLog model and service
1 parent e42fbfe commit e1a653b

File tree

5 files changed

+137
-5
lines changed

5 files changed

+137
-5
lines changed

backend/src/app.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { GitHubModule } from './github/github.module';
2121
import { AppConfigService } from './config/config.service';
2222
import { getDatabaseConfig } from './database.config';
2323
import { DashboardModule } from './dashboard/dashboard.module';
24-
import { Role } from './auth/role/role.model';
24+
import { InterceptorModule } from './interceptor/interceptor.module';
2525

2626
@Module({
2727
imports: [
@@ -59,6 +59,7 @@ import { Role } from './auth/role/role.model';
5959
TypeOrmModule.forFeature([User]),
6060
GitHubModule,
6161
DashboardModule,
62+
InterceptorModule,
6263
],
6364
providers: [
6465
AppResolver,

backend/src/interceptor/LoggingInterceptor.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@ import {
77
ContextType,
88
} from '@nestjs/common';
99
import { Observable } from 'rxjs';
10+
import { tap } from 'rxjs/operators';
1011
import { GqlExecutionContext } from '@nestjs/graphql';
12+
import { TelemetryLogService } from './telemetry-log.service';
1113

1214
@Injectable()
1315
export class LoggingInterceptor implements NestInterceptor {
1416
private readonly logger = new Logger('RequestLogger');
17+
private startTime: number;
18+
19+
constructor(private telemetryLogService: TelemetryLogService) {}
1520

1621
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
1722
const contextType = context.getType();
@@ -42,6 +47,9 @@ export class LoggingInterceptor implements NestInterceptor {
4247

4348
const { operation, fieldName } = info;
4449
let variables = '';
50+
const startTime = Date.now();
51+
const request = ctx.getContext().req;
52+
const userId = request?.user?.id;
4553

4654
try {
4755
variables = JSON.stringify(ctx.getContext()?.req?.body?.variables ?? {});
@@ -50,12 +58,28 @@ export class LoggingInterceptor implements NestInterceptor {
5058
}
5159

5260
this.logger.log(
53-
`[GraphQL] ${operation.operation.toUpperCase()} \x1B[33m${fieldName}\x1B[39m${
61+
`[GraphQL] ${operation.operation.toUpperCase()} [33m${fieldName}[39m${
5462
variables ? ` Variables: ${variables}` : ''
5563
}`,
5664
);
5765

58-
return next.handle();
66+
return next.handle().pipe(
67+
tap({
68+
next: (value) => {
69+
const timeConsumed = Date.now() - startTime;
70+
this.telemetryLogService.create({
71+
timestamp: new Date(),
72+
requestMethod: operation.operation.toUpperCase(),
73+
endpoint: fieldName,
74+
input: variables,
75+
output: JSON.stringify(value),
76+
timeConsumed,
77+
userId,
78+
handler: 'GraphQL',
79+
});
80+
},
81+
}),
82+
);
5983
}
6084

6185
private handleRestRequest(
@@ -64,13 +88,30 @@ export class LoggingInterceptor implements NestInterceptor {
6488
): Observable<any> {
6589
const httpContext = context.switchToHttp();
6690
const request = httpContext.getRequest();
91+
const startTime = Date.now();
6792

68-
const { method, url, body } = request;
93+
const { method, url, body, user } = request;
6994

7095
this.logger.log(
7196
`[REST] ${method.toUpperCase()} ${url} Body: ${JSON.stringify(body)}`,
7297
);
7398

74-
return next.handle();
99+
return next.handle().pipe(
100+
tap({
101+
next: (value) => {
102+
const timeConsumed = Date.now() - startTime;
103+
this.telemetryLogService.create({
104+
timestamp: new Date(),
105+
requestMethod: method,
106+
endpoint: url,
107+
input: JSON.stringify(body),
108+
output: JSON.stringify(value),
109+
timeConsumed,
110+
userId: user?.id,
111+
handler: 'REST',
112+
});
113+
},
114+
}),
115+
);
75116
}
76117
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { TelemetryLog } from './telemetry-log.model';
4+
import { TelemetryLogService } from './telemetry-log.service';
5+
import { LoggingInterceptor } from './LoggingInterceptor';
6+
7+
@Module({
8+
imports: [TypeOrmModule.forFeature([TelemetryLog])],
9+
providers: [TelemetryLogService, LoggingInterceptor],
10+
exports: [TelemetryLogService, LoggingInterceptor],
11+
})
12+
export class InterceptorModule {}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Field, ID, ObjectType } from '@nestjs/graphql';
2+
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
3+
4+
@Entity()
5+
@ObjectType()
6+
export class TelemetryLog {
7+
@PrimaryGeneratedColumn()
8+
@Field(() => ID)
9+
id: number;
10+
11+
@Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
12+
@Field(() => Date)
13+
timestamp: Date;
14+
15+
@Column()
16+
@Field()
17+
requestMethod: string;
18+
19+
@Column('text')
20+
@Field()
21+
endpoint: string;
22+
23+
@Column('text', { nullable: true })
24+
@Field({ nullable: true })
25+
input: string;
26+
27+
@Column('text', { nullable: true })
28+
@Field({ nullable: true })
29+
output: string;
30+
31+
@Column({ nullable: true })
32+
@Field({ nullable: true })
33+
inputToken: string;
34+
35+
@Column({ nullable: true })
36+
@Field({ nullable: true })
37+
outputToken: string;
38+
39+
@Column('int')
40+
@Field()
41+
timeConsumed: number;
42+
43+
@Column({ nullable: true })
44+
@Field({ nullable: true })
45+
userId: string;
46+
47+
@Column({ nullable: true })
48+
@Field({ nullable: true })
49+
handler: string;
50+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository } from 'typeorm';
4+
import { TelemetryLog } from './telemetry-log.model';
5+
6+
@Injectable()
7+
export class TelemetryLogService {
8+
constructor(
9+
@InjectRepository(TelemetryLog)
10+
private telemetryLogRepository: Repository<TelemetryLog>,
11+
) {}
12+
13+
async create(data: Partial<TelemetryLog>): Promise<TelemetryLog> {
14+
const telemetryLog = this.telemetryLogRepository.create(data);
15+
return await this.telemetryLogRepository.save(telemetryLog);
16+
}
17+
18+
async findAll(): Promise<TelemetryLog[]> {
19+
return await this.telemetryLogRepository.find({
20+
order: { timestamp: 'DESC' },
21+
take: 100,
22+
});
23+
}
24+
25+
async findById(id: number): Promise<TelemetryLog> {
26+
return await this.telemetryLogRepository.findOne({ where: { id } });
27+
}
28+
}

0 commit comments

Comments
 (0)