Skip to content

Commit cd2ae4c

Browse files
committed
feat: loki
1 parent a02f99b commit cd2ae4c

File tree

6 files changed

+91
-0
lines changed

6 files changed

+91
-0
lines changed

apps/api-gateway/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@nestjs/platform-socket.io": "^11.1.9",
3333
"@nestjs/swagger": "^11.2.1",
3434
"@nestjs/websockets": "^11.1.9",
35+
"axios": "^1.13.2",
3536
"bullmq": "^5.63.1",
3637
"class-transformer": "^0.5.1",
3738
"class-validator": "^0.14.2",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Injectable, LoggerService } from '@nestjs/common';
2+
import axios from 'axios';
3+
4+
@Injectable()
5+
export class LokiLogger implements LoggerService {
6+
private lokiUrl = process.env.LOKI_URL ?? 'http://loki:3100/loki/api/v1/push';
7+
private appName = process.env.APP_NAME ?? 'api-gateway';
8+
9+
log(message: string, context?: string) {
10+
this.sendLog('info', message, context);
11+
console.log(`[INFO]${context ? ` [${context}]` : ''} ${message}`);
12+
}
13+
14+
error(message: string, trace?: string, context?: string) {
15+
this.sendLog('error', `${message} ${trace || ''}`, context);
16+
console.error(`[ERROR]${context ? ` [${context}]` : ''} ${message}`, trace);
17+
}
18+
19+
warn(message: string, context?: string) {
20+
this.sendLog('warn', message, context);
21+
console.warn(`[WARN]${context ? ` [${context}]` : ''} ${message}`);
22+
}
23+
24+
debug(message: string, context?: string) {
25+
this.sendLog('debug', message, context);
26+
console.debug(`[DEBUG]${context ? ` [${context}]` : ''} ${message}`);
27+
}
28+
29+
private async sendLog(level: string, msg: string, context?: string) {
30+
try {
31+
await axios.post(this.lokiUrl, {
32+
streams: [
33+
{
34+
stream: { level, app: this.appName, context: context || 'default' },
35+
values: [[`${Date.now() * 1_000_000}`, msg]], // nanoseconds
36+
},
37+
],
38+
});
39+
} catch (err) {
40+
console.error('Failed to send log to Loki', err);
41+
}
42+
}
43+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
2+
import { Observable, tap } from 'rxjs';
3+
import { LokiLogger } from './loki-logger.service';
4+
5+
@Injectable()
6+
export class LokiLoggingInterceptor implements NestInterceptor {
7+
constructor(private readonly logger: LokiLogger) {}
8+
9+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
10+
const req = context.switchToHttp().getRequest();
11+
const { method, url, body } = req;
12+
const start = Date.now();
13+
14+
return next.handle().pipe(
15+
tap({
16+
next: (res) => {
17+
const ms = Date.now() - start;
18+
this.logger.log(`${method} ${url} ${res?.statusCode || 200} - ${ms}ms`, 'HTTP');
19+
},
20+
error: (err) => {
21+
const ms = Date.now() - start;
22+
this.logger.error(
23+
`${method} ${url} - ${err.message} - ${ms}ms`,
24+
err.stack,
25+
'HTTP',
26+
);
27+
},
28+
}),
29+
);
30+
}
31+
}

apps/api-gateway/src/main.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import cookieParser from 'cookie-parser';
66
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
77
import { ConfigService } from '@nestjs/config';
88
import { Configuration } from '@leetcode/config';
9+
import { LokiLogger } from './logging/loki-logger.service';
10+
import { LokiLoggingInterceptor } from './logging/loki-logging.interceptor';
911

1012
declare global {
1113
namespace Express {
@@ -17,6 +19,7 @@ declare global {
1719

1820
async function bootstrap() {
1921
const app = await NestFactory.create(AppModule);
22+
const logger = new LokiLogger();
2023

2124
// CONFIGURATION
2225
const configService = app.get(ConfigService<Configuration>);
@@ -26,6 +29,9 @@ async function bootstrap() {
2629
// VALIDATION
2730
app.useGlobalPipes(new ValidationPipe({ exceptionFactory }));
2831

32+
// LOGGING
33+
app.useGlobalInterceptors(new LokiLoggingInterceptor(logger));
34+
2935
// MIDDLWARE
3036
app.use(cookieParser());
3137
app.enableCors({

docker-compose.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,13 @@ services:
347347
- "3001:3000"
348348
restart: unless-stopped
349349

350+
loki:
351+
image: grafana/loki:latest
352+
container_name: loki
353+
ports:
354+
- "3100:3100"
355+
restart: unless-stopped
356+
350357
volumes:
351358
pgdata:
352359
redis-data:

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)