@autofleet/kafka 0.1.1 → 0.2.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 CHANGED
@@ -7,6 +7,11 @@ Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://w
7
7
  - [Installation](#installation)
8
8
  - [Features](#features)
9
9
  - [Quick Start](#quick-start)
10
+ - [Initialization & Bootstrap](#initialization--bootstrap)
11
+ - [Lifecycle Overview](#lifecycle-overview)
12
+ - [Production Bootstrap Pattern](#production-bootstrap-pattern)
13
+ - [Development Bootstrap Pattern](#development-bootstrap-pattern)
14
+ - [Bootstrap Options](#bootstrap-options)
10
15
  - [Usage](#usage)
11
16
  - [Setup with Multiple Producers](#setup-with-multiple-producers)
12
17
  - [Publishing Messages](#publishing-messages)
@@ -20,6 +25,7 @@ Internal wrapper for Apache Kafka producer using [@platformatic/kafka](https://w
20
25
  - [Partition Control](#partition-control)
21
26
  - [API Reference](#api-reference)
22
27
  - [Best Practices](#best-practices)
28
+ - [Troubleshooting](#troubleshooting)
23
29
  - [Testing](#testing)
24
30
 
25
31
  ## Installation
@@ -91,6 +97,177 @@ app.get('/health/ready', async (req, res) => {
91
97
  });
92
98
  ```
93
99
 
100
+ ## Initialization & Bootstrap
101
+
102
+ ### Lifecycle Overview
103
+
104
+ The `@autofleet/kafka` package follows a clear, predictable lifecycle:
105
+
106
+ ```
107
+ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐
108
+ │ create() │────▶│ bootstrap() │────▶│ publish/use │────▶│disconnect()│
109
+ │ (sync) │ │ (async) │ │ (async) │ │ (async) │
110
+ └─────────────┘ └──────────────┘ └──────────────┘ └────────────┘
111
+ Instant Validates Runtime Cleanup
112
+ construction connectivity operations
113
+ ```
114
+
115
+ **1. `create()` - Synchronous Construction**
116
+ - Creates the KafkaManager instance
117
+ - Sets up configuration
118
+ - Does NOT connect to Kafka yet
119
+ - Returns immediately
120
+
121
+ **2. `bootstrap()` - Async Initialization**
122
+ - Explicitly connects all producers to Kafka
123
+ - Validates broker connectivity
124
+ - Fetches cluster metadata
125
+ - Fails fast with detailed diagnostics
126
+ - **Recommended before serving traffic**
127
+
128
+ **3. Runtime Operations**
129
+ - `publish()`, `publishBatch()` work normally
130
+ - Lazy initialization also supported (backwards compatible)
131
+ - Health checks available
132
+
133
+ **4. `disconnect()` - Cleanup**
134
+ - Gracefully closes all connections
135
+ - Automatic on SIGTERM/SIGINT
136
+
137
+ ### Production Bootstrap Pattern
138
+
139
+ **Recommended production service initialization:**
140
+
141
+ ```typescript
142
+ import { KafkaManager } from '@autofleet/kafka';
143
+ import logger from './logger';
144
+
145
+ // 1. Synchronous construction
146
+ export const kafka = KafkaManager.create({
147
+ enabled: process.env.ENABLE_KAFKA === 'true',
148
+ logger,
149
+ bootstrapTimeoutMs: 15000, // 15s bootstrap timeout
150
+ healthCheckCacheMs: 2000, // Cache health for 2s
151
+ strictBootstrap: true, // Fail if ANY producer fails
152
+ producers: {
153
+ main: {
154
+ brokers: process.env.KAFKA_BROKERS?.split(',') || [],
155
+ clientId: 'my-service-main',
156
+ },
157
+ analytics: {
158
+ brokers: process.env.ANALYTICS_BROKERS?.split(',') || [],
159
+ clientId: 'my-service-analytics',
160
+ },
161
+ },
162
+ });
163
+
164
+ // 2. Bootstrap before serving traffic
165
+ async function startServer() {
166
+ logger.info('Bootstrapping Kafka...');
167
+
168
+ try {
169
+ const result = await kafka.bootstrap();
170
+
171
+ logger.info('Kafka bootstrap successful', {
172
+ duration: result.duration,
173
+ producers: Object.keys(result.results),
174
+ });
175
+ } catch (error) {
176
+ logger.error('Fatal: Kafka bootstrap failed', { error });
177
+ process.exit(1); // Fail fast in production
178
+ }
179
+
180
+ // 3. Start HTTP server only after Kafka is ready
181
+ app.listen(3000, () => {
182
+ logger.info('Server ready - Kafka connected');
183
+ });
184
+ }
185
+
186
+ startServer();
187
+ ```
188
+
189
+ ### Development Bootstrap Pattern
190
+
191
+ **For development/local environments with optional Kafka:**
192
+
193
+ ```typescript
194
+ async function startServer() {
195
+ // Try to bootstrap, but don't fail if Kafka unavailable
196
+ try {
197
+ await kafka.bootstrap({
198
+ timeoutMs: 5000, // Shorter timeout for dev
199
+ strict: false, // Allow partial failures
200
+ });
201
+ logger.info('Kafka connected');
202
+ } catch (error) {
203
+ logger.warn('Kafka unavailable - using lazy initialization', { error });
204
+ // Server still starts, producers will retry on first publish
205
+ }
206
+
207
+ app.listen(3000, () => {
208
+ logger.info('Server ready');
209
+ });
210
+ }
211
+ ```
212
+
213
+ ### Bootstrap Options
214
+
215
+ #### Strict Mode (Default)
216
+
217
+ Bootstrap fails if **any** producer fails to connect:
218
+
219
+ ```typescript
220
+ await kafka.bootstrap(); // strict: true by default
221
+
222
+ // Throws if any producer fails
223
+ // Perfect for production where all clusters must be available
224
+ ```
225
+
226
+ #### Non-Strict Mode
227
+
228
+ Bootstrap succeeds if **at least one** producer connects:
229
+
230
+ ```typescript
231
+ const result = await kafka.bootstrap({
232
+ strict: false,
233
+ });
234
+
235
+ if (!result.success) {
236
+ logger.warn('Some producers failed:', result.results);
237
+ }
238
+
239
+ // Check which ones failed
240
+ const failed = Object.entries(result.results)
241
+ .filter(([_, r]) => !r.success)
242
+ .map(([name]) => name);
243
+
244
+ logger.warn('Failed producers:', failed);
245
+ // Continue - failed producers use lazy initialization
246
+ ```
247
+
248
+ #### Selective Bootstrap
249
+
250
+ Bootstrap specific producers only:
251
+
252
+ ```typescript
253
+ // Only bootstrap critical producer
254
+ await kafka.bootstrap({
255
+ producers: ['main'],
256
+ timeoutMs: 10000,
257
+ });
258
+
259
+ // Analytics producer will use lazy initialization
260
+ ```
261
+
262
+ #### Custom Timeouts
263
+
264
+ ```typescript
265
+ await kafka.bootstrap({
266
+ timeoutMs: 20000, // 20 second timeout
267
+ strict: true,
268
+ });
269
+ ```
270
+
94
271
  ## Usage
95
272
 
96
273
  ### Setup with Multiple Producers
@@ -202,30 +379,148 @@ await kafka.publish('main', 'topic', { data: 'test' });
202
379
 
203
380
  ### Health Checks & Readiness Probes
204
381
 
205
- Use the built-in health check for Kubernetes readiness probes:
382
+ The package provides comprehensive health check APIs suitable for Kubernetes probes:
383
+
384
+ #### Readiness Probe
385
+
386
+ Checks if Kafka is ready to handle traffic (connects to brokers):
206
387
 
207
388
  ```typescript
208
389
  import { kafka } from './kafka';
209
390
 
210
- // Express/Fastify/etc
211
391
  app.get('/health/ready', async (req, res) => {
212
392
  const ready = await kafka.isReady();
213
- res.status(ready ? 200 : 503).json({
214
- ready,
215
- kafka: kafka.getConnectionStatus(),
216
- });
393
+
394
+ if (!ready) {
395
+ const health = kafka.getHealth();
396
+ return res.status(503).json({
397
+ ready: false,
398
+ kafka: health,
399
+ });
400
+ }
401
+
402
+ res.json({ ready: true });
217
403
  });
404
+ ```
405
+
406
+ **Readiness check features:**
407
+ - Revalidates connectivity if cache is stale
408
+ - Configurable cache duration (default: 1 second)
409
+ - Force revalidation with `isReady({ force: true })`
410
+ - Respects `healthCheckTimeoutMs` configuration
411
+
412
+ #### Liveness Probe
413
+
414
+ Lightweight check for process health (does NOT connect to Kafka):
218
415
 
219
- // Or manually check each producer
220
- app.get('/health/detailed', async (req, res) => {
221
- const status = kafka.getConnectionStatus();
222
- const allConnected = Object.values(status).every(s => s);
416
+ ```typescript
417
+ app.get('/health/live', (req, res) => {
418
+ const live = kafka.isLive();
419
+ res.status(live ? 200 : 503).json({ live });
420
+ });
421
+ ```
223
422
 
224
- res.status(allConnected ? 200 : 503).json({
225
- producers: status,
423
+ **Liveness check:**
424
+ - Returns `false` if manager is in a fatal state
425
+ - Returns `false` if graceful shutdown has started
426
+ - Does NOT perform Kafka operations (very fast)
427
+ - Perfect for Kubernetes liveness probes
428
+
429
+ #### Detailed Health Snapshot
430
+
431
+ Get comprehensive health information for all producers:
432
+
433
+ ```typescript
434
+ app.get('/status/kafka', (req, res) => {
435
+ const health = kafka.getHealth();
436
+
437
+ res.json({
438
+ producers: health,
226
439
  enabled: kafka.isEnabled,
440
+ live: kafka.isLive(),
227
441
  });
228
442
  });
443
+
444
+ // Example response:
445
+ {
446
+ "producers": {
447
+ "main": {
448
+ "name": "main",
449
+ "enabled": true,
450
+ "isConnected": true,
451
+ "lastPingAt": 1701234567890,
452
+ "lastPingSucceededAt": 1701234567890,
453
+ "lastError": null,
454
+ "clusterId": "prod-kafka-main-01",
455
+ "brokerCount": 3,
456
+ "brokers": ["kafka1:9092", "kafka2:9092", "kafka3:9092"]
457
+ },
458
+ "analytics": {
459
+ "name": "analytics",
460
+ "enabled": true,
461
+ "isConnected": false,
462
+ "lastPingAt": 1701234560000,
463
+ "lastPingSucceededAt": 1701234500000,
464
+ "lastError": {
465
+ "message": "Connection refused",
466
+ "timestamp": 1701234560000
467
+ },
468
+ "clusterId": null,
469
+ "brokerCount": 0,
470
+ "brokers": ["analytics-kafka:9092"]
471
+ }
472
+ },
473
+ "enabled": true,
474
+ "live": true
475
+ }
476
+ ```
477
+
478
+ #### Connection Status
479
+
480
+ Get quick connection status summary:
481
+
482
+ ```typescript
483
+ const status = kafka.getConnectionStatus();
484
+
485
+ // Returns:
486
+ {
487
+ "main": {
488
+ "connected": true,
489
+ "lastSuccessAt": 1701234567890,
490
+ "lastError": null
491
+ },
492
+ "analytics": {
493
+ "connected": false,
494
+ "lastSuccessAt": 1701234500000,
495
+ "lastError": "Connection refused"
496
+ }
497
+ }
498
+ ```
499
+
500
+ #### Kubernetes Example
501
+
502
+ ```yaml
503
+ apiVersion: v1
504
+ kind: Pod
505
+ metadata:
506
+ name: my-service
507
+ spec:
508
+ containers:
509
+ - name: app
510
+ image: my-service:latest
511
+ livenessProbe:
512
+ httpGet:
513
+ path: /health/live
514
+ port: 3000
515
+ initialDelaySeconds: 10
516
+ periodSeconds: 10
517
+ readinessProbe:
518
+ httpGet:
519
+ path: /health/ready
520
+ port: 3000
521
+ initialDelaySeconds: 15
522
+ periodSeconds: 5
523
+ timeoutSeconds: 3
229
524
  ```
230
525
 
231
526
  ### Migration from getKafka() Pattern
@@ -303,16 +598,41 @@ async function publishEvent() {
303
598
  ```typescript
304
599
  interface KafkaManagerOptions {
305
600
  // Enable/disable Kafka - when false, returns mock implementations
601
+ // @default true
306
602
  enabled?: boolean;
307
603
 
308
604
  // Custom logger instance
309
605
  logger?: LoggerInstanceManager;
310
606
 
311
- // Skip automatic graceful shutdown (default: false)
607
+ // Skip automatic graceful shutdown
608
+ // @default false
312
609
  dontGracefulShutdown?: boolean;
313
610
 
314
- // Named producers configuration
611
+ // Named producers configuration (required)
315
612
  producers: Record<string, ProducerConfig>;
613
+
614
+ // ===== NEW: Health & Bootstrap Options =====
615
+
616
+ // Global timeout for health checks in ms
617
+ // Used by isReady() and ping operations
618
+ // @default 5000 (5 seconds)
619
+ healthCheckTimeoutMs?: number;
620
+
621
+ // How long to cache health check results in ms
622
+ // Set to 0 to always revalidate
623
+ // @default 1000 (1 second)
624
+ healthCheckCacheMs?: number;
625
+
626
+ // Default timeout for bootstrap operations in ms
627
+ // Can be overridden per bootstrap() call
628
+ // @default 30000 (30 seconds)
629
+ bootstrapTimeoutMs?: number;
630
+
631
+ // Default strict mode for bootstrap
632
+ // If true, all producers must succeed
633
+ // If false, at least one producer must succeed
634
+ // @default true
635
+ strictBootstrap?: boolean;
316
636
  }
317
637
  ```
318
638
 
@@ -402,9 +722,11 @@ await kafka.publish('main', 'events', data, {
402
722
 
403
723
  ## API Reference
404
724
 
405
- ### `KafkaManager.create(options): KafkaManager<ProducerNames>`
725
+ ### Core Lifecycle Methods
406
726
 
407
- Creates a new KafkaManager instance with multiple named producers. Producers are initialized lazily on first use. **Producer names are type-safe** - TypeScript will autocomplete and validate them based on your configuration.
727
+ #### `KafkaManager.create(options): KafkaManager<ProducerNames>`
728
+
729
+ Creates a new KafkaManager instance with multiple named producers. **Producer names are type-safe** - TypeScript will autocomplete and validate them based on your configuration.
408
730
 
409
731
  **Parameters:**
410
732
  - `options` - Configuration options (see [Configuration](#configuration))
@@ -437,7 +759,123 @@ await kafka.publish('analytics', 'metrics', { value: 1 }); // ✅ Valid
437
759
  // await kafka.publish('wrong', 'topic', {}); // ❌ TypeScript error!
438
760
  ```
439
761
 
440
- ### `publish<T>(producerName, topic, value, options?): Promise<RecordMetadata[]>`
762
+ #### `bootstrap(options?): Promise<BootstrapResult>`
763
+
764
+ **NEW:** Explicitly bootstrap all (or specific) producers. Connects to brokers, validates connectivity, and returns detailed results. **Recommended before serving traffic in production.**
765
+
766
+ **Parameters:**
767
+ - `options.timeoutMs` (optional) - Maximum time to wait for all producers (default: `bootstrapTimeoutMs` config)
768
+ - `options.strict` (optional) - Fail if ANY producer fails (default: `strictBootstrap` config)
769
+ - `options.producers` (optional) - Array of specific producer names to bootstrap (default: all)
770
+
771
+ **Returns:** `BootstrapResult` with success status, duration, and per-producer results
772
+
773
+ **Examples:**
774
+ ```typescript
775
+ // Bootstrap all producers with defaults
776
+ await kafka.bootstrap();
777
+
778
+ // Non-strict mode - continue if some fail
779
+ const result = await kafka.bootstrap({
780
+ timeoutMs: 10000,
781
+ strict: false,
782
+ });
783
+ if (!result.success) {
784
+ console.error('Some producers failed:', result.results);
785
+ }
786
+
787
+ // Bootstrap specific producers only
788
+ await kafka.bootstrap({ producers: ['main'] });
789
+ ```
790
+
791
+ ### Health & Monitoring Methods
792
+
793
+ #### `getHealth(): Record<ProducerNames, ProducerHealth>`
794
+
795
+ Get detailed health snapshot for all producers. Returns comprehensive metadata including connection state, timestamps, errors, and cluster info.
796
+
797
+ **Returns:** Object mapping producer names to `ProducerHealth` objects
798
+
799
+ **Example:**
800
+ ```typescript
801
+ const health = kafka.getHealth();
802
+ console.log(health.main.clusterId); // 'prod-kafka-01'
803
+ console.log(health.main.lastPingSucceededAt);// 1701234567890
804
+ console.log(health.main.lastError); // null or error object
805
+ ```
806
+
807
+ #### `getProducerHealth(name): ProducerHealth`
808
+
809
+ Get detailed health for a specific producer.
810
+
811
+ **Parameters:**
812
+ - `name` - Producer name (type-safe)
813
+
814
+ **Returns:** `ProducerHealth` object
815
+
816
+ **Example:**
817
+ ```typescript
818
+ const health = kafka.getProducerHealth('main');
819
+ console.log('Cluster:', health.clusterId);
820
+ console.log('Brokers:', health.brokerCount);
821
+ ```
822
+
823
+ #### `isReady(options?): Promise<boolean>`
824
+
825
+ Check if Kafka is ready to handle traffic. Revalidates connectivity if cache is stale. **Use for Kubernetes readiness probes.**
826
+
827
+ **Parameters:**
828
+ - `options.timeout` (optional) - Override default health check timeout
829
+ - `options.force` (optional) - Force revalidation, ignore cache
830
+
831
+ **Returns:** `true` if all producers are connected, `false` otherwise
832
+
833
+ **Examples:**
834
+ ```typescript
835
+ // Use cached result if fresh
836
+ const ready = await kafka.isReady();
837
+
838
+ // Force immediate revalidation
839
+ const ready = await kafka.isReady({ force: true });
840
+
841
+ // Custom timeout
842
+ const ready = await kafka.isReady({ timeout: 10000 });
843
+ ```
844
+
845
+ #### `isLive(): boolean`
846
+
847
+ Lightweight liveness check - does NOT perform Kafka operations. Only checks internal state for fatal errors. **Perfect for Kubernetes liveness probes.**
848
+
849
+ **Returns:** `false` if manager is in fatal state or graceful shutdown has started
850
+
851
+ **Example:**
852
+ ```typescript
853
+ app.get('/health/live', (req, res) => {
854
+ res.status(kafka.isLive() ? 200 : 503).json({ live: kafka.isLive() });
855
+ });
856
+ ```
857
+
858
+ #### `getConnectionStatus(): Record<ProducerNames, ConnectionStatus>`
859
+
860
+ Get connection status for all producers with timestamps and errors.
861
+
862
+ **Returns:** Object mapping producer names to connection status
863
+
864
+ **Example:**
865
+ ```typescript
866
+ const status = kafka.getConnectionStatus();
867
+ // {
868
+ // main: {
869
+ // connected: true,
870
+ // lastSuccessAt: 1701234567890,
871
+ // lastError: null
872
+ // }
873
+ // }
874
+ ```
875
+
876
+ ### Publishing Methods
877
+
878
+ #### `publish<T>(producerName, topic, value, options?): Promise<RecordMetadata[]>`
441
879
 
442
880
  Publishes a single message to a topic using a named producer. Producer name is **type-safe** - must match one of the configured producers.
443
881
 
@@ -657,6 +1095,200 @@ await kafka.publish('main', 'orders', orderData);
657
1095
  await kafka.publish('analytics', 'metrics', metricsData);
658
1096
  ```
659
1097
 
1098
+ ## Troubleshooting
1099
+
1100
+ ### Common Issues and Solutions
1101
+
1102
+ #### Bootstrap Fails with Connection Timeout
1103
+
1104
+ **Problem:**
1105
+ ```
1106
+ [main] Failed to connect to Kafka brokers: Connection timeout after 5000ms
1107
+
1108
+ Possible causes:
1109
+ 1. Brokers are unreachable: kafka:9092
1110
+ 2. Network connectivity issues
1111
+ 3. Firewall blocking ports
1112
+ ```
1113
+
1114
+ **Solutions:**
1115
+ 1. **Verify brokers are running:**
1116
+ ```bash
1117
+ kubectl get pods -l app=kafka
1118
+ ```
1119
+
1120
+ 2. **Test connectivity:**
1121
+ ```bash
1122
+ nc -zv kafka-broker 9092
1123
+ ```
1124
+
1125
+ 3. **Check network policies:**
1126
+ - Ensure service can reach Kafka namespace
1127
+ - Verify firewall rules allow traffic on port 9092
1128
+
1129
+ 4. **Increase timeout:**
1130
+ ```typescript
1131
+ await kafka.bootstrap({ timeoutMs: 30000 }); // 30 seconds
1132
+ ```
1133
+
1134
+ #### SASL Authentication Failed
1135
+
1136
+ **Problem:**
1137
+ ```
1138
+ [main] Failed to connect to Kafka brokers: SASL authentication failed
1139
+ ```
1140
+
1141
+ **Solutions:**
1142
+ 1. **Verify credentials:**
1143
+ ```typescript
1144
+ producers: {
1145
+ main: {
1146
+ brokers: ['kafka:9092'],
1147
+ sasl: {
1148
+ mechanism: 'scram-sha-256',
1149
+ username: process.env.KAFKA_USERNAME, // Check this
1150
+ password: process.env.KAFKA_PASSWORD, // And this
1151
+ },
1152
+ },
1153
+ }
1154
+ ```
1155
+
1156
+ 2. **Check mechanism:**
1157
+ - Verify broker supports the SASL mechanism
1158
+ - Common mechanisms: `'plain'`, `'scram-sha-256'`, `'scram-sha-512'`
1159
+
1160
+ 3. **Inspect secrets:**
1161
+ ```bash
1162
+ kubectl get secret kafka-credentials -o yaml
1163
+ ```
1164
+
1165
+ #### Producer Not Found Error
1166
+
1167
+ **Problem:**
1168
+ ```
1169
+ Producer 'analytics' not found. Available producers: main
1170
+ ```
1171
+
1172
+ **Solution:**
1173
+ You're trying to use a producer that wasn't configured:
1174
+
1175
+ ```typescript
1176
+ // Add the missing producer
1177
+ const kafka = KafkaManager.create({
1178
+ producers: {
1179
+ main: { brokers: ['kafka:9092'] },
1180
+ analytics: { brokers: ['kafka-analytics:9092'] }, // Add this
1181
+ },
1182
+ });
1183
+ ```
1184
+
1185
+ #### Readiness Check Always Fails
1186
+
1187
+ **Problem:**
1188
+ Health checks keep failing even though Kafka is up.
1189
+
1190
+ **Solutions:**
1191
+ 1. **Check timeout is sufficient:**
1192
+ ```typescript
1193
+ const kafka = KafkaManager.create({
1194
+ healthCheckTimeoutMs: 5000, // Increase if needed
1195
+ });
1196
+ ```
1197
+
1198
+ 2. **Force revalidation:**
1199
+ ```typescript
1200
+ const ready = await kafka.isReady({ force: true });
1201
+ ```
1202
+
1203
+ 3. **Inspect detailed health:**
1204
+ ```typescript
1205
+ const health = kafka.getHealth();
1206
+ console.log(health); // Check lastError for details
1207
+ ```
1208
+
1209
+ #### Bootstrap Succeeds but Publish Fails
1210
+
1211
+ **Problem:**
1212
+ Bootstrap passes, but publish throws "topic auto-create disabled".
1213
+
1214
+ **Solution:**
1215
+ Enable topic auto-creation or create topics manually:
1216
+
1217
+ ```typescript
1218
+ producers: {
1219
+ main: {
1220
+ brokers: ['kafka:9092'],
1221
+ autoCreateTopics: true, // Enable for dev/test
1222
+ },
1223
+ }
1224
+
1225
+ // Or create topics manually:
1226
+ // kafka-topics --create --topic my-topic --bootstrap-server kafka:9092
1227
+ ```
1228
+
1229
+ #### Graceful Shutdown Not Working
1230
+
1231
+ **Problem:**
1232
+ Service doesn't close Kafka connections on shutdown.
1233
+
1234
+ **Solutions:**
1235
+ 1. **Ensure graceful shutdown is enabled:**
1236
+ ```typescript
1237
+ const kafka = KafkaManager.create({
1238
+ dontGracefulShutdown: false, // Default
1239
+ producers: { /* ... */ },
1240
+ });
1241
+ ```
1242
+
1243
+ 2. **Manual disconnect if needed:**
1244
+ ```typescript
1245
+ process.on('SIGTERM', async () => {
1246
+ await kafka.disconnect();
1247
+ process.exit(0);
1248
+ });
1249
+ ```
1250
+
1251
+ ### Debugging Tips
1252
+
1253
+ #### Enable Debug Logging
1254
+
1255
+ ```typescript
1256
+ import { KafkaManager } from '@autofleet/kafka';
1257
+ import logger from './logger';
1258
+
1259
+ const kafka = KafkaManager.create({
1260
+ logger, // Ensure logger has debug level enabled
1261
+ producers: { /* ... */ },
1262
+ });
1263
+
1264
+ // Check health frequently
1265
+ setInterval(() => {
1266
+ const health = kafka.getHealth();
1267
+ logger.debug('Kafka health', { health });
1268
+ }, 10000);
1269
+ ```
1270
+
1271
+ #### Inspect Cluster Metadata
1272
+
1273
+ ```typescript
1274
+ const health = kafka.getProducerHealth('main');
1275
+ console.log('Cluster ID:', health.clusterId);
1276
+ console.log('Brokers:', health.brokerCount);
1277
+ console.log('Last success:', new Date(health.lastPingSucceededAt));
1278
+ console.log('Last error:', health.lastError);
1279
+ ```
1280
+
1281
+ #### Test Connectivity Manually
1282
+
1283
+ ```typescript
1284
+ try {
1285
+ await kafka.pingProducer('main');
1286
+ console.log('✓ Connection successful');
1287
+ } catch (error) {
1288
+ console.error('✗ Connection failed:', error.message);
1289
+ }
1290
+ ```
1291
+
660
1292
  ## Testing
661
1293
 
662
1294
  Run the test suite: