[spring-projects/spring-boot]提供忽略路由采样的配置

2024-06-26 787 views
4

现在,您有 3 个选项可以配置是否报告跟踪。

  1. 使用概率配置属性,它只允许您控制报告的百分比

  2. 但是使用ObservationRegistryPredicate,似乎没有办法忽略由于 HTTP 请求而创建的任何跨度,因为后续跨度没有可以匹配的 http.url 属性。

  3. 提供自定义的Sampler。但是该shouldSample方法不会接收跨度的最终属性,因此您无法确定样本适用于哪条路径。

理想情况下,Spring 会提供一种配置方法。主要用例是忽略与之相关的任何跟踪,/actuator因为这些跟踪通常仅用于健康检查,并且会使跟踪后端变得混乱。

如果还有其他我还没想到的有效方法,我很乐意听听!

回答

3

有一种方法,但不太好:

@Configuration(proxyBeanMethods = false)
class MyObservationConfig {
    @Bean
    ObservationPredicate predicate() {
        return (s, context) -> {
            if (context instanceof ServerRequestObservationContext serverRequestObservationContext) {
                HttpServletRequest carrier = serverRequestObservationContext.getCarrier();
                return !carrier.getServletPath().startsWith("/actuator");
            }
            return true;
        };
    }
}

Context完全填充在里面ObservationFilter,但我找不到办法阻止报告观察结果ObservationFilter

@jonatan-ivanov 你知道有什么办法吗?

5

@mhalbritter 不幸的是,正如我在原始问题中提到的那样,它不起作用。至少在我的经验中没有。

这样,第一个跨度将被忽略,但任何后续观察仍将被报告。在谓词内部,似乎没有办法获取父跨度,因此您无法递归地找出新观察是否与请求相关。

例如,使用该谓词,我们仍然可以看到使用 WebClient 或 RestTemplate 的调用被报告给我们的跟踪后端,因为它们没有使用HttpServletRequestContext

8

如果您想阻止报告跨度,您可以:

  • 编写ObservationPredicate并过滤掉Observation:这也会过滤掉指标。
  • 写一个SpanExportingPredicate(参见SpanIgnoringSpanExportingPredicate:):这只会过滤掉跨度并保留指标。

您可以注入自己的采样器,但它适用于不同的用例,我会选择上述选项。

这是一个工作示例(类似于 Moritz 的:https://github.com/spring-projects/spring-boot/issues/34400#issuecomment-1453162960),它过滤掉服务器(和客户端)上执行器的观察结果:CommonActuatorConfig

我不确定我是否遇到了后续跨度的问题:使用执行器,您如何进行任何(自定义健康检查)?您可以获取父级,有一种getParentObservation方法。

4

@jonatan-ivanov 这个解决方案的问题在于谓词只作用于跨度。我知道有一种getParentObservation方法,但总是null在使用谓词过滤掉时ServerRequestObservationContext

您最终得到的是一堆断开连接的 Spring Security 跟踪(没有父级),当您使用 tempo 之类的工具并尝试查询事物时,这会造成混乱。这也意味着您在 Spring 应用程序中的任何自定义观察仍会被报告,但您无法看到它们运行的​​原因(如果它们是由排除的路径中的调用发起的)。是的,我们确实注册了自定义健康检查,但这个用例不仅仅是忽略执行器。

5

我不确定您说的“谓词仅作用于跨度”是什么意思。ObservationPredicate作用于Observations 的SpanPredicate作用于Spans。除了Observations/ Spans 之外,它们还应该作用于什么?

我知道有一种getParentObservation方法但总是null使用谓词来过滤掉ServerRequestObservationContext

由于ServerRequestObservationContext属于 ,Observation而 又属于服务器收到的请求 ,因此它没有父级,这是第一个Observation。它应该有什么父级?

你最终得到的是一堆 Spring Security 跟踪

现在我们开始讨论!:) 如果您根本不想看到 Spring Security 跨度,您可以禁用它们。如果您只想在未过滤服务器请求的观察时看到它们,那么同样,只有在它们没有父级时,您才能禁用它们。

2

我认为我们彼此误解了,因此我整理了一个可供您玩的最小可重复样本。

我期望发生的情况是:任何与呼叫相关的跨度都不/actuator/health应被报告。

