Skip to content
Open
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Increment the:
* [CODE HEALTH] Fix clang-tidy narrowing conversions in baggage
[#3989](https://github.com/open-telemetry/opentelemetry-cpp/pull/3989)

* [EXAMPLE] Add explicit_parent example
[#3935](https://github.com/open-telemetry/opentelemetry-cpp/pull/3935)

Important changes:

* Enable WITH_OTLP_RETRY_PREVIEW by default
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_subdirectory(metrics_simple)
add_subdirectory(multithreaded)
add_subdirectory(multi_processor)
add_subdirectory(environment_carrier)
add_subdirectory(explicit_parent)

if(WITH_EXAMPLES_HTTP)
add_subdirectory(http)
Expand Down
17 changes: 17 additions & 0 deletions examples/explicit_parent/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")

cc_binary(
name = "example_explicit_parent",
srcs = [
"main.cc",
],
tags = ["ostream"],
deps = [
"//api",
"//exporters/ostream:ostream_span_exporter",
"//sdk/src/trace",
],
)
12 changes: 12 additions & 0 deletions examples/explicit_parent/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

add_executable(example_explicit_parent main.cc)
target_link_libraries(
example_explicit_parent PRIVATE opentelemetry-cpp::trace
opentelemetry-cpp::ostream_span_exporter)

if(BUILD_TESTING)
add_test(NAME examples.explicit_parent
COMMAND "$<TARGET_FILE:example_explicit_parent>")
endif()
67 changes: 67 additions & 0 deletions examples/explicit_parent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# OpenTelemetry C++ Explicit Parent Span Example

This example demonstrates how to explicitly set a parent span when the
thread-local active context cannot be relied upon. This is typical in
callback or deferred-execution patterns, where spans are created at
registration time and their children are created later in a different scope
— so `GetCurrent()` cannot know which span should be the parent of a new child.

## Running the example

Build and Deploy this opentelemetry-cpp example as described in [INSTALL.md](../../INSTALL.md).

## Auxiliary function

This example provides an auxiliary function that is able to create child
spans from a given parent, independently of the active span:

- `CreateSpan`: Creating Child Spans for a given explicit parent, or a root
span when no parent is passed.

> It's worth noting that `shared_ptr`'s are used because it helps for keeping
> spans alive and passing them across different lambda functions or execution
> scopes for more complex use cases.

## Example Flow

- This example creates 2 parent spans (`parent_1`, `parent_2`) at registration
time, before any processing begins.

- Each is captured in a lambda and queued for later execution. When invoked,
the captured span is passed explicitly to `ProcessTask`, which creates a
`nested` child span under it.

- The parent spans are kept alive until all tasks have been processed, ensuring
the parent-child relationships are always valid.

## Span Hierarchy

Only 2 traces are generated in this example. Each one contains 2 spans.
The Span hierarchy is shown below:

```text
parent_1 (root)
└─→ nested

parent_2 (root)
└─→ nested
```

## Output

The output will be a set of spans, in which you can check how the relationships
outlined in the previous diagrams are fulfilled. For example:

### Trace 1

| name | parent_span_id | span_id |
| -------- | ---------------- | ---------------- |
| parent_1 | 0000000000000000 | f026e45ec526047e |
| nested | f026e45ec526047e | d701525271ff5d72 |

### Trace 2

| name | parent_span_id | span_id |
| -------- | ---------------- | ---------------- |
| parent_2 | 0000000000000000 | a038787bb5eb6f1b |
| nested | a038787bb5eb6f1b | 529c7df6581d9279 |
130 changes: 130 additions & 0 deletions examples/explicit_parent/main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include <functional>
#include <string>
#include <utility>
#include <vector>

#include "opentelemetry/exporters/ostream/span_exporter_factory.h"
#include "opentelemetry/nostd/shared_ptr.h"
#include "opentelemetry/nostd/string_view.h"
#include "opentelemetry/nostd/variant.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/trace/exporter.h"
#include "opentelemetry/sdk/trace/processor.h"
#include "opentelemetry/sdk/trace/provider.h"
#include "opentelemetry/sdk/trace/simple_processor_factory.h"
#include "opentelemetry/sdk/trace/tracer_provider.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"
#include "opentelemetry/trace/span.h"
#include "opentelemetry/trace/span_context.h"
#include "opentelemetry/trace/span_metadata.h"
#include "opentelemetry/trace/span_startoptions.h"
#include "opentelemetry/trace/tracer.h"
#include "opentelemetry/trace/tracer_provider.h"

namespace trace_api = opentelemetry::trace;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace nostd = opentelemetry::nostd;

namespace
{
void InitTracer()
{
auto exporter = opentelemetry::exporter::trace::OStreamSpanExporterFactory::Create();
auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter));
std::shared_ptr<opentelemetry::trace::TracerProvider> provider =
trace_sdk::TracerProviderFactory::Create(std::move(processor),
opentelemetry::sdk::resource::Resource::Create({}));
// Set the global tracer provider
trace_sdk::Provider::SetTracerProvider(provider);
}

void CleanupTracer()
{
std::shared_ptr<opentelemetry::trace::TracerProvider> none;
trace_sdk::Provider::SetTracerProvider(none);
}

nostd::shared_ptr<trace_api::Tracer> get_tracer()
{
auto provider = trace_api::Provider::GetTracerProvider();
return provider->GetTracer("foo_library");
}

namespace utils
{

// An auxiliary function that returns a child span for a desired arbitrary
// parent (when passed). Otherwise, defaults to root span creation.
// In this example, `name` is also passed from outside the function.

nostd::shared_ptr<trace_api::Span> CreateSpan(
const std::string &name,
const nostd::shared_ptr<trace_api::Span> &parent = nostd::shared_ptr<trace_api::Span>{})
{
trace_api::StartSpanOptions opts;
opts.kind = trace_api::SpanKind::kInternal;
if (parent)
{
opts.parent = parent->GetContext();
}

auto span = get_tracer()->StartSpan(name, opts);
return span;
}

} // namespace utils

namespace app
{

void ProcessTask(const nostd::shared_ptr<trace_api::Span> &parent_span)
{
// Simulating work with nested spans
auto nested = utils::CreateSpan("nested", parent_span);
nested->SetStatus(trace_api::StatusCode::kOk);
nested->End();
}

} // namespace app
} // namespace

