#Gestion des erreurs

0 Abonnés · 4 Publications

La gestion des erreurs désigne les procédures de réponse et de récupération des conditions d'erreur présentes dans une application logicielle. Il s'agit du processus comprenant l'anticipation, la détection et la résolution des erreurs d'application, des erreurs de programmation ou des erreurs de communication.

En savoir plus.

Article Lorenzo Scalese · Oct 6, 2025 5m read

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')
groupby"Error message"orderby2desc

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 SQLClass 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 qHandleClassMethod AllExecute(ByRef qHandle As%Binary) As%Status { Set ns = $NamespaceNew$NamespaceSet$Namespace = "%SYS"Set stmt = ##class(%SQL.Statement).%New() Set stmt.%SelectMode = 0Set 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") = 0Quit$$$OK }

/// Récupèreation de la ligne suivante, en passant à la date suivante si nécessaireClassMethod AllFetch(ByRef qHandle As%Binary, ByRef Row As%List, ByRef AtEnd As%Integer = 0) As%Status [ PlaceAfter = AllExecute ] { Set sc = $$$OKSet ns = $NamespaceNew$NamespaceSet$Namespace = "%SYS"If$Get(qHandle("dateResult")) = "" { // Passage à la date suivanteSet pointer = qHandle("pointer") If '$ListNext(qHandle("list"),pointer,oneDate) { Set AtEnd = 1Quit$$$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-laSet 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 avancerSet qHandle("dateResult") = ""Set$Namespace = ns Set sc = ..AllFetch(.qHandle,.Row,.AtEnd) } Quit sc }

ClassMethod AllClose(ByRef qHandle As%Binary) As%Status [ PlaceAfter = AllExecute ] { New$NamespaceSet$Namespace = "%SYS"// Il semble parfois nécessaire pour que %OnClose s'exécute correctementKill 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! 😅

0
0 18
Article Lorenzo Scalese · Juin 18, 2025 5m read

Le bon vieux temps

La classe %Library.DynamicObject existe dans IRIS depuis bien avant que IRIS ne devienne IRIS. Si vous l'utilisez depuis l'époque de Cache, vous souhaiterez peut-être vous familiariser avec certaines de ses modifications.

Dans Cache 2018, la méthode %Get n'avait qu'un seul argument. Il s'agissait de la clé permettant de récupérer les données dans le JSON. Ainsi, si votre objet JSON s'appelait myObj, cela ressemblerait à ceci:

{
    “mybool”:true,
    “mynum”:1234,
    “mystring”:”Hello World!”
}

L'exploitation de myObj.%Get(“mybool”) renverrait 1, myObj.%Get(“mynum”) renverrait 1234 et myObj.%Get("mystring") renverrait la chaîne "Hello World!" 

La définition de ces paramètres, en revanche, nécessitait un peu plus de travail. Par exemple, l'attribution d'une propriété JSON à 0 pouvait signifier le nombre 0, une valeur booléenne signifiant faux ou une chaîne littérale "0". C'est pourquoi la méthode %Set avait toujours un troisième argument facultatif. Pour créer l'objet JSON mentionné ci-dessus, nous pouvions utiliser le code suivant:

set myObj = ##class(%Library.DynamicObject).%New()
do myObj.%Set(“mybool”,1,”boolean”)
do myObj.%Set(“mynum”,1234,”number”)
do myObj.%Set(“mystring”,”Hello World!”,”string”)

Cependant, dans ce cas, nous pourrions également omettre le troisième argument des deux derniers. Ainsi, 1234 serait reconnu comme un nombre, car il n'est pas entre guillemets, tandis que "Hello World!" serait identifié comme une chaîne, car il est entre guillemets. Si nous voulions ajouter la valeur 1234 à l'objet en tant que chaîne, nous pourrions changer la chaîne en nombre. Nous pourrions également spécifier le type "null". Dans ce cas, la valeur doit être "". En pratique, cependant, nous ajoutons souvent ces valeurs à partir de variables dans notre code ObjectScript. Pour cette raison, il peut être préférable de spécifier cet argument, juste au cas où la variable serait une chaîne ou un nombre, afin de garantir que notre JSON arrive à destination correctement encodé.

Comment j'ai appris à ne plus m'inquiéter à propos de <MAXSTRING> et aimer JSON

Comme l'a dit un jour le sage Billy Joel, ""Le bon vieux temps n'était pas toujours bon, demain n'est pas aussi mauvais qu'il n'y paraît." La liste des types pour %Set s'est allongée, et la méthode %Get a acquis deux nouveaux arguments. Ce qui est crucial, c'est qu'elles prennent toutes deux en charge le type "stream". Si vous avez déjà traité de grandes quantités de données JSON, vous avez probablement déjà rencontré une erreur lorsque les données contenues dans le JSON dépassaient la longueur maximale autorisée pour une chaîne dans IRIS. La nouvelle méthode %Get vous permet de spécifier deux arguments supplémentaires, une valeur par défaut et un type. Votre ancien code continue toutefois de fonctionner, car ces deux arguments sont facultatifs et, s'ils sont omis, les méthodes fonctionnent exactement comme en 2018. Si rien n'est trouvé pour la clé donnée, la valeur par défaut est renvoyée. La fonction type fonctionne de manière similaire à l'argument type de la méthode %Set. Vous pouvez également spécifier le type de données que vous récupérez. Prenons l'exemple suivant du bloc try/catch:

try{
    Set mydata = myObj.%Get(“mydata”,”N/A”)
}
catch ex{
    if ex.Name = “<MAXSTRING>”{
        set mydata = myObj.%Get(“mydata”,,”stream”)
    }
}

Il tentera de définir mydata à la valeur située à l'intérieur de "mydata" dans l'objet JSON. Si cet élément n'existe pas, il renverra "N/A" à la place. Si cet élément est trop long pour une chaîne, le système lèvera une exception vers le bloc catch. Nous devons vérifier le nom de cette exception, car si une exception différente s'est produite, il ne serait pas logique d'essayer d'obtenir les données sous forme de flux. Vous pouvez en savoir plus sur la gestion des exceptions ici. Si la valeur est , nous spécifions que nous voulons récupérer mydata sous forme de flux. La récupération des données sous forme de flux renvoie un objet de la classe %Stream.DynamicCharacter. Cela ne déclenche jamais d'exception , mais peut générer une exception si la limite de mémoire du processus est dépassée.

Si vous suivez l'approche décrite ci-dessus, vous ne saurez pas si mydata dans le code est une chaîne ou un flux. Cela signifie que vous devrez suivre le code similaire à celui ci-dessous:

if$ISOBJECT(mydata){
    //Traitement des flux ici
}
else{
    //Traitement des chaînes ici
}

Vous pouvez également utiliser l'option stream chaque fois afin de vous assurer que vous avez toujours un flux à votre disposition. Cependant, cela entraînerait une utilisation inutile des ressources et alourdirait votre code.

Une autre option consiste à ajouter un flux à un objet dynamique à l'aide de %Set. Consultez l'exemple suivant:

set mystream = ##class(%Stream.FileBinary).%New()
do mystream.LinkToFile(“/path/to/your/file”)
do myObj.%Set(“mydata”,mystream,”stream”)

Les données de votre fichier seront désormais enregistrées dans le champ mydata de votre objet dynamique.

%Set et %Get encodent et décodent également les chaînes à l'aide de l'encodage Base64. 

Gardez toujours à l'esprit que l'encodage Base64 est un encodage et non un cryptage ! Il n'y a pas de clés secrètes ni de mots de passe pour décoder votre message, et il est facilement réversible. Par conséquent, vous devez toujours utiliser un protocole crypté, tel que TLS ou HTTPS, pour la transmission ! Base64 est utilisé pour transmettre des caractères non ASCII de manière à ce que les systèmes ASCII puissent les recevoir et les transmettre.

Maintenant que cette remarque importante est faite, nous pouvons enfin voir comment cela fonctionne. Si nous apportons une petite modification à l'exemple de code précédent, le contenu du flux de fichiers sera encodé en Base64.

set mystream = ##class(%Stream.FileBinary).%New()
do mystream.LinkToFile(“/path/to/your/file”)
do myObj.%Set(“mydata”,mystream,”stream>base64”)

D'autre part, si les données du fichier étaient déjà encodées en Base64 et que nous voulions les convertir en données décodées, il suffirait de changer un seul caractère.

set mystream = ##class(%Stream.FileBinary).%New()
do mystream.LinkToFile(“/path/to/your/file”)
do myObj.%Set(“mydata”,mystream,”stream<base64”)

Les signes " supérieur à" ou "inférieur à" indiquent toujours la direction dans laquelle la conversion a lieu. Si nous convertissons un flux non codé en une chaîne Base64, le signe pointera vers Base64. Si nous convertissons un flux codé en Base64 en un flux non codé, le signe pointera de Base64 vers le flux. La même fonctionnalité existe pour les chaînes lorsque string>base64 et string

set something = myObj.%Get(“something”,”NA”,”string>base64”)

Si l'élément "quelque chose" existe, il sera renvoyé sous sa forme encodée en Base64. Cependant, s'il n'existe pas, "NA" sera renvoyé sans être encodé.

Il existe une restriction concernant l'option d'encodage Base64. Seuls les caractères dont le code est compris entre 0 et 255 peuvent être encodés en Base64. Les codes de caractères supérieurs à 255 entraîneront une exception <WIDE CHAR>. FPar exemple, la ligne suivante provoquera une telle exception:

set mychar = $C(256)
do myobj.%Set(“mychar”,mychar,”string>base64”)

Alors, j'ai entendu dire que vous aimiez JSON . . .

Parfois, il y a du JSON à l'intérieur de votre JSON. La manière par défaut de gérer cette situation est généralement celle que vous choisiriez. Cependant, une autre option a été ajoutée à l'argument de type pour traiter un cas d'utilisation différent. Il s'agit du type "json". Regardez l'objet JSON suivant:

{
    “mystring”:”Hello World!”,
    “mynumber”:1234,
    “myjson”:{
        “subitem1”:”Hello Mars!”,
        “subitem2”:”Hello Stars!”
    }
}

En règle générale, lorsque vous rencontrez ce problème, vous devez utiliser la méthode %Get avec la clé « myjson » pour obtenir un objet dynamique. Voici un exemple:

set myjson = myobj.%Get(“myjson”)
write myjson.%Get(“subitem1”)

La ligne ci-dessus écrirait "Hello Mars!". C'est le cas le plus courant dans cette situation. Cependant, dans certains cas, vous préférerez peut-être obtenir le JSON réel contenu dans cet élément sous forme de chaîne. Dans ce cas, vous pouvez procéder comme suit:

set myjson = myobj.%Get(“myjson”,,”json”)
write myjson

La chaîne JSON sera écrite exactement telle qu'elle est:

{“subitem1”:”Hello Mars!”,”subitem2”:”Hello Stars!”}

Cela peut s'avérer utile dans les cas où nous voulons transmettre le JSON tel quel à un autre processus. Notez que, contrairement à tous les autres types nouveaux, celui-ci n'est pris en charge que pour la méthode %Get, et non pour la méthode %Set.

Hourra, hourra, les massives!

Jusqu'à présent, nous avons discuté de ces nouveaux objets dans le contexte de la classe %Library.DynamicObject, mais ils sont également pris en charge pour la classe %Library.DynamicArray. Dans cette classe, %Set et %Get prennent en charge les mêmes arguments de type que dans la classe %Library.DynamicObject. La classe de tableaux dynamiques dispose toutefois d'une méthode %Push supplémentaire. Elle prend en charge les mêmes types que %Set, à l'exception du type JSON.

Sans plus attendre, c'est probablement le bon moment pour revoir vos anciens codes et implémenter ces changements à votre avantage!

0
0 35
Article Iryna Mykhailova · Mars 7, 2025 3m read

Je me suis lancé un défi : trouver un moyen de faire en sorte qu'une variable se surveille elle-même pour une certaine valeur et fasse quelque chose lorsqu'elle atteint cette valeur sans avoir à la vérifier à chaque fois que quelque chose la touche. En gros, un moyen de dire "à un moment donné pendant l'exécution de ce code, si x = 0 (ou quelle que soit la condition) faire ceci". La classe avec laquelle j'ai fini par surveiller un %Status :

0
0 42
Annonce Irène Mykhailova · Juin 11, 2024

Bonjour à tous,

L'équipe de certification d'InterSystems Learning Services développe un examen de certification InterSystems ObjectScript Specialist, et nous contactons la communauté pour obtenir des commentaires qui nous aideront à évaluer et à établir le contenu de cet examen. Veuillez noter qu'il s'agit de l'un des deux examens en cours de développement pour remplacer notre examen InterSystems IRIS Core Solutions Developer. Vous pouvez trouver plus de détails sur notre examen InterSystems IRIS Developer Professional ici.

0
0 36