Aller au contenu

veaf-logs — lire les journaux de DCS

veaf-logs ouvre un journal DCS et n'en montre que ce qui compte. Il connaît les scripts VEAF, CTLD, CSAR, AIEN et Skynet, sait reconnaître les erreurs d'Eagle Dynamics sans conséquence, et suit le fichier en direct pendant que la mission tourne. Il lit aussi les autres journaux d'un serveur : DCSServerBot, Real Weather et LotAtc (voir Autres journaux d'un serveur).

Lancer

Depuis l'exécutable téléchargé avec la release :

.\veaf-logs.exe

Sans argument, il rouvre les fichiers de la dernière session ; à défaut, le dcs.log courant (Saved Games\DCS\Logs\dcs.log). On peut aussi lui passer un chemin, ou faire glisser un fichier dessus.

veaf-logs.exe est un programme Windows autonome, écrit en Go : un seul fichier d'une quinzaine de Mo, sans Python ni rien d'autre à installer. Ses sources sont dans src/go/veaf-logs/, dont le README.md dit comment le construire.

Journal d'un serveur distant

Fichier › Ouvrir un journal distant… (Ctrl+Maj+O) suit le dcs.log d'un serveur DCS par SSH, avec les mêmes filtres, règles et profils qu'un fichier local. Une machine héberge souvent plusieurs instances DCS : on déclare la machine une fois, puis un journal par instance, dans ~/veafmct.yaml (voir la configuration globale utilisateur) :

servers:
  veaf:
    host: dcs.veaf.org
    user: veaf
    port: 22                  # facultatif
    key: ~/.ssh/id_ed25519    # facultatif : sinon l'agent SSH et les clés par défaut
    logs:
      private1: C:/Users/veaf/Saved Games/private1_server/Logs/dcs.log
      public1: C:/Users/veaf/Saved Games/public1_server/Logs/dcs.log

Le menu propose alors veaf › private1, veaf › public1… Chaque instance ouvre un onglet veaf:private1, rouvert au prochain lancement comme un fichier local — une fois la fenêtre affichée, à sa place parmi les onglets : chaque connexion prend une ou deux secondes, et la fenêtre ne les attend pas.

Authentification par clé uniquement. L'outil ne demande, ne lit ni ne stocke aucun mot de passe. Si la clé de la machine n'est pas encore dans ~/.ssh/known_hosts, une boîte de dialogue montre son empreinte et propose de la mémoriser, comme le ferait ssh la première fois.

Le journal est recopié en local au fil de l'eau : un stat par seconde, et seuls les octets nouveaux transitent. Quand DCS redémarre sur le serveur et repart d'un fichier neuf, l'onglet repart avec lui. Une coupure réseau met l'onglet en attente ; il reprend là où il en était dès que le serveur répond, une nouvelle tentative toutes les cinq secondes. Ouvrir une instance arrêtée (pas de dcs.log sur le serveur) affiche un message ; un onglet déjà ouvert, lui, reste en attente jusqu'au redémarrage de l'instance. Au lancement, un serveur injoignable est signalé dans la barre d'état et ses onglets ne sont pas rouverts : rien ne bloque.

Côté serveur Windows, il faut le service OpenSSH Server (fonctionnalité facultative de Windows, Paramètres › Applications › Fonctionnalités facultatives) : SFTP est inclus et actif par défaut. Piège classique : pour un compte administrateur, la clé publique va dans C:\ProgramData\ssh\administrators_authorized_keys, pas dans ~\.ssh\authorized_keys, et le fichier doit n'être lisible que par SYSTEM et Administrators.

veaf-tools.exe n'embarque pas cette fonction : elle vit dans veaf-logs.

Par l'API de DCSServerBot, sans compte SSH

Un serveur qui fait tourner DCSServerBot avec son plugin RestAPI se lit aussi sans compte sur la machine : une clé API suffit, et le bot ne fait que lire. On déclare l'API à la place de host (un serveur a l'un ou l'autre, jamais les deux) :

