Skip to content

Commit

Permalink
feat(logger): integrate with discord webhook
Browse files Browse the repository at this point in the history
  • Loading branch information
phucvinh57 committed Nov 21, 2023
1 parent fe416ca commit 260e991
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 58 deletions.
10 changes: 1 addition & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,4 @@ CORS_WHITE_LIST=http://localhost:3000,http://localhost:8080
COOKIE_SECRET=
JWT_SECRET=

# environment variables for elasticsearch and kibana docker container
ELASTIC_PASSWORD=abcdef
KIBANA_PASSWORD=abcdef
STACK_VERSION=8.7.0
CLUSTER_NAME=docker-cluster
LICENSE=basic
ES_PORT=9200
KIBANA_PORT=5601
MEM_LIMIT=2073741824 # in bytes
DISCORD_WEBHOOK_URL=
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
"@prisma/client": "^5.1.1",
"@sinclair/typebox": "^0.31.1",
"bcrypt": "^5.1.0",
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"envalid": "^7.3.1",
"fastify": "^4.21.0",
"jsonwebtoken": "^9.0.1",
"moment-timezone": "^0.5.43",
"prisma": "^5.1.1"
},
"devDependencies": {
Expand Down
5 changes: 3 additions & 2 deletions src/Server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import fastify, { FastifyInstance } from 'fastify';
import type { FastifyCookieOptions } from '@fastify/cookie';
import { CORS_WHITE_LIST, envs, loggerConfig, swaggerConfig, swaggerUIConfig } from '@configs';
import { CORS_WHITE_LIST, envs, swaggerConfig, swaggerUIConfig } from '@configs';
import { apiPlugin, authPlugin } from './routes';
import { customErrorHandler } from '@handlers';
import { logger } from '@utils';

export function createServer(config: ServerConfig): FastifyInstance {
const app = fastify({ logger: loggerConfig[envs.NODE_ENV] });
const app = fastify({ logger });

app.register(import('@fastify/sensible'));
app.register(import('@fastify/helmet'));
Expand Down
3 changes: 2 additions & 1 deletion src/configs/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const envs = cleanEnv(process.env, {
}),
JWT_SECRET: str(),
COOKIE_SECRET: str(),
CORS_WHITE_LIST: str()
CORS_WHITE_LIST: str(),
DISCORD_WEBHOOK_URL: str()
});

export const CORS_WHITE_LIST = envs.CORS_WHITE_LIST.split(',');
2 changes: 1 addition & 1 deletion src/configs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
*/

export * from './env';
export * from './logger';
export * from './swagger';
export * from './logger/discord';
43 changes: 0 additions & 43 deletions src/configs/logger.ts

This file was deleted.

62 changes: 62 additions & 0 deletions src/configs/logger/discord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { envs } from '@configs/env';
import { WebhookClient } from 'discord.js';
import moment from 'moment-timezone';
import build from 'pino-abstract-transport';

export default function (options: DiscordLogOptions) {
const threadId = options.threadId;

const webhookClient = new WebhookClient({
url: options.webhookUrl
});

const fields = options.ignore ? options.ignore.split(',') : null;
const ignoreFields = fields && fields.length > 0 ? fields.map((field) => field.trim()) : null;

return build(async function (source) {
for await (const obj of source) {
const level = obj.level;
let levelName: LogLevel;
if (level >= 60) levelName = 'FATAL';
else if (level >= 50) levelName = 'ERROR';
else if (level >= 40) levelName = 'WARN';
else levelName = 'INFO';

if (ignoreFields) ignoreFields.forEach((field) => delete obj[field]);
const time = moment.tz('Asia/Ho_Chi_Minh').format('HH:mm:ss DD/MM/YYYY');
const payload = JSON.stringify(obj, null, 2);

const header = `🪲 [**${envs.NODE_ENV.toUpperCase()}**] [${levelName}] [${time}] 🪲\n`;
const beginBody = '```bash\n';
const endBody = '\n```';
if (payload.length > 1800) {
// Split message
const subMessages: string[] = [];
let i = 0;
while (i * 1800 < payload.length) {
subMessages.push(payload.substring(i * 1800, (i + 1) * 1800));
i++;
}
for (const message of subMessages) {
try {
await webhookClient.send({
threadId,
content: header + beginBody + message + endBody
});
} catch (e) {
// Do nothing
}
}
} else {
webhookClient
.send({
threadId,
content: header + beginBody + payload + endBody
})
.catch(() => {
// Do nothing
});
}
}
});
}
7 changes: 7 additions & 0 deletions src/types/log.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type LogLevel = 'INFO' | 'WARN' | 'DEBUG' | 'ERROR' | 'FATAL';

type DiscordLogOptions = {
webhookUrl: string;
threadId?: string;
ignore?: string;
};
51 changes: 50 additions & 1 deletion src/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,53 @@
import { envs, loggerConfig } from '@configs';
import { FastifyError } from 'fastify';
import { PinoLoggerOptions } from 'fastify/types/logger';
import { envs } from '@configs';
import pino from 'pino';

const errorSerialize = (err: FastifyError) => {
const isInternalServerError = !err.statusCode || err.statusCode === 500;
return {
type: err.name,
stack: isInternalServerError && err.stack ? err.stack : 'null',
message: err.message,
statusCode: err.statusCode
};
};

const fileLogTargets = ['info', 'warn', 'error', 'fatal'].map((logLevel) => ({
target: 'pino/file',
level: logLevel,
options: {
destination: process.cwd() + `/logs/${logLevel}.log`,
mkdir: true
}
}));
const pinoLogTarget = {
target: 'pino-pretty',
level: 'info',
options: {
translateTime: 'dd/mm/yy HH:MM:ss',
ignore: 'pid,hostname'
}
};
const discordLogTarget = {
target: '../configs/logger/discord',
level: 'warn',
options: {
webhookUrl: envs.DISCORD_WEBHOOK_URL,
ignore: 'time, pid, hostname'
} as DiscordLogOptions
};

const loggerConfig: Record<NodeEnv, PinoLoggerOptions> = {
development: {
transport: { targets: [pinoLogTarget, discordLogTarget] },
serializers: { err: errorSerialize }
},
production: {
transport: { targets: [discordLogTarget, ...fileLogTargets] },
serializers: { err: errorSerialize }
},
test: { serializers: { err: errorSerialize } }
};

export const logger = pino(loggerConfig[envs.NODE_ENV]);
Loading

0 comments on commit 260e991

Please sign in to comment.