Instrumentación personalizada con OpenTelemetry custom-instrumentation
Cuando OpenTelemetry está habilitado para su entorno, AEM as a Cloud Service rastrea cada solicitud y exporta automáticamente las métricas de JVM, HTTP y AEM. Esa telemetría describe a AEM. Muestra que una página tardó 900 milisegundos en procesarse y qué componentes procesó. No puede mostrar lo que estaba haciendo su propio código, como para qué elemento de lista estaba una consulta del repositorio y si encontró algo, o qué páginas vuelven a una búsqueda costosa porque un autor omitió un campo.
Puede añadir ese conocimiento usted mismo con la API de OpenTelemetry. Los espacios y atributos que crea su código viajan con la telemetría automática: mismo nombre de servicio, mismo procesamiento, mismo proveedor de APM. No configure un extremo, una credencial o un exportador en el código.
Si no tienes experiencia en OpenTelemetry en AEM as a Cloud Service, empieza con el tutorial Observability a través de OpenTelemetry para saber cómo funciona. A continuación, siga Configurar OpenTelemetry para obtener información sobre las aplicaciones de AEM para habilitarla para su entorno.
Los ejemplos de esta página instrumentan el sitio de referencia WKND. En cada ejemplo se describe lo que se desea lograr, el código que se va a agregar y el resultado que se puede esperar en el proveedor de APM. El código instrumentado completo, incluidas las pruebas unitarias que se afirman en los intervalos, se encuentra en la rama tutorial/otel-custom-instrumentation del repositorio WKND.
Funcionamiento de la instrumentación personalizada how-it-works
- El agente Java de OpenTelemetry que Adobe ejecuta en su entorno de AEM contiene el SDK de OpenTelemetry y los exportadores de. Su código depende únicamente de la API de OpenTelemetry.
- Cuando el código llama a
GlobalOpenTelemetry, el agente lo conecta a su propia implementación. Los espacios forman parte del rastro de la solicitud que los ejecuta y la telemetría se exporta con el resto de la telemetría del entorno. - Cuando el agente no está presente (AEM SDK local, pruebas unitarias o un entorno sin OpenTelemetry habilitado), la API no hace nada, con un coste insignificante. Puede implementar el mismo código en cada entorno.
Tracer o @WithSpanSpanWARN y superior, cuando los registros están habilitadosConfiguración del proyecto set-up-project
Paso 1: Añadir la API de OpenTelemetry add-api
Agregue la API OpenTelemetry y las anotaciones de instrumentación a la sección dependencyManagement del elemento principal del proyecto pom.xml, con el ámbito 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>
A continuación, agregue ambas dependencias, sin versiones, a pom.xml de cada paquete que las use, como 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>
Usar opentelemetry-api 1.61.0 y opentelemetry-instrumentation-annotations 2.27.0 o versiones anteriores. Coinciden con el agente Java de OpenTelemetry que ejecuta AEM as a Cloud Service y las versiones posteriores del agente siguen admitiéndolas. Las funciones de la API añadidas después de la versión compatible con el agente no tienen ningún efecto.
Paso 2: Incrustar la API en el paquete embed-api
AEM as a Cloud Service no exporta los paquetes de API de OpenTelemetry, por lo que cada paquete que utilice la API debe contener su propia copia. Añada las siguientes instrucciones a la configuración de enlace del paquete. Con el tipo de archivo del proyecto AEM, esa es la configuración de bnd-maven-plugin en core/pom.xml. Conservar las entradas que la instrucción Import-Package ya tenga.
Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
!io.opentelemetry.* al principio de Import-Package-includepackage: io.opentelemetry.*-conditionalpackage, sus espacios y atributos se descartan sin error.Cada paquete que utiliza la API incrusta su propia copia y el agente conecta cada copia por separado.
OpenTelemetrySdk ni llame a GlobalOpenTelemetry.set(). La telemetría enviada de esa forma evita al agente, la eliminación de los detalles de infraestructura de Cloud Service descritos en Qué datos se envían y el punto de conexión que configuró.Paso 3: Creación de un rastreador create-tracer
Cree un(a) Tracer para la base de código y reutilícelo en todas partes. En WKND, una pequeña clase lo sostiene:
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() {
}
}
El nombre de ámbito aparece en cada intervalo que cree a través de este rastreador, como el ámbito de instrumentación (o nombre de biblioteca). Utilice un nombre que identifique el código, como el paquete Java base del proyecto.
Lugar en el que aparecen los espacios where-spans-appear
En AEM as a Cloud Service, una solicitud de página ya produce intervalos desde el propio AEM. Los espacios que crea el código se agregan dentro de ellos. Por ejemplo, esta es la ruta al componente Lista de imágenes en un seguimiento de la página de 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
Mientras un componente se procesa, el intervalo de su script HTL es el intervalo actual. Un modelo Sling que el script carga con data-sly-use se ejecuta dentro de ese intervalo, de modo que el modelo creado se convierta en sus elementos secundarios.
Cada intervalo de solicitud se denomina GET /* (o POST /*, etc.). Para encontrar los seguimientos de una página determinada, busque en el atributo url.path de la extensión de solicitud o agregue sus propios atributos, como se muestra en Ejemplo 3.
Ejemplos examples
Ejemplo 1: Seguimiento de una operación costosa trace-operation
Lo que desea lograr trace-operation-goal
El componente Lista de imágenes WKND (wknd/components/image-list) muestra una lista de páginas, cada una con su primera imagen. Para encontrar esa imagen, su modelo Sling, ImageListImpl, ejecuta una consulta QueryBuilder para cada elemento de la lista. Los propios espacios de AEM ya muestran esas consultas: en la página de WKND Magazine, que enumera cinco artículos, el seguimiento contiene cinco espacios de JCR query bajo el espacio HTL de la lista de imágenes. No muestran para qué elemento de lista estaba cada consulta, si encontró una imagen o el tiempo empleado en procesar los resultados. El código original sincronizó la consulta con System.currentTimeMillis() y escribió el resultado en una entrada de registro DEBUG, que no está disponible en producción. Desea que cada consulta aparezca en el seguimiento como una operación con nombre, con ese contexto.
El código trace-operation-code
En ImageListImpl.findPageComponentResources(), cree un intervalo alrededor de la consulta:
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()convierte su espacio en el elemento principal de todo lo que se ejecuta dentro del bloque, incluidos los espacios que AEM crea para la consulta del repositorio.- Llamar siempre a
end()en un bloque definally. Un intervalo que no ha finalizado nunca se exporta. - Asigne al intervalo un nombre fijo que describa la operación y coloque valores que varíen, como rutas, en los atributos.
El resultado trace-operation-outcome
En la página de la revista WKND, que enumera cinco artículos, el seguimiento ahora contiene cinco ImageList.findPageComponentResources espacios dentro de la lista de imágenes, uno para cada elemento de lista. Cada uno es el elemento principal de la extensión JCR query que AEM crea para su consulta:
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
Cada uno de los espacios tiene el ámbito de instrumentación com.adobe.aem.guides.wknd y los atributos siguientes:
wknd.query.path/content/wknd/us/en/magazine/arctic-surfing/jcr:contentwknd.query.resource_typewknd/components/imagewknd.query.limit1wknd.query.result_count1Ahora puede ver, para cada elemento de la lista, qué página buscó la consulta, si encontró una imagen (wknd.query.result_count es 0 cuando no lo encontró) y cuánto tiempo tardó toda la búsqueda, incluido el procesamiento de los resultados. En la página WKND Magazine, cada búsqueda tarda unos 6,5 milisegundos, de los cuales la propia consulta del repositorio tarda unos 3,5: la mitad del tiempo se destina a procesar los resultados, que los propios intervalos de AEM no muestran. Dado que el recuento de consultas aumenta con la longitud de la lista, también puede ver cuánto tiempo de procesamiento del componente corresponde a las búsquedas.
Ejemplo 2: Rastrear un método con una anotación with-span
Lo que desea lograr with-span-goal
El modelo SocialImageImpl de WKND se ejecuta en todas las páginas para rellenar la metaetiqueta og:image. Si un autor no ha establecido una imagen destacada, el modelo recorre el árbol de contenido de la página para encontrar la primera imagen y visita hasta 500 recursos. Uno quiere saber con qué frecuencia corre ese paseo y qué parte del árbol visita sin reestructurar el método.
El código with-span-code
Anotar el método 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
}
- El agente inicia un intervalo cada vez que se ejecuta el método y lo finaliza cuando el método devuelve o emite una excepción.
@WithSpanfunciona en métodos públicos, protegidos y privados.- Para registrar un parámetro de método como un atributo, anote el parámetro con
@SpanAttribute, por ejemplovoid applyTemplate(@SpanAttribute("wknd.page.template") String template). - La anotación solo tiene efecto cuando se ejecuta el agente. En el SDK local de AEM y en las pruebas unitarias, el método se ejecuta sin cambios.
El resultado with-span-outcome
Un intervalo de SocialImage.resolveFromFirstImageOnPage solo aparece en los seguimientos de páginas que no tienen una imagen destacada:
wknd.social_image.nodes_visited21wknd.social_image.walk_truncatedfalsecode.namespacecom.adobe.aem.guides.wknd.core.models.impl.SocialImageImpl (agregado por el agente)code.functionresolveFromFirstImageOnPage (agregado por el agente)Busque este nombre de span para enumerar las páginas que necesitan una imagen destacada. Si wknd.social_image.walk_truncated es true, la caminata se detuvo a los 500 recursos y es posible que la página no tenga ninguna imagen social.
Los intervalos creados por @WithSpan tienen el ámbito de instrumentación del agente (io.opentelemetry.opentelemetry-instrumentation-annotations-1.16), no el suyo.
Ejemplo 3: Agregar contexto empresarial al intervalo actual current-span
Lo que desea lograr current-span-goal
Desea filtrar los seguimientos por un hecho que su código sepa, aquí, de dónde viene la imagen social de la página, sin crear otro intervalo.
El código current-span-code
En SocialImageImpl, agregue un atributo al span que ya está activo:
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()devuelve el intervalo activo más interno, independientemente de si AEM o el código lo han creado. Para un modelo Sling cargado desde HTL, es el lapso de la secuencia de comandos HTL que cargó el modelo.- Si no hay ningún span activo, la llamada a no hace nada.
- Definir
AttributeKeyconstantes una vez mantiene los nombres de atributos coherentes en todo el código.
El resultado current-span-outcome
El intervalo del script customheaderlibs.html de la página, donde WKND carga este modelo, lleva wknd.social_image.source con el valor featured_image, first_image_on_page o none. Busque rastros donde wknd.social_image.source es none para encontrar las páginas publicadas sin una imagen social.
En el sitio WKND, cada página informa de first_image_on_page o none: ninguna página establece una imagen destacada, por lo que cada página paga por el recorrido del árbol de contenido de Ejemplo 2. Al establecer una imagen destacada en las páginas más visitadas, se elimina ese trabajo.
Ejemplo 4: Registrar un error record-errors
Lo que desea lograr record-errors-goal
El modelo ContentAIAvailabilityImpl de WKND comprueba si la Búsqueda por IA de contenido está configurada leyendo una configuración OSGi. Si la búsqueda falla, el modelo trata la inteligencia artificial aplicada al contenido como no configurada y registra una advertencia. La página se sigue procesando y la solicitud se realiza correctamente, por lo que es fácil pasar por alto el error. Desea que el error aparezca en las vistas de error del proveedor de APM, adjuntas a la operación que falló.
El código 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()agrega un eventoexceptional intervalo, con el tipo, el mensaje y el seguimiento de pila de la excepción.setStatus(StatusCode.ERROR, ...)marca el intervalo como erróneo. Configúrelo en el span que represente la operación que ha fallado. La solicitud en sí no se marca como errónea, ya que el componente se recuperó y se procesó la página.
El resultado record-errors-outcome
El seguimiento contiene un intervalo de ContentAIAvailability.resolveConfigured con el estado Error y la descripción Content AI configuration lookup failed. El intervalo tiene un evento exception:
exception.typejava.io.IOExceptionexception.messageConfiguration store unavailableexception.stacktraceLa extensión se cuenta como un error en el proveedor de APM, por lo que aparece siempre que el proveedor muestre operaciones fallidas. El intervalo de solicitud mantiene su estado, ya que la solicitud devolvió el valor HTTP 200.
Ejemplo 5: Correlacionar registros con seguimientos correlate-logs
Lo que desea lograr correlate-logs-goal
Cuando la búsqueda de inteligencia artificial aplicada al contenido en Ejemplo 4 da error, desea pasar de la advertencia de los registros al seguimiento de la solicitud que la produjo y viceversa.
El código correlate-logs-code
No necesita ningún código OpenTelemetry. Inicie sesión con SLF4J como de costumbre y habilite los registros (logs: true) en su configuración de OpenTelemetry:
LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
El agente agrega los ID de seguimiento y de span del span actual a cada entrada de registro. Debido a que ContentAIAvailabilityImpl registra mientras su intervalo de Ejemplo 4 es actual, la advertencia está vinculada a dicho intervalo.
El resultado correlate-logs-outcome
Su proveedor de APM recibe una entrada de registro como la siguiente y puede mostrarla con su seguimiento:
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 o superior. Las entradas INFO y DEBUG no lo son. No confíe en ellos para los diagnósticos de producción: registre la información como un atributo span en su lugar.Prácticas recomendadas best-practices
- El nombre se expande después de las operaciones, no después de los datos. Use nombres como
ImageList.findPageComponentResourcesy coloque valores que varíen, como rutas e ID, en los atributos. - Agregue a sus propios nombres de atributos un espacio de nombres para su proyecto, como
wknd.. Esto evita choques con las convenciones semánticas de OpenTelemetry y con los nombres que utiliza AEM. No utilice el prefijoaem., que está reservado para AEM. - No registre datos personales ni secretos en los nombres de span, atributos, eventos o mensajes de registro. Esto incluye nombres, direcciones de correo electrónico, direcciones IP, tokens y cadenas de consulta que pueden contenerlos.
- Agregar espacios para operaciones, no iteraciones. Por ejemplo, Ejemplo 2 crea un intervalo para el recorrido del árbol de contenido y registra el número de recursos visitados como un atributo, en lugar de crear un intervalo para cada recurso. AEM ya crea muchos espacios para una sola página (unos 260 para la página de WKND Magazine) y cada espacio agrega al volumen que ingiere su proveedor de APM.
- Cree trazadores una vez y, y vuelva a usarlos.
- Finalizar cada intervalo en un bloque de
finallyy cerrar cadaScopeen el subproceso que lo abrió. - Pase el contexto a los subprocesos que inicie usted mismo, por ejemplo con
Context.current().wrap(runnable), para que el trabajo realizado en otro subproceso se una al mismo seguimiento. - Se espera un nuevo seguimiento para trabajar fuera de una solicitud. El código que se ejecuta en un trabajo de Sling, una tarea programada o un detector de eventos no tiene intervalo de solicitud. Los espacios que crea allí comienzan sus propios seguimientos, uno para cada ejecución.
Prueba de la instrumentación test
- Pruebas unitarias: Sin el agente, la API no hace nada, por lo que las pruebas unitarias existentes siguen pasando sin cambios.
- Compilación: Ejecute
mvn clean installlocalmente. El analizador de AEM, que también ejecuta Cloud Manager, comprueba que se puedan resolver las importaciones del paquete. - Contenido del paquete: Compruebe que el paquete contiene la API completa, tal como se describe en Paso 2. Por ejemplo,
unzip -l core/target/<your-core-bundle>.jar | grep "io/opentelemetry/api/baggage/"debe enumerar clases y el paqueteMETA-INF/MANIFEST.MFno debe importar ningún paqueteio.opentelemetry. - Su proveedor de APM: Implemente en un entorno de desarrollo que tenga habilitado OpenTelemetry, solicite una página que ejecute su código y busque en su proveedor de APM los nombres de sus span o el ámbito de instrumentación que eligió en Paso 3.
Resolución de problemas 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.* al principio de Import-Package. Consulte Paso 2.missing requirement ... osgi.wiring.package=io.opentelemetry.api.incubator.@WithSpan se expandirá, pero los demás expansiones y los atributos que configuró con Span.current() no aparecen.-includepackage: io.opentelemetry.*. Consulte Paso 2.Context.current().wrap(runnable).WARN nivel y superiores, y solo cuando los registros están habilitados en su configuración.Artículos relacionados related-articles
- OpenTelemetry
- Registro
- API de Java OpenTelemetry (opentelemetry.io)
- Anotaciones para el agente Java OpenTelemetry (opentelemetry.io)
- Convenciones semánticas de OpenTelemetry (opentelemetry.io)
- Sitio de referencia WKND (GitHub)
- Rama de instrumentación personalizada WKND (GitHub)