mirror of
https://github.com/Tencent/flare.git
synced 2026-10-16 20:31:05 +08:00
tracing: migrate opentracing-cpp → opentelemetry-cpp (API only) (#201)
* tracing: migrate opentracing-cpp -> opentelemetry-cpp (API only) opentracing-cpp is deprecated/superseded by OpenTelemetry. flare ships no real tracer -- it exposes a pluggable abstraction (TracingOpsProvider / QuickerSpan / TracingOps) over the tracing-API types, with providers plugged in by users. Re-base that abstraction on the opentelemetry-cpp **API** (header-only, v1.27.0) and drop opentracing-cpp from both build systems. Scope: API only -- no SDK/exporter, so no protobuf/grpc is pulled in and the deliberately-pinned (abseil-free) protobuf 3.21.12 stays intact. The abstraction and the opaque `bytes tracing_context` wire format are kept; only the types/bodies change. Source (flare/rpc/tracing/): - tracing_ops_provider.h: re-typed virtuals; new flare-owned SpanStartOptions (parent SpanContext + SpanKind + timestamps + start attrs) replacing opentracing::StartSpanOptions/ChildOf. Extract -> std::optional<SpanContext>. - tracing_ops.h/.cc: span_ is nostd::shared_ptr<trace::Span>; SetAttribute/ AddEvent/End; SpanContext is a value type; buffered ops keep flare's owning variant and convert to (non-owning) AttributeValue only at flush. - framework_tags.h -> constexpr std::string_view; string_view_interop.h -> ToStd/ToOtel (nostd<->std). Standard-tag table is a literal allowlist; span.kind moves from a tag to SpanStartOptions::kind. - Call sites rpc_channel.cc / normal_connection_handler.cc updated; rpc_meta.proto comment updated (wire field unchanged). - Tests/benchmark rewritten onto a recording fake otel::trace::Span (OTel's noop span can't be asserted on); integration test asserts span kind via the provider-recorded "span.kind" instead of an opentracing tag. Build: - blade: BLADE_ROOT vcpkg_config gains opentelemetry-cpp; thin wrapper //thirdparty/opentelemetry-cpp:api (refs opentelemetry_common -- the api component is header-only with no .a; that lib's include dir carries the api + absl headers). opentracing thirdparty dir removed. - bazel: deps.bzl http_archive for opentelemetry-cpp 1.27.0 + api-only bazel/opentelemetry-cpp.BUILD (header-only, bundled nostd -> no abseil); patch_cmds drop the archive's nested BUILDs so the api headers can be globbed. opentracing BUILD/patch + .bazelignore/.gitattributes entries removed. Verified: blade and bazel both build //flare/rpc/tracing/... and pass tracing_ops_test + integration_test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci: bazelignore thirdparty/opentelemetry-cpp (blade BUILD) The repo-wide `bazel build ...` job parses every BUILD file; the blade thin wrapper thirdparty/opentelemetry-cpp/BUILD uses blade-only syntax (vcpkg#...:lib, visibility='PUBLIC'). Add it to .bazelignore (the opentracing entry was removed but its replacement wasn't added). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tracing: clang-format (v15) the migrated files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: drop accidentally-committed .claude/ tooling files Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * doc(tracing): note provider ABI contract for OpenTelemetry nostd types The TracingOpsProvider interface uses OpenTelemetry API types (nostd::shared_ptr<Span>, nostd::string_view, AttributeValue) directly. The nostd:: backing is a compile-time choice (bundled / std via OPENTELEMETRY_STL_VERSION / abseil) and the three are mutually incompatible types, so a provider must be built with the same OpenTelemetry configuration as flare. flare leaves the default nostd in place. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: gitignore .claude/ (Claude Code local tooling) Prevents accidentally re-committing .claude/settings*.json (cf. review comments on PR #201). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * tracing: address PR #201 review follow-ups (non-blocking) - forcibly_sampled now routes `flare.sampling_priority` through the provider's `SetFrameworkTag` translation (before `IsSampled`) instead of a raw `SetAttribute`, keeping the `flare.`-prefixed tag consistent with every other framework tag and letting providers honor the force-sample signal. Add `ext::kSamplingPriority`. - An empty log key (single-arg `AddTracingLog`) now materializes as the default event name "log" so backends that group/display by event name don't render a blank one; document the per-log-event mapping. - Guard `QuickerValue`'s reliance on P0608R3 (C++20) with a static_assert so a future standard downgrade can't silently bind `const char*` to bool. - Reset `DummySpan`'s inline-static tags/logs at the start of the recording test so assertions can't observe leftovers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -11,8 +11,8 @@ thirdparty/jemalloc
|
||||
thirdparty/jsoncpp
|
||||
thirdparty/lz4
|
||||
thirdparty/nghttp2
|
||||
thirdparty/opentelemetry-cpp
|
||||
thirdparty/openssl
|
||||
thirdparty/opentracing-cpp
|
||||
thirdparty/protobuf
|
||||
thirdparty/snappy
|
||||
thirdparty/yaml-cpp
|
||||
|
||||
@@ -24,5 +24,4 @@ thirdparty/blade/cmake-3.31.6-linux-x86_64.tar.gz filter=lfs diff=lfs merge=lfs
|
||||
thirdparty/yaml-cpp/yaml-cpp-yaml-cpp-0.6.3.tar.gz filter=lfs diff=lfs merge=lfs -text
|
||||
thirdparty/blade/blade.zip filter=lfs diff=lfs merge=lfs -text
|
||||
thirdparty/openssl/openssl-1.0.2u.tar.gz filter=lfs diff=lfs merge=lfs -text
|
||||
thirdparty/opentracing-cpp/opentracing-cpp-1.5.1.tar.gz filter=lfs diff=lfs merge=lfs -text
|
||||
thirdparty/zstd/zstd-1.4.5.tar.gz filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -42,3 +42,6 @@ GTAGS
|
||||
# IntelliJ IDEA
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# Claude Code
|
||||
.claude
|
||||
|
||||
@@ -210,5 +210,11 @@ vcpkg_config(
|
||||
'nghttp2': {},
|
||||
# curl: openssl TLS + nghttp2 http2 (zlib auto-detected from the install).
|
||||
'curl': {'features': ['openssl', 'http2']},
|
||||
# opentelemetry-cpp API for distributed tracing (replaces the vendored
|
||||
# opentracing-cpp). Default features only -> no exporters, so no
|
||||
# protobuf/grpc pulled in (keeps the abseil-free protobuf 3.21.12 pin
|
||||
# intact). It pulls abseil + nlohmann-json, which coexist fine. flare
|
||||
# consumes only the header-only `opentelemetry_api` target.
|
||||
'opentelemetry-cpp': {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -141,8 +141,8 @@ END OF TERMS AND CONDITIONS
|
||||
|
||||
Open Source Software Licensed under the Apache version 2.0 License and Other Licenses of the Third-Party Components therein:
|
||||
--------------------------------------------------------------------
|
||||
1. opentracing-cpp
|
||||
Copyright The OpenTracing Authors
|
||||
1. opentelemetry-cpp
|
||||
Copyright The OpenTelemetry Authors
|
||||
|
||||
A copy of Apache version 2.0 is included in this file.
|
||||
|
||||
|
||||
+14
-10
@@ -149,17 +149,21 @@ def flare_dependencies():
|
||||
sha256 = "62b2b1acee40c4de5a4913e27a4b4194813cf2b7815b73febec7ae53054646ca",
|
||||
)
|
||||
|
||||
if not native.existing_rule("com_github_opentracing_opentracing_cpp"):
|
||||
if not native.existing_rule("io_opentelemetry_cpp"):
|
||||
http_archive(
|
||||
name = "com_github_opentracing_opentracing_cpp",
|
||||
build_file = Label("//bazel:opentracing-cpp.BUILD"),
|
||||
# opentracing-cpp 1.5.1 bundles a variant impl using std::result_of,
|
||||
# which C++20 removed. Replace with std::invoke_result.
|
||||
patches = [Label("//bazel:opentracing-cpp-cxx20.patch")],
|
||||
patch_args = ["-p1"],
|
||||
urls = ["https://github.com/opentracing/opentracing-cpp/archive/refs/tags/v1.5.1.zip"],
|
||||
strip_prefix = "opentracing-cpp-1.5.1",
|
||||
sha256 = "7a007a4cd987fb86ce05eeec8fe9e9f2052988c835bb6c738ec00bc167750f81",
|
||||
name = "io_opentelemetry_cpp",
|
||||
build_file = Label("//bazel:opentelemetry-cpp.BUILD"),
|
||||
# The archive ships its own (SDK-wide, abseil/grpc-pulling) Bazel
|
||||
# packages. We only want the header-only API, so drop every nested
|
||||
# BUILD file -- this collapses the archive into a single package
|
||||
# rooted at our build_file, letting it glob `api/include/**`.
|
||||
patch_cmds = [
|
||||
"find . -mindepth 2 -name BUILD -delete",
|
||||
"find . -mindepth 2 -name BUILD.bazel -delete",
|
||||
],
|
||||
urls = ["https://github.com/open-telemetry/opentelemetry-cpp/archive/refs/tags/v1.27.0.zip"],
|
||||
strip_prefix = "opentelemetry-cpp-1.27.0",
|
||||
sha256 = "4a382e8bfa70c83e3d4b7bc806e383ea01a18b13e4c0ab67b466aac65044bad4",
|
||||
)
|
||||
|
||||
if not native.existing_rule("com_github_olafvdspek_ctemplate"):
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# opentelemetry-cpp, API only. flare uses just the header-only tracing API (no
|
||||
# SDK / exporters, hence no protobuf / grpc). With neither `HAVE_ABSEIL` nor an
|
||||
# `OPENTELEMETRY_STL_VERSION` defined, the `nostd::` types use opentelemetry's
|
||||
# own bundled, self-contained implementations -- so this is purely header-only
|
||||
# with no external dependencies.
|
||||
|
||||
cc_library(
|
||||
name = "api",
|
||||
hdrs = glob(["api/include/**/*.h"]),
|
||||
includes = ["api/include"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -1,20 +0,0 @@
|
||||
--- a/3rd_party/include/opentracing/variant/variant.hpp
|
||||
+++ b/3rd_party/include/opentracing/variant/variant.hpp
|
||||
@@ -166,7 +166,7 @@
|
||||
template <typename F, typename V, typename Enable = void>
|
||||
struct result_of_unary_visit
|
||||
{
|
||||
- using type = typename std::result_of<F(V&)>::type;
|
||||
+ using type = typename std::invoke_result<F, V&>::type;
|
||||
};
|
||||
|
||||
template <typename F, typename V>
|
||||
@@ -178,7 +178,7 @@
|
||||
template <typename F, typename V, typename Enable = void>
|
||||
struct result_of_binary_visit
|
||||
{
|
||||
- using type = typename std::result_of<F(V&, V&)>::type;
|
||||
+ using type = typename std::invoke_result<F, V&, V&>::type;
|
||||
};
|
||||
|
||||
template <typename F, typename V>
|
||||
@@ -1,64 +0,0 @@
|
||||
# This file is based on https://github.com/opentracing/opentracing-cpp/blob/master/BUILD.bazel
|
||||
|
||||
# Change the header file prefix opentracing to opentracing-cpp,
|
||||
# and no longer call cmake to generate version information,
|
||||
# which is managed by bazel
|
||||
|
||||
cc_library(
|
||||
name = "opentracing",
|
||||
srcs = glob(
|
||||
["src/**/*.cpp"],
|
||||
exclude = [
|
||||
"src/dynamic_load_unsupported.cpp",
|
||||
"src/dynamic_load_windows.cpp",
|
||||
],
|
||||
),
|
||||
hdrs = glob(["include/opentracing/**/*.h"]) + [
|
||||
":include/opentracing/config.h",
|
||||
":include/opentracing/version.h",
|
||||
],
|
||||
include_prefix = "opentracing-cpp",
|
||||
includes = ["include"],
|
||||
linkopts = [
|
||||
"-ldl",
|
||||
],
|
||||
strip_include_prefix = "include/opentracing",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//3rd_party:expected",
|
||||
"//3rd_party:variant",
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "config_h",
|
||||
outs = ["include/opentracing/config.h"],
|
||||
cmd = """cat <<EOF >$@
|
||||
#pragma once
|
||||
|
||||
#define OPENTRACING_BUILD_DYNAMIC_LOADING
|
||||
EOF
|
||||
""",
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "version_h",
|
||||
outs = ["include/opentracing/version.h"],
|
||||
cmd = """cat <<EOF >$@
|
||||
#ifndef OPENTRACING_VERSION_H
|
||||
#define OPENTRACING_VERSION_H
|
||||
|
||||
#define OPENTRACING_VERSION "1.5.1"
|
||||
#define OPENTRACING_ABI_VERSION "2"
|
||||
|
||||
// clang-format off
|
||||
#define BEGIN_OPENTRACING_ABI_NAMESPACE \
|
||||
inline namespace v2 {
|
||||
#define END_OPENTRACING_ABI_NAMESPACE \
|
||||
} // namespace v2
|
||||
// clang-format on
|
||||
|
||||
#endif // OPENTRACING_VERSION_H
|
||||
EOF
|
||||
""",
|
||||
)
|
||||
@@ -3,7 +3,9 @@
|
||||
[分布式调用追踪](https://research.google/pubs/pub36356/)可以通过对服务调用链的跟踪,构建一个从服务请求开始到各个服务交互的全部调用过程的视图。用户可以从中了解到诸如应用调用的时延,网络调用的生命周期,系统的性能瓶颈等等信息。
|
||||
Flare内置了对调用追踪逻辑的支持,通过不同的内置或第三方[provider](../rpc/tracing/tracing_ops_provider.h),可以将RPC调用情况上报至相应的管理平台。
|
||||
|
||||
*我们只支持符合[OpenTracing](https://opentracing.io/)规范的调用追踪系统。*
|
||||
*调用追踪基于 [OpenTelemetry](https://opentelemetry.io/) 的 C++ trace API(仅 API,provider 负责对接具体的导出/上报实现)。*
|
||||
|
||||
> **provider 实现者注意**:[`TracingOpsProvider`](../rpc/tracing/tracing_ops_provider.h) 的接口直接使用了 OpenTelemetry 的 API 类型(如 `nostd::shared_ptr<Span>`、`nostd::string_view`、`AttributeValue` 等)。这些 `nostd::` 类型的底层实现由**编译期配置**决定:默认为内置实现,定义 `OPENTELEMETRY_STL_VERSION`(≥ 对应阈值,flare 为 C++20 即 `2020`)时映射到 `std::`,启用 abseil 时映射到 `absl::`——三者是**互不兼容的不同类型**。因此 provider 必须使用与 flare 相同的 OpenTelemetry 配置进行编译,否则会出现 ABI 不兼容(接口类型不匹配的编译错误,或更隐蔽的链接期/运行期问题)。flare 自身不强制设置该宏,沿用 OpenTelemetry 的默认 `nostd` 实现。
|
||||
|
||||
显然,由于*各服务只上报自己的信息*的天性,为了能将整个调用链路串接起来,需要整个调用链条都使用相同的调用追踪平台才能获得有意义的结果。
|
||||
|
||||
@@ -41,6 +43,8 @@ Flare内置了对调用追踪逻辑的支持,通过不同的内置或第三方
|
||||
|
||||
*如果多次使用相同的`key`调用`SetTracingTag(key, value)`,那么只有最后一次调用的`value`会生效,之前的调用会被覆盖。`AddTracingLog(...)`则全都会上报。*
|
||||
|
||||
*基于 OpenTelemetry 时,每一条`AddTracingLog(...)`会映射为 span 上的一个独立 event(`key`作为 event 名,`value`放在`value`属性下),这与 opentracing 把多个 field 归并到一条带时间戳的 log record 不同。对于空`key`(单参形式`AddTracingLog(value)`),event 名默认取`"log"`,以免部分后端按 event 名展示时出现空名。*
|
||||
|
||||
### 程序代码修改
|
||||
|
||||
如果业务代码遵照了我们的[开发建议](intro-rpc.md),包括但不限于下列要求,那么**业务代码只需要按照上文描述开启相关开关即可,不需要针对RPC调用追踪开发逻辑**。
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ cc_library(
|
||||
'//flare/rpc/tracing:tracing',
|
||||
'//thirdparty/gflags:gflags',
|
||||
'//thirdparty/jsoncpp:jsoncpp',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
'//flare/base:logging',
|
||||
'//flare/base:ref_ptr',
|
||||
'//flare/base:tsc',
|
||||
|
||||
@@ -126,7 +126,7 @@ cc_library(
|
||||
"//flare/rpc/tracing",
|
||||
"@com_github_gflags_gflags//:gflags",
|
||||
"@com_github_jsoncpp//:jsoncpp",
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
#include "flare/rpc/internal/normal_connection_handler.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/trace/span_context.h"
|
||||
#include "opentelemetry/trace/span_metadata.h"
|
||||
|
||||
#include "flare/base/deferred.h"
|
||||
#include "flare/base/internal/dpc.h"
|
||||
@@ -641,7 +643,9 @@ void NormalConnectionHandler::InitializeForTracing(
|
||||
// start a trace unconditionally (on trace missing, as obvious).
|
||||
!serialized_ctx.empty() || FLAGS_flare_rpc_start_new_trace_on_missing) {
|
||||
auto&& ops = *tracing::GetTracingOps(ctx_->service_name);
|
||||
std::unique_ptr<opentracing::SpanContext> incoming_ctx;
|
||||
// `SpanContext` is a value type in OpenTelemetry. `std::nullopt` means no
|
||||
// (parseable) incoming context, in which case the span starts a new trace.
|
||||
std::optional<opentelemetry::trace::SpanContext> incoming_ctx;
|
||||
|
||||
if (!serialized_ctx.empty()) { // The caller passed a span context to us.
|
||||
if (auto opt = ops.ParseSpanContextFrom(serialized_ctx)) {
|
||||
@@ -656,28 +660,21 @@ void NormalConnectionHandler::InitializeForTracing(
|
||||
// Initialize the tracing context and start a new span.
|
||||
tracing_ctx.tracing_ops = &ops;
|
||||
tracing_ctx.server_span =
|
||||
// `inspection_result.method` is fully qualified. This complies to
|
||||
// what's suggested by OpenTracing standards:
|
||||
//
|
||||
// > Examples of default operation names:
|
||||
// > ...
|
||||
// > - The concatenated names of an RPC service and method
|
||||
//
|
||||
// @sa:
|
||||
// https://opentracing.io/docs/best-practices/instrumenting-frameworks
|
||||
ops.StartSpanWithLazyOptions(inspection_result.method, [&](auto&& f) {
|
||||
f(opentracing::ChildOf(incoming_ctx ? incoming_ctx.get() : nullptr));
|
||||
// `inspection_result.method` is fully qualified, the conventional
|
||||
// operation name (the concatenated RPC service and method names).
|
||||
ops.StartSpanWithLazyOptions(
|
||||
inspection_result.method, [&](tracing::SpanStartOptions& opts) {
|
||||
opts.parent =
|
||||
incoming_ctx
|
||||
? *incoming_ctx
|
||||
: opentelemetry::trace::SpanContext::GetInvalid();
|
||||
// span kind is a start-time property in OpenTelemetry (not a
|
||||
// tag).
|
||||
opts.kind = opentelemetry::trace::SpanKind::kServer;
|
||||
});
|
||||
|
||||
// TJG requires `span_kind` to be set in `start_options`. This is
|
||||
// silly, to say the least.
|
||||
//
|
||||
// Note that setting tag (esp. in TJG's case) is slow.
|
||||
f(opentracing::SetTag(opentracing::ext::span_kind,
|
||||
opentracing::ext::span_kind_rpc_server));
|
||||
});
|
||||
|
||||
// `incoming_ctx` is going away, I'm not sure if all implementation can
|
||||
// accept this situation (as `server_span` may still referencing it.).
|
||||
// No dangling concern anymore: `StartSpanWithOptions` copies the parent
|
||||
// `SpanContext` (a value type) into the span at start.
|
||||
} // Otherwise left the context untouched, in this case it's treated as if
|
||||
// tracing is not enabled.
|
||||
}
|
||||
@@ -690,7 +687,8 @@ void NormalConnectionHandler::FinishTracing(
|
||||
service_context.advise_trace_forcibly_sampled) {
|
||||
span.AdviseForciblySampled();
|
||||
}
|
||||
span.SetFrameworkTag(tracing::ext::kInvocationStatus, service_context.status);
|
||||
span.SetFrameworkTag(tracing::ToOtel(tracing::ext::kInvocationStatus),
|
||||
service_context.status);
|
||||
|
||||
controller->SetTraceForciblySampled(span.IsForciblySampled());
|
||||
span.Report(); // Report the span after RPC is completed.
|
||||
|
||||
@@ -519,7 +519,7 @@ cc_library(
|
||||
'//flare/rpc/internal:stream_call_gate',
|
||||
'//flare/rpc/tracing:tracing',
|
||||
'//thirdparty/gflags:gflags',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
':rpc_meta_proto',
|
||||
'//flare/base/internal:annotation',
|
||||
'//flare/base:object_pool',
|
||||
|
||||
@@ -520,7 +520,7 @@ cc_library(
|
||||
"//flare/rpc/protocol:stream_protocol",
|
||||
"//flare/rpc/tracing",
|
||||
"@com_github_gflags_gflags//:gflags",
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/trace/span_metadata.h"
|
||||
|
||||
#include "flare/base/buffer/zero_copy_stream.h"
|
||||
#include "flare/base/callback.h"
|
||||
@@ -776,23 +776,21 @@ tracing::QuickerSpan RpcChannel::StartTracingSpanFor(
|
||||
// Start a new span for this RPC.
|
||||
auto span =
|
||||
rpc::session_context->tracing.tracing_ops->StartSpanWithLazyOptions(
|
||||
// As suggested by OpenTracing standard, we use fully-qualified method
|
||||
// name here.
|
||||
method->full_name(), [&](auto&& f) {
|
||||
f(opentracing::ChildOf(
|
||||
rpc::session_context->tracing.server_span.SpanContext()));
|
||||
// As suggested by tracing conventions, we use the fully-qualified
|
||||
// method name here.
|
||||
method->full_name(), [&](tracing::SpanStartOptions& opts) {
|
||||
// An invalid parent context (server span not tracing) makes this a
|
||||
// root span; the value type is copied into the span at start.
|
||||
opts.parent =
|
||||
rpc::session_context->tracing.server_span.GetSpanContext();
|
||||
opts.kind = opentelemetry::trace::SpanKind::kClient;
|
||||
});
|
||||
|
||||
// Tags are set separately for better performance.
|
||||
span.SetStandardTag(opentracing::ext::span_kind,
|
||||
opentracing::ext::span_kind_rpc_client);
|
||||
span.SetStandardTag(opentracing::ext::peer_service,
|
||||
method->service()->full_name().c_str());
|
||||
span.SetStandardTag(peer.Family() == AF_INET
|
||||
? opentracing::ext::peer_host_ipv4
|
||||
: opentracing::ext::peer_host_ipv6,
|
||||
span.SetStandardTag("peer.service", method->service()->full_name().c_str());
|
||||
span.SetStandardTag(peer.Family() == AF_INET ? "peer.ipv4" : "peer.ipv6",
|
||||
[peer] { return EndpointGetIp(peer); });
|
||||
span.SetStandardTag(opentracing::ext::peer_port, EndpointGetPort(peer));
|
||||
span.SetStandardTag("peer.port", EndpointGetPort(peer));
|
||||
return span;
|
||||
}
|
||||
|
||||
@@ -809,8 +807,9 @@ void RpcChannel::FinishTracingSpanWith(int completion_status,
|
||||
rpc::session_context->tracing.server_span.SetForciblySampled();
|
||||
}
|
||||
}
|
||||
span->SetFrameworkTag(tracing::ext::kInvocationStatus, completion_status);
|
||||
// `opentracing::ext::error` is not set to avoid TJG's poor implementation.
|
||||
span->SetFrameworkTag(tracing::ToOtel(tracing::ext::kInvocationStatus),
|
||||
completion_status);
|
||||
// An `error` tag / span status is not set to avoid TJG's poor implementation.
|
||||
// FIXME: What about other tracing providers then?
|
||||
span->Report();
|
||||
}
|
||||
|
||||
@@ -97,7 +97,9 @@ message RpcRequestMeta {
|
||||
required string method_name = 2; // `MethodDescriptor::full_name()`.
|
||||
optional uint32 request_id = 3; // For logging purpose.
|
||||
optional uint32 timeout = 4; // Relative time. In milliseconds.
|
||||
optional bytes tracing_context = 5; // @sa: opentracing::Tracer::Inject(...)
|
||||
// Opaque, provider-serialized span context.
|
||||
// @sa: flare::tracing::TracingOpsProvider::Inject(...)
|
||||
optional bytes tracing_context = 5;
|
||||
|
||||
// Bitwise or of `CompressionAlgorithms`, representing acceptable compression
|
||||
// algorithms.
|
||||
|
||||
+10
-9
@@ -28,11 +28,11 @@ cc_library(
|
||||
'//flare/base:chrono',
|
||||
'//flare/base:dependency_registry',
|
||||
'//flare/base:function',
|
||||
'//flare/base:maybe_owning',
|
||||
'//flare/base:never_destroyed',
|
||||
'//flare/base:string',
|
||||
'//flare/rpc/internal:sampler',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//flare/rpc/tracing:string_view_interop',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
],
|
||||
visibility = 'PUBLIC',
|
||||
)
|
||||
@@ -41,7 +41,7 @@ cc_library(
|
||||
name = 'framework_tags',
|
||||
hdrs = 'framework_tags.h',
|
||||
deps = [
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
]
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ cc_library(
|
||||
name = 'string_view_interop',
|
||||
hdrs = 'string_view_interop.h',
|
||||
deps = [
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
]
|
||||
)
|
||||
|
||||
@@ -63,7 +63,7 @@ cc_library(
|
||||
deps = [
|
||||
'//flare/base:dependency_registry',
|
||||
'//flare/base:function',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
'//flare/base/internal:macro',
|
||||
],
|
||||
visibility = 'PUBLIC',
|
||||
@@ -75,9 +75,10 @@ cc_library(
|
||||
srcs = 'tracing_ops.cc',
|
||||
deps = [
|
||||
':tracing_ops_provider',
|
||||
':string_view_interop',
|
||||
'//flare/base:chrono',
|
||||
'//flare/base:exposed_var',
|
||||
'//flare/base:maybe_owning',
|
||||
'//flare/base:function',
|
||||
'//flare/base:never_destroyed',
|
||||
'//flare/base:overloaded',
|
||||
'//flare/base:random',
|
||||
@@ -88,7 +89,7 @@ cc_library(
|
||||
'//flare/base/thread:thread_cached',
|
||||
'//flare/rpc/internal:sampler',
|
||||
'//thirdparty/gflags:gflags',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
'//flare/base/internal:hash_map',
|
||||
],
|
||||
visibility = 'PUBLIC',
|
||||
@@ -103,6 +104,7 @@ cc_test(
|
||||
'//flare/rpc/tracing:string_view_interop',
|
||||
'//flare/testing:main',
|
||||
'//thirdparty/googletest:gmock',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
]
|
||||
)
|
||||
|
||||
@@ -120,7 +122,6 @@ cc_test(
|
||||
srcs = 'integration_test.cc',
|
||||
deps = [
|
||||
':tracing',
|
||||
'//flare/base:overloaded',
|
||||
'//flare/fiber:fiber',
|
||||
'//flare/rpc:rpc',
|
||||
'//flare/testing:echo_service_proto_flare',
|
||||
@@ -129,6 +130,6 @@ cc_test(
|
||||
'//flare/testing:relay_service_proto_flare',
|
||||
'//thirdparty/gflags:gflags',
|
||||
'//thirdparty/googletest:gmock',
|
||||
'//thirdparty/opentracing-cpp:opentracing',
|
||||
'//thirdparty/opentelemetry-cpp:api',
|
||||
]
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ cc_library(
|
||||
name = "framework_tags",
|
||||
hdrs = ["framework_tags.h"],
|
||||
deps = [
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ cc_library(
|
||||
name = "string_view_interop",
|
||||
hdrs = ["string_view_interop.h"],
|
||||
deps = [
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -56,7 +56,7 @@ cc_library(
|
||||
"//flare/base:dependency_registry",
|
||||
"//flare/base:function",
|
||||
"//flare/base/net:endpoint",
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -66,10 +66,11 @@ cc_library(
|
||||
hdrs = ["tracing_ops.h"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
":string_view_interop",
|
||||
":tracing_ops_provider",
|
||||
"//flare/base:chrono",
|
||||
"//flare/base:exposed_var",
|
||||
"//flare/base:maybe_owning",
|
||||
"//flare/base:function",
|
||||
"//flare/base:never_destroyed",
|
||||
"//flare/base:overloaded",
|
||||
"//flare/base:random",
|
||||
@@ -80,7 +81,7 @@ cc_library(
|
||||
"//flare/base/thread:thread_cached",
|
||||
"//flare/rpc/internal:sampler",
|
||||
"@com_github_gflags_gflags//:gflags",
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -120,6 +121,6 @@ cc_test(
|
||||
"//flare/testing:main",
|
||||
"//flare/testing:relay_service_proto_flare",
|
||||
"@com_github_gflags_gflags//:gflags",
|
||||
"@com_github_opentracing_opentracing_cpp//:opentracing",
|
||||
"@io_opentelemetry_cpp//:api",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -15,24 +15,29 @@
|
||||
#ifndef FLARE_RPC_TRACING_FRAMEWORK_TAGS_H_
|
||||
#define FLARE_RPC_TRACING_FRAMEWORK_TAGS_H_
|
||||
|
||||
#include "opentracing/string_view.h"
|
||||
#include <string_view>
|
||||
|
||||
namespace flare::tracing::ext {
|
||||
|
||||
// Here we defined some tags recognized by Flare framework. `TracingOpsProvider`
|
||||
// must translate the tags declared here to names recognized by its own
|
||||
// implementation.
|
||||
|
||||
// `opentracing::string_view` does not provide a `constexpr` constructor,
|
||||
// unfortunately.
|
||||
//
|
||||
// They share the `flare.` prefix so `detail::IsFrameworkTag` can recognize them
|
||||
// (@sa: `tracing_ops.cc`).
|
||||
|
||||
// Return value of (remote) method invocation.
|
||||
inline static const opentracing::string_view kInvocationStatus =
|
||||
"flare.invocation_status";
|
||||
inline constexpr std::string_view kInvocationStatus = "flare.invocation_status";
|
||||
|
||||
// If the trace is deemed special, a tracking id is tagged on it. (Some internal
|
||||
// frameworks called it as "dyeing key", I'm not very satisfied with that name.)
|
||||
inline static const opentracing::string_view kTrackingId = "flare.tracking_id";
|
||||
inline constexpr std::string_view kTrackingId = "flare.tracking_id";
|
||||
|
||||
// Set when the framework wants the span force-sampled (e.g. a failed call).
|
||||
// OpenTelemetry's sampling flag is immutable once a span starts, so we can't
|
||||
// flip a live span; instead we hand this to the provider, which must translate
|
||||
// it into whatever its own backend honors as "report this span regardless".
|
||||
inline constexpr std::string_view kSamplingPriority = "flare.sampling_priority";
|
||||
|
||||
} // namespace flare::tracing::ext
|
||||
|
||||
|
||||
@@ -12,23 +12,32 @@
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "gmock/gmock-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/common/attribute_value.h"
|
||||
#include "opentelemetry/common/key_value_iterable.h"
|
||||
#include "opentelemetry/nostd/shared_ptr.h"
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
#include "opentelemetry/nostd/variant.h"
|
||||
#include "opentelemetry/trace/span.h"
|
||||
#include "opentelemetry/trace/span_context.h"
|
||||
#include "opentelemetry/trace/span_metadata.h"
|
||||
|
||||
#include "flare/base/overloaded.h"
|
||||
#include "flare/fiber/this_fiber.h"
|
||||
#include "flare/rpc/rpc_channel.h"
|
||||
#include "flare/rpc/rpc_client_controller.h"
|
||||
#include "flare/rpc/rpc_server_controller.h"
|
||||
#include "flare/rpc/server.h"
|
||||
#include "flare/rpc/tracing/framework_tags.h"
|
||||
#include "flare/rpc/tracing/string_view_interop.h"
|
||||
#include "flare/rpc/tracing/tracing_ops_provider.h"
|
||||
#include "flare/testing/echo_service.flare.pb.h"
|
||||
#include "flare/testing/endpoint.h"
|
||||
@@ -44,18 +53,29 @@ namespace flare::tracing {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToString(const opentracing::Value& v) {
|
||||
std::stringstream ss;
|
||||
opentracing::Value::visit(
|
||||
v, Overloaded{
|
||||
[&](std::nullptr_t) {},
|
||||
[&](const auto& v) -> std::void_t<decltype(ss << v)> { ss << v; },
|
||||
[&](auto&&) {}});
|
||||
return ss.str();
|
||||
std::string AttrToString(const otel::common::AttributeValue& v) {
|
||||
return otel::nostd::visit(
|
||||
[](auto&& x) -> std::string {
|
||||
using T = std::decay_t<decltype(x)>;
|
||||
if constexpr (std::is_same_v<T, otel::nostd::string_view>) {
|
||||
return std::string(x.data(), x.size());
|
||||
} else if constexpr (std::is_same_v<T, const char*>) {
|
||||
return std::string(x);
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return x ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
return std::to_string(x);
|
||||
} else {
|
||||
return {}; // `span<>` alternatives, not used here.
|
||||
}
|
||||
},
|
||||
v);
|
||||
}
|
||||
|
||||
opentracing::SpanContext* null_span_context = nullptr;
|
||||
opentracing::Tracer* null_tracer = nullptr;
|
||||
// The span kind is a start-time property in OpenTelemetry (not a tag), but the
|
||||
// test still wants to observe it, so the dummy provider records it under this
|
||||
// key.
|
||||
constexpr auto kSpanKind = "span.kind";
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -67,46 +87,47 @@ struct MaterializedSpan {
|
||||
std::mutex lock;
|
||||
std::vector<MaterializedSpan> reported_spans;
|
||||
|
||||
class DummySpan : public opentracing::Span {
|
||||
// A recording fake span. (OpenTelemetry's bundled noop span discards
|
||||
// everything, so it can't be asserted on.)
|
||||
class DummySpan : public otel::trace::Span {
|
||||
public:
|
||||
void FinishWithOptions(
|
||||
const opentracing::FinishSpanOptions&) noexcept override {
|
||||
void SetAttribute(
|
||||
otel::nostd::string_view key,
|
||||
const otel::common::AttributeValue& value) noexcept override {
|
||||
tags_.push_back({std::string(key.data(), key.size()), AttrToString(value)});
|
||||
}
|
||||
|
||||
void AddEvent(otel::nostd::string_view) noexcept override {}
|
||||
void AddEvent(otel::nostd::string_view,
|
||||
otel::common::SystemTimestamp) noexcept override {}
|
||||
void AddEvent(
|
||||
otel::nostd::string_view name, otel::common::SystemTimestamp,
|
||||
const otel::common::KeyValueIterable& attributes) noexcept override {
|
||||
attributes.ForEachKeyValue(
|
||||
[&](otel::nostd::string_view,
|
||||
otel::common::AttributeValue value) noexcept {
|
||||
logs_.push_back(
|
||||
{std::string(name.data(), name.size()), AttrToString(value)});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void SetStatus(otel::trace::StatusCode,
|
||||
otel::nostd::string_view) noexcept override {}
|
||||
void UpdateName(otel::nostd::string_view name) noexcept override {
|
||||
op_name_ = std::string(name.data(), name.size());
|
||||
}
|
||||
void End(const otel::trace::EndSpanOptions&) noexcept override {
|
||||
MaterializedSpan ms = {.method = op_name_, .tags = tags_, .logs = logs_};
|
||||
std::scoped_lock _(lock);
|
||||
reported_spans.push_back(ms);
|
||||
}
|
||||
|
||||
void SetOperationName(opentracing::string_view name) noexcept override {
|
||||
op_name_ = name;
|
||||
otel::trace::SpanContext GetContext() const noexcept override {
|
||||
return otel::trace::SpanContext::GetInvalid();
|
||||
}
|
||||
bool IsRecording() const noexcept override { return true; }
|
||||
|
||||
void SetTag(opentracing::string_view key,
|
||||
const opentracing::Value& value) noexcept override {
|
||||
tags_.push_back(std::pair(key, ToString(value)));
|
||||
}
|
||||
|
||||
// Not used.
|
||||
void SetBaggageItem(opentracing::string_view,
|
||||
opentracing::string_view) noexcept override {}
|
||||
std::string BaggageItem(opentracing::string_view) const noexcept override {
|
||||
return "";
|
||||
}
|
||||
|
||||
void Log(std::initializer_list<
|
||||
std::pair<opentracing::string_view, opentracing::Value>>
|
||||
vs) noexcept override {
|
||||
for (auto&& [k, v] : vs) {
|
||||
logs_.push_back(std::pair(k, ToString(v)));
|
||||
}
|
||||
}
|
||||
|
||||
const opentracing::SpanContext& context() const noexcept override {
|
||||
return *null_span_context; // U.B.?
|
||||
}
|
||||
|
||||
const opentracing::Tracer& tracer() const noexcept override {
|
||||
return *null_tracer; // U.B.?
|
||||
}
|
||||
void set_op_name(std::string name) { op_name_ = std::move(name); }
|
||||
|
||||
private: // Testing purpose.
|
||||
std::string op_name_;
|
||||
@@ -119,38 +140,42 @@ class DummyProvider : public TracingOpsProvider {
|
||||
explicit DummyProvider(std::string service)
|
||||
: service_name_(std::move(service)) {}
|
||||
|
||||
std::unique_ptr<opentracing::Span> StartSpanWithOptions(
|
||||
opentracing::string_view operation_name,
|
||||
const opentracing::StartSpanOptions& options) const noexcept override {
|
||||
auto result = std::make_unique<DummySpan>();
|
||||
result->SetOperationName(std::string(operation_name));
|
||||
for (auto&& [k, v] : options.tags) {
|
||||
result->SetTag(k, v);
|
||||
otel::nostd::shared_ptr<otel::trace::Span> StartSpanWithOptions(
|
||||
otel::nostd::string_view operation_name,
|
||||
const SpanStartOptions& options) const noexcept override {
|
||||
otel::nostd::shared_ptr<otel::trace::Span> span(new DummySpan());
|
||||
auto* dummy = static_cast<DummySpan*>(span.get());
|
||||
dummy->set_op_name(
|
||||
std::string(operation_name.data(), operation_name.size()));
|
||||
// Record the span kind as a tag for the test to observe.
|
||||
dummy->SetAttribute(
|
||||
kSpanKind,
|
||||
options.kind == otel::trace::SpanKind::kClient ? "client" : "server");
|
||||
for (auto&& [k, v] : options.attributes) {
|
||||
dummy->SetAttribute(k, v);
|
||||
}
|
||||
return result;
|
||||
return span;
|
||||
}
|
||||
|
||||
void SetFrameworkTag(opentracing::Span* span, opentracing::string_view key,
|
||||
const opentracing::Value& value) override {
|
||||
if (key == ext::kInvocationStatus) {
|
||||
span->SetTag("dummy.invocation-status",
|
||||
std::to_string(value.get<std::int64_t>()));
|
||||
void SetFrameworkTag(otel::trace::Span* span, otel::nostd::string_view key,
|
||||
const otel::common::AttributeValue& value) override {
|
||||
if (ToStd(key) == ext::kInvocationStatus) {
|
||||
span->SetAttribute("dummy.invocation-status", value);
|
||||
} else {
|
||||
FLARE_CHECK(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool Inject(const opentracing::SpanContext& sc,
|
||||
std::string* out) const override {
|
||||
bool Inject(const otel::trace::SpanContext&, std::string*) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
opentracing::expected<std::unique_ptr<opentracing::SpanContext>> Extract(
|
||||
const std::string& in) const override {
|
||||
return std::unique_ptr<opentracing::SpanContext>();
|
||||
std::optional<otel::trace::SpanContext> Extract(
|
||||
const std::string&) const override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool IsSampled(const opentracing::Span&) const noexcept override {
|
||||
bool IsSampled(const otel::trace::Span&) const noexcept override {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -251,17 +276,16 @@ TEST_F(TracingIntegrationTest, All) {
|
||||
|
||||
for (auto&& span : reported_spans) { // Server spans.
|
||||
std::unordered_map tag{span.tags.begin(), span.tags.end()};
|
||||
if (tag[opentracing::ext::span_kind] ==
|
||||
opentracing::ext::span_kind_rpc_client) {
|
||||
if (tag[kSpanKind] == "client") {
|
||||
continue;
|
||||
}
|
||||
ASSERT_THAT(span.tags,
|
||||
::testing::UnorderedElementsAre(
|
||||
std::pair(opentracing::ext::span_kind,
|
||||
opentracing::ext::span_kind_rpc_server),
|
||||
std::pair("dummy.invocation-status", "0"),
|
||||
std::pair(kTagKey, kTagValue)));
|
||||
ASSERT_THAT(span.logs, ::testing::ElementsAre(std::pair("", kLogValue)));
|
||||
ASSERT_THAT(span.tags, ::testing::UnorderedElementsAre(
|
||||
std::pair(kSpanKind, "server"),
|
||||
std::pair("dummy.invocation-status", "0"),
|
||||
std::pair(kTagKey, kTagValue)));
|
||||
// An empty log key (single-arg `AddTracingLog`) materializes as the default
|
||||
// event name "log" (@sa: `QuickerSpan::FlushBufferedOps`).
|
||||
ASSERT_THAT(span.logs, ::testing::ElementsAre(std::pair("log", kLogValue)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "opentracing-cpp/span.h" // FIXME: `opentracing/string_view.h`
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
|
||||
// It's unfortunate that `opentracing-cpp` comes with its own `string_view`, and
|
||||
// does not interoperate with `std::`'s well, so we provide some helpers here.
|
||||
// `opentelemetry`'s `nostd::string_view` only adopts `std::string_view` when
|
||||
// the standard one isn't available; flare always builds with C++17, but `nostd`
|
||||
// still doesn't offer a `std::string_view` conversion in either direction.
|
||||
// These helpers bridge the two without copying.
|
||||
|
||||
namespace flare::tracing {
|
||||
|
||||
inline bool operator==(std::string_view s, opentracing::string_view o) {
|
||||
return s == std::string_view(o.data(), o.size());
|
||||
inline std::string_view ToStd(opentelemetry::nostd::string_view s) noexcept {
|
||||
return std::string_view(s.data(), s.size());
|
||||
}
|
||||
|
||||
inline bool operator==(opentracing::string_view o, std::string_view s) {
|
||||
return s == std::string_view(o.data(), o.size());
|
||||
inline opentelemetry::nostd::string_view ToOtel(std::string_view s) noexcept {
|
||||
return opentelemetry::nostd::string_view(s.data(), s.size());
|
||||
}
|
||||
|
||||
} // namespace flare::tracing
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "gflags/gflags.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/common/attribute_value.h"
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
#include "opentelemetry/trace/span_metadata.h" // `EndSpanOptions`
|
||||
|
||||
#include "flare/base/exposed_var.h"
|
||||
#include "flare/base/internal/cpu.h"
|
||||
@@ -29,7 +32,9 @@
|
||||
#include "flare/base/never_destroyed.h"
|
||||
#include "flare/base/overloaded.h"
|
||||
#include "flare/base/random.h"
|
||||
#include "flare/base/string.h"
|
||||
#include "flare/base/thread/thread_cached.h"
|
||||
#include "flare/rpc/tracing/string_view_interop.h"
|
||||
#include "flare/rpc/tracing/tracing_ops_provider.h"
|
||||
|
||||
DEFINE_string(flare_tracing_provider, "",
|
||||
@@ -62,65 +67,81 @@ std::unique_ptr<TracingOps> MakeTracingOps(
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct StringViewHash {
|
||||
std::size_t operator()(const opentracing::string_view& s) const noexcept {
|
||||
return std::hash<std::string_view>()(std::string_view(s.data(), s.size()));
|
||||
}
|
||||
};
|
||||
|
||||
bool IsStandardTag(const opentracing::string_view& tag) {
|
||||
bool IsStandardTag(otel::nostd::string_view tag) {
|
||||
// TODO(luobogao): We need a better way to enumerate standard tags.
|
||||
static const std::unordered_set<opentracing::string_view, StringViewHash>
|
||||
kStandardTags = {opentracing::ext::span_kind,
|
||||
opentracing::ext::span_kind_rpc_client,
|
||||
opentracing::ext::span_kind_rpc_server,
|
||||
opentracing::ext::error,
|
||||
opentracing::ext::component,
|
||||
opentracing::ext::sampling_priority,
|
||||
opentracing::ext::peer_service,
|
||||
opentracing::ext::peer_hostname,
|
||||
opentracing::ext::peer_address,
|
||||
opentracing::ext::peer_host_ipv4,
|
||||
opentracing::ext::peer_host_ipv6,
|
||||
opentracing::ext::peer_port,
|
||||
opentracing::ext::http_url,
|
||||
opentracing::ext::http_method,
|
||||
opentracing::ext::http_status_code,
|
||||
opentracing::ext::database_instance,
|
||||
opentracing::ext::database_statement,
|
||||
opentracing::ext::database_type,
|
||||
opentracing::ext::database_user,
|
||||
opentracing::ext::message_bus_destination};
|
||||
return kStandardTags.find(tag) != kStandardTags.end();
|
||||
//
|
||||
// These are the conventional (OpenTracing-era) tag names flare still
|
||||
// recognizes. Note `span.kind` is no longer a tag -- it's carried as
|
||||
// `SpanStartOptions::kind`.
|
||||
static const std::unordered_set<std::string_view> kStandardTags = {
|
||||
"error",
|
||||
"component",
|
||||
"sampling_priority",
|
||||
"peer.service",
|
||||
"peer.hostname",
|
||||
"peer.address",
|
||||
"peer.ipv4",
|
||||
"peer.ipv6",
|
||||
"peer.port",
|
||||
"http.url",
|
||||
"http.method",
|
||||
"http.status_code",
|
||||
"db.instance",
|
||||
"db.statement",
|
||||
"db.type",
|
||||
"db.user",
|
||||
"message_bus.destination",
|
||||
};
|
||||
return kStandardTags.find(ToStd(tag)) != kStandardTags.end();
|
||||
}
|
||||
|
||||
bool IsFrameworkTag(const opentracing::string_view& tag) {
|
||||
return StartsWith(std::string_view(tag.data(), tag.size()), "flare.");
|
||||
bool IsFrameworkTag(otel::nostd::string_view tag) {
|
||||
return StartsWith(ToStd(tag), "flare.");
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
void QuickerSpan::FlushBufferedOps() {
|
||||
for (auto&& e : buffered_ops_) {
|
||||
opentracing::Value translated;
|
||||
|
||||
std::visit(
|
||||
// `AttributeValue` is a non-owning view; `scratch` backs any string it
|
||||
// refers to and must outlive the `Set*`/`AddEvent` call below. (For the
|
||||
// buffered-`std::string` case the storage in `e.value` is itself stable
|
||||
// through this iteration.)
|
||||
std::string scratch;
|
||||
otel::common::AttributeValue attr = std::visit(
|
||||
// Functors are evaluated now, otherwise the value is returned as-is.
|
||||
Overloaded{
|
||||
// Not using `const Function<std::string()>` here, otherwise the
|
||||
// catch-all below would take precedence in overload resolution.
|
||||
[&](Function<std::string()>& f) { translated = f(); },
|
||||
[&](std::string_view& sv) { translated = std::string(sv); },
|
||||
[&](auto&& v) { translated = v; }},
|
||||
[&](Function<std::string()>& f) -> otel::common::AttributeValue {
|
||||
scratch = f();
|
||||
return otel::nostd::string_view(scratch.data(), scratch.size());
|
||||
},
|
||||
[&](std::string& s) -> otel::common::AttributeValue {
|
||||
return otel::nostd::string_view(s.data(), s.size());
|
||||
},
|
||||
[&](auto&& v) -> otel::common::AttributeValue { return v; }},
|
||||
e.value);
|
||||
|
||||
if (e.type == Operation::FrameworkTag) {
|
||||
ops_->provider_->SetFrameworkTag(span_.get(), e.GetKey(), translated);
|
||||
ops_->provider_->SetFrameworkTag(span_.get(), e.GetKey(), attr);
|
||||
} else if (e.type == Operation::StandardTag ||
|
||||
e.type == Operation::UserTag) {
|
||||
span_->SetTag(e.GetKey(), translated);
|
||||
span_->SetAttribute(e.GetKey(), attr);
|
||||
} else {
|
||||
FLARE_CHECK(e.type == Operation::Log);
|
||||
span_->Log({std::pair(e.GetKey(), translated)});
|
||||
// OpenTelemetry has no opentracing-style "log fields on span"; the
|
||||
// closest is an event. The flare log key becomes the event name and the
|
||||
// value an attribute. Note each flare log is its own event, unlike
|
||||
// opentracing which grouped multiple fields under one timestamped record.
|
||||
//
|
||||
// flare permits an empty log key (single-arg `AddTracingLog(value)`);
|
||||
// give those a non-empty event name so backends that group / display by
|
||||
// event name don't render a blank one.
|
||||
auto event_name = e.GetKey();
|
||||
span_->AddEvent(
|
||||
event_name.empty() ? otel::nostd::string_view("log") : event_name,
|
||||
{{"value", attr}});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,18 +149,18 @@ void QuickerSpan::FlushBufferedOps() {
|
||||
void QuickerSpan::ReportViaDpc() {
|
||||
auto cb = [span = std::move(span_),
|
||||
finished_at = ReadSteadyClock()]() mutable {
|
||||
// No we cannot simply call `opentracing::Span::Finish()` here as by the
|
||||
// time we're called (asynchronously via DPC), an undetermined time period
|
||||
// has passed. Because `Finish()` internally captures current timestamp as
|
||||
// "finishing timestamp", it's likely wrong. Therefore, here we use the time
|
||||
// point we recorded when `ReportViaDpc()` was called to finish the span.
|
||||
opentracing::FinishSpanOptions options;
|
||||
options.finish_steady_timestamp = finished_at;
|
||||
span->FinishWithOptions(options);
|
||||
// No we cannot simply call `Span::End()` with default options here as by
|
||||
// the time we're called (asynchronously via DPC), an undetermined time
|
||||
// period has passed and `End()` would otherwise capture "now" as the finish
|
||||
// time. Use the time point recorded when `ReportViaDpc()` was called
|
||||
// instead.
|
||||
otel::trace::EndSpanOptions options;
|
||||
options.end_steady_time = otel::common::SteadyTimestamp(finished_at);
|
||||
span->End(options);
|
||||
reported_spans->Add(1);
|
||||
|
||||
// Any sane implementation shouldn't report twice (implicitly via `span`'s
|
||||
// dtor.)
|
||||
// Any sane implementation shouldn't report twice (the API contract is that
|
||||
// a `Span`'s dtor does not implicitly `End()`.)
|
||||
};
|
||||
internal::QueueDpc(std::move(cb));
|
||||
|
||||
|
||||
@@ -16,43 +16,50 @@
|
||||
#define FLARE_RPC_TRACING_TRACING_OPS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "opentracing-cpp/span.h"
|
||||
#include "opentracing-cpp/tracer.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/common/attribute_value.h"
|
||||
#include "opentelemetry/common/timestamp.h"
|
||||
#include "opentelemetry/nostd/shared_ptr.h"
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
#include "opentelemetry/trace/span.h"
|
||||
#include "opentelemetry/trace/span_context.h"
|
||||
|
||||
#include "flare/base/chrono.h"
|
||||
#include "flare/base/function.h"
|
||||
#include "flare/base/internal/test_prod.h"
|
||||
#include "flare/base/maybe_owning.h"
|
||||
#include "flare/base/never_destroyed.h"
|
||||
#include "flare/base/string.h"
|
||||
#include "flare/rpc/internal/sampler.h"
|
||||
#include "flare/rpc/tracing/framework_tags.h"
|
||||
#include "flare/rpc/tracing/string_view_interop.h"
|
||||
#include "flare/rpc/tracing/tracing_ops_provider.h"
|
||||
|
||||
namespace flare::tracing {
|
||||
|
||||
namespace detail {
|
||||
|
||||
bool IsStandardTag(const opentracing::string_view& tag);
|
||||
bool IsFrameworkTag(const opentracing::string_view& tag);
|
||||
bool IsStandardTag(otel::nostd::string_view tag);
|
||||
bool IsFrameworkTag(otel::nostd::string_view tag);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class TracingOps;
|
||||
|
||||
// A wrapper for `opentracing::Span` that is... quicker.
|
||||
// A wrapper for `opentelemetry::trace::Span` that is... quicker.
|
||||
class QuickerSpan {
|
||||
public:
|
||||
// The default-constructed one is a "noop" span (i.e., all its methods are
|
||||
// effectively do nothing.).
|
||||
QuickerSpan() = default;
|
||||
|
||||
explicit QuickerSpan(TracingOps* ops, std::unique_ptr<opentracing::Span> span)
|
||||
explicit QuickerSpan(TracingOps* ops,
|
||||
otel::nostd::shared_ptr<otel::trace::Span> span)
|
||||
: ops_(ops), span_(std::move(span)) {}
|
||||
|
||||
~QuickerSpan() {
|
||||
@@ -69,24 +76,24 @@ class QuickerSpan {
|
||||
|
||||
// Set standard tag on span.
|
||||
//
|
||||
// Only tags defined in `opentracing::ext::` should be used here. These tags
|
||||
// are forwarded to `span` without further translation.
|
||||
// Only standard tags (@sa: `detail::IsStandardTag`) should be used here.
|
||||
// These tags are forwarded to `span` without further translation.
|
||||
template <class V>
|
||||
void SetStandardTag(const opentracing::string_view&, V&& value);
|
||||
void SetStandardTag(otel::nostd::string_view, V&& value);
|
||||
|
||||
// Tags defined by flare framework should be translated by the provider before
|
||||
// setting it into span.
|
||||
//
|
||||
// @sa: `framework_tags.h`
|
||||
template <class V>
|
||||
void SetFrameworkTag(const opentracing::string_view&, V&& value);
|
||||
void SetFrameworkTag(otel::nostd::string_view, V&& value);
|
||||
|
||||
// User tags are passed through. It's recommended to use a distinct prefix for
|
||||
// user tags to avoid name collision.
|
||||
template <class V>
|
||||
void SetUserTag(std::string key, V&& value);
|
||||
|
||||
// Append a log item to the trace.
|
||||
// Append a log item to the trace. (Materialized as an OpenTelemetry event.)
|
||||
void Log(std::string key, std::string value);
|
||||
|
||||
// Baggage items, AFAICS, are only sensible for framework's use. As we don't
|
||||
@@ -97,16 +104,18 @@ class QuickerSpan {
|
||||
|
||||
// Flush any tags buffered tags and report it to the provider.
|
||||
//
|
||||
// If the provider's `SetTag` is way too slow (e.g., tjg provider is likely
|
||||
// not very performance), it might be beneficial to buffer KV pairs ourself
|
||||
// and add them later when the span is indeed going to be reported.
|
||||
// If the provider's `SetAttribute` is way too slow, it might be beneficial to
|
||||
// buffer KV pairs ourself and add them later when the span is indeed going to
|
||||
// be reported.
|
||||
void Report();
|
||||
|
||||
// You'll need this to derive your own client-side span.
|
||||
//
|
||||
// `nullptr` is returned if `Tracing()` does not hold.
|
||||
const opentracing::SpanContext* SpanContext() const noexcept {
|
||||
return Tracing() ? &span_->context() : nullptr;
|
||||
// `opentelemetry::trace::SpanContext` is a value type. An invalid context
|
||||
// (`SpanContext::GetInvalid()`) is returned if `Tracing()` does not hold.
|
||||
otel::trace::SpanContext GetSpanContext() const noexcept {
|
||||
return Tracing() ? span_->GetContext()
|
||||
: otel::trace::SpanContext::GetInvalid();
|
||||
}
|
||||
|
||||
// Serialize span context to byte stream, which is (normally) transmitted to
|
||||
@@ -159,26 +168,38 @@ class QuickerSpan {
|
||||
} log{};
|
||||
|
||||
struct BufferedOp {
|
||||
// We keep our own owning variant rather than `opentelemetry::common::
|
||||
// AttributeValue` because the latter is a non-owning view type (it holds a
|
||||
// `nostd::string_view`, not an owning string). We convert to
|
||||
// `AttributeValue` only at flush time (@sa: `FlushBufferedOps`).
|
||||
using QuickerValue =
|
||||
std::variant<bool, std::int32_t, std::uint32_t, std::int64_t,
|
||||
std::uint64_t, std::string, Function<std::string()>>;
|
||||
|
||||
// Callers pass `const char*` (e.g. `full_name().c_str()`) which must select
|
||||
// the `std::string` alternative, not `bool`. Only P0608R3 (C++20) makes the
|
||||
// pointer-to-`bool` narrowing conversion non-viable so overload resolution
|
||||
// picks `std::string`; under C++17 a `const char*` would silently bind to
|
||||
// `bool`. flare is C++20, so assert it rather than rely on it implicitly.
|
||||
static_assert(__cplusplus >= 202002L,
|
||||
"QuickerValue relies on P0608R3 (C++20) so that a "
|
||||
"`const char*` value selects `std::string`, not `bool`.");
|
||||
|
||||
Operation type;
|
||||
|
||||
// For user tags or logs, `key`'s lifetime is not guaranteed, so we make a
|
||||
// copy here. Not applicable to non-user tags.
|
||||
//
|
||||
// This is an implementation detail, don't touch it.
|
||||
std::variant<opentracing::string_view, std::string> key;
|
||||
// copy here. Standard/framework tag keys are static literals, so we keep a
|
||||
// (non-owning) view for them.
|
||||
std::variant<otel::nostd::string_view, std::string> key;
|
||||
QuickerValue value;
|
||||
|
||||
template <class V>
|
||||
BufferedOp(standard_tag_t, const opentracing::string_view& key, V&& value)
|
||||
BufferedOp(standard_tag_t, otel::nostd::string_view key, V&& value)
|
||||
: type(Operation::StandardTag),
|
||||
key(key),
|
||||
value(std::forward<V>(value)) {}
|
||||
template <class V>
|
||||
BufferedOp(framework_tag_t, const opentracing::string_view& key, V&& value)
|
||||
BufferedOp(framework_tag_t, otel::nostd::string_view key, V&& value)
|
||||
: type(Operation::FrameworkTag),
|
||||
key(key),
|
||||
value(std::forward<V>(value)) {}
|
||||
@@ -192,9 +213,9 @@ class QuickerSpan {
|
||||
key = std::move(key_str);
|
||||
}
|
||||
|
||||
opentracing::string_view GetKey() const noexcept {
|
||||
otel::nostd::string_view GetKey() const noexcept {
|
||||
return key.index() == 0 ? std::get<0>(key)
|
||||
: opentracing::string_view(std::get<1>(key));
|
||||
: otel::nostd::string_view(std::get<1>(key));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,7 +236,7 @@ class QuickerSpan {
|
||||
|
||||
private:
|
||||
TracingOps* ops_;
|
||||
std::unique_ptr<opentracing::Span> span_;
|
||||
otel::nostd::shared_ptr<otel::trace::Span> span_;
|
||||
std::vector<BufferedOp> buffered_ops_;
|
||||
// FIXME: Thread-safety. (`std::atomic_ref` should come to rescue.) To make
|
||||
// the whole class movable, we cannot simply use `std::atomic<T>` here.
|
||||
@@ -223,19 +244,13 @@ class QuickerSpan {
|
||||
};
|
||||
|
||||
// This class implements (and hides implementation detail of) all operations
|
||||
// required by distributed tracing (given the the concrete implementation
|
||||
// conforms to OpenTracing standard.)
|
||||
// required by distributed tracing.
|
||||
//
|
||||
// To be fair, `opentracing-cpp` already implements almost *everything* we need
|
||||
// (it even comes with a "noop" tracer which is "reimplemented" here), except
|
||||
// that performance is not seriously guaranteed. (The "noop" tracer does state
|
||||
// that it comes with minimal perf. overhead, but that's still relatively large
|
||||
// compared to implementation here.)
|
||||
//
|
||||
// Here we "reinvent the wheel" for:
|
||||
// We "reinvent the wheel" (rather than exposing `opentelemetry::trace::Tracer`
|
||||
// directly) for:
|
||||
//
|
||||
// - Better performance when distributed tracing is NOT enabled.
|
||||
// - Unified interface for supporting non-(opentracing-)standard tags.
|
||||
// - Unified interface for supporting framework-specific (non-standard) tags.
|
||||
//
|
||||
// Note that you need to execute a barrier on DPC to wait for pending DPCs
|
||||
// posted by `QuickerSpan::Report()`.
|
||||
@@ -250,39 +265,41 @@ class TracingOps {
|
||||
//
|
||||
// DO NOT RELY ON `apply_opts` BEING EVALUATED UNCONDITIONALLY.
|
||||
//
|
||||
// It's recommended NOT to apply tags in `apply_opts`, as some
|
||||
// implementation's `SetTag` is slow. By calling `QuickerSpan::SetXxxTag`
|
||||
// instead, the framework can buffer the calls to `SetXxxTag` until it's
|
||||
// absolutely necessary (and eliminate the call completely if possible),
|
||||
// therefore boosting performance. (However, some tracing provider DO require
|
||||
// tags be set in `apply_opts`, consult documentation of provider you use for
|
||||
// details.)
|
||||
// `apply_opts` is invoked as `apply_opts(SpanStartOptions&)` and should set
|
||||
// the parent context / span kind / start-time attributes on it. It's
|
||||
// recommended NOT to apply ordinary tags here (use `QuickerSpan::SetXxxTag`
|
||||
// instead), as some implementation's `SetAttribute` is slow and the framework
|
||||
// can buffer / elide those calls.
|
||||
template <class F>
|
||||
QuickerSpan StartSpanWithLazyOptions(std::string_view operation_name,
|
||||
F&& apply_opts) {
|
||||
if (Tracing()) {
|
||||
opentracing::StartSpanOptions options = {
|
||||
.start_system_timestamp = ReadSystemClock(),
|
||||
.start_steady_timestamp = ReadSteadyClock()};
|
||||
std::forward<F>(apply_opts)([&](auto&& opt) { opt.Apply(options); });
|
||||
SpanStartOptions options;
|
||||
options.start_system_timestamp =
|
||||
otel::common::SystemTimestamp(ReadSystemClock());
|
||||
options.start_steady_timestamp =
|
||||
otel::common::SteadyTimestamp(ReadSteadyClock());
|
||||
std::forward<F>(apply_opts)(options);
|
||||
|
||||
// OpenTracing uses its own `string_view`, which by the of writing, cannot
|
||||
// be constructed from `std::string_view` implicitly.
|
||||
opentracing::string_view onsv(operation_name.data(),
|
||||
operation_name.size());
|
||||
return QuickerSpan{this, provider_->StartSpanWithOptions(onsv, options)};
|
||||
return QuickerSpan{this,
|
||||
provider_->StartSpanWithOptions(
|
||||
otel::nostd::string_view(operation_name.data(),
|
||||
operation_name.size()),
|
||||
options)};
|
||||
}
|
||||
|
||||
return QuickerSpan{nullptr /* Doesn't matter. */, nullptr};
|
||||
return QuickerSpan{nullptr /* Doesn't matter. */,
|
||||
otel::nostd::shared_ptr<otel::trace::Span>(nullptr)};
|
||||
}
|
||||
|
||||
// Deserialize span context from byte stream.
|
||||
opentracing::expected<std::unique_ptr<opentracing::SpanContext>>
|
||||
ParseSpanContextFrom(const std::string& serialized) {
|
||||
// Deserialize span context from byte stream. `std::nullopt` if there's
|
||||
// nothing (parseable) to extract.
|
||||
std::optional<otel::trace::SpanContext> ParseSpanContextFrom(
|
||||
const std::string& serialized) {
|
||||
if (Tracing()) {
|
||||
return provider_->Extract(serialized);
|
||||
}
|
||||
return std::unique_ptr<opentracing::SpanContext>();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -303,8 +320,7 @@ TracingOps* GetTracingOps(std::string_view service);
|
||||
////////////////////////////////////////
|
||||
|
||||
template <class V>
|
||||
void QuickerSpan::SetStandardTag(const opentracing::string_view& key,
|
||||
V&& value) {
|
||||
void QuickerSpan::SetStandardTag(otel::nostd::string_view key, V&& value) {
|
||||
FLARE_DCHECK(detail::IsStandardTag(key));
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
buffered_ops_.emplace_back(standard_tag, key, std::forward<V>(value));
|
||||
@@ -312,8 +328,7 @@ void QuickerSpan::SetStandardTag(const opentracing::string_view& key,
|
||||
}
|
||||
|
||||
template <class V>
|
||||
void QuickerSpan::SetFrameworkTag(const opentracing::string_view& key,
|
||||
V&& value) {
|
||||
void QuickerSpan::SetFrameworkTag(otel::nostd::string_view key, V&& value) {
|
||||
FLARE_DCHECK(detail::IsFrameworkTag(key));
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
buffered_ops_.emplace_back(framework_tag, key, std::forward<V>(value));
|
||||
@@ -322,7 +337,8 @@ void QuickerSpan::SetFrameworkTag(const opentracing::string_view& key,
|
||||
|
||||
template <class V>
|
||||
void QuickerSpan::SetUserTag(std::string key, V&& value) {
|
||||
FLARE_DCHECK(!detail::IsStandardTag(key) && !detail::IsFrameworkTag(key));
|
||||
FLARE_DCHECK(!detail::IsStandardTag(otel::nostd::string_view(key)) &&
|
||||
!detail::IsFrameworkTag(otel::nostd::string_view(key)));
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
buffered_ops_.emplace_back(user_tag, std::move(key),
|
||||
std::forward<V>(value));
|
||||
@@ -331,13 +347,13 @@ void QuickerSpan::SetUserTag(std::string key, V&& value) {
|
||||
|
||||
inline void QuickerSpan::Log(std::string key, std::string value) {
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
buffered_ops_.emplace_back(log, key, std::move(value));
|
||||
buffered_ops_.emplace_back(log, std::move(key), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
inline bool QuickerSpan::WriteContextTo(std::string* serialized) {
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
return ops_->provider_->Inject(span_->context(), serialized);
|
||||
return ops_->provider_->Inject(span_->GetContext(), serialized);
|
||||
}
|
||||
serialized->clear(); // Nothing to inject otherwise.
|
||||
return true;
|
||||
@@ -346,18 +362,24 @@ inline bool QuickerSpan::WriteContextTo(std::string* serialized) {
|
||||
inline void QuickerSpan::Report() {
|
||||
if (FLARE_UNLIKELY(Tracing())) {
|
||||
if (forcibly_sampled_) {
|
||||
// Any sane implementation should treat the trace as sampled afterwards.
|
||||
span_->SetTag(opentracing::ext::sampling_priority, 1);
|
||||
// OpenTelemetry's sampling decision is fixed at span start (immutable
|
||||
// trace flags), so we can't flip a live span to "sampled". Record the
|
||||
// intent as a framework tag and let the provider translate it into
|
||||
// whatever its backend honors -- routing it through `SetFrameworkTag`
|
||||
// (rather than a raw `SetAttribute`) keeps the `flare.`-prefixed tag
|
||||
// consistent with every other framework tag, and runs before the
|
||||
// `IsSampled` check below so a provider can act on it.
|
||||
ops_->GetProvider()->SetFrameworkTag(span_.get(),
|
||||
ToOtel(ext::kSamplingPriority), 1);
|
||||
}
|
||||
if (ops_->GetProvider()->IsSampled(*span_)) {
|
||||
FlushBufferedOps(); // Flushing buffered ops is done only when sampled.
|
||||
ReportViaDpc();
|
||||
} else {
|
||||
// If not sampled, finishing the span should be relatively cheap.
|
||||
//
|
||||
// FIXME: `Span`'s destructor unconditionally calls `steady_clock::now()`,
|
||||
// and that hurts performance (@sa: `doc/timestamps.md`.)
|
||||
span_ = nullptr; // Finishes the span implicitly.
|
||||
// If not sampled, dropping the span should be relatively cheap. The
|
||||
// OpenTelemetry API does not auto-`End()` on destruction, so this neither
|
||||
// exports nor pays for a final timestamp.
|
||||
span_ = nullptr;
|
||||
}
|
||||
} // Nothing to do otherwise.
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
#include "flare/rpc/tracing/tracing_ops.h"
|
||||
|
||||
#include "benchmark/benchmark.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
|
||||
#include "flare/rpc/tracing/framework_tags.h"
|
||||
#include "flare/rpc/tracing/string_view_interop.h"
|
||||
|
||||
// Let's make sure the perf. overhead is minimal when tracing is not enabled.
|
||||
|
||||
@@ -60,9 +60,9 @@ BENCHMARK(Benchmark_TracingOpsParseSpanContext);
|
||||
void Benchmark_QuickerSpan(benchmark::State& state) {
|
||||
while (state.KeepRunning()) {
|
||||
auto span = ops.StartSpanWithLazyOptions(op_name, [](auto opts) {});
|
||||
span.SetStandardTag(opentracing::ext::peer_host_ipv4, remote_peer);
|
||||
span.SetStandardTag(opentracing::ext::peer_port, 12345);
|
||||
span.SetFrameworkTag(ext::kTrackingId, "123");
|
||||
span.SetStandardTag("peer.ipv4", remote_peer);
|
||||
span.SetStandardTag("peer.port", 12345);
|
||||
span.SetFrameworkTag(ToOtel(ext::kTrackingId), "123");
|
||||
span.SetUserTag("user.tag1", "name");
|
||||
span.Report();
|
||||
benchmark::DoNotOptimize(span);
|
||||
|
||||
@@ -16,10 +16,18 @@
|
||||
#define FLARE_RPC_TRACING_TRACING_OPS_PROVIDER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "opentracing-cpp/span.h"
|
||||
#include "opentracing-cpp/tracer.h"
|
||||
#include "opentelemetry/common/attribute_value.h"
|
||||
#include "opentelemetry/common/timestamp.h"
|
||||
#include "opentelemetry/nostd/shared_ptr.h"
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
#include "opentelemetry/trace/span.h"
|
||||
#include "opentelemetry/trace/span_context.h"
|
||||
#include "opentelemetry/trace/span_metadata.h" // `SpanKind`
|
||||
|
||||
#include "flare/base/dependency_registry.h"
|
||||
#include "flare/base/function.h"
|
||||
@@ -27,6 +35,32 @@
|
||||
|
||||
namespace flare::tracing {
|
||||
|
||||
namespace otel = opentelemetry;
|
||||
|
||||
// Options for starting a span.
|
||||
//
|
||||
// This replaces `opentracing::StartSpanOptions`: OpenTelemetry has no
|
||||
// `ChildOf`-style option objects, so the framework collects the parent context,
|
||||
// span kind, timestamps, and any tags a provider needs at start-time here. The
|
||||
// provider turns this into an `opentelemetry::trace::StartSpanOptions` plus the
|
||||
// initial attribute set.
|
||||
struct SpanStartOptions {
|
||||
// A default-constructed (invalid) `SpanContext` means "this is a root span".
|
||||
// `SpanContext` is a value type in OpenTelemetry, not a heap pointer.
|
||||
otel::trace::SpanContext parent = otel::trace::SpanContext::GetInvalid();
|
||||
|
||||
otel::trace::SpanKind kind = otel::trace::SpanKind::kInternal;
|
||||
|
||||
otel::common::SystemTimestamp start_system_timestamp;
|
||||
otel::common::SteadyTimestamp start_steady_timestamp;
|
||||
|
||||
// Some providers require certain tags (e.g. span kind) be present at span
|
||||
// start rather than set later. They're carried here as (key, value) pairs.
|
||||
// The keys must outlive the `StartSpanWithOptions` call.
|
||||
std::vector<std::pair<otel::nostd::string_view, otel::common::AttributeValue>>
|
||||
attributes;
|
||||
};
|
||||
|
||||
// `TracingOps` uses this class to implement its job.
|
||||
//
|
||||
// For more detail about `TracingOps`, see its class header comment.
|
||||
@@ -34,19 +68,26 @@ class TracingOpsProvider {
|
||||
public:
|
||||
virtual ~TracingOpsProvider() = default;
|
||||
|
||||
virtual std::unique_ptr<opentracing::Span> StartSpanWithOptions(
|
||||
opentracing::string_view operation_name,
|
||||
const opentracing::StartSpanOptions& options) const noexcept = 0;
|
||||
virtual otel::nostd::shared_ptr<otel::trace::Span> StartSpanWithOptions(
|
||||
otel::nostd::string_view operation_name,
|
||||
const SpanStartOptions& options) const noexcept = 0;
|
||||
|
||||
virtual void SetFrameworkTag(opentracing::Span* span,
|
||||
opentracing::string_view key,
|
||||
const opentracing::Value& value) = 0;
|
||||
// Translate the flare framework tag `key` to the name(s) recognized by the
|
||||
// provider's own implementation and set it onto `span`.
|
||||
virtual void SetFrameworkTag(otel::trace::Span* span,
|
||||
otel::nostd::string_view key,
|
||||
const otel::common::AttributeValue& value) = 0;
|
||||
|
||||
virtual bool Inject(const opentracing::SpanContext& sc,
|
||||
// Serialize `sc` (a value type) into `out`. The wire format is opaque to the
|
||||
// framework -- the provider owns it (@sa: `rpc_meta.proto`'s
|
||||
// `tracing_context`).
|
||||
virtual bool Inject(const otel::trace::SpanContext& sc,
|
||||
std::string* out) const = 0;
|
||||
|
||||
virtual opentracing::expected<std::unique_ptr<opentracing::SpanContext>>
|
||||
Extract(const std::string& in) const = 0;
|
||||
// Inverse of `Inject`. Returns `std::nullopt` if `in` carries no (parseable)
|
||||
// context.
|
||||
virtual std::optional<otel::trace::SpanContext> Extract(
|
||||
const std::string& in) const = 0;
|
||||
|
||||
// Check if `span` is sampled. If not, buffered tags are discarded without
|
||||
// flushing into `span` (for perf. reasons).
|
||||
@@ -54,7 +95,7 @@ class TracingOpsProvider {
|
||||
// Besides, for non-sampled spans, they're always destroyed synchronously, as
|
||||
// we expect destroying non-sampled spans should be lightweight (If this is
|
||||
// not your case, let us know and see if this interface should be refined.).
|
||||
virtual bool IsSampled(const opentracing::Span& span) const noexcept = 0;
|
||||
virtual bool IsSampled(const otel::trace::Span& span) const noexcept = 0;
|
||||
};
|
||||
|
||||
// The framework passes necessary information to `TracingOpsProvider` via this
|
||||
|
||||
@@ -14,14 +14,23 @@
|
||||
|
||||
#include "flare/rpc/tracing/tracing_ops.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "opentracing/ext/tags.h"
|
||||
#include "opentelemetry/common/attribute_value.h"
|
||||
#include "opentelemetry/common/key_value_iterable.h"
|
||||
#include "opentelemetry/nostd/shared_ptr.h"
|
||||
#include "opentelemetry/nostd/string_view.h"
|
||||
#include "opentelemetry/nostd/variant.h"
|
||||
#include "opentelemetry/trace/span.h"
|
||||
#include "opentelemetry/trace/span_context.h"
|
||||
#include "opentelemetry/trace/span_metadata.h"
|
||||
|
||||
#include "flare/rpc/tracing/framework_tags.h"
|
||||
#include "flare/rpc/tracing/string_view_interop.h"
|
||||
@@ -31,104 +40,135 @@ using namespace std::literals;
|
||||
|
||||
namespace flare::tracing {
|
||||
|
||||
opentracing::SpanContext* null_span_context = nullptr;
|
||||
opentracing::Tracer* null_tracer = nullptr;
|
||||
namespace {
|
||||
|
||||
class DummySpan : public opentracing::Span {
|
||||
// Stringify an `AttributeValue` for assertion purposes.
|
||||
std::string AttrToString(const otel::common::AttributeValue& v) {
|
||||
return otel::nostd::visit(
|
||||
[](auto&& x) -> std::string {
|
||||
using T = std::decay_t<decltype(x)>;
|
||||
if constexpr (std::is_same_v<T, otel::nostd::string_view>) {
|
||||
return std::string(x.data(), x.size());
|
||||
} else if constexpr (std::is_same_v<T, const char*>) {
|
||||
return std::string(x);
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
return x ? "true" : "false";
|
||||
} else if constexpr (std::is_arithmetic_v<T>) {
|
||||
return std::to_string(x);
|
||||
} else {
|
||||
return {}; // `span<>` alternatives, not used by these tests.
|
||||
}
|
||||
},
|
||||
v);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A recording fake span. (OpenTelemetry's bundled noop span discards
|
||||
// everything, so it can't be asserted on.)
|
||||
class DummySpan : public otel::trace::Span {
|
||||
public:
|
||||
void FinishWithOptions(
|
||||
const opentracing::FinishSpanOptions&) noexcept override {}
|
||||
|
||||
void SetOperationName(opentracing::string_view name) noexcept override {
|
||||
op_name = name;
|
||||
void SetAttribute(
|
||||
otel::nostd::string_view key,
|
||||
const otel::common::AttributeValue& value) noexcept override {
|
||||
tags.push_back({std::string(key.data(), key.size()), AttrToString(value)});
|
||||
}
|
||||
|
||||
void SetTag(opentracing::string_view key,
|
||||
const opentracing::Value& value) noexcept override {
|
||||
tags.push_back(std::pair(key, value.get<std::string>()));
|
||||
void AddEvent(otel::nostd::string_view) noexcept override {}
|
||||
void AddEvent(otel::nostd::string_view,
|
||||
otel::common::SystemTimestamp) noexcept override {}
|
||||
void AddEvent(
|
||||
otel::nostd::string_view name, otel::common::SystemTimestamp,
|
||||
const otel::common::KeyValueIterable& attributes) noexcept override {
|
||||
attributes.ForEachKeyValue(
|
||||
[&](otel::nostd::string_view,
|
||||
otel::common::AttributeValue value) noexcept {
|
||||
logs.push_back(
|
||||
{std::string(name.data(), name.size()), AttrToString(value)});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void SetBaggageItem(opentracing::string_view,
|
||||
opentracing::string_view) noexcept override {}
|
||||
|
||||
std::string BaggageItem(opentracing::string_view) const noexcept override {
|
||||
return "";
|
||||
void SetStatus(otel::trace::StatusCode,
|
||||
otel::nostd::string_view) noexcept override {}
|
||||
void UpdateName(otel::nostd::string_view name) noexcept override {
|
||||
op_name = std::string(name.data(), name.size());
|
||||
}
|
||||
|
||||
void Log(std::initializer_list<
|
||||
std::pair<opentracing::string_view, opentracing::Value>>) noexcept
|
||||
override {}
|
||||
|
||||
const opentracing::SpanContext& context() const noexcept override {
|
||||
return *null_span_context; // U.B.?
|
||||
}
|
||||
|
||||
const opentracing::Tracer& tracer() const noexcept override {
|
||||
return *null_tracer; // U.B.?
|
||||
void End(const otel::trace::EndSpanOptions&) noexcept override {}
|
||||
otel::trace::SpanContext GetContext() const noexcept override {
|
||||
return otel::trace::SpanContext::GetInvalid();
|
||||
}
|
||||
bool IsRecording() const noexcept override { return true; }
|
||||
|
||||
public: // Testing purpose.
|
||||
std::string op_name;
|
||||
inline static std::vector<std::pair<std::string, std::string>> tags;
|
||||
inline static std::vector<std::pair<std::string, std::string>> logs;
|
||||
};
|
||||
|
||||
class DummyProvider : public TracingOpsProvider {
|
||||
public:
|
||||
std::unique_ptr<opentracing::Span> StartSpanWithOptions(
|
||||
opentracing::string_view operation_name,
|
||||
const opentracing::StartSpanOptions& options) const noexcept override {
|
||||
auto result = std::make_unique<DummySpan>();
|
||||
result->SetOperationName(operation_name);
|
||||
return result;
|
||||
otel::nostd::shared_ptr<otel::trace::Span> StartSpanWithOptions(
|
||||
otel::nostd::string_view operation_name,
|
||||
const SpanStartOptions&) const noexcept override {
|
||||
otel::nostd::shared_ptr<otel::trace::Span> span(new DummySpan());
|
||||
static_cast<DummySpan*>(span.get())->op_name =
|
||||
std::string(operation_name.data(), operation_name.size());
|
||||
return span;
|
||||
}
|
||||
|
||||
void SetFrameworkTag(opentracing::Span* span, opentracing::string_view key,
|
||||
const opentracing::Value& value) override {
|
||||
if (key == ext::kTrackingId) {
|
||||
span->SetTag("dummy.tracking-id", value);
|
||||
void SetFrameworkTag(otel::trace::Span* span, otel::nostd::string_view key,
|
||||
const otel::common::AttributeValue& value) override {
|
||||
if (ToStd(key) == ext::kTrackingId) {
|
||||
span->SetAttribute("dummy.tracking-id", value);
|
||||
} else {
|
||||
FLARE_CHECK(0);
|
||||
}
|
||||
}
|
||||
|
||||
bool Inject(const opentracing::SpanContext& sc,
|
||||
std::string* out) const override {
|
||||
bool Inject(const otel::trace::SpanContext&, std::string*) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
opentracing::expected<std::unique_ptr<opentracing::SpanContext>> Extract(
|
||||
const std::string& in) const override {
|
||||
return std::unique_ptr<opentracing::SpanContext>();
|
||||
std::optional<otel::trace::SpanContext> Extract(
|
||||
const std::string&) const override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool IsSampled(const opentracing::Span&) const noexcept override {
|
||||
bool IsSampled(const otel::trace::Span&) const noexcept override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TracingOps, Noop) {
|
||||
TracingOps ops(nullptr);
|
||||
auto span = ops.StartSpanWithLazyOptions("my op", [](auto start_opts) {});
|
||||
ASSERT_EQ(nullptr, span.span_);
|
||||
span.SetStandardTag(opentracing::ext::peer_host_ipv4, "127.0.0.1"s);
|
||||
span.SetFrameworkTag(ext::kTrackingId, "tracking-id"s);
|
||||
auto span = ops.StartSpanWithLazyOptions("my op", [](auto&&) {});
|
||||
ASSERT_FALSE(span.span_);
|
||||
span.SetStandardTag("peer.ipv4", "127.0.0.1"s);
|
||||
span.SetFrameworkTag(ToOtel(ext::kTrackingId), "tracking-id"s);
|
||||
span.SetUserTag("user-tag", "value"s);
|
||||
span.Report();
|
||||
// Nothing should happen.
|
||||
}
|
||||
|
||||
TEST(TracingOps, DummyProvider) {
|
||||
// `DummySpan::tags/logs` are `inline static` (the fake span records into
|
||||
// shared state); reset them so the assertions below don't observe leftovers
|
||||
// from any other case.
|
||||
DummySpan::tags.clear();
|
||||
DummySpan::logs.clear();
|
||||
|
||||
TracingOps ops(std::make_unique<DummyProvider>());
|
||||
auto span = ops.StartSpanWithLazyOptions("my op", [](auto start_opts) {});
|
||||
span.SetStandardTag(opentracing::ext::peer_host_ipv4, "127.0.0.1"s);
|
||||
span.SetFrameworkTag(ext::kTrackingId, "tracking-id"s);
|
||||
auto span = ops.StartSpanWithLazyOptions("my op", [](auto&&) {});
|
||||
span.SetStandardTag("peer.ipv4", "127.0.0.1"s);
|
||||
span.SetFrameworkTag(ToOtel(ext::kTrackingId), "tracking-id"s);
|
||||
span.SetUserTag("user-tag", "value"s);
|
||||
auto p = dynamic_cast<DummySpan*>(span.span_.get());
|
||||
ASSERT_EQ("my op", p->op_name);
|
||||
span.Report();
|
||||
ASSERT_THAT(DummySpan::tags,
|
||||
::testing::ElementsAre(
|
||||
std::pair(opentracing::ext::peer_host_ipv4, "127.0.0.1"),
|
||||
std::pair("peer.ipv4", "127.0.0.1"),
|
||||
std::pair("dummy.tracking-id", "tracking-id"), // Translated.
|
||||
std::pair("user-tag", "value")));
|
||||
|
||||
|
||||
Vendored
+3
-6
@@ -58,12 +58,9 @@ vcpkg `.a` 能链进 `.so`。
|
||||
> protobuf 原先也列在这里(钉死 3.4.1),现已升级到 vcpkg 3.21.12 并迁移完成,
|
||||
> 见上方 ✅ 表格与 #184。
|
||||
|
||||
- **opentracing-cpp**(本地 1.5.1)
|
||||
vcpkg 的 opentracing(baseline 1.6.0)内置的 `variant.hpp` 用了 `std::result_of`,它在
|
||||
C++17 起被弃用、C++20 移除,flare 的现代工具链(Xcode clang)直接编译失败(`no type named
|
||||
'result_of' in namespace 'std'`)。flare 的 foreign build 正是靠本地 `variant.patch` /
|
||||
`cxx17.patch` 修掉这点(bazel 侧也有对应的 `opentracing-cpp-cxx20.patch`)。vcpkg port 不带
|
||||
这些补丁,要走 vcpkg 只能自写带 patch 的 overlay port,成本过高。
|
||||
> opentracing-cpp 原先也列在这里(本地 1.5.1,因 `std::result_of` 需打补丁)。现已弃用
|
||||
> opentracing,迁移到 **opentelemetry-cpp**(vcpkg,仅用 header-only 的 trace API,
|
||||
> `'//thirdparty/opentelemetry-cpp:api'`;不带 SDK/exporter,因此不引入 protobuf/grpc)。
|
||||
|
||||
### 版本钉死——vcpkg 只有差异过大的新版
|
||||
|
||||
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
# opentelemetry-cpp (API only) via vcpkg (vcpkg#opentelemetry-cpp, pinned by the
|
||||
# baseline in BLADE_ROOT). flare uses only the header-only `opentelemetry_api`
|
||||
# component for distributed tracing (no SDK / exporters). This thin wrapper
|
||||
# keeps the //thirdparty/opentelemetry-cpp:api reference stable for consumers.
|
||||
cc_library(
|
||||
name = 'api',
|
||||
hdrs = [],
|
||||
# The `opentelemetry_api` component is header-only (no `.a`), so blade can't
|
||||
# reference it as a library. `opentelemetry_common` is the smallest real lib
|
||||
# in the install; depending on it gives us the include dir (which holds both
|
||||
# `opentelemetry/` and `absl/` headers) and links cleanly. We still consume
|
||||
# only the API headers -- no SDK/exporter (and thus no protobuf/grpc).
|
||||
deps = ['vcpkg#opentelemetry-cpp:opentelemetry_common'],
|
||||
visibility = 'PUBLIC',
|
||||
)
|
||||
Vendored
-38
@@ -1,38 +0,0 @@
|
||||
include('//thirdparty/foreign_build.bld')
|
||||
|
||||
cmake_build(
|
||||
name='opentracing-cpp_build',
|
||||
source_package='opentracing-cpp-1.5.1.tar.gz',
|
||||
package_name='opentracing-cpp',
|
||||
lib_names=['opentracing', 'opentracing_mocktracer'],
|
||||
cmake_options=[
|
||||
'-DBUILD_SHARED_LIBS=ON',
|
||||
'-DBUILD_STATIC_LIBS=ON',
|
||||
'-DBUILD_MOCKTRACER=ON',
|
||||
'-DBUILD_TESTING=OFF',
|
||||
'-DCMAKE_POSITION_INDEPENDENT_CODE=ON',
|
||||
'-DCMAKE_BUILD_TYPE=Release',
|
||||
'-DCMAKE_CXX_STANDARD=17',
|
||||
],
|
||||
strip_include_prefix='opentracing',
|
||||
generate_dynamic=True,
|
||||
patches=['variant.patch', 'cxx17.patch'],
|
||||
)
|
||||
|
||||
foreign_cc_library(
|
||||
name='opentracing',
|
||||
deps=[
|
||||
':opentracing-cpp_build',
|
||||
],
|
||||
visibility='PUBLIC',
|
||||
has_dynamic=True,
|
||||
)
|
||||
|
||||
foreign_cc_library(
|
||||
name='opentracing_mocktracer',
|
||||
deps=[
|
||||
':opentracing-cpp_build',
|
||||
],
|
||||
visibility='PUBLIC',
|
||||
has_dynamic=True,
|
||||
)
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -39,7 +39,7 @@
|
||||
# ==============================================================================
|
||||
# Configure compilers
|
||||
|
||||
-set(CMAKE_CXX_STANDARD 11)
|
||||
+set(CMAKE_CXX_STANDARD 17)
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything \
|
||||
-Wno-c++98-compat \
|
||||
@@ -1,3 +0,0 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:015c4187f7a6426a2b5196f0ccd982aa87f010cf61f507ae3ce5c90523f92301
|
||||
size 158305
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
--- a/3rd_party/include/opentracing/variant/variant.hpp
|
||||
+++ b/3rd_party/include/opentracing/variant/variant.hpp
|
||||
@@ -167,7 +167,7 @@
|
||||
template <typename F, typename V, typename Enable = void>
|
||||
struct result_of_unary_visit
|
||||
{
|
||||
- using type = typename std::result_of<F(V&)>::type;
|
||||
+ using type = typename std::invoke_result<F, V&>::type;
|
||||
};
|
||||
|
||||
template <typename F, typename V>
|
||||
@@ -179,7 +179,7 @@
|
||||
template <typename F, typename V, typename Enable = void>
|
||||
struct result_of_binary_visit
|
||||
{
|
||||
- using type = typename std::result_of<F(V&, V&)>::type;
|
||||
+ using type = typename std::invoke_result<F, V&, V&>::type;
|
||||
};
|
||||
|
||||
template <typename F, typename V>
|
||||
Reference in New Issue
Block a user