bootifyjs 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootifyjs",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Spring Boot inspired framework fro Node.js built on top of fastify and express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,355 +0,0 @@
1
- import Fastify, {
2
- FastifyInstance,
3
- FastifyPluginAsync,
4
- FastifyPluginCallback,
5
- FastifyServerOptions,
6
- } from 'fastify';
7
- import { registerControllers } from '../decorators/controller.decorator';
8
- import Logger from '../logger/Logger';
9
- import { requestContext } from '../middleware/requestcontext';
10
- import { httpLoggerMiddleware } from '../middleware/http';
11
-
12
- interface FastifyServerConfig {
13
- port?: number;
14
- host?: string;
15
- route?: FastifyPluginCallback | FastifyPluginAsync;
16
- enableCors?: boolean;
17
- enableSwagger?: boolean;
18
- enableLogging?: boolean;
19
- enableErrorHandling?: boolean;
20
- enableDatabase?: boolean;
21
- enableRedis?: boolean;
22
- corsOptions?: {
23
- origin?: string | string[];
24
- methods?: string[];
25
- credentials?: boolean;
26
- };
27
- swaggerOptions?: {
28
- routePrefix?: string;
29
- swaggerDoc?: any;
30
- };
31
- enableMetrics?: boolean;
32
- enableCookie?: boolean;
33
- enableAuth?: boolean;
34
- }
35
-
36
- export class Application {
37
- private server: FastifyInstance;
38
- private config: FastifyServerConfig;
39
-
40
- constructor(config: FastifyServerConfig = {}) {
41
- this.config = {
42
- port: 3000,
43
- host: '0.0.0.0',
44
- enableCors: true,
45
- enableSwagger: false,
46
- enableLogging: true,
47
- enableErrorHandling: true,
48
- enableDatabase: false,
49
- enableRedis: false,
50
- enableCookie: false,
51
- enableAuth: false,
52
- enableMetrics: false,
53
- corsOptions: {
54
- origin: [
55
- 'http://localhost:4000',
56
- 'http://localhost:3000',
57
- 'http://localhost:5173',
58
- ],
59
- methods: ['GET', 'PUT', 'PATCH', 'POST', 'DELETE'],
60
- credentials: true,
61
- },
62
- swaggerOptions: {
63
- routePrefix: '/docs',
64
- swaggerDoc: {
65
- info: {
66
- title: 'API Documentation',
67
- version: '1.0.0',
68
- },
69
- },
70
- },
71
- ...config,
72
- };
73
-
74
- this.server = Fastify({
75
- ajv: {
76
- customOptions: {
77
- strict: false,
78
- },
79
- },
80
- genReqId: (req) => {
81
- const requestId = req.headers['x-request-id'];
82
- return Array.isArray(requestId) ? requestId[0] : requestId || crypto.randomUUID();
83
- },
84
- });
85
-
86
- this.setupServer();
87
- }
88
-
89
- private async setupServer() {
90
- try {
91
- // Register CORS
92
- // if (this.config.enableCors) {
93
- // Logger.info('Registering CORS');
94
- // await this.server.register(require('@fastify/cors'), this.config.corsOptions);
95
- // }
96
-
97
- // Register Swagger
98
- // if (this.config.enableSwagger) {
99
- // await this.server.register(require('@fastify/swagger'), this.config.swaggerOptions?.swaggerDoc);
100
- // await this.server.register(require('@fastify/swagger-ui'), {
101
- // routePrefix: this.config.swaggerOptions?.routePrefix,
102
- // });
103
- // }
104
-
105
- // Register Cookie support
106
- // if (this.config.enableCookie) {
107
- // await this.server.register(require('@fastify/cookie'), {
108
- // hook: 'onRequest',
109
- // parseOptions: {},
110
- // });
111
- // }
112
-
113
- // Register Metrics
114
- // if (this.config.enableMetrics) {
115
- // await this.server.register(require('fastify-metrics'), {
116
- // endpoint: '/metrics',
117
- // });
118
- // }
119
-
120
- // Register error handling
121
- if (this.config.enableErrorHandling) {
122
- this.server.setErrorHandler(this.globalErrorHandler);
123
- }
124
-
125
- // Add common schemas
126
- this.addCommonSchemas();
127
-
128
- // Add logging hooks
129
- if (this.config.enableLogging) {
130
- this.setupLogging();
131
- }
132
-
133
- // Setup route logging
134
- this.setupRouteLogging();
135
-
136
- // Register custom routes
137
- if (this.config.route) {
138
- await this.server.register(this.config.route);
139
- }
140
-
141
- } catch (error) {
142
- Logger.error('Failed to setup server:', error as Error);
143
- throw error;
144
- }
145
- }
146
-
147
- private globalErrorHandler = (error: any, request: any, reply: any) => {
148
- Logger.error('Global error handler:', error);
149
-
150
- const statusCode = error.statusCode || 500;
151
- const message = error.message || 'Internal Server Error';
152
-
153
- reply.status(statusCode).send({
154
- statusCode,
155
- error: error.name || 'Error',
156
- message,
157
- });
158
- };
159
-
160
- private addCommonSchemas() {
161
- this.server.addSchema({
162
- $id: 'errorResponseSchema',
163
- type: 'object',
164
- description: 'Error Response',
165
- properties: {
166
- statusCode: { type: 'number' },
167
- error: { type: 'string' },
168
- message: { type: 'string' },
169
- },
170
- });
171
-
172
- this.server.addSchema({
173
- $id: 'messageResponseSchema',
174
- type: 'object',
175
- description: 'Message Response',
176
- properties: {
177
- message: { type: 'string' },
178
- },
179
- });
180
- }
181
-
182
- private setupLogging() {
183
- // requestContext.run
184
-
185
- this.server.addHook('preHandler', (request, reply, done) => {
186
- request.raw.id = request.id
187
- reply.header('x-request-id', request.id)
188
- httpLoggerMiddleware(request.raw, reply.raw)
189
- // console.log('Request ID:', request.id)
190
- // console.log('User ID:', request.user)
191
-
192
- requestContext.run(
193
- { requestId: request.id, userId: "pxpxpx", username: "priyadarship4@gmail.com" },
194
- () => {
195
- done()
196
- }
197
- )
198
- })
199
-
200
- // this.server.addHook('onRequest', async (request, reply) => {
201
- // const startTime = Date.now();
202
- // request.raw.startTime = startTime;
203
- // reply.header('x-request-id', request.id);
204
-
205
- // Logger.info({
206
- // requestId: request.id,
207
- // method: request.method,
208
- // url: request.url,
209
- // userAgent: request.headers['user-agent'],
210
- // ip: request.ip,
211
- // message: 'Incoming request'
212
- // });
213
- // });
214
-
215
- // this.server.addHook('onResponse', async (request, reply) => {
216
- // const duration = Date.now() - (request.raw.startTime || Date.now());
217
-
218
- // Logger.info({
219
- // requestId: request.id,
220
- // method: request.method,
221
- // url: request.url,
222
- // statusCode: reply.statusCode,
223
- // duration: `${duration}ms`,
224
- // message: 'Request completed'
225
- // });
226
- // });
227
- }
228
-
229
- private setupRouteLogging() {
230
- const routeOptions: any[] = [];
231
-
232
- this.server.addHook('onRoute', (routeOption) => {
233
- routeOptions.push(routeOption);
234
- });
235
-
236
- this.server.addHook('onReady', (done) => {
237
- this.printFastifyRoutes(routeOptions, { colors: true });
238
- done();
239
- });
240
- }
241
-
242
- private printFastifyRoutes(routeOptions: any[], opts: { colors: boolean }) {
243
- const { colors } = opts;
244
-
245
- if (routeOptions.length === 0) {
246
- return;
247
- }
248
-
249
- routeOptions.sort((a, b) => a.url.localeCompare(b.url));
250
-
251
- const logEntries = [];
252
- logEntries.push("Available routes:");
253
-
254
- for (const routeOption of routeOptions) {
255
- const { method, url } = routeOption;
256
- if (method === "HEAD") continue;
257
- const formattedMethod = colors ? this.colorMethod(method) : method;
258
- logEntries.push(`${formattedMethod}\t${url}`);
259
- }
260
-
261
- Logger.info(logEntries.join("\n"));
262
- }
263
-
264
- private colorMethod(method: string): string {
265
- const COLORS = {
266
- yellow: 33,
267
- green: 32,
268
- blue: 34,
269
- red: 31,
270
- grey: 90,
271
- clear: 39,
272
- };
273
-
274
- const colorText = (color: number, string: string): string => {
275
- return `\u001b[${color}m${string}\u001b[${COLORS.clear}m`;
276
- };
277
-
278
- switch (method) {
279
- case "POST": return colorText(COLORS.yellow, method);
280
- case "GET": return colorText(COLORS.green, method);
281
- case "PUT": return colorText(COLORS.blue, method);
282
- case "DELETE": return colorText(COLORS.red, method);
283
- case "PATCH": return colorText(COLORS.grey, method);
284
- default: return method;
285
- }
286
- }
287
-
288
- public registerControllers(controllers: Function[]) {
289
- registerControllers(this.server, controllers);
290
- }
291
-
292
- public async start(port?: number, host?: string) {
293
- const serverPort = port || this.config.port || 3000;
294
- const serverHost = host || this.config.host || '0.0.0.0';
295
-
296
- try {
297
- // Initialize Redis if enabled
298
- if (this.config.enableRedis) {
299
- Logger.info('Connecting to Redis...');
300
- // Add Redis initialization logic here
301
- Logger.info('Redis connected');
302
- }
303
-
304
- // Initialize Database if enabled
305
- if (this.config.enableDatabase) {
306
- Logger.info('Connecting to database...');
307
- // Add Database initialization logic here
308
- Logger.info('Database connected');
309
- }
310
-
311
- Logger.info(`Starting server at http://${serverHost}:${serverPort}`);
312
- await this.server.listen({ port: serverPort, host: serverHost });
313
- Logger.info(`Server listening on http://${serverHost}:${serverPort}`);
314
-
315
- // Setup graceful shutdown
316
- this.setupGracefulShutdown();
317
-
318
- } catch (err) {
319
- // Logger.error(err)
320
- Logger.error('Failed to start server:', err as Error);
321
- process.exit(1);
322
- }
323
- }
324
-
325
- private setupGracefulShutdown() {
326
- const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
327
-
328
- signals.forEach((signal) => {
329
- process.on(signal, async () => {
330
- try {
331
- await this.server.close();
332
- Logger.info(`Closed application on ${signal}`);
333
- process.exit(0);
334
- } catch (err) {
335
- Logger.error(`Error closing application on ${signal}`, err as Error);
336
- process.exit(1);
337
- }
338
- });
339
- });
340
-
341
- process.on('unhandledRejection', (err: Error) => {
342
- // Logger.error(err)
343
- Logger.error('Unhandled Rejection:', err as Error);
344
- process.exit(1);
345
- });
346
- }
347
-
348
- public getServer(): FastifyInstance {
349
- return this.server;
350
- }
351
-
352
- public updateConfig(newConfig: Partial<FastifyServerConfig>) {
353
- this.config = { ...this.config, ...newConfig };
354
- }
355
- }
@@ -1,28 +0,0 @@
1
- import { Container, injectable } from "inversify";
2
- const container = new Container();
3
-
4
- interface BeanOptions {
5
- scope?: 'singleton' | 'transient';
6
- bindTo?: Function[]; // Array of interfaces to bind to
7
- }
8
- export function Bean(options: BeanOptions) {
9
- return function (target: any) {
10
- const { scope = 'singleton', bindTo = [] } = options || {};
11
-
12
- injectable()(target);
13
-
14
- // Bind to self (class constructor)
15
- const binding = container.bind(target).toSelf();
16
- scope === 'singleton' ? binding.inSingletonScope() : binding.inTransientScope();
17
-
18
- // Bind to each specified interface
19
- bindTo.forEach(intrfc => {
20
- const interfaceBinding = container.bind(intrfc).to(target);
21
- scope === 'singleton'
22
- ? interfaceBinding.inSingletonScope()
23
- : interfaceBinding.inTransientScope();
24
- });
25
- };
26
- }
27
-
28
- export default container;
@@ -1,5 +0,0 @@
1
- export interface ICacheClient {
2
- get(key: string): Promise<string | null>;
3
- set(key: string, value: string, ttlSeconds?: number): Promise<void>;
4
- del(key: string): Promise<void>;
5
- }
@@ -1,36 +0,0 @@
1
- import container from '../../container/container';
2
- import { ICacheClient } from './ICacheClient';
3
-
4
- export function Cache(ttlSeconds: number = 60, keyGenerator?: (...args: any[]) => string): MethodDecorator {
5
- return (target, propertyKey, descriptor) => {
6
- const originalMethod = descriptor.value as (...args: any[]) => any;
7
- const className = target.constructor.name;
8
- const methodName = String(propertyKey);
9
-
10
- descriptor.value = async function (this: any, ...args: any[]) {
11
- const cacheClient = container.get<ICacheClient>('CacheClient');
12
- const cacheKey = keyGenerator
13
- ? keyGenerator(...args)
14
- : `cache:${className}:${methodName}:${JSON.stringify(args)}`;
15
-
16
- try {
17
- const cached = await cacheClient.get(cacheKey);
18
- if (cached) return JSON.parse(cached);
19
- } catch (err) {
20
- console.error('Cache read error:', err);
21
- }
22
-
23
- const result = await originalMethod.apply(this, args);
24
-
25
- try {
26
- await cacheClient.set(cacheKey, JSON.stringify(result), ttlSeconds);
27
- } catch (err) {
28
- console.error('Cache write error:', err);
29
- }
30
-
31
- return result;
32
- } as any;
33
-
34
- return descriptor;
35
- };
36
- }
@@ -1,119 +0,0 @@
1
- import 'reflect-metadata';
2
- import { FastifyInstance, FastifyRequest, FastifyReply, RouteOptions } from 'fastify';
3
- import container from '../container/container';
4
- import Logger from '../logger/Logger';
5
-
6
- const CONTROLLER_METADATA = 'controller:metadata';
7
- export const ROUTE_METADATA = 'route:metadata';
8
-
9
- type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
10
-
11
- interface RouteDefinition {
12
- path: string;
13
- method: HttpMethod;
14
- handlerName: string;
15
- options?: Partial<RouteOptions>;
16
- }
17
-
18
- export function Controller(prefix: string = ''): ClassDecorator {
19
- return (target: Function) => {
20
- Reflect.defineMetadata(CONTROLLER_METADATA, { prefix }, target);
21
- };
22
- }
23
-
24
- export function registerControllers(fastify: FastifyInstance, controllers: Function[]) {
25
- const routeOptions: any[] = [];
26
-
27
- const fastifyWithHooks = fastify as unknown as {
28
- addHook: (hookName: string, handler: (...args: any[]) => void) => void;
29
- };
30
-
31
- fastifyWithHooks.addHook("onRoute", (routeOption) => {
32
- routeOptions.push(routeOption);
33
- });
34
-
35
- fastifyWithHooks.addHook("onReady", (done) => {
36
- printFastifyRoutes(routeOptions, { colors: true });
37
- done();
38
- });
39
-
40
- controllers.forEach(Controller => {
41
- const controllerMetadata = Reflect.getMetadata(CONTROLLER_METADATA, Controller);
42
- const routes: RouteDefinition[] = Reflect.getMetadata(ROUTE_METADATA, Controller) || [];
43
- const controllerInstance = container.get(Controller) as Record<string, (request: FastifyRequest, reply: FastifyReply) => Promise<any>>;
44
-
45
- const prefix = controllerMetadata?.prefix || '';
46
-
47
- routes.forEach(route => {
48
- const fullPath = `${prefix}${route.path}`.replace(/\/\//g, '/');
49
-
50
- const handler = async (request: FastifyRequest, reply: FastifyReply) => {
51
- try {
52
- const result = await controllerInstance[route.handlerName](request, reply);
53
- if (result !== undefined) return result;
54
- } catch (error: any) {
55
- Logger.error('Controller error:', error);
56
- const statusCode = error.statusCode || 500;
57
- const message = error.message || 'Internal Server Error';
58
- reply.status(statusCode).send({ error: message });
59
- }
60
- };
61
-
62
- const routeConfig = {
63
- ...route.options,
64
- handler,
65
- url: fullPath,
66
- method: route.method
67
- };
68
-
69
- (fastify as any).route(routeConfig);
70
- });
71
- });
72
- }
73
-
74
- export function printFastifyRoutes(routeOptions: any[], opts: { colors: boolean }) {
75
- const { colors } = opts;
76
-
77
- if (routeOptions.length === 0) {
78
- return;
79
- }
80
-
81
- routeOptions.sort((a, b) => a.url.localeCompare(b.url));
82
-
83
- const logEntries = [];
84
- logEntries.push("Available routes:");
85
-
86
- for (const routeOption of routeOptions) {
87
- const { method, url } = routeOption;
88
- if (method === "HEAD") continue;
89
- const formattedMethod = colors ? colorMethod(method) : method;
90
- logEntries.push(`${formattedMethod}\t${url}`);
91
- }
92
-
93
- Logger.info(logEntries.join("\n"));
94
- }
95
-
96
- const COLORS = {
97
- yellow: 33,
98
- green: 32,
99
- blue: 34,
100
- red: 31,
101
- grey: 90,
102
- magenta: 35,
103
- clear: 39,
104
- };
105
-
106
- function colorText(color: number, string: string): string {
107
- return `\u001b[${color}m${string}\u001b[${COLORS.clear}m`;
108
- }
109
-
110
- function colorMethod(method: string): string {
111
- switch (method) {
112
- case "POST": return colorText(COLORS.yellow, method);
113
- case "GET": return colorText(COLORS.green, method);
114
- case "PUT": return colorText(COLORS.blue, method);
115
- case "DELETE": return colorText(COLORS.red, method);
116
- case "PATCH": return colorText(COLORS.grey, method);
117
- default: return method;
118
- }
119
- }
@@ -1,25 +0,0 @@
1
- import { ROUTE_METADATA } from './controller.decorator';
2
-
3
- export function createMethodDecorator(method: string) {
4
- return (path: string = '', options?: any): MethodDecorator => {
5
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
6
- const routes: any[] = Reflect.getMetadata(ROUTE_METADATA, target.constructor) || [];
7
-
8
- routes.push({
9
- path,
10
- method: method.toUpperCase(),
11
- handlerName: propertyKey,
12
- options
13
- });
14
-
15
- Reflect.defineMetadata(ROUTE_METADATA, routes, target.constructor);
16
- return descriptor;
17
- };
18
- };
19
- }
20
-
21
- export const Get = createMethodDecorator('GET');
22
- export const Post = createMethodDecorator('POST');
23
- export const Put = createMethodDecorator('PUT');
24
- export const Delete = createMethodDecorator('DELETE');
25
- export const Patch = createMethodDecorator('PATCH');
@@ -1,23 +0,0 @@
1
- export function Timed(): MethodDecorator {
2
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
3
- const originalMethod = descriptor.value;
4
-
5
- descriptor.value = async function (...args: any[]) {
6
- const start = process.hrtime.bigint();
7
- try {
8
- const result = await originalMethod.apply(this, args);
9
- const end = process.hrtime.bigint();
10
- const duration = Number(end - start) / 1_000_000;
11
- console.log(`[TIMED] ${target.constructor.name}.${String(propertyKey)} took ${duration.toFixed(2)}ms`);
12
- return result;
13
- } catch (error) {
14
- const end = process.hrtime.bigint();
15
- const duration = Number(end - start) / 1_000_000;
16
- console.error(`[TIMED-ERROR] ${target.constructor.name}.${String(propertyKey)} failed after ${duration.toFixed(2)}ms`, error);
17
- throw error;
18
- }
19
- };
20
-
21
- return descriptor;
22
- };
23
- }
@@ -1,9 +0,0 @@
1
- export interface ITransaction {
2
- transaction: any;
3
- commit: () => Promise<void>;
4
- rollback: () => Promise<void>;
5
- }
6
-
7
- export interface IDatabaseClient {
8
- startTransaction(): Promise<ITransaction>;
9
- }
@@ -1,24 +0,0 @@
1
- import container from '../../container/container';
2
- import { IDatabaseClient } from './IDatabaseClient';
3
-
4
- export function Transactional(): MethodDecorator {
5
- return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
6
- const originalMethod = descriptor.value;
7
-
8
- descriptor.value = async function (...args: any[]) {
9
- const dbClient = container.get<IDatabaseClient>('DatabaseClient');
10
- const transaction = await dbClient.startTransaction();
11
-
12
- try {
13
- const result = await originalMethod.apply(this, [...args, transaction.transaction]);
14
- await transaction.commit();
15
- return result;
16
- } catch (error) {
17
- await transaction.rollback();
18
- throw error;
19
- }
20
- };
21
-
22
- return descriptor;
23
- };
24
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- export * from './application/Application';
2
- export { default as container, Bean } from './container/container';
3
- export * from './decorators/controller.decorator';
4
- export * from './decorators/http.decorator';
5
- export * from './decorators/timing.decorator';
6
- export * from './decorators/cache/cache.decorator';
7
- export * from './decorators/cache/ICacheClient';
8
- export * from './decorators/transaction/transactional.decorator';
9
- export * from './decorators/transaction/IDatabaseClient';
10
- export { default as logger, } from './logger/Logger';
@@ -1,179 +0,0 @@
1
- import pino from 'pino'
2
- import { LogContext, requestContext } from '../middleware/requestcontext'
3
- // import { requestContext, LogContext } from '../middleware/requestcontext'
4
-
5
- // --- Define interfaces for your specific log types ---
6
- interface AppLogPayload {
7
- // For general application messages
8
- message: string
9
- [key: string]: any // Allow other custom fields
10
- }
11
-
12
- interface AuditLogPayload {
13
- // Based on your AuditLog type
14
- action: string
15
- resources: string[]
16
- details: any // Renamed from 'data' to avoid conflict if 'data' is a generic field
17
- [key: string]: any
18
- }
19
-
20
- interface EventLogPayload {
21
- // Based on your EventLog type
22
- eventName: string
23
- status: 'success' | 'failure' | 'pending'
24
- metadata: any
25
- [key: string]: any
26
- }
27
-
28
- // (HttpLogPayload is implicitly handled by pino-http)
29
-
30
- class Logger {
31
- private pinoInstance: pino.Logger
32
- private static instance: Logger
33
-
34
- private constructor() {
35
- const basePinoOptions: pino.LoggerOptions = {
36
- level: process.env.LOG_LEVEL || 'info',
37
- timestamp: () => `,"time":"${new Date().toISOString()}"`,
38
- messageKey: 'message', // Consistent message key
39
- base: {
40
- // Remove default pino fields if not needed
41
- pid: undefined,
42
- hostname: undefined,
43
- },
44
- formatters: {
45
- level: (label: string) => ({ level: label.toLowerCase() }), // Ensure lowercase level
46
- },
47
- mixin: () => {
48
- const store = requestContext.getStore()
49
- return {
50
- // Common context fields from AsyncLocalStorage
51
- requestId: store?.requestId,
52
- userId: store?.userId, // This might be overridden by specific log methods like audit
53
- traceId: store?.traceId,
54
- username: store?.username,
55
- // 'additionalContext' from ALS will be merged into the 'context' field for ClickHouse by the transport
56
- // context: store?.additionalContext || {},
57
- }
58
- },
59
- }
60
-
61
- const transports = []
62
- // const clickHouseTransportPath = path.resolve(__dirname, 'pino-clickhouse-transport.js')
63
-
64
- transports.push({
65
- level: 'info', // Minimum level for ClickHouse transport
66
- // target: clickHouseTransportPath,
67
- options: {
68
- url: process.env.CLICKHOUSE_URL || 'http://localhost:8123',
69
- username: process.env.CLICKHOUSE_USER || 'default',
70
- password: process.env.CLICKHOUSE_PASSWORD || '',
71
- database: process.env.CLICKHOUSE_DB || 'default',
72
- maxBatchSize: parseInt(process.env.CLICKHOUSE_MAX_BATCH_SIZE || '1000', 10),
73
- flushInterval: parseInt(process.env.CLICKHOUSE_FLUSH_INTERVAL || '3000', 10),
74
- application: process.env.APP_NAME || 'my-application',
75
- },
76
- })
77
-
78
- if (process.env.NODE_ENV !== 'production') {
79
- transports.push({
80
- level: 'debug',
81
- target: 'pino-pretty',
82
- options: {
83
- colorize: true,
84
- translateTime: 'SYS:standard',
85
- ignore: 'pid,hostname,context,username,requestId,userId,traceId,application',
86
- messageKey: 'message', // Ensure pino-pretty uses the same messageKey
87
- },
88
- })
89
- }
90
-
91
- this.pinoInstance = pino({
92
- ...basePinoOptions,
93
- // transport: transports.length > 0 ? { targets: transports } : undefined,
94
- })
95
- }
96
-
97
- public static getInstance(): Logger {
98
- if (!Logger.instance) {
99
- try {
100
- Logger.instance = new Logger()
101
- } catch (error: any) {
102
- console.error('CRITICAL: Error creating logger instance:', error.message)
103
- // Fallback to a very basic console logger if main setup fails
104
- const fallbackPino = pino({ level: 'info', messageKey: 'message' })
105
- fallbackPino.error(
106
- { err: error },
107
- 'Fallback console logger activated due to error in primary logger setup.'
108
- )
109
- const fallbackInstance = Object.create(Logger.prototype) as Logger
110
- fallbackInstance.pinoInstance = fallbackPino
111
- Logger.instance = fallbackInstance
112
- }
113
- }
114
- return Logger.instance
115
- }
116
-
117
- /**
118
- * Provides access to the raw pino instance, primarily for use with pino-http.
119
- */
120
- public getPinoInstance(): pino.Logger {
121
- return this.pinoInstance
122
- }
123
-
124
- // --- Application Logging Methods ---
125
- private appLog(level: pino.Level, payload: AppLogPayload): void {
126
- this.pinoInstance[level]({ ...payload, logType: 'application' })
127
- }
128
- public info(message: string, data?: Record<string, any>): void {
129
- this.appLog('info', { message, ...data })
130
- }
131
- public error(message: string, error?: Error, data?: Record<string, any>): void {
132
- const payload: AppLogPayload = { message, ...data }
133
- if (error) {
134
- payload.err = error // pino stdSerializers.err will handle this
135
- }
136
- this.appLog('error', payload)
137
- }
138
- public warn(message: string, data?: Record<string, any>): void {
139
- this.appLog('warn', { message, ...data })
140
- }
141
- public debug(message: string, data?: Record<string, any>): void {
142
- this.appLog('debug', { message, ...data })
143
- }
144
- public fatal(message: string, error?: Error, data?: Record<string, any>): void {
145
- const payload: AppLogPayload = { message, ...data }
146
- if (error) {
147
- payload.err = error
148
- }
149
- this.appLog('fatal', payload)
150
- }
151
- public trace(message: string, data?: Record<string, any>): void {
152
- this.appLog('trace', { message, ...data })
153
- }
154
-
155
- // --- Audit Logging Method ---
156
- public audit(payload: AuditLogPayload): void {
157
- // userId from payload will override userId from mixin if both exist
158
- this.pinoInstance.info({ ...payload, logType: 'audit' })
159
- }
160
-
161
- // --- Event Logging Method ---
162
- public event(payload: EventLogPayload): void {
163
- this.pinoInstance.info({ ...payload, logType: 'event' })
164
- }
165
-
166
- // --- Child Logger ---
167
- public child(bindings: Partial<LogContext> & Record<string, any> = {}): Logger {
168
- // Create a new pino child logger instance with additional static bindings
169
- const pinoChild = this.pinoInstance.child(bindings)
170
- // Create a new Logger wrapper for this pino child
171
- const childLoggerInstance = Object.create(Logger.prototype) as Logger
172
- childLoggerInstance.pinoInstance = pinoChild
173
- return childLoggerInstance
174
- }
175
- }
176
-
177
- const logger = Logger.getInstance()
178
-
179
- export default logger
@@ -1,81 +0,0 @@
1
- import PinoHttp from 'pino-http'
2
- import pino from 'pino'
3
- import logger from '../logger/Logger'
4
- export const httpLoggerMiddleware = PinoHttp({
5
- logger: logger.getPinoInstance(), // Use the main pino instance
6
-
7
- customLogLevel: (req, res, err) => {
8
- if (res.statusCode >= 500 || err) return 'error'
9
- if (res.statusCode >= 400) return 'warn'
10
- return 'info'
11
- },
12
- serializers: {
13
- req: (req) => ({
14
- id: req.id, // Made available as objFromPinoHttp.req.id
15
- method: req.method,
16
- url: req.url,
17
- remoteAddress: req.remoteAddress || req.socket?.remoteAddress,
18
- }),
19
- res: (res) => ({
20
- statusCode: res.statusCode,
21
- }),
22
- err: pino.stdSerializers.err,
23
- },
24
-
25
- // This formatter *must* return the complete, flat object you want to log.
26
- formatters: {
27
- log: (objFromPinoHttp: any) => {
28
- // objFromPinoHttp contains:
29
- // - .req, .res, .err (from serializers above)
30
- // - .responseTime (number)
31
- // - .level (string, from customLogLevel)
32
- // - .message (string, from customSuccessMessage/customErrorMessage)
33
- // - properties from `customProps` (e.g., .requestId from req.id fallback)
34
- // - IMPORTANT: It ALSO contains properties from the parent `appLogger`'s `mixin`
35
- // because pino-http uses the parent logger, and its mixin will have run.
36
-
37
- const finalHttpLog: Record<string, any> = {
38
- // Standard fields (time will be added by parent pino instance when it actually logs)
39
- level: 'hello',
40
- test: '123',
41
- message: objFromPinoHttp.message,
42
-
43
- // Contextual fields (these should be present in objFromPinoHttp thanks to parent logger's mixin)
44
- requestId: objFromPinoHttp.requestId, // This should be the definitive one after mixin
45
- userId: objFromPinoHttp.userId,
46
- username: objFromPinoHttp.username,
47
- traceId: objFromPinoHttp.traceId,
48
- context: objFromPinoHttp.context, // from ALS additionalContext
49
- application: objFromPinoHttp.application, // from parent logger or transport default
50
-
51
- // HTTP specific data, flattened:
52
- logType: 'http',
53
- method: objFromPinoHttp.req?.method,
54
- url: objFromPinoHttp.req?.url,
55
- remoteAddress: objFromPinoHttp.req?.remoteAddress,
56
- statusCode: objFromPinoHttp.res?.statusCode,
57
- responseTimeMs: objFromPinoHttp.responseTime,
58
- // If you need req.id specifically, and it's different from the main requestId:
59
- // originalHttpRequestId: objFromPinoHttp.req?.id,
60
-
61
- // Error object (already serialized)
62
- err: objFromPinoHttp.err,
63
- }
64
-
65
- // Clean up any top-level undefined properties from the explicitly built object
66
- Object.keys(finalHttpLog).forEach((key) => {
67
- if (finalHttpLog[key] === undefined) {
68
- delete (finalHttpLog as any)[key]
69
- }
70
- })
71
-
72
- // DEBUG: Log what this formatter is about to return
73
- // console.log('--- pino-http formatters.log IS RETURNING: ---', JSON.stringify(finalHttpLog, null, 2));
74
-
75
- return finalHttpLog
76
- },
77
- },
78
-
79
- customSuccessMessage: (req, res) => `${req.method} ${req.url} request_completed`,
80
- customErrorMessage: (req, res, err) => `${req.method} ${req.url} request_errored`,
81
- })
@@ -1,12 +0,0 @@
1
- import { AsyncLocalStorage } from 'async_hooks'
2
- import { FastifyRequest } from 'fastify'
3
-
4
- export interface LogContext {
5
- requestId: string
6
- userId?: string
7
- traceId?: string
8
- username?: string
9
- additionalContext?: { [key: string]: any } // Additional context data
10
- }
11
-
12
- export const requestContext = new AsyncLocalStorage<LogContext>()