Mastra

Trace Mastra agents, workflows, and tools with Neatlogs (TypeScript).

Wrap the Mastra entities you build with wrapMastra — it patches Agent, Workflow, Vector, and Memory methods to emit method-level spans (AGENT, WORKFLOW, TOOL, LLM, RETRIEVER, VECTOR_STORE). Initialize Neatlogs once at startup, before constructing your entities.

Prerequisites

npm install neatlogs@latest @mastra/core @ai-sdk/openai

Usage

import { init, wrapMastra, flush, shutdown } from 'neatlogs';

async function main() {
  await init({ apiKey: process.env.NEATLOGS_API_KEY, workflowName: 'mastra-demo' });

  const { Agent } = await import('@mastra/core/agent');
  const { openai } = await import('@ai-sdk/openai');

  const agent = wrapMastra(
    new Agent({ name: 'assistant', instructions: 'Be concise.', model: openai('gpt-4o') }),
  );

  const res = await agent.generate('In one sentence, what is Mastra?');
  console.log(res.text);

  await flush();
  await shutdown();
}

main().catch(console.error);
Note

Alternative: getMastraObservability(). If you prefer Mastra's native observability hook, pass await getMastraObservability() into new Mastra({ observability }). It's an async helper exported from the main neatlogs package and requires the extra @neatlogs/instrumentation-mastra package. For most apps, wrapMastra is simpler and needs no extra dependency.

Sessions & end-user

Say your Mastra agent calls generate() a handful of times across one back-and-forth and you'd like all of it filed under a single session, tied to whichever of your users is chatting — since wrapMastra already opens the trace root, wrap the turn in identify() and each generate() picks up the session and end-user:

// Same sessionId every turn → one session; endUserId attributes it to your user.
await identify(
  { sessionId: `conv_${conversationId}`, endUserId: userId },
  async () => {
    await agent.generate(message);
  },
);

Not every run is a conversation. A standalone workflow — a one-off job, not a back-and-forth — is a single trace with no turns to group. You still attribute it to the customer it ran for, sourcing the id and any metadata from your own user or request object:

Warning

A standalone workflow must not carry a sessionId. Omit it entirely — Neatlogs sets sessionId = traceId, so the run is its own single-turn session, fully attributed to the end-user. Reusing one sessionId across unrelated runs wrongly folds them into a single multi-turn conversation.

async function summarize(document, user) {
  // Standalone workflow — attribute to the end-user; do NOT pass sessionId.
  return identify(
    { endUserId: String(user.id), endUserMetadata: { plan: user.plan } },
    async () => {
      const res = await agent.generate(`Summarize:\n${document}`);
      return res.text;
    },
  );
}

Sessions and End-User Identity walk through the whole model.

On this page

Ask Neatlogs AI

Answers from the docs

How can I help?

Ask anything about instrumenting, tracing, or the Neatlogs dashboard.