{"id":35052209,"url":"https://github.com/jonaskahn/nodejs-open-telemetry","last_synced_at":"2026-04-25T13:34:03.277Z","repository":{"id":285803535,"uuid":"959400507","full_name":"jonaskahn/nodejs-open-telemetry","owner":"jonaskahn","description":"Demo nodejs application tracing method for open telemetry","archived":false,"fork":false,"pushed_at":"2025-04-03T11:34:06.000Z","size":346,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-17T05:10:29.966Z","etag":null,"topics":["nodejs","opentelemetry"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonaskahn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-02T18:19:02.000Z","updated_at":"2025-04-03T11:37:19.000Z","dependencies_parsed_at":"2025-04-02T19:38:06.195Z","dependency_job_id":null,"html_url":"https://github.com/jonaskahn/nodejs-open-telemetry","commit_stats":null,"previous_names":["jonaskahn/nodejs-open-telemetry"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jonaskahn/nodejs-open-telemetry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaskahn%2Fnodejs-open-telemetry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaskahn%2Fnodejs-open-telemetry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaskahn%2Fnodejs-open-telemetry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaskahn%2Fnodejs-open-telemetry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonaskahn","download_url":"https://codeload.github.com/jonaskahn/nodejs-open-telemetry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonaskahn%2Fnodejs-open-telemetry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32264427,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nodejs","opentelemetry"],"created_at":"2025-12-27T09:36:28.225Z","updated_at":"2026-04-25T13:34:03.263Z","avatar_url":"https://github.com/jonaskahn.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenTelemetry NodeJS Demo\n\n\u003e A practical demonstration of OpenTelemetry implementation in a NodeJS application\n\nThis project demonstrates how to implement OpenTelemetry tracing in a real-world NodeJS application with multiple services and scheduled jobs.\n\n![Demo](./img/img.png)\n## Quick Start\n\n```bash\n# Start Jaeger for telemetry visualization\ndocker-compose up -d\n\n# Install dependencies\nnpm install\n\n# Run with telemetry enabled (default)\nnpm start\n\n# View traces in Jaeger UI\n# Open http://localhost:16686 in your browser\n```\n\n## Project Overview\n\nThis demo includes:\n\n- **5 Core Services**: User, Data, Notification, Authentication, and Logging\n- **2 Scheduled Jobs**: Data Backup and Report Generation\n- **OpenTelemetry Integration**: Complete tracing with nested spans, context propagation, and error handling\n\n## Key Features Demonstrated\n\n- ✅ **Hierarchical Tracing**: Nested spans showing parent-child relationships in complex operations\n- ✅ **Execution ID Tracking**: Time-based execution IDs for correlating related operations\n- ✅ **Span Attributes**: Contextual information attached to spans for better observability\n- ✅ **Error Handling**: Proper error recording and propagation in spans\n- ✅ **Toggle Telemetry**: Enable/disable telemetry through environment variables\n\n## Adding Telemetry to Your Code: Step-by-Step Guide\n\n### 1. Basic Pattern: The Private Implementation Approach\n\n```javascript\n// BEFORE: Original function\nfunction processOrder(order) {\n  // Implementation\n}\n\n// AFTER: With telemetry\n// Step 1: Create private implementation\nfunction _processOrder(order) {\n  // Same implementation as before\n}\n\n// Step 2: Create public wrapper with telemetry\nfunction processOrder(order) {\n  const executionId = generateExecutionId('order');\n  \n  return telemetry.wrapWithSpan(\n    'processOrder', \n    { 'order.id': order.id, 'execution.id': executionId }, \n    () =\u003e _processOrder(order)\n  );\n}\n```\n\n### 2. Creating Hierarchical Traces\n\n```javascript\n// Parent function\nfunction _processOrder(order) {\n  // Validate order first\n  _validateOrder(order);\n  \n  // Process payment\n  const payment = _processPayment(order.payment);\n  \n  // Create shipment\n  const shipment = _createShipment(order.items, order.address);\n  \n  return { orderId: order.id, status: 'completed' };\n}\n\n// Child function with its own span\nfunction _validateOrder(order) {\n  return telemetry.wrapWithSpan(\n    '_validateOrder',\n    { 'order.id': order.id },\n    () =\u003e {\n      // Validation logic\n      return isValid;\n    }\n  );\n}\n```\n\n### 3. Error Handling in Spans\n\n```javascript\nfunction processOrder(order) {\n  return telemetry.wrapWithSpan(\n    'processOrder', \n    { attributes }, \n    async (span) =\u003e {\n      try {\n        const result = await _processOrder(order);\n        return result;\n      } catch (error) {\n        // Record error in span\n        span.setStatus({\n          code: SpanStatusCode.ERROR,\n          message: error.message\n        });\n        span.recordException(error);\n        throw error;\n      }\n    }\n  );\n}\n```\n\n## Real-World Examples\n\n### Data Backup Job Example\n\nThis example demonstrates a complete implementation of telemetry in a scheduled backup job:\n\n```javascript\n// PUBLIC API\nconst initBackupJob = telemetry.wrapWithSpan(_initBackupJob, 'initBackupJob', {\n  'job.name': 'dataBackupJob',\n  'job.type': 'cron',\n});\n\nfunction performBackup(executionId) {\n  const execId = executionId || generateExecutionId('backup');\n  return telemetry.wrapWithSpan(() =\u003e _performBackup(execId), `performBackup.${execId}`, {\n    'backup.type': 'scheduled',\n    'backup.execution_id': execId,\n  })();\n}\n\n// IMPLEMENTATION DETAILS\nfunction _initBackupJob() {\n  if (!CONFIG.enabled) {\n    loggingService.logInfo('Data backup job is disabled');\n    return false;\n  }\n\n  loggingService.logInfo(`Scheduling data backup job with schedule: ${CONFIG.schedule}`);\n\n  const job = cron.schedule(CONFIG.schedule, () =\u003e {\n    const executionId = generateExecutionId('backup');\n    const executionTracer = telemetry.getTracer(`dataBackupJob.${executionId}`);\n\n    executionTracer.startActiveSpan('backupJob.execution', span =\u003e {\n      try {\n        span.setAttribute('backup.scheduled_time', new Date().toISOString());\n        span.setAttribute('backup.cron_pattern', CONFIG.schedule);\n        span.setAttribute('backup.execution_id', executionId);\n\n        const result = performBackup(executionId);\n\n        span.setAttribute('backup.success', result.success);\n        span.end();\n        return result;\n      } catch (error) {\n        span.recordException(error);\n        span.setStatus({ code: SpanStatusCode.ERROR });\n        span.end();\n        throw error;\n      }\n    });\n  });\n\n  return job;\n}\n```\n\n### Notification Chain Example\n\nThis shows a more complex service with multiple nested levels:\n\n```javascript\n// PUBLIC API\nconst sendNotification = telemetry.wrapWithSpan(\n  _sendNotification,\n  'notificationService.sendNotification',\n  { 'notification.operation': 'send' }\n);\n\n// IMPLEMENTATION DETAILS\nfunction _sendNotification(userId, message, channel = 'email') {\n  try {\n    const notificationId = uuidv4();\n    const content = await _prepareNotificationContent(userId, message, channel);\n    \n    // Record notification\n    notifications[notificationId] = { /*...*/ };\n    \n    // Deliver through chosen channel\n    const deliveryResult = await _deliverNotification(userId, notifications[notificationId], channel);\n    \n    notifications[notificationId].status = 'sent';\n    return notifications[notificationId];\n  } catch (error) {\n    loggingService.logError(`Failed to send notification: ${error.message}`);\n    throw error;\n  }\n}\n\n// Nested functions with their own spans\nfunction _prepareNotificationContent(userId, message, channel) {/*...*/}\nfunction _getUserNotificationPreferences(userId) {/*...*/}\nfunction _getUserDeviceInfo(userId) {/*...*/}\nfunction _deliverNotification(userId, notification, channel) {/*...*/}\n```\n\n## Visualizing Telemetry Implementation with Diagrams\n\n### Report Generation Job - Before vs After Telemetry\n\nThe following diagrams illustrate how report generation works before and after implementing OpenTelemetry:\n\n#### Before Adding Telemetry\n\n```mermaid\nflowchart TD\n    A[Schedule Report Job] --\u003e B[Generate Report]\n    B --\u003e C[Get User Data]\n    B --\u003e D[Process Data]\n    B --\u003e E[Create Report]\n    E --\u003e F[Send Notification]\n    \n    style A fill:#f9f9f9,stroke:#333,stroke-width:1px\n    style B fill:#f9f9f9,stroke:#333,stroke-width:1px\n    style C fill:#f9f9f9,stroke:#333,stroke-width:1px\n    style D fill:#f9f9f9,stroke:#333,stroke-width:1px\n    style E fill:#f9f9f9,stroke:#333,stroke-width:1px\n    style F fill:#f9f9f9,stroke:#333,stroke-width:1px\n    \n    classDef error fill:#ffcccc,stroke:#ff0000,stroke-width:1px\n    classDef success fill:#ccffcc,stroke:#00cc00,stroke-width:1px\n```\n\n**Issues with this approach:**\n- No traceability between operations\n- Difficult to measure performance of individual steps\n- Limited visibility into errors\n- No correlation between related operations\n\n#### After Adding Telemetry\n\n```mermaid\nflowchart TD\n    A[Schedule Report Job] --\u003e|Span: initReportJob| B[Generate Report]\n    B --\u003e|Span: generateUsageReport| C[Get User Data]\n    B --\u003e|Span: generateUsageReport| D[Process Data]\n    B --\u003e|Span: generateUsageReport| E[Create Report]\n    E --\u003e|Span: sendNotification| F[Send Notification]\n    \n    linkStyle 0 stroke:#7f00ff,stroke-width:2px\n    linkStyle 1 stroke:#7f00ff,stroke-width:2px\n    linkStyle 2 stroke:#7f00ff,stroke-width:2px\n    linkStyle 3 stroke:#7f00ff,stroke-width:2px\n    linkStyle 4 stroke:#7f00ff,stroke-width:2px\n    \n    style A fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    style B fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    style C fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    style D fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    style E fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    style F fill:#f0e6ff,stroke:#7f00ff,stroke-width:2px\n    \n    classDef error fill:#ffcccc,stroke:#ff0000,stroke-width:2px\n    classDef success fill:#ccffcc,stroke:#00cc00,stroke-width:2px\n    \n    subgraph Observability[OpenTelemetry Observability]\n        Trace[Trace with ID]\n        ExecID[Execution ID: report-20250405-123456-789]\n        PerfMetrics[Performance Metrics]\n        ErrorTracking[Error Tracking]\n        Spans[Hierarchical Spans]\n    end\n    \n    B --- Trace\n    B --- ExecID\n    C --- PerfMetrics\n    E --- Spans\n    F --- ErrorTracking\n```\n\n**Benefits from telemetry:**\n- Each operation has its own span for detailed tracking\n- Unique execution ID connects all related operations\n- Performance metrics captured for each step\n- Error information recorded with complete context\n- Visual representation in Jaeger UI showing hierarchical relationships\n\n### Execution Flow Difference\n\n#### Traditional Approach\n- Function calls occur linearly\n- Errors might be logged but context is limited\n- No performance metrics for individual operations\n- Difficult to trace request path through complex operations\n\n#### Telemetry-Enhanced Approach\n- Each operation creates spans that capture:\n  - Timing information (start/end)\n  - Operation context (parameters, IDs)\n  - Relationship to parent operations\n  - Error details with stack traces\n  - Custom attributes for business context\n\n## Configuring OpenTelemetry\n\n### Installation\n\n```bash\nnpm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http\n```\n\n### Using telemetry.js as Your Standard Implementation\n\nThe `src/middleware/telemetry.js` file in this project provides a standard, production-ready implementation that you should use as your template. This implementation:\n\n1. Handles initialization and configuration \n2. Provides convenient wrapper methods for tracing\n3. Supports enabling/disabling telemetry at runtime\n4. Includes proper error handling and context propagation\n\n#### Key Parts of telemetry.js\n\n```javascript\n// Create a singleton Telemetry class\nclass Telemetry {\n  constructor(serviceName, serviceVersion, enabled = true) {\n    this.serviceName = serviceName || process.env.SERVICE_NAME || 'default-service';\n    this.serviceVersion = serviceVersion || process.env.SERVICE_VERSION || '1.0.0';\n    this.enabled = enabled;\n    this.initialized = false;\n  }\n  \n  // Enable/disable telemetry at runtime\n  setEnabled(enabled) {\n    this.enabled = !!enabled;\n    return this;\n  }\n  \n  // Core tracing method - wrap any function with tracing\n  wrapWithSpan(fn, name, attributes = {}) {\n    // If telemetry is disabled, just return the original function\n    if (!this.enabled) {\n      return (...args) =\u003e fn(...args);\n    }\n    \n    return (...args) =\u003e {\n      const tracer = this.getTracer(name.split('.')[0] || 'default');\n      return tracer.startActiveSpan(name, { attributes }, span =\u003e {\n        try {\n          const result = fn(...args);\n          \n          // Handle promises\n          if (result \u0026\u0026 typeof result.then === 'function') {\n            return result\n              .then(value =\u003e {\n                span.end();\n                return value;\n              })\n              .catch(error =\u003e {\n                span.setStatus({ code: SpanStatusCode.ERROR });\n                span.recordException(error);\n                span.end();\n                throw error;\n              });\n          }\n          \n          span.end();\n          return result;\n        } catch (error) {\n          span.setStatus({ code: SpanStatusCode.ERROR });\n          span.recordException(error);\n          span.end();\n          throw error;\n        }\n      });\n    };\n  }\n  \n  // Additional methods...\n}\n\n// Create singleton instance\nconst telemetry = new Telemetry(\n  process.env.SERVICE_NAME,\n  process.env.SERVICE_VERSION,\n  process.env.TELEMETRY_ENABLED !== 'false'\n);\n\n// Export the singleton\nmodule.exports = telemetry;\n```\n\n#### Why Use This as a Standard\n\n1. **Consistent API**: Provides a unified API across your codebase\n2. **Environment-aware**: Automatically configures from environment variables\n3. **Toggle Support**: Can easily disable telemetry without code changes\n4. **Error Handling**: Properly records errors and ends spans\n5. **Promise Support**: Handles both synchronous and asynchronous code\n6. **Lightweight**: Minimal overhead when disabled\n7. **Maintainable**: Centralized configuration makes updates easier\n\nTo use this in your project, copy the `telemetry.js` file to your project and start using the exported `telemetry` object throughout your codebase.\n\n### Enable/Disable Telemetry\n\n#### Using Environment Variables\n\nCreate a `.env` file:\n\n```\n# Enable/disable telemetry\nTELEMETRY_ENABLED=true\n\n# Service configuration\nSERVICE_NAME=my-service-name\nSERVICE_VERSION=1.0.0\n\n# Exporter configuration\nOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces\n```\n\n#### Using Runtime API\n\n```javascript\n// Disable telemetry at runtime\ntelemetry.setEnabled(false);\n\n// Check if telemetry is enabled\nconsole.log(`Telemetry enabled: ${telemetry.isEnabled()}`);\n```\n\n#### Running with Different Configurations\n\n```bash\n# Run with telemetry enabled (default)\nnpm start\n\n# Run with telemetry explicitly enabled\nnpm run start:with-telemetry\n\n# Run with telemetry disabled\nnpm run start:no-telemetry\n\n# Check current configuration\nnpm run test:config\n```\n\n## Viewing Traces in Jaeger\n\nAfter running the application, open Jaeger UI:\n\n1. Go to http://localhost:16686\n2. Select a service from the dropdown (e.g., \"service-cronjob-demo\")\n3. Click \"Find Traces\"\n4. Select a trace to see detailed span information\n\n### Interpreting Traces\n\n- **Trace View**: Shows the complete execution flow with nested spans\n- **Timeline**: Visual representation of execution duration\n- **Tags/Attributes**: Contains additional context (look for execution_id, etc.)\n- **Logs**: Shows events recorded during execution\n\n### Finding Specific Information\n\n- Use the search feature to find traces with specific attributes\n- Example: Search for `backup.execution_id=backup-20250403-*` to find all backups from a specific day\n- Click on a span to view detailed attributes (additional context that might not be visible in the main view)\n\n## Running Tests\n\n```bash\n# Test notification chain (5 nested levels)\nnpm run test:notification-chain\n\n# Test report job initialization\nnpm run test:report-job\n```\n\n## Project Structure\n\n```\n├── src/\n│   ├── services/           # Service modules\n│   ├── jobs/               # Scheduled job modules\n│   ├── middleware/\n│   │   └── telemetry.js    # OpenTelemetry configuration\n│   └── utils/\n│       └── idGenerator.js  # Time-based execution ID generator\n└── tests/                  # Test examples showing telemetry in action\n```\n\n---\n\nFor more details, refer to the official [OpenTelemetry documentation](https://opentelemetry.io/docs/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonaskahn%2Fnodejs-open-telemetry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonaskahn%2Fnodejs-open-telemetry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonaskahn%2Fnodejs-open-telemetry/lists"}