Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better logging #62

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@
"drizzle-orm": "^0.33.0",
"express-session": "^1.18.0",
"graphql": "^16.9.0",
"nest-winston": "^1.9.7",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"pg": "^8.13.0",
"redis": "^4.7.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"winston": "^3.15.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { AuthenticatedGuard } from "./common/guards/auth.guard";
import { DrizzleModule } from "./drizzle/drizzle.module";
import { AuthModule } from "./modules/auth/auth.module";
import { validate } from "./utils/env.validate";
import { ApolloLogger } from "./utils/graphql.logger";

@Module({
imports: [
Expand Down Expand Up @@ -48,6 +49,7 @@ import { validate } from "./utils/env.validate";
provide: APP_GUARD,
useClass: AuthenticatedGuard,
},
ApolloLogger,
],
})
export class AppModule {}
1 change: 1 addition & 0 deletions apps/api/src/app.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class AppResolver {
return this.appService.getHello();
}

@Public()
@Query(() => String)
async db(): Promise<string> {
return this.appService.testDb();
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/drizzle/drizzle.logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable, Logger, LoggerService } from "@nestjs/common";
import { Logger as DrizzleLoggerInterface } from "drizzle-orm";

@Injectable()
export class DrizzleLogger implements DrizzleLoggerInterface {
constructor(private readonly logger: LoggerService) {}

logQuery(query: string, params: unknown[]): void {
if (query && params) this.logger.log(`Query: ${query} ${params}`);
}

Check warning on line 10 in apps/api/src/drizzle/drizzle.logger.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/drizzle/drizzle.logger.ts#L9-L10

Added lines #L9 - L10 were not covered by tests
}
11 changes: 9 additions & 2 deletions apps/api/src/drizzle/drizzle.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import { Inject, Injectable, Scope } from "@nestjs/common";
import { Inject, Injectable, Logger, Scope } from "@nestjs/common";
import { CONTEXT } from "@nestjs/graphql";
import { NodePgDatabase, drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { DrizzleLogger } from "./drizzle.logger";
import { CONNECTION_POOL } from "./drizzle.module-definition";
import * as schema from "./schema";
import { DRIZZLE_DATABASE_KEY } from "./transaction.interceptor";

@Injectable({ scope: Scope.REQUEST })
export class DrizzleService {
private readonly logger = new Logger("Drizzle:Service");

constructor(
@Inject(CONNECTION_POOL) private readonly pool: Pool,
@Inject(CONTEXT) private readonly context: any,
) {}

public get db(): NodePgDatabase<typeof schema> {
return (
this.context[DRIZZLE_DATABASE_KEY] ?? drizzle(this.pool, { schema })
this.context[DRIZZLE_DATABASE_KEY] ??
drizzle(this.pool, {
schema,
logger: new DrizzleLogger(this.logger),
})

Check warning on line 25 in apps/api/src/drizzle/drizzle.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/drizzle/drizzle.service.ts#L21-L25

Added lines #L21 - L25 were not covered by tests
);
}
}
9 changes: 8 additions & 1 deletion apps/api/src/drizzle/transaction.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@
ExecutionContext,
Inject,
Injectable,
Logger,
NestInterceptor,
} from "@nestjs/common";
import { GqlExecutionContext } from "@nestjs/graphql";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { Observable, from, throwError } from "rxjs";
import { catchError, switchMap } from "rxjs/operators";
import { DrizzleLogger } from "./drizzle.logger";
import { CONNECTION_POOL } from "./drizzle.module-definition";
import * as schema from "./schema";

export const DRIZZLE_DATABASE_KEY = "DRIZZLE_DATABASE";

