Skip to content

Commit

Permalink
HPCC-30405 CPasstrhough span Implementation
Browse files Browse the repository at this point in the history
- factors CNoopSpan
- Adds cppunit skip directives
- Adds passthrough cppunit
- Ensures passthrough valus logged

Signed-off-by: Rodrigo Pastrana <[email protected]>
  • Loading branch information
rpastrana committed Oct 12, 2023
1 parent 606b71c commit cf01f09
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 36 deletions.
136 changes: 114 additions & 22 deletions system/jlib/jtrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,38 +535,128 @@ class CSpan : public CInterfaceOf<ISpan>
CSpan * localParentSpan = nullptr;
};

static Owned<ISpan> noopSpan;
class CNoopSpan : public CInterfaceOf<ISpan>
class CPassThroughSpan : public CInterfaceOf<ISpan> //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<IProperties> retrievedSpanCtxAttributes = createProperties();
bool getSpanCtxSuccess = getSpanContext(retrievedSpanCtxAttributes.get(), true);

return noopSpan.getLink();
if (!getSpanCtxSuccess)
return;
{
Owned<IPropertyIterator> 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
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -952,21 +1042,23 @@ class CTraceManager : implements ITraceManager, public CInterface

ISpan * createServerSpan(const char * name, StringArray & httpHeaders, SpanFlags flags) override
{
Owned<IProperties> headerProperties = getHeadersAsProperties(httpHeaders);
if (isTracingEnabled())
{
Owned<IProperties> 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
{
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
Expand Down
154 changes: 140 additions & 14 deletions testing/unittests/jlibtests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<IProperties> mockHTTPHeaders = createProperties();
createMockHTTPHeaders(mockHTTPHeaders, false);
Owned<ISpan> serverSpan = queryTraceManager().createServerSpan("invalidPropegatedServerSpan", mockHTTPHeaders);
Expand All @@ -340,22 +336,23 @@ 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;
}

Owned<IProperties> mockHTTPHeaders = createProperties();
createMockHTTPHeaders(mockHTTPHeaders, true);

Owned<ISpan> 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<IProperties> 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);

Expand All @@ -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<IProperties> mockHTTPHeaders = createProperties();

Owned<ISpan> passthrough = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders);

Owned<IProperties> 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<IProperties> mockHTTPHeaders = createProperties();
createMockHTTPHeaders(mockHTTPHeaders, true);

Owned<ISpan> serverSpan = queryTraceManager().createServerSpan("propegatedServerSpan", mockHTTPHeaders);
Owned<ISpan> clientSpan = serverSpan->createClientSpan("clientSpan");
Owned<ISpan> internalSpan = clientSpan->createInternalSpan("internalSpan");
Owned<ISpan> internalSpan2 = internalSpan->createInternalSpan("internalSpan2");

StringBuffer out;
out.set("{");
internalSpan2->toString(out);
out.append("}");
{
Owned<IPropertyTree> 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<IPropertyTree> 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<IProperties> mockHTTPHeaders = createProperties();
createMockHTTPHeaders(mockHTTPHeaders, true);

Owned<ISpan> 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<IPropertyTree> 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()
Expand Down

0 comments on commit cf01f09

Please sign in to comment.