/** * ───────────────────────────────────────────────────────────────────────────── * AppLog — journal de diagnostic (portage de `util/AppLog.kt`, v1.3.1). * * Ligne horodatée par événement notable (rappels, import/export, erreurs), * buffer circulaire de MAX_LINES, persisté dans le localStorage (Android : * `filesDir/debug-log.txt`). Exportable depuis Paramètres (« Logs de * diagnostic ») pour le debug à distance — l'utilisateur joint le fichier à * son rapport de bug. * * ⚠️ NE JAMAIS PROPAGER D'EXCEPTION depuis log() (fix Android v1.3.3, §14 * #45) : log() est appelé de partout, y compris dans des callbacks système — * une erreur d'écriture (quota localStorage dépassé…) doit coûter AU PIRE * une ligne de journal, jamais un crash. * ───────────────────────────────────────────────────────────────────────────── */ import { store } from '../data/store.js'; /** Taille maximale du buffer circulaire (identique au Kotlin). */ export const MAX_LINES = 500; /** Buffer mémoire (miroir de l'ArrayDeque Kotlin). */ let buffer = []; /** Formatteur partagé — 2 chiffres. */ function pad(n) { return String(n).padStart(2, '0'); } /** * Ligne horodatée : « 2026-09-08 12:34:56 [tag] message » (heure locale — * même format qu'Android SimpleDateFormat "yyyy-MM-dd HH:mm:ss"). * * @param {number} tsMs * @param {string} tag * @param {string} message * @returns {string} */ export function formatLine(tsMs, tag, message) { const d = new Date(tsMs); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` + `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())} ` + `[${tag}] ${message}`; } /** * Buffer circulaire : garde les MAX dernières lignes (pur — testable). * * @param {string[]} existing * @param {number} max * @returns {string[]} */ export function trimLines(existing, max) { return existing.length <= max ? existing : existing.slice(existing.length - max); } /** * Charge le journal persisté dans le buffer mémoire. À appeler une fois au * démarrage — un journal corrompu ne doit jamais empêcher l'app de démarrer * (on perd l'historique, pas le process). */ export function init() { const raw = store.getDebugLog(); buffer = raw ? trimLines(raw.split('\n'), MAX_LINES) : []; log('app', `démarrage — ${buffer.length} lignes de logs existantes`); } /** * Journalise une ligne (buffer mémoire + persistance). Fail-safe : toute * erreur d'IO est avalée (quota localStorage…). * * @param {string} tag * @param {string} message */ export function log(tag, message) { const line = formatLine(Date.now(), tag, message); buffer = trimLines([...buffer, line], MAX_LINES); try { store.setDebugLog(buffer.join('\n')); } catch (e) { // Journalisation best-effort : ne jamais faire planter l'app pour un log } } /** @returns {string} tout le journal (une ligne par \n). */ export function all() { return buffer.join('\n'); } /** @returns {number} nombre de lignes en buffer. */ export function count() { return buffer.length; } /** Efface le journal (bouton Paramètres) puis journalise l'effacement. */ export function clear() { buffer = []; try { store.setDebugLog(''); } catch (e) { // idem : best-effort } log('app', 'logs effacés'); }