Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.sedata-ai.tech/llms.txt

Use this file to discover all available pages before exploring further.

A data processor is a pure function (data) => data that runs on every attribute object the package emits. They’re the right place to redact, hash, allow-list, or enrich attributes site-wide.

Signature

type DataProcessor = (data: Record<string, any>) => Record<string, any>
You configure them on TelemetryConfig.dataProcessors:
const config: TelemetryConfig = {
  // ...
  dataProcessors: [
    addEnv,
    redactPII,
    hashUserId,
  ],
}
Processors run in order. Each receives the output of the previous one.

What gets processed

Every call to:
  • startActiveSpan(name, attributes, fn)
  • getHistogram(...)(value, attributes)
  • getIncrementCounter(...)(value, attributes)
…runs its attributes through every processor before recording. The session id (mcp.session.id) is merged in before processors run, so processors see it and can override or remove it.

Common recipes

Add deployment metadata

const addEnv = (data: Record<string, any>) => ({
  ...data,
  'deployment.env': process.env.DEPLOY_ENV ?? 'dev',
  'deployment.region': process.env.AWS_REGION ?? 'local',
})

Redact specific keys

const SENSITIVE_KEYS = new Set(['mcp.request.argument.api_key', 'mcp.request.argument.password'])

const redactKeys = (data: Record<string, any>) => {
  const out: Record<string, any> = {}
  for (const [k, v] of Object.entries(data)) {
    out[k] = SENSITIVE_KEYS.has(k) ? '[REDACTED]' : v
  }
  return out
}

Hash user identifiers

import { createHash } from 'node:crypto'

const hashUserIds = (data: Record<string, any>) => {
  const out = { ...data }
  for (const k of Object.keys(out)) {
    if (k.endsWith('user.id') && typeof out[k] === 'string') {
      out[k] = createHash('sha256').update(out[k]).digest('hex').slice(0, 16)
    }
  }
  return out
}

Drop attributes by pattern

const dropDebug = (data: Record<string, any>) => {
  const out: Record<string, any> = {}
  for (const [k, v] of Object.entries(data)) {
    if (!k.startsWith('debug.')) out[k] = v
  }
  return out
}

Calling processors directly

You can run the configured chain manually for one-off uses:
const cleaned = telemetry.processTelemetryAttributes({
  'user.email': 'jane@acme.com',
})
// → { 'mcp.session.id': '...', 'user.email': '[REDACTED:email]' }
This is useful when you’re building your own log lines and want them to share the same redaction logic as your spans.

Order matters

A common bug: redact-then-add ordering. If you add deployment env after you redact, the env keys are not redacted (which is usually fine but worth being deliberate about).
dataProcessors: [
  redactPII,   // 1. scrub anything sensitive that came from user input
  addEnv,      // 2. add deployment metadata
  enrichTrace, // 3. enrich with computed signals
]

Performance

Processors run on the hot path of every span and metric record. Keep them:
  • Pure — no I/O, no awaits.
  • Fast — use precompiled regexes, avoid full string clones when nothing needs replacing.
  • Allocation-light — return the same object when nothing changed.
const redactIfNeeded = (data: Record<string, any>) => {
  let cloned: Record<string, any> | null = null
  for (const k of Object.keys(data)) {
    const v = data[k]
    if (typeof v === 'string' && PII_RE.test(v)) {
      cloned ??= { ...data }
      cloned[k] = v.replace(PII_RE, '[REDACTED]')
    }
  }
  return cloned ?? data // unchanged → return same reference
}

Next

PII sanitization

A more focused recipe for the common case.

Custom spans & metrics

Where processors fit in your custom code.