opentelemetry-cpp icon indicating copy to clipboard operation
opentelemetry-cpp copied to clipboard

Exporting spans from inside DLL on Windows does not work.

Open rsomla1 opened this issue 1 year ago • 4 comments

This issue is specific to Windows environment and using DLL libraries.

I build a simple executable which sets span exporter and creates a span. It also calls a library function that crates another span. Here is the main executable

int main(int argc, const char* argv[])
try
{
  // Configure trace provider that exports to stdout

  using prv_factory = trace_sdk::TracerProviderFactory;
  using prc_factory = trace_sdk::SimpleSpanProcessorFactory;
  using exp_factory = exporter::trace::OStreamSpanExporterFactory;

  trace::Provider::SetTracerProvider(
    std::shared_ptr<trace::TracerProvider>{
      prv_factory::Create(prc_factory::Create(exp_factory::Create(std::cout)))
    }
  );

  // Get provider and create a span

  auto provider = trace::Provider::GetTracerProvider();
  auto tracer = provider->GetTracer("try", "0.0.1");
  auto span = tracer->StartSpan("main");
  trace::Scope scope{tracer->WithActiveSpan(span)};

  // Call library function that creates another span.
  // Expecting that it will use trace provider configured above.

  foo();

  cout << "Done!" << endl;
}
catch(...) {}

Function foo() defined in a DLL library does this

extern "C" EXPORT void foo()
{
  auto provider = trace::Provider::GetTracerProvider();
  auto tracer = provider->GetTracer("lib", "0.0.1");
  auto span = tracer->StartSpan("foo");

  cout << "foo DONE!" << endl;
}

When I build this code on Linux it produces the expected output with two spans, the "foo" span a child of "main" span:

~/cpp/otel-issue/build./try
foo DONE!
{
  name          : foo
  trace_id      : 79194137338009900a5afe676c2c2471
  span_id       : 6f26aecf124938e5
  tracestate    :   parent_span_id: 0d9ec5b22ac67a29
  start         : 1732718683944699646
  duration      : 24848
  description   :   span kind     : Internal
  status        : Unset
  attributes    :   events        :   links         :   resources     :         service.name: unknown_service
        telemetry.sdk.version: 1.17.0
        telemetry.sdk.language: cpp
        telemetry.sdk.name: opentelemetry
  instr-lib     : lib-0.0.1
}
Done!
{
  name          : main
  trace_id      : 79194137338009900a5afe676c2c2471
  span_id       : 0d9ec5b22ac67a29
  tracestate    :   parent_span_id: 0000000000000000
  start         : 1732718683944630175
  duration      : 216489
  description   :   span kind     : Internal
  status        : Unset
  attributes    :   events        :   links         :   resources     :         service.name: unknown_service
        telemetry.sdk.version: 1.17.0
        telemetry.sdk.language: cpp
        telemetry.sdk.name: opentelemetry
  instr-lib     : try-0.0.1
}

However, if I build and run the same code on Windows the output is as follows without the "foo" span:

C:\Work\MySQL\cpp\build-internals>Release\try.exe
foo DONE!
Done!
{
  name          : main
  trace_id      : c265485eac059ec46d9d170a4f004d81
  span_id       : e615d927b958fde0
  tracestate    :
  parent_span_id: 0000000000000000
  start         : 1732718442900722900
  duration      : 694400
  description   :
  span kind     : Internal
  status        : Unset
  attributes    :
  events        :
  links         :
  resources     :
        telemetry.sdk.language: cpp
        telemetry.sdk.name: opentelemetry
        telemetry.sdk.version: 1.17.0
        service.name: unknown_service
  instr-lib     : try-0.0.1
}

As you can see I am testing with otel 1.17.0. My guess is that in case of Windows the connection between tracer provider configured in the main application and the one obtained inside DLL is broken. Therefore the function inside DLL gets the phony provider which ignores all traces sent to it. This is why the span from foo() is not seen in the Windows output.

