> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send C++ logs, metrics, and traces to Bronto

> Instrument C++ applications with the OpenTelemetry C++ SDK to send logs, metrics, and traces to Bronto over OTLP/HTTP, via an OTel Collector or directly.

One initialisation function sends **logs, metrics, and traces** from your C++ application to Bronto. All three providers share a single `Resource`, so telemetry from the process carries one identity.

The application exports to an [OpenTelemetry Collector](/agent-setup/open-telemetry), which forwards to Bronto. If you don't run a Collector, [export directly to Bronto](#direct-export-to-bronto) instead — only the URL and an API key header change.

<Note>
  C++ has no automatic bridge from a popular logging library (spdlog, glog) in the official SDK, and auto-instrumentation coverage is thinner than in other languages. Log records are emitted through the OTel logs API directly, as shown below. Check the [OpenTelemetry C++ registry](https://opentelemetry.io/ecosystem/registry/?language=cpp) for community bridges and instrumentation.
</Note>

## Prerequisites

* C++14 or later, CMake 3.12 or later
* vcpkg or Conan, or a manual build of `opentelemetry-cpp`
* An OTel Collector reachable from your application, with `logs`, `metrics`, and `traces` pipelines forwarding to Bronto — see [Connect OpenTelemetry Collector to Bronto](/agent-setup/open-telemetry)

## Install dependencies

<CodeGroup>
  ```bash vcpkg theme={"dark"}
  vcpkg install opentelemetry-cpp
  ```

  ```bash Conan theme={"dark"}
  conan install opentelemetry-cpp/1.17.0@ --build=missing
  ```
</CodeGroup>

Link the components for all three signals in your `CMakeLists.txt`:

```cmake CMakeLists.txt theme={"dark"}
find_package(opentelemetry-cpp CONFIG REQUIRED)

target_link_libraries(myapp PRIVATE
    opentelemetry-cpp::logs
    opentelemetry-cpp::metrics
    opentelemetry-cpp::trace
    opentelemetry-cpp::otlp_http_log_record_exporter
    opentelemetry-cpp::otlp_http_metric_exporter
    opentelemetry-cpp::otlp_http_exporter
    opentelemetry-cpp::resources
)
```

## Initialise the SDK

Call `InitOtel()` once at startup, before any span or log emission:

```cpp otel.cpp theme={"dark"}
#include "opentelemetry/exporters/otlp/otlp_http_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_exporter_options.h"
#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_log_record_exporter_options.h"
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_factory.h"
#include "opentelemetry/exporters/otlp/otlp_http_metric_exporter_options.h"
#include "opentelemetry/logs/provider.h"
#include "opentelemetry/metrics/provider.h"
#include "opentelemetry/sdk/logs/logger_provider_factory.h"
#include "opentelemetry/sdk/logs/processor/batch_log_record_processor_factory.h"
#include "opentelemetry/sdk/metrics/meter_provider.h"
#include "opentelemetry/sdk/metrics/export/periodic_exporting_metric_reader_factory.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/trace/batch_span_processor_factory.h"
#include "opentelemetry/sdk/trace/tracer_provider_factory.h"
#include "opentelemetry/trace/provider.h"

namespace logs_api     = opentelemetry::logs;
namespace logs_sdk     = opentelemetry::sdk::logs;
namespace metrics_api  = opentelemetry::metrics;
namespace metrics_sdk  = opentelemetry::sdk::metrics;
namespace otlp         = opentelemetry::exporter::otlp;
namespace resource     = opentelemetry::sdk::resource;
namespace trace_api    = opentelemetry::trace;
namespace trace_sdk    = opentelemetry::sdk::trace;

constexpr const char *kOtlpEndpoint = "http://localhost:4318";

void InitOtel()
{
    auto res = resource::Resource::Create({
        {"service.name",           "my-service"},
        {"service.namespace",      "my-team"},
        {"deployment.environment", "production"},
    });

    // Logs
    otlp::OtlpHttpLogRecordExporterOptions log_opts;
    log_opts.url = std::string(kOtlpEndpoint) + "/v1/logs";
    auto log_provider = logs_sdk::LoggerProviderFactory::Create(
        logs_sdk::BatchLogRecordProcessorFactory::Create(
            otlp::OtlpHttpLogRecordExporterFactory::Create(log_opts), {}),
        res);
    logs_api::Provider::SetLoggerProvider(
        opentelemetry::nostd::shared_ptr<logs_api::LoggerProvider>(log_provider.release()));

    // Metrics
    otlp::OtlpHttpMetricExporterOptions metric_opts;
    metric_opts.url = std::string(kOtlpEndpoint) + "/v1/metrics";
    auto reader = metrics_sdk::PeriodicExportingMetricReaderFactory::Create(
        otlp::OtlpHttpMetricExporterFactory::Create(metric_opts), {});
    auto meter_provider = std::shared_ptr<metrics_sdk::MeterProvider>(
        new metrics_sdk::MeterProvider({}, res));
    meter_provider->AddMetricReader(std::move(reader));
    metrics_api::Provider::SetMeterProvider(
        opentelemetry::nostd::shared_ptr<metrics_api::MeterProvider>(meter_provider));

    // Traces
    otlp::OtlpHttpExporterOptions trace_opts;
    trace_opts.url = std::string(kOtlpEndpoint) + "/v1/traces";
    auto tracer_provider = trace_sdk::TracerProviderFactory::Create(
        trace_sdk::BatchSpanProcessorFactory::Create(
            otlp::OtlpHttpExporterFactory::Create(trace_opts), {}),
        res);
    trace_api::Provider::SetTracerProvider(
        opentelemetry::nostd::shared_ptr<trace_api::TracerProvider>(tracer_provider.release()));
}
```

<Note>
  The metrics factory signatures vary across `opentelemetry-cpp` releases more than the logs and traces ones. If the block above doesn't compile against your build, check the metrics example for your version in the [opentelemetry-cpp repository](https://github.com/open-telemetry/opentelemetry-cpp/tree/main/examples).
</Note>

Two resource attributes determine how Bronto organises your data:

| OTel attribute      | Bronto concept | Description                                  |
| ------------------- | -------------- | -------------------------------------------- |
| `service.name`      | Dataset        | Groups telemetry from one service            |
| `service.namespace` | Collection     | Groups related services or a team's services |

<Note>
  `http://localhost:4318` is the standard OTLP/HTTP address for a Collector on the same host. Use the address reachable from your application if the Collector runs in another container, pod, or host. No authentication is needed between the application and the Collector — the Collector holds the Bronto API key.
</Note>

## Instrument your application

```cpp main.cpp theme={"dark"}
#include "otel.h"

int main()
{
    InitOtel();

    auto logger = logs_api::Provider::GetLoggerProvider()
        ->GetLogger("my-logger", "my-service");
    auto tracer = trace_api::Provider::GetTracerProvider()
        ->GetTracer("my-service");
    auto payments = metrics_api::Provider::GetMeterProvider()
        ->GetMeter("my-service")
        ->CreateUInt64Counter("app.payments");

    {
        auto span  = tracer->StartSpan("process-payment");
        auto scope = tracer->WithActiveSpan(span);

        span->SetAttribute("payment.amount", 99.99);
        payments->Add(1);
        logger->EmitLogRecord(logs_api::Severity::kInfo, "Processing payment");

        span->End();
    }

    // Flush buffered telemetry before exit.
    logs_api::Provider::GetLoggerProvider()->ForceFlush();
    return 0;
}
```

A log record emitted inside an active span picks up that span's `trace_id` and `span_id` from the current context, so you can jump from a log line to its trace in Bronto.

## Verify

Run your application, then check each signal in Bronto, filtering by the `service.name` you set:

* **Logs** — the [Search](https://app.bronto.io/search) page, in the dataset named after your service
* **Metrics** — the Metric Explorer
* **Traces** — the Explore Traces page

If nothing arrives:

* Confirm the Collector is running and reachable at the configured URL, and that its pipelines include an `otlp` receiver and the Bronto exporters.
* Confirm `InitOtel()` runs before the first log or span.
* The batch processors export on a background thread — call `ForceFlush()` before exit so short-lived programs flush.

For signal-specific reference material, see [Send Metrics to Bronto](/metrics/send-metrics) and [Send Traces to Bronto](/tracing/send-traces).

## Direct export to Bronto

Without a Collector, the application exports straight to Bronto over OTLP/HTTP. Point the endpoint at your Bronto region and add your API key to each exporter's options:

```cpp theme={"dark"}
constexpr const char *kOtlpEndpoint = "https://ingestion.eu.bronto.io"; // or ingestion.us.bronto.io

log_opts.url = std::string(kOtlpEndpoint) + "/v1/logs";
log_opts.http_headers.insert({"x-bronto-api-key", "<YOUR_API_KEY>"});
```

Apply the same `http_headers` insert to `metric_opts` and `trace_opts`.

| Region | Base endpoint                    |
| ------ | -------------------------------- |
| EU     | `https://ingestion.eu.bronto.io` |
| US     | `https://ingestion.us.bronto.io` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions.

<Tip>
  Prefer the Collector for multi-service environments — it batches, filters, and enriches telemetry, and keeps the API key out of every application. Direct export suits single services and simple architectures.
</Tip>

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry's [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) define `gen_ai.*` span attributes for model, token usage, and prompt and response content.

### Manual spans

There is no first-party GenAI instrumentation for C++, so set the attributes yourself around each model call:

```cpp theme={"dark"}
auto span  = tracer->StartSpan("chat gpt-4o-mini");
auto scope = tracer->WithActiveSpan(span);

span->SetAttribute("gen_ai.provider.name", "openai");
span->SetAttribute("gen_ai.request.model", "gpt-4o-mini");
span->SetAttribute("gen_ai.usage.input_tokens", 33);
span->SetAttribute("gen_ai.usage.output_tokens", 74);
// Bronto flattens array attributes — this is searchable as finish_reasons.0
span->SetAttribute("gen_ai.response.finish_reasons",
                   std::vector<std::string>{"stop"});

span->End();
```

Message content (`gen_ai.input.messages` / `gen_ai.output.messages`) is opt-in by convention and off by default in the languages that have auto-instrumentation. Since you are setting attributes by hand, apply the same discipline — gate prompt and response content behind your own config flag rather than always sending it.

See the [GenAI span conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md) for the full attribute list.

For the recommended attribute set and Bronto search queries, see [LLM Observability](/ai-features/llm-observability).
