Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iii-hq/sdk/llms.txt

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

Overview

The III SDK includes built-in OpenTelemetry support for traces, metrics, and logs. All telemetry data is exported to the III Engine via a shared WebSocket connection using OTLP JSON format.
OpenTelemetry is enabled by default. To disable, set OTEL_ENABLED=false or { otel: { enabled: false } } in init options.

Quick Start

import { init } from 'iii-sdk'

const iii = init('ws://localhost:49134', {
  otel: {
    enabled: true,                     // Default: true
    serviceName: 'my-service',         // Default: 'iii-node'
    serviceVersion: '1.0.0',           // Default: 'unknown'
    metricsEnabled: true,              // Default: true
    metricsExportIntervalMs: 60000,    // Default: 60s
    fetchInstrumentationEnabled: true  // Default: true (auto-instruments fetch)
  }
})

// Traces, metrics, and logs are automatically exported!

Configuration

Service Identity

import { init } from 'iii-sdk'

const iii = init('ws://localhost:49134', {
  otel: {
    serviceName: 'payment-service',       // Required for grouping
    serviceVersion: '2.1.0',              // Useful for rollback correlation
    serviceNamespace: 'production',       // Environment/namespace
    serviceInstanceId: 'pod-abc-123'      // Defaults to auto-generated UUID
  }
})

Environment Variables

You can also configure OpenTelemetry via environment variables:
OTEL_ENABLED=true
OTEL_SERVICE_NAME=my-service
SERVICE_VERSION=1.2.3
SERVICE_NAMESPACE=staging
SERVICE_INSTANCE_ID=worker-5
III_BRIDGE_URL=ws://engine:49134
OTEL_METRICS_ENABLED=true

Distributed Tracing

Automatic Tracing

All function invocations are automatically traced with parent-child span relationships:
import { getContext } from 'iii-sdk'

iii.registerFunction({ id: 'orders::create' }, async (input) => {
  const { logger, trace } = getContext()
  
  // This function call is automatically a child span
  const user = await iii.call('users::get', { id: input.userId })
  
  // Another child span
  const inventory = await iii.call('inventory::reserve', { sku: input.sku })
  
  logger.info('Order created', { orderId: input.id })
  return { success: true }
})

// Trace hierarchy:
// orders::create (parent)
// ├── users::get (child)
// └── inventory::reserve (child)

Custom Spans

Create custom spans for fine-grained tracing:
import { withSpan, SpanKind, getContext } from 'iii-sdk'

iii.registerFunction({ id: 'analytics::report' }, async (input) => {
  const { trace } = getContext()
  
  // Add custom attributes to the function span
  trace?.setAttribute('report.type', input.type)
  trace?.setAttribute('report.date_range', input.dateRange)
  
  // Create a custom span for database query
  const data = await withSpan(
    'query-analytics-db',
    { kind: SpanKind.CLIENT },
    async (span) => {
      span.setAttribute('db.system', 'postgresql')
      span.setAttribute('db.statement', 'SELECT * FROM analytics WHERE ...')
      
      const result = await db.query('SELECT ...')
      span.setAttribute('db.rows_returned', result.length)
      
      return result
    }
  )
  
  // Create a custom span for aggregation
  const aggregated = await withSpan(
    'aggregate-data',
    { kind: SpanKind.INTERNAL },
    async (span) => {
      span.addEvent('Starting aggregation', { rows: data.length })
      const result = processData(data)
      span.addEvent('Aggregation complete', { buckets: result.length })
      return result
    }
  )
  
  return aggregated
})

HTTP Client Tracing

HTTP requests are automatically instrumented:
// Node.js: fetch is auto-instrumented by default
iii.registerFunction({ id: 'external::fetch-user' }, async (input) => {
  // Automatically creates a CLIENT span with rich attributes:
  // - http.request.method: GET
  // - url.full: https://api.example.com/users/123
  // - server.address: api.example.com
  // - http.response.status_code: 200
  const response = await fetch(`https://api.example.com/users/${input.id}`)
  return response.json()
})

