Skip to content

Commit e2decc9

Browse files
committed
feat(logger): add Loki transport for Winston
1 parent cfb79d0 commit e2decc9

8 files changed

Lines changed: 381 additions & 40 deletions

File tree

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,6 @@ S3_ACCESS_KEY=''
4949
S3_SECRET_KEY=''
5050

5151
IMAGOR_SECRET=''
52-
IMAGOR_URL=''
52+
IMAGOR_URL=''
53+
54+
LOKI_HOST=''

libs/bootstrap/src/bootstrap.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Logger, VersioningType } from '@nestjs/common';
22
import { ConfigService } from '@nestjs/config';
33
import { NestFactory } from '@nestjs/core';
44
import { DEFAULT_THROTTLER_OPTIONS } from './configs/throttler';
5-
import { setupCors, setupLogger, setupThrottler, setupSwagger, LoggingInterceptor } from './setups';
5+
import { setupCors, setupLogger, setupThrottler, setupSwagger } from './setups';
66
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
77
import type { BootstrapOptions } from './interfaces/options.interface';
88
import fastifyCookie from '@fastify/cookie';
@@ -21,8 +21,6 @@ export async function bootstrapApp(options: BootstrapOptions) {
2121
},
2222
});
2323

24-
const winston = setupLogger(options.serviceName);
25-
2624
const {
2725
appModule,
2826
apiPrefix,
@@ -48,8 +46,6 @@ export async function bootstrapApp(options: BootstrapOptions) {
4846
bufferLogs: true,
4947
});
5048

51-
app.useLogger(winston);
52-
5349
const logger = new Logger(serviceName[0].toUpperCase() + serviceName.slice(1));
5450
const configService = app.get(ConfigService);
5551
const port = configService.getOrThrow<number>(portEnvKey, defaultPort);
@@ -64,7 +60,7 @@ export async function bootstrapApp(options: BootstrapOptions) {
6460
return payload;
6561
});
6662

67-
app.useGlobalInterceptors(new LoggingInterceptor());
63+
await setupLogger(app, options.serviceName);
6864

6965
await app.register(fastifyCompress, {
7066
global: true,

libs/bootstrap/src/setups/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export { setupCors } from './cors';
22
export { setupThrottler } from './throttler';
33
export { setupSwagger } from './swagger';
4-
export { setupLogger, LoggingInterceptor } from './logger';
4+
export { setupLogger } from './logger';

libs/bootstrap/src/setups/logger.ts

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,66 @@ import { Observable, throwError } from 'rxjs';
99
import { tap, catchError } from 'rxjs/operators';
1010
import type { FastifyRequest } from 'fastify';
1111
import { WinstonModule, utilities } from 'nest-winston';
12-
import { format, transports } from 'winston';
12+
import { format, transport, transports } from 'winston';
13+
import Loki from 'winston-loki';
14+
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
15+
import { ConfigService } from '@nestjs/config';
1316

14-
export function setupLogger(service: string) {
15-
const isProduction = process.env.NODE_ENV === 'production';
17+
export function setupLogger(app: NestFastifyApplication, service: string) {
18+
const cfg = app.get(ConfigService);
1619

17-
return WinstonModule.createLogger({
18-
level: isProduction ? 'info' : 'debug',
19-
transports: [
20-
new transports.Console({
21-
format: format.combine(
22-
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
23-
format.ms(),
24-
format.errors({ stack: true }),
25-
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'context'] }),
26-
format((info) => {
27-
const mask = (obj: any) => {
28-
const sensitive = ['password', 'token', 'secret', 'authorization'];
29-
for (const key in obj) {
30-
if (sensitive.includes(key.toLowerCase())) obj[key] = '***';
31-
else if (typeof obj[key] === 'object') mask(obj[key]);
32-
}
33-
};
34-
if (info.metadata) mask(info.metadata);
35-
return info;
36-
})(),
37-
38-
isProduction
39-
? format.json()
40-
: utilities.format.nestLike(service, {
41-
colors: true,
42-
prettyPrint: false,
43-
}),
44-
),
20+
const isProduction = cfg.get('NODE_ENV') === 'production';
21+
const loki = cfg.get('LOKI_HOST');
22+
23+
const transportsList: transport[] = [
24+
new transports.Console({
25+
format: format.combine(
26+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
27+
format.ms(),
28+
format.errors({ stack: true }),
29+
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'context'] }),
30+
format((info) => {
31+
const mask = (obj: any) => {
32+
const sensitive = ['password', 'token', 'secret', 'authorization'];
33+
for (const key in obj) {
34+
if (sensitive.includes(key.toLowerCase())) obj[key] = '***';
35+
else if (typeof obj[key] === 'object') mask(obj[key]);
36+
}
37+
};
38+
if (info.metadata) mask(info.metadata);
39+
return info;
40+
})(),
41+
42+
isProduction
43+
? format.json()
44+
: utilities.format.nestLike(service, {
45+
colors: true,
46+
prettyPrint: false,
47+
}),
48+
),
49+
}),
50+
];
51+
52+
if (loki) {
53+
transportsList.push(
54+
new Loki({
55+
host: loki,
56+
labels: { app: service },
57+
json: true,
58+
format: format.json(),
59+
replaceTimestamp: true,
60+
onConnectionError: (err) => console.error('Loki connection error:', err),
4561
}),
46-
],
62+
);
63+
}
64+
65+
const logger = WinstonModule.createLogger({
66+
level: isProduction ? 'info' : 'debug',
67+
transports: transportsList,
4768
});
69+
70+
app.useLogger(logger);
71+
app.useGlobalInterceptors(new LoggingInterceptor());
4872
}
4973

5074
@Injectable()

libs/config/src/config.schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export const ConfigSchema = z.object({
9191
S3_SECRET_KEY: z.string({
9292
error: 'S3_SECRET_KEY is missing (MinIO root password or IAM secret)',
9393
}),
94+
LOKI_HOST: z.string().url().min(1).optional(),
9495
});
9596

9697
export type Config = z.infer<typeof ConfigSchema>;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"transliteration": "^2.6.1",
8080
"ua-parser-js": "^2.0.9",
8181
"winston": "^3.19.0",
82+
"winston-loki": "^6.1.4",
8283
"zod": "^4.3.6"
8384
},
8485
"devDependencies": {

0 commit comments

Comments
 (0)