|
| 1 | +# LaunchDarkly C++ Server SDK - OpenTelemetry Integration Example |
| 2 | + |
| 3 | +This example demonstrates how to integrate the LaunchDarkly C++ Server SDK with OpenTelemetry tracing to automatically enrich your distributed traces with feature flag evaluation data. |
| 4 | + |
| 5 | +## What This Example Shows |
| 6 | + |
| 7 | +- Setting up OpenTelemetry with OTLP HTTP exporter |
| 8 | +- Configuring the LaunchDarkly OpenTelemetry tracing hook |
| 9 | +- Creating HTTP spans with Boost.Beast |
| 10 | +- Automatic feature flag span events in traces |
| 11 | +- Passing explicit parent span context to evaluations |
| 12 | + |
| 13 | +## Prerequisites |
| 14 | + |
| 15 | +- C++17 or later |
| 16 | +- CMake 3.19 or later |
| 17 | +- Boost 1.81 or later |
| 18 | +- LaunchDarkly SDK key |
| 19 | +- OpenTelemetry collector (or compatible backend) running on `localhost:4318` |
| 20 | + |
| 21 | +## Building |
| 22 | + |
| 23 | +From the repository root: |
| 24 | + |
| 25 | +```bash |
| 26 | +mkdir build && cd build |
| 27 | +cmake .. -DLD_BUILD_EXAMPLES=ON -DLD_BUILD_OTEL_SUPPORT=ON |
| 28 | +cmake --build . --target hello-cpp-server-otel |
| 29 | +``` |
| 30 | + |
| 31 | +## Running |
| 32 | + |
| 33 | +### 1. Start an OpenTelemetry Collector |
| 34 | + |
| 35 | +The easiest way is using Docker: |
| 36 | + |
| 37 | +```bash |
| 38 | +docker run -p 4318:4318 otel/opentelemetry-collector:latest |
| 39 | +``` |
| 40 | + |
| 41 | +Or use Jaeger (which has a built-in OTLP receiver): |
| 42 | + |
| 43 | +```bash |
| 44 | +docker run -d -p 16686:16686 -p 4318:4318 jaegertracing/all-in-one:latest |
| 45 | +``` |
| 46 | + |
| 47 | +### 2. Set Your LaunchDarkly SDK Key |
| 48 | + |
| 49 | +Either edit `main.cpp` and set the `SDK_KEY` constant, or use an environment variable: |
| 50 | + |
| 51 | +```bash |
| 52 | +export LD_SDK_KEY=your-sdk-key-here |
| 53 | +``` |
| 54 | + |
| 55 | +### 3. Create a Feature Flag |
| 56 | + |
| 57 | +In your LaunchDarkly dashboard, create a boolean flag named `show-detailed-weather`. |
| 58 | + |
| 59 | +### 4. Run the Example |
| 60 | + |
| 61 | +```bash |
| 62 | +./build/examples/hello-cpp-server-otel/hello-cpp-server-otel |
| 63 | +``` |
| 64 | + |
| 65 | +You should see: |
| 66 | + |
| 67 | +``` |
| 68 | +*** SDK successfully initialized! |
| 69 | +
|
| 70 | +*** Weather server running on http://0.0.0.0:8080 |
| 71 | +*** Try: curl http://localhost:8080/weather |
| 72 | +*** OpenTelemetry tracing enabled (OTLP HTTP to localhost:4318) |
| 73 | +*** LaunchDarkly integration enabled with OpenTelemetry tracing hook |
| 74 | +``` |
| 75 | + |
| 76 | +### 5. Make Requests |
| 77 | + |
| 78 | +```bash |
| 79 | +curl http://localhost:8080/weather |
| 80 | +``` |
| 81 | + |
| 82 | +### 6. View Traces |
| 83 | + |
| 84 | +If using Jaeger, open http://localhost:16686 in your browser. You should see traces with: |
| 85 | + |
| 86 | +- HTTP request spans |
| 87 | +- Feature flag evaluation events with attributes: |
| 88 | + - `feature_flag.key`: "show-detailed-weather" |
| 89 | + - `feature_flag.provider.name`: "LaunchDarkly" |
| 90 | + - `feature_flag.context.id`: Context canonical key |
| 91 | + - `feature_flag.result.value`: The flag value (since `IncludeValue` is enabled) |
| 92 | + |
| 93 | +## How It Works |
| 94 | + |
| 95 | +### OpenTelemetry Setup |
| 96 | + |
| 97 | +```cpp |
| 98 | +void InitTracer() { |
| 99 | + opentelemetry::exporter::otlp::OtlpHttpExporterOptions opts; |
| 100 | + opts.url = "http://localhost:4318/v1/traces"; |
| 101 | + |
| 102 | + auto exporter = opentelemetry::exporter::otlp::OtlpHttpExporterFactory::Create(opts); |
| 103 | + auto processor = trace_sdk::SimpleSpanProcessorFactory::Create(std::move(exporter)); |
| 104 | + std::shared_ptr<trace_api::TracerProvider> provider = |
| 105 | + trace_sdk::TracerProviderFactory::Create(std::move(processor)); |
| 106 | + trace_api::Provider::SetTracerProvider(provider); |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +### LaunchDarkly Hook Setup |
| 111 | + |
| 112 | +```cpp |
| 113 | +auto hook_options = launchdarkly::server_side::integrations::otel::TracingHookOptionsBuilder() |
| 114 | + .IncludeValue(true) // Include flag values in traces |
| 115 | + .CreateSpans(false) // Only create span events, not full spans |
| 116 | + .Build(); |
| 117 | +auto tracing_hook = std::make_shared<launchdarkly::server_side::integrations::otel::TracingHook>(hook_options); |
| 118 | + |
| 119 | +auto config = launchdarkly::server_side::ConfigBuilder(sdk_key) |
| 120 | + .Hooks(tracing_hook) |
| 121 | + .Build(); |
| 122 | +``` |
| 123 | + |
| 124 | +### Passing Parent Span Context |
| 125 | + |
| 126 | +When using async frameworks like Boost.Beast, you need to manually pass the parent span: |
| 127 | + |
| 128 | +```cpp |
| 129 | +auto span = tracer->StartSpan("HTTP GET /weather"); |
| 130 | +auto scope = trace_api::Scope(span); |
| 131 | + |
| 132 | +// Create hook context with the span |
| 133 | +auto hook_ctx = launchdarkly::server_side::integrations::otel::MakeHookContextWithSpan(span); |
| 134 | + |
| 135 | +// Pass it to the evaluation |
| 136 | +auto flag_value = ld_client->BoolVariation(context, "my-flag", false, hook_ctx); |
| 137 | +``` |
| 138 | +
|
| 139 | +This ensures feature flag events appear as children of the correct span. |
| 140 | +
|
| 141 | +## What You'll See |
| 142 | +
|
| 143 | +### In Your Application Logs |
| 144 | +
|
| 145 | +``` |
| 146 | +*** SDK successfully initialized! |
| 147 | + |
| 148 | +*** Weather server running on http://0.0.0.0:8080 |
| 149 | +``` |
| 150 | +
|
| 151 | +### In Your Traces |
| 152 | +
|
| 153 | +Each HTTP request will have: |
| 154 | +1. **Root Span**: "HTTP GET /weather" with HTTP attributes |
| 155 | +2. **Span Event**: "feature_flag" with LaunchDarkly evaluation details |
| 156 | +
|
| 157 | +Example trace structure: |
| 158 | +``` |
| 159 | +HTTP GET /weather (span) |
| 160 | + └─ feature_flag (event) |
| 161 | + ├─ feature_flag.key: "show-detailed-weather" |
| 162 | + ├─ feature_flag.provider.name: "LaunchDarkly" |
| 163 | + ├─ feature_flag.context.id: "user:weather-api-user" |
| 164 | + └─ feature_flag.result.value: "true" |
| 165 | +``` |
| 166 | +
|
| 167 | +## Customization |
| 168 | +
|
| 169 | +### Include/Exclude Flag Values |
| 170 | +
|
| 171 | +For privacy, you can exclude flag values from traces: |
| 172 | +
|
| 173 | +```cpp |
| 174 | +.IncludeValue(false) // Don't include flag values |
| 175 | +``` |
| 176 | + |
| 177 | +### Create Dedicated Spans |
| 178 | + |
| 179 | +For detailed performance tracking: |
| 180 | + |
| 181 | +```cpp |
| 182 | +.CreateSpans(true) // Create a span for each evaluation |
| 183 | +``` |
| 184 | + |
| 185 | +This creates spans like `LDClient.BoolVariation` in addition to the feature_flag event. |
| 186 | + |
| 187 | +### Set Environment ID |
| 188 | + |
| 189 | +To include environment information in traces: |
| 190 | + |
| 191 | +```cpp |
| 192 | +.EnvironmentId("production") |
| 193 | +``` |
| 194 | + |
| 195 | +## Troubleshooting |
| 196 | + |
| 197 | +### No traces appear |
| 198 | + |
| 199 | +1. Verify OpenTelemetry collector is running: `curl http://localhost:4318/v1/traces` |
| 200 | +2. Check the SDK initialized successfully |
| 201 | +3. Ensure you're making requests to the server |
| 202 | + |
| 203 | +### Feature flag events missing |
| 204 | + |
| 205 | +1. Verify the hook is registered before creating the client |
| 206 | +2. Check that you're passing the HookContext when evaluating flags in async contexts |
| 207 | +3. Ensure there's an active span when the evaluation happens |
| 208 | + |
| 209 | +## Architecture |
| 210 | + |
| 211 | +This example uses: |
| 212 | +- **Boost.Beast**: Async HTTP server |
| 213 | +- **OpenTelemetry C++**: Distributed tracing |
| 214 | +- **LaunchDarkly C++ Server SDK**: Feature flags |
| 215 | +- **LaunchDarkly OTel Integration**: Automatic trace enrichment |
| 216 | + |
| 217 | +The integration is non-invasive - the hook automatically captures all flag evaluations without changing your evaluation code. |
0 commit comments