LlamaIndex Tutorial (TypeScript): adding audit logs for intermediate developers

By Cyprian AaronsUpdated 2026-04-21
llamaindexadding-audit-logs-for-intermediate-developerstypescript

This tutorial shows how to add audit logs to a LlamaIndex TypeScript app so you can track every user request, retrieved document, and final answer. You need this when you’re building internal tools, regulated workflows, or any AI system where you must explain what happened after the fact.

What You'll Need

  • Node.js 18+
  • A TypeScript project with tsconfig.json
  • LlamaIndex TypeScript packages:
    • llamaindex
    • dotenv
  • An OpenAI API key in .env
  • A terminal and a code editor
  • Basic familiarity with VectorStoreIndex, Document, and QueryEngine

Step-by-Step

  1. Install the dependencies and set up your environment file.
    Keep the audit log output separate from your app logic; that makes it easier to ship logs to a file, SIEM, or database later.
npm install llamaindex dotenv
OPENAI_API_KEY=your_openai_api_key_here
  1. Create a small audit logger that writes structured JSON lines.
    JSONL is a good default because it is easy to grep locally and easy to ingest into logging systems later.
import fs from "node:fs";

type AuditEvent = {
  ts: string;
  type: string;
  requestId: string;
  userId?: string;
  data: Record<string, unknown>;
};

const stream = fs.createWriteStream("audit.log", { flags: "a" });

export function auditLog(event: AuditEvent) {
  stream.write(JSON.stringify(event) + "\n");
}
  1. Build your index and wrap retrieval with audit events.
    The key idea is to log before and after each important step: query received, retrieval started, retrieved nodes, and answer returned.
import "dotenv/config";
import { Document, VectorStoreIndex } from "llamaindex";
import { auditLog } from "./audit";

async function main() {
  const requestId = crypto.randomUUID();
  const userId = "user-123";

  const docs = [
    new Document({ text: "Claims must be approved by an adjuster." }),
    new Document({ text: "Policy renewals happen 30 days before expiry." }),
  ];

  auditLog({
    ts: new Date().toISOString(),
    type: "query_received",
    requestId,
    userId,
    data: { question: "When do policy renewals happen?" },
  });

  const index = await VectorStoreIndex.fromDocuments(docs);
  const queryEngine = index.asQueryEngine();

  auditLog({
    ts: new Date().toISOString(),
    type: "retrieval_started",
    requestId,
    userId,
    data: {},
  });

  const response = await queryEngine.query({
    query: "When do policy renewals happen?",
  });
  1. Log the retrieved source nodes and final response text.
    For audit purposes, store the source node IDs, snippets, and similarity metadata if available. Avoid dumping raw embeddings or secrets into the log.
  const sourceNodes =
    response.sourceNodes?.map((node) => ({
      id: node.node.id_,
      score: node.score,
      text: node.node.getText().slice(0, 200),
    })) ?? [];

  auditLog({
    ts: new Date().toISOString(),
    type: "retrieval_completed",
    requestId,
    userId,
    data: { sourceNodes },
  });

  auditLog({
    ts: new Date().toISOString(),
    type: "answer_returned",
    requestId,
    userId,
    data: { answer: response.toString() },
  });

  console.log(response.toString());
}

main().catch((err) => {
  console.error(err);
});
  1. Add a simple guardrail for failures so errors are auditable too.
    In production, failed requests matter just as much as successful ones because they tell you where the agent broke and what context it had.
import { auditLog } from "./audit";

process.on("unhandledRejection", (reason) => {
  auditLog({
    ts: new Date().toISOString(),
    type: "runtime_error",
    requestId: "unknown",
    data: { reason: String(reason) },
  });
});

process.on("uncaughtException", (error) => {
  auditLog({
    ts: new Date().toISOString(),
    type: "runtime_error",
    requestId: "unknown",
    data: { message: error.message, stack: error.stack },
  });
});

Testing It

Run the script and confirm it prints an answer in the terminal. Then open audit.log and check that you see a sequence like query_received, retrieval_started, retrieval_completed, and answer_returned.

You should also verify that each event has a timestamp and request ID. If you want a stronger test, run two queries back-to-back and confirm the logs are separated by different requestId values.

If the retrieved text looks wrong, inspect the logged source snippets first. That usually tells you whether the issue is in ingestion, retrieval, or prompting.

Next Steps

  • Add a real persistence layer for logs:
    • PostgreSQL table
    • OpenSearch
    • CloudWatch or Datadog
  • Include more context in each event:
    • tenant ID
    • model name
    • latency
    • token usage
  • Wrap this pattern into middleware so every agent tool call gets audited automatically

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