rsomla1 avatar Nov 28 '24 15:11 rsomla1

CMakeLists.txt I wanted to add a small cmake project that reproduces the issue but the interface does not allow me to attach .cc files. So I paste them here instead.

The try.cc source

#include <iostream>

#ifdef _WIN32
#define IMPORT __declspec(dllimport)
#else
#define IMPORT
#endif

#include <opentelemetry/exporters/ostream/span_exporter_factory.h>
#include <opentelemetry/sdk/trace/processor.h>
#include <opentelemetry/sdk/trace/simple_processor_factory.h>
#include <opentelemetry/sdk/trace/tracer_provider_factory.h>
#include <opentelemetry/trace/provider.h>


using namespace std;
namespace trace      = opentelemetry::trace;
namespace exporter   = opentelemetry::exporter;
namespace trace_sdk  = opentelemetry::sdk::trace;
namespace nostd      = opentelemetry::v1::nostd;


void setup_exporter();
extern "C" IMPORT void foo();


int main(int argc, const char* argv[])
try
{
  // Configure trace provider that exports to stdout

  using prv_factory = trace_sdk::TracerProviderFactory;
  using prc_factory = trace_sdk::SimpleSpanProcessorFactory;
  using exp_factory = exporter::trace::OStreamSpanExporterFactory;

  trace::Provider::SetTracerProvider(
    std::shared_ptr<trace::TracerProvider>{
      prv_factory::Create(prc_factory::Create(exp_factory::Create(std::cout)))
    }
  );

  // Get provider and create a span

  auto provider = trace::Provider::GetTracerProvider();
  auto tracer = provider->GetTracer("try", "0.0.1");
  auto span = tracer->StartSpan("main");
  trace::Scope scope{tracer->WithActiveSpan(span)};

  // Call library function that creates another span.
  // Expecting that it will use trace provider configured above.

  foo();

  cout << "Done!" << endl;
}
catch(const char *e)
{
  cout << "ERROR: " << e << endl;
  exit(1);
}
catch(std::exception &e)
{
  cout << "EXCEPTION: " << e.what() << endl;
  exit(1);
}

The lib.cc source

#include <iostream>

#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT
#endif


#include <opentelemetry/trace/provider.h>

namespace trace      = opentelemetry::trace;
using namespace std;

extern "C" EXPORT void foo()
{
  auto provider = trace::Provider::GetTracerProvider();
  auto tracer = provider->GetTracer("lib", "0.0.1");
  auto span = tracer->StartSpan("foo");

  cout << "foo DONE!" << endl;
}

rsomla1 avatar Nov 28 '24 15:11 rsomla1

A related issue: https://github.com/open-telemetry/opentelemetry-cpp/issues/2534

rsomla1 avatar Nov 28 '24 15:11 rsomla1

I support my own soft fork which I'm using at work, but being soft fork would make it also not acceptable solution by many.

My goal is to have single opinionated .dll and easy to reuse precompiled sdk suitable for our needs. I've tried some years ago the vcpkg/CMake and dll but was not satisifed, hence my soft fork. Also I'm relying on bazel, and almost completely ignoring cmake (I simply understand bazel better than cmake).

So long story short, the version I've got works with your example, but being soft fork also makes it not suitable for your needs - e.g. I won't be able to support it. Given that it works, it's possible that you may have some mis-configuration with CMake and should be something easy to fix.

Here is what I've got - https://github.com/malkia/opentelemetry-cpp/commit/1ef815f30d72b72030490b58b3887f74af574128


Edit: I saw that the request was to make lib a .dll itself - in my example above that's not the case, but added it since. Also added some docs and running depends on it to show what gets imported

https://github.com/malkia/opentelemetry-cpp/blob/main/x/rsomla1/README.md

malkia avatar Dec 02 '24 03:12 malkia

This issue was marked as stale due to lack of activity.

github-actions[bot] avatar Feb 02 '25 02:02 github-actions[bot]