optictrace 1.0.1

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 ADDED
@@ -0,0 +1,99 @@
1
+ # OpticTrace SDK
2
+
3
+ OpenTelemetry-based SDK for OpticTrace observability. This SDK provides automatic and manual instrumentation for Node.js applications, including HTTP, Express, PostgreSQL, and MongoDB.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install optictrace
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Automatic Instrumentation**: HTTP, Express, PG, and MongoDB.
14
+ - **Environment Logging**: Tag logs and traces with `development`, `staging`, `production`, etc.
15
+ - **Manual Logging**: Send logs directly to the OpticTrace Hub.
16
+ - **API Discovery**: Automatically discover and sync your Express routes.
17
+ - **OpenAPI Generation**: Generate OpenAPI 3.0 specs from your Express app.
18
+
19
+ ## Usage
20
+
21
+ ### Initialization
22
+
23
+ ```javascript
24
+ import OT from 'optictrace';
25
+
26
+ OT.init({
27
+ serviceName: 'my-service',
28
+ hubUrl: 'http://localhost:8080',
29
+ apiKey: 'your-api-key',
30
+ environment: 'production'
31
+ });
32
+ ```
33
+
34
+ ### Express Integration & Route Discovery
35
+
36
+ ```javascript
37
+ import express from 'express';
38
+ import OT from 'optictrace';
39
+
40
+ const app = express();
41
+
42
+ // ... define your routes ...
43
+
44
+ OT.discoverRoutes(app);
45
+ ```
46
+
47
+ ### Database Tracing
48
+
49
+ #### PostgreSQL
50
+
51
+ ```javascript
52
+ import { Pool } from 'pg';
53
+ import OT from 'optictrace';
54
+
55
+ const pool = OT.wrapPool(new Pool({
56
+ // ... config ...
57
+ }));
58
+
59
+ // All queries via this pool will now be traced automatically
60
+ await pool.query('SELECT * FROM users');
61
+ ```
62
+
63
+ #### MongoDB
64
+
65
+ ```javascript
66
+ import { MongoClient } from 'mongodb';
67
+ import OT from 'optictrace';
68
+
69
+ const client = OT.wrapMongoClient(new MongoClient('mongodb://localhost:27017'));
70
+
71
+ // All operations will be traced
72
+ const db = client.db('test');
73
+ await db.collection('users').find({}).toArray();
74
+ ```
75
+
76
+ ### Manual Logging
77
+
78
+ ```javascript
79
+ import OT from 'optictrace';
80
+
81
+ OT.log('INFO', 'User logged in', { userId: '123' });
82
+ OT.log('ERROR', 'Database connection failed', { error: err.message });
83
+ ```
84
+
85
+ ### OpenAPI Specification Generation
86
+
87
+ ```javascript
88
+ import OT from 'optictrace';
89
+
90
+ // Save OpenAPI spec to a file
91
+ OT.saveOpenApi(app, './openapi.yaml');
92
+
93
+ // Or get the YAML string
94
+ const yaml = OT.generateOpenApiSpec(app);
95
+ ```
96
+
97
+ ## License
98
+
99
+ MIT
@@ -0,0 +1,43 @@
1
+ import type { Pool } from 'pg';
2
+ import type { MongoClient } from 'mongodb';
3
+ import type { Application } from 'express';
4
+ import type { OpticTraceConfig, LogLevel, LogAttributes } from './types';
5
+ /**
6
+ * Initialize the OpticTrace SDK
7
+ */
8
+ export declare function init(config: OpticTraceConfig): void;
9
+ /**
10
+ * Discover and register API routes from Express app
11
+ */
12
+ export declare function discoverRoutes(app: Application): void;
13
+ /**
14
+ * Send a log message to OpticTrace
15
+ */
16
+ export declare function log(level: LogLevel, message: string, attributes?: LogAttributes): void;
17
+ /**
18
+ * Wrap a PostgreSQL connection pool for automatic query tracing
19
+ */
20
+ export declare function wrapPool<T extends Pool>(pool: T): T;
21
+ /**
22
+ * Wrap a MongoDB client for automatic query tracing
23
+ */
24
+ export declare function wrapMongoClient<T extends MongoClient>(client: T): T;
25
+ /**
26
+ * Generate OpenAPI 3.0 specification from Express app routes
27
+ */
28
+ export declare function generateOpenApiSpec(app: Application): string;
29
+ /**
30
+ * Save OpenAPI specification to a file
31
+ */
32
+ export declare function saveOpenApi(app: Application, filePath: string): void;
33
+ declare const _default: {
34
+ init: typeof init;
35
+ discoverRoutes: typeof discoverRoutes;
36
+ log: typeof log;
37
+ wrapPool: typeof wrapPool;
38
+ wrapMongoClient: typeof wrapMongoClient;
39
+ generateOpenApiSpec: typeof generateOpenApiSpec;
40
+ saveOpenApi: typeof saveOpenApi;
41
+ };
42
+ export default _default;
43
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,IAAI,EAA+B,MAAM,IAAI,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAA4B,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,KAAK,EAAE,gBAAgB,EAAS,QAAQ,EAAE,aAAa,EAAmD,MAAM,SAAS,CAAC;AASjI;;GAEG;AACH,wBAAgB,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAkEnD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CA6CrD;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAE,aAAkB,GAAG,IAAI,CA4B1F;AAsCD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAyCnD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CA2EnE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAqG5D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAQpE;;;;;;;;;;AAGD,wBAA0G"}
package/dist/index.js ADDED
@@ -0,0 +1,399 @@
1
+ import { NodeSDK } from '@opentelemetry/sdk-node';
2
+ import * as fs from 'fs';
3
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
4
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
5
+ import { Resource } from '@opentelemetry/resources';
6
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
7
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
8
+ import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
9
+ import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
10
+ import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
11
+ import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
12
+ import { logs } from '@opentelemetry/api-logs';
13
+ let sdk;
14
+ let routeDiscoveryUrl;
15
+ let serviceName;
16
+ let logger;
17
+ let apiKey;
18
+ let currentEnvironment = 'development';
19
+ /**
20
+ * Initialize the OpticTrace SDK
21
+ */
22
+ export function init(config) {
23
+ const { serviceName: name = 'nodejs-service', hubUrl = 'http://localhost:8080', apiKey: key, environment = process.env.OPTICTRACE_ENV || process.env.NODE_ENV || 'development', // Default to development
24
+ } = config;
25
+ currentEnvironment = environment;
26
+ serviceName = name;
27
+ apiKey = key;
28
+ routeDiscoveryUrl = `${hubUrl}/v1/routes`;
29
+ const resource = new Resource({
30
+ [ATTR_SERVICE_NAME]: serviceName,
31
+ 'deployment.environment': environment,
32
+ });
33
+ const traceExporter = new OTLPTraceExporter({
34
+ url: `${hubUrl}/v1/traces`,
35
+ headers: apiKey ? { 'X-API-Key': apiKey } : {},
36
+ });
37
+ const logExporter = new OTLPLogExporter({
38
+ url: `${hubUrl}/v1/logs`,
39
+ headers: apiKey ? { 'X-API-Key': apiKey } : {},
40
+ });
41
+ // Configure logging with immediate export
42
+ const loggerProvider = new LoggerProvider({ resource });
43
+ loggerProvider.addLogRecordProcessor(new BatchLogRecordProcessor(logExporter, {
44
+ maxQueueSize: 100,
45
+ maxExportBatchSize: 1, // Export immediately
46
+ scheduledDelayMillis: 500, // Export every 500ms
47
+ }));
48
+ logs.setGlobalLoggerProvider(loggerProvider);
49
+ logger = loggerProvider.getLogger(serviceName);
50
+ sdk = new NodeSDK({
51
+ resource,
52
+ traceExporter,
53
+ instrumentations: [
54
+ new HttpInstrumentation(),
55
+ new ExpressInstrumentation(),
56
+ new PgInstrumentation({
57
+ enhancedDatabaseReporting: true,
58
+ }),
59
+ new MongoDBInstrumentation({
60
+ enhancedDatabaseReporting: true,
61
+ }),
62
+ ],
63
+ });
64
+ sdk.start();
65
+ console.log(`[OpticTrace] SDK initialized for service: ${serviceName}`);
66
+ // Graceful shutdown
67
+ process.on('SIGTERM', () => {
68
+ sdk?.shutdown()
69
+ .then(() => console.log('[OpticTrace] SDK shut down successfully'))
70
+ .catch((error) => console.error('[OpticTrace] Error shutting down SDK', error))
71
+ .finally(() => process.exit(0));
72
+ });
73
+ }
74
+ /**
75
+ * Discover and register API routes from Express app
76
+ */
77
+ export function discoverRoutes(app) {
78
+ const appWithRouter = app;
79
+ if (!app || !appWithRouter._router) {
80
+ console.error('[OpticTrace] Invalid Express app provided');
81
+ return;
82
+ }
83
+ const routes = [];
84
+ // Extract routes from Express app
85
+ const extractRoutes = (stack, basePath = '') => {
86
+ stack.forEach((layer) => {
87
+ if (layer.route) {
88
+ // Regular route
89
+ const path = basePath + layer.route.path;
90
+ const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase());
91
+ methods.forEach(method => {
92
+ routes.push({ method, path });
93
+ });
94
+ }
95
+ else if (layer.name === 'router' && layer.handle?.stack) {
96
+ // Nested router
97
+ const routerPath = layer.regexp?.source
98
+ .replace('\\/?', '')
99
+ .replace('(?=\\/|$)', '')
100
+ .replace(/\\\//g, '/')
101
+ .replace(/\^/g, '')
102
+ .replace(/\$/g, '') || '';
103
+ extractRoutes(layer.handle.stack, basePath + routerPath);
104
+ }
105
+ });
106
+ };
107
+ extractRoutes(appWithRouter._router.stack);
108
+ fetch(routeDiscoveryUrl, {
109
+ method: 'POST',
110
+ headers: {
111
+ 'Content-Type': 'application/json',
112
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
113
+ },
114
+ body: JSON.stringify({ service: serviceName, routes }),
115
+ })
116
+ .then(() => console.log(`[OpticTrace] Discovered ${routes.length} routes`))
117
+ .catch((err) => console.error('[OpticTrace] Failed to send routes:', err));
118
+ }
119
+ /**
120
+ * Send a log message to OpticTrace
121
+ */
122
+ export function log(level, message, attributes = {}) {
123
+ // Send log directly to Hub via HTTP
124
+ const logData = {
125
+ timestamp: new Date().toISOString(),
126
+ service: serviceName,
127
+ level: level,
128
+ message: message,
129
+ attributes: attributes,
130
+ trace_id: '',
131
+ span_id: '',
132
+ environment: currentEnvironment,
133
+ };
134
+ // Send to Hub backend
135
+ fetch(`${routeDiscoveryUrl.replace('/v1/routes', '/api/logs/ingest')}`, {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json',
139
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
140
+ },
141
+ body: JSON.stringify(logData),
142
+ }).catch(() => {
143
+ // Silently fail - don't want logging to break the app
144
+ });
145
+ // Also log to console for debugging
146
+ const consoleMethod = level === 'ERROR' ? 'error' : level === 'WARN' ? 'warn' : 'log';
147
+ console[consoleMethod](`[${level}] ${message}`);
148
+ }
149
+ /**
150
+ * Internal function to trace database queries
151
+ */
152
+ function traceQuery(query, params, duration, dbSystem = 'postgresql', dbName = 'default', error = null) {
153
+ const traceData = {
154
+ timestamp: new Date().toISOString(),
155
+ service: serviceName,
156
+ query: query,
157
+ duration_ms: duration,
158
+ error: error ? error.message : null,
159
+ params: params ? JSON.stringify(params) : null,
160
+ db_system: dbSystem,
161
+ db_name: dbName,
162
+ environment: currentEnvironment,
163
+ };
164
+ // Send to Hub backend
165
+ fetch(`${routeDiscoveryUrl.replace('/v1/routes', '/api/traces/ingest')}`, {
166
+ method: 'POST',
167
+ headers: {
168
+ 'Content-Type': 'application/json',
169
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
170
+ },
171
+ body: JSON.stringify(traceData),
172
+ }).catch(() => {
173
+ // Silently fail
174
+ });
175
+ }
176
+ /**
177
+ * Wrap a PostgreSQL connection pool for automatic query tracing
178
+ */
179
+ export function wrapPool(pool) {
180
+ const originalQuery = pool.query.bind(pool);
181
+ // Override query method - use Function type to avoid complex overload issues
182
+ pool.query = function (...args) {
183
+ const startTime = Date.now();
184
+ let queryText = '';
185
+ let queryParams = null;
186
+ // Extract query and params from various call signatures
187
+ if (typeof args[0] === 'string') {
188
+ queryText = args[0];
189
+ queryParams = args[1] || null;
190
+ }
191
+ else if (args[0] && typeof args[0] === 'object' && 'text' in args[0]) {
192
+ const config = args[0];
193
+ queryText = config.text;
194
+ queryParams = config.values || null;
195
+ }
196
+ // Execute the original query
197
+ const result = originalQuery(...args);
198
+ // Handle promise-based queries
199
+ if (result && typeof result.then === 'function') {
200
+ return result
201
+ .then((res) => {
202
+ const duration = Date.now() - startTime;
203
+ traceQuery(queryText, queryParams, duration, 'postgresql', 'users_db');
204
+ return res;
205
+ })
206
+ .catch((err) => {
207
+ const duration = Date.now() - startTime;
208
+ traceQuery(queryText, queryParams, duration, 'postgresql', 'users_db', err);
209
+ throw err;
210
+ });
211
+ }
212
+ return result;
213
+ };
214
+ return pool;
215
+ }
216
+ /**
217
+ * Wrap a MongoDB client for automatic query tracing
218
+ */
219
+ export function wrapMongoClient(client) {
220
+ const originalDb = client.db.bind(client);
221
+ // Override db method - use Function type to avoid complex type issues
222
+ client.db = function (dbName) {
223
+ const db = originalDb(dbName);
224
+ const originalCollection = db.collection.bind(db);
225
+ // Override collection method
226
+ db.collection = function (collectionName) {
227
+ const collection = originalCollection(collectionName);
228
+ // Methods to wrap
229
+ const methods = [
230
+ 'find', 'findOne', 'insertOne', 'insertMany',
231
+ 'updateOne', 'updateMany', 'deleteOne', 'deleteMany',
232
+ 'countDocuments', 'aggregate'
233
+ ];
234
+ methods.forEach(method => {
235
+ const collectionWithMethod = collection;
236
+ if (typeof collectionWithMethod[method] === 'function') {
237
+ const originalMethod = collectionWithMethod[method].bind(collection);
238
+ collectionWithMethod[method] = function (...args) {
239
+ const startTime = Date.now();
240
+ const result = originalMethod(...args);
241
+ const handleFinish = (err, res) => {
242
+ const duration = Date.now() - startTime;
243
+ const query = `${method}(${JSON.stringify(args[0] || {})})`;
244
+ traceQuery(query, args.slice(1), duration, 'mongodb', dbName || 'default', err);
245
+ };
246
+ // Handle both promise-based and cursor-based return types
247
+ if (result && typeof result.then === 'function') {
248
+ return result
249
+ .then((res) => {
250
+ handleFinish(null, res);
251
+ return res;
252
+ })
253
+ .catch((err) => {
254
+ handleFinish(err);
255
+ throw err;
256
+ });
257
+ }
258
+ else if (result && typeof result.toArray === 'function') {
259
+ // Specialized path for cursors
260
+ const cursor = result;
261
+ const originalToArray = cursor.toArray.bind(result);
262
+ cursor.toArray = function (...taArgs) {
263
+ return originalToArray(...taArgs)
264
+ .then((res) => {
265
+ handleFinish(null, res);
266
+ return res;
267
+ })
268
+ .catch((err) => {
269
+ handleFinish(err);
270
+ throw err;
271
+ });
272
+ };
273
+ return result;
274
+ }
275
+ return result;
276
+ };
277
+ }
278
+ });
279
+ return collection;
280
+ };
281
+ return db;
282
+ };
283
+ return client;
284
+ }
285
+ /**
286
+ * Generate OpenAPI 3.0 specification from Express app routes
287
+ */
288
+ export function generateOpenApiSpec(app) {
289
+ const appWithRouter = app;
290
+ if (!app || !appWithRouter._router) {
291
+ throw new Error('[OpticTrace] Invalid Express app provided');
292
+ }
293
+ const routes = [];
294
+ // Extract routes from Express app (reusing logic from discoverRoutes)
295
+ const extractRoutes = (stack, basePath = '') => {
296
+ stack.forEach((layer) => {
297
+ if (layer.route) {
298
+ const path = basePath + layer.route.path;
299
+ const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase());
300
+ methods.forEach(method => {
301
+ routes.push({ method, path });
302
+ });
303
+ }
304
+ else if (layer.name === 'router' && layer.handle?.stack) {
305
+ const routerPath = layer.regexp?.source
306
+ .replace('\\/?', '')
307
+ .replace('(?=\\/|$)', '')
308
+ .replace(/\\\//g, '/')
309
+ .replace(/\^/g, '')
310
+ .replace(/\$/g, '') || '';
311
+ extractRoutes(layer.handle.stack, basePath + routerPath);
312
+ }
313
+ });
314
+ };
315
+ extractRoutes(appWithRouter._router.stack);
316
+ const spec = {
317
+ openapi: '3.0.0',
318
+ info: {
319
+ title: `${serviceName} API`,
320
+ version: '1.0.0',
321
+ description: `Auto-generated OpenAPI spec for ${serviceName}`,
322
+ },
323
+ paths: {},
324
+ };
325
+ routes.forEach((route) => {
326
+ // Convert Express path parameters (:id) to OpenAPI path parameters ({id})
327
+ const openApiPath = route.path.replace(/:(\w+)/g, '{$1}');
328
+ if (!spec.paths[openApiPath]) {
329
+ spec.paths[openApiPath] = {};
330
+ }
331
+ const method = route.method.toLowerCase();
332
+ spec.paths[openApiPath][method] = {
333
+ summary: `${route.method} ${route.path}`,
334
+ responses: {
335
+ 200: {
336
+ description: 'Successful response',
337
+ },
338
+ },
339
+ };
340
+ // Add parameters if any
341
+ const params = route.path.match(/:(\w+)/g);
342
+ if (params) {
343
+ spec.paths[openApiPath][method].parameters = params.map((p) => ({
344
+ name: p.substring(1),
345
+ in: 'path',
346
+ required: true,
347
+ schema: { type: 'string' },
348
+ }));
349
+ }
350
+ });
351
+ // Helper to convert object to simple YAML
352
+ const toYaml = (obj, indent = 0) => {
353
+ const spaces = ' '.repeat(indent);
354
+ let yaml = '';
355
+ for (const [key, value] of Object.entries(obj)) {
356
+ if (value === null || value === undefined) {
357
+ yaml += `${spaces}${key}: null\n`;
358
+ }
359
+ else if (Array.isArray(value)) {
360
+ yaml += `${spaces}${key}:\n`;
361
+ value.forEach((item) => {
362
+ if (typeof item === 'object') {
363
+ yaml += `${spaces} - ${toYaml(item, indent + 4).trimStart()}`;
364
+ }
365
+ else {
366
+ yaml += `${spaces} - ${item}\n`;
367
+ }
368
+ });
369
+ }
370
+ else if (typeof value === 'object') {
371
+ yaml += `${spaces}${key}:\n${toYaml(value, indent + 2)}`;
372
+ }
373
+ else if (typeof value === 'string') {
374
+ yaml += `${spaces}${key}: "${value}"\n`;
375
+ }
376
+ else {
377
+ yaml += `${spaces}${key}: ${value}\n`;
378
+ }
379
+ }
380
+ return yaml;
381
+ };
382
+ return toYaml(spec);
383
+ }
384
+ /**
385
+ * Save OpenAPI specification to a file
386
+ */
387
+ export function saveOpenApi(app, filePath) {
388
+ try {
389
+ const yamlSpec = generateOpenApiSpec(app);
390
+ fs.writeFileSync(filePath, yamlSpec, 'utf8');
391
+ console.log(`[OpticTrace] OpenAPI specification saved to ${filePath}`);
392
+ }
393
+ catch (error) {
394
+ console.error(`[OpticTrace] Failed to save OpenAPI specification: ${error}`);
395
+ }
396
+ }
397
+ // Default export
398
+ export default { init, discoverRoutes, log, wrapPool, wrapMongoClient, generateOpenApiSpec, saveOpenApi };
399
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * OpticTrace SDK Type Definitions
3
+ */
4
+ import type { Pool } from 'pg';
5
+ import type { MongoClient } from 'mongodb';
6
+ import type { Application } from 'express';
7
+ /**
8
+ * SDK Configuration
9
+ */
10
+ export interface OpticTraceConfig {
11
+ /** Name of the service */
12
+ serviceName: string;
13
+ /** Hub backend URL */
14
+ hubUrl?: string;
15
+ /** API key for authentication */
16
+ apiKey: string;
17
+ /** Deployment environment (default: 'development') */
18
+ environment?: string;
19
+ }
20
+ /**
21
+ * Route definition for API discovery
22
+ */
23
+ export interface Route {
24
+ method: string;
25
+ path: string;
26
+ }
27
+ /**
28
+ * Supported environments
29
+ */
30
+ export type Environment = 'development' | 'staging' | 'uat' | 'production';
31
+ /**
32
+ * Log severity levels
33
+ */
34
+ export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
35
+ /**
36
+ * Database system types
37
+ */
38
+ export type DBSystem = 'postgresql' | 'mongodb' | 'mysql' | 'sqlite';
39
+ /**
40
+ * Log attributes (key-value pairs)
41
+ */
42
+ export type LogAttributes = Record<string, string | number | boolean>;
43
+ /**
44
+ * Express Router Layer (internal structure)
45
+ */
46
+ export interface RouterLayer {
47
+ route?: {
48
+ path: string;
49
+ methods: Record<string, boolean>;
50
+ };
51
+ name?: string;
52
+ regexp?: RegExp & {
53
+ source: string;
54
+ };
55
+ handle?: {
56
+ stack: RouterLayer[];
57
+ };
58
+ }
59
+ /**
60
+ * PostgreSQL Query Config
61
+ */
62
+ export interface QueryConfig {
63
+ text: string;
64
+ values?: unknown[];
65
+ }
66
+ /**
67
+ * Database query parameters (can be array or object)
68
+ */
69
+ export type QueryParams = unknown[] | Record<string, unknown> | null;
70
+ /**
71
+ * OpticTrace SDK Interface
72
+ */
73
+ export interface OpticTraceSDK {
74
+ /**
75
+ * Initialize the SDK
76
+ */
77
+ init(config: OpticTraceConfig): void;
78
+ /**
79
+ * Discover and register API routes
80
+ */
81
+ discoverRoutes(app: Application): void;
82
+ /**
83
+ * Send a log message
84
+ */
85
+ log(level: LogLevel, message: string, attributes?: LogAttributes): void;
86
+ /**
87
+ * Wrap a PostgreSQL connection pool for automatic tracing
88
+ */
89
+ wrapPool<T extends Pool>(pool: T): T;
90
+ /**
91
+ * Wrap a MongoDB client for automatic tracing
92
+ */
93
+ wrapMongoClient<T extends MongoClient>(client: T): T;
94
+ /**
95
+ * Generate OpenAPI 3.0 specification from Express app routes
96
+ */
97
+ generateOpenApiSpec(app: Application): string;
98
+ /**
99
+ * Save OpenAPI specification to a file
100
+ */
101
+ saveOpenApi(app: Application, filePath: string): void;
102
+ }
103
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAA+B,MAAM,IAAI,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,KAAK;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,KAAK,GAAG,YAAY,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,KAAK,CAAC,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC;IACF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,MAAM,CAAC,EAAE;QACL,KAAK,EAAE,WAAW,EAAE,CAAC;KACxB,CAAC;CACL;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAErC;;OAEG;IACH,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAAC;IAEvC;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAExE;;OAEG;IACH,QAAQ,CAAC,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;IAErC;;OAEG;IACH,eAAe,CAAC,CAAC,SAAS,WAAW,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IAErD;;OAEG;IACH,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAAC;IAE9C;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzD"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * OpticTrace SDK Type Definitions
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "optictrace",
3
+ "version": "1.0.1",
4
+ "description": "OpenTelemetry-based SDK for OpticTrace observability",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "watch": "tsc --watch",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "echo \"Error: no test specified\" && exit 1"
24
+ },
25
+ "keywords": [
26
+ "observability",
27
+ "opentelemetry",
28
+ "tracing",
29
+ "logging",
30
+ "typescript"
31
+ ],
32
+ "author": "Rajat",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/rajat/project-lens"
37
+ },
38
+ "dependencies": {
39
+ "@opentelemetry/api": "^1.9.0",
40
+ "@opentelemetry/api-logs": "^0.54.0",
41
+ "@opentelemetry/auto-instrumentations-node": "^0.54.0",
42
+ "@opentelemetry/exporter-logs-otlp-http": "^0.54.0",
43
+ "@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
44
+ "@opentelemetry/instrumentation-express": "^0.44.0",
45
+ "@opentelemetry/instrumentation-http": "^0.54.0",
46
+ "@opentelemetry/instrumentation-mongodb": "^0.48.0",
47
+ "@opentelemetry/instrumentation-pg": "^0.48.0",
48
+ "@opentelemetry/resources": "^1.29.0",
49
+ "@opentelemetry/sdk-logs": "^0.54.0",
50
+ "@opentelemetry/sdk-node": "^0.54.0",
51
+ "@opentelemetry/semantic-conventions": "^1.29.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/express": "^5.0.6",
55
+ "@types/mongodb": "^4.0.6",
56
+ "@types/node": "^20.10.6",
57
+ "@types/pg": "^8.10.9",
58
+ "express": "^5.2.1",
59
+ "mongodb": "^7.1.0",
60
+ "typescript": "^5.3.3"
61
+ },
62
+ "peerDependencies": {
63
+ "express": "^4.18.0",
64
+ "mongodb": "^6.3.0",
65
+ "pg": "^8.11.0"
66
+ },
67
+ "peerDependenciesMeta": {
68
+ "pg": {
69
+ "optional": true
70
+ },
71
+ "mongodb": {
72
+ "optional": true
73
+ }
74
+ }
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,462 @@
1
+ import { NodeSDK } from '@opentelemetry/sdk-node';
2
+ import * as fs from 'fs';
3
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
4
+ import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
5
+ import { Resource } from '@opentelemetry/resources';
6
+ import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
7
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
8
+ import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express';
9
+ import { PgInstrumentation } from '@opentelemetry/instrumentation-pg';
10
+ import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb';
11
+ import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
12
+ import { logs } from '@opentelemetry/api-logs';
13
+ import type { Pool, QueryResult, QueryResultRow } from 'pg';
14
+ import type { MongoClient, Db, Collection, Document } from 'mongodb';
15
+ import type { Application } from 'express';
16
+ import type { Logger } from '@opentelemetry/api-logs';
17
+ import type { OpticTraceConfig, Route, LogLevel, LogAttributes, DBSystem, RouterLayer, QueryConfig, QueryParams } from './types';
18
+
19
+ let sdk: NodeSDK | undefined;
20
+ let routeDiscoveryUrl: string;
21
+ let serviceName: string;
22
+ let logger: Logger | undefined;
23
+ let apiKey: string | undefined;
24
+ let currentEnvironment: string = 'development';
25
+
26
+ /**
27
+ * Initialize the OpticTrace SDK
28
+ */
29
+ export function init(config: OpticTraceConfig): void {
30
+ const {
31
+ serviceName: name = 'nodejs-service',
32
+ hubUrl = 'http://localhost:8080',
33
+ apiKey: key,
34
+ environment = process.env.OPTICTRACE_ENV || process.env.NODE_ENV || 'development', // Default to development
35
+ } = config;
36
+
37
+ currentEnvironment = environment;
38
+
39
+ serviceName = name;
40
+ apiKey = key;
41
+ routeDiscoveryUrl = `${hubUrl}/v1/routes`;
42
+
43
+ const resource = new Resource({
44
+ [ATTR_SERVICE_NAME]: serviceName,
45
+ 'deployment.environment': environment,
46
+ });
47
+
48
+ const traceExporter = new OTLPTraceExporter({
49
+ url: `${hubUrl}/v1/traces`,
50
+ headers: apiKey ? { 'X-API-Key': apiKey } : {},
51
+ });
52
+
53
+ const logExporter = new OTLPLogExporter({
54
+ url: `${hubUrl}/v1/logs`,
55
+ headers: apiKey ? { 'X-API-Key': apiKey } : {},
56
+ });
57
+
58
+ // Configure logging with immediate export
59
+ const loggerProvider = new LoggerProvider({ resource });
60
+ loggerProvider.addLogRecordProcessor(
61
+ new BatchLogRecordProcessor(logExporter, {
62
+ maxQueueSize: 100,
63
+ maxExportBatchSize: 1, // Export immediately
64
+ scheduledDelayMillis: 500, // Export every 500ms
65
+ })
66
+ );
67
+ logs.setGlobalLoggerProvider(loggerProvider);
68
+ logger = loggerProvider.getLogger(serviceName);
69
+
70
+ sdk = new NodeSDK({
71
+ resource,
72
+ traceExporter,
73
+ instrumentations: [
74
+ new HttpInstrumentation(),
75
+ new ExpressInstrumentation(),
76
+ new PgInstrumentation({
77
+ enhancedDatabaseReporting: true,
78
+ }),
79
+ new MongoDBInstrumentation({
80
+ enhancedDatabaseReporting: true,
81
+ }),
82
+ ],
83
+ });
84
+
85
+ sdk.start();
86
+ console.log(`[OpticTrace] SDK initialized for service: ${serviceName}`);
87
+
88
+ // Graceful shutdown
89
+ process.on('SIGTERM', () => {
90
+ sdk?.shutdown()
91
+ .then(() => console.log('[OpticTrace] SDK shut down successfully'))
92
+ .catch((error) => console.error('[OpticTrace] Error shutting down SDK', error))
93
+ .finally(() => process.exit(0));
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Discover and register API routes from Express app
99
+ */
100
+ export function discoverRoutes(app: Application): void {
101
+ const appWithRouter = app as Application & { _router?: { stack: RouterLayer[] } };
102
+
103
+ if (!app || !appWithRouter._router) {
104
+ console.error('[OpticTrace] Invalid Express app provided');
105
+ return;
106
+ }
107
+
108
+ const routes: Route[] = [];
109
+
110
+ // Extract routes from Express app
111
+ const extractRoutes = (stack: RouterLayer[], basePath = ''): void => {
112
+ stack.forEach((layer) => {
113
+ if (layer.route) {
114
+ // Regular route
115
+ const path = basePath + layer.route.path;
116
+ const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase());
117
+ methods.forEach(method => {
118
+ routes.push({ method, path });
119
+ });
120
+ } else if (layer.name === 'router' && layer.handle?.stack) {
121
+ // Nested router
122
+ const routerPath = layer.regexp?.source
123
+ .replace('\\/?', '')
124
+ .replace('(?=\\/|$)', '')
125
+ .replace(/\\\//g, '/')
126
+ .replace(/\^/g, '')
127
+ .replace(/\$/g, '') || '';
128
+ extractRoutes(layer.handle.stack, basePath + routerPath);
129
+ }
130
+ });
131
+ };
132
+
133
+ extractRoutes(appWithRouter._router.stack);
134
+
135
+ fetch(routeDiscoveryUrl, {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/json',
139
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
140
+ },
141
+ body: JSON.stringify({ service: serviceName, routes }),
142
+ })
143
+ .then(() => console.log(`[OpticTrace] Discovered ${routes.length} routes`))
144
+ .catch((err) => console.error('[OpticTrace] Failed to send routes:', err));
145
+ }
146
+
147
+ /**
148
+ * Send a log message to OpticTrace
149
+ */
150
+ export function log(level: LogLevel, message: string, attributes: LogAttributes = {}): void {
151
+ // Send log directly to Hub via HTTP
152
+ const logData = {
153
+ timestamp: new Date().toISOString(),
154
+ service: serviceName,
155
+ level: level,
156
+ message: message,
157
+ attributes: attributes,
158
+ trace_id: '',
159
+ span_id: '',
160
+ environment: currentEnvironment,
161
+ };
162
+
163
+ // Send to Hub backend
164
+ fetch(`${routeDiscoveryUrl.replace('/v1/routes', '/api/logs/ingest')}`, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
169
+ },
170
+ body: JSON.stringify(logData),
171
+ }).catch(() => {
172
+ // Silently fail - don't want logging to break the app
173
+ });
174
+
175
+ // Also log to console for debugging
176
+ const consoleMethod = level === 'ERROR' ? 'error' : level === 'WARN' ? 'warn' : 'log';
177
+ console[consoleMethod](`[${level}] ${message}`);
178
+ }
179
+
180
+ /**
181
+ * Internal function to trace database queries
182
+ */
183
+ function traceQuery(
184
+ query: string,
185
+ params: QueryParams,
186
+ duration: number,
187
+ dbSystem: DBSystem = 'postgresql',
188
+ dbName = 'default',
189
+ error: Error | null = null
190
+ ): void {
191
+ const traceData = {
192
+ timestamp: new Date().toISOString(),
193
+ service: serviceName,
194
+ query: query,
195
+ duration_ms: duration,
196
+ error: error ? error.message : null,
197
+ params: params ? JSON.stringify(params) : null,
198
+ db_system: dbSystem,
199
+ db_name: dbName,
200
+ environment: currentEnvironment,
201
+ };
202
+
203
+ // Send to Hub backend
204
+ fetch(`${routeDiscoveryUrl.replace('/v1/routes', '/api/traces/ingest')}`, {
205
+ method: 'POST',
206
+ headers: {
207
+ 'Content-Type': 'application/json',
208
+ ...(apiKey ? { 'X-API-Key': apiKey } : {})
209
+ },
210
+ body: JSON.stringify(traceData),
211
+ }).catch(() => {
212
+ // Silently fail
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Wrap a PostgreSQL connection pool for automatic query tracing
218
+ */
219
+ export function wrapPool<T extends Pool>(pool: T): T {
220
+ const originalQuery = pool.query.bind(pool);
221
+
222
+ // Override query method - use Function type to avoid complex overload issues
223
+ (pool as { query: Function }).query = function (...args: unknown[]): unknown {
224
+ const startTime = Date.now();
225
+ let queryText = '';
226
+ let queryParams: QueryParams = null;
227
+
228
+ // Extract query and params from various call signatures
229
+ if (typeof args[0] === 'string') {
230
+ queryText = args[0];
231
+ queryParams = (args[1] as QueryParams) || null;
232
+ } else if (args[0] && typeof args[0] === 'object' && 'text' in args[0]) {
233
+ const config = args[0] as QueryConfig;
234
+ queryText = config.text;
235
+ queryParams = config.values || null;
236
+ }
237
+
238
+ // Execute the original query
239
+ const result = (originalQuery as Function)(...args);
240
+
241
+ // Handle promise-based queries
242
+ if (result && typeof (result as { then?: Function }).then === 'function') {
243
+ return (result as Promise<unknown>)
244
+ .then((res) => {
245
+ const duration = Date.now() - startTime;
246
+ traceQuery(queryText, queryParams, duration, 'postgresql', 'users_db');
247
+ return res;
248
+ })
249
+ .catch((err) => {
250
+ const duration = Date.now() - startTime;
251
+ traceQuery(queryText, queryParams, duration, 'postgresql', 'users_db', err);
252
+ throw err;
253
+ });
254
+ }
255
+
256
+ return result;
257
+ };
258
+
259
+ return pool;
260
+ }
261
+
262
+ /**
263
+ * Wrap a MongoDB client for automatic query tracing
264
+ */
265
+ export function wrapMongoClient<T extends MongoClient>(client: T): T {
266
+ const originalDb = client.db.bind(client);
267
+
268
+ // Override db method - use Function type to avoid complex type issues
269
+ (client as { db: Function }).db = function (dbName?: string): Db {
270
+ const db = originalDb(dbName);
271
+ const originalCollection = db.collection.bind(db);
272
+
273
+ // Override collection method
274
+ (db as { collection: Function }).collection = function (collectionName: string): Collection<Document> {
275
+ const collection = originalCollection(collectionName);
276
+
277
+ // Methods to wrap
278
+ const methods = [
279
+ 'find', 'findOne', 'insertOne', 'insertMany',
280
+ 'updateOne', 'updateMany', 'deleteOne', 'deleteMany',
281
+ 'countDocuments', 'aggregate'
282
+ ];
283
+
284
+ methods.forEach(method => {
285
+ const collectionWithMethod = collection as Collection<Document> & Record<string, Function>;
286
+
287
+ if (typeof collectionWithMethod[method] === 'function') {
288
+ const originalMethod = collectionWithMethod[method].bind(collection);
289
+ collectionWithMethod[method] = function (...args: unknown[]): unknown {
290
+ const startTime = Date.now();
291
+ const result = originalMethod(...args);
292
+
293
+ const handleFinish = (err: Error | null, res?: unknown): void => {
294
+ const duration = Date.now() - startTime;
295
+ const query = `${method}(${JSON.stringify(args[0] || {})})`;
296
+ traceQuery(query, args.slice(1), duration, 'mongodb', dbName || 'default', err);
297
+ };
298
+
299
+ // Handle both promise-based and cursor-based return types
300
+ if (result && typeof (result as { then?: Function }).then === 'function') {
301
+ return (result as Promise<unknown>)
302
+ .then((res: unknown) => {
303
+ handleFinish(null, res);
304
+ return res;
305
+ })
306
+ .catch((err: Error) => {
307
+ handleFinish(err);
308
+ throw err;
309
+ });
310
+ } else if (result && typeof (result as { toArray?: Function }).toArray === 'function') {
311
+ // Specialized path for cursors
312
+ const cursor = result as { toArray: (...args: unknown[]) => Promise<unknown> };
313
+ const originalToArray = cursor.toArray.bind(result);
314
+ cursor.toArray = function (...taArgs: unknown[]): Promise<unknown> {
315
+ return originalToArray(...taArgs)
316
+ .then((res: unknown) => {
317
+ handleFinish(null, res);
318
+ return res;
319
+ })
320
+ .catch((err: Error) => {
321
+ handleFinish(err);
322
+ throw err;
323
+ });
324
+ };
325
+ return result;
326
+ }
327
+
328
+ return result;
329
+ };
330
+ }
331
+ });
332
+
333
+ return collection;
334
+ };
335
+
336
+ return db;
337
+ };
338
+
339
+ return client;
340
+ }
341
+
342
+ /**
343
+ * Generate OpenAPI 3.0 specification from Express app routes
344
+ */
345
+ export function generateOpenApiSpec(app: Application): string {
346
+ const appWithRouter = app as Application & { _router?: { stack: RouterLayer[] } };
347
+
348
+ if (!app || !appWithRouter._router) {
349
+ throw new Error('[OpticTrace] Invalid Express app provided');
350
+ }
351
+
352
+ const routes: Route[] = [];
353
+
354
+ // Extract routes from Express app (reusing logic from discoverRoutes)
355
+ const extractRoutes = (stack: RouterLayer[], basePath = ''): void => {
356
+ stack.forEach((layer) => {
357
+ if (layer.route) {
358
+ const path = basePath + layer.route.path;
359
+ const methods = Object.keys(layer.route.methods).map(m => m.toUpperCase());
360
+ methods.forEach(method => {
361
+ routes.push({ method, path });
362
+ });
363
+ } else if (layer.name === 'router' && layer.handle?.stack) {
364
+ const routerPath = layer.regexp?.source
365
+ .replace('\\/?', '')
366
+ .replace('(?=\\/|$)', '')
367
+ .replace(/\\\//g, '/')
368
+ .replace(/\^/g, '')
369
+ .replace(/\$/g, '') || '';
370
+ extractRoutes(layer.handle.stack, basePath + routerPath);
371
+ }
372
+ });
373
+ };
374
+
375
+ extractRoutes(appWithRouter._router.stack);
376
+
377
+ const spec = {
378
+ openapi: '3.0.0',
379
+ info: {
380
+ title: `${serviceName} API`,
381
+ version: '1.0.0',
382
+ description: `Auto-generated OpenAPI spec for ${serviceName}`,
383
+ },
384
+ paths: {} as Record<string, any>,
385
+ };
386
+
387
+ routes.forEach((route) => {
388
+ // Convert Express path parameters (:id) to OpenAPI path parameters ({id})
389
+ const openApiPath = route.path.replace(/:(\w+)/g, '{$1}');
390
+
391
+ if (!spec.paths[openApiPath]) {
392
+ spec.paths[openApiPath] = {};
393
+ }
394
+
395
+ const method = route.method.toLowerCase();
396
+ spec.paths[openApiPath][method] = {
397
+ summary: `${route.method} ${route.path}`,
398
+ responses: {
399
+ 200: {
400
+ description: 'Successful response',
401
+ },
402
+ },
403
+ };
404
+
405
+ // Add parameters if any
406
+ const params = route.path.match(/:(\w+)/g);
407
+ if (params) {
408
+ spec.paths[openApiPath][method].parameters = params.map((p) => ({
409
+ name: p.substring(1),
410
+ in: 'path',
411
+ required: true,
412
+ schema: { type: 'string' },
413
+ }));
414
+ }
415
+ });
416
+
417
+ // Helper to convert object to simple YAML
418
+ const toYaml = (obj: any, indent = 0): string => {
419
+ const spaces = ' '.repeat(indent);
420
+ let yaml = '';
421
+
422
+ for (const [key, value] of Object.entries(obj)) {
423
+ if (value === null || value === undefined) {
424
+ yaml += `${spaces}${key}: null\n`;
425
+ } else if (Array.isArray(value)) {
426
+ yaml += `${spaces}${key}:\n`;
427
+ value.forEach((item) => {
428
+ if (typeof item === 'object') {
429
+ yaml += `${spaces} - ${toYaml(item, indent + 4).trimStart()}`;
430
+ } else {
431
+ yaml += `${spaces} - ${item}\n`;
432
+ }
433
+ });
434
+ } else if (typeof value === 'object') {
435
+ yaml += `${spaces}${key}:\n${toYaml(value, indent + 2)}`;
436
+ } else if (typeof value === 'string') {
437
+ yaml += `${spaces}${key}: "${value}"\n`;
438
+ } else {
439
+ yaml += `${spaces}${key}: ${value}\n`;
440
+ }
441
+ }
442
+ return yaml;
443
+ };
444
+
445
+ return toYaml(spec);
446
+ }
447
+
448
+ /**
449
+ * Save OpenAPI specification to a file
450
+ */
451
+ export function saveOpenApi(app: Application, filePath: string): void {
452
+ try {
453
+ const yamlSpec = generateOpenApiSpec(app);
454
+ fs.writeFileSync(filePath, yamlSpec, 'utf8');
455
+ console.log(`[OpticTrace] OpenAPI specification saved to ${filePath}`);
456
+ } catch (error) {
457
+ console.error(`[OpticTrace] Failed to save OpenAPI specification: ${error}`);
458
+ }
459
+ }
460
+
461
+ // Default export
462
+ export default { init, discoverRoutes, log, wrapPool, wrapMongoClient, generateOpenApiSpec, saveOpenApi };
package/src/types.ts ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * OpticTrace SDK Type Definitions
3
+ */
4
+
5
+ import type { Pool, QueryResult, QueryResultRow } from 'pg';
6
+ import type { MongoClient } from 'mongodb';
7
+ import type { Application } from 'express';
8
+ import type { Logger } from '@opentelemetry/api-logs';
9
+
10
+ /**
11
+ * SDK Configuration
12
+ */
13
+ export interface OpticTraceConfig {
14
+ /** Name of the service */
15
+ serviceName: string;
16
+ /** Hub backend URL */
17
+ hubUrl?: string;
18
+ /** API key for authentication */
19
+ apiKey: string;
20
+ /** Deployment environment (default: 'development') */
21
+ environment?: string;
22
+ }
23
+
24
+ /**
25
+ * Route definition for API discovery
26
+ */
27
+ export interface Route {
28
+ method: string;
29
+ path: string;
30
+ }
31
+
32
+ /**
33
+ * Supported environments
34
+ */
35
+ export type Environment = 'development' | 'staging' | 'uat' | 'production';
36
+
37
+ /**
38
+ * Log severity levels
39
+ */
40
+ export type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';
41
+
42
+ /**
43
+ * Database system types
44
+ */
45
+ export type DBSystem = 'postgresql' | 'mongodb' | 'mysql' | 'sqlite';
46
+
47
+ /**
48
+ * Log attributes (key-value pairs)
49
+ */
50
+ export type LogAttributes = Record<string, string | number | boolean>;
51
+
52
+ /**
53
+ * Express Router Layer (internal structure)
54
+ */
55
+ export interface RouterLayer {
56
+ route?: {
57
+ path: string;
58
+ methods: Record<string, boolean>;
59
+ };
60
+ name?: string;
61
+ regexp?: RegExp & { source: string };
62
+ handle?: {
63
+ stack: RouterLayer[];
64
+ };
65
+ }
66
+
67
+ /**
68
+ * PostgreSQL Query Config
69
+ */
70
+ export interface QueryConfig {
71
+ text: string;
72
+ values?: unknown[];
73
+ }
74
+
75
+ /**
76
+ * Database query parameters (can be array or object)
77
+ */
78
+ export type QueryParams = unknown[] | Record<string, unknown> | null;
79
+
80
+ /**
81
+ * OpticTrace SDK Interface
82
+ */
83
+ export interface OpticTraceSDK {
84
+ /**
85
+ * Initialize the SDK
86
+ */
87
+ init(config: OpticTraceConfig): void;
88
+
89
+ /**
90
+ * Discover and register API routes
91
+ */
92
+ discoverRoutes(app: Application): void;
93
+
94
+ /**
95
+ * Send a log message
96
+ */
97
+ log(level: LogLevel, message: string, attributes?: LogAttributes): void;
98
+
99
+ /**
100
+ * Wrap a PostgreSQL connection pool for automatic tracing
101
+ */
102
+ wrapPool<T extends Pool>(pool: T): T;
103
+
104
+ /**
105
+ * Wrap a MongoDB client for automatic tracing
106
+ */
107
+ wrapMongoClient<T extends MongoClient>(client: T): T;
108
+
109
+ /**
110
+ * Generate OpenAPI 3.0 specification from Express app routes
111
+ */
112
+ generateOpenApiSpec(app: Application): string;
113
+
114
+ /**
115
+ * Save OpenAPI specification to a file
116
+ */
117
+ saveOpenApi(app: Application, filePath: string): void;
118
+ }