Skip to main content
The withSpan() function wraps a code block in a span context, enabling manual tracing without decorators. It supports both synchronous and asynchronous execution.

Usage

Wrap any function execution to create a traced span:
import { withSpan } from 'zeroeval';

const result = await withSpan(
  { name: 'process-batch' },
  async () => {
    const data = await fetchData();
    return processData(data);
  }
);

Type Signature

function withSpan<T>(
  opts: SpanOptions,
  fn: () => Promise<T> | T
): Promise<T> | T

Parameters

opts
SpanOptions
required
Configuration options for the span
fn
() => Promise<T> | T
required
The function to execute within the span context. Can be synchronous or asynchronous.

SpanOptions

name
string
required
The name of the span. Used to identify this operation in your traces.
sessionId
string
Optional session identifier to group related spans together.
sessionName
string
Optional human-readable name for the session.
tags
Record<string, string>
String key-value pairs for categorizing and filtering spans.
tags: { environment: 'staging', operation: 'batch-process' }
attributes
Record<string, unknown>
Additional metadata to attach to the span. Accepts any JSON-serializable values.
attributes: { batchSize: 100, retryAttempt: 1 }
inputData
unknown
Explicit input data to log with the span. Only logged if provided.
outputData
unknown
Explicit output data to log with the span. If not provided, the function’s return value is logged.

Return Value

Returns the same type as the provided function:
  • If fn returns Promise<T>, withSpan() returns Promise<T>
  • If fn returns T, withSpan() returns T

Async Example

Trace asynchronous operations:
import { withSpan } from 'zeroeval';

async function processUserData(userId: string) {
  return await withSpan(
    {
      name: 'fetch-user-data',
      tags: { userId },
      attributes: { source: 'database' }
    },
    async () => {
      const user = await db.users.findById(userId);
      const orders = await db.orders.findByUser(userId);
      return { user, orders };
    }
  );
}

Sync Example

Trace synchronous operations:
import { withSpan } from 'zeroeval';

function calculateMetrics(data: number[]) {
  return withSpan(
    {
      name: 'calculate-metrics',
      inputData: { count: data.length },
      attributes: { algorithm: 'mean' }
    },
    () => {
      const sum = data.reduce((a, b) => a + b, 0);
      return sum / data.length;
    }
  );
}

Error Handling

Errors thrown inside the function are automatically captured:
import { withSpan } from 'zeroeval';

try {
  await withSpan(
    { name: 'risky-operation' },
    async () => {
      throw new Error('Something went wrong');
    }
  );
} catch (error) {
  // Error is captured in the span and re-thrown
  console.error('Operation failed:', error);
}
The span captures:
  • code - Error name (e.g., ‘Error’, ‘TypeError’)
  • message - Error message
  • stack - Stack trace
The error is then re-thrown for your application to handle.

Nested Spans

You can nest withSpan() calls to create parent-child span relationships:
import { withSpan } from 'zeroeval';

async function processDocument(docId: string) {
  return await withSpan(
    { name: 'process-document', tags: { docId } },
    async () => {
      const content = await withSpan(
        { name: 'fetch-content' },
        async () => db.documents.getContent(docId)
      );

      const analyzed = await withSpan(
        { name: 'analyze-content' },
        async () => analyzeText(content)
      );

      return analyzed;
    }
  );
}
The SDK’s AsyncLocalStorage-based context automatically links child spans to their parent.

Input/Output Logging

Control what gets logged:
import { withSpan } from 'zeroeval';

// Automatic output capture
const result = await withSpan(
  { name: 'compute', inputData: { type: 'batch' } },
  async () => {
    return { success: true, count: 42 }; // Automatically logged
  }
);

// Explicit output override
const result2 = await withSpan(
  {
    name: 'sensitive-compute',
    inputData: 'REDACTED',
    outputData: 'REDACTED'
  },
  async () => {
    return sensitiveOperation(); // Return value not logged
  }
);
If neither inputData nor outputData is provided, only the output (return value) is captured. Use inputData to explicitly log input context.

Advanced Example

Combine sessions, tags, and attributes for comprehensive tracing:
import { withSpan } from 'zeroeval';

class DataPipeline {
  async runPipeline(jobId: string, data: any[]) {
    return await withSpan(
      {
        name: 'pipeline-execution',
        sessionId: jobId,
        sessionName: 'Data Pipeline Run',
        tags: {
          jobType: 'batch',
          priority: 'high'
        },
        attributes: {
          recordCount: data.length,
          startTime: new Date().toISOString()
        },
        inputData: { jobId, recordCount: data.length }
      },
      async () => {
        const validated = await this.validate(data);
        const transformed = await this.transform(validated);
        const loaded = await this.load(transformed);
        return { loaded: loaded.length, jobId };
      }
    );
  }

  private async validate(data: any[]) {
    return await withSpan(
      { name: 'validate-data' },
      async () => {
        return data.filter(d => d.id && d.value);
      }
    );
  }

  private async transform(data: any[]) {
    return await withSpan(
      { name: 'transform-data' },
      async () => {
        return data.map(d => ({ ...d, processed: true }));
      }
    );
  }

  private async load(data: any[]) {
    return await withSpan(
      { name: 'load-data' },
      async () => {
        await db.bulk.insert(data);
        return data;
      }
    );
  }
}

When to Use

Use withSpan() when:
  • You can’t use decorators (e.g., functional code, arrow functions)
  • You want to trace a specific code block within a larger function
  • You need fine-grained control over span boundaries
  • You’re working with frameworks that don’t support decorators
For class methods, consider using the @span() decorator instead.

Build docs developers (and LLMs) love