Gestion des journaux et alarmes dans un système de contrôle de mouvement avec C# et SQLite

Notes d'étude sur l'enregistrement des opérations, des journaux et des alarmes, avec requêtes via SQLite.

Analyse : que faut-il enregistrer ? Catégories

  • Enregistrements d'opérations : modification de paramètres, commandes manuelles (boutons de contrôle), connexion, déconnexion.
  • Informations de journal : liste AddLog de la fenêtre de supervision, également écrite en base.
  • Informations d'alarme : AddAlarm affiché dans la fenêtre de supervision, également écrit en base ; alarmes de position non atteinte, alarmes de servomoteur.

1. Interafce utilisateur des alarmes

(L'interface utilisateur de l'écran de requête est décrite dans la section 5.)

2. Classe d'entité

namespace ModelesMagasinAutomatique
{
    /// <summary>
    /// Classe de journal système
    /// </summary>
    public class JournalSysteme
    {
        public JournalSysteme() { }

        public JournalSysteme(string message, string etatAlarme, TypeJournal type, string utilisateur)
        {
            Horodatage = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            Message = message;
            EtatAlarme = etatAlarme;
            Type = type;
            Utilisateur = utilisateur;
        }

        public string Horodatage { get; set; }
        public string Message { get; set; }
        /// <summary>
        /// État de l'alarme : déclenchement ou acquittement
        /// </summary>
        public string EtatAlarme { get; set; }
        /// <summary>
        /// Type de journal (énumération)
        /// </summary>
        public TypeJournal Type { get; set; }
        public string Utilisateur { get; set; }
    }

    /// <summary>
    /// Types de journaux
    /// </summary>
    public enum TypeJournal
    {
        Info,
        Alarme,
        Operation
    }
}

Les noms de classe et propriétés doivent correspondre aux noms de table et de colonnes.

3. Classe d'accès aux données (DAL)

using System;
using System.Collections.Generic;
using System.Linq;
using ModelesMagasinAutomatique;
using SqlSugar;

namespace AccesDonneesMagasin
{
    public class ServiceJournal
    {
        /// <summary>
        /// Insère un enregistrement de journal
        /// </summary>
        public static bool AjouterJournal(JournalSysteme journal)
        {
            return HelperSqlSugar.ClientSqlSugar.Insertable(journal).ExecuteCommand() == 1;
        }

        /// <summary>
        /// Recherche des journaux selon des critères
        /// </summary>
        public static List<JournalSysteme> ObtenirJournauxParCondition(
            string debut, string fin, string typeJournal, string etatAlarme)
        {
            var requete = HelperSqlSugar.ClientSqlSugar.Queryable<JournalSysteme>()
                .Where(j => SqlFunc.Between(j.Horodatage, debut, fin));

            if (!string.IsNullOrEmpty(typeJournal))
            {
                var typeEnum = (TypeJournal)Enum.Parse(typeof(TypeJournal), typeJournal);
                requete = requete.Where(j => j.Type == typeEnum);
            }

            if (!string.IsNullOrEmpty(etatAlarme))
            {
                requete = requete.Where(j => j.EtatAlarme == etatAlarme);
            }

            return requete.ToList();
        }
    }
}

4. Utilisation

4.1 Enregistrement de connexion

// Exemple dans l'événement de connexion réussie
ServiceJournal.AjouterJournal(new JournalSysteme("Connexion réussie", "Déclenché", TypeJournal.Operation, nomUtilisateur));

4.2 Enregistrement de déconnexion système

private void FormPrincipale_FormClosing(object sender, FormClosingEventArgs e)
{
    // Écrire en base
    if (!ServiceJournal.AjouterJournal(new JournalSysteme("Fermeture système", "Déclenché", TypeJournal.Operation, Program.AdminGlobal.NomUtilisateur)))
    {
        NLogHelper.Error("Erreur d'écriture du journal de déconnexion en base");
    }
    cts.Cancel();
}

