e-volv
Docs menu

Any other language: OpenTelemetry

For languages without a native SDK — Java, .NET, Ruby, PHP, Rust — the honest answer is the one those teams already run: their own OpenTelemetry SDK, pointed at Evolve with a preset. A preset is one file that sets the OTLP/HTTP exporter at Evolve’s ingest, the project key as the Authorization header, gzip, the resource attributes the product reads, and the exception convention that becomes an error group.

The endpoint for every preset is https://api.e-volv.io/api/public/v1/logs/otlp: traces post to …/logs/otlp/v1/traces, logs to …/logs/otlp/v1/logs. The key is the ingest key of the project’s service (minted on the Observer Projects page, shown once) — export it as EVOLVE_LOGS_KEY.

Presets live in the repository under packages/logs-otel-presets/ — copy the one for your language into your service and adjust service.name, deployment.environment and service.release. A conformance script (conformance/check.sh) runs the collector path end to end against a stub ingest.

Java (agent)

Run the OTel Java agent with the preset as its configuration file. The agent records thrown exceptions as span events with the standard exception.* attributes — the ingest maps those to error groups — and the Logback/Log4j2 bridges forward error lines as OTel logs.

java
# the key and resource attributes ride on env vars: properties files do
# not expand ${VAR}, and the environment overrides the file
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer evk_..."
export OTEL_RESOURCE_ATTRIBUTES="service.name=my-service,deployment.environment=production,service.release=$GIT_SHA"
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.javaagent.configuration-file=otel.properties MyApp.jar

# otel.properties (packages/logs-otel-presets/java/otel.properties)
otel.traces.exporter=otlp
otel.logs.exporter=otlp
otel.exporter.otlp.endpoint=https://api.e-volv.io/api/public/v1/logs/otlp
otel.exporter.otlp.protocol=http/protobuf
otel.exporter.otlp.compression=gzip

.NET (auto-instrumentation)

dotnet
set -a; . ./otel.env; set +a   # packages/logs-otel-presets/dotnet/otel.env
EVOLVE_LOGS_KEY=evk_... dotnet run

OTEL_TRACES_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.e-volv.io/api/public/v1/logs/otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_COMPRESSION=gzip
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ${EVOLVE_LOGS_KEY}"
OTEL_RESOURCE_ATTRIBUTES="service.name=my-service,deployment.environment=production,service.release=${GIT_SHA}"

Ruby

ruby
# packages/logs-otel-presets/ruby/preset.rb
OpenTelemetry::SDK.configure do |c|
  c.service_name = ENV.fetch("OTEL_SERVICE_NAME", "my-service")
  c.add_span_processor(
    OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
      OpenTelemetry::Exporter::OTLP::TraceExporter.new(
        endpoint: "https://api.e-volv.io/api/public/v1/logs/otlp/v1/traces",
        headers: { "Authorization" => "Bearer #{ENV.fetch('EVOLVE_LOGS_KEY')}" },
        compression: "gzip"
      )
    )
  )
end

PHP

php
// packages/logs-otel-presets/php/preset.php
$transport = (new OtlpHttpTransportFactory())->create(
    'https://api.e-volv.io/api/public/v1/logs/otlp/v1/traces',
    'application/x-protobuf',
    ['Authorization' => 'Bearer ' . getenv('EVOLVE_LOGS_KEY'),
     'Content-Encoding' => 'gzip'],
);
$tracerProvider = new TracerProvider(
    new BatchSpanProcessor(new SpanExporter($transport)),
    new AlwaysOnSampler(),
    ResourceInfo::create(Attributes::create([
        'service.name' => 'my-service',
        'deployment.environment' => 'production',
        'service.release' => getenv('GIT_SHA') ?: '',
    ])),
);

Rust

rust
// packages/logs-otel-presets/rust/preset.rs
// features = ["http-proto", "reqwest-client", "gzip-http"]
use opentelemetry_otlp::{Compression, WithExportConfig, WithHttpConfig};

let exporter = opentelemetry_otlp::SpanExporter::builder()
    .with_http()
    .with_endpoint(otlp_base) // /v1/traces is appended by the exporter
    .with_headers(HashMap::from([("Authorization".to_string(), format!("Bearer {key}"))]))
    .with_compression(Compression::Gzip)
    .build()?;
let provider = TracerProvider::builder()
    .with_batch_exporter(exporter, opentelemetry_sdk::runtime::Tokio)
    .with_resource(Resource::new(vec![
        KeyValue::new("service.name", "my-service"),
        KeyValue::new("deployment.environment", "production"),
        KeyValue::new("service.release", git_sha),
    ]))
    .build();

Exceptions become error groups

Whatever the language, record exceptions with the SDK’s standard mechanism — record_exception, recordException, span.record_error(...) — so the span event carries exception.type, exception.message and exception.stacktrace. The ingest reads exactly those attributes; within a minute the error appears under an error group on the project’s Errors page, with the trace graph showing which spans it hit.

Everything else: the collector

For anything the presets do not cover — a metrics pipeline, a protocol we do not ingest natively, a service that already exports to a collector — the collector configuration in the OTLP section of the Observer docs (and docs/OBSERVER-OTEL-COLLECTOR.md in the repository) forwards OTLP to the same ingest endpoints. One collector can carry a whole fleet of languages.

Verifying an install

Trigger one error through the instrumented path, then check the project’s Errors and Traces pages within a minute. Nothing appears: confirm the endpoint ends in /api/public/v1/logs/otlp, that the key belongs to this project, and that a 401 or 402 is not being logged by the exporter.

Back to SDK overview.