实际情况:任何自定义观察、Spring 安全观察或数据源观察等仍被报告,但现在已断开连接。

从你的回复中我了解到的情况来看,你正在假设这样的事情应该有效:

@Configuration
public class TestConfiguration {
    @Bean
    ObservationPredicate ignoreActuator() {
        return (s, context) -> ignoreActuatorRecursive(context);
    };

    public boolean ignoreActuatorRecursive(Observation.ContextView context) {
        if (context instanceof ServerRequestObservationContext serverRequestObservationContext) {
            HttpServletRequest carrier = serverRequestObservationContext.getCarrier();
            return !carrier.getServletPath().startsWith("/actuator");
        }

        if (context.getParentObservation() != null) {  // <----- getParentObservation() is always null
            return ignoreActuatorRecursive(context.getParentObservation().getContextView());
        }

        return true;
    }
}

这会导致报告以下内容: 图像

这是不正确的行为,因为它会创建断开的跨度,并且实际上不会导致报告给跟踪后端的数据有任何有意义的减少。该配置不起作用有几个原因:

  1. 从来没有家长对这些观察进行过观察
  2. Spring security 没有可匹配的 http.url 属性(也不应该有)
  3. 您在应用程序中可能拥有的任何自定义观察结果(例如数据源等)也将在此处报告,除非您添加属性以匹配每个观察结果的 URL(其中一些您无法控制)

这对你有帮助吗?此应用程序仅使用该配置运行,pom 中的内容如下:

```xml 4.0.0 启动 spring-boot-starter-parent 3.0.3 com.示例 演示 0.0.1-快照 演示 Spring Boot 的演示项目 17 启动 spring-boot-starter-执行器 启动 spring-boot-starter-web 启动 spring-boot-starter-安全 io.微米 微米观察 io.微米 微米追踪桥梁酒店 io.opentelemetry opentelemetry-导出器-zipkin io.微米 微米注册表-普罗米修斯 运行 启动 spring-boot-maven 插件 ```
1

样本肯定有帮助,谢谢。你能和我们分享一下吗?

7

@mhalbritter 我做到了,除了上面的配置类之外,没有其他变化。我还提供了所有使用的依赖项。如果这还不够,你还想了解更多,请告诉我。

2

我猜@mhalbritter 想要的是我们可以克隆、导入到 IDE 并调试的项目。我们看到的是上面的代码片段,而不是示例项目。

我还没有尝试为此设置示例项目,但是有一件事令我困惑,在你的代码片段中我看到了这一点:

