Pourquoi Logging et monitoring Next.js ?
Contexte réel :
En tant que développeur senior, il est crucial d'avoir un système de logging et de monitoring en place pour suivre les performances de l'application, détecter les erreurs et garantir une expérience utilisateur fluide. Un cas concret est la gestion des erreurs lors du chargement d'une page, où un bon système de logging permettrait de capturer ces erreurs et de les corriger rapidement, améliorant ainsi la fidélité des utilisateurs.
Prérequis
- Connaissances en JavaScript ES6+
- Compréhension de base de React
- Connaitre Next.js (principales fonctionnalités)
- Node.js v14.0 ou plus tard installé sur sa machine
- Outils d'éditeur de code moderne (VSCode recommandé)
- Base de connaissances en gestion des erreurs et logging
Outils à installer :
npm(Node Package Manager) pour gérer les dépendances du projet.nextjspour créer le projet.
## Installation de Node.js et npm via nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
nvm install 14
nvm use 14
## Création d'un nouveau projet Next.js
npx create-next-app@latest logging-monitoring-nextjs
cd logging-monitoring-nextjs
Concepts fondamentaux
Logging
Logging est le processus de collecte et de stockage des informations détaillées sur les opérations réalisées par une application. Il aide à suivre l'exécution de l'application, détecter les erreurs et analyser sa performance.
Conceit :
Un log est un enregistrement d'événements qui se produisent au cours de l'exécution d'un programme. Ces événements peuvent être des informations d'utilisation, des erreurs, des avertissements ou n'importe quel autre type d'information pertinent pour comprendre le comportement de l'application.
Schéma mental :
+-------------------+
| Application |
| |
| +------------+ |
| | Log Event | |
| +------------+ |
| |
| +------------+ |
| | Log Event | |
| +------------+ |
| |
+-------------------+
Exemple de code :
// pages/index.js
import { useEffect } from 'react';
const Home = () => {
useEffect(() => {
console.log('Page chargée avec succès');
// Simuler une erreur pour le logging
try {
someFunctionThatDoesNotExist();
} catch (error) {
console.error('Erreur lors du chargement de la page :', error);
}
}, []);
return <h1>Bienvenue sur Next.js</h1>;
};
export default Home;
Monitoring
Le monitoring consiste à surveiller les performances de l'application en temps réel. Il aide à détecter rapidement les problèmes et à optimiser le comportement de l'application.
Conceit :
Un système de monitoring collecte des métriques (données quantifiables) sur la performance de l'application, comme le temps de réponse, la consommation de ressources et le nombre d'utilisateurs actifs.
Schéma mental :
+-------------------+
| Application |
| |
| +------------+ |
| | Metric | |
| +------------+ |
| |
| +------------+ |
| | Metric | |
| +------------+ |
| |
+-------------------+
Exemple de code :
// utils/monitoring.js
import { performance } from 'perf_hooks';
export const startTimer = (label) => {
performance.mark(label);
};
export const endTimer = (label) => {
performance.mark(`${label}:end`);
performance.measure(label, label, `${label}:end`);
};
nextjs
// pages/index.js
import { useEffect } from 'react';
import { startTimer, endTimer } from '../utils/monitoring';
const Home = () => {
useEffect(() => {
startTimer('PageLoadTime');
console.log('Page chargée avec succès');
// Simuler une erreur pour le logging
try {
someFunctionThatDoesNotExist();
} catch (error) {
console.error('Erreur lors du chargement de la page :', error);
}
endTimer('PageLoadTime');
}, []);
return <h1>Bienvenue sur Next.js</h1>;
};
export default Home;
Mise en pratique : projet fil rouge
Projet fil rouge :
Construisons un simple gestionnaire de tâches. Ce projet comprendra des routes API pour créer, lire, mettre à jour et supprimer des tâches.
Étape 1 : Initialisation du projet
npx create-next-app@latest task-manager
cd task-manager
Étape 2 : Création des fichiers
Créer les fichiers suivants dans le répertoire pages/api :
tasks.js[id].js
Étape 3 : Implémentation de la gestion API
// pages/api/tasks.js
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
nextjs
// pages/api/[id].js
export default function handler(req, res) {
const id = req.query.id;
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ message: 'Task not found' });
}
if (req.method === 'GET') {
res.status(200).json(task);
} else if (req.method === 'PUT') {
const updatedTask = req.body;
task.title = updatedTask.title;
res.status(200).json(updatedTask);
} else if (req.method === 'DELETE') {
tasks.splice(tasks.indexOf(task), 1);
res.status(204).end();
} else {
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Étape 4 : Création des composants frontend
Créer les fichiers suivants dans le répertoire components :
TaskList.jsAddTaskForm.js
// components/TaskList.js
import { useEffect, useState } from 'react';
import axios from 'axios';
const TaskList = () => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
fetch('/api/tasks')
.then(response => response.json())
.then(data => setTasks(data))
.catch(error => console.error('Error fetching tasks:', error));
}, []);
return (
<ul>
{tasks.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
};
export default TaskList;
nextjs
// components/AddTaskForm.js
import axios from 'axios';
const AddTaskForm = () => {
const [title, setTitle] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
await axios.post('/api/tasks', { title });
setTitle('');
} catch (error) {
console.error('Error adding task:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Ajouter une tâche"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<button type="submit">Ajouter</button>
</form>
);
};
export default AddTaskForm;
Étape 5 : Intégration des composants dans la page
// pages/index.js
import TaskList from '../components/TaskList';
import AddTaskForm from '../components/AddTaskForm';
const Home = () => {
return (
<div>
<h1>Gestionnaire de Tâches</h1>
<AddTaskForm />
<TaskList />
</div>
);
};
export default Home;
Étape 6 : Installation et utilisation d'une bibliothèque de logging
npm install winston
Utilisation de Winston pour le logging :
// utils/logger.js
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'info',
format: format.json(),
transports: [
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: format.simple()
}));
}
module.exports = logger;
nextjs
// pages/api/tasks.js
const logger = require('../utils/logger');
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
logger.info('GET /api/tasks');
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
logger.info(`POST /api/tasks : ${JSON.stringify(newTask)}`);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
logger.warn(`Method ${req.method} Not Allowed`);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Erreurs fréquentes et debugging
Erreur 1 : Accès à une variable non définie
Code incorrect :
// pages/api/tasks.js
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
console.log('GET /api/tasks');
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
console.log(`POST /api/tasks : ${JSON.stringify(newTask)}`);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
console.warn(`Method ${req.method} Not Allowed`);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Code correct :
// pages/api/tasks.js
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
console.log('GET /api/tasks');
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
console.log(`POST /api/tasks : ${JSON.stringify(newTask)}`);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
console.warn(`Method ${req.method} Not Allowed`);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Erreur 2 : Route API non trouvée
Code incorrect :
// pages/api/[id].js
export default function handler(req, res) {
const id = req.query.id;
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ message: 'Task not found' });
}
if (req.method === 'GET') {
res.status(200).json(task);
} else if (req.method === 'PUT') {
const updatedTask = req.body;
task.title = updatedTask.title;
res.status(200).json(updatedTask);
} else if (req.method === 'DELETE') {
tasks.splice(tasks.indexOf(task), 1);
res.status(204).end();
} else {
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Code correct :
// pages/api/[id].js
export default function handler(req, res) {
const id = req.query.id;
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
const task = tasks.find(t => t.id === id);
if (!task) {
return res.status(404).json({ message: 'Task not found' });
}
if (req.method === 'GET') {
res.status(200).json(task);
} else if (req.method === 'PUT') {
const updatedTask = req.body;
task.title = updatedTask.title;
res.status(200).json(updatedTask);
} else if (req.method === 'DELETE') {
tasks.splice(tasks.indexOf(task), 1);
res.status(204).end();
} else {
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Erreur 3 : Erreur de syntaxe dans le fichier pages/api/tasks.js
Code incorrect :
// pages/api/tasks.js
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
console.log('GET /api/tasks');
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
console.log(`POST /api/tasks : ${JSON.stringify(newTask)}`);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
console.warn(`Method ${req.method} Not Allowed`);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Code correct :
// pages/api/tasks.js
export default function handler(req, res) {
const tasks = [
{ id: '1', title: 'Faire les courses' },
{ id: '2', title: 'Nettoyer la maison' },
];
if (req.method === 'GET') {
console.log('GET /api/tasks');
res.status(200).json(tasks);
} else if (req.method === 'POST') {
const newTask = req.body;
tasks.push(newTask);
console.log(`POST /api/tasks : ${JSON.stringify(newTask)}`);
res.status(201).json(newTask);
} else {
res.setHeader('Allow', ['GET', 'POST']);
console.warn(`Method ${req.method} Not Allowed`);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
Pour aller plus loin
Pour approfondir ce sujet, je vous recommande de consulter les documents officiels de Next.js et Winston :
Ces ressources fournissent des informations détaillées sur la configuration et l'utilisation de ces outils pour un développement robuste et performant. N'oubliez pas d'intégrer également des métriques et des alertes pour surveiller les performances de votre application en production.