Skip to main content

Overview

Stepkit provides built-in logging and performance tracking:
  • Structured logs for each step execution
  • Stopwatch timing for performance analysis
  • Performance summaries with statistics
  • Custom log functions for integration with logging services
  • Per-step logging control

Basic Logging

Enable logging by passing a log option:
const pipeline = stepkit<{ userId: string }>()
  .step('fetch-user', async ({ userId }) => {
    return { user: await getUser(userId) }
  })
  .step('process', ({ user }) => {
    return { processed: true }
  })

await pipeline.run({ userId: '42' }, { log: true })
Output:
🚀 Starting pipeline with input: {
  userId: "42"
}

📍 Step: fetch-user
✅ fetch-user completed
   Output: user

📍 Step: process
✅ process completed
   Output: processed

✨ Pipeline completed successfully

Stopwatch Mode

Enable detailed performance tracking with the stopwatch option:
const pipeline = stepkit<{ userId: string }>()
  .step('fetch-user', async ({ userId }) => {
    await sleep(150)
    return { user: { id: userId, email: '[email protected]' } }
  })
  .step(
    'fetch-data',
    async ({ user }) => {
      await sleep(120)
      return { orders: [{ id: 'o1' }, { id: 'o2' }] }
    },
    async ({ user }) => {
      await sleep(80)
      return { alerts: ['notice'] }
    }
  )
  .step('process', ({ orders }) => ({ orderCount: orders.length }))

await pipeline.run({ userId: '42' }, { log: { stopwatch: true } })
Output:
🚀 Starting pipeline with input: {
  userId: "42",
}

📍 Step: fetch-user
✅ fetch-user completed in 178ms
   Output: user

📍 Step: fetch-data
✅ fetch-data completed in 121ms
   Output: orders, alerts

📍 Step: process
✅ process completed in 0ms
   Output: orderCount

âąī¸  Performance Summary:
┌──────────────────────────────────────────────────────┐
│ ✅ fetch-user                                  178ms │
│ ✅ fetch-data                                  121ms │
│ ✅ process                                       0ms │
└──────────────────────────────────────────────────────┘

📊 Statistics:
   Average: 100ms
   Slowest: fetch-user (178ms)
   Fastest: process (0ms)

⏰ Total Pipeline Time: 299ms

✨ Pipeline completed successfully

Log Configuration

log
boolean | LogConfig
Enable logging for the pipeline. Can be a boolean or a configuration object.

LogConfig Options

logFn
(message: string, ...args: unknown[]) => void
default:"console.log"
Custom function for standard log messages.
errorLogFn
(message: string, ...args: unknown[]) => void
default:"console.error"
Custom function for error messages.
stopwatch
boolean | StopwatchConfig
Enable performance tracking. Can be a boolean or a configuration object for fine-grained control.

Stopwatch Configuration

showStepDuration
boolean
default:"true"
Show duration for each step as it completes.
showSummary
boolean
default:"true"
Show the performance summary table at the end.
showTotal
boolean
default:"true"
Show the total pipeline execution time.

Configuring Logs

At Build Time

Configure logging when creating the pipeline:
const pipeline = stepkit<{ id: string }>({
  log: {
    stopwatch: {
      showStepDuration: true,
      showSummary: true,
      showTotal: true,
    },
  },
})
  .step('step-1', () => ({ a: 1 }))
  .step('step-2', () => ({ b: 2 }))

await pipeline.run({ id: '1' })
// Logging applies to all runs

At Runtime

Override logging configuration for a specific run:
const pipeline = stepkit<{ id: string }>()
  .step('step-1', () => ({ a: 1 }))
  .step('step-2', () => ({ b: 2 }))

// Run with logging
await pipeline.run({ id: '1' }, { log: { stopwatch: true } })

// Run without logging
await pipeline.run({ id: '2' })

Custom Log Functions

Integrate with logging services:
import { logger } from './logger'

const pipeline = stepkit<{ userId: string }>()
  .step('fetch-user', async ({ userId }) => {
    return { user: await getUser(userId) }
  })

await pipeline.run(
  { userId: '42' },
  {
    log: {
      logFn: (message, ...args) => {
        logger.info(message, ...args)
      },
      errorLogFn: (message, ...args) => {
        logger.error(message, ...args)
      },
      stopwatch: true,
    },
  },
)

Structured Logging

import pino from 'pino'

const logger = pino()

const pipeline = stepkit<{ requestId: string }>()
  .step('process-request', async ({ requestId }) => {
    return { result: 'done' }
  })

await pipeline.run(
  { requestId: 'req-123' },
  {
    log: {
      logFn: (message, ...args) => {
        logger.info({ message, args }, 'Pipeline step')
      },
      errorLogFn: (message, ...args) => {
        logger.error({ message, args }, 'Pipeline error')
      },
      stopwatch: true,
    },
  },
)

Per-Step Logging

Control logging for individual steps:
const pipeline = stepkit<{ id: string }>()
  .step({ name: 'step-1', log: true }, () => ({ a: 1 }))
  .step({ name: 'step-2', log: false }, () => ({ b: 2 })) // Silent
  .step('step-3', () => ({ c: 3 }))

await pipeline.run({ id: '1' }, { log: true })
// Only step-1 and step-3 produce logs

Step Icons

