Some checks failed
SourceFinder CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
SourceFinder CI/CD Pipeline / Unit Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Integration Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Performance Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Code Coverage Report (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (16.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (18.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (20.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Regression Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Audit (push) Has been cancelled
SourceFinder CI/CD Pipeline / Notify Results (push) Has been cancelled
- Architecture modulaire avec injection de dépendances - Système de scoring intelligent multi-facteurs (spécificité, fraîcheur, qualité, réutilisation) - Moteur anti-injection 4 couches (preprocessing, patterns, sémantique, pénalités) - API REST complète avec validation et rate limiting - Repository JSON avec index mémoire et backup automatique - Provider LLM modulaire pour génération de contenu - Suite de tests complète (Jest) : * Tests unitaires pour sécurité et scoring * Tests d'intégration API end-to-end * Tests de sécurité avec simulation d'attaques * Tests de performance et charge - Pipeline CI/CD avec GitHub Actions - Logging structuré et monitoring - Configuration ESLint et environnement de test 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
322 lines
8.9 KiB
JavaScript
322 lines
8.9 KiB
JavaScript
// ========================================
|
|
// FICHIER: lib/ErrorReporting.js - SYSTÈME DE LOGGING SOURCEFINDER
|
|
// Description: Système de logging Pino avec traçage hiérarchique et WebSocket
|
|
// ========================================
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
const pino = require('pino');
|
|
const pretty = require('pino-pretty');
|
|
const { PassThrough } = require('stream');
|
|
const WebSocket = require('ws');
|
|
|
|
// Import du traçage (injection différée pour éviter références circulaires)
|
|
const { setLogger } = require('./trace');
|
|
|
|
// WebSocket server for real-time logs
|
|
let wsServer;
|
|
const wsClients = new Set();
|
|
|
|
// Configuration Pino avec fichiers datés
|
|
const now = new Date();
|
|
const timestamp = now.toISOString().slice(0, 10) + '_' +
|
|
now.toLocaleTimeString('fr-FR').replace(/:/g, '-');
|
|
const logFile = path.join(__dirname, '..', 'logs', `sourcefinder-${timestamp}.log`);
|
|
|
|
const prettyStream = pretty({
|
|
colorize: true,
|
|
translateTime: 'HH:MM:ss.l',
|
|
ignore: 'pid,hostname',
|
|
messageFormat: '{msg}',
|
|
customPrettifiers: {
|
|
level: (logLevel) => {
|
|
const levels = {
|
|
10: '🔍 DEBUG',
|
|
20: '📝 INFO',
|
|
25: '🤖 PROMPT',
|
|
26: '⚡ LLM',
|
|
30: '⚠️ WARN',
|
|
40: '❌ ERROR',
|
|
50: '💀 FATAL',
|
|
5: '👁️ TRACE'
|
|
};
|
|
return levels[logLevel] || logLevel;
|
|
}
|
|
}
|
|
});
|
|
|
|
const tee = new PassThrough();
|
|
let consolePipeInitialized = false;
|
|
|
|
// File destination with dated filename
|
|
const fileDest = pino.destination({
|
|
dest: logFile,
|
|
mkdir: true,
|
|
sync: false,
|
|
minLength: 0
|
|
});
|
|
tee.pipe(fileDest);
|
|
|
|
// Niveaux personnalisés pour SourceFinder
|
|
const customLevels = {
|
|
trace: 5, // Traçage hiérarchique détaillé
|
|
debug: 10, // Debug standard
|
|
info: 20, // Informations importantes
|
|
prompt: 25, // Requêtes vers LLMs
|
|
llm: 26, // Réponses LLM
|
|
warn: 30, // Avertissements
|
|
error: 40, // Erreurs
|
|
fatal: 50 // Erreurs fatales
|
|
};
|
|
|
|
// Logger Pino principal
|
|
const logger = pino(
|
|
{
|
|
level: (process.env.LOG_LEVEL || 'info').toLowerCase(),
|
|
base: undefined,
|
|
timestamp: pino.stdTimeFunctions.isoTime,
|
|
customLevels: customLevels,
|
|
useOnlyCustomLevels: true
|
|
},
|
|
tee
|
|
);
|
|
|
|
// Initialiser WebSocket server si activé
|
|
function initWebSocketServer() {
|
|
if (!wsServer && process.env.ENABLE_LOG_WS === 'true') {
|
|
try {
|
|
const logPort = process.env.LOG_WS_PORT || 8082;
|
|
wsServer = new WebSocket.Server({ port: logPort });
|
|
|
|
wsServer.on('connection', (ws) => {
|
|
wsClients.add(ws);
|
|
logger.info('Client connecté au WebSocket des logs');
|
|
|
|
ws.on('close', () => {
|
|
wsClients.delete(ws);
|
|
logger.info('Client WebSocket déconnecté');
|
|
});
|
|
|
|
ws.on('error', (error) => {
|
|
logger.error('Erreur WebSocket:', error.message);
|
|
wsClients.delete(ws);
|
|
});
|
|
});
|
|
|
|
wsServer.on('error', (error) => {
|
|
if (error.code === 'EADDRINUSE') {
|
|
logger.warn(`Port WebSocket ${logPort} déjà utilisé`);
|
|
wsServer = null;
|
|
} else {
|
|
logger.error('Erreur serveur WebSocket:', error.message);
|
|
}
|
|
});
|
|
|
|
logger.info(`Serveur WebSocket des logs démarré sur le port ${logPort}`);
|
|
} catch (error) {
|
|
logger.warn(`Échec démarrage serveur WebSocket: ${error.message}`);
|
|
wsServer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Diffusion vers clients WebSocket
|
|
function broadcastLog(message, level) {
|
|
const logData = {
|
|
timestamp: new Date().toISOString(),
|
|
level: level.toUpperCase(),
|
|
message: message,
|
|
service: 'SourceFinder'
|
|
};
|
|
|
|
wsClients.forEach(ws => {
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
try {
|
|
ws.send(JSON.stringify(logData));
|
|
} catch (error) {
|
|
logger.error('Échec envoi log vers client WebSocket:', error.message);
|
|
wsClients.delete(ws);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fonction principale de logging SourceFinder
|
|
async function logSh(message, level = 'INFO') {
|
|
// Initialiser WebSocket si demandé
|
|
if (!wsServer) {
|
|
initWebSocketServer();
|
|
}
|
|
|
|
// Initialiser sortie console si demandée
|
|
if (!consolePipeInitialized && (process.env.ENABLE_CONSOLE_LOG === 'true' || process.env.NODE_ENV === 'development')) {
|
|
tee.pipe(prettyStream).pipe(process.stdout);
|
|
consolePipeInitialized = true;
|
|
}
|
|
|
|
const pinoLevel = level.toLowerCase();
|
|
|
|
// Métadonnées de traçage pour logging hiérarchique
|
|
const traceData = {};
|
|
if (message.includes('▶') || message.includes('✔') || message.includes('✖') || message.includes('•')) {
|
|
traceData.trace = true;
|
|
traceData.service = 'SourceFinder';
|
|
traceData.evt = message.includes('▶') ? 'span.start' :
|
|
message.includes('✔') ? 'span.end' :
|
|
message.includes('✖') ? 'span.error' : 'span.event';
|
|
}
|
|
|
|
// Ajouter contexte SourceFinder
|
|
traceData.service = 'SourceFinder';
|
|
traceData.timestamp = new Date().toISOString();
|
|
|
|
// Logger avec Pino
|
|
switch (pinoLevel) {
|
|
case 'error':
|
|
logger.error(traceData, message);
|
|
break;
|
|
case 'warning':
|
|
case 'warn':
|
|
logger.warn(traceData, message);
|
|
break;
|
|
case 'debug':
|
|
logger.debug(traceData, message);
|
|
break;
|
|
case 'trace':
|
|
logger.trace(traceData, message);
|
|
break;
|
|
case 'prompt':
|
|
logger.prompt(traceData, message);
|
|
break;
|
|
case 'llm':
|
|
logger.llm(traceData, message);
|
|
break;
|
|
case 'fatal':
|
|
logger.fatal(traceData, message);
|
|
break;
|
|
default:
|
|
logger.info(traceData, message);
|
|
}
|
|
|
|
// Diffuser vers clients WebSocket
|
|
broadcastLog(message, level);
|
|
|
|
// Force flush pour affichage temps réel
|
|
logger.flush();
|
|
}
|
|
|
|
// Méthodes de logging spécialisées SourceFinder
|
|
const sourceFinderLogger = {
|
|
// Recherche de news
|
|
newsSearch: (message, metadata = {}) => {
|
|
logSh(`🔍 [NEWS_SEARCH] ${message}`, 'INFO');
|
|
if (Object.keys(metadata).length > 0) {
|
|
logSh(` Métadonnées: ${JSON.stringify(metadata)}`, 'DEBUG');
|
|
}
|
|
},
|
|
|
|
// Interactions LLM
|
|
llmRequest: (message, metadata = {}) => {
|
|
logSh(`🤖 [LLM_REQUEST] ${message}`, 'PROMPT');
|
|
if (metadata.tokens) {
|
|
logSh(` Tokens: ${metadata.tokens}`, 'DEBUG');
|
|
}
|
|
},
|
|
|
|
llmResponse: (message, metadata = {}) => {
|
|
logSh(`⚡ [LLM_RESPONSE] ${message}`, 'LLM');
|
|
if (metadata.duration) {
|
|
logSh(` Durée: ${metadata.duration}ms`, 'DEBUG');
|
|
}
|
|
},
|
|
|
|
// Opérations de stock
|
|
stockOperation: (message, operation, count = 0, metadata = {}) => {
|
|
logSh(`📦 [STOCK_${operation.toUpperCase()}] ${message}`, 'INFO');
|
|
if (count > 0) {
|
|
logSh(` Articles traités: ${count}`, 'DEBUG');
|
|
}
|
|
if (Object.keys(metadata).length > 0) {
|
|
logSh(` Détails: ${JSON.stringify(metadata)}`, 'DEBUG');
|
|
}
|
|
},
|
|
|
|
// Scoring d'articles
|
|
scoringOperation: (message, score = null, metadata = {}) => {
|
|
const scoreStr = score !== null ? ` [Score: ${score}]` : '';
|
|
logSh(`🎯 [SCORING]${scoreStr} ${message}`, 'INFO');
|
|
if (Object.keys(metadata).length > 0) {
|
|
logSh(` Métadonnées: ${JSON.stringify(metadata)}`, 'DEBUG');
|
|
}
|
|
},
|
|
|
|
// Erreurs spécifiques
|
|
antiInjectionAlert: (message, metadata = {}) => {
|
|
logSh(`🛡️ [ANTI_INJECTION] ${message}`, 'WARN');
|
|
if (Object.keys(metadata).length > 0) {
|
|
logSh(` Contexte: ${JSON.stringify(metadata)}`, 'WARN');
|
|
}
|
|
},
|
|
|
|
// Performance et métriques
|
|
performance: (message, duration, metadata = {}) => {
|
|
logSh(`⏱️ [PERFORMANCE] ${message} (${duration}ms)`, 'DEBUG');
|
|
if (Object.keys(metadata).length > 0) {
|
|
logSh(` Métriques: ${JSON.stringify(metadata)}`, 'DEBUG');
|
|
}
|
|
}
|
|
};
|
|
|
|
// Nettoyer logs anciens
|
|
async function cleanLocalLogs() {
|
|
try {
|
|
const logsDir = path.join(__dirname, '../logs');
|
|
|
|
try {
|
|
const files = await fs.readdir(logsDir);
|
|
const cutoffDate = new Date();
|
|
cutoffDate.setDate(cutoffDate.getDate() - 7); // Garder 7 jours
|
|
|
|
for (const file of files) {
|
|
if (file.endsWith('.log') && file.startsWith('sourcefinder-')) {
|
|
const filePath = path.join(logsDir, file);
|
|
const stats = await fs.stat(filePath);
|
|
|
|
if (stats.mtime < cutoffDate) {
|
|
await fs.unlink(filePath);
|
|
logSh(`🗑️ Log ancien supprimé: ${file}`, 'INFO');
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Répertoire pourrait ne pas exister
|
|
}
|
|
} catch (error) {
|
|
// Échec silencieux
|
|
}
|
|
}
|
|
|
|
// Fonction de nettoyage générale
|
|
async function cleanLogSheet() {
|
|
try {
|
|
logSh('🧹 Nettoyage logs SourceFinder...', 'INFO');
|
|
await cleanLocalLogs();
|
|
logSh('✅ Nettoyage logs terminé', 'INFO');
|
|
} catch (error) {
|
|
logSh('Erreur nettoyage logs: ' + error.message, 'ERROR');
|
|
}
|
|
}
|
|
|
|
// Injecter logSh dans le système de traçage
|
|
setLogger(logSh);
|
|
|
|
// Exports pour SourceFinder
|
|
module.exports = {
|
|
logSh,
|
|
...sourceFinderLogger,
|
|
cleanLogSheet,
|
|
initWebSocketServer,
|
|
// Import du traçage
|
|
setupTracer: require('./trace').setupTracer,
|
|
tracer: require('./trace').tracer
|
|
}; |