servers:
  veaf-api:
    api:
      url: https://dcs.veaf.org/serverapi       # l'API, préfixe compris s'il y en a un
      api_key_file: ~/.veaf/dcssb-api-key       # un fichier qui ne contient que la clé
    # logs:                                     # facultatif : restreindre ou renommer
    #   private1: {instance: VEAF Private 1}    # le nom du serveur côté bot
    #   bot: {node: dcs-veaf-org}               # le journal du bot d'un nœud

Sans logs:, le menu demande à l'API ce qu'elle a : chaque serveur DCS qu'elle connaît (veaf-api › VEAF Private 1…) et le journal du bot (veaf-api › bot). Les onglets se rouvrent au lancement comme ceux d'un serveur SSH.

Sur dcs.veaf.org, l'adresse est https://dcs.veaf.org/serverapi, et le fichier de clé contient la clé technicien que donne David, pas celle du bot : depuis Internet, seule la clé technicien passe. Une autre clé y reçoit la même réponse qu'une route absente ; l'outil la présente comme une authentification refusée.

Fichier › Ouvrir un ancien journal…, sur un onglet ouvert par l'API, liste les fichiers de ce journal avec leur taille et leur date : dcs.log.old, les copies dcs-AAAAMMJJ-HHMMSS.log que le bot fait à chaque arrêt, ou les dcssb-<nœud>.log.1 à .5 du bot. Le fichier choisi s'ouvre dans un onglet en lecture seule, chargé du début à la fin par morceaux ; il n'est pas rouvert au lancement suivant.

Ce qui change par rapport à SSH :

  • un onglet s'ouvre sur les 2 000 dernières entrées du journal, puis suit ce qui s'écrit ; pour remonter plus loin, ouvrir le fichier par Ouvrir un ancien journal… ;
  • l'API rend des entrées, que l'outil réécrit en lignes : seuls l'alignement du niveau (les espaces après INFO) et une ligne de plus de 8 Kio (coupée par le bot) diffèrent du fichier d'origine, et aucune règle ne les lit ;
  • quand le journal redémarre et que le bot ne retrouve plus la fin de l'ancien fichier, la barre d'état le dit : des lignes ont été perdues ;
  • la barre d'état dit aussi pourquoi un journal ne se lit pas : clé refusée, pas encore de journal, serveur ou fichier inconnu, journal sur un autre nœud, serveur injoignable.

Côté serveur, il faut :

  • DCSServerBot avec les routes de lecture des journaux (/instance/logs, /instance/log, /node/logs, /node/log) : la branche veaf de VEAF/DCSServerBot, en attendant la version de DCSServerBot qui les publiera ;
  • le plugin RestAPI avec une api_key dans son restapi.yaml : sans clé, ces routes n'existent pas ;
  • un reverse proxy en HTTPS devant l'API (Caddy, nginx, IIS), le service web du bot n'écoutant que sur 127.0.0.1 ;
  • à partir de la version 3.0.5 de DCSServerBot, qui demande par défaut un JWT aux connexions venues d'Internet : autoriser la clé API sur les routes de journaux et sur servers, que le menu interroge (endpoints.<route>.security.remote: api_key dans restapi.yaml).

Sécurité :

  • la clé vit dans un fichier lisible par toi seul, jamais dans veafmct.yaml ; l'outil la lit à l'ouverture d'un onglet et ne l'écrit nulle part, ni dans la session, ni dans un message, ni dans le rapport /bug ;
  • l'outil refuse une URL en http:// sauf vers 127.0.0.1 ou localhost (le bout d'un tunnel SSH) : la clé et les journaux traversent le réseau ;
  • les journaux contiennent des noms de joueurs et des adresses IP : une clé se donne à qui administre le serveur.

Onglets

Quand les onglets dépassent la largeur de la fenêtre, la barre défile : à la molette au-dessus d'elle, ou par les flèches ‹ › qui apparaissent alors à sa droite. L'onglet courant est toujours ramené dans la partie visible. À partir de deux onglets, le bouton ≡ à gauche de la barre les liste tous ; un clic sur un titre y va.