@Injectable()
export class TransactionInterceptor implements NestInterceptor {
private readonly logger = new Logger("Drizzle:TransactionInterceptor");

constructor(@Inject(CONNECTION_POOL) private readonly pool: Pool) {}

intercept(
Expand All @@ -26,7 +30,10 @@
const gqlContext = GqlExecutionContext.create(context);
const ctx = gqlContext.getContext();

const db = drizzle(this.pool, { schema });
const db = drizzle(this.pool, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the db is not injected?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya 3ammena, separate issue wallah 😭 See #2

schema,
logger: new DrizzleLogger(this.logger),
});

Check warning on line 36 in apps/api/src/drizzle/transaction.interceptor.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/drizzle/transaction.interceptor.ts#L33-L36

Added lines #L33 - L36 were not covered by tests

return from(
db.transaction(async (tx) => {
Expand Down
9 changes: 8 additions & 1 deletion apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
import { NestFactory } from "@nestjs/core";
import RedisStore from "connect-redis";
import * as session from "express-session";
import { WinstonModule } from "nest-winston";

Check warning on line 6 in apps/api/src/main.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/main.ts#L6

Added line #L6 was not covered by tests
import * as passport from "passport";
import { createClient } from "redis";
import { createLogger } from "winston";

Check warning on line 9 in apps/api/src/main.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/main.ts#L9

Added line #L9 was not covered by tests
import { AppModule } from "./app.module";
import { winstonConfig } from "./utils/winston.config";

Check warning on line 11 in apps/api/src/main.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/main.ts#L11

Added line #L11 was not covered by tests

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const loggerInstance = createLogger(winstonConfig);

const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({ instance: loggerInstance }),
});

Check warning on line 18 in apps/api/src/main.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/main.ts#L14-L18

Added lines #L14 - L18 were not covered by tests
const configService = app.get(ConfigService);

app.useGlobalPipes(
Expand Down
61 changes: 61 additions & 0 deletions apps/api/src/utils/graphql.logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
ApolloServerPlugin,
BaseContext,
GraphQLRequestContext,
GraphQLRequestContextWillSendResponse,
GraphQLRequestListener,
Copy link
Contributor

@bassiounix bassiounix Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import

Suggested change
GraphQLRequestListener,
Remove `GraphQLRequestListener`

} from "@apollo/server";
import { Plugin } from "@nestjs/apollo";
import { Logger } from "@nestjs/common";
import { performance } from "perf_hooks";

@Plugin()
export class ApolloLogger implements ApolloServerPlugin {
private readonly logger = new Logger("GraphQL");

async requestDidStart(requestContext: GraphQLRequestContext<BaseContext>) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark full access specifier as the logger

Suggested change
async requestDidStart(requestContext: GraphQLRequestContext<BaseContext>) {
public async requestDidStart(requestContext: GraphQLRequestContext<BaseContext>) {

const thatLogger = this.logger;
if (requestContext.request.operationName === "IntrospectionQuery") {
return;
}

requestContext.request.http?.headers.set(
"x-request-id",
crypto.randomUUID(),
);
if (!requestContext.request.http?.headers.get("x-session-id")) {
requestContext.request.http?.headers.set(
"x-session-id",
crypto.randomUUID(),
);
}
const now = performance.now();
return {
async willSendResponse(
responseContext: GraphQLRequestContextWillSendResponse<BaseContext>,
) {
if (
requestContext.request.operationName ===
"IntrospectionQuery"
) {
return;
}
thatLogger.log({
sessionId:
requestContext.request.http?.headers.get(
"x-session-id",
),
requestId:
requestContext.request.http?.headers.get(
"x-request-id",
),
query: requestContext.request.query,
variables: requestContext.request.variables,
resposneTime: performance.now() - now,
responseDate: new Date().toISOString(),
error: responseContext.errors,
});
},
};
}

Check warning on line 60 in apps/api/src/utils/graphql.logger.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/utils/graphql.logger.ts#L17-L60

Added lines #L17 - L60 were not covered by tests
}
74 changes: 74 additions & 0 deletions apps/api/src/utils/winston.config.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the place of configs. A separate config module is required.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bassiounix This is a feature request, we can create a separate issue for this.

The intention of this PR is to add logging, not create a config module.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as winston from "winston";
import "winston-daily-rotate-file";
import { utilities as nestWinstonModuleUtilities } from "nest-winston";

const dbRotateTransport = new winston.transports.DailyRotateFile({
filename: "logs/db-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
format: winston.format.combine(
winston.format((info) => {
if (
info.context === "Drizzle:Service" ||
info.context === "Drizzle:TransactionInterceptor"
)
return info;
else return false;
})(),
winston.format.json(),
),
});

const httpRotateTransport = new winston.transports.DailyRotateFile({
filename: "logs/graphql-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
format: winston.format.combine(
winston.format((info) => {
if (info.context === "GraphQL") return info;
else return false;
})(),
winston.format.json(),
),
});

const errorRotateTransport = new winston.transports.DailyRotateFile({
filename: "logs/error-%DATE%.log",
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
level: "error",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
});

export const winstonConfig = {
level: process.env.LOG_LEVEL || "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
handleExceptions: true,
handleRejections: true,
exitOnError: false,
transports: [
errorRotateTransport,
dbRotateTransport,
httpRotateTransport,
new winston.transports.Console({
format: winston.format.combine(
nestWinstonModuleUtilities.format.nestLike("NestWinston", {
colors: true,
prettyPrint: true,
}),
),
}),
],
};

Check warning on line 74 in apps/api/src/utils/winston.config.ts

View check run for this annotation

Codecov / codecov/patch

apps/api/src/utils/winston.config.ts#L2-L74

Added lines #L2 - L74 were not covered by tests
Loading