Strumentazione personalizzata con OpenTelemetry custom-instrumentation
Quando OpenTelemetry è abilitato per il tuo ambiente, AEM as a Cloud Service traccia ogni richiesta ed esporta automaticamente le metriche JVM, HTTP e AEM. La telemetria descrive AEM. Mostra che per il rendering di una pagina sono stati necessari 900 millisecondi e quali componenti sono stati sottoposti a rendering. Non può mostrare ciò che stava facendo il tuo codice, ad esempio per quale elemento di elenco era una query dell’archivio e se ha trovato qualcosa, o quali pagine rientrano in una ricerca costosa perché un autore ha lasciato un campo.
Puoi aggiungere questa conoscenza con l’API OpenTelemetry. Le estensioni e gli attributi creati dal codice viaggiano con la telemetria automatica: stesso nome di servizio, stessa elaborazione, stesso provider APM. Non è possibile configurare un endpoint, una credenziale o un sistema di esportazione nel codice.
Se hai poca esperienza con OpenTelemetry su AEM as a Cloud Service, inizia con l’esercitazione Observability via OpenTelemetry per scoprire come funziona. Quindi segui Configure OpenTelemetry for insights into AEM applications per abilitarlo nel tuo ambiente.
Gli esempi in questa pagina sono validi per il sito di riferimento WKND. Ogni esempio descrive cosa desideri ottenere, il codice da aggiungere e il risultato che puoi aspettarti dal provider APM. Il codice instrumentato completo, inclusi gli unit test che asseriscono gli span, si trova nel ramo tutorial/otel-custom-instrumentation dell’archivio WKND.
Funzionamento della strumentazione personalizzata how-it-works
- L’agente Java OpenTelemetry eseguito da Adobe nell’ambiente AEM contiene il SDK OpenTelemetry e gli esportatori. Il codice dipende solo dall’API di OpenTelemetry.
- Quando il codice chiama
GlobalOpenTelemetry, l’agente lo connette alla propria implementazione. Le estensioni diventano parte della traccia della richiesta che le esegue e la telemetria viene esportata con il resto della telemetria dell’ambiente. - Se l’agente non è presente (l’SDK AEM locale, gli unit test o un ambiente senza OpenTelemetry abilitato), l’API non esegue alcuna operazione, a un costo trascurabile. Puoi distribuire lo stesso codice in ogni ambiente.
Tracer o l’annotazione @WithSpanSpanWARN e superiore, quando i registri sono abilitatiConfigurare il progetto set-up-project
Passaggio 1: aggiungere l’API OpenTelemetry add-api
Aggiungere l’API OpenTelemetry e le annotazioni di strumentazione alla sezione dependencyManagement dell’elemento padre del progetto pom.xml, con l’ambito 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>
Quindi aggiungi entrambe le dipendenze, senza versioni, a pom.xml di ogni bundle che le utilizza, ad esempio 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>
Utilizzare opentelemetry-api 1.61.0 e opentelemetry-instrumentation-annotations 2.27.0 o versioni precedenti. Corrispondono all’agente Java OpenTelemetry eseguito da AEM as a Cloud Service, e le versioni successive dell’agente continuano a supportarle. Le funzioni API aggiunte dopo la versione supportata dall’agente non hanno alcun effetto.
Passaggio 2: incorporare l’API nel bundle embed-api
AEM as a Cloud Service non esporta i pacchetti API OpenTelemetry, pertanto ogni bundle che utilizza l’API deve contenere una propria copia. Aggiungi le seguenti istruzioni alla configurazione del binding del bundle. Con Archetipo progetto AEM, ovvero la configurazione bnd-maven-plugin in core/pom.xml. Mantieni le voci già presenti nell’istruzione Import-Package.
Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
!io.opentelemetry.* all’inizio di Import-Package-includepackage: io.opentelemetry.*-conditionalpackage, gli span e gli attributi verranno eliminati senza errori.Ogni bundle che utilizza l’API incorpora la propria copia e l’agente connette ciascuna copia separatamente.
OpenTelemetrySdk personalizzata o chiamare GlobalOpenTelemetry.set(). La telemetria inviata ignora l'agente, la rimozione dei dettagli dell'infrastruttura Cloud Service descritti in Dati inviati e l'endpoint configurato.Passaggio 3: creare un tracciatore create-tracer
Crea un Tracer per la base di codice e riutilizzalo ovunque. In WKND, una piccola classe la detiene:
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() {
}
}
Il nome dell’ambito viene visualizzato su ogni estensione creata mediante il tracciatore, come ambito della strumentazione (o nome della libreria). Utilizza un nome che identifichi il codice, ad esempio il pacchetto Java di base del progetto.
Dove vengono visualizzate le estensioni where-spans-appear
In AEM as a Cloud Service, una richiesta di pagina produce già estensioni provenienti da AEM stessa. Le estensioni create dal codice vengono aggiunte al loro interno. Ad esempio, questo è il percorso del componente Elenco immagini in una traccia della pagina 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
Durante il rendering di un componente, l’estensione del relativo script HTL è l’estensione corrente. Un modello Sling che lo script carica con data-sly-use viene eseguito all’interno di tale estensione, in modo che il modello crei diventi i relativi figli.
Ogni intervallo di richieste è denominato GET /* (o POST /* e così via). Per trovare le tracce per una pagina particolare, eseguire una ricerca nell’attributo url.path dell’intervallo di richieste oppure aggiungere attributi personalizzati, come illustrato nell’esempio 3.
Esempi examples
Esempio 1: tracciare un’operazione costosa trace-operation
Cosa desideri ottenere trace-operation-goal
Il componente Elenco immagini WKND (wknd/components/image-list) visualizza un elenco di pagine, ciascuna con la prima immagine. Per trovare tale immagine, il relativo modello Sling, ImageListImpl, esegue una query QueryBuilder per ogni elemento dell’elenco. Le estensioni di AEM mostrano già queste query: nella pagina WKND Magazine, che elenca cinque articoli, la traccia contiene cinque estensioni JCR query sotto l’estensione HTL dell’elenco di immagini. Non viene visualizzata la voce di elenco per la quale è stata eseguita ogni query, se è stata trovata un’immagine o il tempo impiegato per elaborare i risultati. Il codice originale ha temporizzato la query con System.currentTimeMillis() e ha scritto il risultato in una voce di registro DEBUG, che non è disponibile in produzione. Si desidera che ogni query venga visualizzata nell’analisi come un’operazione denominata, con tale contesto.
Il codice trace-operation-code
In ImageListImpl.findPageComponentResources(), creare un’estensione intorno alla query:
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()rende l’estensione padre di tutto ciò che viene eseguito all’interno del blocco, incluse le estensioni create da AEM per la query dell’archivio.- Chiama sempre
end()in un bloccofinally. Un’estensione non terminata non viene mai esportata. - Assegna all’estensione un nome fisso che descriva l’operazione e inserisci valori variabili, come percorsi, negli attributi.
Il risultato trace-operation-outcome
Nella pagina WKND Magazine, che elenca cinque articoli, la traccia ora contiene cinque ImageList.findPageComponentResources espansioni all’interno dell’elenco immagini, una per ogni elemento dell’elenco. Ciascuno è l’elemento padre dell’estensione JCR query creata da AEM per la query:
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
Ciascuna estensione ha l’ambito di strumentazione com.adobe.aem.guides.wknd e i seguenti attributi:
wknd.query.path/content/wknd/us/en/magazine/arctic-surfing/jcr:contentwknd.query.resource_typewknd/components/imagewknd.query.limit1wknd.query.result_count1Ora puoi vedere, per ogni elemento dell’elenco, quale pagina ha cercato la query, se ha trovato un’immagine (wknd.query.result_count è 0 quando non l’ha trovata) e per quanto tempo è durata l’intera ricerca, inclusa l’elaborazione dei risultati. Nella pagina WKND Magazine, ogni ricerca impiega circa 6,5 millisecondi, dei quali circa 3,5 impiegato dalla query dell’archivio stesso: la metà del tempo viene impiegata per elaborare i risultati, che le espansioni di AEM non mostrano. Poiché il conteggio delle query aumenta con la lunghezza dell’elenco, puoi anche vedere per quanto tempo di rendering del componente influiscono le ricerche.
Esempio 2: tracciare un metodo con un’annotazione with-span
Cosa desideri ottenere with-span-goal
Il modello SocialImageImpl di WKND viene eseguito su ogni pagina per compilare il tag meta og:image. Se un autore non ha impostato un’immagine in primo piano, il modello sposta la struttura del contenuto della pagina per trovare la prima immagine, visitando fino a 500 risorse. Volete sapere quanto spesso quella camminata scorre, e quanto dell’albero visita, senza ristrutturare il metodo.
Il codice with-span-code
Annotare il metodo con @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’agente avvia un’estensione ogni volta che il metodo viene eseguito e la termina quando il metodo restituisce o genera un’eccezione.
@WithSpanfunziona con metodi pubblici, protetti e privati.- Per registrare un parametro di metodo come attributo, annotare il parametro con
@SpanAttribute, ad esempiovoid applyTemplate(@SpanAttribute("wknd.page.template") String template). - L’annotazione ha effetto solo dove viene eseguito l’agente. Nel SDK AEM locale e negli unit test, il metodo viene eseguito senza modifiche.
Il risultato with-span-outcome
Un’estensione SocialImage.resolveFromFirstImageOnPage viene visualizzata solo nelle tracce di pagine che non contengono immagini in primo piano:
wknd.social_image.nodes_visited21wknd.social_image.walk_truncatedfalsecode.namespacecom.adobe.aem.guides.wknd.core.models.impl.SocialImageImpl (aggiunto dall’agente)code.functionresolveFromFirstImageOnPage (aggiunto dall’agente)Cerca questo nome per elencare le pagine che richiedono un’immagine in primo piano. Se wknd.social_image.walk_truncated è true, la passeggiata si è interrotta su 500 risorse e la pagina potrebbe non avere alcuna immagine social.
Le estensioni create da @WithSpan contengono l’ambito di strumentazione dell’agente (io.opentelemetry.opentelemetry-instrumentation-annotations-1.16), non l’ambito dell’utente.
Esempio 3: aggiungere contesto aziendale all’estensione corrente current-span
Cosa desideri ottenere current-span-goal
Si desidera filtrare le tracce in base al fatto che il codice conosce, qui, da dove proviene l’immagine social della pagina, senza creare un’altra estensione.
Il codice current-span-code
In SocialImageImpl, aggiungi un attributo all’estensione già attiva:
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()restituisce l’estensione attiva più interna, sia essa creata da AEM o dal codice. Per un modello Sling caricato da HTL, è l’estensione dello script HTL che ha caricato il modello.- Se non è attiva alcuna estensione, la chiamata non esegue alcuna operazione.
- Se si definiscono le costanti
AttributeKey, i nomi degli attributi nel codice rimarranno coerenti.
Il risultato current-span-outcome
L’estensione dello script customheaderlibs.html della pagina, in cui WKND carica questo modello, contiene wknd.social_image.source con il valore featured_image, first_image_on_page o none. Per trovare le pagine pubblicate senza un’immagine social, cercare le tracce in cui wknd.social_image.source è none.
Sul sito WKND, ogni pagina riporta first_image_on_page o none: nessuna pagina imposta un’immagine in primo piano, quindi ogni pagina paga per la camminata della struttura del contenuto da Esempio 2. Se si imposta un’immagine in primo piano sulle pagine più visitate, questo viene rimosso.
Esempio 4: registrare un errore record-errors
Cosa desideri ottenere record-errors-goal
Il modello ContentAIAvailabilityImpl di WKND controlla se la Ricerca IA del contenuto è configurata leggendo una configurazione OSGi. Se la ricerca non riesce, il modello tratta IA per la gestione dei contenuti come non configurato e registra un avviso. Il rendering della pagina viene comunque eseguito e la richiesta ha esito positivo, pertanto l’errore può facilmente mancare. Si desidera che l’errore venga visualizzato nelle visualizzazioni degli errori del provider APM, associate all’operazione non riuscita.
Il codice 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()aggiunge un eventoexceptionall’estensione, con il tipo, il messaggio e la traccia dello stack dell’eccezione.setStatus(StatusCode.ERROR, ...)contrassegna l’estensione come non riuscita. Impostarlo sull’estensione che rappresenta l’operazione non riuscita. La richiesta stessa non è contrassegnata come non riuscita, perché il componente è stato recuperato e la pagina è stata sottoposta a rendering.
Il risultato record-errors-outcome
La traccia contiene un intervallo di ContentAIAvailability.resolveConfigured con stato Errore e descrizione Content AI configuration lookup failed. L’estensione ha un evento exception:
exception.typejava.io.IOExceptionexception.messageConfiguration store unavailableexception.stacktraceL’estensione conta come un errore nel provider APM, quindi viene visualizzata ovunque il provider mostri operazioni non riuscite. L’intervallo di richieste mantiene il proprio stato, perché la richiesta ha restituito HTTP 200.
Esempio 5: correlare i registri con le tracce correlate-logs
Cosa desideri ottenere correlate-logs-goal
Quando la ricerca di IA per la gestione dei contenuti in Esempio 4 non riesce, passare dall’avviso nei registri alla traccia della richiesta che l’ha generata e tornare indietro.
Il codice correlate-logs-code
Non è necessario alcun codice OpenTelemetry. Effettua l’accesso con SLF4J come al solito e abilita i registri (logs: true) nella configurazione di OpenTelemetry:
LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
L’agente aggiunge gli ID di traccia e di estensione dell’estensione corrente a ogni voce del registro. Poiché ContentAIAvailabilityImpl registra mentre la relativa estensione da Esempio 4 è corrente, l’avviso è collegato a tale estensione.
Il risultato correlate-logs-outcome
Il provider APM riceve una voce di registro come la seguente e può visualizzarla con la relativa traccia:
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 e superiore. INFO e DEBUG voci non lo sono. Non fare affidamento su di essi per la diagnostica della produzione: registra le informazioni come attributo span.Best practice best-practices
- Il nome si estende dopo le operazioni, non i dati. Usare nomi come
ImageList.findPageComponentResourcese inserire valori variabili, come percorsi e ID, negli attributi. - Aggiungi ai nomi degli attributi uno spazio dei nomi per il progetto, ad esempio
wknd.. In questo modo si evitano conflitti con le convenzioni semantiche OpenTelemetry e con i nomi utilizzati da AEM. Non utilizzare il prefissoaem., riservato per AEM. - Non registrare dati personali o segreti in nomi di estensione, attributi, eventi o messaggi di registro. Ciò include nomi, indirizzi e-mail, indirizzi IP, token e stringhe di query che possono contenerli.
- Aggiungere intervalli per le operazioni, non iterazioni. Ad esempio, Nell’esempio 2 viene creata un’estensione per il percorso della struttura contenuto e viene registrato il numero di risorse visitate come attributo, anziché creare un’estensione per ogni risorsa. AEM crea già molte estensioni per una singola pagina (circa 260 per la pagina WKND Magazine) e ogni estensione aggiunge al volume acquisito dal provider APM.
- Creare i tracciatori una volta, e riutilizzarli.
- Termina ogni intervallo in un blocco
finallye chiudi ogniScopenel thread che l’ha aperto. - Passare il contesto ai thread che si avviano, ad esempio con
Context.current().wrap(runnable), in modo che il lavoro eseguito su un altro thread si unisca alla stessa traccia. - È prevista una nuova traccia per il lavoro all’esterno di una richiesta. Il codice che viene eseguito in un processo Sling, un’attività pianificata o un listener di eventi non ha un’estensione di richiesta. Le estensioni create lì iniziano le proprie tracce, una per ogni esecuzione.
Verifica la strumentazione test
- Test di unità: senza l’agente, l’API non esegue alcuna operazione, pertanto gli unit test esistenti continuano a passare senza modifiche.
- Build: Eseguire
mvn clean installlocalmente. L’analizzatore di AEM, eseguito anche da Cloud Manager, controlla che le importazioni del bundle possano essere risolte. - Contenuto bundle: Verificare che il bundle contenga l’API completa, come descritto in Passaggio 2. Ad esempio,
unzip -l core/target/<your-core-bundle>.jar | grep "io/opentelemetry/api/baggage/"deve elencare le classi e il pacchettoMETA-INF/MANIFEST.MFdel bundle non deve importare alcun pacchettoio.opentelemetry. - Provider APM: distribuire in un ambiente di sviluppo in cui è abilitato OpenTelemetry, richiedere una pagina che esegua il codice e cercare nel provider APM i nomi di span o l’ambito di strumentazione scelto in Passaggio 3.
Risoluzione di problemi 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.* all’inizio di Import-Package. Vedi Passaggio 2.missing requirement ... osgi.wiring.package=io.opentelemetry.api.incubator.@WithSpan, ma le altre estensioni e gli attributi impostati con Span.current() no.-includepackage: io.opentelemetry.*. Vedi Passaggio 2.Context.current().wrap(runnable).WARN e superiore e solo quando i registri sono abilitati nella configurazione.Articoli correlati related-articles
- OpenTelemetry
- Registrazione
- API Java OpenTelemetry (opentelemetry.io)
- Annotazioni per l’agente Java OpenTelemetry (opentelemetry.io)
- Convenzioni semantiche OpenTelemetry (opentelemetry.io)
- Sito di riferimento WKND (GitHub)
- Ramo di strumentazione personalizzato WKND (GitHub)