Instrumentation personnalisée avec OpenTelemetry custom-instrumentation
Lorsque la fonction OpenTelemetry est activée pour votre environnement, AEM as a Cloud Service effectue le suivi de chaque requête et exporte automatiquement les mesures JVM, HTTP et AEM. Cette télémétrie décrit AEM. Cela indique qu’une page a mis 900 millisecondes pour s’afficher et quels composants elle a rendus. Il ne peut pas afficher ce que faisait votre propre code, par exemple pour quel élément de liste une requête de référentiel était destinée et s’il a trouvé quelque chose, ou quelles pages sont renvoyées à une recherche coûteuse parce qu’un auteur a oublié un champ.
Vous pouvez ajouter ces connaissances vous-même avec l’API OpenTelemetry. Les étendues et attributs créés par votre code se déplacent avec la télémétrie automatique : même nom de service, même traitement, même fournisseur APM. Vous ne configurez pas de point d’entrée, d’informations d’identification ou d’exportateur dans votre code.
Si vous découvrez OpenTelemetry sur AEM as a Cloud Service, commencez par le tutoriel Observability via OpenTelemetry pour découvrir comment il fonctionne. Suivez ensuite Configuration d’OpenTelemetry pour obtenir des informations sur les applications AEM afin de l’activer pour votre environnement.
Les exemples de cette page instrumentent le site de référence WKND. Chaque exemple décrit ce que vous souhaitez accomplir, le code à ajouter et le résultat attendu de la part de votre fournisseur APM. Le code instrumenté complet, y compris les tests unitaires qui s’appuient sur les étendues, se trouve sur la branche tutorial/otel-custom-instrumentation du référentiel WKND.
Fonctionnement des instruments personnalisés how-it-works
- L’agent Java OpenTelemetry qu’Adobe exécute dans votre environnement AEM contient le SDK et les exportateurs OpenTelemetry. Votre code dépend uniquement de l’API OpenTelemetry API.
- Lorsque votre code appelle
GlobalOpenTelemetry, l’agent le connecte à sa propre implémentation. Vos plages font partie de la trace de la requête qui les exécute et votre télémétrie est exportée avec le reste de la télémétrie de votre environnement. - Lorsque l’agent n’est pas présent (le SDK AEM local, les tests unitaires ou un environnement sans OpenTelemetry activé), l’API ne fait rien, à un coût négligeable. Vous pouvez déployer le même code dans chaque environnement.
Tracer, ou l’annotation @WithSpanSpanWARN et supérieur, lorsque les journaux sont activésConfigurer votre projet set-up-project
Étape 1 : ajouter l’API OpenTelemetry add-api
Ajoutez l’API OpenTelemetry et les annotations d’instrumentation à la section dependencyManagement du pom.xml parent de votre projet, avec la portée provided :
<properties>
<opentelemetry.version>1.61.0</opentelemetry.version>
<opentelemetry-instrumentation.version>2.27.0</opentelemetry-instrumentation.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>${opentelemetry.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
<version>${opentelemetry-instrumentation.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
Ajoutez ensuite les deux dépendances, sans versions, au pom.xml de chaque lot qui les utilise, par exemple core/pom.xml :
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-annotations</artifactId>
</dependency>
Utilisez opentelemetry-api 1.61.0 et opentelemetry-instrumentation-annotations 2.27.0, ou des versions antérieures. Elles correspondent à l’agent Java OpenTelemetry qu’AEM as a Cloud Service exécute et les versions ultérieures de l’agent continuent de les prendre en charge. Les fonctionnalités d’API ajoutées après la version prise en charge par l’agent n’ont aucun effet.
Étape 2 : incorporation de l’API dans votre lot embed-api
AEM as a Cloud Service n’exporte pas les packages d’API OpenTelemetry, de sorte que chaque lot qui utilise l’API doit contenir sa propre copie. Ajoutez les instructions suivantes à la configuration de liaison de votre lot. Avec l’archétype de projet , il s’agit de la configuration bnd-maven-plugin dans core/pom.xml. Conservez toutes les entrées de votre instruction Import-Package.
Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
!io.opentelemetry.* début Import-Package-includepackage: io.opentelemetry.*-conditionalpackage, vos étendues et attributs sont ignorés sans erreur.Chaque lot qui utilise l’API incorpore sa propre copie et l’agent connecte chaque copie séparément.
OpenTelemetrySdk ou n’appelez pas de GlobalOpenTelemetry.set(). La télémétrie envoyée de cette manière contourne l’agent, la suppression des détails de l’infrastructure Cloud Service décrite dans Quelles données sont envoyées et le point d’entrée que vous avez configuré.Étape 3 : créer un traceur create-tracer
Créez une Tracer pour votre base de code et réutilisez-la partout. Dans WKND, une petite classe la contient :
package com.adobe.aem.guides.wknd.core.telemetry;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
public final class WkndTelemetry {
/** Instrumentation scope: identifies telemetry produced by WKND code in your APM provider. */
public static final String SCOPE = "com.adobe.aem.guides.wknd";
public static final Tracer TRACER = GlobalOpenTelemetry.getTracer(SCOPE);
private WkndTelemetry() {
}
}
Le nom de la portée s’affiche sur chaque étendue que vous créez par l’intermédiaire de ce traceur, sous la forme de portée d’instrumentation (ou nom de bibliothèque). Utilisez un nom qui identifie votre code, tel que le package Java de base de votre projet.
Emplacement de vos étendues where-spans-appear
Sur AEM as a Cloud Service, une requête de page produit déjà des plages à partir d’AEM. Les plages créées par votre code y sont ajoutées. Par exemple, voici le chemin d’accès au composant Liste d’images dans une trace de la page WKND Magazine :
GET /* the request (AEM)
└─ SlingMainServlet.service Sling request processing (AEM)
└─ AEM component: cq:Page (AEM)
└─ AEM component: wknd/components/page the page component (AEM)
└─ HTL: libs.core.wcm.components.page.v3.page.page__002e__html
└─ ... containers and layout grids (AEM)
└─ AEM component: wknd/components/image-list
└─ HTL: apps.wknd.components.image__002d__list.image__002d__list__002e__html
└─ ... spans that the Image List's Sling Model creates
Lorsqu’un composant effectue le rendu, la portée de son script HTL est la portée actuelle. Un modèle Sling que le script charge avec data-sly-use s’exécute à l’intérieur de cette étendue, de sorte que les étendues créées par le modèle deviennent ses enfants.
Chaque plage de requêtes est nommée GET /* (ou POST /*, etc.). Pour rechercher les traces d’une page particulière, recherchez l’attribut url.path de l’étendue de la requête ou ajoutez vos propres attributs, comme indiqué dans Exemple 3.
Exemples examples
Exemple 1 : suivi d’une opération coûteuse trace-operation
Ce que vous souhaitez accomplir trace-operation-goal
Le composant Liste d’images WKND (wknd/components/image-list) affiche une liste de pages, chacune avec sa première image. Pour trouver cette image, son modèle Sling, ImageListImpl, exécute une requête QueryBuilder pour chaque élément de la liste. Les propres étendues AEM affichent déjà ces requêtes : sur la page du magazine WKND, qui répertorie cinq articles, la trace contient cinq étendues de JCR query sous l’étendue HTL de la liste d’images. Elles n’indiquent pas à quel élément de liste chaque requête correspondait, si elle a trouvé une image ou le temps passé à traiter les résultats. Le code d’origine a minuté la requête avec System.currentTimeMillis() et a écrit le résultat dans une entrée de journal DEBUG, qui n’est pas disponible en production. Vous souhaitez que chaque requête apparaisse dans la trace sous la forme d’une opération nommée, avec ce contexte.
Le code trace-operation-code
Dans ImageListImpl.findPageComponentResources(), créez une étendue autour de la requête :
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
// ... build the QueryBuilder parameters, as before
final Span span = WkndTelemetry.TRACER.spanBuilder("ImageList.findPageComponentResources")
.setAttribute("wknd.query.path", page.getContentResource().getPath())
.setAttribute("wknd.query.resource_type", slingResourceType)
.setAttribute("wknd.query.limit", limit)
.startSpan();
try (Scope ignored = span.makeCurrent()) {
// ... run the query and collect the resources, as before
span.setAttribute("wknd.query.result_count", componentResources.size());
} catch (RuntimeException e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Image List query failed");
throw e;
} finally {
span.end();
}
return componentResources;
makeCurrent()fait de votre étendue le parent de tout ce qui s’exécute à l’intérieur du bloc, y compris les étendues créées par AEM pour la requête de référentiel.- Appelez toujours
end()dans un bloc definally. Une étendue non terminée n’est jamais exportée. - Attribuez à la plage un nom fixe qui décrit l’opération et placez des valeurs variables, telles que des chemins, dans les attributs.
Le résultat trace-operation-outcome
Sur la page du magazine WKND, qui répertorie cinq articles, la trace contient désormais cinq plages d’ImageList.findPageComponentResources dans la liste d’images, une pour chaque élément de liste. Chacune d’elles est le parent de l’étendue de JCR query créée par AEM pour sa requête :
AEM component: wknd/components/image-list
└─ HTL: apps.wknd.components.image__002d__list.image__002d__list__002e__html
├─ ImageList.findPageComponentResources your span
│ └─ JCR query AEM
├─ ImageList.findPageComponentResources
│ └─ JCR query
└─ ... three more
Chacune de vos plages possède la portée d’instrumentation com.adobe.aem.guides.wknd et les attributs suivants :
wknd.query.path/content/wknd/us/en/magazine/arctic-surfing/jcr:contentwknd.query.resource_typewknd/components/imagewknd.query.limit1wknd.query.result_count1Vous pouvez désormais voir, pour chaque élément de liste, quelle page la requête a recherchée, si elle a trouvé une image (wknd.query.result_count est 0 alors que ce n’est pas le cas) et combien de temps a pris l’ensemble de la recherche, y compris le traitement des résultats. Sur la page du magazine WKND, chaque recherche prend environ 6,5 millisecondes, dont environ 3,5 millisecondes pour la requête de référentiel elle-même : la moitié du temps est consacrée au traitement des résultats, ce que les plages propres à AEM n’affichent pas. Étant donné que le nombre de requêtes augmente avec la longueur de la liste, vous pouvez également voir le temps de rendu du composant pris en compte par les recherches.
Exemple 2 : suivi d’une méthode avec une annotation with-span
Ce que vous souhaitez accomplir with-span-goal
Le modèle de SocialImageImpl de WKND s’exécute sur chaque page pour renseigner la balise meta og:image. Si un auteur n’a pas défini d’image en vedette, le modèle parcourt l’arborescence de contenu de la page pour trouver la première image, visitant jusqu’à 500 ressources. Vous voulez savoir à quelle fréquence cette marche court, et quelle partie de l’arbre elle visite, sans restructurer la méthode.
Le code with-span-code
Annotez la méthode avec @WithSpan :
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.instrumentation.annotations.WithSpan;
@WithSpan("SocialImage.resolveFromFirstImageOnPage")
protected boolean resolveFromFirstImageOnPage() {
Resource contentResource = currentPage.getContentResource();
if (contentResource == null) {
return false;
}
int[] visitedCount = new int[]{0};
Resource firstImage = findFirstImage(contentResource, 0, visitedCount);
// Span.current() is the span that @WithSpan started for this method.
Span.current().setAttribute("wknd.social_image.nodes_visited", visitedCount[0]);
Span.current().setAttribute("wknd.social_image.walk_truncated", visitedCount[0] > MAX_NODES_TO_VISIT);
// ... use firstImage, as before
}
- L’agent démarre une plage chaque fois que la méthode s’exécute et la termine lorsque la méthode renvoie ou renvoie une exception.
@WithSpanfonctionne sur des méthodes publiques, protégées et privées.- Pour enregistrer un paramètre de méthode en tant qu’attribut, annotez le paramètre avec
@SpanAttribute, par exemplevoid applyTemplate(@SpanAttribute("wknd.page.template") String template). - L’annotation ne prend effet que là où l’agent s’exécute. Dans le SDK AEM local et dans les tests unitaires, la méthode s’exécute inchangée.
Le résultat with-span-outcome
Une étendue de SocialImage.resolveFromFirstImageOnPage apparaît uniquement dans les traces des pages qui n’ont pas d’image en vedette :
wknd.social_image.nodes_visited21wknd.social_image.walk_truncatedfalsecode.namespacecom.adobe.aem.guides.wknd.core.models.impl.SocialImageImpl (ajoutée par l’agent)code.functionresolveFromFirstImageOnPage (ajoutée par l’agent)Recherchez ce nom d’étendue pour répertorier les pages qui ont besoin d’une image en vedette. Si wknd.social_image.walk_truncated est true, la marche s’est arrêtée à 500 ressources et la page peut ne pas avoir d’image sociale du tout.
Les plages créées par @WithSpan portent la portée d’instrumentation de l’agent (io.opentelemetry.opentelemetry-instrumentation-annotations-1.16), et non la vôtre.
Exemple 3 : ajouter un contexte métier à la plage actuelle current-span
Ce que vous souhaitez accomplir current-span-goal
Vous souhaitez filtrer les traces en fonction d’un fait que votre code connaît, ici, l’origine de l’image du réseau social de la page, sans créer d’autre étendue.
Le code current-span-code
Dans SocialImageImpl, ajoutez un attribut à l’étendue déjà active :
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
private static final AttributeKey<String> IMAGE_SOURCE = AttributeKey.stringKey("wknd.social_image.source");
@PostConstruct
protected void init() {
if (currentPage == null) {
return;
}
final String source;
if (resolveFromFeaturedImage()) {
source = "featured_image";
} else if (resolveFromFirstImageOnPage()) {
source = "first_image_on_page";
} else {
source = "none";
}
Span.current().setAttribute(IMAGE_SOURCE, source);
}
Span.current()renvoie la plage la plus active, qu’AEM ou votre code l’ait créée. Pour un modèle Sling chargé à partir de HTL, c’est l’étendue du script HTL qui a chargé le modèle.- Si aucune plage n’est active, l’appel ne fait rien.
- La définition de constantes
AttributeKeyune fois permet de conserver la cohérence des noms d’attributs dans votre code.
Le résultat current-span-outcome
L’étendue du script de customheaderlibs.html de la page, où WKND charge ce modèle, transporte les wknd.social_image.source avec la valeur featured_image, first_image_on_page ou none. Recherchez les traces où wknd.social_image.source est none pour trouver les pages publiées sans image de réseau social.
Sur le site WKND, chaque page indique first_image_on_page ou none : aucune page ne définit une image en vedette, de sorte que chaque page paie pour la présentation de l’arborescence de contenu à partir de Exemple 2. La définition d’une image en vedette sur les pages les plus visitées supprime ce travail.
Exemple 4 : enregistrement d’une erreur record-errors
Ce que vous souhaitez accomplir record-errors-goal
Le modèle ContentAIAvailabilityImpl de WKND vérifie si la Recherche optimisée par l’IA de contenu est configurée en lisant une configuration OSGi. Si la recherche échoue, le modèle traite l’IA dédiée au contenu comme non configurée et consigne un avertissement. La page est toujours rendue et la requête réussit. Il est donc facile de manquer l’échec. Vous souhaitez que l’échec apparaisse dans les vues d’erreur de votre fournisseur APM, associées à l’opération ayant échoué.
Le code record-errors-code
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
private boolean resolveConfigured() {
if (configurationAdmin == null) {
return false;
}
final Span span = WkndTelemetry.TRACER.spanBuilder("ContentAIAvailability.resolveConfigured").startSpan();
// Close the scope in finally, not with try-with-resources: try-with-resources closes it before
// the catch block runs, and the warning logged there must carry this span's trace and span IDs.
final Scope scope = span.makeCurrent();
try {
Configuration configuration = lookupConfiguration();
// ... read the API key, as before
span.setAttribute("wknd.content_ai.configured", isConfigured);
return isConfigured;
} catch (IOException e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Content AI configuration lookup failed");
LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
return false;
} finally {
scope.close();
span.end();
}
}
recordException()ajoute un événementexceptionà l’étendue, avec le type d’exception, le message et la trace de la pile.setStatus(StatusCode.ERROR, ...)marque l’étendue comme ayant échoué. Définissez-le sur la plage qui représente l’opération qui a échoué. La requête elle-même n’est pas marquée comme ayant échoué, car le composant a été récupéré et la page rendue.
Le résultat record-errors-outcome
La trace contient une étendue ContentAIAvailability.resolveConfigured avec le statut Erreur et la description Content AI configuration lookup failed. L’étendue comporte un événement exception :
exception.typejava.io.IOExceptionexception.messageConfiguration store unavailableexception.stacktraceL’étendue compte comme une erreur dans votre fournisseur APM. Elle apparaît donc partout où votre fournisseur affiche des opérations ayant échoué. L’étendue de la requête conserve son statut, car la requête a renvoyé un HTTP 200.
Exemple 5 : corréler les logs avec les traces correlate-logs
Ce que vous souhaitez accomplir correlate-logs-goal
Lorsque la recherche de l’IA dédiée au contenu dans Exemple 4 échoue, vous devez passer de l’avertissement dans vos journaux à la trace de la requête qui l’a générée, puis revenir en arrière.
Le code correlate-logs-code
Vous n’avez pas besoin de code OpenTelemetry. Connectez-vous avec SLF4J comme d’habitude et activez les journaux (logs: true) dans votre configuration OpenTelemetry :
LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
L’agent ajoute les identifiants de suivi et d’étendue de l’étendue actuelle à chaque entrée du journal. Étant donné que ContentAIAvailabilityImpl enregistre tant que son étendue de Exemple 4 est actuelle, l’avertissement est lié à cette étendue.
Le résultat correlate-logs-outcome
Votre fournisseur APM reçoit une entrée de journal telle que la suivante et peut l’afficher avec sa trace :
WARNUnable to determine whether Content AI is configured (PID com.adobe.cq.wcm.core.components.internal.services.contentai.ContentAIClientImpl)ContentAIAvailability.resolveConfiguredexception.type, exception.message, exception.stacktracecom.adobe.aem.guides.wknd.core.models.impl.contentai.ContentAIAvailabilityImplWARN et supérieur sont envoyées. Les entrées INFO et DEBUG ne le sont pas. Ne vous fiez pas à eux pour les diagnostics de production : enregistrez les informations en tant qu’attribut d’étendue à la place.Bonnes pratiques best-practices
- Le nom s’étend après les opérations, et non sur les données. Utilisez des noms tels que
ImageList.findPageComponentResourceset placez des valeurs qui varient, telles que des chemins d’accès et des identifiants, dans les attributs. - Préfixez vos propres noms d’attribut avec un espace de noms pour votre projet, tel que
wknd.. Cela permet d’éviter les conflits avec les conventions sémantiques OpenTelemetry et avec les noms utilisés par AEM. N’utilisez pas le préfixeaem., qui est réservé à AEM. - N’enregistrez pas de données personnelles ou de secrets dans les noms d’étendue, les attributs, les événements ou les messages du journal. Cela inclut les noms, adresses e-mail, adresses IP, jetons et chaînes de requête qui peuvent les contenir.
- Ajoutez des étendues pour les opérations, et non des itérations. Par exemple, la mention Exemple 2 crée une étendue pour la marche de l’arborescence de contenu et enregistre le nombre de ressources visitées en tant qu’attribut, plutôt que de créer une étendue pour chaque ressource. AEM crée déjà de nombreuses plages pour une seule page (environ 260 pour la page du magazine WKND), et chaque plage s’ajoute au volume ingéré par votre fournisseur APM.
- Créez des traceurs une seule fois puis réutilisez-les.
- Terminez chaque étendue dans un bloc de
finally, puis fermez chaqueScopedu thread qui l’a ouverte. - Transmettez le contexte aux threads que vous démarrez vous-même par exemple avec
Context.current().wrap(runnable), de sorte que le travail effectué sur un autre thread rejoigne la même trace. - Attendez-vous à une nouvelle trace pour le travail en dehors d’une requête. Le code qui s’exécute dans une tâche Sling, une tâche planifiée ou un écouteur d’événement n’a pas de durée de requête. Les plages que vous y créez commencent leurs propres traces, une pour chaque exécution.
Tester votre instrumentation test
- Tests unitaires : sans l’agent, l’API ne fait rien, de sorte que vos tests unitaires existants continuent à passer inchangés.
- Build: Exécutez
mvn clean installlocalement. L’analyseur AEM, que Cloud Manager exécute également, vérifie que les importations de votre lot peuvent être résolues. - Contenu du bundle : vérifiez que votre bundle contient l’API complète, comme décrit dans Étape 2. Par exemple,
unzip -l core/target/<your-core-bundle>.jar | grep "io/opentelemetry/api/baggage/"devez répertorier les classes et leMETA-INF/MANIFEST.MFdu lot ne doit importer aucun packageio.opentelemetry. - Votre fournisseur APM : effectuez un déploiement dans un environnement de développement pour lequel OpenTelemetry est activé, demandez une page qui exécute votre code et recherchez vos noms d’étendue ou la portée d’instrumentation que vous avez choisie à l’étape 3 dans votre fournisseur APM.
Résolution des problèmes troubleshooting
[api-regions-exportsimports] ... is importing package(s) [io.opentelemetry.api.incubator, io.opentelemetry.api.incubator.internal, io.opentelemetry.sdk.autoconfigure] in start level 20 but no bundle is exporting these for that start level.!io.opentelemetry.* au début de la Import-Package. Voir Étape 2.missing requirement ... osgi.wiring.package=io.opentelemetry.api.incubator.@WithSpan étendues apparaissent, mais pas les autres étendues ni les attributs que vous définissez avec Span.current().-includepackage: io.opentelemetry.*. Voir Étape 2.Context.current().wrap(runnable).WARN et supérieur sont envoyées, et uniquement lorsque les journaux sont activés dans votre configuration.Articles connexes related-articles
- OpenTelemetry
- Journalisation
- API Java OpenTelemetry (opentelemetry.io)
- Annotations pour l’agent Java OpenTelemetry (opentelemetry.io)
- Conventions sémantiques OpenTelemetry (opentelemetry.io)
- Site de référence WKND (GitHub)
- Branche d’instrumentation personnalisée WKND (GitHub)