> ## 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 PHP logs and traces to Bronto via OpenTelemetry

> Instrument PHP applications with the OpenTelemetry PHP SDK and auto-instrumentation to send logs and traces to Bronto over OTLP via a Collector.

This page covers instrumenting a PHP application with the OpenTelemetry SDK to send **logs and traces** to Bronto over OTLP/HTTP via a local OTel Collector. Both signals are co-equal: logs are bridged via a Monolog handler — no log statement changes needed — and traces are emitted via a tracer provider.

<Tip>
  If you don't have a Collector and want to export directly from your application to Bronto, see [Direct export to Bronto](#direct-export-to-bronto) at the bottom of this page.
</Tip>

## Prerequisites

* PHP 8.1 or later
* Composer
* A running OTel Collector configured to forward logs and traces to Bronto — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry)
* [OpenTelemetry PHP SDK documentation](https://opentelemetry.io/docs/languages/php/)

## Install dependencies

```bash theme={"dark"}
composer require \
  open-telemetry/api \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  open-telemetry/opentelemetry-logger-monolog \
  monolog/monolog \
  php-http/guzzle7-adapter \
  guzzlehttp/psr7
```

| Package                                       | Purpose                                      |
| --------------------------------------------- | -------------------------------------------- |
| `open-telemetry/api`                          | Core OTel API                                |
| `open-telemetry/sdk`                          | SDK — `LoggerProvider`, processors, resource |
| `open-telemetry/exporter-otlp`                | OTLP/HTTP exporter                           |
| `open-telemetry/opentelemetry-logger-monolog` | Bridges Monolog into OTel                    |
| `php-http/guzzle7-adapter`                    | HTTP client for the exporter                 |

## Configure the log bridge

Set up a `LoggerProvider` with an OTLP exporter and attach the `OtelHandler` to your Monolog logger.

```php configure_logging.php theme={"dark"}
<?php

use Monolog\Logger;
use OpenTelemetry\Contrib\Logs\Monolog\Handler as OtelHandler;
use OpenTelemetry\Contrib\Otlp\LogsExporter;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\Processor\BatchLogRecordProcessor;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SemConv\ResourceAttributes;

function configureOtelLogging(): Logger
{
    $resource = ResourceInfo::create(Attributes::create([
        ResourceAttributes::SERVICE_NAME      => 'my-service',
        ResourceAttributes::SERVICE_NAMESPACE => 'my-team',
        'deployment.environment'              => 'production',
    ]));

    $transport = (new OtlpHttpTransportFactory())->create(
        'http://localhost:4318/v1/logs',
        'application/x-protobuf'
    );

    $exporter = new LogsExporter($transport);

    $loggerProvider = LoggerProvider::builder()
        ->setResource($resource)
        ->addLogRecordProcessor(new BatchLogRecordProcessor($exporter))
        ->build();

    $handler = new OtelHandler($loggerProvider, Logger::DEBUG);

    $logger = new Logger('my-service');
    $logger->pushHandler($handler);
    $logger->pushHandler(new \Monolog\Handler\StreamHandler('php://stdout', Logger::DEBUG));

    return $logger;
}
```

## Configure the OTLP exporter

The `OtlpHttpTransportFactory` in the snippet above connects to the OTel Collector on `localhost:4318`. If your Collector runs on a different host or port, update the URL accordingly.

## Set resource attributes

Resource attributes are attached to every log record exported from this process. Two attributes drive how Bronto organises incoming logs:

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

These are set via `ResourceInfo::create` in the setup above.

## Complete example

```php index.php theme={"dark"}
<?php

require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/configure_logging.php';

$logger = configureOtelLogging();

$logger->info('Application started');
$logger->warning('Low disk space', ['disk_free_gb' => 2.1]);
$logger->error('Database connection failed', ['exception' => 'timeout']);
```

Existing Monolog log statements require no changes. The `OtelHandler` is added alongside any existing handlers (console, file, etc.) so both continue to work.

## Verify delivery

After running your application, check both signals in Bronto:

* **Logs**: open the [Search](https://app.bronto.io/search) page and filter by the dataset name you set in `service.name` — your log records should appear within a few seconds.
* **Traces**: open the Explore Traces page and filter by the same `service.name` — your spans should appear within a few seconds.

If no logs appear, check:

* The OTel Collector is running and reachable at the configured endpoint.
* The Collector's pipeline includes a `logs` pipeline with an `otlp` receiver and the Bronto exporter — see [Connect Open Telemetry to Bronto](/agent-setup/open-telemetry).
* `configureOtelLogging()` is called before the first log statement.

## Traces

<Tip>
  Auto-instrumentation packages are available for Laravel, Symfony, Guzzle, PSR-18 HTTP clients, and more. Install the relevant `open-telemetry/opentelemetry-auto-*` package via Composer and register the instrumentation — no manual span code needed. See [PHP instrumentation libraries](https://opentelemetry.io/docs/languages/php/libraries/) for the full list.
</Tip>

### Configure the tracer provider

No additional packages are needed — `open-telemetry/sdk` and `open-telemetry/exporter-otlp` already include tracing support.

```php configure_tracing.php theme={"dark"}
<?php

use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

function configureOtelTracing(ResourceInfo $resource): TracerProvider
{
    $transport = (new OtlpHttpTransportFactory())->create(
        'http://localhost:4318/v1/traces',
        'application/x-protobuf'
    );

    $spanExporter = new SpanExporter($transport);

    $tracerProvider = TracerProvider::builder()
        ->setResource($resource)
        ->addSpanProcessor(new BatchSpanProcessor($spanExporter))
        ->build();

    \OpenTelemetry\API\Globals::registerInitializer(function (Configurator $configurator) use ($tracerProvider) {
        $configurator->withTracerProvider($tracerProvider);
    });

    return $tracerProvider;
}
```

Pass the same `$resource` used for logging so both signals share `service.name` and `service.namespace`.

### Creating spans

```php theme={"dark"}
$tracer = $tracerProvider->getTracer('my-service');

$span = $tracer->spanBuilder('process-payment')->startSpan();
$scope = $span->activate();
try {
    $span->setAttribute('payment.amount', 99.99);
    $logger->info('Processing payment');  // trace_id and span_id injected automatically
} finally {
    $scope->detach();
    $span->end();
}
```

## GenAI semantic conventions

If your application calls an LLM, OpenTelemetry defines [GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md). PHP contrib has OpenAI client instrumentation, but it currently emits `openai.*` rather than standard `gen_ai.*` fields. For portable GenAI fields, set the attributes yourself around each LLM call:

<Note>
  This page was verified on **July 17, 2026**. GenAI libraries and semantic conventions are evolving rapidly, so package configuration and emitted attributes can change between releases. Keep your instrumentation current and verify the fields emitted by the version you deploy.
</Note>

```php theme={"dark"}
$tracer = $tracerProvider->getTracer('my-service');

$span = $tracer->spanBuilder('chat gpt-4o-mini')->startSpan();
$scope = $span->activate();
try {
    $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);
    $span->setAttribute('gen_ai.response.finish_reasons', ['stop']);
} finally {
    $scope->detach();
    $span->end();
}
```

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

See [LLM Observability](/ai-features/llm-observability) for the full recommended attribute set and how to search and aggregate GenAI fields in Bronto.

## Direct export to Bronto

If you are not using an OTel Collector, export directly to Bronto by replacing the transport configurations with the Bronto OTLP endpoints and your API key:

```php theme={"dark"}
// Logs
$logTransport = (new OtlpHttpTransportFactory())->create(
    'https://ingestion.eu.bronto.io/v1/logs', // or ingestion.us.bronto.io
    'application/x-protobuf',
    ['x-bronto-api-key' => '<YOUR_API_KEY>']
);

// Traces
$traceTransport = (new OtlpHttpTransportFactory())->create(
    'https://ingestion.eu.bronto.io/v1/traces', // or ingestion.us.bronto.io
    'application/x-protobuf',
    ['x-bronto-api-key' => '<YOUR_API_KEY>']
);
```

| Region | Logs endpoint                            | Traces endpoint                            |
| ------ | ---------------------------------------- | ------------------------------------------ |
| EU     | `https://ingestion.eu.bronto.io/v1/logs` | `https://ingestion.eu.bronto.io/v1/traces` |
| US     | `https://ingestion.us.bronto.io/v1/logs` | `https://ingestion.us.bronto.io/v1/traces` |

See [API Keys](/Account-Management/API-Keys) for how to create a key with ingestion permissions. No other changes to the rest of the setup are required.