4.3 Enregistrement de modification de paramètres

/// <summary>
/// Modifie un paramètre (réutilisable pour différents types d'objets)
/// </summary>
private void ModifierParametre<T>(string typeParametre, T objet)
{
    var sb = new System.Text.StringBuilder();

    foreach (var controle in this.tabControle.SelectedTab.Controls)
    {
        if (controle is NumericUpDown numeric)
        {
            string nomPropriete = numeric.Name;
            string valeurActuelle = ObtenirValeurPropriete(objet, nomPropriete);
            if (!string.IsNullOrEmpty(valeurActuelle) && numeric.Value.ToString() != valeurActuelle)
            {
                sb.Append(numeric.Tag.ToString() + " modifié à : " + numeric.Value.ToString() + "; ");
                DefinirValeurPropriete(objet, nomPropriete, numeric.Value.ToString());
            }
        }
        else if (controle is ComboBox cmb)
        {
            string nomPropriete = cmb.Name;
            string valeurActuelle = ObtenirValeurPropriete(objet, nomPropriete);
            if (!string.IsNullOrEmpty(valeurActuelle) && cmb.Text != valeurActuelle)
            {
                sb.Append(cmb.Tag.ToString() + " modifié à : " + cmb.Text + "; ");
                DefinirValeurPropriete(objet, nomPropriete, cmb.Text);
            }
        }
    }

    if (sb.Length > 0)
    {
        var resultat = typeParametre == "Paramètres de base"
            ? moteurEx.SauvegarderParametresBase()
            : moteurEx.SauvegarderParametresAvances();

        if (resultat.Reussi)
        {
            if (!ServiceJournal.AjouterJournal(new JournalSysteme(sb.ToString(), "Déclenché", TypeJournal.Operation, Program.AdminGlobal.NomUtilisateur)))
            {
                NLogHelper.Error("Erreur d'écriture de la modification de paramètre en base");
            }
            MessageBox.Show(typeParametre + " modifié avec succès", "Modification paramètre");
        }
        else
        {
            MessageBox.Show(typeParametre + " échec de modification", "Modification paramètre");
        }
    }
    else
    {
        MessageBox.Show("Aucune modification détectée", "Modification paramètre");
    }
}

4.4 Informations de journal et d’alarme

/// <summary>
/// Ajoute un message de journal
/// </summary>
public void AjouterLog(int niveau, string message)
{
    if (!this.listeInfos.InvokeRequired)
    {
        var item = new ListViewItem(HeureCourante, niveau);
        item.SubItems.Add(message);
        this.listeInfos.Items.Insert(0, item);
        if (this.listeInfos.Items.Count > 100)
            this.listeInfos.Items.RemoveAt(100);
    }
    else
    {
        this.listeInfos.Invoke(new Action(() =>
        {
            var item = new ListViewItem(HeureCourante, niveau);
            item.SubItems.Add(message);
            this.listeInfos.Items.Insert(0, item);
            if (this.listeInfos.Items.Count > 100)
                this.listeInfos.Items.RemoveAt(100);
        }));
    }

    // Écrire en base
    if (!ServiceJournal.AjouterJournal(new JournalSysteme(message, "Déclenché", TypeJournal.Info, Program.AdminGlobal.NomUtilisateur)))
    {
        NLogHelper.Error("Erreur d'écriture du journal info en base");
    }
}

/// <summary>
/// Ajoute une alarme (déclenchement/acquittement)
/// </summary>
public void AjouterAlarme(string info, bool estDeclenchement)
{
    if (estDeclenchement)
    {
        if (!ListeAlarmes.Contains(info))
            ListeAlarmes.Add(info);
    }
    else
    {
        if (ListeAlarmes.Contains(info))
            ListeAlarmes.Remove(info);
    }
    RafraichirAlarme();

    // Écrire en base
    if (!ServiceJournal.AjouterJournal(new JournalSysteme(info, estDeclenchement ? "Déclenché" : "Acquitté", TypeJournal.Alarme, Program.AdminGlobal.NomUtilisateur)))
    {
        NLogHelper.Error("Erreur d'écriture de l'alarme en base");
    }
}

