使用OpenTelemetry进行自定义检测 custom-instrumentation
为您的环境启用OpenTelemetry后,AEM as a Cloud Service会跟踪每个请求并自动导出JVM、HTTP和AEM指标。 该遥测技术描述了AEM。 它显示页面渲染耗时900毫秒以及渲染的组件。 它无法显示您自己的代码正在执行什么操作,例如存储库查询用于哪个列表项,是否找到任何内容,或者哪些页面因作者省略字段而回退到成本高昂的搜索。
您可以使用OpenTelemetry API自行添加该知识。 代码创建的范围和属性使用自动遥测传输:相同的服务名称、相同的处理、相同的APM提供程序。 您未在代码中配置端点、凭据或导出程序。
如果您不熟悉AEM as a Cloud Service上的OpenTelemetry,请从教程Observability via OpenTelemetry开始,了解它的工作方式。 然后按照配置OpenTelemetry以深入了解AEM应用程序为您的环境启用它。
此页面上的示例检测WKND引用站点。 每个示例都描述了要达到的目标、要添加的代码以及预期在APM提供程序中产生的结果。 完整的检测代码(包括在跨域上声明的单元测试)位于WKND存储库的tutorial/otel-custom-instrumentation分支上。
自定义检测的工作原理 how-it-works
- Adobe在您的AEM环境中运行的OpenTelemetry Java代理包含OpenTelemetry SDK和导出程序。 您的代码仅依赖于OpenTelemetry API。
- 当您的代码调用
GlobalOpenTelemetry时,代理会将其连接到其自己的实现。 范围将成为运行这些范围的请求跟踪的一部分,并且遥测将与环境的其余遥测一起导出。 - 如果代理不存在(本地AEM SDK、单元测试或未启用OpenTelemetry的环境),则API不会执行任何操作,开销极小。 您可以将相同的代码部署到每个环境。
Tracer或@WithSpan注释Span APIWARN级别及以上启用日志时设置您的项目 set-up-project
步骤1:添加OpenTelemetry API add-api
将OpenTelemetry API和检测注释添加到项目父pom.xml的dependencyManagement部分,范围为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>
然后将两个依赖项(不带版本)添加到使用它们的每个包的pom.xml中,如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>
使用opentelemetry-api 1.61.0和opentelemetry-instrumentation-annotations 2.27.0或更早版本。 它们与AEM as a Cloud Service运行的OpenTelemetry Java代理匹配,并且代理的更高版本继续支持它们。 在代理支持的版本之后添加的API功能不起作用。
步骤2:将API嵌入捆绑包中 embed-api
AEM as a Cloud Service不导出OpenTelemetry API包,因此使用该API的每个包都必须包含其自己的副本。 将以下说明添加到捆绑包的捆绑配置中。 对于AEM项目原型,它是core/pom.xml中的bnd-maven-plugin配置。 保留Import-Package指令已有的任何条目。
Import-Package: !io.opentelemetry.*,javax.annotation;version=0.0.0,*
-includepackage: io.opentelemetry.*
Import-Package开头的!io.opentelemetry.*-includepackage: io.opentelemetry.*-conditionalpackage),则将丢弃范围和属性,而不会出现错误。每个使用该API的包都嵌入其自身的副本,并且代理会单独连接每个副本。
OpenTelemetrySdk实例或调用GlobalOpenTelemetry.set()。 通过此方式发送的遥测绕过了代理,删除了发送了哪些数据中描述的Cloud Service基础结构详细信息,以及您配置的端点。第3步:创建跟踪器 create-tracer
为代码库创建一个Tracer并在所有位置重复使用。 在WKND中,有一个小类包含它:
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() {
}
}
范围名称显示在您通过此跟踪器创建的每个跨度上,作为检测范围(或库名称)。 使用可标识代码的名称,如项目的基本Java包。
显示跨距的位置 where-spans-appear
在AEM as a Cloud Service上,页面请求已从AEM本身生成跨区。 您的代码创建的范围将添加到其中。 例如,下面是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
在组件呈现时,其HTL脚本的跨度是当前跨度。 脚本加载且包含data-sly-use的Sling模型在该范围内运行,因此该模型创建的跨度将成为其子项。
每个请求范围都名为GET /*(或POST /*,以此类推)。 要查找特定页面的跟踪,请搜索请求范围的url.path属性,或添加您自己的属性,如示例3所示。
示例 examples
示例1:跟踪昂贵操作 trace-operation
您希望实现的目标 trace-operation-goal
WKND图像列表组件(wknd/components/image-list)显示一个页面列表,每个页面都具有其第一个图像。 为了查找该图像,其Sling模型ImageListImpl对列表中的每个项目运行一个QueryBuilder查询。 AEM自己的范围已显示这些查询:在列出五篇文章的WKND Magazine页面上,跟踪在图像列表的HTL范围下包含五个JCR query范围。 它们不会显示每个查询针对的列表项,无论它找到的是图像,还是处理结果所花费的时间。 原始代码使用System.currentTimeMillis()对查询进行计时,并将结果写入到DEBUG日志条目,该日志条目在生产环境中不可用。 您希望每个查询都以命名操作的形式显示在跟踪中,并包含该上下文。
代码 trace-operation-code
在ImageListImpl.findPageComponentResources()中,围绕查询创建一个范围:
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()将您的范围设置为块内运行的所有内容的父级,包括AEM为存储库查询创建的范围。- 始终在
finally块中调用end()。 从不导出未结束的跨度。 - 为跨度提供一个描述操作的固定名称,并将更改的值(如路径)置于属性中。
结果 trace-operation-outcome
在列出五篇文章的WKND杂志页面上,跟踪现在包含图像列表内的五个ImageList.findPageComponentResources跨区,每个列表项各一个。 每个对象都是AEM为其查询创建的JCR 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
您的每个跨域都具有检测范围com.adobe.aem.guides.wknd和以下属性:
wknd.query.path/content/wknd/us/en/magazine/arctic-surfing/jcr:contentwknd.query.resource_typewknd/components/imagewknd.query.limit1wknd.query.result_count1现在,您可以看到对于每个列表项,查询搜索了哪个页面,它是否找到了图像(wknd.query.result_count是0,而它没有),以及整个查找耗时(包括处理结果)。 在WKND Magazine页面上,每个查找大约需要6.5毫秒,其中存储库查询本身大约需要3.5毫秒:一半的时间用于处理结果,而AEM自己的跨度不显示这些结果。 由于查询计数会随着列表的长度而增加,因此您还可以看到查找占用的组件渲染时间。
示例2:使用注释跟踪方法 with-span
您希望实现的目标 with-span-goal
WKND的SocialImageImpl模型在每个页面上运行以填充og:image Meta标记。 如果作者未设置精选图像,则模型将遍历页面的内容树以查找第一个图像,并访问多达500个资源。 您希望了解该行走运行的频率,以及它访问树的频率,而不重新构建方法。
代码 with-span-code
使用@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
}
- 代理在每次运行方法时都会启动一个范围,并在方法返回或引发异常时结束该范围。
@WithSpan使用公共、受保护以及私有方法。- 要将方法参数记录为属性,请使用
@SpanAttribute注释该参数,例如void applyTemplate(@SpanAttribute("wknd.page.template") String template)。 - 注释仅在代理运行时生效。 在本地AEM SDK和单元测试中,方法未更改地运行。
结果 with-span-outcome
SocialImage.resolveFromFirstImageOnPage范围仅出现在没有精选图像的页面的跟踪中:
wknd.social_image.nodes_visited21wknd.social_image.walk_truncatedfalsecode.namespacecom.adobe.aem.guides.wknd.core.models.impl.SocialImageImpl (由代理添加)code.functionresolveFromFirstImageOnPage (由代理添加)搜索此范围名称可列出需要精选图像的页面。 如果wknd.social_image.walk_truncated为true,则漫游停止在500个资源,并且页面可能根本没有社交图像。
由@WithSpan创建的跨域承载代理的检测范围(io.opentelemetry.opentelemetry-instrumentation-annotations-1.16),而不是您的检测范围。
示例3:将业务上下文添加到当前范围 current-span
您希望实现的目标 current-span-goal
您希望通过代码知道页面社交图像来源的事实来过滤跟踪,而不是创建另一个范围。
代码 current-span-code
在SocialImageImpl中,向已处于活动状态的范围中添加一个属性:
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()返回最内层的活动范围,无论AEM还是您的代码创建了该范围。 对于从HTL加载的Sling模型,它是加载该模型的HTL脚本的范围。- 如果没有活动span,则调用不会执行任何操作。
- 定义
AttributeKey常量后,将使所有代码中的属性名称保持一致。
结果 current-span-outcome
页面customheaderlibs.html脚本的跨度(其中WKND加载此模型)携带值为featured_image、first_image_on_page或none的wknd.social_image.source。 搜索wknd.social_image.source为none的跟踪以查找在没有社交图像的情况下发布的页面。
在WKND网站上,每个页面都会报告first_image_on_page或none:没有页面设置特色图像,因此每个页面都会支付从示例2开始的内容树演练的费用。 在访问次数最多的页面上设置精选图像会移除该效果。
示例4:记录错误 record-errors
您希望实现的目标 record-errors-goal
WKND的ContentAIAvailabilityImpl模型检查是否通过读取OSGi配置配置了ContentAI 搜索。 如果查找失败,则模型会将内容人工智能视为未配置并记录警告。 页面仍会呈现,并且请求成功,因此很容易错过失败。 您希望失败显示在APM提供程序的错误视图中,附加到失败的操作。
代码 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()向范围中添加一个exception事件,该事件具有异常的类型、消息和栈栈跟踪。setStatus(StatusCode.ERROR, ...)将范围标记为失败。 在表示失败的操作的范围上设置它。 请求本身不会标记为失败,因为组件已恢复并且页面已渲染。
结果 record-errors-outcome
跟踪包含状态为 错误 和描述为Content AI configuration lookup failed的ContentAIAvailability.resolveConfigured范围。 范围具有exception事件:
exception.typejava.io.IOExceptionexception.messageConfiguration store unavailableexception.stacktracespan在APM提供程序中计为错误,因此无论您的提供程序显示失败的操作如何,它都会显示。 请求范围会保持其状态,因为请求返回了HTTP 200。
示例5:将日志与跟踪关联 correlate-logs
您希望实现的目标 correlate-logs-goal
当示例4中的内容人工智能查找失败时,您想要从日志中的警告转到生成该警告的请求的跟踪,然后再返回。
代码 correlate-logs-code
您不需要任何OpenTelemetry代码。 照常使用SLF4J进行记录,并在OpenTelemetry配置中启用日志(logs: true):
LOGGER.warn("Unable to determine whether Content AI is configured (PID {})", CONTENT_AI_CLIENT_PID, e);
代理将当前范围的跟踪和范围ID添加到每个日志条目。 由于ContentAIAvailabilityImpl在其从示例4起的范围为当前时进行记录,因此该警告链接到该范围。
结果 correlate-logs-outcome
您的APM提供商会收到如下所示的日志条目,并可显示其跟踪信息:
WARNUnable to determine whether Content AI is configured (PID com.adobe.cq.wcm.core.components.internal.services.contentai.ContentAIClientImpl)ContentAIAvailability.resolveConfigured范围的IDexception.type, exception.message, exception.stacktracecom.adobe.aem.guides.wknd.core.models.impl.contentai.ContentAIAvailabilityImplWARN级别及以上的日志条目。 INFO和DEBUG条目不是。 不要依赖它们进行生产诊断:请将该信息记录为span属性。最佳做法 best-practices
- 名称在操作后跨越,而不是跨数据。 使用名称(如
ImageList.findPageComponentResources),并将更改的值(如路径和ID)放入属性中。 - 为您自己的属性名称添加项目的命名空间,如
wknd.。 这样可避免与OpenTelemetry语义约定和AEM使用的名称冲突。 请勿使用为AEM保留的aem.前缀。 - 不要在span名称、属性、事件或日志消息中记录个人数据或密钥。 这包括名称、电子邮件地址、IP地址、令牌和可包含这些地址的查询字符串。
- 为操作添加跨区,而不是为迭代添加跨区。 例如,示例2为内容树演练创建一个范围,并将访问的资源数记录为一个属性,而不是为每个资源创建一个范围。 AEM已经为单个页面创建了多个跨区(WKND Magazine页面大约创建了260个),每个跨区都会添加到APM提供商摄取的卷中。
- 创建跟踪器一次,并重用它们。
- 在
finally块中结束每个span,并在打开它的线程上每隔Scope关闭一次。 - 将上下文传递给您自己启动的线程,例如
Context.current().wrap(runnable),以便在另一个线程上完成的工作将加入相同的跟踪。 - 需要新的工作跟踪而不是请求。 在Sling作业、计划任务或事件侦听器中运行的代码没有请求范围。 您在此处创建的跨度会启动它们自己的跟踪,每次运行一个。
测试您的工具 test
- 单元测试:如果没有代理,API将不执行任何操作,因此您现有的单元测试将保持不变。
- 生成:在本地运行
mvn clean install。 AEM分析器(Cloud Manager也运行)会检查是否能够解析捆绑包的导入。 - 包内容:检查您的包是否包含完整的API,如步骤2中所述。 例如,
unzip -l core/target/<your-core-bundle>.jar | grep "io/opentelemetry/api/baggage/"必须列出类,捆绑包的META-INF/MANIFEST.MF不得导入任何io.opentelemetry包。 - 您的APM提供程序:部署到启用了OpenTelemetry的开发环境,请求运行代码的页面,然后搜索您的APM提供程序以查找您的跨域名称或您在步骤3中选择的检测范围。
疑难解答 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.missing requirement ... osgi.wiring.package=io.opentelemetry.api.incubator。@WithSpan范围出现,但您的其他范围以及您使用Span.current()设置的属性未出现。-includepackage: io.opentelemetry.*。 请参阅步骤2。Context.current().wrap(runnable)。WARN级别及更高级别的条目,并且仅在配置中启用日志时发送。相关文章 related-articles
- OpenTelemetry
- 日志记录
- OpenTelemetry Java API (opentelemetry.io)
- OpenTelemetry Java代理的注释 (opentelemetry.io)
- OpenTelemetry语义约定 (opentelemetry.io)
- WKND引用站点 (GitHub)
- WKND自定义检测分支 (GitHub)