Different step types have distinct icons:
  • 📍 Step: Regular step execution
  • 🔄 Transform: Context transformation
  • 🔀 Branch: Conditional branching
  • â­ī¸ Skip: Step skipped (condition or resume)
  • ✅ Success: Step completed successfully
  • ❌ Error: Step failed
const pipeline = stepkit<{ value: number }>()
  .step('regular', () => ({ a: 1 }))
  .transform('cleanup', ({ value }) => ({ value }))
  .branchOn(
    'route',
    {
      when: ({ value }) => value > 0,
      then: (b) => b.step('positive', () => ({ sign: 'positive' })),
    },
    {
      default: (b) => b.step('negative', () => ({ sign: 'negative' })),
    },
  )

await pipeline.run({ value: 5 }, { log: { stopwatch: true } })
Output:
📍 Step: regular
✅ regular completed in 0ms

🔄 Transform: cleanup
✅ cleanup completed in 0ms

🔀 Branch: route
   â†ŗ Executing: positive

📍 Step: route/positive/positive
✅ route/positive/positive completed in 0ms
✅ route completed in 1ms

Performance Summary

The performance summary includes:
  1. Table of steps with status icons and durations
  2. Statistics:
    • Average duration across successful steps
    • Slowest step with duration
    • Fastest step with duration
  3. Total pipeline time
âąī¸  Performance Summary:
┌──────────────────────────────────────────────────────┐
│ ✅ fetch-user                                  178ms │
│ ✅ fetch-data                                  121ms │
│ ❌ maybe-slow                                  201ms │
│ ✅ process                                       0ms │
└──────────────────────────────────────────────────────┘

📊 Statistics:
   Average: 50ms
   Slowest: fetch-user (178ms)
   Fastest: process (0ms)

⏰ Total Pipeline Time: 511ms

Error Logging

Failed steps are clearly indicated:
const pipeline = stepkit()
  .step({ name: 'failing', onError: 'continue' }, () => {
    throw new Error('Something went wrong')
  })
  .step('continue', () => ({ done: true }))

await pipeline.run({}, { log: { stopwatch: true } })
Output:
📍 Step: failing
❌ failing failed after 2ms
   Error: Error: Something went wrong

📍 Step: continue
✅ continue completed in 0ms

Logging with Branches

Branches show which path was taken:
const pipeline = stepkit<{ type: 'a' | 'b' }>()
  .branchOn(
    'route',
    {
      name: 'type-a',
      when: ({ type }) => type === 'a',
      then: (b) => b.step('handle-a', () => ({ result: 'A' })),
    },
    {
      name: 'type-b',
      when: ({ type }) => type === 'b',
      then: (b) => b.step('handle-b', () => ({ result: 'B' })),
    },
  )

await pipeline.run({ type: 'a' }, { log: true })
Output:
🔀 Branch: route
   â†ŗ Executing: type-a

📍 Step: route/type-a/handle-a
✅ route/type-a/handle-a completed

Disabling Specific Log Components

await pipeline.run(
  { userId: '42' },
  {
    log: {
      stopwatch: {
        showStepDuration: false, // Hide individual step durations
        showSummary: true,       // Show summary table
        showTotal: true,         // Show total time
      },
    },
  },
)
Output:
🚀 Starting pipeline with input: { userId: "42" }

📍 Step: fetch-user
✅ fetch-user completed

📍 Step: process
✅ process completed

âąī¸  Performance Summary:
┌──────────────────────────────────────────────────────┐
│ ✅ fetch-user                                  178ms │
│ ✅ process                                       0ms │
└──────────────────────────────────────────────────────┘

⏰ Total Pipeline Time: 178ms

Combining with Other Features

With Checkpoints

Logs show resume information:
await pipeline.runCheckpoint(checkpoint, { log: { stopwatch: true } })
Output:
🚀 Resuming pipeline from checkpoint step: step-2

â­ī¸  Step: step-1 (resume-skip)
â­ī¸  Step: step-2 (resume-skip)

📍 Step: step-3
✅ step-3 completed in 5ms

âąī¸  Performance Summary:
â†Ēī¸ Resumed from checkpoint: step-2
┌──────────────────────────────────────────────────────┐
│ â­ī¸  step-1                                    skipped │
│ â­ī¸  step-2                                    skipped │
│ ✅ step-3                                         5ms │
└──────────────────────────────────────────────────────┘

With Stop Early

Logs indicate early termination:
await pipeline.run(
  { n: 0 },
  {
    log: { stopwatch: true },
    onStepComplete: (e) => {
      if (e.stepName === 's2') e.stopPipeline()
    },
  },
)
Output:
âšī¸ Early stop requested by: s2

âąī¸  Performance Summary:
┌──────────────────────────────────────────────────────┐
│ ✅ s1                                            10ms │
│ ✅ s2                                             5ms │
└──────────────────────────────────────────────────────┘

âšī¸  Stopped early (requested by s2)

⏰ Total Pipeline Time: 15ms

✨ Pipeline stopped early

Best Practices

  • Use log: { stopwatch: true } during development for detailed insights
  • Use custom logFn to integrate with your logging infrastructure
  • Disable step-level logging for noisy or sensitive operations
  • Use performance summaries to identify bottlenecks
  • Keep logging enabled in production with structured loggers

Build docs developers (and LLMs) love