int main(int /* argc */, char ** /* argv */)
{
InitTracer();

// In this example, we simulate handling of 2 independent traces that are alive at the same time.
// This may happen for various reasons. For example, 2 different tasks
// are scheduled to run within the same process, and we may want to nest
// spans to each of them, but cannot know which one will be active at runtime.
auto parent_1 = utils::CreateSpan("parent_1");
auto parent_2 = utils::CreateSpan("parent_2");

// To make sure that both the wanted parent-child relationship is met and
// the parent is kept alive, the parent span is passed.
std::vector<std::function<void()>> tasks = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for a follow up PR if interested: One case that isn't well documented or demonstrated in the examples is explicit creation of a root span. This can be helpful/necessary to break up large traces and is called out in the spec here: specification/trace/api.md?plain=1#L388

This example seems like a great place to show it.

 opentelemetry::context::Context context_root{opentelemetry::trace::kIsRootSpanKey, true};
    trace_api::StartSpanOptions options;
    options.parent   = context_root;

[&parent_1] { app::ProcessTask(parent_1); },
[&parent_2] { app::ProcessTask(parent_2); },
};

// Order doesn't matter
for (auto &task : tasks)
{
task();
}

// Both root spans are kept alive till the end, to prove they can both
// outlive the task queue processing without interfering each other
// when we explicitly set parent spans with the aux functions
parent_1->SetStatus(trace_api::StatusCode::kOk);
parent_1->End();

parent_2->SetStatus(trace_api::StatusCode::kOk);
parent_2->End();

CleanupTracer();
return 0;
}
Loading