Skip to content

CeranaStudio/tsc-opentelemetry-instrumentation

Repository files navigation

@waitingliou/tsc-otel-instrumentation

TypeScript Build Status License: Apache 2.0 OpenTelemetry npm version

An automatic instrumentation tool, complimentary to OpenTelemetry to enable tracing in your business logic without messing up your codebase.

test

Specification

A TypeScript transformer that automatically instruments your business logic (class-level function calls) with OpenTelemetry spans at compile time through AST weaving, achieving true "application-level transparency" as described in Google's Dapper paper.

  • Zero-touch: No code changes required in your business logic
  • Deep Tracing: Automatically traces all function calls, including private methods
  • Minimal Runtime Overhead: Instead of runtime monkey-patch → compile-time patching

Design Philosophy This transformer uses a strategically designed AST visitor that specifically targets class-level method declarations to achieve optimal performance and precision in business logic tracing.

Principles:

  • Precision over Volume: Focuses on business logic methods in classes where most application logic resides
  • Performance Optimization: Avoids instrumenting every methods to prevent span explosion and performance degradation
  • Strategic Targeting: Selectively instruments ts.isMethodDeclaration nodes which typically contain core business operations
  • Noise Reduction: Excludes utility functions, helpers, and nested functions that would create excessive telemetry noise

This intentional scope limitation ensures you get high-value tracing data without overwhelming your observability infrastructure or impacting application performance.

OpenTelemetry Compatible: This package follows OpenTelemetry Semantic Conventions and integrates seamlessly with the OpenTelemetry ecosystem.

Installation

# Install as development dependency (compile-time only)
npm install @waitingliou/tsc-otel-instrumentation --save-dev

Important

This package is a compile-time TypeScript transformer that modifies your code during the build process. The generated JavaScript code has no runtime dependency on this package - only on @opentelemetry/api.

Usage:

1. Configure TypeScript Compiler

Add the transformer to your project's tsconfig.json:

{
  "compilerOptions": {
    "plugins": [
      {
        "transform": "@waitingliou/tsc-otel-instrumentation/transformer",
        "include": [
          "**/*Service.ts",
          "**/*Repository.ts"
        ],
        "exclude": [
          "**/*.test.ts",
          "**/*.spec.ts"
        ],
        "instrumentPrivateMethods": true,
        "spanNamePrefix": "myapp",
        "autoInjectTracer": true
      }
    ]
  }
}

2. Install and Configure ts-patch

ts-patch patches your local TypeScript installation so the official tsc can load custom transformers from compilerOptions.plugins.

# Install ts-patch as development dependency  
npm install ts-patch --save-dev
npx ts-patch install -s

I recommend adding a postinstall step to your npm scripts to ensure teammates automatically set up ts-patch after installing dependencies:

"scripts": {
    "build": "tsc",
    "dev": "tsc --watch",
    "test": "jest",
    "postinstall": "ts-patch install -s"
},

3. Add OpenTelemetry Dependencies

Install OpenTelemetry API as a production dependency (required at runtime) in your project:

# Runtime dependency for generated instrumentation code
npm install @opentelemetry/api

4. Configuration Options

Complete tsconfig.json Compile Plugin Configuration Example

{
  "compilerOptions": {
    "plugins": [
      {
        "transform": "@waitingliou/tsc-otel-instrumentation/transformer",
        "include": [
          "**/*Service.ts",
          "**/*Repository.ts",
          "src/business/**/*.ts"
        ],
        "exclude": [
          "**/*.test.ts",
          "**/*.spec.ts",
          "**/node_modules/**"
        ],
        "instrumentPrivateMethods": true,
        "spanNamePrefix": "myapp",
        "autoInjectTracer": true,
        "commonAttributes": {
          "service.name": "user-management-service",
          "service.version": "1.2.0",
          "deployment.environment": "production"
        },
        "includeMethods": ["create*", "updateUser", "get?ser"],
        "excludeMethods": ["toString", "test*", "deprecated"],
        "debug": false,
        "logLevel": "warn",
        "maxMethodsPerFile": 50
      }
    ]
  }
}

Configuration Options Reference

Option Type Required Default Description
include string[] - Glob patterns for files to instrument. Supports standard glob syntax: **/*Service.ts, src/business/**/*.ts
exclude string[] ["**/*.test.ts", "**/*.spec.ts", "**/node_modules/**"] Glob patterns for files to skip. Takes priority over include
instrumentPrivateMethods boolean false Include methods starting with _ (underscore). Example: _privateMethod
spanNamePrefix string "ts-otel-weaver" Prefix for all span names. Final span: {prefix}.{ClassName}.{methodName}
autoInjectTracer boolean true Automatically add import { trace } from "@opentelemetry/api" to instrumented files
commonAttributes Record<string, string> {} Key-value pairs added to ALL spans. Use for service metadata, environment info, etc.
includeMethods string[] [] Method name patterns to instrument. Supports exact names and glob patterns (*, ?). Highest priority - only these methods will be instrumented if specified
excludeMethods string[] ["constructor", "toString", "valueOf", "toJSON", "inspect"] Method name patterns to skip. Supports exact names and glob patterns (*, ?). Ignored if includeMethods is set
debug boolean false Enable detailed transformation logs during compilation
logLevel 'none' | 'error' | 'warn' | 'info' | 'debug' 'warn' Control console output verbosity
maxMethodsPerFile number 100 Safety limit to prevent accidentally instrumenting too many methods

