@noony-serverless/core 0.3.4 → 0.4.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 +199 -0
- package/build/core/containerPool.d.ts +129 -26
- package/build/core/containerPool.js +213 -68
- package/build/core/handler.d.ts +2 -2
- package/build/core/handler.js +6 -12
- package/build/core/index.d.ts +1 -0
- package/build/core/index.js +1 -0
- package/build/core/logger.d.ts +89 -1
- package/build/core/logger.js +136 -5
- package/build/core/telemetry/config.d.ts +331 -0
- package/build/core/telemetry/config.js +153 -0
- package/build/core/telemetry/index.d.ts +22 -0
- package/build/core/telemetry/index.js +45 -0
- package/build/core/telemetry/provider.d.ts +203 -0
- package/build/core/telemetry/provider.js +3 -0
- package/build/core/telemetry/providers/console-provider.d.ts +54 -0
- package/build/core/telemetry/providers/console-provider.js +124 -0
- package/build/core/telemetry/providers/index.d.ts +10 -0
- package/build/core/telemetry/providers/index.js +19 -0
- package/build/core/telemetry/providers/noop-provider.d.ts +51 -0
- package/build/core/telemetry/providers/noop-provider.js +67 -0
- package/build/core/telemetry/providers/opentelemetry-provider.d.ts +102 -0
- package/build/core/telemetry/providers/opentelemetry-provider.js +342 -0
- package/build/middlewares/bodyValidationMiddleware.js +1 -1
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +16 -8
- package/build/middlewares/dependencyInjectionMiddleware.js +31 -11
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +1 -1
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
- package/build/middlewares/guards/guards/FastAuthGuard.js +3 -2
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +7 -9
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
- package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/services/FastUserContextService.d.ts +11 -32
- package/build/middlewares/index.d.ts +1 -0
- package/build/middlewares/index.js +1 -0
- package/build/middlewares/openTelemetryMiddleware.d.ts +162 -0
- package/build/middlewares/openTelemetryMiddleware.js +359 -0
- package/build/middlewares/rateLimitingMiddleware.js +16 -5
- package/build/utils/container.utils.js +4 -1
- package/build/utils/fastify-wrapper.d.ts +74 -0
- package/build/utils/fastify-wrapper.js +175 -0
- package/build/utils/index.d.ts +4 -0
- package/build/utils/index.js +23 -1
- package/build/utils/otel.helper.d.ts +122 -0
- package/build/utils/otel.helper.js +258 -0
- package/build/utils/pubsub-trace.utils.d.ts +102 -0
- package/build/utils/pubsub-trace.utils.js +155 -0
- package/build/utils/wrapper-utils.d.ts +177 -0
- package/build/utils/wrapper-utils.js +236 -0
- package/package.json +61 -2
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
import { Handler } from '../core/handler';
|
|
3
|
+
/**
|
|
4
|
+
* Create a Fastify route handler wrapper for a Noony handler
|
|
5
|
+
*
|
|
6
|
+
* Wraps a Noony handler into a Fastify route handler for use with Fastify server.
|
|
7
|
+
* This pattern enables running Noony handlers with Fastify's high-performance HTTP framework.
|
|
8
|
+
*
|
|
9
|
+
* @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
|
|
10
|
+
* @param functionName - Name for error logging purposes
|
|
11
|
+
* @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
|
|
12
|
+
* Uses singleton pattern to prevent re-initialization across requests
|
|
13
|
+
* @returns Fastify route handler: `(req: FastifyRequest, reply: FastifyReply) => Promise<void>`
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This wrapper ensures:
|
|
17
|
+
* - Dependencies are initialized before handler execution (singleton pattern for efficiency)
|
|
18
|
+
* - Noony handlers work seamlessly with Fastify routing
|
|
19
|
+
* - Errors are caught and returned as proper HTTP responses
|
|
20
|
+
* - Response is not sent twice (`reply.sent` check)
|
|
21
|
+
* - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
|
|
22
|
+
* - Real errors return 500 with generic message for security
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* Creating Fastify app with multiple routes:
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import Fastify from 'fastify';
|
|
28
|
+
* import { createFastifyHandler } from '@noony-serverless/core';
|
|
29
|
+
* import { loginHandler, getConfigHandler } from './handlers';
|
|
30
|
+
*
|
|
31
|
+
* // Initialize dependencies once per app startup
|
|
32
|
+
* let initialized = false;
|
|
33
|
+
* async function initializeDependencies(): Promise<void> {
|
|
34
|
+
* if (initialized) return;
|
|
35
|
+
* const db = await databaseService.connect();
|
|
36
|
+
* await initializeServices(db);
|
|
37
|
+
* initialized = true;
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* const server = Fastify({ logger: true });
|
|
41
|
+
*
|
|
42
|
+
* // Helper shorthand
|
|
43
|
+
* const adapt = (handler, name) => createFastifyHandler(handler, name, initializeDependencies);
|
|
44
|
+
*
|
|
45
|
+
* // Auth routes
|
|
46
|
+
* server.post('/api/auth/login', adapt(loginHandler, 'login'));
|
|
47
|
+
*
|
|
48
|
+
* // Config routes
|
|
49
|
+
* server.get('/api/config', adapt(getConfigHandler, 'getConfig'));
|
|
50
|
+
*
|
|
51
|
+
* // Start server
|
|
52
|
+
* server.listen({ port: 3000 }, (err) => {
|
|
53
|
+
* if (err) throw err;
|
|
54
|
+
* console.log('Server running on port 3000');
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* Fastify routing with path parameters:
|
|
60
|
+
* ```typescript
|
|
61
|
+
* const server = Fastify();
|
|
62
|
+
*
|
|
63
|
+
* // Routes with path parameters work seamlessly
|
|
64
|
+
* server.get('/api/users/:userId', adapt(getUserHandler, 'getUser'));
|
|
65
|
+
* server.patch('/api/config/sections/:sectionId', adapt(updateSectionHandler, 'updateSection'));
|
|
66
|
+
*
|
|
67
|
+
* // Path parameters available in Noony handler via context.req.params
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
|
|
71
|
+
* @see {@link wrapNoonyHandler} for Express integration
|
|
72
|
+
*/
|
|
73
|
+
export declare function createFastifyHandler(noonyHandler: Handler<unknown>, functionName: string, initializeDependencies: () => Promise<void>): (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
74
|
+
//# sourceMappingURL=fastify-wrapper.d.ts.map
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFastifyHandler = createFastifyHandler;
|
|
4
|
+
const logger_1 = require("../core/logger");
|
|
5
|
+
/**
|
|
6
|
+
* Adapt Fastify Request to GenericRequest for Noony handlers
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
function adaptFastifyRequest(req) {
|
|
11
|
+
return {
|
|
12
|
+
method: req.method,
|
|
13
|
+
url: req.url,
|
|
14
|
+
path: req.routeOptions?.url || req.url,
|
|
15
|
+
headers: req.headers,
|
|
16
|
+
query: (req.query || {}),
|
|
17
|
+
params: (req.params || {}),
|
|
18
|
+
body: req.body,
|
|
19
|
+
// Fastify already parses the body, so set parsedBody for BodyValidationMiddleware
|
|
20
|
+
parsedBody: req.body,
|
|
21
|
+
ip: req.ip,
|
|
22
|
+
userAgent: req.headers['user-agent'],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Adapt Fastify Reply to GenericResponse for Noony handlers
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
function adaptFastifyResponse(reply) {
|
|
31
|
+
let statusCode = 200;
|
|
32
|
+
let headersSent = false;
|
|
33
|
+
const response = {
|
|
34
|
+
status(code) {
|
|
35
|
+
statusCode = code;
|
|
36
|
+
reply.code(code);
|
|
37
|
+
return response;
|
|
38
|
+
},
|
|
39
|
+
json(data) {
|
|
40
|
+
headersSent = true;
|
|
41
|
+
reply.send(data);
|
|
42
|
+
return response;
|
|
43
|
+
},
|
|
44
|
+
send(data) {
|
|
45
|
+
headersSent = true;
|
|
46
|
+
reply.send(data);
|
|
47
|
+
return response;
|
|
48
|
+
},
|
|
49
|
+
header(name, value) {
|
|
50
|
+
reply.header(name, value);
|
|
51
|
+
return response;
|
|
52
|
+
},
|
|
53
|
+
headers(headers) {
|
|
54
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
55
|
+
reply.header(key, value);
|
|
56
|
+
});
|
|
57
|
+
return response;
|
|
58
|
+
},
|
|
59
|
+
end() {
|
|
60
|
+
headersSent = true;
|
|
61
|
+
reply.send();
|
|
62
|
+
},
|
|
63
|
+
get statusCode() {
|
|
64
|
+
return statusCode;
|
|
65
|
+
},
|
|
66
|
+
get headersSent() {
|
|
67
|
+
return headersSent || reply.sent;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return response;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a Fastify route handler wrapper for a Noony handler
|
|
74
|
+
*
|
|
75
|
+
* Wraps a Noony handler into a Fastify route handler for use with Fastify server.
|
|
76
|
+
* This pattern enables running Noony handlers with Fastify's high-performance HTTP framework.
|
|
77
|
+
*
|
|
78
|
+
* @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
|
|
79
|
+
* @param functionName - Name for error logging purposes
|
|
80
|
+
* @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
|
|
81
|
+
* Uses singleton pattern to prevent re-initialization across requests
|
|
82
|
+
* @returns Fastify route handler: `(req: FastifyRequest, reply: FastifyReply) => Promise<void>`
|
|
83
|
+
*
|
|
84
|
+
* @remarks
|
|
85
|
+
* This wrapper ensures:
|
|
86
|
+
* - Dependencies are initialized before handler execution (singleton pattern for efficiency)
|
|
87
|
+
* - Noony handlers work seamlessly with Fastify routing
|
|
88
|
+
* - Errors are caught and returned as proper HTTP responses
|
|
89
|
+
* - Response is not sent twice (`reply.sent` check)
|
|
90
|
+
* - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
|
|
91
|
+
* - Real errors return 500 with generic message for security
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* Creating Fastify app with multiple routes:
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import Fastify from 'fastify';
|
|
97
|
+
* import { createFastifyHandler } from '@noony-serverless/core';
|
|
98
|
+
* import { loginHandler, getConfigHandler } from './handlers';
|
|
99
|
+
*
|
|
100
|
+
* // Initialize dependencies once per app startup
|
|
101
|
+
* let initialized = false;
|
|
102
|
+
* async function initializeDependencies(): Promise<void> {
|
|
103
|
+
* if (initialized) return;
|
|
104
|
+
* const db = await databaseService.connect();
|
|
105
|
+
* await initializeServices(db);
|
|
106
|
+
* initialized = true;
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* const server = Fastify({ logger: true });
|
|
110
|
+
*
|
|
111
|
+
* // Helper shorthand
|
|
112
|
+
* const adapt = (handler, name) => createFastifyHandler(handler, name, initializeDependencies);
|
|
113
|
+
*
|
|
114
|
+
* // Auth routes
|
|
115
|
+
* server.post('/api/auth/login', adapt(loginHandler, 'login'));
|
|
116
|
+
*
|
|
117
|
+
* // Config routes
|
|
118
|
+
* server.get('/api/config', adapt(getConfigHandler, 'getConfig'));
|
|
119
|
+
*
|
|
120
|
+
* // Start server
|
|
121
|
+
* server.listen({ port: 3000 }, (err) => {
|
|
122
|
+
* if (err) throw err;
|
|
123
|
+
* console.log('Server running on port 3000');
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* Fastify routing with path parameters:
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const server = Fastify();
|
|
131
|
+
*
|
|
132
|
+
* // Routes with path parameters work seamlessly
|
|
133
|
+
* server.get('/api/users/:userId', adapt(getUserHandler, 'getUser'));
|
|
134
|
+
* server.patch('/api/config/sections/:sectionId', adapt(updateSectionHandler, 'updateSection'));
|
|
135
|
+
*
|
|
136
|
+
* // Path parameters available in Noony handler via context.req.params
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
|
|
140
|
+
* @see {@link wrapNoonyHandler} for Express integration
|
|
141
|
+
*/
|
|
142
|
+
function createFastifyHandler(noonyHandler, functionName, initializeDependencies) {
|
|
143
|
+
return async (req, reply) => {
|
|
144
|
+
try {
|
|
145
|
+
// Ensure dependencies are initialized
|
|
146
|
+
await initializeDependencies();
|
|
147
|
+
// Adapt Fastify req/reply to GenericRequest/GenericResponse
|
|
148
|
+
const genericReq = adaptFastifyRequest(req);
|
|
149
|
+
const genericRes = adaptFastifyResponse(reply);
|
|
150
|
+
// Execute Noony handler with adapted request/response
|
|
151
|
+
await noonyHandler.executeGeneric(genericReq, genericRes);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
// Ignore RESPONSE_SENT markers (response already sent by middleware)
|
|
155
|
+
if (error instanceof Error && error.message === 'RESPONSE_SENT') {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
logger_1.logger.error(`${functionName} handler error`, {
|
|
159
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
160
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
161
|
+
});
|
|
162
|
+
// Graceful error handling - only send if response not already sent
|
|
163
|
+
if (!reply.sent) {
|
|
164
|
+
reply.code(500).send({
|
|
165
|
+
success: false,
|
|
166
|
+
error: {
|
|
167
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
168
|
+
message: 'An unexpected error occurred',
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=fastify-wrapper.js.map
|
package/build/utils/index.d.ts
CHANGED
|
@@ -3,4 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { getService } from './container.utils';
|
|
5
5
|
export { asString, asStringArray, asNumber, asBoolean, } from './query-param.utils';
|
|
6
|
+
export { isPubSubMessage, extractTraceContext, injectTraceContext, createParentContext, type PubSubMessage, type TraceContext, } from './pubsub-trace.utils';
|
|
7
|
+
export { createOTELMixin, getOTELContext, getOTELContextFromSpan, getOTELContextFromContext, formatTraceIdForCloudLogging, createCloudLoggingEntry, isOTELActive, isOTELInstalled, type OTELLogContext, } from './otel.helper';
|
|
8
|
+
export { createHttpFunction, wrapNoonyHandler } from './wrapper-utils';
|
|
9
|
+
export { createFastifyHandler } from './fastify-wrapper';
|
|
6
10
|
//# sourceMappingURL=index.d.ts.map
|
package/build/utils/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Utility functions for Noony Core
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.asBoolean = exports.asNumber = exports.asStringArray = exports.asString = exports.getService = void 0;
|
|
6
|
+
exports.createFastifyHandler = exports.wrapNoonyHandler = exports.createHttpFunction = exports.isOTELInstalled = exports.isOTELActive = exports.createCloudLoggingEntry = exports.formatTraceIdForCloudLogging = exports.getOTELContextFromContext = exports.getOTELContextFromSpan = exports.getOTELContext = exports.createOTELMixin = exports.createParentContext = exports.injectTraceContext = exports.extractTraceContext = exports.isPubSubMessage = exports.asBoolean = exports.asNumber = exports.asStringArray = exports.asString = exports.getService = void 0;
|
|
7
7
|
// Container utilities
|
|
8
8
|
var container_utils_1 = require("./container.utils");
|
|
9
9
|
Object.defineProperty(exports, "getService", { enumerable: true, get: function () { return container_utils_1.getService; } });
|
|
@@ -13,4 +13,26 @@ Object.defineProperty(exports, "asString", { enumerable: true, get: function ()
|
|
|
13
13
|
Object.defineProperty(exports, "asStringArray", { enumerable: true, get: function () { return query_param_utils_1.asStringArray; } });
|
|
14
14
|
Object.defineProperty(exports, "asNumber", { enumerable: true, get: function () { return query_param_utils_1.asNumber; } });
|
|
15
15
|
Object.defineProperty(exports, "asBoolean", { enumerable: true, get: function () { return query_param_utils_1.asBoolean; } });
|
|
16
|
+
// Pub/Sub trace propagation utilities
|
|
17
|
+
var pubsub_trace_utils_1 = require("./pubsub-trace.utils");
|
|
18
|
+
Object.defineProperty(exports, "isPubSubMessage", { enumerable: true, get: function () { return pubsub_trace_utils_1.isPubSubMessage; } });
|
|
19
|
+
Object.defineProperty(exports, "extractTraceContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.extractTraceContext; } });
|
|
20
|
+
Object.defineProperty(exports, "injectTraceContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.injectTraceContext; } });
|
|
21
|
+
Object.defineProperty(exports, "createParentContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.createParentContext; } });
|
|
22
|
+
// OpenTelemetry logger integration utilities
|
|
23
|
+
var otel_helper_1 = require("./otel.helper");
|
|
24
|
+
Object.defineProperty(exports, "createOTELMixin", { enumerable: true, get: function () { return otel_helper_1.createOTELMixin; } });
|
|
25
|
+
Object.defineProperty(exports, "getOTELContext", { enumerable: true, get: function () { return otel_helper_1.getOTELContext; } });
|
|
26
|
+
Object.defineProperty(exports, "getOTELContextFromSpan", { enumerable: true, get: function () { return otel_helper_1.getOTELContextFromSpan; } });
|
|
27
|
+
Object.defineProperty(exports, "getOTELContextFromContext", { enumerable: true, get: function () { return otel_helper_1.getOTELContextFromContext; } });
|
|
28
|
+
Object.defineProperty(exports, "formatTraceIdForCloudLogging", { enumerable: true, get: function () { return otel_helper_1.formatTraceIdForCloudLogging; } });
|
|
29
|
+
Object.defineProperty(exports, "createCloudLoggingEntry", { enumerable: true, get: function () { return otel_helper_1.createCloudLoggingEntry; } });
|
|
30
|
+
Object.defineProperty(exports, "isOTELActive", { enumerable: true, get: function () { return otel_helper_1.isOTELActive; } });
|
|
31
|
+
Object.defineProperty(exports, "isOTELInstalled", { enumerable: true, get: function () { return otel_helper_1.isOTELInstalled; } });
|
|
32
|
+
// Wrapper utilities for GCP Functions, Express, and Fastify
|
|
33
|
+
var wrapper_utils_1 = require("./wrapper-utils");
|
|
34
|
+
Object.defineProperty(exports, "createHttpFunction", { enumerable: true, get: function () { return wrapper_utils_1.createHttpFunction; } });
|
|
35
|
+
Object.defineProperty(exports, "wrapNoonyHandler", { enumerable: true, get: function () { return wrapper_utils_1.wrapNoonyHandler; } });
|
|
36
|
+
var fastify_wrapper_1 = require("./fastify-wrapper");
|
|
37
|
+
Object.defineProperty(exports, "createFastifyHandler", { enumerable: true, get: function () { return fastify_wrapper_1.createFastifyHandler; } });
|
|
16
38
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry Helper Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides helper functions for integrating OpenTelemetry with logging systems.
|
|
5
|
+
* These utilities enable automatic trace/span ID injection into log entries for
|
|
6
|
+
* correlation with distributed traces in Cloud Logging and other observability platforms.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/otel.helper
|
|
9
|
+
*/
|
|
10
|
+
import type { Context as OtelContext, Span } from '@opentelemetry/api';
|
|
11
|
+
/**
|
|
12
|
+
* OTEL context object for logger integration
|
|
13
|
+
*/
|
|
14
|
+
export interface OTELLogContext {
|
|
15
|
+
traceId?: string;
|
|
16
|
+
spanId?: string;
|
|
17
|
+
traceFlags?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create Pino mixin for automatic trace/span ID injection
|
|
21
|
+
*
|
|
22
|
+
* This function creates a Pino mixin that automatically adds OpenTelemetry
|
|
23
|
+
* trace and span IDs to every log entry. This enables log-trace correlation
|
|
24
|
+
* in Cloud Logging and other observability platforms.
|
|
25
|
+
*
|
|
26
|
+
* Usage with Pino:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import pino from 'pino';
|
|
29
|
+
* import { createOTELMixin } from '@noony-serverless/core';
|
|
30
|
+
*
|
|
31
|
+
* const logger = pino({
|
|
32
|
+
* mixin: createOTELMixin,
|
|
33
|
+
* // ... other config
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* logger.info('User created'); // Automatically includes traceId, spanId, traceFlags
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* Log output example:
|
|
40
|
+
* ```json
|
|
41
|
+
* {
|
|
42
|
+
* "level": 30,
|
|
43
|
+
* "time": 1640000000000,
|
|
44
|
+
* "msg": "User created",
|
|
45
|
+
* "traceId": "13ea7e3c2d3b4547baaa399062df1f2d",
|
|
46
|
+
* "spanId": "1234567890123456",
|
|
47
|
+
* "traceFlags": 1
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @returns Mixin object with trace context or empty object if no active span
|
|
52
|
+
*/
|
|
53
|
+
export declare const createOTELMixin: () => OTELLogContext;
|
|
54
|
+
/**
|
|
55
|
+
* Extract OTEL context from active span
|
|
56
|
+
*
|
|
57
|
+
* Similar to createOTELMixin but returns undefined if no span is active,
|
|
58
|
+
* making it easier to conditionally add trace context.
|
|
59
|
+
*
|
|
60
|
+
* @returns OTEL context or undefined if no active span
|
|
61
|
+
*/
|
|
62
|
+
export declare const getOTELContext: () => OTELLogContext | undefined;
|
|
63
|
+
/**
|
|
64
|
+
* Extract OTEL context from a specific span
|
|
65
|
+
*
|
|
66
|
+
* Useful when you have a reference to a span and want to extract its context
|
|
67
|
+
* for logging or propagation purposes.
|
|
68
|
+
*
|
|
69
|
+
* @param span - The OpenTelemetry span to extract context from
|
|
70
|
+
* @returns OTEL context from the span
|
|
71
|
+
*/
|
|
72
|
+
export declare const getOTELContextFromSpan: (span: Span) => OTELLogContext;
|
|
73
|
+
/**
|
|
74
|
+
* Extract OTEL context from an OTEL Context object
|
|
75
|
+
*
|
|
76
|
+
* Useful when working with OTEL Context propagation (e.g., in Pub/Sub messages)
|
|
77
|
+
* and you need to extract the span context for logging.
|
|
78
|
+
*
|
|
79
|
+
* @param context - The OpenTelemetry context to extract from
|
|
80
|
+
* @returns OTEL context from the context or undefined if no span
|
|
81
|
+
*/
|
|
82
|
+
export declare const getOTELContextFromContext: (context: OtelContext) => OTELLogContext | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Format trace ID for Cloud Logging
|
|
85
|
+
*
|
|
86
|
+
* Cloud Logging expects trace IDs in a specific format:
|
|
87
|
+
* projects/[PROJECT_ID]/traces/[TRACE_ID]
|
|
88
|
+
*
|
|
89
|
+
* This function formats a raw trace ID into the Cloud Logging format.
|
|
90
|
+
*
|
|
91
|
+
* @param traceId - Raw trace ID (32-character hex string)
|
|
92
|
+
* @param projectId - GCP project ID (optional, defaults to GOOGLE_CLOUD_PROJECT env var)
|
|
93
|
+
* @returns Formatted trace ID for Cloud Logging or undefined if inputs invalid
|
|
94
|
+
*/
|
|
95
|
+
export declare const formatTraceIdForCloudLogging: (traceId?: string, projectId?: string) => string | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Create Cloud Logging compatible log entry
|
|
98
|
+
*
|
|
99
|
+
* Combines OTEL context with log metadata to create a Cloud Logging compatible
|
|
100
|
+
* log entry with trace correlation.
|
|
101
|
+
*
|
|
102
|
+
* @param message - Log message
|
|
103
|
+
* @param metadata - Additional log metadata
|
|
104
|
+
* @param projectId - GCP project ID (optional)
|
|
105
|
+
* @returns Cloud Logging compatible log entry
|
|
106
|
+
*/
|
|
107
|
+
export declare const createCloudLoggingEntry: (message: string, metadata?: Record<string, any>, projectId?: string) => Record<string, any>;
|
|
108
|
+
/**
|
|
109
|
+
* Check if OpenTelemetry is available and active
|
|
110
|
+
*
|
|
111
|
+
* Useful for conditional OTEL feature usage in libraries and applications.
|
|
112
|
+
*
|
|
113
|
+
* @returns true if OTEL is available and there's an active span
|
|
114
|
+
*/
|
|
115
|
+
export declare const isOTELActive: () => boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Check if OpenTelemetry SDK is installed
|
|
118
|
+
*
|
|
119
|
+
* @returns true if @opentelemetry/api is installed
|
|
120
|
+
*/
|
|
121
|
+
export declare const isOTELInstalled: () => boolean;
|
|
122
|
+
//# sourceMappingURL=otel.helper.d.ts.map
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenTelemetry Helper Utilities
|
|
4
|
+
*
|
|
5
|
+
* Provides helper functions for integrating OpenTelemetry with logging systems.
|
|
6
|
+
* These utilities enable automatic trace/span ID injection into log entries for
|
|
7
|
+
* correlation with distributed traces in Cloud Logging and other observability platforms.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/otel.helper
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isOTELInstalled = exports.isOTELActive = exports.createCloudLoggingEntry = exports.formatTraceIdForCloudLogging = exports.getOTELContextFromContext = exports.getOTELContextFromSpan = exports.getOTELContext = exports.createOTELMixin = void 0;
|
|
13
|
+
// Conditional OpenTelemetry import (optional dependency)
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
let trace;
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
18
|
+
const otelApi = require('@opentelemetry/api');
|
|
19
|
+
trace = otelApi.trace;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// OpenTelemetry not installed - helpers will return empty objects
|
|
23
|
+
trace = null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create Pino mixin for automatic trace/span ID injection
|
|
27
|
+
*
|
|
28
|
+
* This function creates a Pino mixin that automatically adds OpenTelemetry
|
|
29
|
+
* trace and span IDs to every log entry. This enables log-trace correlation
|
|
30
|
+
* in Cloud Logging and other observability platforms.
|
|
31
|
+
*
|
|
32
|
+
* Usage with Pino:
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import pino from 'pino';
|
|
35
|
+
* import { createOTELMixin } from '@noony-serverless/core';
|
|
36
|
+
*
|
|
37
|
+
* const logger = pino({
|
|
38
|
+
* mixin: createOTELMixin,
|
|
39
|
+
* // ... other config
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* logger.info('User created'); // Automatically includes traceId, spanId, traceFlags
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* Log output example:
|
|
46
|
+
* ```json
|
|
47
|
+
* {
|
|
48
|
+
* "level": 30,
|
|
49
|
+
* "time": 1640000000000,
|
|
50
|
+
* "msg": "User created",
|
|
51
|
+
* "traceId": "13ea7e3c2d3b4547baaa399062df1f2d",
|
|
52
|
+
* "spanId": "1234567890123456",
|
|
53
|
+
* "traceFlags": 1
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @returns Mixin object with trace context or empty object if no active span
|
|
58
|
+
*/
|
|
59
|
+
const createOTELMixin = () => {
|
|
60
|
+
if (!trace) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const span = trace.getActiveSpan();
|
|
65
|
+
if (!span) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
const spanContext = span.spanContext();
|
|
69
|
+
return {
|
|
70
|
+
traceId: spanContext.traceId,
|
|
71
|
+
spanId: spanContext.spanId,
|
|
72
|
+
traceFlags: spanContext.traceFlags,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Gracefully handle any errors in trace extraction
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
exports.createOTELMixin = createOTELMixin;
|
|
81
|
+
/**
|
|
82
|
+
* Extract OTEL context from active span
|
|
83
|
+
*
|
|
84
|
+
* Similar to createOTELMixin but returns undefined if no span is active,
|
|
85
|
+
* making it easier to conditionally add trace context.
|
|
86
|
+
*
|
|
87
|
+
* @returns OTEL context or undefined if no active span
|
|
88
|
+
*/
|
|
89
|
+
const getOTELContext = () => {
|
|
90
|
+
if (!trace) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const span = trace.getActiveSpan();
|
|
95
|
+
if (!span) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const spanContext = span.spanContext();
|
|
99
|
+
return {
|
|
100
|
+
traceId: spanContext.traceId,
|
|
101
|
+
spanId: spanContext.spanId,
|
|
102
|
+
traceFlags: spanContext.traceFlags,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
exports.getOTELContext = getOTELContext;
|
|
110
|
+
/**
|
|
111
|
+
* Extract OTEL context from a specific span
|
|
112
|
+
*
|
|
113
|
+
* Useful when you have a reference to a span and want to extract its context
|
|
114
|
+
* for logging or propagation purposes.
|
|
115
|
+
*
|
|
116
|
+
* @param span - The OpenTelemetry span to extract context from
|
|
117
|
+
* @returns OTEL context from the span
|
|
118
|
+
*/
|
|
119
|
+
const getOTELContextFromSpan = (span) => {
|
|
120
|
+
try {
|
|
121
|
+
const spanContext = span.spanContext();
|
|
122
|
+
return {
|
|
123
|
+
traceId: spanContext.traceId,
|
|
124
|
+
spanId: spanContext.spanId,
|
|
125
|
+
traceFlags: spanContext.traceFlags,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
exports.getOTELContextFromSpan = getOTELContextFromSpan;
|
|
133
|
+
/**
|
|
134
|
+
* Extract OTEL context from an OTEL Context object
|
|
135
|
+
*
|
|
136
|
+
* Useful when working with OTEL Context propagation (e.g., in Pub/Sub messages)
|
|
137
|
+
* and you need to extract the span context for logging.
|
|
138
|
+
*
|
|
139
|
+
* @param context - The OpenTelemetry context to extract from
|
|
140
|
+
* @returns OTEL context from the context or undefined if no span
|
|
141
|
+
*/
|
|
142
|
+
const getOTELContextFromContext = (context) => {
|
|
143
|
+
if (!trace) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const span = trace.getSpan(context);
|
|
148
|
+
if (!span) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const spanContext = span.spanContext();
|
|
152
|
+
return {
|
|
153
|
+
traceId: spanContext.traceId,
|
|
154
|
+
spanId: spanContext.spanId,
|
|
155
|
+
traceFlags: spanContext.traceFlags,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
exports.getOTELContextFromContext = getOTELContextFromContext;
|
|
163
|
+
/**
|
|
164
|
+
* Format trace ID for Cloud Logging
|
|
165
|
+
*
|
|
166
|
+
* Cloud Logging expects trace IDs in a specific format:
|
|
167
|
+
* projects/[PROJECT_ID]/traces/[TRACE_ID]
|
|
168
|
+
*
|
|
169
|
+
* This function formats a raw trace ID into the Cloud Logging format.
|
|
170
|
+
*
|
|
171
|
+
* @param traceId - Raw trace ID (32-character hex string)
|
|
172
|
+
* @param projectId - GCP project ID (optional, defaults to GOOGLE_CLOUD_PROJECT env var)
|
|
173
|
+
* @returns Formatted trace ID for Cloud Logging or undefined if inputs invalid
|
|
174
|
+
*/
|
|
175
|
+
const formatTraceIdForCloudLogging = (traceId, projectId) => {
|
|
176
|
+
if (!traceId) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const project = projectId || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT;
|
|
180
|
+
if (!project) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
return `projects/${project}/traces/${traceId}`;
|
|
184
|
+
};
|
|
185
|
+
exports.formatTraceIdForCloudLogging = formatTraceIdForCloudLogging;
|
|
186
|
+
/**
|
|
187
|
+
* Create Cloud Logging compatible log entry
|
|
188
|
+
*
|
|
189
|
+
* Combines OTEL context with log metadata to create a Cloud Logging compatible
|
|
190
|
+
* log entry with trace correlation.
|
|
191
|
+
*
|
|
192
|
+
* @param message - Log message
|
|
193
|
+
* @param metadata - Additional log metadata
|
|
194
|
+
* @param projectId - GCP project ID (optional)
|
|
195
|
+
* @returns Cloud Logging compatible log entry
|
|
196
|
+
*/
|
|
197
|
+
const createCloudLoggingEntry = (message,
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
|
+
metadata = {}, projectId
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
201
|
+
) => {
|
|
202
|
+
const otelContext = (0, exports.getOTELContext)();
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
const entry = {
|
|
205
|
+
message,
|
|
206
|
+
...metadata,
|
|
207
|
+
};
|
|
208
|
+
if (otelContext?.traceId) {
|
|
209
|
+
entry.traceId = otelContext.traceId;
|
|
210
|
+
entry.spanId = otelContext.spanId;
|
|
211
|
+
// Add Cloud Logging trace reference
|
|
212
|
+
const formattedTrace = (0, exports.formatTraceIdForCloudLogging)(otelContext.traceId, projectId);
|
|
213
|
+
if (formattedTrace) {
|
|
214
|
+
entry['logging.googleapis.com/trace'] = formattedTrace;
|
|
215
|
+
}
|
|
216
|
+
// Add span ID for Cloud Logging correlation
|
|
217
|
+
if (otelContext.spanId) {
|
|
218
|
+
entry['logging.googleapis.com/spanId'] = otelContext.spanId;
|
|
219
|
+
}
|
|
220
|
+
// Add trace sampled flag
|
|
221
|
+
if (otelContext.traceFlags !== undefined) {
|
|
222
|
+
entry['logging.googleapis.com/trace_sampled'] =
|
|
223
|
+
(otelContext.traceFlags & 1) === 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return entry;
|
|
227
|
+
};
|
|
228
|
+
exports.createCloudLoggingEntry = createCloudLoggingEntry;
|
|
229
|
+
/**
|
|
230
|
+
* Check if OpenTelemetry is available and active
|
|
231
|
+
*
|
|
232
|
+
* Useful for conditional OTEL feature usage in libraries and applications.
|
|
233
|
+
*
|
|
234
|
+
* @returns true if OTEL is available and there's an active span
|
|
235
|
+
*/
|
|
236
|
+
const isOTELActive = () => {
|
|
237
|
+
if (!trace) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const span = trace.getActiveSpan();
|
|
242
|
+
return !!span;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
exports.isOTELActive = isOTELActive;
|
|
249
|
+
/**
|
|
250
|
+
* Check if OpenTelemetry SDK is installed
|
|
251
|
+
*
|
|
252
|
+
* @returns true if @opentelemetry/api is installed
|
|
253
|
+
*/
|
|
254
|
+
const isOTELInstalled = () => {
|
|
255
|
+
return !!trace;
|
|
256
|
+
};
|
|
257
|
+
exports.isOTELInstalled = isOTELInstalled;
|
|
258
|
+
//# sourceMappingURL=otel.helper.js.map
|