diff --git a/system/jlib/jtrace.cpp b/system/jlib/jtrace.cpp index 632ddf23797..1f8bf86dc06 100644 --- a/system/jlib/jtrace.cpp +++ b/system/jlib/jtrace.cpp @@ -535,38 +535,128 @@ class CSpan : public CInterfaceOf CSpan * localParentSpan = nullptr; }; -static Owned noopSpan; -class CNoopSpan : public CInterfaceOf +class CPassThroughSpan : public CInterfaceOf //extend CSpan and override methods to do nothing? { public: virtual void setSpanAttribute(const char * key, const char * val) override {}; virtual void setSpanAttributes(const IProperties * attributes) override {}; virtual void addSpanEvent(const char * eventName) override {}; - virtual bool getSpanContext(IProperties * ctxProps, bool otelFormatted) const override { return false; }; - virtual void toString(StringBuffer & out) const override {}; - virtual void toLog(StringBuffer & out) const override {}; - virtual void getLogPrefix(StringBuffer & out) const override {}; + void setSpanContext(const IProperties * httpHeaders, SpanFlags flags) + { + if (httpHeaders) + { + if (httpHeaders->hasProp(kGlobalIdHttpHeaderName)) + hpccGlobalId.set(httpHeaders->queryProp(kGlobalIdHttpHeaderName)); + else if (httpHeaders->hasProp(kLegacyGlobalIdHttpHeaderName)) + hpccGlobalId.set(httpHeaders->queryProp(kLegacyGlobalIdHttpHeaderName)); + else if (hasMask(flags, SpanFlags::EnsureGlobalId) || queryTraceManager().alwaysCreateGlobalIds()) + { + StringBuffer generatedId; + appendGloballyUniqueId(generatedId); + hpccGlobalId.set(generatedId.str()); + } + + if (httpHeaders->hasProp(kCallerIdHttpHeaderName)) + hpccCallerId.set(httpHeaders->queryProp(kCallerIdHttpHeaderName)); + else if (httpHeaders->hasProp(kLegacyCallerIdHttpHeaderName)) + hpccCallerId.set(httpHeaders->queryProp(kLegacyCallerIdHttpHeaderName)); + + if (httpHeaders->hasProp(opentelemetry::trace::propagation::kTraceParent.data())) + { + otelParent.set(httpHeaders->queryProp(opentelemetry::trace::propagation::kTraceParent.data())); + } - virtual const char* queryGlobalId() const override { return nullptr; }; - virtual const char* queryCallerId() const override { return nullptr; }; - virtual const char* queryLocalId() const override { return nullptr; }; + if (httpHeaders->hasProp(opentelemetry::trace::propagation::kTraceState.data())) + { + otelState.set(httpHeaders->queryProp(opentelemetry::trace::propagation::kTraceState.data())); + } + } + } - virtual ISpan * createClientSpan(const char * name) override { return getInstance(); }; - virtual ISpan * createInternalSpan(const char * name) override { return getInstance(); }; + virtual bool getSpanContext(IProperties * ctxProps, bool otelFormatted) const override + { + if (ctxProps == nullptr) + return false; + + ctxProps->setNonEmptyProp(kGlobalIdHttpHeaderName, queryGlobalId()); + ctxProps->setNonEmptyProp(kCallerIdHttpHeaderName, queryCallerId()); + ctxProps->setNonEmptyProp(opentelemetry::trace::propagation::kTraceParent.data(), otelParent.get()); + ctxProps->setNonEmptyProp(opentelemetry::trace::propagation::kTraceState.data(), otelState.get()); + + return true; + }; - static ISpan * getInstance() + virtual void toString(StringBuffer & out) const override { - if(!noopSpan.get()) - noopSpan.setown(new CNoopSpan()); + Owned retrievedSpanCtxAttributes = createProperties(); + bool getSpanCtxSuccess = getSpanContext(retrievedSpanCtxAttributes.get(), true); - return noopSpan.getLink(); + if (!getSpanCtxSuccess) + return; + { + Owned it = retrievedSpanCtxAttributes->getIterator(); + for (it->first(); it->isValid(); it->next()) + { + out.appendf("\"%s\":\"%s\",", it->getPropKey(), it->queryPropValue()); + } + } + if (!isEmptyString(out.str())) + out.append("\"Type\":\"PassThrough\""); + //passthrough spans are nameless? + //out.append(",\"Name\":\"").append(name.get()).append("\""); } + //this span will not be logged by any exporter! + virtual void toLog(StringBuffer & out) const override + { + toString(out); + } + + virtual void getLogPrefix(StringBuffer & out) const override {} + + virtual const char* queryGlobalId() const override { return hpccGlobalId.get(); } + virtual const char* queryCallerId() const override { return hpccCallerId.get(); } + virtual const char* queryLocalId() const override { return hpccLocalId.get(); } + + virtual ISpan * createClientSpan(const char * name) override { return getPassthrough(); } + virtual ISpan * createInternalSpan(const char * name) override { return getPassthrough(); } + + ISpan * getPassthrough() + { + return LINK(this); + } + + CPassThroughSpan(const IProperties * httpHeaders, SpanFlags flags = SpanFlags::None) + { + setSpanContext(httpHeaders, flags); + bool createLocalId = !isEmptyString(hpccGlobalId); + if (createLocalId) + hpccLocalId.set(ln_uid::createUniqueIdString().c_str()); + } + + virtual void beforeDispose() override + { + StringBuffer out; + toLog(out); + if (isEmptyString(out.str())) //if no passthrough attributes, no need to log anything + return; + + //MCoperatorTrace not yet available + //LOG(MCoperatorTrace, "TraceSpan: %s", out.str()); + DBGLOG("TraceSpan: {%s}", out.str()); + } private: - CNoopSpan() = default; - CNoopSpan(const CNoopSpan&) = delete; - CNoopSpan& operator=(const CNoopSpan&) = delete; + //Why not a ptree instead of all these StringAttrs? or extend CSpan and override methods to do nothing? + StringAttr hpccGlobalId; + StringAttr hpccCallerId; + StringAttr hpccLocalId; + StringAttr otelParent; + StringAttr otelState; + + CPassThroughSpan() = default; + CPassThroughSpan(const CPassThroughSpan&) = delete; + CPassThroughSpan& operator=(const CPassThroughSpan&) = delete; }; class CInternalSpan : public CSpan @@ -885,7 +975,7 @@ class CTraceManager : implements ITraceManager, public CInterface DBGLOG("traceConfig tree: %s", xml.str()); #endif bool disableTracing = traceConfig && traceConfig->getPropBool("@disable", false); - +disableTracing = true; using namespace opentelemetry::trace; if (disableTracing) { @@ -952,13 +1042,14 @@ class CTraceManager : implements ITraceManager, public CInterface ISpan * createServerSpan(const char * name, StringArray & httpHeaders, SpanFlags flags) override { + Owned headerProperties = getHeadersAsProperties(httpHeaders); if (isTracingEnabled()) { - Owned headerProperties = getHeadersAsProperties(httpHeaders); return new CServerSpan(name, moduleName.get(), headerProperties, flags); } else - return CNoopSpan::getInstance(); + return new CPassThroughSpan(headerProperties, flags); + //return CNoOTelSpan::getInstance(); } ISpan * createServerSpan(const char * name, const IProperties * httpHeaders, SpanFlags flags) override @@ -966,7 +1057,8 @@ class CTraceManager : implements ITraceManager, public CInterface if (isTracingEnabled()) return new CServerSpan(name, moduleName.get(), httpHeaders, flags); else - return CNoopSpan::getInstance(); + return new CPassThroughSpan(httpHeaders, flags); + //return CNoOTelSpan::getInstance(); } const char * getTracedComponentName() const override diff --git a/testing/unittests/jlibtests.cpp b/testing/unittests/jlibtests.cpp index ba2d9a5caac..b45b4c8c166 100644 --- a/testing/unittests/jlibtests.cpp +++ b/testing/unittests/jlibtests.cpp @@ -47,6 +47,9 @@ class JlibTraceTest : public CppUnit::TestFixture //CPPUNIT_TEST(testTraceDisableConfig); CPPUNIT_TEST(testStringArrayPropegatedServerSpan); CPPUNIT_TEST(testDisabledTracePropegatedValues); + CPPUNIT_TEST(testDisabledTraceToStringFormat); + CPPUNIT_TEST(testDisabledTraceNOPropegatedValues); + CPPUNIT_TEST(testMultiNestedPassthroughSpanTraceOutput); CPPUNIT_TEST(testIDPropegation); CPPUNIT_TEST(testTraceConfig); CPPUNIT_TEST(testRootServerSpan); @@ -317,13 +320,6 @@ class JlibTraceTest : public CppUnit::TestFixture void testInvalidPropegatedServerSpan() { - if (!queryTraceManager().isTracingEnabled()) - { - //CPPUNIT_SKIP_MESSAGE("Skipping testIDPropegation, tracing is enabled"); - DBGLOG("Skipping testInvalidPropegatedServerSpan, tracing is not enabled"); - return; - } - Owned mockHTTPHeaders = createProperties(); createMockHTTPHeaders(mockHTTPHeaders, false); Owned serverSpan = queryTraceManager().createServerSpan("invalidPropegatedServerSpan", mockHTTPHeaders); @@ -340,10 +336,10 @@ class JlibTraceTest : public CppUnit::TestFixture { //only interested in propegated values, no local trace/span //usefull if tracemanager.istraceenabled() is false - if (!queryTraceManager().isTracingEnabled()) + if (queryTraceManager().isTracingEnabled()) { //CPPUNIT_SKIP_MESSAGE("Skipping testIDPropegation, tracing is enabled"); - DBGLOG("Skipping testStringArrayPropegatedServerSpan, tracing is not enabled"); + DBGLOG("Skipping testDisabledTracePropegatedValues, tracing is enabled"); return; } @@ -351,11 +347,12 @@ class JlibTraceTest : public CppUnit::TestFixture createMockHTTPHeaders(mockHTTPHeaders, true); Owned serverSpan = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders); - //at this point the serverSpan should have the following context attributes - //remoteParentSpanID, globalID, callerID + //at this point the nootel serverSpan should have the following context attributes + //traceState, globalID, callerID, localID, "traceparent", "tracestate" + //retrieve serverSpan context with the intent to interrogate pass through attributes Owned retrievedSpanCtxAttributes = createProperties(); - bool getSpanCtxSuccess = serverSpan->getSpanContext(retrievedSpanCtxAttributes.get(), false); + bool getSpanCtxSuccess = serverSpan->getSpanContext(retrievedSpanCtxAttributes.get(), false); //false/true shouldn't matter CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected getSpanContext failure detected", true, getSpanCtxSuccess); @@ -364,8 +361,137 @@ class JlibTraceTest : public CppUnit::TestFixture CPPUNIT_ASSERT_MESSAGE("Unexpected CallerID detected", strsame("IncomingCID", retrievedSpanCtxAttributes->queryProp(kCallerIdHttpHeaderName))); - CPPUNIT_ASSERT_MESSAGE("Unexpected Declared Parent SpanID detected", - strsame("4b960b3e4647da3f", retrievedSpanCtxAttributes->queryProp("remoteParentSpanID"))); + CPPUNIT_ASSERT_MESSAGE("Unexpected propogated OTel parenttrace detected", + strsame("00-beca49ca8f3138a2842e5cf21402bfff-4b960b3e4647da3f-01", retrievedSpanCtxAttributes->queryProp("traceparent"))); + + CPPUNIT_ASSERT_MESSAGE("Unexpected propogated OTel tracestate detected", + strsame("hpcc=4b960b3e4647da3f", retrievedSpanCtxAttributes->queryProp("tracestate"))); + } + + void testDisabledTraceNOPropegatedValues() + { + if (queryTraceManager().isTracingEnabled()) + { + //CPPUNIT_SKIP_MESSAGE("Skipping testDisabledTraceNOPropegatedValues, tracing is enabled"); + DBGLOG("Skipping testDisabledTraceNOPropegatedValues, tracing is enabled"); + return; + } + + Owned mockHTTPHeaders = createProperties(); + + Owned passthrough = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders); + + Owned retrievedSpanCtxAttributes = createProperties(); + bool getSpanCtxSuccess = passthrough->getSpanContext(retrievedSpanCtxAttributes.get(), false); //false/true shouldn't matter + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected getSpanContext failure detected", true, getSpanCtxSuccess); + StringBuffer str; + passthrough->toString(str); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected noneempty tostring detected", true, isEmptyString(str.str())); + } + + void testMultiNestedPassthroughSpanTraceOutput() + { + if (queryTraceManager().isTracingEnabled()) + { + //CPPUNIT_SKIP_MESSAGE("Skipping testIDPropegation, tracing is enabled"); + DBGLOG("Skipping testMultiNestedPassthroughSpanTraceOutput, tracing is not disabled"); + return; + } + + Owned mockHTTPHeaders = createProperties(); + createMockHTTPHeaders(mockHTTPHeaders, true); + + Owned serverSpan = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders); + Owned clientSpan = serverSpan->createClientSpan("clientSpan"); + Owned internalSpan = clientSpan->createInternalSpan("internalSpan"); + Owned internalSpan2 = internalSpan->createInternalSpan("internalSpan2"); + + StringBuffer out; + out.set("{"); + internalSpan2->toString(out); + out.append("}"); + { + Owned jtraceAsTree; + try + { + jtraceAsTree.setown(createPTreeFromJSONString(out.str())); + } + catch (IException *e) + { + StringBuffer msg; + msg.append("Unexpected toLog format failure detected: "); + e->errorMessage(msg); + e->Release(); + CPPUNIT_ASSERT_MESSAGE(msg.str(), false); + } + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected toLog format failure detected", true, jtraceAsTree != nullptr); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected missing 'Type' entry in toLog output", true, jtraceAsTree->hasProp("Type")); + } + + out.set("{"); + internalSpan2->toString(out); + out.append("}"); + { + Owned jtraceAsTree; + try + { + jtraceAsTree.setown(createPTreeFromJSONString(out.str())); + } + catch (IException *e) + { + StringBuffer msg; + msg.append("Unexpected toString format failure detected: "); + e->errorMessage(msg); + e->Release(); + CPPUNIT_ASSERT_MESSAGE(msg.str(), false); + } + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected toString format failure detected", true, jtraceAsTree != nullptr); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected missing 'Type' entry in toString output", true, jtraceAsTree->hasProp("Type")); + } + } + + void testDisabledTraceToStringFormat() + { + //only interested in propegated values, no local trace/span + //usefull if tracemanager.istraceenabled() is false + if (queryTraceManager().isTracingEnabled()) + { + //CPPUNIT_SKIP_MESSAGE("Skipping testDisabledTraceToStringFormat, tracing is enabled"); + DBGLOG("Skipping testDisabledTraceToStringFormat, tracing is enabled"); + return; + } + + Owned mockHTTPHeaders = createProperties(); + createMockHTTPHeaders(mockHTTPHeaders, true); + + Owned passThroughSpan = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders); + + StringBuffer passThroughSpanStr; + passThroughSpan->toString(passThroughSpanStr); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Unexpected passthroughspan empty toString", false, isEmptyString(passThroughSpanStr.str())); + + StringBuffer passThroughSpanJSONStr; + passThroughSpanJSONStr.setf("{%s}", passThroughSpanStr.str()); + { + DBGLOG("PassthrughSpan JSON: %s", passThroughSpanJSONStr.str()); + Owned jtraceAsTree; + try + { + jtraceAsTree.setown(createPTreeFromJSONString(passThroughSpanJSONStr.str())); + } + catch (IException *e) + { + StringBuffer msg; + msg.append("Unexpected toString format failure detected: "); + e->errorMessage(msg); + e->Release(); + CPPUNIT_ASSERT_MESSAGE(msg.str(), false); + } + } } void testMultiNestedSpanTraceOutput()