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 a75a57bba..5e5b3d9a0 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 03ada480f..927c5a04a 100644
--- a/tests/functests/APITest.cpp
+++ b/tests/functests/APITest.cpp
@@ -1452,6 +1452,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 conforming 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)