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 +99 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +399 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +75 -0
- package/src/index.ts +462 -0
- package/src/types.ts +118 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
|
+
}
|