Skip to content

Commit

Permalink
feat(be): logging
Browse files Browse the repository at this point in the history
  • Loading branch information
krystxf committed Oct 6, 2024
1 parent 5c835e0 commit fd366f4
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 15 deletions.
2 changes: 1 addition & 1 deletion backend-nest/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ model Log {
host String?
path String?
statusCode Int?
duration Int?
duration Int? // in milliseconds
createdAt DateTime @default(now())
}

Expand Down
17 changes: 15 additions & 2 deletions backend-nest/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { ApolloDriver, type ApolloDriverConfig } from "@nestjs/apollo";
import { CacheModule } from "@nestjs/cache-manager";
import { Module } from "@nestjs/common";
import {
MiddlewareConsumer,
Module,
NestModule,
RequestMethod,
} from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GraphQLModule } from "@nestjs/graphql";
import { ScheduleModule } from "@nestjs/schedule";

import { GRAPHQL_API_ROOT } from "src/constants/graphql.const";
import { LoggerMiddleware } from "src/middleware/logger.middleware";
import { DepartureModule } from "src/modules/departure/departure.module";
import { ImportModule } from "src/modules/import/import.module";
import { PlatformModule } from "src/modules/platform/platform.module";
Expand Down Expand Up @@ -35,4 +41,11 @@ import { StopModule } from "src/modules/stop/stop.module";
controllers: [],
providers: [],
})
export class AppModule {}
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes({
path: "*",
method: RequestMethod.ALL,
});
}
}
13 changes: 13 additions & 0 deletions backend-nest/src/enums/log.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export { LogType } from "@prisma/client";

export enum StopSyncTrigger {
CRON = "CRON",
INIT = "INIT",
}

export enum LogMessage {
IMPORT_STOPS = "Import stops",
REST = "REST",
GRAPHQL = "GraphQL",
GRAPHQL_INTROSPECTION = "GraphQL - Introspection Query",
}
99 changes: 99 additions & 0 deletions backend-nest/src/middleware/logger.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Injectable, NestMiddleware } from "@nestjs/common";
import { LogType } from "@prisma/client";
import { NextFunction, Request, Response } from "express";

import { LogMessage } from "src/enums/log.enum";
import { PrismaService } from "src/modules/prisma/prisma.service";
import {
isGraphqlRequest,
isIntrospectionQuery,
} from "src/utils/graphql.utils";
import { isSuccess } from "src/utils/status-code.utils";

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
constructor(private prisma: PrismaService) {}

use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();

res.on("finish", async () => {
if (isGraphqlRequest(req)) {
try {
await createGraphqlLog({
req,
res,
duration: Date.now() - start,
prisma: this.prisma,
});
} catch (error) {
console.error("Failed to create Graphql log", error);
}
} else {
try {
await createRestLog({
req,
res,
duration: Date.now() - start,
prisma: this.prisma,
});
} catch (error) {
console.error("Failed to create REST log", error);
}
}
});

if (next) {
next();
}
}
}

const createRestLog = async ({
req,
res,
duration,
prisma,
}: {
req: Request;
res: Response;
duration: number;
prisma: PrismaService;
}): Promise<void> => {
prisma.log.create({
data: {
type: isSuccess(res.statusCode) ? LogType.INFO : LogType.ERROR,
message: LogMessage.REST,
statusCode: res.statusCode,
host: req.headers.host ?? null,
path: req.originalUrl,
duration,
},
});
};

const createGraphqlLog = async ({
req,
res,
duration,
prisma,
}: {
req: Request;
res: Response;
duration: number;
prisma: PrismaService;
}): Promise<void> => {
prisma.log.create({
data: {
type: isSuccess(res.statusCode) ? LogType.INFO : LogType.ERROR,
message: isIntrospectionQuery(req)
? LogMessage.GRAPHQL_INTROSPECTION
: LogMessage.GRAPHQL,
statusCode: res.statusCode,
host: req.headers.host ?? null,
duration,
path: req.originalUrl,
description: req.body.query,
},
});
};
38 changes: 32 additions & 6 deletions backend-nest/src/modules/import/import.controller.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
import { Controller, OnModuleInit } from "@nestjs/common";
import { Cron, CronExpression } from "@nestjs/schedule";

import { LogMessage, LogType, StopSyncTrigger } from "src/enums/log.enum";
import { ImportService } from "src/modules/import/import.service";
import { PrismaService } from "src/modules/prisma/prisma.service";

@Controller("import")
export class ImportController implements OnModuleInit {
constructor(private readonly importService: ImportService) {}
constructor(
private readonly importService: ImportService,
private readonly prismaService: PrismaService,
) {}

async onModuleInit() {
console.log("Starting initial stop sync");
this.syncStops();

this.syncStops(StopSyncTrigger.INIT);
}

@Cron(CronExpression.EVERY_7_HOURS)
async cronSyncStops(): Promise<void> {
console.log("Starting scheduled stop sync");
this.syncStops();

this.syncStops(StopSyncTrigger.CRON);
}

async syncStops(): Promise<void> {
async syncStops(trigger: StopSyncTrigger): Promise<void> {
const start = Date.now();

try {
await this.importService.syncStops();
console.log("Scheduled stop sync completed successfully");

await this.prismaService.log.create({
data: {
type: LogType.INFO,
message: LogMessage.IMPORT_STOPS,
duration: Date.now() - start,
description: `Trigger: ${trigger};`,
},
});
} catch (error) {
console.error("Error during scheduled stop sync:", error);
await this.prismaService.log.create({
data: {
type: LogType.ERROR,
message: LogMessage.IMPORT_STOPS,
duration: Date.now() - start,
description: `Trigger: ${trigger}; Error: ${error}`,
},
});
} finally {
console.log("Finished stop sync");
}
}
}
5 changes: 0 additions & 5 deletions backend-nest/src/modules/import/import.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,6 @@ export class ImportService {
}

async syncStops(): Promise<void> {
console.log("Syncing stops and routes");

const platformsData = await this.getPlatforms();
const stopsData = await this.getStops();

Expand All @@ -156,7 +154,6 @@ export class ImportService {
const isMetro = routeNames.some(
(routeName) => metroLine.safeParse(routeName).success,
);

return {
latitude,
longitude,
Expand Down Expand Up @@ -194,7 +191,5 @@ export class ImportService {
routes: platform.routes,
})),
});

console.log(`Import finished`);
}
}
1 change: 0 additions & 1 deletion backend-nest/src/modules/stop/stop.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export class StopController {
@Query("metroOnly")
metroOnlyQuery: unknown,
) {
console.log("Fetching all stops");
const metroOnly: boolean = metroOnlyQuery === "true";
const stops = await this.stopService.getAll({ metroOnly });

Expand Down
11 changes: 11 additions & 0 deletions backend-nest/src/utils/graphql.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Request } from "express";

import { GRAPHQL_API_ROOT } from "src/constants/graphql.const";

export const isGraphqlRequest = (req: Request): boolean => {
return req.originalUrl === GRAPHQL_API_ROOT;
};

export const isIntrospectionQuery = (req: Request): boolean => {
return String(req.body?.query).startsWith("query IntrospectionQuery {");
};
10 changes: 10 additions & 0 deletions backend-nest/src/utils/status-code.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Checks if the given HTTP status code represents a successful response.
* @param statusCode - The HTTP status code to check.
* @returns True if the status code is in the range [200, 300), false otherwise.
*/
export const isSuccess = (statusCode: number): boolean => {
// Ensure the status code is a positive integer
const code = Math.floor(Math.abs(statusCode));
return code >= 200 && code < 300;
};

0 comments on commit fd366f4

Please sign in to comment.