if (context.getParentObservation() != null) {  // <----- THIS IS NEVER TRUE

但你会说:

从来没有家长对这些观察进行过观察

那么有没有父节点呢?如果有父节点,它是什么?

4

永远没有父级。context.getParentObservation != null始终为 false(即始终为 null)。我将修改该代码片段中的注释以使其更清晰。我没有示例项目可供您加载,但我提供了您在本地运行它所需的所有 pom 和代码。

3

因此,安全跨度应该有一个父级。当我尝试为您提供解决方法时,这是我的假设:如果它们始终有一个父级,那么唯一没有父级的情况就是它们的父级被忽略,因此您也可以忽略它们,因此您可以执行以下操作:

if (context..getName().startsWith("spring.security.") && context.getParentObservation() == null) {
    return false; // assuming parent is also ignored
}

如果这个假设不成立,我们需要修复 Spring Security 中的仪表。让我们内部讨论这个问题,但你能尝试另一种解决方法吗?你能尝试使用SpanFilter并设置sampledfalse执行器跨度吗?

8

您的假设不成立。所有观察结果(无论是否被忽略)都没有父集。

的函数签名SpanFilter不允许修改采样状态,仅允许在对 进行操作时从跨度中添加/删除数据FinishedSpan

https://github.com/micrometer-metrics/tracing/blob/e76ef15fd01df8d8711c7f3b9ee9003b1bae224f/micrometer-tracing/src/main/java/io/micrometer/tracing/exporter/FinishedSpan.java#L35

相反,我尝试使用,SpanExportingPredicate但它从未被调用。保留类型以使其清晰。

    @Bean
    SpanExportingPredicate spanExportingPredicate() {
        return new SpanExportingPredicate() {
            @Override
            public boolean isExportable(FinishedSpan span) {
                return span.getTags().get("http.url") == null || !span.getTags().get("http.url").startsWith("/actuator");
            }
        };
    }

如果它被调用,我预计它会遇到与ObservationPredicate

7

唉。这是示例:sb34400.zip。运行说明在自述文件中。

5

@mhalbritter 谢谢!

@braunsonm

您的假设不成立。所有观察结果(无论是否被忽略)都没有父集。

很抱歉,我错过了在谓词测试之后设置的父级,因此父级会存在,但不是在谓词中需要它的时候,我打开了一个问题来改进这一点:https://github.com/micrometer-metrics/micrometer/issues/3678

我认为无法看到 Spring Security Observations 属于哪个请求是一个问题,我也为此创建了一个:https ://github.com/spring-projects/spring-security/issues/12854 仅供参考:这不需要设置任何键值或标签,如果保存Context此信息就足够了,因为 ObservationPredicate 可以访问它。

相反,我尝试使用 SpanExportingPredicate,但它从未被调用。

我错过了SpanExportingPredicate添加的内容3.1.0-M1https://github.com/spring-projects/spring-boot/pull/34002如果您想使用它,您需要使用3.1.x或者您可以执行与拉取请求中看到相同的操作(您可能需要覆盖其他 bean)。

我想我可以给你一个不那么简单的解决方法:

// This adds the http.url keyvalue to security observations from the root (mvc) observation
// You add an ignoreSpan=true keyValue instead if you want, or something that can signal to the SpanExportingPredicate what to ignore
@Bean
ObservationFilter urlObservationFilter() {
    return context -> {
        if (context.getName().startsWith("spring.security.")) {
            Context root = getRoot(context);
            if (root.getName().equals("http.server.requests")) {
                context.addHighCardinalityKeyValue(root.getHighCardinalityKeyValue("http.url"));
            }
        }

        return context;
    };
}

private Observation.Context getRoot(Observation.Context context) {
    if (context.getParentObservation() == null) {
        return context;
    }
    else {
        return getRoot((Context) context.getParentObservation().getContextView());
    }
}

// This ignores actuator spans but its logic depends on the ObservationFilter above
// Auto-configuration for SpanExportingPredicate was added in 3.1.0-M1
// So either you use 3.1.x or you can add the same to your config : https://github.com/spring-projects/spring-boot/pull/34002
@Bean
SpanExportingPredicate actuatorSpanExportingPredicate() {
    return span -> !span.getTags().get("http.url").startsWith("/actuator");
}

一旦上述 Spring Security 问题得到解决,此解决方法将不再必要。您认为在 Boot 中解决此问题如何?我认为我们在这里能做的事情不多。

9

我同意,特别是我不认为http.url在这里添加特定的观察是正确的方法,因为我提到自定义观察或库提供的观察也需要添加可能具有泄漏抽象的属性。

我认为您打开的千分尺问题在完成后将会解决这个问题。

感谢您对此问题的深入研究。

1

我不认为将 http.url 添加到具体观察中是正确的方法

这就是为什么我评论说您可以使用任何您想要的数据(例如ignoreSpan=true:),请随意使用您自己的键值,您“只”需要维护两个组件之间的“合同”。

7

你好@jonatan-ivanov,我尝试通过从https://github.com/spring-projects/spring-boot/pull/34002添加配置来应用您的修复程序,但结果发现SpanProcessor没有注释,@ConditionalOnMissingBean所以我无法替换它,除非有什么我不知道的方法?

它似乎恰恰是唯一没有注释的 bean,这是预期的还是可能是错过的?https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java#L127

2

@sibethencourt 如果您检查 的SpanProcessor使用位置,您会发现SdkTracerProvider。因此,您可能还需要SdkTracerProvider根据您的情况创建一个。

如果你检查一下它的用法,这对我来说似乎是故意的ObjectProvider<SpanProcessor>

spanProcessors.orderedStream().forEach(builder::addSpanProcessor);

因此它支持多个附加处理器。

0

嗨 Jonatan,

谢谢回复。是的,我设法创建了第二个支持跨度处理器的处理器SpanExportingPredicate,但由于原始处理器也在那里,我无法禁用它,所以两者都在发送跟踪,所以最后即使其中一个处理器成功过滤了跟踪,原始处理器仍然会发送它。

我想如果我创建一个新的SdkTracerProvider作品我会尝试一下,谢谢?