Trace Class
TheTrace class represents a high-level operation or workflow in your LLM application. Traces capture the entire lifecycle of a request, batch job, or user interaction, and can contain multiple child spans for granular tracking.
Overview
A trace is the top-level unit of observability that represents:- A single user request to your API
- A background job or workflow
- A conversation turn in a chatbot
- A complete RAG pipeline execution
- Any end-to-end operation you want to track
- Metadata: name, status, timestamps, tags, attributes
- Context: session ID, reference ID
- Children: one or more spans representing sub-operations
Creation
Create a trace using theMonitor.logTrace() method:
const trace = monitor.logTrace({
name: 'User Login',
sessionId: 'session-abc-123',
tags: ['auth', 'production'],
attributes: { userId: 'user-456' }
});
Properties
trace
trace: CreateLogTraceRequest
{
projectId: string;
trace: {
name: string;
status: TraceStatus;
sessionId?: string;
referenceId: string;
tags?: string[];
attributes?: Record<string, LogAttributesValue>; // LogAttributesValue = string | number | boolean
startedAt: number; // Unix timestamp in milliseconds
endedAt?: number; // Set when trace.end() is called
}
}
traceId
traceId: string | undefined
const trace = monitor.logTrace({ name: 'Operation' });
console.log(trace.traceId); // undefined (not flushed yet)
trace.end();
await monitor.flush();
console.log(trace.traceId); // "trace_abc123xyz" (assigned by server)
Methods
logSpan()
Create a child span within this trace.logSpan(options: LogSpanOptions): Span
Parameters
object
required
Show properties
Show properties
string
required
Human-readable name for the span (e.g., “LLM Call”, “Database Query”).
SpanStatus
default:"unknown"
Initial SpanStatus:
'success' | 'failure' | 'aborted' | 'cancelled' | 'unknown'string
Custom reference ID. Auto-generated UUID if not provided.
string
ID of the prompt used in this span (for LLM calls).
string | null
ID of the deployment used in this span.
boolean
Whether to run evaluators on this span after completion.
string[]
Tags for categorization (e.g.,
['llm', 'openai']).Record<string, LogAttributesValue>
Additional metadata (e.g.,
{ model: 'gpt-4o', tokens: 1500 }). LogAttributesValue = string | number | booleanLogSpanContent
LogSpanContent (input/output data). Defaults to
monitor.defaultContent.Returns
Span
A new Span instance. See Span Class for details.
Examples
const trace = monitor.logTrace({ name: 'API Request' });
const span = trace.logSpan({
name: 'Process Data'
});
// Do work...
await processData();
span.update({ status: 'success' });
span.end();
trace.end();
const trace = monitor.logTrace({
name: 'Chat Completion',
sessionId: userId
});
const llmSpan = trace.logSpan({
name: 'OpenAI GPT-4',
promptId: deployment.promptId,
deploymentId: deployment.id,
runEvaluation: true,
tags: ['llm', 'openai', 'gpt-4o'],
attributes: {
model: 'gpt-4o',
temperature: 0.7
}
});
const response = await openai.chat.completions.create({ /* ... */ });
llmSpan.update({
status: 'success',
content: {
type: 'Model',
provider: 'openai',
model: 'gpt-4o',
input: JSON.stringify(messages),
output: JSON.stringify(response.choices[0].message)
}
});
llmSpan.end();
trace.end();
const trace = monitor.logTrace({ name: 'RAG Pipeline' });
// Parent span
const ragSpan = trace.logSpan({
name: 'RAG Operation',
tags: ['rag']
});
// Child spans
const embeddingSpan = ragSpan.logSpan({
name: 'Generate Embedding',
tags: ['embedding']
});
embeddingSpan.end();
const retrievalSpan = ragSpan.logSpan({
name: 'Vector Search',
tags: ['retrieval']
});
retrievalSpan.end();
const llmSpan = ragSpan.logSpan({
name: 'Generate Answer',
tags: ['llm']
});
llmSpan.end();
ragSpan.end();
trace.end();
update()
Update trace metadata in place.update(updates: TraceUpdates): this
Parameters
object
required
Partial updates to apply to the trace.
Show properties
Show properties
string
Update the trace name.
TraceStatus
Update the TraceStatus:
'success' | 'failure' | 'aborted' | 'cancelled' | 'pending' | 'unknown'string[]
Update tags (replaces existing tags).
Record<string, LogAttributesValue>
Update attributes (merges with existing attributes). LogAttributesValue =
string | number | booleanReturns
Returnsthis for method chaining.
Examples
const trace = monitor.logTrace({
name: 'Process Order',
status: 'pending'
});
try {
await processOrder();
trace.update({ status: 'success' });
} catch (error) {
trace.update({ status: 'failure' });
}
trace.end();
const trace = monitor.logTrace({ name: 'User Request' });
// Add attribute after getting user info
const userId = await getUserId();
trace.update({
attributes: { userId }
});
// Add more attributes
trace.update({
attributes: {
processingTime: Date.now() - startTime,
itemsProcessed: 42
}
});
trace.end();
monitor.logTrace({ name: 'Quick Operation' })
.update({ status: 'pending' })
.update({ tags: ['fast', 'important'] })
.end();
trace.update({
name: 'Updated Name',
status: 'success',
tags: ['completed', 'verified'],
attributes: {
duration: 1234,
outcome: 'positive'
}
});
end()
Mark the trace as complete and ready to be flushed.end(): string | undefined
Behavior
- Sets
endedAttimestamp if not already set - Marks the trace as
readyin the monitor’s buffer - Recursively ends all child spans (and their descendants)
- Returns the trace’s reference ID
Always call
end() on your traces! Traces that are never ended will never be flushed to the API.Returns
string | undefined
The trace’s reference ID for correlation with external systems.
Examples
const trace = monitor.logTrace({ name: 'Operation' });
// Do work...
trace.end(); // Required!
const trace = monitor.logTrace({ name: 'Critical Operation' });
try {
await doWork();
trace.update({ status: 'success' });
} catch (error) {
trace.update({ status: 'failure' });
throw error;
} finally {
trace.end(); // Always called, even if error thrown
}
const trace = monitor.logTrace({
name: 'Async Job',
referenceId: `job-${jobId}`
});
// Do work...
const refId = trace.end();
console.log(`Trace completed: ${refId}`); // "job-123"
const trace = monitor.logTrace({ name: 'Parent' });
const span1 = trace.logSpan({ name: 'Child 1' });
const span2 = span1.logSpan({ name: 'Grandchild' });
const span3 = trace.logSpan({ name: 'Child 2' });
// Don't need to manually end children
trace.end(); // Automatically ends span1, span2, span3
Complete Examples
Example 1: Simple API Request
import { Adaline } from '@adaline/client';
import { Gateway } from '@adaline/gateway';
import { OpenAI } from '@adaline/openai';
const adaline = new Adaline();
const gateway = new Gateway();
const openaiProvider = new OpenAI();
const monitor = adaline.initMonitor({ projectId: 'my-api' });
async function handleChatRequest(userId: string, message: string) {
// Create trace for this request
const trace = monitor.logTrace({
name: 'Chat Request',
sessionId: userId,
tags: ['chat', 'api'],
attributes: {
userId,
messageLength: message.length
}
});
try {
// Log the LLM call
const llmSpan = trace.logSpan({
name: 'OpenAI Completion',
tags: ['llm']
});
const model = openaiProvider.chatModel({
modelName: 'gpt-4o',
apiKey: process.env.OPENAI_API_KEY!
});
const gatewayResponse = await gateway.completeChat({
model,
messages: [
{ role: 'user', content: [{ modality: 'text', value: message }] }
]
});
llmSpan.update({
status: 'success',
content: {
type: 'Model',
provider: 'openai',
model: 'gpt-4o',
input: JSON.stringify(gatewayResponse.provider.request),
output: JSON.stringify(gatewayResponse.provider.response)
}
});
llmSpan.end();
trace.update({ status: 'success' });
return gatewayResponse.response.messages[0].content[0].value;
} catch (error) {
trace.update({
status: 'failure',
attributes: {
error: error instanceof Error ? error.message : String(error)
}
});
throw error;
} finally {
trace.end();
}
}
Example 2: Multi-Step Workflow
async function processUserOnboarding(userId: string) {
const trace = monitor.logTrace({
name: 'User Onboarding',
sessionId: userId,
status: 'pending',
tags: ['onboarding', 'workflow'],
attributes: { userId }
});
try {
// Step 1: Create account
const createSpan = trace.logSpan({
name: 'Create Account',
tags: ['database']
});
await createAccount(userId);
createSpan.update({ status: 'success' });
createSpan.end();
// Step 2: Send welcome email
const emailSpan = trace.logSpan({
name: 'Send Welcome Email',
tags: ['email']
});
await sendWelcomeEmail(userId);
emailSpan.update({ status: 'success' });
emailSpan.end();
// Step 3: Generate personalized content
const llmSpan = trace.logSpan({
name: 'Generate Welcome Message',
tags: ['llm', 'personalization']
});
const welcomeMsg = await generateWelcomeMessage(userId);
llmSpan.update({
status: 'success',
content: {
type: 'Model',
provider: 'openai',
model: 'gpt-4o',
input: JSON.stringify({ userId }),
output: JSON.stringify({ message: welcomeMsg })
}
});
llmSpan.end();
trace.update({ status: 'success' });
} catch (error) {
trace.update({
status: 'failure',
attributes: { error: String(error) }
});
throw error;
} finally {
trace.end();
}
}
Example 3: Nested Operations (RAG Pipeline)
async function answerQuestion(sessionId: string, question: string) {
const trace = monitor.logTrace({
name: 'RAG Question Answering',
sessionId,
tags: ['rag', 'qa'],
attributes: { questionLength: question.length }
});
try {
// Parent span for entire RAG operation
const ragSpan = trace.logSpan({
name: 'RAG Pipeline',
tags: ['pipeline']
});
// Step 1: Generate embedding (child of ragSpan)
const embedSpan = ragSpan.logSpan({
name: 'Generate Query Embedding',
tags: ['embedding']
});
const embedding = await generateEmbedding(question);
embedSpan.update({
status: 'success',
content: {
type: 'Embeddings',
input: JSON.stringify({ query: question }),
output: JSON.stringify({ dimensions: embedding.length })
}
});
embedSpan.end();
// Step 2: Retrieve documents (child of ragSpan)
const retrieveSpan = ragSpan.logSpan({
name: 'Vector Search',
tags: ['retrieval', 'vector-db']
});
const docs = await retrieveDocuments(embedding);
retrieveSpan.update({
status: 'success',
content: {
type: 'Retrieval',
input: JSON.stringify({ embedding: 'vector', topK: 5 }),
output: JSON.stringify({ documentIds: docs.map(d => d.id) })
},
attributes: { documentsFound: docs.length }
});
retrieveSpan.end();
// Step 3: Generate answer (child of ragSpan)
const llmSpan = ragSpan.logSpan({
name: 'Generate Answer',
tags: ['llm', 'answer-generation'],
runEvaluation: true
});
const answer = await generateAnswer(question, docs);
llmSpan.update({
status: 'success',
content: {
type: 'Model',
provider: 'openai',
model: 'gpt-4o',
input: JSON.stringify({ question, context: docs }),
output: JSON.stringify({ answer })
}
});
llmSpan.end();
ragSpan.update({ status: 'success' });
ragSpan.end();
trace.update({ status: 'success' });
return answer;
} catch (error) {
trace.update({ status: 'failure' });
throw error;
} finally {
trace.end();
}
}
Example 4: Long-Running Background Job
async function processBatch(batchId: string) {
const trace = monitor.logTrace({
name: 'Batch Processing',
referenceId: `batch-${batchId}`,
status: 'pending',
tags: ['batch', 'background'],
attributes: {
batchId,
startTime: Date.now()
}
});
const items = await getBatchItems(batchId);
trace.update({
attributes: { itemCount: items.length }
});
let successCount = 0;
let failureCount = 0;
for (const item of items) {
const itemSpan = trace.logSpan({
name: `Process Item ${item.id}`,
tags: ['item'],
attributes: { itemId: item.id }
});
try {
await processItem(item);
itemSpan.update({ status: 'success' });
successCount++;
} catch (error) {
itemSpan.update({
status: 'failure',
attributes: { error: String(error) }
});
failureCount++;
}
itemSpan.end();
}
trace.update({
status: failureCount === 0 ? 'success' : 'failure',
attributes: {
successCount,
failureCount,
duration: Date.now() - trace.trace.trace.startedAt
}
});
trace.end();
}
Type Definitions
type LogAttributesValue = string | number | boolean;
type TraceStatus =
| 'success'
| 'failure'
| 'aborted'
| 'cancelled'
| 'pending'
| 'unknown';
interface LogTraceOptions {
name: string;
status?: TraceStatus;
sessionId?: string;
referenceId?: string;
tags?: string[];
attributes?: Record<string, LogAttributesValue>;
}
interface TraceUpdates {
name?: string;
status?: TraceStatus;
tags?: string[];
attributes?: Record<string, LogAttributesValue>;
}
interface CreateLogTraceRequest {
projectId: string;
trace: {
name: string;
status: TraceStatus;
sessionId?: string;
referenceId: string;
tags?: string[];
attributes?: Record<string, LogAttributesValue>; // LogAttributesValue = string | number | boolean
startedAt: number;
endedAt?: number;
};
}
Best Practices
1. Always Use Try-Finally
// ✅ Good: trace.end() always called
const trace = monitor.logTrace({ name: 'Operation' });
try {
await doWork();
trace.update({ status: 'success' });
} finally {
trace.end();
}
// ❌ Bad: trace.end() might not be called
const trace = monitor.logTrace({ name: 'Operation' });
await doWork();
trace.end(); // Skipped if doWork() throws!
2. Use Meaningful Names
// ✅ Good: Descriptive names
const trace = monitor.logTrace({ name: 'User Registration Flow' });
const trace = monitor.logTrace({ name: 'PDF Processing Pipeline' });
const trace = monitor.logTrace({ name: 'RAG Question Answering' });
// ❌ Bad: Generic names
const trace = monitor.logTrace({ name: 'Request' });
const trace = monitor.logTrace({ name: 'Function' });
const trace = monitor.logTrace({ name: 'Process' });
3. Add Context with Attributes
// ✅ Good: Rich context
const trace = monitor.logTrace({
name: 'API Request',
sessionId: userId,
tags: ['api', 'production', 'premium-tier'],
attributes: {
userId,
endpoint: '/api/chat',
method: 'POST',
clientVersion: '2.1.0',
region: 'us-east-1'
}
});
4. Update Status Based on Outcome
// ✅ Good: Accurate status tracking
const trace = monitor.logTrace({ name: 'Operation', status: 'pending' });
try {
await doWork();
trace.update({ status: 'success' });
} catch (error) {
trace.update({ status: 'failure' });
throw error;
} finally {
trace.end();
}
5. Use Sessions for Related Traces
// ✅ Good: Group related traces
const sessionId = `user-${userId}-${Date.now()}`;
// First trace
const trace1 = monitor.logTrace({
name: 'Login',
sessionId
});
// Later traces in same session
const trace2 = monitor.logTrace({
name: 'Chat Message 1',
sessionId
});
const trace3 = monitor.logTrace({
name: 'Chat Message 2',
sessionId
});
Common Patterns
Pattern 1: Request Handler
async function handleRequest(req: Request) {
const trace = monitor.logTrace({
name: `${req.method} ${req.url}`,
sessionId: req.headers.get('session-id') || undefined,
tags: ['api', 'http'],
attributes: {
method: req.method,
path: req.url,
userAgent: req.headers.get('user-agent') || 'unknown'
}
});
try {
const result = await processRequest(req);
trace.update({ status: 'success' });
return result;
} catch (error) {
trace.update({ status: 'failure' });
throw error;
} finally {
trace.end();
}
}
Pattern 2: Background Job
async function runJob(jobId: string) {
const trace = monitor.logTrace({
name: 'Background Job',
referenceId: jobId,
status: 'pending',
tags: ['job', 'async']
});
try {
// Job logic with spans...
trace.update({ status: 'success' });
} catch (error) {
trace.update({ status: 'failure' });
} finally {
trace.end();
}
}