Supported Function Types

This transformer automatically detects and properly instruments various TypeScript method types based on the design principles outlined in the Specification section.

Function Type Support Matrix

Function Type Status Scope Sync Async Generator Example
Class Methods Supported Class getUser(id: string): User
Private Methods Supported Class private _validateUser(user: User)
Static Methods Supported Class static getInstance(): UserService
Async Methods Supported Class async createUser(data: UserData)
Generator Methods Supported Class *generateSequence(count: number)
Async Generators Supported Class async *processUsers()
Arrow Function Properties 🎯 Excluded by Design Class getUser = (id: string) => {}
Function Expressions 🎯 Excluded by Design Class getUser: () => User = function() {}
Standalone Functions 🎯 Excluded by Design Global function getUserById(id: string)
Arrow Functions 🎯 Excluded by Design Global const processUser = (user) => {}
Nested Functions 🎯 Excluded by Design Function Functions inside other functions
Object Methods 🎯 Excluded by Design Object const obj = { method() {} }

Auto-generated spans structure examples

When you call a method, it automatically generates a tracing structure like:

myapp.UserService.getUser
├──  myapp.UserService.createUser
│   ├── myapp.CacheService.get
│   ├── myapp.UserService._validateUserData (private method)
│   ├── myapp.UserRepository.save
│   │   ├── myapp.UserRepository._validateUser
│   │   └── myapp.UserRepository._persistUser  
├── myapp.UserService._processUserData
│   ├── myapp.ValidationService.validate
│   └── myapp.TransformService.transform
└── myapp.NotificationService.sendWelcome

Project Examples

Name Description
Node.js & Express.js A comprehensive example showcasing how to integrate Express.js + Nodejs runtime projects with @waitingliou/tsc-otel-instrumentation, and demo a real-world tracing sceanario with Grafana Cloud.
Bun & Hono.js A comprehensive example showcasing how to integrate Hono.js + Bun runtime projects with @waitingliou/tsc-otel-instrumentation, and demo a real-world tracing sceanario with Grafana Cloud.

Debugging and Verification

Compile and check:

npm run build

# check import
head -5 dist/your-service.js
# should see:
# import { trace, SpanStatusCode, SpanKind } from "@opentelemetry/api";
# const tracer = trace.getTracer("@waitingliou/tsc-otel-instrumentation");

Compile-Time Performance Impact

As a compile-time transformer, this package adds instrumentation during the TypeScript compilation phase. Below is a performance benchmark measured on a production-scale codebase to help you understand the expected overhead.

mem

Benchmark Environment

Test Subject:

  • Project: Core package of app.pathors.com
  • Nature: Production-grade enterprise application
  • Purpose: Real-world performance evaluation on actual production codebase

Codebase Scale:

  • Files: 1,652
  • Lines of Library: 10,247
  • Lines of Definitions: 274,130
  • Lines of TypeScript: 9,971

Test Environment:

  • OS: macOS 14.2 (Darwin 23.2.0)
  • CPU: Apple Silicon (M-3)
  • Memory: 16GB
  • Node.js: v20.x
  • TypeScript: v5.x
  • ts-patch: v3.x

Measurement Method:

  • Tool: /usr/bin/time -l (memory & time) + tsc --extendedDiagnostics (compilation phases)
  • Runs: 10 iterations, averaged for statistical reliability
  • Variants: Without transformer (Baseline) vs. With transformer (Instrumented)

Performance Results

Results (mean of 10 runs)

Metric Baseline With Instrumentation Overhead
Real Time 3.783s 4.449s +17.6%
Total Compile Time 3.173s 3.571s +12.6%
Max RSS 469.22 MB 488.35 MB +4.1%
TSC Memory 351.22 MB 387.31 MB +10.3%
Parse Time 1.342s 1.455s +8.4%
Bind Time 0.416s 0.410s −1.4%
Check Time 1.080s 1.207s +11.8%
Emit Time 0.337s 0.499s +48.1%

Analysis

Key Observations:

  • Overall Impact: +17.6% increase in real compile time, +12.6% in total compilation time
  • Memory Overhead: Modest increase of ~4-10% in memory consumption
  • Emit Phase: The most significant impact (+48.1%) occurs during code emission, which is expected as the transformer injects tracing code into method bodies
  • Type Checking: Minor impact (+11.8%) on type checking phase
  • Parse/Bind: Minimal impact on parsing (+8.4%) and binding phases

Trade-off Note: This is a one-time compile-time cost that enables zero-runtime overhead tracing. Unlike runtime instrumentation libraries, there's no performance penalty during application execution.

📋 Requirements

Development Dependencies

  • Node.js >= 18.0.0
  • TypeScript >= 4.5.0
  • ts-patch for transformer integration

Runtime Dependencies

  • @opentelemetry/api >= 1.9.0 (for generated instrumentation code)

📄 License

Apache-2.0 License - see LICENSE file for details.

🙏 Acknowledgments

  • Inspired by runtime monkey-patching of OpenTelemetry Instrumentation libraries
  • Thanks to @Pathors. As a intern in @Pathors, I was inspired with the idea of developing this tool during the process of integrating OTel.
  • Leverages TypeScript Compiler API for AST transformations

🔗 Related Projects

About

OpenTelemetry Instrumentation in your business logic through TypeScript Compiler

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published