generated from nhcarrigan/template
feat: multiple improvements to library functionality #50
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @copyright 2026 NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FastifyInstance, FastifyRequest } from 'fastify';
|
||||||
|
import { logger } from '../../utils/logger';
|
||||||
|
|
||||||
|
interface LogBody {
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
message: string;
|
||||||
|
context?: string;
|
||||||
|
error?: {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
stack?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function (fastify: FastifyInstance) {
|
||||||
|
fastify.post('/log', async function (request: FastifyRequest<{ Body: LogBody }>) {
|
||||||
|
const { level, message, context, error } = request.body;
|
||||||
|
|
||||||
|
if (level === 'error' && error) {
|
||||||
|
const errorObj = new Error(error.message);
|
||||||
|
errorObj.name = error.name;
|
||||||
|
if (error.stack) {
|
||||||
|
errorObj.stack = error.stack;
|
||||||
|
}
|
||||||
|
await logger.error(context || 'Frontend', errorObj);
|
||||||
|
} else if (level === 'error') {
|
||||||
|
await logger.log('warn', `[Frontend Error] ${message}`);
|
||||||
|
} else {
|
||||||
|
await logger.log(level, `[Frontend] ${message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @copyright 2026 NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Logger } from "@nhcarrigan/logger";
|
||||||
|
|
||||||
|
export const logger = new Logger("Library", process.env.LOG_TOKEN ?? "");
|
||||||
+34
-1
@@ -1,9 +1,42 @@
|
|||||||
import Fastify from 'fastify';
|
import Fastify from 'fastify';
|
||||||
import { app } from './app/app';
|
import { app } from './app/app';
|
||||||
|
import { logger } from './app/utils/logger';
|
||||||
|
|
||||||
const host = process.env.HOST ?? 'localhost';
|
const host = process.env.HOST ?? 'localhost';
|
||||||
const port = process.env.PORT ? Number(process.env.PORT) : 12321;
|
const port = process.env.PORT ? Number(process.env.PORT) : 12321;
|
||||||
|
|
||||||
|
// Global error handlers
|
||||||
|
process.on('uncaughtException', (error: Error) => {
|
||||||
|
void logger.error('Uncaught Exception', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason: unknown) => {
|
||||||
|
const error = reason instanceof Error ? reason : new Error(String(reason));
|
||||||
|
void logger.error('Unhandled Rejection', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('warning', (warning: Error) => {
|
||||||
|
void logger.log('warn', `Process Warning: ${warning.name} - ${warning.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
void logger.log('info', 'SIGTERM signal received: closing HTTP server');
|
||||||
|
server.close(() => {
|
||||||
|
void logger.log('info', 'HTTP server closed');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
void logger.log('info', 'SIGINT signal received: closing HTTP server');
|
||||||
|
server.close(() => {
|
||||||
|
void logger.log('info', 'HTTP server closed');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Instantiate Fastify with some config
|
// Instantiate Fastify with some config
|
||||||
const server = Fastify({
|
const server = Fastify({
|
||||||
logger: true,
|
logger: true,
|
||||||
@@ -19,6 +52,6 @@ server.listen({ port, host }, (err) => {
|
|||||||
server.log.error(err);
|
server.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.log(`[ ready ] http://${host}:${port}`);
|
void logger.log('info', `Server ready at http://${host}:${port}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { AuthService } from './services/auth.service';
|
|||||||
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
import { AuthInterceptor } from './interceptors/auth.interceptor';
|
||||||
import { initializeAuth } from './initializers/auth.initializer';
|
import { initializeAuth } from './initializers/auth.initializer';
|
||||||
import { GlobalErrorHandler } from './services/global-error-handler.service';
|
import { GlobalErrorHandler } from './services/global-error-handler.service';
|
||||||
|
import { ConsoleLoggerService } from './services/console-logger.service';
|
||||||
|
import { initializeConsoleLogger } from './initializers/console-logger.initializer';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
@@ -31,6 +33,12 @@ export const appConfig: ApplicationConfig = {
|
|||||||
useFactory: initializeAuth,
|
useFactory: initializeAuth,
|
||||||
deps: [AuthService],
|
deps: [AuthService],
|
||||||
multi: true
|
multi: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: initializeConsoleLogger,
|
||||||
|
deps: [ConsoleLoggerService],
|
||||||
|
multi: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @copyright 2026 NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ConsoleLoggerService } from '../services/console-logger.service';
|
||||||
|
|
||||||
|
export function initializeConsoleLogger(consoleLogger: ConsoleLoggerService) {
|
||||||
|
return () => {
|
||||||
|
consoleLogger.initialise();
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* @copyright 2026 NHCarrigan
|
||||||
|
* @license Naomi's Public License
|
||||||
|
* @author Naomi Carrigan
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Injectable, inject } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
interface LogPayload {
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error';
|
||||||
|
message: string;
|
||||||
|
context?: string;
|
||||||
|
error?: {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
stack?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ConsoleLoggerService {
|
||||||
|
private http = inject(HttpClient);
|
||||||
|
private originalConsole = {
|
||||||
|
log: console.log.bind(console),
|
||||||
|
error: console.error.bind(console),
|
||||||
|
warn: console.warn.bind(console),
|
||||||
|
debug: console.debug.bind(console),
|
||||||
|
info: console.info.bind(console)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the console override to pipe logs to the API.
|
||||||
|
*/
|
||||||
|
initialise(): void {
|
||||||
|
console.log = (...args: unknown[]) => {
|
||||||
|
this.originalConsole.log(...args);
|
||||||
|
this.sendLog('info', this.formatArgs(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
console.info = (...args: unknown[]) => {
|
||||||
|
this.originalConsole.info(...args);
|
||||||
|
this.sendLog('info', this.formatArgs(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
console.debug = (...args: unknown[]) => {
|
||||||
|
this.originalConsole.debug(...args);
|
||||||
|
this.sendLog('debug', this.formatArgs(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
console.warn = (...args: unknown[]) => {
|
||||||
|
this.originalConsole.warn(...args);
|
||||||
|
this.sendLog('warn', this.formatArgs(args));
|
||||||
|
};
|
||||||
|
|
||||||
|
console.error = (...args: unknown[]) => {
|
||||||
|
this.originalConsole.error(...args);
|
||||||
|
|
||||||
|
// Check if the first argument is an Error object
|
||||||
|
if (args[0] instanceof Error) {
|
||||||
|
const error = args[0];
|
||||||
|
this.sendLog('error', error.message, 'Console', {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.sendLog('error', this.formatArgs(args));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global error handlers
|
||||||
|
window.addEventListener('error', (event: ErrorEvent) => {
|
||||||
|
this.originalConsole.error('Uncaught Error:', event.error);
|
||||||
|
this.sendLog('error', event.message, 'Window Error', {
|
||||||
|
name: event.error?.name || 'Error',
|
||||||
|
message: event.message,
|
||||||
|
stack: event.error?.stack
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
|
||||||
|
this.originalConsole.error('Unhandled Promise Rejection:', event.reason);
|
||||||
|
|
||||||
|
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
||||||
|
this.sendLog('error', error.message, 'Unhandled Rejection', {
|
||||||
|
name: error.name,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatArgs(args: unknown[]): string {
|
||||||
|
return args.map(arg => {
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
if (arg instanceof Error) {
|
||||||
|
return `${arg.name}: ${arg.message}`;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(arg);
|
||||||
|
} catch {
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
}).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendLog(level: LogPayload['level'], message: string, context?: string, error?: LogPayload['error']): void {
|
||||||
|
const payload: LogPayload = {
|
||||||
|
level,
|
||||||
|
message,
|
||||||
|
context,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
|
||||||
|
this.http.post(`${environment.apiUrl}/log`, payload).subscribe({
|
||||||
|
error: (err) => {
|
||||||
|
this.originalConsole.error('Failed to send log to API:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,4 +18,7 @@ MOD_ROLE_ID="op://Environment Variables - Naomi/Library/mod role id"
|
|||||||
STAFF_ROLE_ID="op://Environment Variables - Naomi/Library/staff role id"
|
STAFF_ROLE_ID="op://Environment Variables - Naomi/Library/staff role id"
|
||||||
|
|
||||||
# Application URL
|
# Application URL
|
||||||
BASE_URL="op://Environment Variables - Naomi/Library/localhost url"
|
BASE_URL="op://Environment Variables - Naomi/Library/localhost url"
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"@fastify/rate-limit": "^10.3.0",
|
"@fastify/rate-limit": "^10.3.0",
|
||||||
"@fastify/sensible": "6.0.4",
|
"@fastify/sensible": "6.0.4",
|
||||||
"@fastify/static": "^9.0.0",
|
"@fastify/static": "^9.0.0",
|
||||||
|
"@nhcarrigan/logger": "1.1.1",
|
||||||
"@prisma/client": "6.19.2",
|
"@prisma/client": "6.19.2",
|
||||||
"dompurify": "^3.3.1",
|
"dompurify": "^3.3.1",
|
||||||
"fastify": "5.7.3",
|
"fastify": "5.7.3",
|
||||||
|
|||||||
Generated
+8
@@ -56,6 +56,9 @@ importers:
|
|||||||
'@fastify/static':
|
'@fastify/static':
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.0
|
version: 9.0.0
|
||||||
|
'@nhcarrigan/logger':
|
||||||
|
specifier: 1.1.1
|
||||||
|
version: 1.1.1
|
||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: 6.19.2
|
specifier: 6.19.2
|
||||||
version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)
|
version: 6.19.2(prisma@6.19.2(typescript@5.9.3))(typescript@5.9.3)
|
||||||
@@ -2488,6 +2491,9 @@ packages:
|
|||||||
typescript: '>=5'
|
typescript: '>=5'
|
||||||
vitest: '>=2'
|
vitest: '>=2'
|
||||||
|
|
||||||
|
'@nhcarrigan/logger@1.1.1':
|
||||||
|
resolution: {integrity: sha512-P6OEQFHDtf6psybYGljuCxkSW6DLQCsx1aZZ3w4YKBXHBFjDbhuvpM9K1kPhVN48hakitx2WPLEoIFr6YZELYw==}
|
||||||
|
|
||||||
'@noble/hashes@1.4.0':
|
'@noble/hashes@1.4.0':
|
||||||
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
|
resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@@ -12722,6 +12728,8 @@ snapshots:
|
|||||||
- eslint-import-resolver-webpack
|
- eslint-import-resolver-webpack
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@nhcarrigan/logger@1.1.1': {}
|
||||||
|
|
||||||
'@noble/hashes@1.4.0': {}
|
'@noble/hashes@1.4.0': {}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ MOD_ROLE_ID="op://Environment Variables - Naomi/Library/mod role id"
|
|||||||
STAFF_ROLE_ID="op://Environment Variables - Naomi/Library/staff role id"
|
STAFF_ROLE_ID="op://Environment Variables - Naomi/Library/staff role id"
|
||||||
|
|
||||||
# Application URL
|
# Application URL
|
||||||
BASE_URL="op://Environment Variables - Naomi/Library/base url"
|
BASE_URL="op://Environment Variables - Naomi/Library/base url"
|
||||||
|
|
||||||
|
# Logger
|
||||||
|
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"
|
||||||
Reference in New Issue
Block a user