// Disable if needed:
const iii = init('ws://localhost:49134', {
  otel: { fetchInstrumentationEnabled: false }
})

Third-Party Instrumentations

import { PrismaInstrumentation } from '@prisma/instrumentation'
import { init } from 'iii-sdk'

const iii = init('ws://localhost:49134', {
  otel: {
    instrumentations: [
      new PrismaInstrumentation()  // Auto-trace all Prisma queries
    ]
  }
})

Metrics

Automatic Metrics

The SDK automatically reports worker metrics:
  • iii.worker.cpu_usage - CPU usage percentage
  • iii.worker.memory_usage - Memory usage in bytes
  • iii.worker.active_invocations - Number of active function calls
import { init } from 'iii-sdk'

const iii = init('ws://localhost:49134', {
  enableMetricsReporting: true,  // Default: true
  otel: {
    metricsEnabled: true,         // Default: true
    metricsExportIntervalMs: 30000 // Export every 30s
  }
})

Custom Metrics

import { getMeter } from 'iii-sdk'

const meter = getMeter()
if (meter) {
  // Counter: monotonically increasing value
  const orderCounter = meter.createCounter('orders.created', {
    description: 'Total number of orders created'
  })
  
  // Histogram: distribution of values
  const orderValueHistogram = meter.createHistogram('orders.value', {
    description: 'Order value distribution',
    unit: 'USD'
  })
  
  // UpDownCounter: value that can increase or decrease
  const activeOrdersGauge = meter.createUpDownCounter('orders.active', {
    description: 'Number of active orders'
  })
  
  iii.registerFunction({ id: 'orders::create' }, async (input) => {
    orderCounter.add(1, { region: input.region })
    orderValueHistogram.record(input.total, { currency: input.currency })
    activeOrdersGauge.add(1)
    
    // ... create order logic
    
    return { success: true }
  })
}

Logs

See the Context-Aware Logging guide for details on using the Logger API.

Telemetry Export

Separate WebSocket Connection

Telemetry uses a dedicated WebSocket connection separate from the main III connection. This ensures telemetry export doesn’t interfere with function invocations.
import { init } from 'iii-sdk'

const iii = init('ws://localhost:49134', {
  // Main connection config
  reconnectionConfig: { maxRetries: -1 },
  
  // Telemetry connection config (independent)
  otel: {
    engineWsUrl: 'ws://telemetry-endpoint:49134',  // Can be different!
    reconnectionConfig: {
      maxRetries: 5,      // Less critical than main connection
      maxDelayMs: 60000   // Higher delay tolerance
    }
  }
})

OTLP JSON Format

Telemetry is exported using OTLP JSON over WebSocket with binary frame prefixes:
  • Traces: OTLP + JSON payload
  • Metrics: MTRC + JSON payload
  • Logs: LOGS + JSON payload

Shutdown

import { shutdownOtel } from 'iii-sdk'

// Graceful shutdown flushes all pending telemetry
process.on('SIGTERM', async () => {
  await iii.shutdown()  // Automatically calls shutdownOtel()
  process.exit(0)
})

// Or shutdown OTel separately:
await shutdownOtel()

Best Practices

Observability Checklist:
  • ✅ Set meaningful serviceName and serviceVersion
  • ✅ Use serviceNamespace to differentiate environments (dev, staging, prod)
  • ✅ Add custom span attributes for business-critical operations
  • ✅ Use structured logging with context (see Context Logging)
  • ✅ Create custom metrics for domain-specific KPIs
  • ✅ Use semantic conventions for standard operations (HTTP, DB, etc.)
  • ✅ Configure separate reconnection for telemetry (less aggressive than main connection)
  • ✅ Always flush telemetry on shutdown (await iii.shutdown())

Build docs developers (and LLMs) love