diff --git a/.vitepress/config/constants.ts b/.vitepress/config/constants.ts index 7e88929..4299d4e 100644 --- a/.vitepress/config/constants.ts +++ b/.vitepress/config/constants.ts @@ -9,6 +9,7 @@ export const navItemOrder: Record = { "auth", "application", "events", + "opentelemetry", ], tutorials: [ "app-endpoint", @@ -86,6 +87,18 @@ export const defaultSidebarOrder: string[] = ["overview", "quickstart"]; // Section-specific overrides (only if you need different ordering for a specific section) export const sidebarItemOrder: Record = { + guides: [ + "application", + "events", + "resolver", + "secretmanager", + "static-website-hosting", + "opentelemetry", + "tailordb", + "executor", + "function", + "auth", + ], "setup-auth": [ "overview", "setup-identity-provider", diff --git a/docs/guides/index.md b/docs/guides/index.md index 3cf3aa8..1af860b 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -12,6 +12,7 @@ Comprehensive guides for building applications on Tailor Platform. - [Resolver](resolver.md) - Build custom GraphQL resolvers with business logic and data transformations - [Events](events.md) - Event types and payloads for building dynamic workflows and automations - [Secret Manager](secretmanager.md) - Securely manage API keys, tokens, and credentials with versioning +- [Observability / OpenTelemetry](opentelemetry.md) - Forward platform-generated traces and logs to external backends like New Relic, Honeycomb, or Datadog - [Static Website Hosting](static-website-hosting.md) - Deploy single-page applications with CDN caching and access control ## Service-Specific Guides diff --git a/docs/guides/opentelemetry.md b/docs/guides/opentelemetry.md new file mode 100644 index 0000000..d7f352d --- /dev/null +++ b/docs/guides/opentelemetry.md @@ -0,0 +1,253 @@ +--- +doc_type: guide +title: Observability / OpenTelemetry +--- + +# Observability / OpenTelemetry + +Tailor Platform automatically generates [OpenTelemetry](https://github.com/open-telemetry/opentelemetry-specification) signals — **traces** and **logs** — for the services in your workspace. By configuring a **telemetry export**, you can forward those signals to the observability backend of your choice, such as New Relic, Honeycomb, Grafana, or Datadog, without changing any application code. + +Common use cases include: + +- Sending request traces from your platform services to an existing distributed tracing backend +- Centralizing platform logs alongside your application logs +- Enriching all outgoing signals with a consistent service name and deployment environment + +## How telemetry export works + +The platform collects the OpenTelemetry signals your services emit and forwards them, over OTLP (the OpenTelemetry Protocol), to each **telemetry export** you have configured. A telemetry export is the resource *you* define — it points at one backend and decides which signals that backend receives. The internal platform component that performs this routing is **TelemetryRouter**; you don't configure it directly, but it is the source of the `tailor_telemetryrouter_` prefix on the resources below. + +```mermaid +flowchart TD + A[Your workspace services
emit OpenTelemetry signals] -->|traces / logs| B[TelemetryRouter
routes your workspace's signals] + B -->|OTLP| C[telemetry export
New Relic] + B -->|OTLP| D[telemetry export
Honeycomb] + B -->|OTLP| E[telemetry export
...] +``` + +You configure telemetry export through the Tailor Terraform provider. There are two resources: + +- `tailor_telemetryrouter_telemetry_export` — defines an export destination. A workspace may have many; each is given a `name` that is unique within that workspace. +- `tailor_telemetryrouter_resource_attributes_config` — defines workspace-level resource attributes attached to every outgoing signal. A workspace has at most one. + +A few things to keep in mind: + +- **Traces and logs can be enabled independently** per export, so each destination receives only the signals you choose. +- **Authentication credentials are never stored inline** — they are referenced from [Secret Manager](secretmanager.md) and resolved at send time. + +::: warning Metrics +Metric signals are not part of telemetry export today. While the `enable_metrics` field exists, the platform does not currently emit metric signals, so enabling it has no practical effect. Configure your destinations for traces and logs. +::: + +## Configuring a telemetry export + +A telemetry export defines a single destination. Most OTLP-compatible backends can be targeted **directly**, with no additional infrastructure on your side. + +The following example forwards traces and logs to [New Relic](https://github.com/newrelic), authenticating with a license key stored in Secret Manager: + +```hcl {{ title: 'telemetry_export.tf' }} +resource "tailor_telemetryrouter_telemetry_export" "newrelic" { + workspace_id = tailor_workspace.example.id + + name = "newrelic" + endpoint = "https://otlp.nr-data.net" + protocol = "http" + + auth = { + api_key = { + header_name = "api-key" + secret_value = { + vault_name = tailor_secretmanager_vault.example.name + secret_name = "newrelic-license-key" + } + } + } + + enable_traces = true + enable_logs = true +} +``` + +For more backends — including ones that cannot ingest OTLP directly, such as Datadog — see [Backend examples](#backend-examples). + +For the full list of arguments and their constraints, refer to the [Tailor Platform Provider documentation](https://registry.terraform.io/providers/tailor-platform/tailor/latest/docs/resources/telemetryrouter_telemetry_export). + +### Authentication + +The optional `auth` block configures how Tailor authenticates to the destination. Set **exactly one** of the following. + +**API key** — the secret value is sent in the header you name: + +```hcl {{ title: 'auth_api_key.tf' }} +auth = { + api_key = { + header_name = "x-api-key" + secret_value = { + vault_name = tailor_secretmanager_vault.example.name + secret_name = "backend-api-key" + } + } +} +``` + +**Bearer token** — the secret value is sent as an `Authorization: Bearer ` header: + +```hcl {{ title: 'auth_bearer.tf' }} +auth = { + bearer_token = { + secret_value = { + vault_name = tailor_secretmanager_vault.example.name + secret_name = "backend-token" + } + } +} +``` + +In both cases `secret_value` references a secret in [Secret Manager](secretmanager.md): + +- `vault_name` — the name of the vault that stores the secret +- `secret_name` — the key of the secret within the vault + +## Endpoint format + +Use a TLS-enabled endpoint (HTTPS) so credentials and signals are encrypted in transit; plaintext endpoints are not recommended, and a plaintext gRPC target fails to connect. The required `endpoint` format depends on the `protocol`: + +- **`http`** — an HTTPS base URL, for example `https://otlp.example.com`. Do **not** include the OTLP signal path (`/v1/traces`, `/v1/logs`); it is appended automatically for each enabled signal. Avoid query strings and fragments. +- **`grpc`** — either a bare `host:port` (include the port, for example `otlp.example.com:4317`) or an `https://host[:port]` URL (the port defaults to `443` when omitted). Do not include a path, query string, or fragment. + +Endpoints that resolve to a private or internal host (for example RFC 1918 ranges or loopback addresses) are rejected by default. + +| Protocol | Recommended | Avoid | +| --- | --- | --- | +| `http` | `https://otlp.example.com` | `https://otlp.example.com/v1/traces` (signal path), `http://otlp.example.com` (not TLS) | +| `grpc` | `otlp.example.com:4317`, `https://otlp.example.com` | `otlp.example.com` (no port), `https://otlp.example.com/path` (has path) | + +## Backend examples + +### New Relic + +New Relic ingests OTLP directly. Point the export at its OTLP endpoint and authenticate with a license key sent in the `api-key` header — see the [example above](#configuring-a-telemetry-export). Use the OTLP endpoint that matches your New Relic account's region (for example `https://otlp.nr-data.net` for a US account); see New Relic's documentation for the endpoint of your region. + +### Honeycomb + +[Honeycomb](https://github.com/honeycombio) also ingests OTLP directly. Authenticate with an API key sent in the `x-honeycomb-team` header: + +```hcl {{ title: 'honeycomb_export.tf' }} +resource "tailor_telemetryrouter_telemetry_export" "honeycomb" { + workspace_id = tailor_workspace.example.id + + name = "honeycomb" + endpoint = "https://api.honeycomb.io" + protocol = "http" + + auth = { + api_key = { + header_name = "x-honeycomb-team" + secret_value = { + vault_name = tailor_secretmanager_vault.example.name + secret_name = "honeycomb-api-key" + } + } + } + + enable_traces = true + enable_logs = true +} +``` + +Use the endpoint that matches your Honeycomb account's region (for example `https://api.honeycomb.io` for a US account); see Honeycomb's documentation for the endpoint of your region. + +### Routing through an OpenTelemetry Collector (Datadog) + +Datadog provides a [direct OTLP intake endpoint](https://docs.datadoghq.com/opentelemetry/setup/otlp_ingest/), but it comes with constraints — trace intake is in preview and requires requesting access, and some behavior differs from the Agent/Collector path. For a full-featured setup, a common approach is to run your own [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) with the Datadog exporter: point your Tailor telemetry export at the collector and let it forward signals to Datadog. + +This collector pattern applies to any backend you'd rather reach through a collector — for processing, fan-out, or vendor-specific exporters — not just Datadog. + +```mermaid +flowchart LR + A[Tailor Platform
telemetry export] -->|OTLP| B[Your OpenTelemetry
Collector] + B -->|datadog exporter| C[Datadog] +``` + +#### 1. Configure your OpenTelemetry Collector + +Run an OpenTelemetry Collector that receives OTLP from Tailor and exports to Datadog. A minimal configuration: + +```yaml {{ title: 'otel-collector.yaml' }} +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + +processors: + batch: {} + +exporters: + datadog: + api: + key: ${env:DD_API_KEY} + site: datadoghq.com + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [datadog] + logs: + receivers: [otlp] + processors: [batch] + exporters: [datadog] +``` + +This collector must be reachable from Tailor over TLS at a publicly resolvable address. Terminate TLS in front of the collector (for example with a load balancer) and expose its OTLP gRPC endpoint. + +#### 2. Point the telemetry export at your collector + +Configure the export `endpoint` to your collector's OTLP endpoint rather than Datadog directly: + +```hcl {{ title: 'datadog_export.tf' }} +resource "tailor_telemetryrouter_telemetry_export" "datadog" { + workspace_id = tailor_workspace.example.id + + name = "datadog-collector" + endpoint = "otel-collector.example.com:4317" + protocol = "grpc" + + enable_traces = true + enable_logs = true +} +``` + +If your collector requires authentication, add an `auth` block as described in [Authentication](#authentication) so the credentials stay in Secret Manager. (The `headers` map can carry static, non-secret headers, but its values are persisted in Terraform state, so don't put credentials there.) Because the endpoint points at your own collector, make sure it is not a private or internal host — see [Endpoint format](#endpoint-format). + +## Configuring resource attributes + +The `tailor_telemetryrouter_resource_attributes_config` resource defines workspace-level resource attributes that are attached to every outgoing signal. It is a singleton: a workspace has at most one resource attributes configuration. + +```hcl {{ title: 'resource_attributes.tf' }} +resource "tailor_telemetryrouter_resource_attributes_config" "this" { + workspace_id = tailor_workspace.example.id + + service_name_prefix = "acme" + deployment_environment_name = "production" +} +``` + +`service_name_prefix` is prepended to each microservice's own `service.name` to form the final `service.name` attribute on outgoing signals (`-`), making platform signals easy to identify in your backend. `deployment_environment_name` follows the OpenTelemetry semantic convention `deployment.environment.name`. + +For the full list of arguments and their constraints, refer to the [Tailor Platform Provider documentation](https://registry.terraform.io/providers/tailor-platform/tailor/latest/docs/resources/telemetryrouter_resource_attributes_config). + +## Security considerations + +- Prefer the `auth` block for credentials: it references secrets in [Secret Manager](secretmanager.md), so the secret values themselves are never stored in the export configuration or in Terraform state. +- The `headers` map is marked sensitive (redacted from Terraform plan and diff output), but its values are still persisted in Terraform state. Use it only for non-secret, static headers — put credentials in the `auth` block instead. +- Use TLS-enabled (HTTPS) endpoints so credentials and signals are encrypted in transit. Endpoints that resolve to a private or internal host are rejected. + +## Best practices + +1. **Separate environments**: Use distinct exports (and Secret Manager vaults) per environment, and set `deployment_environment_name` accordingly. +2. **Enable only the signals you need**: Leave `enable_traces` / `enable_logs` off for destinations that should not receive a given signal. +3. **Use a consistent service name prefix**: Set `service_name_prefix` so platform signals are easy to identify in your backend. +4. **Rotate credentials**: Update the referenced secret values when rotating API keys or tokens. diff --git a/schema.yml b/schema.yml index dd37df7..a00faa3 100644 --- a/schema.yml +++ b/schema.yml @@ -16,6 +16,7 @@ links: - registry.terraform.io - en.wikipedia.org - vitest.dev + - docs.datadoghq.com - unicode.org - ibm.com - iana.org