OpenTelemetry Ingest
Ship logs and metrics from anywhere — Vercel, Lambda, Railway, or any OTel SDK — into a SMLL workspace.
SMLL accepts OTLP/HTTP at a Bearer-authenticated endpoint, so any OpenTelemetry SDK or external provider can stream logs and metrics into your workspace alongside the logs already collected from your services.
How it works
- Create a Telemetry Stream under Workspace → Log Ingest (admin only). Each stream is scoped to one VPC.
- Copy the Bearer token shown once on creation.
- Point your OTel exporter at
https://api.smll.io/otlp/v1/logs(and/otlp/v1/metricsfor metrics) with the token in theAuthorizationheader.
The Go API authenticates by SHA-256 hashing the token and matching it against log_streams.token_hash. It then server-injects the tenancy labels (workspace_id, vpc_id, stream_id, source=otel, plus namespace=vpc-<id[:8]>) so customers cannot spoof them — even with a leaked token, all data is bound to the correct tenant.
OTel attributes (service.name, host.name, trace_id, etc.) are appended as a JSON suffix on each log line so they remain searchable in Loki via the json parser.
Endpoints
| Endpoint | Method | Auth |
|---|---|---|
https://api.smll.io/otlp/v1/logs | POST | Authorization: Bearer <token> |
https://api.smll.io/otlp/v1/metrics | POST | Authorization: Bearer <token> |
Both endpoints accept OTLP/HTTP+protobuf and OTLP/HTTP+JSON. Bodies are capped at 4 MiB per request.
Setup snippets
Environment variables (any SDK)
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.smll.io/otlp/v1/logs
OTEL_EXPORTER_OTLP_LOGS_HEADERS=Authorization=Bearer smll_xxxxxxxx...
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.smll.io/otlp/v1/metrics
OTEL_EXPORTER_OTLP_METRICS_HEADERS=Authorization=Bearer smll_xxxxxxxx...
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobufNode.js
// npm i @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http \
// @opentelemetry/sdk-metrics @opentelemetry/exporter-metrics-otlp-http
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { LoggerProvider, BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
const headers = { Authorization: `Bearer ${process.env.SMLL_TOKEN}` };
// Logs
const logProvider = new LoggerProvider();
logProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(
new OTLPLogExporter({ url: "https://api.smll.io/otlp/v1/logs", headers }),
),
);
// Metrics
const meterProvider = new MeterProvider({
readers: [
new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({ url: "https://api.smll.io/otlp/v1/metrics", headers }),
exportIntervalMillis: 30_000,
}),
],
});Python
# pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.metrics import set_meter_provider
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
import os
headers = {"Authorization": f"Bearer {os.environ['SMLL_TOKEN']}"}
# Logs
log_provider = LoggerProvider()
log_provider.add_log_record_processor(
BatchLogRecordProcessor(
OTLPLogExporter(endpoint="https://api.smll.io/otlp/v1/logs", headers=headers)
)
)
set_logger_provider(log_provider)
# Metrics
meter_provider = MeterProvider(metric_readers=[
PeriodicExportingMetricReader(
OTLPMetricExporter(endpoint="https://api.smll.io/otlp/v1/metrics", headers=headers),
export_interval_millis=30_000,
)
])
set_meter_provider(meter_provider)Go
// go get go.opentelemetry.io/otel/sdk/log
// go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
// go.opentelemetry.io/otel/sdk/metric
// go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
sdklog "go.opentelemetry.io/otel/sdk/log"
)
ctx := context.Background()
exp, _ := otlploghttp.New(ctx,
otlploghttp.WithEndpoint("api.smll.io"),
otlploghttp.WithURLPath("/otlp/v1/logs"),
otlploghttp.WithHeaders(map[string]string{"Authorization": "Bearer " + token}),
)
provider := sdklog.NewLoggerProvider(sdklog.WithProcessor(sdklog.NewBatchProcessor(exp)))
defer provider.Shutdown(ctx)Vercel
Add these to Project → Environment Variables and they'll be picked up by @vercel/otel:
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.smll.io
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer smll_xxxxxxxx...
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufAWS Lambda
Use the AWS Distro for OpenTelemetry layer and set the same environment variables on your function.
Viewing ingested logs
OTLP-ingested logs land in the same /logs UI as scraped pod stdout — set the Source filter to OTel and (optionally) pick a specific stream from the Stream dropdown.
OTLP-ingested metrics flow into the standard Prometheus instance and appear in the workspace Metrics tab once a service.name resource attribute is set.
Pricing
OTel ingest is billed exactly the same as scraped logs and metrics — see Logs Retention and Monitoring.
Rotating tokens
Tokens are shown once. If you lose one, open the stream's detail page and click Rotate token — the previous token stops working immediately and a new one is shown. Always redeploy any workloads using the old token.
Limits
| Limit | Value |
|---|---|
| Body size per request | 4 MiB |
| Per-tenant ingestion rate | 8 MiB/s, 16 MiB burst |
| Per-stream rate | 4 MB/s, 8 MB burst |
| Max active streams per tenant | 5,000 |
| Token format | smll_ + 48 hex chars |
These limits apply per workspace, not per stream — they're enforced at the Loki/Prometheus layer to protect the shared infrastructure.