Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(async)

if(WITH_EXAMPLES_HTTP)
add_subdirectory(http)
Expand Down
17 changes: 17 additions & 0 deletions examples/async/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_async",
srcs = [
"main.cc",
],
tags = ["ostream"],
deps = [
"//api",
"//exporters/ostream:ostream_span_exporter",
"//sdk/src/trace",
],
)
11 changes: 11 additions & 0 deletions examples/async/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

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

if(BUILD_TESTING)
add_test(NAME examples.async COMMAND "$<TARGET_FILE:example_async>")
endif()
177 changes: 177 additions & 0 deletions examples/async/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# OpenTelemetry C++ ASYNC Example

This is a simple example that demonstrates tracing async requests between
generic clients and servers. It simulates context injections and extractions
and shows how to explicitly set parent span_id's in different situations, when
the scope is not available, or different threads are run in parallel.

The proposed pattern may be used for any kind of asynchronous client/server
communication.

## Running the example

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

## Example Flow

* This example creates 2 asynchronous requests from a theoretical client. The
requests have distributed tracing context `injected` into a map, simulating
headers.

* These requests then arrive to a server, which `extracts` the span information
and creates child spans sharing the same trace id's with the client request,
and marking it as parent. After that, other spans are `nested`, simulating
server work.

* Answers contain again the context `injected`, and the client extracts them
without more context than the headers arriving from the

* The Parent spans that originated the only 2 `trace-id`'s of this example are
kept alive during the whole example, and ended at the end.

```text
[Client] [Server]

request1 span ──────────→ server span
└─→ nested span
request2 span ──────────→ server span
└─→ nested
←────────── reply
process_answer
←────────── reply
process_answer
```

## Span Hierarchy

Only 2 traces are generated in this example. Each one contains 4 spans in
total, created across both a server and a client. The Span hierarchy is
shown below:

```text
request1 (trace: 29656cc8..., in client)
└─→ server (in server)
├─→ nested (in server)
└─→ answer (in client)

request2 (trace: 24a0afe3..., in client)
└─→ server
├─→ nested (in server)
└─→ answer (in client)
```

## Auxiliary functions

This example provides a small set of auxiliary functions that are able to
create child spans from a previous one, independently on the active span.

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. These functions are:

### Creating Child Spans

An auxiliary function that returns a child span for a desired arbitrary parent.
In this example, the `SpanKind` and `name` are also passed from outside the
function.

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

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

### Creating Child Spans from incoming requests

An auxiliary function that returns a child span from incoming headers that
contain tracing information. In this example, the `SpanKind` and `name` are
also passed from outside the function.

```cpp
nostd::shared_ptr<trace_api::Span> create_child_span_from_remote(header_map &headers,
const std::string &name,
trace_api::SpanKind kind)
{
HttpTextMapCarrier carrier(headers);
auto current_ctx = ctx::RuntimeContext::GetCurrent();
auto new_context = ctx::propagation::GlobalTextMapPropagator::GetGlobalPropagator()->Extract(
carrier, current_ctx);
auto remote_span = opentelemetry::trace::GetSpan(new_context);

return create_child_span(name, remote_span, kind);
}

```

### Injecting arbitrary spans into carriers (i.e. headers)

An auxiliary function that `injects` the tracing information to a given carrier
structure. In this example a simple implementation of the `TextMapCarrier` as
an `std::map<std::string, std::string>` was used.

It's important to note that the `trace_api::SetSpan(current_ctx, span)`
function is needed for the injection to explicitly use the desired span
context.

```cpp
void inject_trace_context(const nostd::shared_ptr<trace_api::Span> &span, header_map &headers)
{
if (!span)
{
return;
}

// First, we set the Span into the context explicitly
auto current_ctx = ctx::RuntimeContext::GetCurrent();
auto ctx_with_span = trace_api::SetSpan(current_ctx, span);

// Then we inject the span info into the headers
HttpTextMapCarrier carrier(headers);
ctx::propagation::GlobalTextMapPropagator::GetGlobalPropagator()->Inject(carrier, ctx_with_span);
}
```

The previous examples make use of the following aliases:

```cpp
namespace trace_api = opentelemetry::trace;
namespace trace_sdk = opentelemetry::sdk::trace;
namespace nostd = opentelemetry::nostd;
namespace ctx = opentelemetry::context;
using header_map = std::map<std::string, std::string>;
```

## 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 `29656cc8079bff4fe30bdb96b0f24bef`

| name | parent_span_id | span_id | events |
|------|----------------|---------|--------|
| request1 | 0000000000000000 | f026e45ec526047e | _(none)_ |
| server | f026e45ec526047e | d701525271ff5d72 | Processing in server, Replying answer |
| nested | d701525271ff5d72 | c6aaee683b544ec3 | Nested did some work |
| process_answer | d701525271ff5d72 | fedcd1e2a22a1916 | Answer processed |

### Trace 2 `24a0afe30c007794d43dac03c4b7c956`

| name | parent_span_id | span_id | events |
|------|----------------|---------|--------|
| request2 | 0000000000000000 | a038787bb5eb6f1b | _(none)_ |
| server | a038787bb5eb6f1b | 529c7df6581d9279 | Processing in server, Replying answer |
| nested | 529c7df6581d9279 | d4946bb9738ae08b | Nested did some work |
| process_answer | 529c7df6581d9279 | 2b396d7d207addca | Answer processed |
Loading
Loading