Il est courant de devoir consigner certains évènements survenant lors de l'utilisation d'une application, VBA ou autre, et ces données sont souvent écrites dans un fichier de log. Que ce soit à l'ouverture ou la fermeture du fichier, lorsque certaines erreurs surviennent, ou lors de manipulations délicates des données, conserver une trace de "ce qui s'est passé" est un bon moyen de pouvoir suivre l'utilisation d'une appli et de la déboguer rapidement lorsqu'un problème survient.
Le traçage d'un fichier Excel à l'aide d'un mouchard (??), l'historique d'un fichier, la liste des utilisateurs du fichiers et ce qu'on y fait (sélection d'une feuille que l'on pense inutilisée, par exemple, ...) sont des activités qui peuvent renseigner sur l'utilisation d'un fichier et donner des pistes d'améliorations intéressantes.
Cadre de travail
Dans ce billet, je généralisais l'écriture dans un fichier texte grâce à une procédure générique, de manière à ajouter de l'abstraction et à ne plus devoir se préoccuper de la technique d'écriture dans un fichier. En effet, devoir écrire dans un fichier texte requiert de savoir ouvrir un fichier (un "canal"), d'utiliser des instructions pour dire que l'on remplace le contenu ou que l'on en ajoute, sans oublier bien entendu de fermer le fichier après écriture. Tout cela peut être inclus dans une procédure au nom évocateur de WriteLinesInTextFile qui est appelée comme une instruction native du VBA et qui pourrait être stockée dans un module "Tools" comme je l'explique dans ce billet, puisque générique à VBA et donc, non spécifique à une techno particulière.
Code vba : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Sub WriteLinesInTextFile(Filename As String, Lines, Optional Replace As Boolean) Dim Channel As Long Dim i As Long Channel = FreeFile If Replace Then Open Filename For Output As Channel Else Open Filename For Append As Channel End If For i = LBound(Lines) To UBound(Lines) Print #Channel, Lines(i) Next i Close Channel End Sub |
Code : | Sélectionner tout |
Tools.WriteLinesInTextFile "c:\data\temp\Monfichier.txt", Array("Pierre", "Martine", "Manon"), True
Cahier des charges
Poursuivons dans la systématisation de notre approche de programmation en formalisant l'écriture de logs lors de l'utilisation de notre application.
En général, un fichier de log est créé pour être aisément lisible. Ce sera donc souvent un simple fichier texte dons la ligne est structurée, plus rarement un CSV, même si ce format peut être pratique pour une exploitation rapide en Excel. Chaque développeur fera "à sa sauce" en la matière. Ce qui importe, c'est que le fichier soit exploitable et qu'il permette de savoir rapidement ce qui s'est passé dans l'appli. On pourrait ainsi avoir un fichier de log qui ne reprend que les erreurs survenues durant l'exécution, un log à l'ouverture et la fermeture, un log à l'enregistrement du fichier, etc...
Pour l'exemple, notre cahier des charges mentionne que l'on souhaite:
- un log pour l'ouverture du fichier;
- un log pour la fermeture du fichier;
- un log pour la sauvegarde du fichier;
- un log pour certaines erreurs que l'on veut pouvoir tracer;
- le log reprendra également le moment du log, ainsi que l'utilisateur actif.
Création de la ligne de log
Regardons d'abord où et comment coder l'écriture du log.
Dans la mesure où le log dépend de l'application, il semblera normal que la fonction d'écriture du log se trouve dans le module de l'application. Fidèle à mes bonnes pratiques de codage et comme je le mentionne dans un billet de blog cité plus haut, mes développements contiennent un module appTools qui reprend des fonctions spécifiques de mon application (récupération de paramètres, version et date de l'application, nom de l'application à utiliser dans les msgbox, etc). Tout naturellement, j'insérerai dans ce module la fonction WriteLog qui reprendra les données prévues dans le cahier des charges.
Code vba : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | Sub WriteLog(Message As String) Dim DateLog As String Dim FileName As String DateLog = Format(Now, "yyyy-mm-ddThh:nn:ss") FileName = ThisWorkbook.Path & "\" & Replace(ThisWorkbook.Name, ".", "_") & ".log" Message = DateLog & " | " & Environ("username") & " | " & Message Tools.WriteLinesInTextFile FileName, Array(Message), False End Sub |
Normalement, cette fonction se passe de commentaires. Elle formate la ligne du log en reprenant le moment du log normalisé, le nom de l'utilisateur puis le message reçu en argument. Elle crée aussi le nom du fichier de log qui reprend le nom du fichier et lui ajoute l'extension .log. Lorsque la ligne de log est créée, la fonction appelle la fonction générique Tools.WriteLinesInTexteFile vue en début de billet. On remarque donc ici que le programmeur n'a pas besoin de savoir comment on écrit dans un fichier texte avec Open, Append, Close & Cie, il utilise la fonction de appTools qui elle-même utilise la fonction du Tools. On lui a donc bien permis de faire abstraction de toute la mécanique d'écriture.
Création des logs lors d'évènements ou à des moments clé de l'exécution
Il suffit maintenant au programmeur d'appeler la fonction appTools.WriteLog lors de la survenance d'évènements ou erreurs, ou à des passages clé du code. Pour coller au cahier des charges, voici les évènements de classeur qui doivent déclencher l'écriture d'un log. On remarque ici que l'on se contente de passer le message à WriteLog puisque c'est cette fonction qui va composer la ligne du log.
Code vba : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | Private Sub Workbook_BeforeClose(Cancel As Boolean) appTools.WriteLog "Fermeture du fichier" End Sub Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean) appTools.WriteLog "Enregistrement du fichier" End Sub Private Sub Workbook_Open() appTools.WriteLog "Ouverture du fichier" End Sub |
Voici également une erreur qui doit déclencher l'écriture du log (c'est, évidemment, pour l'exemple). On remarquera que comme le message est un peu particulier puisqu'il reprend le numéro et la description de l'erreur, on passe par un appTools.LogError qui utilise l'erreur active pour constituer le message qui sera envoyé à appTools.WriteLog qui lui même formatera la ligne pour l'envoyer à Tools.WriteLinesInTextFile.
Code vba : | Sélectionner tout |
1 2 3 4 5 6 7 8 | Sub Test() On Error GoTo Catch Debug.Print 5 / 0 Catch: If Err <> 0 Then appTools.LogError End Sub |
Code : | Sélectionner tout |
1 2 3 4 5 6 | Sub LogError() Dim Message As String Message = Err.Number & " - " & Err.Source & " - " & Err.Description WriteLog Message End Sub |
Voici, pour l'exemple, le contenu du fichier Log après quelques utilisations (ouverture, erreur division par zéro, enregistrement, fermeture, puis à nouveau ouverture, et fermeture après travail et demande d'enregistrement lors de la fermeture).
Code text : | Sélectionner tout |
1 2 3 4 5 6 7 | 2021-09-16T20:22:41 | PierreFauconnier | Ouverture du fichier 2021-09-16T20:33:44 | PierreFauconnier | 11 - VBAProject - Division par zéro 2021-09-16T20:33:46 | PierreFauconnier | Enregistrement du fichier 2021-09-16T20:33:53 | PierreFauconnier | Fermeture du fichier 2021-09-16T20:35:29 | PierreFauconnier | Ouverture du fichier 2021-09-16T20:38:15 | PierreFauconnier | Fermeture du fichier 2021-09-16T20:38:18 | PierreFauconnier | Enregistrement du fichier |
Conclusions
On remarque, à nouveau, qu'une approche systématique de nos développements nous permet de créer du code récupérable d'un projet à l'autre. La constitution de bibliothèques contenant VOS fonctions et procédures va vous permettre un gain de temps et une fiabilité accrue sur vos développements.
N'hésitez pas à commenter ce billet. C'est toujours intéressant d'avoir un retour sur mes astuces et bonnes pratiques