Configuration

Tracing structuré

Runique expose un système de tracing structuré via RuniqueLog. Par défaut, un subscriber console est installé et les domaines sont opt-in : tant qu'un domaine n'est pas activé, ses événements ne sont pas émis. Quelques sites critiques émettent toujours (voir Erreurs inconditionnelles).

Activation rapide en développement

RuniqueApp::builder(config)
    .with_log(|l| l.dev())   // tout à DEBUG si DEBUG=true
    // ...

.dev() est un no-op si DEBUG n'est pas true — utilisable inconditionnellement.


Configuration granulaire

.with_log(|l| l
    .forms(|f| f
        .validate(Level::DEBUG)
        .finalize(Level::DEBUG)
    )
    .admin(|a| a
        .crud(Level::INFO)
        .auth(Level::WARN)
    )
    .auth(|a| a
        .login(Level::INFO)
        .reset(Level::WARN)
    )
    .mailer(|m| m.send(Level::INFO))
    .builder(|b| b
        .templates(Level::INFO)
        .middleware(Level::DEBUG)
        .routes(Level::INFO)
        .statics(Level::INFO)
    )
    .rate_limit(Level::WARN)
)

Domaines disponibles

forms — Pipeline formulaire

ChampMomentDonnées loggées
fieldEnregistrement d'un champnom, type, required
set_valueValeur assignée par fill()nom, valeur (password masqué)
validateRésultat de validationchamp, ok/error, nb global errors
renderRendu HTMLchamp, ok/error
finalizeHash/move fichierchamp, ok/error

admin — Panel admin

ChampMomentDonnées loggées
authVérification accès + CSRF failresource, action
crudDispatch + résultat create/edit/deleteresource, action, ok/error
listDispatch + résultat listeresource, rows, total, page
bulkActions de masseresource, action

auth — Authentification

ChampMomentDonnées loggées
loginCréation de sessionuser_id, username, is_superuser, exclusive, db_persist
resetFlux reset mot de passeemail, étape (token généré / email envoyé / invalide / ok / error)

mailer

ChampMomentDonnées loggées
sendEnvoi emailbackend, to, subject, ok/error

builder — Démarrage (one-time)

ChampMomentDonnées loggées
templatesChargement Terainternal, user, total
registryRessources admincount
middlewareStack middlewarecount + slot + name pour chaque entrée
routesRegistry URLcount
staticsFichiers statiquesstatic_url, static_dir, media_url, media_dir

errors — Pages d'erreur HTTP

ChampMomentDonnées loggées
httpErreur HTTP gérée (404/validation/forbidden)method, path, type / erreur
renderÉchec de rendu d'un template d'erreur (404/429/500)template, erreur — plancher WARN (toujours visible, voir plus bas)
.with_log(|l| l.errors(|e| e.http(Level::INFO).render(Level::WARN)))

Champs plats sur RuniqueLog

ChampMomentDonnées loggées
rate_limitRequête bloquéeip, retry_after
csrfToken CSRF détecté dans une URL GETpath
sessionOpérations session storeevent
dbRequêtes base de donnéesquery, duration
host_validationHôte rejetéhost

Sorties de log

Par défaut Runique installe un subscriber console (Stdout, couleurs). On configure une ou plusieurs sorties cumulables via .output() :

use runique::prelude::{LogOutput, LogRotation};

.with_log(|l| l
    .output(LogOutput::stdout())                 // console couleurs
    .output(LogOutput::file("logs/app.json"))    // JSON (déduit de l'extension .json)
    .output(LogOutput::file("logs/app.log")      // texte brut
        .rotation(LogRotation::Daily)))
  • Le format est déduit de l'extension : .json → JSON structuré (une ligne par événement), sinon texte brut.
  • L'écriture fichier est non bloquante ; les logs sont vidés proprement à l'extinction.
  • Rotation : Daily (défaut), Hourly, Never.
  • RUNIQUE_LOG_FILE=/chemin/app.json ajoute une sortie fichier au runtime, sans recompiler.

Sink personnalisé

Pour router les logs vers une destination arbitraire (base de données, collecteur HTTP, file de messages), implémente LogSink — aucun type tracing n'est exposé :

use runique::prelude::{LogOutput, LogRecord, LogSink};

struct MonSink;

impl LogSink for MonSink {
    fn log(&self, record: &LogRecord) {
        // record.level / target / message / file / line / fields
        // Ne bloque pas : pour de l'async, enfile dans ton propre channel.
    }
}

.with_log(|l| l.output(LogOutput::sink(MonSink)))

Le sink reçoit tous les événements du process (Runique et ton application) ; distingue-les par record.target (les événements Runique ont un target commençant par runique). Runique ne fournit volontairement pas de sink base de données (cela surchargerait la DB) — LogSink est la porte pour le brancher toi-même.

Subscriber externe

Si ton application gère son propre subscriber tracing (stack de layers custom, OpenTelemetry…), déclare .external() : Runique n'installe rien et te laisse le créneau global unique, tout en continuant d'émettre ses événements vers la façade tracing (ton subscriber les reçoit).

Minimal :

.with_log(|l| l.external())

Complet :

use runique::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Tu poses TON subscriber, avant build()
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::INFO)
        .init();

    RuniqueApp::builder(RuniqueConfig::from_env())
        .with_database().await
        .routes(url::urlpatterns())
        .with_log(|l| l.external())   // Runique n'installe pas son subscriber
        .build().await?
        .run().await
}

Pour ignorer les logs internes de Runique, filtre leur target dans ton EnvFilter :

tracing_subscriber::fmt()
    .with_env_filter("info,runique=off")   // garde tes logs, coupe ceux de Runique
    .init();

En mode .external(), les sorties .output() sont ignorées (c'est ton subscriber qui décide où vont les logs).


Erreurs inconditionnelles (toujours actives)

Indépendamment de la config tracing, certains événements sont toujours émis :

  • Template invalide au démarragetracing::error! (nom du template + ligne) avant l'arrêt.
  • Erreurs serveur critiques (500 : base de données, IO, template, interne) — tracing::error!.
  • Sites sensibles à plancher WARN — même domaine désactivé, ces échecs émettent au moins en WARN, car un échec silencieux y casserait une garantie : rotation de l'ID de session (anti-fixation), invalidation des autres sessions (login exclusif), persistance de la session au login, envoi de l'email de reset, et échec de rendu d'un template d'erreur (errors.render).