Article
· 4 hr il y a 5m de lecture

Exemple de fonction table: requête du journal des erreurs d'application

Commençons par une question simple et motivante : au cours des 14 derniers jours, quelles sont les erreurs les plus courantes dans le Journal des erreurs d'application?

Répondre à cette question via le portail de gestion ou le terminal est un processus manuel fastidieux. Nous devrions pouvoir simplement utiliser SQL. Heureusement, quelques requêtes de classe sont disponibles  pour vous aider dans la classe SYS.ApplicationError de l'espace de noms %SYS. Vous pouvez répondre à cette question pour une seule date à l'aide d'une commande telle que:

select "Error message",count(*)
from SYS.ApplicationError_ErrorList('CCR','12/16/2024')
group by "Error message"
order by 2 desc

Malheureusement, la structure des requêtes de classe est soumise aux mêmes contraintes structurelles générales que les pages du portail de gestion ; la requête ErrorList nécessite un espace de noms et une date. Il existe sûrement une meilleure approche que de faire 14 appels conjoints à cette requête de classe pour différentes dates, n'est-ce pas ? D'une certaine manière, c'est un véritable problème. S'il existe une bonne façon de procéder avec du SQL classique et que je l'ai simplement manquée, merci de me le faire savoir!

Logiquement, il convient de rédiger notre propre requête de classe personnalisée. Cela implique d'ajouter un membre de classe Query (par exemple <QueryName>) et d'implémenter des méthodes nommées <QueryName>Execute, <QueryName>Fetch et <QueryName>Close. De manière générale, la méthode Execute configure le contexte de la requête de classe et effectue toutes les tâches initiales, en conservant l'état dans qHandle. La méthode Fetch récupère une seule ligne et indique si toutes les lignes ont été trouvées ou non. Enfin, la méthode Close effectue le nettoyage final. Par exemple, si l'implémentation des méthodes Execute/Fetch utilise une variable globale privée au processus, la méthode Close peut la supprimer.

N'oubliez pas d'ajouter un indicateur [ SqlProc ] magique au membre Query afin qu'il puisse être appelé en tant que TVF (fonction table) à partir d'autres requêtes SQL!

Ci-dessous, vous trouverez un exemple complet fonctionnel:

/// Requêtes utilitaires pour aider à accéder au journal des erreurs de l'application à partir de SQL
Class AppS.Util.ApplicationErrorLog
{

/// Renvoi de toutes les erreurs d'application (toutes dates confondues) à partir du journal des erreurs d'application
Query All() As %Query(ROWSPEC = "Date:%Date,ErrorNumber:%Integer,ErrorMessage:%String,Username:%String") [ SqlProc ]
{
}

/// Récupèration d'une liste de dates comportant des erreurs et la stocke dans qHandle
ClassMethod AllExecute(ByRef qHandle As %Binary) As %Status
{
    Set ns = $Namespace
    New $Namespace
    Set $Namespace = "%SYS"
    Set stmt = ##class(%SQL.Statement).%New()
    Set stmt.%SelectMode = 0
    Set result = ##class(%SQL.Statement).%ExecDirect(stmt,"select %DLIST(""Date"") ""Dates"" from SYS.ApplicationError_DateList(?)",ns)
    $$$ThrowSQLIfError(result.%SQLCODE,result.%Message)
    If 'result.%Next(.sc) {
        Return sc
    }
    Set qHandle("list") = result.%Get("Dates")
    Set qHandle("pointer") = 0
    Quit $$$OK
}

/// Récupèreation de la ligne suivante, en passant à la date suivante si nécessaire
ClassMethod AllFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = AllExecute ]
{
    Set sc = $$$OK
    Set ns = $Namespace
    New $Namespace
    Set $Namespace = "%SYS"
    If $Get(qHandle("dateResult")) = "" {
        // Passage à la date suivante
        Set pointer = qHandle("pointer")
        If '$ListNext(qHandle("list"),pointer,oneDate) {
            Set AtEnd = 1
            Quit $$$OK
        }
        Set qHandle("pointer") = pointer
        Set qHandle("currentDate") = oneDate
        Set qHandle("dateResult") = ##class(%SQL.Statement).%ExecDirect(,"select * from SYS.ApplicationError_ErrorList(?,?)",ns,oneDate)
        $$$ThrowSQLIfError(qHandle("dateResult").%SQLCODE,qHandle("dateResult").%Message)
    }
    If qHandle("dateResult").%Next(.sc) {
        // Si nous avons une ligne pour la date actuelle, ajoutons-la
        Set Row = $ListBuild(qHandle("currentDate"),qHandle("dateResult").%GetData(1),qHandle("dateResult").%GetData(2),qHandle("dateResult").%GetData(6))
    } ElseIf $$$ISOK(sc) {
        // Sinon, il faut vider le jeu de résultats et appeler AllFetch pour avancer
        Set qHandle("dateResult") = ""
        Set $Namespace = ns
        Set sc = ..AllFetch(.qHandle,.Row,.AtEnd)
    }
    Quit sc
}

ClassMethod AllClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllExecute ]
{
    New $Namespace
    Set $Namespace = "%SYS"
    // Il semble parfois nécessaire pour que %OnClose s'exécute correctement
    Kill qHandle("dateResult")
    Quit $$$OK
}

}

Dans cet exemple, nous commençons dans un espace de noms utilisateur, mais toutes les requêtes s'exécutent en réalité dans %SYS. Execute obtient une liste des dates d'erreur pour l'espace de noms actuel et la stocke dans qHandle. Fetch passe à la date suivante lorsque cela est approprié, puis renvoie l'erreur suivante pour la date actuelle. Et Close s'assure que la requête de classe sort de la portée dans %SYS, car j'obtenais parfois des erreurs si ce n'était pas le cas. C'était un peu surprenant, mais cela semble logique, car la requête de classe que nous appelons n'existe que dans %SYS.

La réutilisabilité des fonctions table offre de nombreuses possibilités. Par exemple, nous pouvons en ajouter une autre dans la même classe:

/// Obtenir le nombre d'erreurs survenues au cours des derniers <var>Days</var> jours
Query ErrorCounts(Days As %Integer) As %SQLQuery(ROWSPEC = "Occurrences:%Integer,ErrorMessage:%String") [ SqlProc ]
{
    SELECT COUNT(*) AS Occurrences, ErrorMessage
    FROM AppS_Util.ApplicationErrorLog_All()
    WHERE DATEDIFF(D,"Date",$h) <= :Days
    GROUP BY ErrorMessage
    ORDER BY Occurrences DESC
}

Et maintenant, pour obtenir les erreurs d'application les plus courantes au cours des 14 derniers jours, il suffit de:

call AppS_Util.ApplicationErrorLog_ErrorCounts(14)

Maintenant, il ne nous reste plus qu'à les corriger! 😅

Discussion (0)2
Connectez-vous ou inscrivez-vous pour continuer