Un clic droit sur un onglet propose de le fermer ou de le renommer. Renommer l'onglet… écrit son alias dans ~\veafmct.yaml, et l'alias se retrouve au lancement suivant ; un nom vide rend à l'onglet son nom d'origine. On peut aussi l'écrire à la main :

log_aliases:                       # les fichiers locaux, par leur chemin
  'C:\Users\moi\Saved Games\DCS\Logs\dcs.log': Mon jeu
servers:
  veaf-api:
    api: {url: https://dcs.veaf.org/serverapi, api_key_file: ~/.veaf/dcssb-api-key}
    aliases:                       # les journaux d'un serveur, SSH ou API, par leur nom
      VEAF (www.veaf.org) [fr] - Privé 1: P1

L'outil réécrit le fichier en gardant son contenu et ses commentaires, mais pas toujours leur alignement. Sans alias, les journaux qu'une API découvre perdent les mots que tous ses serveurs partagent : veaf-api:Privé 1 plutôt que veaf-api:VEAF (www.veaf.org) [fr] - Privé 1. Le nom complet reste dans l'info-bulle de l'onglet.

Les trois états

Chaque niveau, chaque source et chaque famille de bruit se règle d'un clic, en faisant défiler trois états :

✓ affiché
◐ contexte — affiché seulement autour d'une ligne retenue
✕ masqué

Le mode contexte est ce qui rend un journal lisible. Mettre INFO en ◐ avec ±3 lignes ne garde que les erreurs et les avertissements, entourés de ce qui les explique. Chaque catégorie peut avoir sa propre portée : le champ ± apparaît à sa droite dès qu'elle passe en ◐, et laisser le champ vide reprend la valeur Contexte des catégories réglée en haut du panneau.

Écarter le bruit ED change l'échelle de ce qu'il reste à lire :

Journal Lignes graves Affichées
Un dcs.log de session 416 34
Ce dcs.log et quatre journaux archivés 2 714 278

« Lignes graves » : de niveau ERROR, ERROR_ONCE ou ALERT. Un journal grossit tant que le jeu tourne, donc un relevé fait un autre jour donne quelques lignes d'écart.

Pour donner toute la largeur au journal, le bouton Panneau des filtres, au début de la barre des profils (ou Ctrl+B), masque ce panneau ; un second appui le rend à la largeur qu'il avait. Masqués, les filtres s'appliquent toujours : la barre d'état continue d'indiquer combien de lignes ils cachent. Le panneau reste masqué d'une session à l'autre.

Profils

La liste déroulante en haut retient un jeu de filtres complet — états des catégories, critères de recherche, portées du contexte. Trois profils sont fournis :

  • Tout — aucun filtre, la porte de sortie quand on s'est perdu ;
  • Lecture — le bruit ED masqué ;
  • Diagnostic — erreurs et avertissements, avec trois lignes de contexte.

Enregistrer… crée un profil ; les profils fournis ne sont ni modifiables ni supprimables. Dès qu'un filtre change, la liste repasse sur « Session courante » : un profil enregistré ne bouge jamais sans qu'on le demande.

Recherche

Trois modes, au choix dans la liste déroulante :

Mode . * ? Exemple
Texte littéral littéraux CSAR.lua
Jokers littéral jokers No taxiroad*Batumi*
Regex joker quantificateurs VEAF\|[WE]\|

En regex, ^ et $ désignent le début et la fin d'une ligne.

≠ inverse le critère, Aa respecte la casse. Ajouter au filtre empile le critère courant : les filtres se cumulent et s'affichent en pastilles retirables d'un clic.

La recherche porte sur la ligne et sur la trace de pile qui la suit : chercher un symbole présent uniquement dans la trace ramène l'erreur qui l'a produite.

Lignes de contexte

Un résultat seul ne dit pas grand-chose : c'est souvent ce qui l'entoure qui explique. Contexte de recherche, dans le panneau latéral, garde ±N lignes de part et d'autre de chaque résultat, comme le -C de grep. Il vaut 0 par défaut : tant qu'on n'y touche pas, une recherche rend exactement ses lignes.

Le champ ± de la barre de recherche donne une portée propre à un critère ; laissé vide, il suit la valeur commune. Quand plusieurs critères sont actifs, c'est la portée la plus large qui s'applique. Un critère inversé (≠) n'a pas de résultat à entourer et n'entre pas dans le calcul.

Les filtres restent prioritaires. Une ligne masquée par son niveau, sa source ou sa famille de bruit le reste, même collée à un résultat : le contexte élargit la recherche, il ne défait pas un filtre. Chercher ERROR avec ±2 en ayant mis INFO à ✕ ne fait pas revenir les INFO voisines.

Les deux contextes se composent : une catégorie en ◐ apparaît autour d'un résultat de recherche selon sa portée à elle.

Lire une ligne en entier

Le détail, sous la table. Cliquer une ligne — n'importe laquelle, pas seulement une erreur — l'affiche en entier en dessous, trace de pile comprise. La séparation se tire à la souris, et Ctrl+I referme le panneau quand on veut toute la hauteur pour la table.

L'ascenseur horizontal. La colonne Message est dimensionnée sur le plus long message du journal : une ligne qui dépasse la fenêtre se lit en défilant vers la droite. Tirer la colonne à la main fige la largeur choisie ; changer de police rend la main.

La taille du texte. Les boutons A− / A+ en haut à droite, Ctrl++ / Ctrl+-, ou Ctrl+molette sur la table. Ctrl+0 revient à la taille d'origine et Affichage → Police… choisit une autre police à chasse fixe. Le choix vaut pour tous les onglets et se retrouve à la session suivante.

Copier

Un bloc de lignes les sélectionner dans la table (Maj+clic, Ctrl+clic), puis Ctrl+C
Une partie d'une ligne la sélectionner au caractère dans le panneau de détail, puis Ctrl+C

Une ligne copiée emporte sa trace de pile : coller un Mission script error sans elle donnerait une erreur que rien n'explique. Le clic droit propose en plus de copier le message sans l'en-tête DCS, quand on ne veut ni l'horodatage ni le sous-système.

Expliquer ce qui est affiché

Analyse → Expliquer ce qui est affiché, ou Ctrl+E. La réponse arrive en deux couches, séparées à l'écran, et l'ordre entre elles est tout le sujet.

Le catalogue répond en premier. Chaque motif reconnu par rules.json est rendu avec son propre texte, repris tel quel. Aucun modèle, aucun coût, aucun réseau : cette couche seule est déjà une réponse, et c'est le mode dégradé du reste.

Le modèle met en contexte ensuite, seulement si on appuie sur Analyser en ligne. Il reçoit l'extrait et les motifs déjà appariés localement, et il enchaîne : ce qui s'est passé en premier, ce qui n'en est qu'une conséquence, la ligne sur laquelle agir. Là où le catalogue est muet, il dit motif non catalogué au lieu de proposer une cause.

Le pire échec de cette fonction n'est pas le silence, c'est une réponse plausible et fausse : « ça vient de votre module X » alors que non. Le lecteur n'a aucun moyen de la distinguer d'une bonne, et il y passera sa soirée. C'est pourquoi les deux couches sont titrées séparément plutôt que suivies d'un avertissement en bas, que personne ne lit.

Sans réseau, sans quota, derrière un proxy d'entreprise : la couche catalogue répond et aucune boîte d'erreur n'apparaît.

Ce qui part de la machine

Rien, tant qu'on n'a pas appuyé sur Analyser en ligne. À ce moment-là part l'extrait affiché, borné (une limite en caractères, les omissions annoncées) et caviardé par le même code que veaf-tools doctor : nom de compte Windows → <user>, adresses IP → <ip>, adresses e-mail → <email>, jetons et mots de passe → <redacted>. Les noms de missions, d'appareils et d'armements sont conservés : ce sont eux qui disent sur quoi ça a planté.

L'en-tête de l'extrait déclare aussi les catégories mises à ✕. Un journal filtré jusqu'à « aucune erreur » parce qu'on a décoché ERROR ne doit pas se lire comme un journal propre.

Motifs récurrents non catalogués

Un message qui revient et que rules.json n'explique pas est une entrée manquante du catalogue. L'analyse en propose une, dans la forme du fichier, identifiants et valeurs variables déjà remplacés par des jokers.

Rien n'est écrit dans rules.json : une proposition reste une proposition, et c'est précisément parce que le catalogue est tenu à la main que son texte est citable. Le help généré n'explique volontairement rien, il dit qu'il est à reformuler.

Préparer un rapport

Le bouton Préparer un rapport assemble en un bloc la sortie de veaf-tools doctor, l'extrait borné et caviardé, les motifs du catalogue et ce que l'analyse a conclu — y compris ce qu'elle n'a pas su expliquer — et le met dans le presse-papier, prêt à coller dans #support ou dans une issue.

C'est un collage, pas un envoi : rien ne part tout seul. Le bloc est taillé pour tenir dans un message Discord ; quand tout ne rentre pas, il annonce ce qu'il a retiré au lieu d'être coupé en silence.

Son format est versionné et documenté : voir Format du bloc de rapport.

Ce qu'il comprend du journal

Les traces de pile restent avec leur erreur. Un Mission script error suivi de son stack traceback forme une seule entrée : filtrer sur les erreurs ne fait plus disparaître l'explication. La ligne porte alors un [+3] qui dit combien de lignes sont repliées derrière elle.

Le niveau affiché est le niveau réel. DCS journalise tout le Lua en INFO SCRIPTING, y compris un VEAF|W| qui est un avertissement. veaf-logs le lit dans le préfixe : filtrer sur WARNING fait bien remonter les avertissements VEAF, CTLD ou CSAR.

Les archives s'ouvrent directement. Les .zip de Saved Games\DCS\Logs contiennent aussi le vidage mémoire, la mission et le rapport dxdiag : le journal est choisi tout seul.

Le journal n'est jamais verrouillé. Sous Windows, un fichier maintenu ouvert ne peut pas être renommé — et c'est ce que DCS fait de son dcs.log à chaque lancement. veaf-logs ouvre et referme à chaque lecture : il n'empêche jamais le jeu de démarrer.

Autres journaux d'un serveur

Un serveur DCS écrit d'autres journaux que dcs.log, chacun avec sa propre forme de ligne. veaf-logs reconnaît celles-ci, avec l'heure, le niveau et l'émetteur de chaque ligne :

Journal Exemple de ligne Source affichée
DCSServerBot (logs\dcssb-*.log, journal du chat) 2026-09-29 18:02:44.490 WARNING⇥New update… DCSSB
DCSServerBot, performances (perf-*.log) 2026-09-29 18:03:38,541⇥INFO⇥4.80s⇥… DCSSB
DCSServerBot, exceptions (async_errors.log) 2026-05-26T00:47:56.023909: Task exception… DCSSB, en ERROR
Real Weather (realweather.log) 2026-09-29T20:03:44.385+0200⇥WARN⇥… Real Weather
LotAtc (LotAtc_server.log) [2026-09-29 20:05:21 +02:00] [W] [clienthandler] Init LotAtc

La forme est reconnue ligne par ligne, pas fichier par fichier : DCSServerBot recopie la sortie de Real Weather telle quelle dans son propre journal, et ces lignes y apparaissent avec la source Real Weather et leur vrai niveau (WARN en avertissement, FATAL en alerte).

Les préfixes des scripts (VEAF, CTLD…) ne sont cherchés que dans les lignes de dcs.log : ailleurs, un message qui contient « SRS » ne vient pas du script SRS.

Cinq familles de bruit sont propres à ces journaux, masquées par les profils Lecture et Diagnostic :

Famille Ce que c'est Part du journal
DCSSB : événements de mission chaque événement de mission relayé du serveur au bot, en JSON 70 %
DCSSB : interrogations périodiques état, charge et images par seconde demandés chaque minute 3,5 %
LotAtc : options au démarrage la liste des options, recopiée à chaque démarrage 19 %
LotAtc : camp des aérodromes le camp de chaque aérodrome, à chaque démarrage de mission 17 %
LotAtc : connexions des clients connexion et déconnexion d'un client, y compris les avertissements Init, Finish et The remote host closed the connection d'une déconnexion normale 33 %

Mesuré le 2026-09-29 sur les journaux de dcs.veaf.org : sur 4 090 entrées du journal DCSServerBot, il en reste 1 084 une fois le bruit écarté ; sur 8 735 entrées LotAtc, 2 669.

Un fichier dont aucune ligne n'a de forme connue s'affiche ligne à ligne, au niveau UNKNOWN. Le panneau des filtres ne liste que les sources et les familles de bruit présentes dans le journal ouvert.

Pour suivre l'un de ces journaux à distance, on le déclare comme un dcs.log dans logs: (Journal d'un serveur distant), par exemple dcssb: C:/Users/veaf/VEAF-DCSServerBot/logs/dcssb-dcs-veaf-org.log.

