CrewAI Tutorial (TypeScript): adding observability for advanced developers

By Cyprian AaronsUpdated 2026-04-21
crewaiadding-observability-for-advanced-developerstypescript

This tutorial shows you how to add observability to a CrewAI TypeScript agent workflow using structured logging and OpenTelemetry-style tracing hooks. You need this when a crew starts failing in production and you want to see which agent, task, or tool call caused the slowdown or bad output.

What You'll Need

  • Node.js 20+
  • A TypeScript project with crewAI installed
  • An API key for your model provider, for example:
    • OPENAI_API_KEY
  • A tracing backend or log sink:
    • Datadog, Honeycomb, Grafana Tempo, or just stdout to start
  • These packages:
    • crewai
    • zod
    • dotenv
    • pino
    • @opentelemetry/api
  • A .env file for secrets

Step-by-Step

  1. Start with a clean TypeScript setup and install the observability packages. I’m using pino for structured logs and @opentelemetry/api so you can wire spans into whatever backend your platform already uses.
npm init -y
npm install crewai zod dotenv pino @opentelemetry/api
npm install -D typescript tsx @types/node
npx tsc --init
  1. Create a small logger module that emits JSON logs with a trace context. This is the part most teams skip, then regret later when they need to correlate agent runs with downstream tool calls.
// src/observability.ts
import pino from "pino";
import { context, trace } from "@opentelemetry/api";

export const logger = pino({
  level: process.env.LOG_LEVEL ?? "info",
});

export function logEvent(event: string, data: Record<string, unknown> = {}) {
  const span = trace.getSpan(context.active());
  const spanContext = span?.spanContext();

  logger.info({
    event,
    traceId: spanContext?.traceId,
    spanId: spanContext?.spanId,
    ...data,
  });
}
  1. Define your agents and tasks with explicit metadata in the descriptions. That metadata is what makes traces useful later because you can search by business action instead of guessing which generic “task” failed.
// src/crew.ts
import { Agent, Crew, Task } from "crewai";
import { z } from "zod";

const InputSchema = z.object({
  customerName: z.string(),
  policyNumber: z.string(),
});

export const input = InputSchema.parse({
  customerName: "Amina Khan",
  policyNumber: "POL-88421",
});

export const triageAgent = new Agent({
  role: "Insurance Triage Analyst",
  goal: "Summarize the customer's issue and identify next action",
  backstory: "You work in claims and policy servicing.",
});

export const triageTask = new Task({
  description:
    `Traceable task=policy_triage customer=${input.customerName} policy=${input.policyNumber}`,
  expectedOutput: "A concise triage summary with recommended next step.",
  agent: triageAgent,
});

export const crew = new Crew({
  agents: [triageAgent],
  tasks: [triageTask],
});
  1. Wrap execution in a root span and log before and after the crew run. In production, this gives you one parent trace per request, then you can attach downstream spans from tools or HTTP calls under it.
// src/main.ts
import "dotenv/config";
import { trace, SpanStatusCode } from "@opentelemetry/api";
import { crew } from "./crew";
import { logEvent } from "./observability";

const tracer = trace.getTracer("crewai-observability-demo");

async function main() {
  return tracer.startActiveSpan("crew.run", async (span) => {
    try {
      logEvent("crew_start", { crew: "insurance-triage" });

      const result = await crew.kickoff();
      logEvent("crew_success", { outputPreview: String(result).slice(0, 200) });

      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      logEvent("crew_error", {
        message: error instanceof Error ? error.message : String(error),
      });
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    } finally {
      span.end();
    }
  });
}

main().then((result) => {
  console.log(String(result));
});
  1. Add tool-level observability if your crew calls external systems. This is where most debugging time goes in real projects because latency usually comes from one slow API call, not the LLM itself.
// src/tools.ts
import { context, trace } from "@opentelemetry/api";
import { logEvent } from "./observability";

const tracer = trace.getTracer("crewai-observability-demo");

export async function fetchPolicySummary(policyNumber: string) {
  return tracer.startActiveSpan("tool.fetchPolicySummary", async (span) => {
    try {
      logEvent("tool_start", { tool: "fetchPolicySummary", policyNumber });

      const response = await fetch(`https://example.com/policies/${policyNumber}`);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);

      const data = await response.json();
      logEvent("tool_success", { tool: "fetchPolicySummary" });

      return data;
    } catch (error) {
      logEvent("tool_error", {
        tool: "fetchPolicySummary",
        message: error instanceof Error ? error.message : String(error),
      });
      span.recordException(error as Error);
      throw error;
    } finally {
      span.end();
    }
  });
}

Testing It

Run the app with your API key set in .env, then confirm you see JSON logs for crew_start, crew_success, and any tool events you added. If you wired OpenTelemetry into a collector later, the same run should appear as a single parent trace with nested spans for the crew and tools.

Use a bad API key or force an HTTP failure in a tool to verify error paths emit crew_error or tool_error. The important check is correlation: every log line should include a traceId when executed inside an active span.

A good smoke test is to run the same input twice and compare outputs plus latency. If one run is slow, your logs should tell you whether it was model latency, tool latency, or task retry behavior.

Next Steps

  • Add an OpenTelemetry SDK exporter so traces go to Datadog, Honeycomb, or Tempo instead of only stdout.
  • Attach token usage and cost metrics to each task result so finance teams can track per-request spend.
  • Propagate correlation IDs from your HTTP layer into the root CrewAI span for end-to-end request tracing.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides