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
}
}
})
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())