From 277bc85d6131be4532065e3c78830a997bd3bfaa Mon Sep 17 00:00:00 2001 From: Max Golovanov Date: Mon, 31 Oct 2022 16:39:39 -0700 Subject: [PATCH 1/2] Add Distributed Tracing semantics to the following APIs: - ISemanticContext - for all events emitted by ILogger - ability to use env_dt aliases on individual events - semantic context projection for C++/CL/CX apps on Windows - add test that emits two events with DT semantics --- lib/api/ContextFieldsProvider.cpp | 64 +++++++++++++++++++++++++ lib/include/public/CommonFields.h | 9 ++++ lib/include/public/ISemanticContext.hpp | 29 ++++++++++- lib/shared/SemanticContextCX.cpp | 21 ++++++++ lib/shared/SemanticContextCX.hpp | 38 ++++++++++++++- tests/functests/APITest.cpp | 43 +++++++++++++++++ 6 files changed, 201 insertions(+), 3 deletions(-) diff --git a/lib/api/ContextFieldsProvider.cpp b/lib/api/ContextFieldsProvider.cpp index e5792cc66..44522c9b8 100644 --- a/lib/api/ContextFieldsProvider.cpp +++ b/lib/api/ContextFieldsProvider.cpp @@ -244,6 +244,70 @@ namespace MAT_NS_BEGIN record.extDevice[0].deviceClass = iter->second.as_string; } + /* Distributed tracing fields. + * + * Part A extension: + * - ext.dt.traceId -> env_dt_traceId + * - ext.dt.spanId -> env_dt_spanId + * - ext.dt.traceFlags -> env_dt_traceFlags + * + * Part B mapping: + * - parentId -> parentId + */ + iter = m_commonContextFields.find(COMMONFIELDS_DT_TRACEID); + + /* Only run the context mapping if TraceId field is set */ + if (iter != m_commonContextFields.end()) + { + /* Collector does not natively support ext.dt extension in CS4.0. + * When it does, fields could eventually be moved to ext.dt in + * CS4.1 or CS5.0. Currently we map fields to corresponding Geneva + * naming convention in order to enable the Distributed Trace View + * experience. This approach is future-proof, since it avoids the + * unnecessary remapping at 1DS Interchange. + */ + CsProtocol::Value strValue; + strValue.stringValue = iter->second.as_string; + // Map to Geneva Part A extension field name. + ext[COMMONFIELDS_DT_TRACEID] = strValue; + + iter = m_commonContextFields.find(COMMONFIELDS_DT_SPANID); + if (iter != m_commonContextFields.end()) + { + strValue.stringValue = iter->second.as_string; + // Map to Geneva Part A extension field name. + ext[COMMONFIELDS_DT_SPANID] = strValue; + } + + iter = m_commonContextFields.find(COMMONFIELDS_DT_PARENTID); + if (iter != m_commonContextFields.end()) + { + strValue.stringValue = iter->second.as_string; + // 1DS pipeline on export to Aria-Kusto does not differentiate + // between Part B and Part C properties. Since Part B (baseData) + // remains hidden below the radar - we populate the field in + // 'data' (Part C). That way the field can be consumed in Aria-Kusto, + // custom Kusto, and in Geneva. + // + // 1DS Interchange maps the field to the same 'parentId' column + // as populated by OpenTelemetry Geneva exporters. That allows us + // to stitch the client and service telemetry in one view. + ext[COMMONFIELDS_DT_PARENTID] = strValue; + } + + iter = m_commonContextFields.find(COMMONFIELDS_DT_TRACEFLAGS); + if (iter != m_commonContextFields.end()) + { + if (iter->second.type == EventProperty::TYPE_INT64) + { + CsProtocol::Value numValue; + numValue.type = CsProtocol::ValueKind::ValueInt64; + numValue.longValue = iter->second.as_int64; + ext[COMMONFIELDS_DT_TRACEFLAGS] = numValue; + } + } + } + iter = m_commonContextFields.find(COMMONFIELDS_COMMERCIAL_ID); if (iter != m_commonContextFields.end()) { diff --git a/lib/include/public/CommonFields.h b/lib/include/public/CommonFields.h index c6ca6ec23..2ce97d088 100644 --- a/lib/include/public/CommonFields.h +++ b/lib/include/public/CommonFields.h @@ -24,6 +24,15 @@ #define COMMONFIELDS_DEVICE_CLASS "DeviceInfo.Class" #define COMMONFIELDS_DEVICE_ORGID "DeviceInfo.OrgId" +/* Ref. https://www.w3.org/TR/trace-context/#traceparent-header-field-values + - ParentId - parent of the current span received via remote context propagation. + - SpanId - current SpanId sent in 'traceparent' to remote service. + */ +#define COMMONFIELDS_DT_TRACEID "env_dt_traceId" +#define COMMONFIELDS_DT_SPANID "env_dt_spanId" +#define COMMONFIELDS_DT_TRACEFLAGS "env_dt_traceFlags" +#define COMMONFIELDS_DT_PARENTID "parentId" + #define COMMONFIELDS_NETWORK_PROVIDER "DeviceInfo.NetworkProvider" #define COMMONFIELDS_NETWORK_TYPE "DeviceInfo.NetworkType" #define COMMONFIELDS_NETWORK_COST "DeviceInfo.NetworkCost" diff --git a/lib/include/public/ISemanticContext.hpp b/lib/include/public/ISemanticContext.hpp index b0adf421a..284e98f8a 100644 --- a/lib/include/public/ISemanticContext.hpp +++ b/lib/include/public/ISemanticContext.hpp @@ -109,12 +109,39 @@ namespace MAT_NS_BEGIN /// Device class. DECLARE_COMMONFIELD(DeviceClass, COMMONFIELDS_DEVICE_CLASS); - /// + /// /// Set the device orgId context information of telemetry event. /// /// Device orgId DECLARE_COMMONFIELD(DeviceOrgId, COMMONFIELDS_DEVICE_ORGID); + /// + /// Set the W3C TraceContext TraceId information of telemetry event. + /// + /// TraceContext TraceId + DECLARE_COMMONFIELD(TraceId, COMMONFIELDS_DT_TRACEID); + + /// + /// Set the W3C TraceContext SpanId information of telemetry event. + /// + /// TraceContext SpanId + DECLARE_COMMONFIELD(SpanId, COMMONFIELDS_DT_SPANID); + + /// + /// Set the W3C TraceContext TraceFlags information of telemetry event. + /// + /// TraceContext TraceFlags + virtual void SetTraceFlags(uint8_t traceFlags) + { + SetCommonField(COMMONFIELDS_DT_TRACEFLAGS, traceFlags); + } + + /// + /// Set the remote context (parent) SpanId information of telemetry event. + /// + /// ParentId + DECLARE_COMMONFIELD(ParentId, COMMONFIELDS_DT_PARENTID); + /// /// Set the network cost context information of telemetry event. /// diff --git a/lib/shared/SemanticContextCX.cpp b/lib/shared/SemanticContextCX.cpp index 3f81f9fa7..72570d63c 100644 --- a/lib/shared/SemanticContextCX.cpp +++ b/lib/shared/SemanticContextCX.cpp @@ -117,6 +117,27 @@ namespace Microsoft { { m_semanticContextCore->SetUserTimeZone(FromPlatformString(userTimeZone)); } + + void SemanticContextImpl::TraceId::set(String^ traceId) + { + m_semanticContextCore->SetTraceId(FromPlatformString(traceId)); + } + + void SemanticContextImpl::SpanId::set(String ^ spanId) + { + m_semanticContextCore->SetSpanId(FromPlatformString(spanId)); + } + + void SemanticContextImpl::ParentId::set(String ^ parentId) + { + m_semanticContextCore->SetParentId(FromPlatformString(parentId)); + } + + void SemanticContextImpl::TraceFlags::set(unsigned char traceFlags) + { + m_semanticContextCore->SetTraceFlags(static_cast(traceFlags)); + } + } } } diff --git a/lib/shared/SemanticContextCX.hpp b/lib/shared/SemanticContextCX.hpp index a2a97dcd6..489acd3ec 100644 --- a/lib/shared/SemanticContextCX.hpp +++ b/lib/shared/SemanticContextCX.hpp @@ -100,6 +100,23 @@ namespace Microsoft { } virtual void SetUserId(String^ userId, PiiKind piiKind) = 0; + + property String^ TraceId { + virtual void set(String^ traceId) = 0; + } + + property String^ SpanId { + virtual void set(String^ spanId) = 0; + } + + property unsigned char TraceFlags + { + virtual void set(unsigned char traceFlags) = 0; + } + + property String^ ParentId { + virtual void set(String^ parentId) = 0; + } }; /// @cond INTERNAL_DOCS @@ -206,8 +223,25 @@ namespace Microsoft { { virtual void set(String^ appExperimentIds); } - - virtual void SetEventExperimentIds(String^ eventName, String^ experimentIds); + + virtual void SetEventExperimentIds(String^ eventName, String^ experimentIds); + + property String^ TraceId { + virtual void set(String^ traceId); + } + + property String^ SpanId { + virtual void set(String ^ spanId); + } + + property unsigned char TraceFlags + { + virtual void set(unsigned char traceFlags); + } + + property String^ ParentId { + virtual void set(String ^ parentId); + } internal: SemanticContextImpl(MAT::ISemanticContext* semanticContextCore); diff --git a/tests/functests/APITest.cpp b/tests/functests/APITest.cpp index 131b45e8e..b62201ae4 100644 --- a/tests/functests/APITest.cpp +++ b/tests/functests/APITest.cpp @@ -1455,6 +1455,49 @@ TEST(APITest, Custom_Decorator) config.AddModule(CFG_MODULE_DECORATOR, nullptr); } +TEST(APITest, TraceContext) +{ + auto& config = LogManager::GetLogConfiguration(); + LogManager::Initialize(TEST_TOKEN, config); + auto logger = LogManager::GetLogger(); + auto context = logger->GetSemanticContext(); + + // Information that is propagated in W3C TraceContext header. + // These fields must be formatted as strings comforming to W3C spec: + // https://www.w3.org/TR/trace-context/#traceparent-header-field-values + context->SetTraceId("0af7651916cd43dd8448eb211c80319c"); + context->SetSpanId("b9c7c989f97918e1"); + context->SetTraceFlags(1); + + // Information that allows to stitch this span to its remote parent, e.g. + // SpanId='00f067aa0ba902b7' is parent of SpanId='b9c7c989f97918e1'. + // ParentId is not sent as part of W3C TraceContext header, but it is + // logged via 1DS pipeline in order to allow attaching local telemetry + // to remote context. + context->SetParentId("00f067aa0ba902b7"); + + // All events emitted by this logger contain the same trace context. + EventProperties myEvent("MyEvent.With.TraceContext", + { + {"message", "Hello, W3CTraceContext!"} + }); + logger->LogEvent(myEvent); + + { + // Verify that individual events are capable of overridding the context. + EventProperties myEvent2("MyEvent2.With.TraceContext", + { + {"message", "Hello again, W3CTraceContext!"} + }); + // This event borrows the logger context TraceId, is manually + // parented to previous SpanId and carries its own SpanId. + myEvent2.SetProperty(COMMONFIELDS_DT_PARENTID, "b9c7c989f97918e1"); + myEvent2.SetProperty(COMMONFIELDS_DT_SPANID, "b9c7c989f97918c2"); + logger->LogEvent(myEvent2); + } + LogManager::FlushAndTeardown(); +} + #endif // HAVE_MAT_DEFAULT_HTTP_CLIENT // TEST_PULL_ME_IN(APITest) From 4c0df71b44b4b3358c7ec282d9a02702c365bdfa Mon Sep 17 00:00:00 2001 From: Max Golovanov Date: Mon, 31 Oct 2022 17:02:03 -0700 Subject: [PATCH 2/2] Fix typo --- tests/functests/APITest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functests/APITest.cpp b/tests/functests/APITest.cpp index b62201ae4..9057311cf 100644 --- a/tests/functests/APITest.cpp +++ b/tests/functests/APITest.cpp @@ -1463,7 +1463,7 @@ TEST(APITest, TraceContext) auto context = logger->GetSemanticContext(); // Information that is propagated in W3C TraceContext header. - // These fields must be formatted as strings comforming to W3C spec: + // These fields must be formatted as strings conforming to W3C spec: // https://www.w3.org/TR/trace-context/#traceparent-header-field-values context->SetTraceId("0af7651916cd43dd8448eb211c80319c"); context->SetSpanId("b9c7c989f97918e1");