Journalisation dans les applications TypeScript : meilleures pratiques en action
La journalisation est une partie fondamentale de toute application de production. Elle permet non seulement de suivre l'exécution du code et de trouver des erreurs, mais aussi de réaliser des audits de sécurité, d'analyser les performances et de déboguer des scénarios complexes. En TypeScript, avec son puissant système de types, l'approche de la journalisation devient encore plus flexible et fiable. Dans cet article, nous analyserons les meilleures pratiques de journalisation avec des exemples concrets.
1. Choix de la bibliothèque et configuration du logger
La première chose à faire est de choisir une bibliothèque appropriée. Parmi les solutions populaires, on distingue winston et pino. Les deux prennent en charge TypeScript nativement, mais ont des priorités différentes : winston pour la flexibilité et les fonctionnalités riches, pino pour des performances maximales.
Examinons un exemple de configuration de winston avec typage :
import winston from 'winston';
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'user-service' }, transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ), }), new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), new winston.transports.File({ filename: 'logs/combined.log' }), ],});
export default logger;Pour pino, la configuration sera plus concise mais tout aussi efficace :
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard', }, },});
export default logger;Conseil : Placez toujours la configuration du niveau de journalisation dans des variables d'environnement (LOG_LEVEL). En production, on utilise généralement info ou warn, et en développement — debug.
2. Journalisation structurée et contexte
L'une des principales erreurs est de journaliser de simples chaînes de caractères. Utilisez plutôt des objets structurés. Ils permettent de filtrer et d'agréger facilement les logs dans des systèmes comme ELK, Grafana ou Datadog.
Exemple de mauvaise journalisation :
logger.info('User logged in successfully');Exemple de bonne journalisation avec contexte :
logger.info({ message: 'User logged in', userId: user.id, email: user.email, loginMethod: 'oauth', ip: request.ip, duration: Date.now() - startTime,});Pour plus de commodité, vous pouvez créer une enveloppe typée :
interface LogContext { requestId?: string; userId?: string; action?: string; [key: string]: unknown;}
function logWithContext(message: string, context: LogContext = {}): void { logger.info({ message, ...context });}
// UtilisationlogWithContext('Order processed', { orderId: '12345', userId: 'user_678', total: 99.99 });Cette approche garantit que chaque log contient les informations clés pour l'analyse.
3. Journalisation des erreurs et exceptions
La gestion des erreurs est la partie la plus importante de la journalisation. Journalisez toujours la pile complète de l'erreur et le contexte dans lequel elle s'est produite. N'utilisez pas console.error — il n'offre pas de flexibilité pour la rotation et le formatage.
Le bon modèle avec TypeScript :
import logger from './logger';
async function fetchUserData(userId: string): Promise<UserData> { try { const response = await api.get(`/users/${userId}`); ret