Gros journaux

Le texte n'est pas chargé en mémoire : seul un index compact l'est, et les lignes sont décodées à l'affichage. Sur un journal de serveur de 130 Mo (954 086 entrées) :

Fenêtre affichée 0,04 s
Indexation complète 2,4 s, en tâche de fond
Mémoire, journal ouvert 116 Mo

L'indexation se poursuit pendant qu'on lit et qu'on filtre ; une barre de progression apparaît, avec un bouton pour l'interrompre — ce qui est déjà indexé reste utilisable.

Raccourcis

Ctrl+D ouvrir le dcs.log courant
Ctrl+O ouvrir un fichier
Ctrl+W fermer l'onglet
Ctrl+F chercher
Ctrl+C copier la sélection
Ctrl+Maj+C copier sans l'en-tête DCS
Ctrl+A tout sélectionner
F suivre la fin du fichier / mettre en pause
Ctrl+Fin aller à la fin
Ctrl+R tout afficher
Ctrl+S enregistrer le profil
Ctrl++ / Ctrl+- agrandir / réduire la police
Ctrl+0 taille de police par défaut
Ctrl+molette agrandir / réduire la police
Ctrl+I afficher ou masquer le panneau de détail
Ctrl+B afficher ou masquer le panneau des filtres
F5 recharger le catalogue de règles

