@aichatwar/shared 1.0.146 → 1.0.148

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.
@@ -10,6 +10,7 @@ interface UserCreatedEvent extends BaseEvent {
10
10
  version: number;
11
11
  isAgent?: boolean;
12
12
  ownerUserId?: string;
13
+ role?: 'user' | 'admin';
13
14
  };
14
15
  }
15
16
  interface UserUpdatedEvent extends BaseEvent {
@@ -19,6 +20,7 @@ interface UserUpdatedEvent extends BaseEvent {
19
20
  email: string;
20
21
  status: UserStatus;
21
22
  version: number;
23
+ role?: 'user' | 'admin';
22
24
  };
23
25
  }
24
26
  interface UserSingedInEvent extends BaseEvent {
package/build/index.d.ts CHANGED
@@ -7,10 +7,13 @@ export * from "./errors/requestValidationError";
7
7
  export * from "./middlewares/error-handler";
8
8
  export * from "./middlewares/jwt-extractor";
9
9
  export * from "./middlewares/login-required";
10
+ export * from "./middlewares/require-role";
10
11
  export * from "./middlewares/validate-request";
11
12
  export * from "./observability/logger";
12
13
  export * from "./observability/correlation";
13
14
  export * from "./observability/express";
15
+ export * from "./observability/tracing";
16
+ export * from "./observability/third-party-logging";
14
17
  export * from "./events/nats/baseListener";
15
18
  export * from "./events/nats/basePublisher";
16
19
  export * from "./events/kafka/baseListener";
package/build/index.js CHANGED
@@ -23,11 +23,14 @@ __exportStar(require("./errors/requestValidationError"), exports);
23
23
  __exportStar(require("./middlewares/error-handler"), exports);
24
24
  __exportStar(require("./middlewares/jwt-extractor"), exports);
25
25
  __exportStar(require("./middlewares/login-required"), exports);
26
+ __exportStar(require("./middlewares/require-role"), exports);
26
27
  __exportStar(require("./middlewares/validate-request"), exports);
27
28
  // Observability (Phase 1)
28
29
  __exportStar(require("./observability/logger"), exports);
29
30
  __exportStar(require("./observability/correlation"), exports);
30
31
  __exportStar(require("./observability/express"), exports);
32
+ __exportStar(require("./observability/tracing"), exports);
33
+ __exportStar(require("./observability/third-party-logging"), exports);
31
34
  __exportStar(require("./events/nats/baseListener"), exports);
32
35
  __exportStar(require("./events/nats/basePublisher"), exports);
33
36
  __exportStar(require("./events/kafka/baseListener"), exports);
@@ -1,7 +1,8 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
- interface JwtPayload {
2
+ export interface JwtPayload {
3
3
  id: string;
4
4
  email: string;
5
+ role?: 'user' | 'admin';
5
6
  }
6
7
  declare global {
7
8
  namespace Express {
@@ -0,0 +1,21 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ /**
3
+ * Middleware to require a specific role or roles
4
+ * Must be used after extractJWTPayload and loginRequired middleware
5
+ *
6
+ * @param allowedRoles - Single role string or array of role strings
7
+ * @returns Express middleware function
8
+ *
9
+ * @example
10
+ * router.get('/admin/users', extractJWTPayload, loginRequired, requireRole('admin'), handler);
11
+ * router.get('/moderator/content', extractJWTPayload, loginRequired, requireRole(['admin', 'moderator']), handler);
12
+ */
13
+ export declare const requireRole: (allowedRoles: string | string[]) => (req: Request, res: Response, next: NextFunction) => void;
14
+ /**
15
+ * Convenience middleware to require admin role
16
+ * Must be used after extractJWTPayload and loginRequired middleware
17
+ *
18
+ * @example
19
+ * router.get('/admin/users', extractJWTPayload, loginRequired, requireAdmin, handler);
20
+ */
21
+ export declare const requireAdmin: (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireAdmin = exports.requireRole = void 0;
4
+ const notAuthorizedError_1 = require("../errors/notAuthorizedError");
5
+ /**
6
+ * Middleware to require a specific role or roles
7
+ * Must be used after extractJWTPayload and loginRequired middleware
8
+ *
9
+ * @param allowedRoles - Single role string or array of role strings
10
+ * @returns Express middleware function
11
+ *
12
+ * @example
13
+ * router.get('/admin/users', extractJWTPayload, loginRequired, requireRole('admin'), handler);
14
+ * router.get('/moderator/content', extractJWTPayload, loginRequired, requireRole(['admin', 'moderator']), handler);
15
+ */
16
+ const requireRole = (allowedRoles) => {
17
+ return (req, res, next) => {
18
+ if (!req.jwtPayload) {
19
+ throw new notAuthorizedError_1.NotAuthorizedError(['Authentication required']);
20
+ }
21
+ const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
22
+ const userRole = req.jwtPayload.role || 'user'; // Default to 'user' if role not set
23
+ if (!roles.includes(userRole)) {
24
+ throw new notAuthorizedError_1.NotAuthorizedError(['Insufficient permissions']);
25
+ }
26
+ next();
27
+ };
28
+ };
29
+ exports.requireRole = requireRole;
30
+ /**
31
+ * Convenience middleware to require admin role
32
+ * Must be used after extractJWTPayload and loginRequired middleware
33
+ *
34
+ * @example
35
+ * router.get('/admin/users', extractJWTPayload, loginRequired, requireAdmin, handler);
36
+ */
37
+ exports.requireAdmin = (0, exports.requireRole)('admin');
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Standardized logging for 3rd-party API calls
3
+ *
4
+ * This module provides helpers for logging:
5
+ * - Retry attempts (WARN level)
6
+ * - Final failures (ERROR level)
7
+ * - Rate limits (WARN/ERROR based on impact)
8
+ * - Unmasked error details (stack traces, status codes, request IDs)
9
+ * - Correlation IDs for traceability
10
+ *
11
+ * Best practices:
12
+ * - Each retry: log as WARN with attempt, backoff, reason
13
+ * - Final failure: log as ERROR with full error context
14
+ * - Rate limits: log as WARN or ERROR depending on user impact
15
+ */
16
+ export interface ThirdPartyErrorContext {
17
+ /** Provider name (e.g., 'openai', 'anthropic', 'cohere') */
18
+ provider: string;
19
+ /** Operation name (e.g., 'generateResponse', 'createAgent') */
20
+ operation: string;
21
+ /** HTTP status code if available */
22
+ statusCode?: number;
23
+ /** Error type/class */
24
+ errorType?: string;
25
+ /** Error message */
26
+ errorMessage: string;
27
+ /** Full error object (for stack trace) */
28
+ error: any;
29
+ /** Whether this error is retryable */
30
+ retryable?: boolean;
31
+ /** Request ID from upstream service if available */
32
+ requestId?: string;
33
+ /** Rate limit headers if available */
34
+ rateLimitHeaders?: {
35
+ limit?: string;
36
+ remaining?: string;
37
+ reset?: string;
38
+ };
39
+ /** Additional metadata */
40
+ metadata?: Record<string, any>;
41
+ }
42
+ export interface RetryAttemptContext {
43
+ /** Provider name */
44
+ provider: string;
45
+ /** Operation name */
46
+ operation: string;
47
+ /** Current attempt number (1-indexed) */
48
+ attempt: number;
49
+ /** Maximum number of attempts */
50
+ maxAttempts: number;
51
+ /** Backoff delay in milliseconds */
52
+ backoffMs: number;
53
+ /** Reason for retry */
54
+ reason: string;
55
+ /** Previous error that triggered retry */
56
+ previousError?: any;
57
+ /** HTTP status code from previous attempt */
58
+ previousStatusCode?: number;
59
+ }
60
+ /**
61
+ * Log a retry attempt for a 3rd-party API call
62
+ *
63
+ * This should be called BEFORE each retry attempt.
64
+ * Logs at WARN level as per observability best practices.
65
+ *
66
+ * @param context Retry attempt context
67
+ */
68
+ export declare function logRetryAttempt(context: RetryAttemptContext): void;
69
+ /**
70
+ * Log a final failure for a 3rd-party API call
71
+ *
72
+ * This should be called when all retries are exhausted or when a non-retryable error occurs.
73
+ * Logs at ERROR level with full unmasked error details.
74
+ *
75
+ * @param context Error context
76
+ */
77
+ export declare function logThirdPartyError(context: ThirdPartyErrorContext): void;
78
+ /**
79
+ * Log a successful 3rd-party API call
80
+ *
81
+ * @param provider Provider name
82
+ * @param operation Operation name
83
+ * @param durationMs Duration in milliseconds
84
+ * @param metadata Additional metadata (e.g., token usage, request ID)
85
+ */
86
+ export declare function logThirdPartySuccess(provider: string, operation: string, durationMs: number, metadata?: Record<string, any>): void;
87
+ /**
88
+ * Extract error context from a caught error
89
+ *
90
+ * Handles various error types:
91
+ * - Axios errors (with response.status, response.data)
92
+ * - OpenAI SDK errors (with status, message)
93
+ * - Anthropic SDK errors (with status, message)
94
+ * - Generic errors
95
+ *
96
+ * @param error Error object
97
+ * @param provider Provider name
98
+ * @param operation Operation name
99
+ * @returns Error context
100
+ */
101
+ export declare function extractErrorContext(error: any, provider: string, operation: string): ThirdPartyErrorContext;
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * Standardized logging for 3rd-party API calls
4
+ *
5
+ * This module provides helpers for logging:
6
+ * - Retry attempts (WARN level)
7
+ * - Final failures (ERROR level)
8
+ * - Rate limits (WARN/ERROR based on impact)
9
+ * - Unmasked error details (stack traces, status codes, request IDs)
10
+ * - Correlation IDs for traceability
11
+ *
12
+ * Best practices:
13
+ * - Each retry: log as WARN with attempt, backoff, reason
14
+ * - Final failure: log as ERROR with full error context
15
+ * - Rate limits: log as WARN or ERROR depending on user impact
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.logRetryAttempt = logRetryAttempt;
19
+ exports.logThirdPartyError = logThirdPartyError;
20
+ exports.logThirdPartySuccess = logThirdPartySuccess;
21
+ exports.extractErrorContext = extractErrorContext;
22
+ const logger_1 = require("./logger");
23
+ const correlation_1 = require("./correlation");
24
+ const tracing_1 = require("./tracing");
25
+ /**
26
+ * Log a retry attempt for a 3rd-party API call
27
+ *
28
+ * This should be called BEFORE each retry attempt.
29
+ * Logs at WARN level as per observability best practices.
30
+ *
31
+ * @param context Retry attempt context
32
+ */
33
+ function logRetryAttempt(context) {
34
+ var _a, _b, _c, _d, _e, _f;
35
+ const correlationId = (0, correlation_1.getCorrelationId)();
36
+ (0, logger_1.logger)().warn({
37
+ message: '3rd-party API retry attempt',
38
+ provider: context.provider,
39
+ operation: context.operation,
40
+ attempt: context.attempt,
41
+ maxAttempts: context.maxAttempts,
42
+ backoffMs: context.backoffMs,
43
+ reason: context.reason,
44
+ previousStatusCode: context.previousStatusCode,
45
+ correlationId,
46
+ // Include previous error details (unmasked) for debugging
47
+ previousError: context.previousError ? {
48
+ type: ((_b = (_a = context.previousError) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) || typeof context.previousError,
49
+ message: (_c = context.previousError) === null || _c === void 0 ? void 0 : _c.message,
50
+ status: ((_d = context.previousError) === null || _d === void 0 ? void 0 : _d.status) || ((_e = context.previousError) === null || _e === void 0 ? void 0 : _e.statusCode),
51
+ code: (_f = context.previousError) === null || _f === void 0 ? void 0 : _f.code,
52
+ // Don't include full stack in retry log (will be in final error log)
53
+ } : undefined,
54
+ });
55
+ // Add retry attempt to span
56
+ (0, tracing_1.addSpanAttributes)({
57
+ [`${context.provider}.retry.attempt`]: context.attempt,
58
+ [`${context.provider}.retry.reason`]: context.reason,
59
+ });
60
+ }
61
+ /**
62
+ * Log a final failure for a 3rd-party API call
63
+ *
64
+ * This should be called when all retries are exhausted or when a non-retryable error occurs.
65
+ * Logs at ERROR level with full unmasked error details.
66
+ *
67
+ * @param context Error context
68
+ */
69
+ function logThirdPartyError(context) {
70
+ var _a, _b, _c;
71
+ const correlationId = (0, correlation_1.getCorrelationId)();
72
+ // Determine log level: ERROR for failures, WARN for rate limits that don't block user
73
+ const isRateLimit = context.statusCode === 429;
74
+ const logLevel = isRateLimit && !context.retryable ? 'warn' : 'error';
75
+ // Build error log entry with unmasked details
76
+ const errorLog = Object.assign({ message: `3rd-party API ${context.operation} failed`, provider: context.provider, operation: context.operation, errorType: context.errorType || ((_b = (_a = context.error) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name) || 'UnknownError', errorMessage: context.errorMessage, statusCode: context.statusCode, retryable: context.retryable, correlationId,
77
+ // Include full error object for stack trace (unmasked)
78
+ err: context.error,
79
+ // Include request ID if available (for upstream debugging)
80
+ requestId: context.requestId,
81
+ // Include rate limit headers if available
82
+ rateLimit: context.rateLimitHeaders }, (context.metadata || {}));
83
+ // Log at appropriate level
84
+ if (logLevel === 'error') {
85
+ (0, logger_1.logger)().error(errorLog);
86
+ }
87
+ else {
88
+ (0, logger_1.logger)().warn(errorLog);
89
+ }
90
+ // Record exception in OpenTelemetry span
91
+ if (context.error instanceof Error) {
92
+ (0, tracing_1.recordSpanException)(context.error);
93
+ }
94
+ else {
95
+ // Create Error object if not already one
96
+ const errorObj = new Error(context.errorMessage);
97
+ if ((_c = context.error) === null || _c === void 0 ? void 0 : _c.stack) {
98
+ errorObj.stack = context.error.stack;
99
+ }
100
+ (0, tracing_1.recordSpanException)(errorObj);
101
+ }
102
+ // Add error attributes to span
103
+ (0, tracing_1.addSpanAttributes)({
104
+ [`${context.provider}.error`]: true,
105
+ [`${context.provider}.error.type`]: context.errorType || 'UnknownError',
106
+ [`${context.provider}.error.status_code`]: context.statusCode || 0,
107
+ [`${context.provider}.error.retryable`]: context.retryable || false,
108
+ });
109
+ }
110
+ /**
111
+ * Log a successful 3rd-party API call
112
+ *
113
+ * @param provider Provider name
114
+ * @param operation Operation name
115
+ * @param durationMs Duration in milliseconds
116
+ * @param metadata Additional metadata (e.g., token usage, request ID)
117
+ */
118
+ function logThirdPartySuccess(provider, operation, durationMs, metadata) {
119
+ const correlationId = (0, correlation_1.getCorrelationId)();
120
+ (0, logger_1.logger)().info(Object.assign({ message: `3rd-party API ${operation} succeeded`, provider,
121
+ operation, duration_ms: durationMs, correlationId }, (metadata || {})));
122
+ // Add success attributes to span
123
+ (0, tracing_1.addSpanAttributes)({
124
+ [`${provider}.success`]: true,
125
+ [`${provider}.duration_ms`]: durationMs,
126
+ });
127
+ }
128
+ /**
129
+ * Extract error context from a caught error
130
+ *
131
+ * Handles various error types:
132
+ * - Axios errors (with response.status, response.data)
133
+ * - OpenAI SDK errors (with status, message)
134
+ * - Anthropic SDK errors (with status, message)
135
+ * - Generic errors
136
+ *
137
+ * @param error Error object
138
+ * @param provider Provider name
139
+ * @param operation Operation name
140
+ * @returns Error context
141
+ */
142
+ function extractErrorContext(error, provider, operation) {
143
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
144
+ // Extract status code from various error formats
145
+ let statusCode;
146
+ let requestId;
147
+ let rateLimitHeaders;
148
+ // Handle Axios errors
149
+ if (error.response) {
150
+ statusCode = error.response.status;
151
+ requestId = ((_a = error.response.headers) === null || _a === void 0 ? void 0 : _a['x-request-id']) || ((_b = error.response.headers) === null || _b === void 0 ? void 0 : _b['request-id']);
152
+ rateLimitHeaders = {
153
+ limit: (_c = error.response.headers) === null || _c === void 0 ? void 0 : _c['x-ratelimit-limit'],
154
+ remaining: (_d = error.response.headers) === null || _d === void 0 ? void 0 : _d['x-ratelimit-remaining'],
155
+ reset: (_e = error.response.headers) === null || _e === void 0 ? void 0 : _e['x-ratelimit-reset'],
156
+ };
157
+ }
158
+ // Handle OpenAI/Anthropic SDK errors
159
+ else if (error.status) {
160
+ statusCode = error.status;
161
+ requestId = error.request_id || error.requestId;
162
+ }
163
+ // Handle HTTP errors with statusCode
164
+ else if (error.statusCode) {
165
+ statusCode = error.statusCode;
166
+ }
167
+ // Determine if error is retryable
168
+ const retryable = isRetryableError(statusCode, error.code);
169
+ // Extract error message
170
+ const errorMessage = error.message ||
171
+ ((_h = (_g = (_f = error.response) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.error) === null || _h === void 0 ? void 0 : _h.message) ||
172
+ ((_k = (_j = error.response) === null || _j === void 0 ? void 0 : _j.data) === null || _k === void 0 ? void 0 : _k.message) ||
173
+ 'Unknown error';
174
+ return {
175
+ provider,
176
+ operation,
177
+ statusCode,
178
+ errorType: ((_l = error.constructor) === null || _l === void 0 ? void 0 : _l.name) || error.type || 'UnknownError',
179
+ errorMessage,
180
+ error,
181
+ retryable,
182
+ requestId,
183
+ rateLimitHeaders,
184
+ metadata: {
185
+ code: error.code,
186
+ type: error.type,
187
+ },
188
+ };
189
+ }
190
+ /**
191
+ * Determine if an error is retryable based on status code and error code
192
+ *
193
+ * @param statusCode HTTP status code
194
+ * @param errorCode Error code (e.g., 'ECONNREFUSED', 'ETIMEDOUT')
195
+ * @returns true if error is retryable
196
+ */
197
+ function isRetryableError(statusCode, errorCode) {
198
+ // Retryable HTTP status codes
199
+ if (statusCode === 429 || statusCode === 500 || statusCode === 502 || statusCode === 503 || statusCode === 504) {
200
+ return true;
201
+ }
202
+ // Retryable network errors
203
+ if (errorCode === 'ECONNREFUSED' || errorCode === 'ETIMEDOUT' || errorCode === 'ENOTFOUND') {
204
+ return true;
205
+ }
206
+ // Non-retryable: authentication, invalid requests, not found
207
+ if (statusCode === 401 || statusCode === 400 || statusCode === 404) {
208
+ return false;
209
+ }
210
+ // Default to retryable for unknown errors (conservative approach)
211
+ return true;
212
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * OpenTelemetry tracing setup with Azure Monitor exporter
3
+ *
4
+ * This module initializes OpenTelemetry SDK with:
5
+ * - Azure Monitor exporter for Application Insights
6
+ * - Auto-instrumentation for HTTP, MongoDB, Redis, Kafka
7
+ * - Correlation ID propagation
8
+ * - Service name and version from environment
9
+ */
10
+ import { NodeSDK } from '@opentelemetry/sdk-node';
11
+ import { Span } from '@opentelemetry/api';
12
+ /**
13
+ * Initialize OpenTelemetry SDK with Azure Monitor exporter
14
+ *
15
+ * Environment variables:
16
+ * - APPLICATIONINSIGHTS_CONNECTION_STRING: Azure Application Insights connection string
17
+ * - SERVICE_NAME: Service name (default: 'unknown-service')
18
+ * - APP_VERSION: Service version (default: 'unknown')
19
+ * - OTEL_SERVICE_NAME: OpenTelemetry service name (falls back to SERVICE_NAME)
20
+ * - OTEL_LOG_LEVEL: OpenTelemetry log level (default: 'info')
21
+ *
22
+ * @returns NodeSDK instance or null if initialization fails
23
+ */
24
+ export declare function initializeTracing(): NodeSDK | null;
25
+ /**
26
+ * Shutdown OpenTelemetry SDK gracefully
27
+ * Call this during service shutdown
28
+ */
29
+ export declare function shutdownTracing(): Promise<void>;
30
+ /**
31
+ * Get the current active span
32
+ * @returns Active span or undefined
33
+ */
34
+ export declare function getActiveSpan(): Span | undefined;
35
+ /**
36
+ * Create a new span for an operation
37
+ * @param name Span name
38
+ * @param fn Function to execute within the span
39
+ * @returns Result of the function
40
+ */
41
+ export declare function withSpan<T>(name: string, fn: (span: Span) => Promise<T> | T): Promise<T>;
42
+ /**
43
+ * Add attributes to the current active span
44
+ * @param attributes Key-value pairs to add
45
+ */
46
+ export declare function addSpanAttributes(attributes: Record<string, string | number | boolean>): void;
47
+ /**
48
+ * Record an exception on the current active span
49
+ * @param error Error to record
50
+ */
51
+ export declare function recordSpanException(error: Error): void;
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * OpenTelemetry tracing setup with Azure Monitor exporter
4
+ *
5
+ * This module initializes OpenTelemetry SDK with:
6
+ * - Azure Monitor exporter for Application Insights
7
+ * - Auto-instrumentation for HTTP, MongoDB, Redis, Kafka
8
+ * - Correlation ID propagation
9
+ * - Service name and version from environment
10
+ */
11
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
+ return new (P || (P = Promise))(function (resolve, reject) {
14
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
15
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
16
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18
+ });
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.initializeTracing = initializeTracing;
22
+ exports.shutdownTracing = shutdownTracing;
23
+ exports.getActiveSpan = getActiveSpan;
24
+ exports.withSpan = withSpan;
25
+ exports.addSpanAttributes = addSpanAttributes;
26
+ exports.recordSpanException = recordSpanException;
27
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
28
+ const auto_instrumentations_node_1 = require("@opentelemetry/auto-instrumentations-node");
29
+ const monitor_opentelemetry_exporter_1 = require("@azure/monitor-opentelemetry-exporter");
30
+ const resources_1 = require("@opentelemetry/resources");
31
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
32
+ const api_1 = require("@opentelemetry/api");
33
+ const correlation_1 = require("./correlation");
34
+ const logger_1 = require("./logger");
35
+ let sdk = null;
36
+ /**
37
+ * Initialize OpenTelemetry SDK with Azure Monitor exporter
38
+ *
39
+ * Environment variables:
40
+ * - APPLICATIONINSIGHTS_CONNECTION_STRING: Azure Application Insights connection string
41
+ * - SERVICE_NAME: Service name (default: 'unknown-service')
42
+ * - APP_VERSION: Service version (default: 'unknown')
43
+ * - OTEL_SERVICE_NAME: OpenTelemetry service name (falls back to SERVICE_NAME)
44
+ * - OTEL_LOG_LEVEL: OpenTelemetry log level (default: 'info')
45
+ *
46
+ * @returns NodeSDK instance or null if initialization fails
47
+ */
48
+ function initializeTracing() {
49
+ // Skip if already initialized
50
+ if (sdk) {
51
+ (0, logger_1.logger)().warn({ message: 'OpenTelemetry SDK already initialized' });
52
+ return sdk;
53
+ }
54
+ const connectionString = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING;
55
+ const serviceName = process.env.OTEL_SERVICE_NAME || process.env.SERVICE_NAME || 'unknown-service';
56
+ const serviceVersion = process.env.APP_VERSION || 'unknown';
57
+ // If no connection string, skip initialization (useful for local dev)
58
+ if (!connectionString) {
59
+ (0, logger_1.logger)().warn({
60
+ message: 'OpenTelemetry initialization skipped: APPLICATIONINSIGHTS_CONNECTION_STRING not set',
61
+ serviceName,
62
+ });
63
+ return null;
64
+ }
65
+ try {
66
+ // Create Azure Monitor trace exporter
67
+ const traceExporter = new monitor_opentelemetry_exporter_1.AzureMonitorTraceExporter({
68
+ connectionString,
69
+ });
70
+ // Create resource with service metadata
71
+ const resource = (0, resources_1.resourceFromAttributes)({
72
+ [semantic_conventions_1.SemanticResourceAttributes.SERVICE_NAME]: serviceName,
73
+ [semantic_conventions_1.SemanticResourceAttributes.SERVICE_VERSION]: serviceVersion,
74
+ [semantic_conventions_1.SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || 'development',
75
+ });
76
+ // Initialize SDK with auto-instrumentation
77
+ sdk = new sdk_node_1.NodeSDK({
78
+ resource,
79
+ traceExporter,
80
+ instrumentations: [
81
+ (0, auto_instrumentations_node_1.getNodeAutoInstrumentations)({
82
+ // Enable HTTP instrumentation (Express, etc.)
83
+ '@opentelemetry/instrumentation-http': {
84
+ enabled: true,
85
+ },
86
+ // Enable MongoDB instrumentation
87
+ '@opentelemetry/instrumentation-mongodb': {
88
+ enabled: true,
89
+ },
90
+ // Enable Redis instrumentation (if using ioredis or node-redis)
91
+ '@opentelemetry/instrumentation-redis': {
92
+ enabled: true,
93
+ },
94
+ // Enable Kafka instrumentation
95
+ '@opentelemetry/instrumentation-kafkajs': {
96
+ enabled: true,
97
+ },
98
+ // Disable fs instrumentation (too noisy)
99
+ '@opentelemetry/instrumentation-fs': {
100
+ enabled: false,
101
+ },
102
+ }),
103
+ ],
104
+ });
105
+ // Start the SDK
106
+ sdk.start();
107
+ (0, logger_1.logger)().info({
108
+ message: 'OpenTelemetry SDK initialized successfully',
109
+ serviceName,
110
+ serviceVersion,
111
+ exporter: 'AzureMonitor',
112
+ });
113
+ return sdk;
114
+ }
115
+ catch (error) {
116
+ (0, logger_1.logger)().error({
117
+ err: error,
118
+ message: 'Failed to initialize OpenTelemetry SDK',
119
+ serviceName,
120
+ });
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Shutdown OpenTelemetry SDK gracefully
126
+ * Call this during service shutdown
127
+ */
128
+ function shutdownTracing() {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ if (sdk) {
131
+ try {
132
+ yield sdk.shutdown();
133
+ (0, logger_1.logger)().info({ message: 'OpenTelemetry SDK shut down successfully' });
134
+ sdk = null;
135
+ }
136
+ catch (error) {
137
+ (0, logger_1.logger)().error({
138
+ err: error,
139
+ message: 'Error shutting down OpenTelemetry SDK',
140
+ });
141
+ }
142
+ }
143
+ });
144
+ }
145
+ /**
146
+ * Get the current active span
147
+ * @returns Active span or undefined
148
+ */
149
+ function getActiveSpan() {
150
+ return api_1.trace.getActiveSpan();
151
+ }
152
+ /**
153
+ * Create a new span for an operation
154
+ * @param name Span name
155
+ * @param fn Function to execute within the span
156
+ * @returns Result of the function
157
+ */
158
+ function withSpan(name, fn) {
159
+ return __awaiter(this, void 0, void 0, function* () {
160
+ const tracer = api_1.trace.getTracer(process.env.SERVICE_NAME || 'unknown-service', process.env.APP_VERSION || 'unknown');
161
+ return tracer.startActiveSpan(name, (span) => __awaiter(this, void 0, void 0, function* () {
162
+ try {
163
+ // Add correlation ID to span attributes
164
+ const correlationId = (0, correlation_1.getCorrelationId)();
165
+ if (correlationId) {
166
+ span.setAttribute('correlation.id', correlationId);
167
+ }
168
+ const result = yield fn(span);
169
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
170
+ return result;
171
+ }
172
+ catch (error) {
173
+ span.setStatus({
174
+ code: api_1.SpanStatusCode.ERROR,
175
+ message: error.message,
176
+ });
177
+ span.recordException(error);
178
+ throw error;
179
+ }
180
+ finally {
181
+ span.end();
182
+ }
183
+ }));
184
+ });
185
+ }
186
+ /**
187
+ * Add attributes to the current active span
188
+ * @param attributes Key-value pairs to add
189
+ */
190
+ function addSpanAttributes(attributes) {
191
+ const span = getActiveSpan();
192
+ if (span) {
193
+ Object.entries(attributes).forEach(([key, value]) => {
194
+ span.setAttribute(key, value);
195
+ });
196
+ }
197
+ }
198
+ /**
199
+ * Record an exception on the current active span
200
+ * @param error Error to record
201
+ */
202
+ function recordSpanException(error) {
203
+ const span = getActiveSpan();
204
+ if (span) {
205
+ span.recordException(error);
206
+ span.setStatus({
207
+ code: api_1.SpanStatusCode.ERROR,
208
+ message: error.message,
209
+ });
210
+ }
211
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aichatwar/shared",
3
- "version": "1.0.146",
3
+ "version": "1.0.148",
4
4
  "main": "./build/index.js",
5
5
  "typs": "./build/index.d.ts",
6
6
  "files": [
@@ -28,6 +28,12 @@
28
28
  "express-validator": "^7.2.1"
29
29
  },
30
30
  "dependencies": {
31
+ "@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.32",
32
+ "@opentelemetry/api": "^1.8.0",
33
+ "@opentelemetry/auto-instrumentations-node": "^0.69.0",
34
+ "@opentelemetry/resources": "^2.5.1",
35
+ "@opentelemetry/semantic-conventions": "^1.24.0",
36
+ "@opentelemetry/sdk-node": "^0.212.0",
31
37
  "jsonwebtoken": "^9.0.2",
32
38
  "kafkajs": "^2.2.4",
33
39
  "mongoose-update-if-current": "^1.4.0",