Skip to content

Commit

Permalink
Merge pull request #18 from fless-lab/feat/logging
Browse files Browse the repository at this point in the history
feat/logging
  • Loading branch information
fless-lab authored Jul 19, 2024
2 parents 1f2617a + 521e3cd commit 4973122
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env
node_modules
package-lock.json
package-lock.json
logs
16 changes: 12 additions & 4 deletions src/apps/shared/middlewares/bruteforce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RateLimiterMongo } from 'rate-limiter-flexible';
import { Request, Response, NextFunction } from 'express';
import config from '../../../config';
import { DB } from '../../../framework';
import { logger } from '../services';

let bruteForceLimiter: RateLimiterMongo | undefined;

Expand All @@ -17,9 +18,9 @@ const setupRateLimiter = async (): Promise<void> => {
blockDuration: Math.ceil(config.bruteForce.maxWait / 1000), // Durée de blocage en secondes
});

console.log('Rate limiter configured.');
logger.info('Rate limiter configured.');
} catch (error) {
console.error('Error setting up rate limiter:', error);
logger.error('Error setting up rate limiter', error as any);
}
};

Expand All @@ -31,6 +32,8 @@ const bruteForceMiddleware = async (
next: NextFunction,
): Promise<void> => {
if (!bruteForceLimiter) {
const error = new Error('Rate limiter not configured yet.');
logger.error(error.message, error);
res.status(500).json({
message: 'Rate limiter not configured yet. Please try again later.',
});
Expand All @@ -42,9 +45,14 @@ const bruteForceMiddleware = async (
next();
} catch (rejRes: any) {
const retrySecs = Math.ceil(rejRes.msBeforeNext / 1000) || 1;
!config.runningProd && res.set('Retry-After', String(retrySecs)); //Send Retry-After only in dev mode
if (!config.runningProd) {
res.set('Retry-After', String(retrySecs)); // Send Retry-After only in dev mode
}
logger.warn(
`<Bruteforce Suspected> Too many attempts from IP: ${req.ip}. Retry after ${retrySecs} seconds.`,
);
res.status(429).json({
message: `Too many attempts, please try again after ${Math.ceil(rejRes.msBeforeNext / 60000)} minutes.`,
message: `<Bruteforce Suspected> Too many attempts, please try again after ${Math.ceil(rejRes.msBeforeNext / 60000)} minutes.`,
});
}
};
Expand Down
8 changes: 8 additions & 0 deletions src/apps/shared/middlewares/client-authentication.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from 'express';
import config from '../../../config';
import { logger } from '../services';

export const clientAuthentication = (
req: Request,
Expand All @@ -9,6 +10,9 @@ export const clientAuthentication = (
const clientToken = req.headers['x-client-token'] as string;

if (!clientToken) {
logger.warn(
`Unauthorized access attempt from IP: ${req.ip} - No client token provided`,
);
return res.status(401).send('Unauthorized');
}

Expand All @@ -20,8 +24,12 @@ export const clientAuthentication = (
const validPass = config.basicAuthPass;

if (username === validUser && password === validPass) {
logger.info(`Client authenticated successfully from IP: ${req.ip}`);
return next();
} else {
logger.warn(
`Forbidden access attempt from IP: ${req.ip} - Invalid credentials`,
);
return res.status(403).send('Forbidden');
}
};
11 changes: 9 additions & 2 deletions src/apps/shared/middlewares/rate-limiter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import rateLimit from 'express-rate-limit';
import config from '../../../config';
import { logger } from '../services';

// TODO: Remove this later and use the one that's in helpers
const msToMinutes = (ms: number): number => {
Expand All @@ -9,6 +10,12 @@ const msToMinutes = (ms: number): number => {
export const apiRateLimiter = rateLimit({
windowMs: config.rate.limit, // Time window in milliseconds
max: config.rate.max, // Maximum number of requests
standardHeaders: !config.runningProd, //Show ratelimit headers when not in production
message: `Too many requests from this IP, please try again after ${msToMinutes(config.rate.limit)} minutes.`,
standardHeaders: !config.runningProd, // Show ratelimit headers when not in production
message: `<DDOS Suspected> Too many requests from this IP, please try again after ${msToMinutes(config.rate.limit)} minutes.`,
handler: (req, res) => {
logger.warn(`Too many requests from IP: ${req.ip}`);
res.status(429).json({
message: `<DDOS Suspected> Too many requests from this IP, please try again after ${msToMinutes(config.rate.limit)} minutes.`,
});
},
});
1 change: 1 addition & 0 deletions src/apps/shared/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './base.service';
export { default as JwtService } from './jwt.service';
export { default as ViewService } from './view.service';
export * from './mail';
export * from './logger.service';
17 changes: 9 additions & 8 deletions src/apps/shared/services/jwt.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import JWT, { SignOptions } from 'jsonwebtoken';
import config from '../../../config';
import { ApiResponse, ErrorResponse } from '../utils';
import { DB } from '../../../framework';
import { logger } from './logger.service';

const redis = DB.redis;

Expand Down Expand Up @@ -42,7 +43,7 @@ class JwtService {
options,
(err: any, token?: string) => {
if (err || !token) {
console.error(err?.message);
logger.error(err?.message, err);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand All @@ -59,7 +60,7 @@ class JwtService {
return new Promise((resolve, reject) => {
client.get(`bl_${token}`, (err: any, result: any) => {
if (err) {
console.error(err.message);
logger.error(err.message, err);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand Down Expand Up @@ -138,7 +139,7 @@ class JwtService {
options,
(err: any, token?: string) => {
if (err || !token) {
console.error(err?.message);
logger.error(err?.message, err);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand All @@ -153,7 +154,7 @@ class JwtService {
this.redisTokenExpireTime,
(redisErr: any) => {
if (redisErr) {
console.error(redisErr.message);
logger.error(redisErr.message, redisErr);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand Down Expand Up @@ -186,7 +187,7 @@ class JwtService {

client.get(userId, (redisErr: any, result: any) => {
if (redisErr) {
console.error(redisErr.message);
logger.error(redisErr.message, redisErr);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand Down Expand Up @@ -218,7 +219,7 @@ class JwtService {
this.redisBlacklistExpireTime,
(redisErr: any) => {
if (redisErr) {
console.error(redisErr.message);
logger.error(redisErr.message, redisErr);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand All @@ -235,7 +236,7 @@ class JwtService {
return new Promise((resolve, reject) => {
client.del(key, (redisErr: any) => {
if (redisErr) {
console.error(redisErr.message);
logger.error(redisErr.message, redisErr);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand Down Expand Up @@ -286,7 +287,7 @@ class JwtService {

client.get(userId, (redisErr: any, result: any) => {
if (redisErr) {
console.error(redisErr.message);
logger.error(redisErr.message, redisErr);
const errorResponse = new ErrorResponse(
'INTERNAL_SERVER_ERROR',
'Internal Server Error',
Expand Down
57 changes: 57 additions & 0 deletions src/apps/shared/services/logger.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createLogger, format, transports, Logger } from 'winston';
import { Format } from 'logform';

class LoggerService {
private logger: Logger;

constructor() {
const logFormat: Format = format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(
(info) => `[${info.timestamp}] (${info.level}): ${info.message}`,
),
);

this.logger = createLogger({
level: 'info',
format: logFormat,
transports: [
new transports.Console({
format: format.combine(format.colorize(), logFormat),
}),
new transports.File({ filename: 'logs/error.log', level: 'error' }),
new transports.File({ filename: 'logs/combined.log' }),
],
exceptionHandlers: [
new transports.File({ filename: 'logs/exceptions.log' }),
],
});

// Environments other than production
if (process.env.NODE_ENV !== 'production') {
this.logger.add(
new transports.Console({
format: format.combine(format.colorize(), logFormat),
}),
);
}
}

log(level: string, message: string, metadata?: Record<string, any>): void {
this.logger.log({ level, message, ...metadata });
}

info(message: string, metadata?: Record<string, any>): void {
this.logger.info(message, metadata);
}

warn(message: string, metadata?: Record<string, any>): void {
this.logger.warn(message, metadata);
}

error(message: string, error?: Error): void {
this.logger.error(message, { error: error?.stack || error });
}
}

export const logger = new LoggerService();
3 changes: 2 additions & 1 deletion src/apps/shared/services/mail/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import path from 'path';
import config from '../../../../config';
import { ErrorResponse } from '../../utils';
import { ErrorResponseType, SuccessResponseType } from '../../types';
import { logger } from '..';

class MailService {
private transporter: Transporter;
Expand Down Expand Up @@ -66,7 +67,7 @@ class MailService {
await this.transporter.sendMail(mailOptions);
return { success: true };
} catch (error) {
console.error('Error sending email:', error);
logger.error('Error sending email', error as Error);
return {
success: false,
error: new ErrorResponse(
Expand Down
5 changes: 3 additions & 2 deletions src/helpers/minio-test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { logger } from '../apps';
import { S3 } from '../framework';

async function testMinioConnection(): Promise<void> {
try {
const client = S3.minio.init();
// Example of checking MinIO server status by listing buckets
await client.listBuckets();
console.info('MinIO is successfully connected and working.');
logger.info('MinIO is successfully connected and working.');
} catch (error) {
console.error('MinIO connection error:', error);
logger.error('MinIO connection error:', error as Error);
throw error;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/helpers/redis-test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from '../apps';
import { DB } from '../framework';

async function testRedisConnection(): Promise<void> {
Expand All @@ -6,9 +7,9 @@ async function testRedisConnection(): Promise<void> {
redis.init();
const client = redis.getClient();
await client.ping();
console.info('Redis is successfully connected and working.');
logger.info('Redis is successfully connected and working.');
} catch (error) {
console.error('Redis connection error:', error);
logger.error('Redis connection error:', error as Error);
throw error;
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { initServices } from './helpers';
import config from './config';
import { WebServer } from './framework';
import { logger } from './apps';

async function startServer() {
try {
await initServices();
const app = WebServer.app;
app.listen(config.port, () => {
console.log(`Server running on http://localhost:${config.port}`);
logger.info(`Server running on http://localhost:${config.port}`);
});
} catch (error) {
console.error('Failed to initialize services:', error);
logger.error('Failed to initialize services', error as any);
}
}

Expand Down

0 comments on commit 4973122

Please sign in to comment.