5. Écran de requête des journaxu

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using ModelesMagasinAutomatique;
using AccesDonneesMagasin;

namespace ApplicationMagasin
{
    public partial class FormulaireJournal : Form
    {
        public FormulaireJournal()
        {
            InitializeComponent();

            this.gridDonnees.AutoGenerateColumns = false;
            this.gridDonnees.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;

            this.gridDonnees.Columns[0].Width = 150;
            this.gridDonnees.Columns[1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            this.gridDonnees.Columns[2].Width = 100;
            this.gridDonnees.Columns[3].Width = 100;
            this.gridDonnees.Columns[4].Width = 100;

            this.chkInfo.Checked = true;
            this.rdbDeclenche.Checked = true;
            this.dateDebut.Value = this.dateFin.Value.AddHours(-3.0);
        }

        private void BtnRechercher_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                this.Invoke(new Action(() =>
                {
                    ExecuterRecherche(
                        DateTime.Now.ToString(this.dateDebut.Text),
                        DateTime.Now.ToString(this.dateFin.Text),
                        ObtenirTypesJournal(),
                        ObtenirEtatAlarme()
                    );
                }));
            });
        }

        private void BtnRechercherAujourdhui_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                this.Invoke(new Action(() =>
                {
                    ExecuterRecherche(
                        DateTime.Now.ToString("yyyy-MM-dd 00:00:00"),
                        DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                        ObtenirTypesJournal(),
                        ObtenirEtatAlarme()
                    );
                }));
            });
        }

        private List<string> ObtenirTypesJournal()
        {
            var types = new List<string>();
            if (chkInfo.Checked) types.Add(chkInfo.Text);
            if (chkAlarme.Checked) types.Add(chkAlarme.Text);
            if (chkOperation.Checked) types.Add(chkOperation.Text);
            return types;
        }

        private string ObtenirEtatAlarme()
        {
            if (rdbDeclenche.Checked) return "Déclenché";
            if (rdbAcquite.Checked) return "Acquitté";
            return string.Empty;
        }

        private void ExecuterRecherche(string debut, string fin, List<string> types, string etat)
        {
            DateTime t1 = Convert.ToDateTime(debut);
            DateTime t2 = Convert.ToDateTime(fin);
            if (t1 > t2)
            {
                MessageBox.Show("La date de début ne peut pas être postérieure à la date de fin.", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            if (etat == "Tous") etat = string.Empty;

            var resultats = new List<JournalSysteme>();
            foreach (var type in types)
            {
                resultats.AddRange(ServiceJournal.ObtenirJournauxParCondition(debut, fin, type, etat));
            }

            // Trier par horodatage (important car les résultats sont groupés par type)
            resultats.Sort((j1, j2) =>
            {
                var dt1 = Convert.ToDateTime(j1.Horodatage);
                var dt2 = Convert.ToDateTime(j2.Horodatage);
                return DateTime.Compare(dt1, dt2);
            });

            this.gridDonnees.DataSource = null;
            this.gridDonnees.DataSource = resultats;
        }

        private void BtnExporter_Click(object sender, EventArgs e)
        {
            string chemin = HelperExcel.ExporterVersExcel(this.gridDonnees);
            if (chemin != null)
                MessageBox.Show("Export réussi.", "Export");
            else
                MessageBox.Show("Échec de l'export.", "Export");
        }
    }
}

6. Résultat visuel

La fenêtre de requête s'affiche avec les résultats triés par date, les filtres par type et état d'alarme, et un bouton d'export vers Excel.

Étiquettes: C# sqlite SqlSugar WinForms journalisation

Publié le 8 juillet à 23h02