module

SessionLog

Journal de session pour les CLI Crystal : le programme se RELANCE lui-même et le parent enregistre dans un fichier tout ce qui s'affiche dans le terminal (questions, réponses, sortie des sous-processus).

  • Terminal interactif : l'enfant tourne sous script(1), qui lui garde un vrai terminal (questions, couleurs, tty?, saisie sans écho, ssh -t intacts) et enregistre la session dans le fichier.
  • Sortie redirigée (tube, cron) : l'enfant écrit dans deux tubes que le parent recopie à la fois à l'écran et dans le fichier.

L'enfant reçoit la variable <APP>_SESSION_LOG (chemin du journal) : il ne se relance pas une seconde fois, ses propres enfants non plus. Fichiers 0600 dans un dossier 0700 ; journaux de plus de keep_days jours effacés à chaque nouvelle session journalisée.

Le choix des commandes à journaliser (et de leur libellé) revient à l'application : JAMAIS celles qui affichent ou font saisir des secrets en clair — un journal ne doit pas devenir une copie du coffre.

require "session-log"

log = SessionLog::Config.new("monoutil", MonOutil::VERSION, "#{ENV["HOME"]}/.local/state/monoutil/logs")
argv, refused = SessionLog.strip_flag(ARGV)
if !refused && !log.active? && journaliser?(argv)
  exit SessionLog.run(log, argv, log.path(argv.first, argv[1]?))
end
exit MonOutil.run(argv)

Constants

FLAG = "--no-log"

Option qui refuse le journal, où qu'elle soit sur la ligne de commande.

VERSION = {{ (((read_file("/tmp/tmp.MICILo/src/src/session_log/../../shard.yml")) .lines .find(&.starts_with?("version:")) || "version: 0.0.0") .gsub(/^version:\s*/, "")) .chomp }}

Lue au compile-time depuis shard.yml via le macro read_file. Évite la désynchronisation entre la constante Crystal et le version: du shard.yml (cas vécu en mai 2026 avec crystal-combine-pdf : 7 versions inscrivaient la mauvaise valeur dans le /Producer des PDFs générés).

Cf. note mémoire ALOLI feedback_shard_version_macro.md.

Instance methods

header(config : Config, argv : Array(String), now : Time = Time.local) : String

En-tête du journal : programme, version, date, ligne de commande. Pur.

Source
path(dir : String, label : String, scope : String | Nil = nil, now : Time = Time.local) : String

<dossier>/2026-10-25--14-03-07--diag--aloli.fr.log : date, heure, libellé de la commande, puis la portée (un domaine, un hôte…) réduite aux caractères sûrs dans un nom de fichier. Pur.

Source
prune(dir : String, keep_days : Int32) : Nil

Efface les journaux de plus de keep_days jours. Jamais bloquant.

Source
run(config : Config, argv : Array(String), log : String, exe : String = Process.executable_path || PROGRAM_NAME) : Int32

Relance le programme (argv complet, options globales comprises) sous journal et renvoie son code de sortie. Le chemin du journal est rappelé sur stderr à la fin. exe : le programme lui-même par défaut.

Source
script_args(log : String, cmd : Array(String), linux : Bool = {{ flag?(:linux) }}) : Array(String)

Arguments de script(1) : enregistre (en ajout, l'en-tête est déjà écrit) la commande cmd dans log. BSD et macOS : script -q -a -F log cmd… (-F : écrit au fil de l'eau, tail -f possible ; code de sortie = celui de la commande). Linux (util-linux) : commande en une chaîne -c, -e pour rendre son code de sortie. Pur.

Source
strip_flag(argv : Array(String), flag : String = FLAG) : Tuple(Array(String), Bool)

Retire --no-log d'argv (où qu'il soit). Renvoie {argv nettoyé, journal refusé ?}. Pur.

Source

Nested types