Ajouter ses propres règles

Les sources reconnues, les couleurs et les familles de bruit vivent dans un seul catalogue, rules.json, intégré à l'exécutable. Le menu Règles en ouvre une copie modifiable, que F5 recharge sans quitter l'application. Cette copie ne vaut que pour la session en cours : une règle qui doit durer va dans src/python/veaf-tools/veaf_logs/rules.json du dépôt, et arrive avec la release suivante.

Un script maison tient en une entrée :

{
  "id": "monscript",
  "label": "MonScript",
  "color": "#ff8800",
  "match": "^MONSCRIPT\\|(?P<lvl>[A-Z])\\|",
  "level_group": "lvl",
  "level_map": {"D": "DEBUG", "I": "INFO", "W": "WARNING", "E": "ERROR"}
}

Une famille de bruit à écarter :

{
  "id": "mon_bruit",
  "label": "Libellé court affiché",
  "help": "Phrase d'explication montrée en infobulle.",
  "default_hidden": true,
  "match": "le motif à masquer",
  "regex": true
}

Les motifs sont appliqués sur des octets pour que l'indexation reste rapide : ils doivent rester en ASCII. Le nombre de familles de bruit est plafonné à 64.

Une famille propre à un autre journal que dcs.log précise son format avec "formats": ["dcssb"] (ou realweather, lotatc) : elle n'est alors cherchée que dans les lignes de ce journal. Les préfixes de sources (match) ne s'appliquent, eux, qu'aux lignes de dcs.log.

Où sont rangés les réglages

Session (fichiers ouverts, filtres, géométrie, police, panneaux affichés) %APPDATA%\veaf_logs\session.json
Profils %APPDATA%\veaf_logs\profiles.json
Serveurs distants (servers:) ~\veafmct.yaml
Clé de l'API d'un serveur le fichier de son api_key_file
Alias des onglets ~\veafmct.yaml (log_aliases:, et aliases: de chaque serveur)
Miroir local d'un journal distant %TEMP%\veaf-logs-*.log, supprimé à la fermeture de l'onglet