kafka-crab-js 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -125
- package/dist/diagnostics/channels.d.ts +138 -0
- package/dist/diagnostics/index.d.ts +7 -0
- package/dist/diagnostics/instrumentation.d.ts +31 -0
- package/dist/index.cjs +393 -603
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -3
- package/dist/index.js +377 -603
- package/dist/index.js.map +4 -4
- package/dist/kafka-client.d.ts +19 -13
- package/js-binding.cjs +52 -52
- package/js-binding.d.ts +2 -0
- package/js-binding.js +52 -52
- package/package.json +17 -40
- package/dist/otel/constants.d.ts +0 -40
- package/dist/otel/instrumentation.d.ts +0 -23
- package/dist/otel/types.d.ts +0 -47
- package/dist/otel/utils.d.ts +0 -15
package/README.md
CHANGED
|
@@ -10,7 +10,81 @@ A lightweight, flexible, and reliable Kafka client for JavaScript/TypeScript. It
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
## What's New in Version
|
|
13
|
+
## What's New in Version 3.0.0
|
|
14
|
+
|
|
15
|
+
### BREAKING CHANGES ⚠️
|
|
16
|
+
|
|
17
|
+
**OpenTelemetry instrumentation has been moved to a separate package: `kafka-crab-js-otel`**
|
|
18
|
+
|
|
19
|
+
This change reduces the core package size and makes OTEL an opt-in dependency.
|
|
20
|
+
|
|
21
|
+
#### Migration from v2.x
|
|
22
|
+
|
|
23
|
+
**Before (v2.x):**
|
|
24
|
+
```javascript
|
|
25
|
+
import { KafkaClient } from 'kafka-crab-js'
|
|
26
|
+
|
|
27
|
+
const client = new KafkaClient({
|
|
28
|
+
brokers: 'localhost:9092',
|
|
29
|
+
clientId: 'my-app',
|
|
30
|
+
otel: {
|
|
31
|
+
serviceName: 'my-service',
|
|
32
|
+
metrics: { enabled: true },
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**After (v3.x):**
|
|
38
|
+
```javascript
|
|
39
|
+
import { KafkaClient } from 'kafka-crab-js'
|
|
40
|
+
import { enableOtelInstrumentation, endSpan } from 'kafka-crab-js-otel'
|
|
41
|
+
|
|
42
|
+
// 1. Enable OTEL instrumentation BEFORE creating client
|
|
43
|
+
// Note: serviceName is set via OTEL SDK Resource, not here
|
|
44
|
+
enableOtelInstrumentation({
|
|
45
|
+
metrics: { enabled: true },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// 2. Create client with diagnostics enabled
|
|
49
|
+
const client = new KafkaClient({
|
|
50
|
+
brokers: 'localhost:9092',
|
|
51
|
+
clientId: 'my-app',
|
|
52
|
+
diagnostics: true, // Required for OTEL to receive events
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 3. For consumers, call endSpan() when processing is complete
|
|
56
|
+
const message = await consumer.recv()
|
|
57
|
+
// ... process message ...
|
|
58
|
+
endSpan(message)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Key Changes
|
|
62
|
+
|
|
63
|
+
| v2.x | v3.x |
|
|
64
|
+
|------|------|
|
|
65
|
+
| `otel` config in KafkaClient | `enableOtelInstrumentation()` from `kafka-crab-js-otel` |
|
|
66
|
+
| Automatic span ending | Call `endSpan(message)` manually for consumers |
|
|
67
|
+
| N/A | `diagnostics: true` required in KafkaClient config |
|
|
68
|
+
| OTEL bundled | Install `kafka-crab-js-otel` separately |
|
|
69
|
+
|
|
70
|
+
#### New Package Structure
|
|
71
|
+
|
|
72
|
+
| Package | Description |
|
|
73
|
+
|---------|-------------|
|
|
74
|
+
| `kafka-crab-js` | Core Kafka client (producer, consumer, streams) |
|
|
75
|
+
| `kafka-crab-js-otel` | OpenTelemetry instrumentation (separate install) |
|
|
76
|
+
|
|
77
|
+
### Internal Improvements
|
|
78
|
+
- Uses Node.js `diagnostics_channel` for observability (zero overhead when not subscribed)
|
|
79
|
+
- Cleaner separation of concerns between core and observability
|
|
80
|
+
- Smaller bundle size for users who don't need OTEL
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Previous Versions
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary>What's New in Version 2.1.0</summary>
|
|
14
88
|
|
|
15
89
|
### New Features
|
|
16
90
|
|
|
@@ -40,103 +114,25 @@ A lightweight, flexible, and reliable Kafka client for JavaScript/TypeScript. It
|
|
|
40
114
|
- GitHub Actions updated to v6
|
|
41
115
|
- Improved caching with actions/cache v4
|
|
42
116
|
|
|
43
|
-
|
|
117
|
+
</details>
|
|
44
118
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- OTEL improvements are transparent and require no code changes
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## What's New in Version 2.0.0
|
|
119
|
+
<details>
|
|
120
|
+
<summary>What's New in Version 2.0.0</summary>
|
|
52
121
|
|
|
53
122
|
### BREAKING CHANGES ⚠️
|
|
54
123
|
|
|
55
|
-
This major version includes important breaking changes that improve API consistency and memory management:
|
|
56
|
-
|
|
57
124
|
1. **Consumer Configuration API Changes**:
|
|
58
125
|
- **REMOVED**: `createTopic` field from `ConsumerConfiguration`
|
|
59
126
|
- **Migration**: Use `createTopic` field in `TopicPartitionConfig` instead when subscribing to topics
|
|
60
|
-
- **Before**: `new KafkaClient().createConsumer({ groupId: 'test', createTopic: true })`
|
|
61
|
-
- **After**: `consumer.subscribe([{ topic: 'my-topic', createTopic: true }])`
|
|
62
127
|
|
|
63
128
|
2. **Stream Lifecycle Management**:
|
|
64
129
|
- Stream consumers now properly implement Node.js stream lifecycle methods (`_destroy()`)
|
|
65
130
|
- **Memory leak prevention**: Streams now automatically disconnect Kafka consumers during destruction
|
|
66
|
-
- Better resource cleanup ensures no hanging connections or memory leaks
|
|
67
|
-
- Error handling during stream destruction is improved with proper error propagation
|
|
68
|
-
|
|
69
|
-
3. **Stream Error Handling**:
|
|
70
|
-
- Stream errors now use `destroy(error)` instead of `emit('error')` for proper stream termination
|
|
71
|
-
- This ensures streams are properly closed when errors occur, preventing resource leaks
|
|
72
|
-
- Error propagation follows Node.js stream standards more closely
|
|
73
|
-
|
|
74
|
-
4. **Rust Configuration Simplification**:
|
|
75
|
-
- Removed `MAX_BATCH_TIMEOUT` constant - now uses only `DEFAULT_BATCH_TIMEOUT` (1000ms)
|
|
76
|
-
- Simplified timeout validation logic in Rust layer
|
|
77
|
-
- Invalid timeout values (< 1ms) now log warnings and use default timeout
|
|
78
|
-
|
|
79
|
-
5. **Code Quality Improvements**:
|
|
80
|
-
- Fixed linting violations: short variable names and `any` type usage
|
|
81
|
-
- Better TypeScript typing throughout the codebase
|
|
82
|
-
- Removed redundant validation functions for cleaner code
|
|
83
|
-
|
|
84
|
-
### Major Updates:
|
|
85
|
-
|
|
86
|
-
1. **Performance Benchmark Suite**:
|
|
87
|
-
- Added comprehensive benchmark suite comparing performance against `kafkajs`, `node-rdkafka`, and `@platformatic/rdkafka`
|
|
88
|
-
- Benchmark scripts with automated setup and data generation
|
|
89
|
-
- Performance testing for both serial and batch processing modes
|
|
90
|
-
- Supports configurable iterations and message sizes for realistic testing scenarios
|
|
91
|
-
|
|
92
|
-
2. **Enhanced Stream Consumer Configuration**:
|
|
93
|
-
- Stream consumer constructor now accepts optional `ReadableOptions` parameter
|
|
94
|
-
- Default `objectMode: true` for better stream processing ergonomics
|
|
95
|
-
- Improved batch processing configuration with proper defaults
|
|
96
|
-
|
|
97
|
-
3. **Flexible Configuration System**:
|
|
98
|
-
- **BREAKING**: Configuration properties now accept `Record<string, any>` instead of `Record<string, string>`
|
|
99
|
-
- Enhanced type flexibility for complex configuration values (numbers, booleans, objects)
|
|
100
|
-
- Better support for advanced librdkafka configuration options
|
|
101
|
-
|
|
102
|
-
4. **Enhanced Topic Management**:
|
|
103
|
-
- Added `createTopic`, `numPartitions`, and `replicas` options to `TopicPartitionConfig`
|
|
104
|
-
- Improved topic creation control during consumer subscription
|
|
105
|
-
- Better handling of partition assignment and topic setup
|
|
106
|
-
|
|
107
|
-
5. **Build System Improvements**:
|
|
108
|
-
- Updated TypeScript configuration with modular `tsconfig.base.json`
|
|
109
|
-
- Added `typecheck` script for standalone type checking
|
|
110
|
-
- Enhanced build pipeline with better declaration file generation
|
|
111
|
-
- Updated to pnpm@10.15.1 for improved dependency management
|
|
112
|
-
|
|
113
|
-
6. **Development Tools Enhancement**:
|
|
114
|
-
- Enhanced oxlint configuration with benchmark-specific rules
|
|
115
|
-
- Added support for benchmark directory linting and formatting
|
|
116
|
-
- Improved development workflow with better error handling rules
|
|
117
|
-
|
|
118
|
-
7. **Consumer Topic Creation Control** (v1.9.0):
|
|
119
|
-
- Added fine-grained control over consumer topic creation behavior
|
|
120
|
-
- Enhanced consistency in topic management and cleanup processes
|
|
121
|
-
- Improved handling of topic creation inconsistencies during consumer operations
|
|
122
|
-
|
|
123
|
-
8. **Enhanced Manual Commit Testing** (v1.9.0):
|
|
124
|
-
- Consolidated manual commit tests for better reliability and coverage
|
|
125
|
-
- Added comprehensive integration tests with PostRebalance event handling
|
|
126
|
-
- Tests now verify offset persistence across consumer restarts and batch processing
|
|
127
|
-
|
|
128
|
-
9. **Async Consumer Commit** (v1.8.0 - BREAKING):
|
|
129
|
-
- **BREAKING**: The `consumer.commit()` method is now async and must be awaited
|
|
130
|
-
- **Before**: `consumer.commit(topic, partition, offset, 'Sync')`
|
|
131
|
-
- **After**: `await consumer.commit(topic, partition, offset, 'Sync')`
|
|
132
|
-
- This change improves performance by using `spawn_blocking` for non-blocking operations
|
|
133
131
|
|
|
134
|
-
|
|
132
|
+
3. **Async Consumer Commit**:
|
|
133
|
+
- **BREAKING**: The `consumer.commit()` method is now async and must be awaited
|
|
135
134
|
|
|
136
|
-
|
|
137
|
-
- **Stream memory management** - streams now automatically clean up resources
|
|
138
|
-
- **Error handling** - streams will properly terminate on errors (existing error handling code still works)
|
|
139
|
-
- **Performance** - simplified timeout logic may provide marginal performance improvements
|
|
135
|
+
</details>
|
|
140
136
|
|
|
141
137
|
---
|
|
142
138
|
|
|
@@ -506,12 +502,12 @@ You can find some examples on the [example](https://github.com/flash-tecnologia/
|
|
|
506
502
|
| Property | Type | Default | Description |
|
|
507
503
|
| --- | --- | --- | --- |
|
|
508
504
|
| `brokers` | `string` || List of brokers to connect to |
|
|
509
|
-
| `clientId` | `string`
|
|
505
|
+
| `clientId` | `string` | `"rdkafka"` | Client id to use for the connection |
|
|
510
506
|
| `securityProtocol` | `SecurityProtocol` || Security protocol to use (PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL) |
|
|
511
507
|
| `logLevel` | `string` | `info` | Log level for the client |
|
|
512
508
|
| `brokerAddressFamily` | `string` | `"v4"` | Address family to use for the connection (v4, v6) |
|
|
513
|
-
| `configuration` | `Record<string, any>` | `{}` | Additional configuration options for the client
|
|
514
|
-
| `
|
|
509
|
+
| `configuration` | `Record<string, any>` | `{}` | Additional configuration options for the client |
|
|
510
|
+
| `diagnostics` | `boolean` | `false` | **v3.0.0+**: Enable diagnostics channel for OTEL instrumentation |
|
|
515
511
|
|
|
516
512
|
### ConsumerConfiguration
|
|
517
513
|
|
|
@@ -565,29 +561,49 @@ Both methods support `'Sync'` and `'Async'` commit modes.
|
|
|
565
561
|
|
|
566
562
|
You can see the available options here: [librdkafka](https://docs.confluent.io/platform/current/clients/librdkafka/html/md_CONFIGURATION.html).
|
|
567
563
|
|
|
568
|
-
### OpenTelemetry
|
|
564
|
+
### OpenTelemetry Instrumentation
|
|
569
565
|
|
|
570
|
-
|
|
566
|
+
> **Note:** Starting with v3.0.0, OpenTelemetry instrumentation has been moved to a separate package: [`kafka-crab-js-otel`](https://www.npmjs.com/package/kafka-crab-js-otel)
|
|
571
567
|
|
|
572
|
-
|
|
568
|
+
#### Installation
|
|
569
|
+
|
|
570
|
+
```bash
|
|
571
|
+
npm install kafka-crab-js-otel @opentelemetry/api
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
#### Usage
|
|
575
|
+
|
|
576
|
+
```javascript
|
|
577
|
+
import { KafkaClient } from 'kafka-crab-js'
|
|
578
|
+
import { enableOtelInstrumentation, endSpan } from 'kafka-crab-js-otel'
|
|
579
|
+
|
|
580
|
+
// Enable instrumentation before creating client
|
|
581
|
+
// Note: serviceName is configured via OTEL SDK Resource
|
|
582
|
+
enableOtelInstrumentation({
|
|
583
|
+
metrics: { enabled: true },
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
// Create client with diagnostics enabled
|
|
573
587
|
const client = new KafkaClient({
|
|
574
|
-
brokers: 'localhost:
|
|
575
|
-
clientId: '
|
|
576
|
-
|
|
577
|
-
serviceName: 'payments-api',
|
|
578
|
-
ignoreTopics: topic => topic.startsWith('internal.'),
|
|
579
|
-
messageHook: (span, message) => {
|
|
580
|
-
span.setAttribute('app.message.key', message.key?.toString('utf8'))
|
|
581
|
-
},
|
|
582
|
-
},
|
|
588
|
+
brokers: 'localhost:9092',
|
|
589
|
+
clientId: 'my-app',
|
|
590
|
+
diagnostics: true,
|
|
583
591
|
})
|
|
592
|
+
|
|
593
|
+
// For consumers, call endSpan() when done processing
|
|
594
|
+
const message = await consumer.recv()
|
|
595
|
+
// ... process message ...
|
|
596
|
+
endSpan(message)
|
|
584
597
|
```
|
|
585
598
|
|
|
586
|
-
|
|
599
|
+
For complete configuration options and examples, see the [kafka-crab-js-otel README](../kafka-crab-js-otel/README.md).
|
|
600
|
+
|
|
601
|
+
#### Examples
|
|
587
602
|
|
|
588
|
-
|
|
589
|
-
-
|
|
590
|
-
-
|
|
603
|
+
See comprehensive examples in the `example/` directory:
|
|
604
|
+
- `example/otel-tracing-example.mjs` - Complete tracing setup with Jaeger
|
|
605
|
+
- `example/otel-metrics-example.mjs` - Metrics collection with Prometheus
|
|
606
|
+
- `example/README.md` - Full documentation for all examples
|
|
591
607
|
|
|
592
608
|
## Performance Benchmarks
|
|
593
609
|
|
|
@@ -606,33 +622,33 @@ node benchmark/consumer.ts
|
|
|
606
622
|
|
|
607
623
|
### Benchmark Results
|
|
608
624
|
|
|
609
|
-
*Benchmarks run on macOS with Apple M1 chip processing 50,000 messages*
|
|
625
|
+
*Benchmarks run on macOS with Apple M1 chip processing 50,000 messages (December 2024)*
|
|
610
626
|
|
|
611
627
|
```
|
|
612
628
|
╔════════════════════════╤═════════╤══════════════════╤═══════════╤══════════════════════════╗
|
|
613
629
|
║ Slower tests │ Samples │ Result │ Tolerance │ Difference with previous ║
|
|
614
630
|
╟────────────────────────┼─────────┼──────────────────┼───────────┼──────────────────────────╢
|
|
615
|
-
║ kafkajs │ 50000 │
|
|
616
|
-
║
|
|
617
|
-
║
|
|
618
|
-
║ node-rdkafka (
|
|
631
|
+
║ kafkajs │ 50000 │ 834.12 op/sec │ ± 0.22 % │ ║
|
|
632
|
+
║ node-rdkafka (evented) │ 84115 │ 24922.67 op/sec │ ± 74.82 % │ + 2887.91 % ║
|
|
633
|
+
║ kafka-crab-js (serial) │ 50000 │ 43213.86 op/sec │ ± 3.46 % │ + 73.39 % ║
|
|
634
|
+
║ node-rdkafka (stream) │ 50000 │ 49805.32 op/sec │ ± 27.10 % │ + 15.25 % ║
|
|
619
635
|
╟────────────────────────┼─────────┼──────────────────┼───────────┼──────────────────────────╢
|
|
620
636
|
║ Fastest test │ Samples │ Result │ Tolerance │ Difference with previous ║
|
|
621
637
|
╟────────────────────────┼─────────┼──────────────────┼───────────┼──────────────────────────╢
|
|
622
|
-
║ kafka-crab-js (batch) │ 50000 │
|
|
638
|
+
║ kafka-crab-js (batch) │ 50000 │ 205985.31 op/sec │ ± 16.53 % │ + 313.58 % ║
|
|
623
639
|
╚════════════════════════╧═════════╧══════════════════╧═══════════╧══════════════════════════╝
|
|
624
640
|
```
|
|
625
641
|
|
|
626
642
|
The benchmark suite compares:
|
|
627
|
-
- **kafka-crab-js (serial)**: Single message processing - **
|
|
628
|
-
- **kafka-crab-js (batch)**: Batch message processing - **
|
|
629
|
-
- **node-rdkafka (evented)**: Event-based processing - **
|
|
630
|
-
- **node-rdkafka (stream)**: Stream-based processing - **
|
|
631
|
-
- **kafkajs**: Official KafkaJS client - **
|
|
643
|
+
- **kafka-crab-js (serial)**: Single message processing - **43,214 ops/sec**
|
|
644
|
+
- **kafka-crab-js (batch)**: Batch message processing - **205,985 ops/sec** (fastest)
|
|
645
|
+
- **node-rdkafka (evented)**: Event-based processing - **24,923 ops/sec**
|
|
646
|
+
- **node-rdkafka (stream)**: Stream-based processing - **49,805 ops/sec**
|
|
647
|
+
- **kafkajs**: Official KafkaJS client - **834 ops/sec**
|
|
632
648
|
|
|
633
649
|
Performance characteristics:
|
|
634
|
-
- **
|
|
635
|
-
- **High throughput**: Batch processing provides
|
|
650
|
+
- **52x faster than kafkajs** in serial mode, **247x faster in batch mode**
|
|
651
|
+
- **High throughput**: Batch processing provides 4.8x performance improvement over serial mode
|
|
636
652
|
- **Low latency**: Optimized for both single and batch message processing
|
|
637
653
|
- **Memory efficient**: Lock-free data structures minimize memory overhead
|
|
638
654
|
- **Concurrent processing**: Zero-contention concurrent operations
|
|
@@ -716,37 +732,66 @@ npx tsx benchmark/consumer.ts
|
|
|
716
732
|
|
|
717
733
|
## OpenTelemetry Instrumentation
|
|
718
734
|
|
|
735
|
+
> **Note:** Starting with v3.0.0, OpenTelemetry instrumentation has been moved to a separate package: [`kafka-crab-js-otel`](https://www.npmjs.com/package/kafka-crab-js-otel)
|
|
736
|
+
|
|
719
737
|
Kafka Crab JS offers turnkey tracing for Kafka workloads:
|
|
720
738
|
|
|
721
739
|
- **Seamless propagation** – Producer instrumentation injects `traceparent`/`tracestate` into Kafka headers while retaining any existing headers (including `Buffer` values) so downstream systems continue to see custom metadata.
|
|
722
740
|
- **Consumer & stream coverage** – Standard consumers, batch consumers, and `createStreamConsumer` streams emit spans that include consumer group, topic, partition, offset, and batch size semantics.
|
|
723
741
|
- **Hook-friendly spans** – Both `messageHook` and `producerHook` callbacks run inside the active span context, simplifying attribute decoration or error handling.
|
|
724
|
-
- **
|
|
742
|
+
- **Zero overhead when disabled** – Uses Node.js `diagnostics_channel` for near-zero cost when OTEL is not active.
|
|
743
|
+
|
|
744
|
+
### Consumer span lifecycle (important)
|
|
745
|
+
|
|
746
|
+
Kafka Crab JS creates consumer `process <topic>` spans, but only your application knows when processing is complete.
|
|
747
|
+
|
|
748
|
+
- For single-message consumers, call `endSpan(message)` when you're done processing the message.
|
|
749
|
+
- For batch consumers, call `endSpan(batch)` when you're done processing the batch.
|
|
750
|
+
- The `endSpan()` helper is exported from `kafka-crab-js-otel`.
|
|
751
|
+
|
|
752
|
+
This closes the span(s) and (when metrics are enabled) records `messaging.process.duration`.
|
|
753
|
+
|
|
754
|
+
### Global instrumentation (singleton)
|
|
755
|
+
|
|
756
|
+
`kafka-crab-js-otel` uses a process-wide OpenTelemetry instrumentation singleton.
|
|
757
|
+
|
|
758
|
+
- Call `enableOtelInstrumentation()` **before** creating any `KafkaClient` instances.
|
|
759
|
+
- Creating multiple `KafkaClient` instances shares the same instrumentation.
|
|
760
|
+
- For tests, use `resetKafkaInstrumentation()` to clear the singleton between runs.
|
|
725
761
|
|
|
726
762
|
### Minimal Setup Example
|
|
727
763
|
|
|
764
|
+
When using stream consumers, the easiest pattern is calling `endSpan()` in a `finally` block:
|
|
765
|
+
|
|
728
766
|
```ts
|
|
729
767
|
import { KafkaClient } from 'kafka-crab-js'
|
|
768
|
+
import { enableOtelInstrumentation, endSpan } from 'kafka-crab-js-otel'
|
|
730
769
|
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
|
731
770
|
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
|
732
771
|
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'
|
|
733
772
|
import { context } from '@opentelemetry/api'
|
|
734
773
|
|
|
774
|
+
// 1. Set up OpenTelemetry SDK
|
|
735
775
|
const provider = new NodeTracerProvider()
|
|
736
776
|
provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
|
|
737
777
|
provider.register()
|
|
738
|
-
|
|
739
778
|
context.setGlobalContextManager(new AsyncHooksContextManager().enable())
|
|
740
779
|
|
|
780
|
+
// 2. Enable kafka-crab-js instrumentation BEFORE creating client
|
|
781
|
+
enableOtelInstrumentation({
|
|
782
|
+
captureMessagePayload: true,
|
|
783
|
+
captureMessageHeaders: true,
|
|
784
|
+
producerHook: span => span.setAttribute('messaging.client.kind', 'producer'),
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
// 3. Create client with diagnostics enabled
|
|
741
788
|
const client = new KafkaClient({
|
|
742
789
|
brokers: 'localhost:29092',
|
|
743
790
|
clientId: 'orders-api',
|
|
744
|
-
|
|
745
|
-
serviceName: 'orders-service',
|
|
746
|
-
producerHook: span => span.setAttribute('messaging.client.kind', 'producer'),
|
|
747
|
-
},
|
|
791
|
+
diagnostics: true, // Required for OTEL to receive events
|
|
748
792
|
})
|
|
749
793
|
|
|
794
|
+
// 4. Use producer - spans are created automatically
|
|
750
795
|
const producer = client.createProducer()
|
|
751
796
|
await producer.send({
|
|
752
797
|
topic: 'orders',
|
|
@@ -756,17 +801,29 @@ await producer.send({
|
|
|
756
801
|
}],
|
|
757
802
|
})
|
|
758
803
|
|
|
804
|
+
// 5. Use stream consumer with proper cleanup
|
|
759
805
|
const consumer = client.createStreamConsumer({
|
|
760
806
|
groupId: 'orders-consumer',
|
|
761
807
|
enableAutoCommit: false,
|
|
762
808
|
})
|
|
763
809
|
|
|
810
|
+
await consumer.subscribe('orders')
|
|
811
|
+
|
|
764
812
|
consumer.on('data', message => {
|
|
765
|
-
|
|
813
|
+
try {
|
|
814
|
+
console.log(message.headers?.['custom-header']?.toString())
|
|
815
|
+
} finally {
|
|
816
|
+
// IMPORTANT: close the "process <topic>" span when your app finishes handling the message
|
|
817
|
+
endSpan(message)
|
|
818
|
+
}
|
|
766
819
|
})
|
|
820
|
+
|
|
821
|
+
// Proper cleanup for stream consumers - use destroy() not disconnect()
|
|
822
|
+
consumer.on('close', () => console.log('Consumer closed'))
|
|
823
|
+
// consumer.destroy()
|
|
767
824
|
```
|
|
768
825
|
|
|
769
|
-
|
|
826
|
+
To disable OTEL, simply don't call `enableOtelInstrumentation()` or set `diagnostics: false` in the client config.
|
|
770
827
|
|
|
771
828
|
## License
|
|
772
829
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kafka Diagnostic Channels
|
|
3
|
+
*
|
|
4
|
+
* This module provides Node.js diagnostic channels for Kafka operations.
|
|
5
|
+
* External observability tools (like OpenTelemetry adapters) can subscribe
|
|
6
|
+
* to these channels to receive events without coupling to any specific
|
|
7
|
+
* tracing/metrics implementation.
|
|
8
|
+
*
|
|
9
|
+
* @see https://nodejs.org/api/diagnostics_channel.html
|
|
10
|
+
*/
|
|
11
|
+
import { type Channel } from 'node:diagnostics_channel';
|
|
12
|
+
import type { Message, ProducerRecord, RecordMetadata } from '../../js-binding.js';
|
|
13
|
+
export declare const CHANNEL_NAMES: {
|
|
14
|
+
readonly PRODUCER_SEND_START: "kafka-crab:producer:send:start";
|
|
15
|
+
readonly PRODUCER_SEND_END: "kafka-crab:producer:send:end";
|
|
16
|
+
readonly PRODUCER_SEND_ERROR: "kafka-crab:producer:send:error";
|
|
17
|
+
readonly CONSUMER_RECEIVE_START: "kafka-crab:consumer:receive:start";
|
|
18
|
+
readonly CONSUMER_RECEIVE_END: "kafka-crab:consumer:receive:end";
|
|
19
|
+
readonly CONSUMER_PROCESS_START: "kafka-crab:consumer:process:start";
|
|
20
|
+
readonly CONSUMER_PROCESS_END: "kafka-crab:consumer:process:end";
|
|
21
|
+
readonly BATCH_RECEIVE_START: "kafka-crab:batch:receive:start";
|
|
22
|
+
readonly BATCH_RECEIVE_END: "kafka-crab:batch:receive:end";
|
|
23
|
+
readonly BATCH_PROCESS_START: "kafka-crab:batch:process:start";
|
|
24
|
+
readonly BATCH_PROCESS_END: "kafka-crab:batch:process:end";
|
|
25
|
+
};
|
|
26
|
+
export type ChannelName = (typeof CHANNEL_NAMES)[keyof typeof CHANNEL_NAMES];
|
|
27
|
+
/**
|
|
28
|
+
* Base event interface with common fields
|
|
29
|
+
*/
|
|
30
|
+
export interface BaseEvent {
|
|
31
|
+
/** Timestamp when the event occurred (ms since epoch) */
|
|
32
|
+
timestamp: number;
|
|
33
|
+
/** Client ID if available */
|
|
34
|
+
clientId?: string;
|
|
35
|
+
/** Server address if configured */
|
|
36
|
+
serverAddress?: string;
|
|
37
|
+
/** Server port if configured */
|
|
38
|
+
serverPort?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Context object for adapters to attach data (e.g., spans).
|
|
41
|
+
* Subscribers can mutate this to pass data between start/end events.
|
|
42
|
+
*/
|
|
43
|
+
context: Record<PropertyKey, unknown>;
|
|
44
|
+
}
|
|
45
|
+
export interface ProducerSendStartEvent extends BaseEvent {
|
|
46
|
+
topic: string;
|
|
47
|
+
record: ProducerRecord;
|
|
48
|
+
messageCount: number;
|
|
49
|
+
}
|
|
50
|
+
export interface ProducerSendEndEvent extends BaseEvent {
|
|
51
|
+
topic: string;
|
|
52
|
+
record: ProducerRecord;
|
|
53
|
+
metadata?: RecordMetadata[];
|
|
54
|
+
durationMs: number;
|
|
55
|
+
error?: Error;
|
|
56
|
+
}
|
|
57
|
+
export interface ProducerSendErrorEvent extends BaseEvent {
|
|
58
|
+
topic: string;
|
|
59
|
+
record: ProducerRecord;
|
|
60
|
+
durationMs: number;
|
|
61
|
+
error: Error;
|
|
62
|
+
}
|
|
63
|
+
export interface ConsumerReceiveStartEvent extends BaseEvent {
|
|
64
|
+
groupId?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface ConsumerReceiveEndEvent extends BaseEvent {
|
|
67
|
+
message: Message | null;
|
|
68
|
+
groupId?: string;
|
|
69
|
+
durationMs: number;
|
|
70
|
+
error?: Error;
|
|
71
|
+
}
|
|
72
|
+
export interface ConsumerProcessStartEvent extends BaseEvent {
|
|
73
|
+
message: Message;
|
|
74
|
+
groupId?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ConsumerProcessEndEvent extends BaseEvent {
|
|
77
|
+
message: Message;
|
|
78
|
+
groupId?: string;
|
|
79
|
+
durationMs: number;
|
|
80
|
+
error?: Error;
|
|
81
|
+
}
|
|
82
|
+
export interface BatchReceiveStartEvent extends BaseEvent {
|
|
83
|
+
groupId?: string;
|
|
84
|
+
requestedSize: number;
|
|
85
|
+
timeoutMs: number;
|
|
86
|
+
}
|
|
87
|
+
export interface BatchReceiveEndEvent extends BaseEvent {
|
|
88
|
+
messages: Message[];
|
|
89
|
+
groupId?: string;
|
|
90
|
+
durationMs: number;
|
|
91
|
+
error?: Error;
|
|
92
|
+
}
|
|
93
|
+
export interface BatchProcessStartEvent extends BaseEvent {
|
|
94
|
+
messages: Message[];
|
|
95
|
+
groupId?: string;
|
|
96
|
+
}
|
|
97
|
+
export interface BatchProcessEndEvent extends BaseEvent {
|
|
98
|
+
messages: Message[];
|
|
99
|
+
groupId?: string;
|
|
100
|
+
durationMs: number;
|
|
101
|
+
error?: Error;
|
|
102
|
+
}
|
|
103
|
+
export type KafkaEvent = ProducerSendStartEvent | ProducerSendEndEvent | ConsumerReceiveStartEvent | ConsumerReceiveEndEvent | ConsumerProcessStartEvent | ConsumerProcessEndEvent | BatchReceiveStartEvent | BatchReceiveEndEvent | BatchProcessStartEvent | BatchProcessEndEvent;
|
|
104
|
+
/**
|
|
105
|
+
* Typed channel wrapper for type-safe publish/subscribe
|
|
106
|
+
*/
|
|
107
|
+
export interface TypedChannel<TEvent> {
|
|
108
|
+
readonly name: string;
|
|
109
|
+
readonly channel: Channel;
|
|
110
|
+
hasSubscribers: boolean;
|
|
111
|
+
publish(event: TEvent): void;
|
|
112
|
+
subscribe(handler: (event: TEvent, name: string | symbol) => void): void;
|
|
113
|
+
unsubscribe(handler: (event: TEvent, name: string | symbol) => void): void;
|
|
114
|
+
}
|
|
115
|
+
export declare const producerSendStartChannel: TypedChannel<ProducerSendStartEvent>;
|
|
116
|
+
export declare const producerSendEndChannel: TypedChannel<ProducerSendEndEvent>;
|
|
117
|
+
export declare const producerSendErrorChannel: TypedChannel<ProducerSendErrorEvent>;
|
|
118
|
+
export declare const consumerReceiveStartChannel: TypedChannel<ConsumerReceiveStartEvent>;
|
|
119
|
+
export declare const consumerReceiveEndChannel: TypedChannel<ConsumerReceiveEndEvent>;
|
|
120
|
+
export declare const consumerProcessStartChannel: TypedChannel<ConsumerProcessStartEvent>;
|
|
121
|
+
export declare const consumerProcessEndChannel: TypedChannel<ConsumerProcessEndEvent>;
|
|
122
|
+
export declare const batchReceiveStartChannel: TypedChannel<BatchReceiveStartEvent>;
|
|
123
|
+
export declare const batchReceiveEndChannel: TypedChannel<BatchReceiveEndEvent>;
|
|
124
|
+
export declare const batchProcessStartChannel: TypedChannel<BatchProcessStartEvent>;
|
|
125
|
+
export declare const batchProcessEndChannel: TypedChannel<BatchProcessEndEvent>;
|
|
126
|
+
export declare const channels: {
|
|
127
|
+
readonly producerSendStart: TypedChannel<ProducerSendStartEvent>;
|
|
128
|
+
readonly producerSendEnd: TypedChannel<ProducerSendEndEvent>;
|
|
129
|
+
readonly producerSendError: TypedChannel<ProducerSendErrorEvent>;
|
|
130
|
+
readonly consumerReceiveStart: TypedChannel<ConsumerReceiveStartEvent>;
|
|
131
|
+
readonly consumerReceiveEnd: TypedChannel<ConsumerReceiveEndEvent>;
|
|
132
|
+
readonly consumerProcessStart: TypedChannel<ConsumerProcessStartEvent>;
|
|
133
|
+
readonly consumerProcessEnd: TypedChannel<ConsumerProcessEndEvent>;
|
|
134
|
+
readonly batchReceiveStart: TypedChannel<BatchReceiveStartEvent>;
|
|
135
|
+
readonly batchReceiveEnd: TypedChannel<BatchReceiveEndEvent>;
|
|
136
|
+
readonly batchProcessStart: TypedChannel<BatchProcessStartEvent>;
|
|
137
|
+
readonly batchProcessEnd: TypedChannel<BatchProcessEndEvent>;
|
|
138
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics module entry point
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all channels and types for external consumption.
|
|
5
|
+
*/
|
|
6
|
+
export { type BaseEvent, batchProcessEndChannel, type BatchProcessEndEvent, batchProcessStartChannel, type BatchProcessStartEvent, batchReceiveEndChannel, type BatchReceiveEndEvent, batchReceiveStartChannel, type BatchReceiveStartEvent, CHANNEL_NAMES, type ChannelName, channels, consumerProcessEndChannel, type ConsumerProcessEndEvent, consumerProcessStartChannel, type ConsumerProcessStartEvent, consumerReceiveEndChannel, type ConsumerReceiveEndEvent, consumerReceiveStartChannel, type ConsumerReceiveStartEvent, type KafkaEvent, producerSendEndChannel, type ProducerSendEndEvent, producerSendErrorChannel, type ProducerSendErrorEvent, producerSendStartChannel, type ProducerSendStartEvent, type TypedChannel, } from './channels.js';
|
|
7
|
+
export { type DiagnosticInstrumentationConfig, instrumentBatchReceive, instrumentConsumerReceive, instrumentProducerSend, } from './instrumentation.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic Channel-based Kafka Instrumentation
|
|
3
|
+
*
|
|
4
|
+
* This module provides instrumentation that publishes events to Node.js
|
|
5
|
+
* diagnostic channels instead of directly calling OpenTelemetry APIs.
|
|
6
|
+
* Observability tools can subscribe to these channels to receive events.
|
|
7
|
+
*/
|
|
8
|
+
import type { KafkaConsumer, KafkaProducer, Message, ProducerRecord, RecordMetadata } from '../../js-binding.js';
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for diagnostic channel instrumentation
|
|
11
|
+
*/
|
|
12
|
+
export interface DiagnosticInstrumentationConfig {
|
|
13
|
+
/** Client ID for attribution */
|
|
14
|
+
clientId?: string;
|
|
15
|
+
/** Server address for attribution */
|
|
16
|
+
serverAddress?: string;
|
|
17
|
+
/** Server port for attribution */
|
|
18
|
+
serverPort?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Instruments a producer send method to publish diagnostic events
|
|
22
|
+
*/
|
|
23
|
+
export declare function instrumentProducerSend(originalSend: (record: ProducerRecord) => Promise<RecordMetadata[]>, config?: DiagnosticInstrumentationConfig): (this: KafkaProducer, record: ProducerRecord) => Promise<RecordMetadata[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Instruments a consumer receive method to publish diagnostic events
|
|
26
|
+
*/
|
|
27
|
+
export declare function instrumentConsumerReceive(originalReceive: () => Promise<Message | null>, groupId?: string, config?: DiagnosticInstrumentationConfig): (this: KafkaConsumer) => Promise<Message | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Instruments a consumer batch receive method to publish diagnostic events
|
|
30
|
+
*/
|
|
31
|
+
export declare function instrumentBatchReceive(originalBatchReceive: (size: number, timeoutMs: number) => Promise<Message[]>, groupId?: string, config?: DiagnosticInstrumentationConfig): (this: KafkaConsumer, size: number, timeoutMs: number) => Promise<Message[]>;
|