Ir para o conteúdo
decodecodeveloper docs
Storefront → Blocks → Monitoring

Telemetry

Send errors, metrics and traces from your servers to your own OpenTelemetry collector or to the hosted Deco CMS, with switches and sample rates kept in the CMS settings block.

Esta página ainda não foi traduzida para o português — o conteúdo abaixo está em inglês.

Your search provider gets slow on Black Friday, and shelves start timing out. You want upstream latency and error rates in your own Grafana, or somewhere you can look at them without running a collector at all.

Deco CMS measures what it sees, resolving content and calling APIs, and sends those measurements as OpenTelemetry to wherever you point it. It's open source, and it works with any collector. This page shows how to choose where telemetry goes, what's sent, and how editors and code share control of how much.

Choose where telemetry goes

Pass telemetry to createCMS. To send to your own collector, give it an OTLP/HTTP endpoint:

cms.ts
import { createCMS } from "@decocms/blocks";
import blocks from "./.deco";
import content from "./.deco/blocks.gen";
 
export const cms = createCMS({
  blocks,
  content,
  telemetry: {
    endpoint: process.env.OTLP_ENDPOINT!,                            // e.g. https://otel.example.com
    headers: { authorization: `Bearer ${process.env.OTLP_TOKEN}` },  // optional
  },
});

That works with the OpenTelemetry Collector, Grafana, Honeycomb, or anything else that accepts OTLP over HTTP.

How the destination is chosen:

  • telemetry: false sends nothing.
  • telemetry: { … } sends there.
  • Without telemetry, the CMS uses OTEL_EXPORTER_OTLP_ENDPOINT (and OTEL_EXPORTER_OTLP_HEADERS) if they're set. Otherwise nothing is sent: in development, in tests, and anywhere you haven't configured it.

The top-level site and token options load hosted releases and drafts. They never turn telemetry on by themselves: telemetry goes only where telemetry (or the environment) points.

What's sent

MeasurementLabelsReported by
Upstream latency: each request to a third-party API, timed until its response headers arrive (http.client.request.duration)provider, operation, status_class (2xx, 4xx, 5xx, error), cached, retriesThe instrumented fetch, createInstrumentedFetch, which every upstream client uses
Error logserror code, block type, providerThe SDK
Traces (off by default)block resolution and upstream spansThe SDK, at traceSampleRate

That's everything the SDK measures. The requests your site serves and its page cache belong to your web framework, so measure them with its OpenTelemetry setup (Next.js has instrumentation.ts; on Cloudflare Workers, turn on Workers observability). Point it at the same collector and both show up side by side.

A retried request counts once, with the number of retries as the retries label, so retries don't inflate request counts. The cached label tells you whether a response came from a cache, so hit rates show up next to latency. Page views from the browser aren't telemetry: they're analytics, a separate feature with its own section in the same settings block.

Telemetry settings are content

How much is sent is a setting editors can see and change. It lives in the telemetry section of your site's CMS settings: the saved block named CMS, whose type is the built-in cms-settings, so it's always in the schema and the site editor shows it as a form under Settings:

.deco/blocks/CMS.json
{
  "__resolveType": "cms-settings",
  "telemetry": {
    "enabled": true,
    "metrics": true,
    "errorSampleRate": 0.05,
    "traceSampleRate": 0
  }
}
FieldDefaultWhat it does
enabledtrueSwitches all telemetry off when false.
metricstrueSends upstream metrics.
errorSampleRate0.05The share of error logs sent (5%).
traceSampleRate0The share of requests traced.

The block and the section are optional: without them, the defaults above apply. deco content doesn't create the block; save it from the site editor, or add the file, when you want to change something. Switching telemetry off is a commit to this file, made in the site editor or by hand, and it ships like any other content. The destination and its credentials never go here: content is edited in the site editor and shipped in releases, so secrets stay in code.

Telemetry follows the release your servers serve. A new release with a changed telemetry section takes effect when the server picks it up, and a draft never changes what's sent, even while someone previews it. A section with variants is read outside any request, so pick between them with date rules, not request rules.

Sampling

Telemetry is sampled and aggregated, so its cost stays flat as traffic grows:

  • Metrics are aggregated in memory, per server, and sent in batches: counts, sums and latency buckets per combination of labels, not one record per request.
  • Error logs are sampled at errorSampleRate, so a burst of identical errors sends a few examples, not thousands.
  • Sending never delays a response. Batches go out in the background, and a collector that's down only loses that batch (see How telemetry is sent).

Code caps what content can raise. The sample rates in the telemetry section are limited by limits on the telemetry option, so an editor can't increase what leaves your servers for a third party:

telemetry: {
  endpoint: process.env.OTLP_ENDPOINT!,
  limits: { errorSampleRate: 0.1, traceSampleRate: 0 },   // the defaults
},

With these defaults, content can send at most 10% of error logs, and traces stay off until code raises traceSampleRate.

Privacy

  • Telemetry never carries request or response bodies, tokens, cookies or authorization headers. The SDK scrubs them before sending, so a third-party endpoint gets the same guarantee.
  • Requests are labeled by route pattern (/products/:slug), never by the raw URL.
  • Error logs are sampled and structured: an error's code, message and where it happened.

Your own tracing

The telemetry option covers the measurements Deco CMS takes. To trace your own code with OpenTelemetry, wrap each function in your block map in a span named after its block type:

.deco/index.ts
import { trace } from "@opentelemetry/api";
import type { Blocks } from "@decocms/blocks";
import { PromoBanner } from "../src/promo-banner";
 
const tracer = trace.getTracer("my-store");
 
// Runs a block function inside a span named after its block type.
function traced<P, R>(type: string, fn: (props: P) => R) {
  return (props: P) =>
    tracer.startActiveSpan(type, async (span) => {
      try {
        return await fn(props);
      } catch (error) {
        span.recordException(error as Error);
        throw error;
      } finally {
        span.end();
      }
    });
}
 
export default {
  "promo-banner": traced("promo-banner", PromoBanner),
} satisfies Blocks;

The wrapped function keeps its props type, so its form doesn't change, and it returns a Promise, which fits the same fields (return types are awaited). Make each request span the parent of these block spans and of your outbound HTTP spans, and add the served revision from client.revision() as an attribute. Keep credentials and customer data out of attributes, and export asynchronously, so a collector that's down never blocks rendering.

Troubleshooting

SymptomWhat to check
Nothing reaches the collectorCheck that telemetry is set (or OTEL_EXPORTER_OTLP_ENDPOINT), and that the telemetry section of the CMS block, if you have one, isn't enabled: false. Metrics are sent in batches, so expect a short delay.
Only some errors arriveError logs are sampled at errorSampleRate, capped by limits.
Upstream calls missingOnly requests made through createInstrumentedFetch are measured; check that your client uses it (see Calling APIs).
Prometheus shows odd countersMetrics use delta temporality; see Metrics.
Telemetry sent while only loading releasesTop-level site and token don't send telemetry. Look for telemetry or OTEL_EXPORTER_OTLP_ENDPOINT in your config and environment.

How batches are encoded and sent is under the hood.