Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tracer: add CEL sampler to OpenTelemetry #38182

Merged
merged 21 commits into from
Feb 26, 2025
Merged
2 changes: 2 additions & 0 deletions api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ api_proto_package(
deps = [
"//envoy/config/core/v3:pkg",
"@com_github_cncf_xds//udpa/annotations:pkg",
"@com_github_cncf_xds//xds/annotations/v3:pkg",
"@com_github_cncf_xds//xds/type/v3:pkg",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";

package envoy.extensions.tracers.opentelemetry.samplers.v3;

import "xds/annotations/v3/status.proto";
import "xds/type/v3/cel.proto";

import "udpa/annotations/status.proto";

option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.samplers.v3";
option java_outer_classname = "CelSamplerProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/samplers/v3;samplersv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Always On Sampler config]
// Configuration for the "CEL" Sampler extension.
//
// [#extension: envoy.tracers.opentelemetry.samplers.cel]

message CELSamplerConfig {
// Expression that, when evaluated, will be used to make sample decision.
xds.type.v3.CelExpression expression = 1
[(xds.annotations.v3.field_status).work_in_progress = true];
}
5 changes: 5 additions & 0 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ REPOSITORY_LOCATIONS_SPEC = dict(
"envoy.tracers.opentelemetry",
"envoy.tracers.opentelemetry.samplers.always_on",
"envoy.tracers.opentelemetry.samplers.dynatrace",
"envoy.tracers.opentelemetry.samplers.cel",
],
release_date = "2025-01-22",
cpe = "N/A",
Expand Down Expand Up @@ -1268,6 +1269,8 @@ REPOSITORY_LOCATIONS_SPEC = dict(
"envoy.formatter.cel",
"envoy.matching.inputs.cel_data_input",
"envoy.matching.matchers.cel_matcher",
"envoy.tracers.opentelemetry",
"envoy.tracers.opentelemetry.samplers.cel",
],
release_date = "2024-10-25",
cpe = "N/A",
Expand Down Expand Up @@ -1299,6 +1302,8 @@ REPOSITORY_LOCATIONS_SPEC = dict(
"envoy.rbac.matchers.upstream_ip_port",
"envoy.matching.inputs.cel_data_input",
"envoy.matching.matchers.cel_matcher",
"envoy.tracers.opentelemetry",
"envoy.tracers.opentelemetry.samplers.cel",
],
release_date = "2025-01-25",
cpe = "cpe:2.3:a:google:flatbuffers:*",
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ EXTENSIONS = {

"envoy.tracers.opentelemetry.samplers.always_on": "//source/extensions/tracers/opentelemetry/samplers/always_on:config",
"envoy.tracers.opentelemetry.samplers.dynatrace": "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config",
"envoy.tracers.opentelemetry.samplers.cel": "//source/extensions/tracers/opentelemetry/samplers/cel:config",

#
# Transport sockets
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,13 @@ envoy.tracers.opentelemetry.samplers.dynatrace:
status: wip
type_urls:
- envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig
envoy.tracers.opentelemetry.samplers.cel:
categories:
- envoy.tracers.opentelemetry.samplers
security_posture: unknown
status: wip
type_urls:
- envoy.extensions.tracers.opentelemetry.samplers.v3.CELSamplerConfig
envoy.tracers.skywalking:
categories:
- envoy.tracers
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/common/expr/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ envoy_cc_library(
"//envoy/singleton:manager_interface",
"//source/common/http:utility_lib",
"//source/common/protobuf",
"@com_github_cncf_xds//xds/type/v3:pkg_cc_proto",
"@com_google_cel_cpp//eval/public:activation",
"@com_google_cel_cpp//eval/public:builtin_func_registrar",
"@com_google_cel_cpp//eval/public:cel_expr_builder_factory",
Expand Down
27 changes: 27 additions & 0 deletions source/extensions/filters/common/expr/evaluator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "extensions/regex_functions.h"

#include "cel/expr/syntax.pb.h"
#include "eval/public/builtin_func_registrar.h"
#include "eval/public/cel_expr_builder_factory.h"

Expand Down Expand Up @@ -141,6 +142,32 @@ BuilderInstanceSharedPtr getBuilder(Server::Configuration::CommonFactoryContext&
[] { return std::make_shared<BuilderInstance>(createBuilder(nullptr)); });
}

// Converts from CEL canonical to CEL v1alpha1
absl::optional<google::api::expr::v1alpha1::Expr>
getExpr(const ::xds::type::v3::CelExpression& expression) {
::cel::expr::Expr expr;
if (expression.has_cel_expr_checked()) {
expr = expression.cel_expr_checked().expr();
} else if (expression.has_cel_expr_parsed()) {
expr = expression.cel_expr_parsed().expr();
} else {
return {};
}

std::string data;
if (!expr.SerializeToString(&data)) {
return {};
}

// Parse the string into the target namespace message
google::api::expr::v1alpha1::Expr v1alpha1Expr;
if (!v1alpha1Expr.ParseFromString(data)) {
return {};
}

return v1alpha1Expr;
}

ExpressionPtr createExpression(Builder& builder, const google::api::expr::v1alpha1::Expr& expr) {
google::api::expr::v1alpha1::SourceInfo source_info;
auto cel_expression_status = builder.CreateExpression(&expr, &source_info);
Expand Down
6 changes: 6 additions & 0 deletions source/extensions/filters/common/expr/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include "eval/public/cel_expression.h"
#include "eval/public/cel_value.h"

#include "xds/type/v3/cel.pb.h"

#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
Expand Down Expand Up @@ -92,6 +94,10 @@ BuilderPtr createBuilder(Protobuf::Arena* arena);
// Gets the singleton expression builder. Must be called on the main thread.
BuilderInstanceSharedPtr getBuilder(Server::Configuration::CommonFactoryContext& context);

// Converts from CEL canonical to CEL v1alpha1
absl::optional<google::api::expr::v1alpha1::Expr>
getExpr(const ::xds::type::v3::CelExpression& expression);

// Creates an interpretable expression from a protobuf representation.
// Throws an exception if fails to construct a runtime expression.
ExpressionPtr createExpression(Builder& builder, const google::api::expr::v1alpha1::Expr& expr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,16 @@ Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config,
const auto span_kind = getSpanKind(config);
if (!extractor.propagationHeaderPresent()) {
// No propagation header, so we can create a fresh span with the given decision.
Tracing::SpanPtr new_open_telemetry_span = tracer.startSpan(
operation_name, stream_info.startTime(), tracing_decision, trace_context, span_kind);
Tracing::SpanPtr new_open_telemetry_span =
tracer.startSpan(operation_name, stream_info, stream_info.startTime(), tracing_decision,
trace_context, span_kind);
return new_open_telemetry_span;
} else {
// Try to extract the span context. If we can't, just return a null span.
absl::StatusOr<SpanContext> span_context = extractor.extractSpanContext();
if (span_context.ok()) {
return tracer.startSpan(operation_name, stream_info.startTime(), span_context.value(),
trace_context, span_kind);
return tracer.startSpan(operation_name, stream_info, stream_info.startTime(),
span_context.value(), trace_context, span_kind);
} else {
ENVOY_LOG(trace, "Unable to extract span context: ", span_context.status());
return std::make_unique<Tracing::NullSpan>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

SamplingResult AlwaysOnSampler::shouldSample(const absl::optional<SpanContext> parent_context,
SamplingResult AlwaysOnSampler::shouldSample(const StreamInfo::StreamInfo&,
const absl::optional<SpanContext> parent_context,
const std::string& /*trace_id*/,
const std::string& /*name*/, OTelSpanKind /*kind*/,
OptRef<const Tracing::TraceContext> /*trace_context*/,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class AlwaysOnSampler : public Sampler, Logger::Loggable<Logger::Id::tracing> {
public:
explicit AlwaysOnSampler(const Protobuf::Message& /*config*/,
Server::Configuration::TracerFactoryContext& /*context*/) {}
SamplingResult shouldSample(const absl::optional<SpanContext> parent_context,
SamplingResult shouldSample(const StreamInfo::StreamInfo& stream_info,
const absl::optional<SpanContext> parent_context,
const std::string& trace_id, const std::string& name,
OTelSpanKind spankind,
OptRef<const Tracing::TraceContext> trace_context,
Expand Down
37 changes: 37 additions & 0 deletions source/extensions/tracers/opentelemetry/samplers/cel/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_cc_library",
"envoy_extension_package",
)

licenses(["notice"]) # Apache 2

envoy_extension_package()

envoy_cc_extension(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
tags = ["skip_on_windows"],
deps = [
":cel_sampler_lib",
"//envoy/registry",
"//source/common/config:utility_lib",
"//source/extensions/filters/common/expr:evaluator_lib",
"@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto",
],
)

envoy_cc_library(
name = "cel_sampler_lib",
srcs = ["cel_sampler.cc"],
hdrs = ["cel_sampler.h"],
deps = [
"//source/common/config:datasource_lib",
"//source/extensions/filters/common/expr:evaluator_lib",
"//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib",
"//source/extensions/tracers/opentelemetry/samplers:sampler_lib",
"@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "source/extensions/tracers/opentelemetry/samplers/cel/cel_sampler.h"

#include <memory>
#include <sstream>
#include <string>

#include "source/common/config/datasource.h"
#include "source/extensions/tracers/opentelemetry/span_context.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

CELSampler::CELSampler(const ::Envoy::LocalInfo::LocalInfo& local_info,
Expr::BuilderInstanceSharedPtr builder,
const google::api::expr::v1alpha1::Expr& expr)
: local_info_(local_info), builder_(builder), parsed_expr_(expr) {
compiled_expr_ = Expr::createExpression(builder_->builder(), parsed_expr_);
}

SamplingResult CELSampler::shouldSample(const StreamInfo::StreamInfo& stream_info,
const absl::optional<SpanContext> parent_context,
const std::string& /*trace_id*/,
const std::string& /*name*/, OTelSpanKind /*kind*/,
OptRef<const Tracing::TraceContext>,
const std::vector<SpanContext>& /*links*/) {

Protobuf::Arena arena;
auto eval_status = Expr::evaluate(*compiled_expr_, arena, &local_info_, stream_info,
nullptr /* request_headers */, nullptr /* response_headers */,
nullptr /* response_trailers */);
SamplingResult result;
if (!eval_status.has_value() || eval_status.value().IsError()) {
result.decision = Decision::Drop;
return result;
}
auto eval_result_val = eval_status.value();
auto eval_result = eval_result_val.IsBool() ? eval_result_val.BoolOrDie() : false;
if (!eval_result) {
result.decision = Decision::Drop;
return result;
}

result.decision = Decision::RecordAndSample;
if (parent_context.has_value()) {
result.tracestate = parent_context.value().tracestate();
}
return result;
}

std::string CELSampler::getDescription() const { return "CELSampler"; }

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include "envoy/extensions/tracers/opentelemetry/samplers/v3/cel_sampler.pb.h"
#include "envoy/server/factory_context.h"

#include "source/common/common/logger.h"
#include "source/common/config/datasource.h"
#include "source/extensions/filters/common/expr/evaluator.h"
#include "source/extensions/tracers/opentelemetry/samplers/sampler.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

namespace Expr = Envoy::Extensions::Filters::Common::Expr;

/**
* @brief A sampler which samples on CEL expression.
*
* - Returns RecordAndSample always.
* - Description MUST be AlwaysOnSampler.
*
*/
class CELSampler : public Sampler, Logger::Loggable<Logger::Id::tracing> {
public:
CELSampler(const ::Envoy::LocalInfo::LocalInfo& local_info,
Expr::BuilderInstanceSharedPtr builder, const google::api::expr::v1alpha1::Expr& expr);
SamplingResult shouldSample(const StreamInfo::StreamInfo& stream_info,
const absl::optional<SpanContext> parent_context,
const std::string& trace_id, const std::string& name,
OTelSpanKind spankind,
OptRef<const Tracing::TraceContext> trace_context,
const std::vector<SpanContext>& links) override;
std::string getDescription() const override;

private:
const ::Envoy::LocalInfo::LocalInfo& local_info_;
Extensions::Filters::Common::Expr::BuilderInstanceSharedPtr builder_;
const google::api::expr::v1alpha1::Expr parsed_expr_;
Extensions::Filters::Common::Expr::ExpressionPtr compiled_expr_;
};

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
44 changes: 44 additions & 0 deletions source/extensions/tracers/opentelemetry/samplers/cel/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "source/extensions/tracers/opentelemetry/samplers/cel/config.h"

#include <memory>

#include "envoy/extensions/tracers/opentelemetry/samplers/v3/cel_sampler.pb.validate.h"

#include "source/common/config/utility.h"
#include "source/common/protobuf/utility.h"
#include "source/extensions/tracers/opentelemetry/samplers/cel/cel_sampler.h"

namespace Envoy {
namespace Extensions {
namespace Tracers {
namespace OpenTelemetry {

SamplerSharedPtr
CELSamplerFactory::createSampler(const Protobuf::Message& config,
Server::Configuration::TracerFactoryContext& context) {
auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig(
dynamic_cast<const ProtobufWkt::Any&>(config), context.messageValidationVisitor(), *this);

const auto& proto_config = MessageUtil::downcastAndValidate<
const envoy::extensions::tracers::opentelemetry::samplers::v3::CELSamplerConfig&>(
*mptr, context.messageValidationVisitor());

auto expr = Expr::getExpr(proto_config.expression());
if (!expr.has_value()) {
throw EnvoyException("CEL expression not set");
}

return std::make_unique<CELSampler>(
context.serverFactoryContext().localInfo(),
Extensions::Filters::Common::Expr::getBuilder(context.serverFactoryContext()), expr.value());
}

/**
* Static registration for the Env sampler factory. @see RegisterFactory.
*/
REGISTER_FACTORY(CELSamplerFactory, SamplerFactory);

} // namespace OpenTelemetry
} // namespace Tracers
} // namespace Extensions
} // namespace Envoy
Loading
Loading