@forklaunch/core 0.4.0 → 0.5.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/lib/controllers/index.d.mts +6 -1
- package/lib/controllers/index.d.ts +6 -1
- package/lib/http/index.d.mts +200 -199
- package/lib/http/index.d.ts +200 -199
- package/lib/http/index.js +371 -253
- package/lib/http/index.js.map +1 -1
- package/lib/http/index.mjs +380 -268
- package/lib/http/index.mjs.map +1 -1
- package/package.json +10 -9
package/lib/http/index.mjs
CHANGED
@@ -9,7 +9,20 @@ function cors(req, res, next) {
|
|
9
9
|
}
|
10
10
|
|
11
11
|
// src/http/middleware/request/createContext.middleware.ts
|
12
|
+
import { trace } from "@opentelemetry/api";
|
12
13
|
import { v4 } from "uuid";
|
14
|
+
|
15
|
+
// src/http/telemetry/constants.ts
|
16
|
+
import {
|
17
|
+
ATTR_HTTP_REQUEST_METHOD,
|
18
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
19
|
+
ATTR_HTTP_ROUTE,
|
20
|
+
ATTR_SERVICE_NAME
|
21
|
+
} from "@opentelemetry/semantic-conventions";
|
22
|
+
var ATTR_API_NAME = "api.name";
|
23
|
+
var ATTR_CORRELATION_ID = "correlation.id";
|
24
|
+
|
25
|
+
// src/http/middleware/request/createContext.middleware.ts
|
13
26
|
function createContext(schemaValidator) {
|
14
27
|
return function setContext(req, res, next) {
|
15
28
|
req.schemaValidator = schemaValidator;
|
@@ -21,6 +34,11 @@ function createContext(schemaValidator) {
|
|
21
34
|
req.context = {
|
22
35
|
correlationId
|
23
36
|
};
|
37
|
+
const span = trace.getActiveSpan();
|
38
|
+
if (span != null) {
|
39
|
+
req.context.span = span;
|
40
|
+
req.context.span?.setAttribute(ATTR_CORRELATION_ID, correlationId);
|
41
|
+
}
|
24
42
|
next?.();
|
25
43
|
};
|
26
44
|
}
|
@@ -206,6 +224,7 @@ function enrichDetails(path, contractDetails, requestSchema, responseSchemas) {
|
|
206
224
|
req.contractDetails = contractDetails;
|
207
225
|
req.requestSchema = requestSchema;
|
208
226
|
res.responseSchemas = responseSchemas;
|
227
|
+
req.context.span?.setAttribute(ATTR_API_NAME, req.contractDetails?.name);
|
209
228
|
next?.();
|
210
229
|
};
|
211
230
|
}
|
@@ -228,7 +247,10 @@ function parse(req, _res, next) {
|
|
228
247
|
headers: req.headers,
|
229
248
|
body: req.body
|
230
249
|
};
|
231
|
-
const parsedRequest = req.schemaValidator.parse(
|
250
|
+
const parsedRequest = req.schemaValidator.parse(
|
251
|
+
req.requestSchema,
|
252
|
+
request
|
253
|
+
);
|
232
254
|
if (parsedRequest.ok && isResponseShape(parsedRequest.value)) {
|
233
255
|
req.body = parsedRequest.value.body;
|
234
256
|
req.params = parsedRequest.value.params;
|
@@ -302,7 +324,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
302
324
|
try {
|
303
325
|
await requestHandler(req, res, next);
|
304
326
|
} catch (error) {
|
305
|
-
if (next) {
|
327
|
+
if (next && typeof next === "function") {
|
306
328
|
next(error);
|
307
329
|
} else {
|
308
330
|
throw error;
|
@@ -338,7 +360,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
338
360
|
...contractDetails.params ? { params: contractDetails.params } : {},
|
339
361
|
...contractDetails.requestHeaders ? { headers: contractDetails.requestHeaders } : {},
|
340
362
|
...contractDetails.query ? { query: contractDetails.query } : {},
|
341
|
-
...isHttpContractDetails(contractDetails) && contractDetails.body ? { body: contractDetails.body } : {}
|
363
|
+
...isHttpContractDetails(contractDetails) && contractDetails.body != null ? { body: contractDetails.body } : {}
|
342
364
|
})
|
343
365
|
);
|
344
366
|
const responseEntries = {
|
@@ -347,7 +369,9 @@ var ForklaunchExpressLikeRouter = class {
|
|
347
369
|
403: schemaValidator.string,
|
348
370
|
404: schemaValidator.string,
|
349
371
|
500: schemaValidator.string,
|
350
|
-
...isPathParamHttpContractDetails(contractDetails) || isHttpContractDetails(contractDetails) ? {
|
372
|
+
...isPathParamHttpContractDetails(contractDetails) || isHttpContractDetails(contractDetails) ? {
|
373
|
+
...contractDetails.responses
|
374
|
+
} : {}
|
351
375
|
};
|
352
376
|
const responseSchemas = {
|
353
377
|
responses: {},
|
@@ -477,9 +501,12 @@ var ForklaunchExpressLikeRouter = class {
|
|
477
501
|
const controllerHandler = this.#extractControllerHandler(handlers);
|
478
502
|
registrationMethod.bind(this.internal)(
|
479
503
|
path,
|
480
|
-
...this.#resolveMiddlewares(
|
481
|
-
|
482
|
-
|
504
|
+
...this.#resolveMiddlewares(
|
505
|
+
path,
|
506
|
+
contractDetails,
|
507
|
+
requestSchema,
|
508
|
+
responseSchemas
|
509
|
+
).concat(handlers),
|
483
510
|
this.#parseAndRunControllerHandler(controllerHandler)
|
484
511
|
);
|
485
512
|
return this.#localParamRequest(
|
@@ -535,9 +562,7 @@ var ForklaunchExpressLikeRouter = class {
|
|
535
562
|
contractDetailsOrMiddlewareOrTypedHandler,
|
536
563
|
middleware2
|
537
564
|
);
|
538
|
-
if (isForklaunchExpressLikeRouter(
|
539
|
-
contractDetailsOrMiddlewareOrTypedHandler
|
540
|
-
)) {
|
565
|
+
if (isForklaunchExpressLikeRouter(contractDetailsOrMiddlewareOrTypedHandler)) {
|
541
566
|
middleware2.push(contractDetailsOrMiddlewareOrTypedHandler.internal);
|
542
567
|
}
|
543
568
|
middleware2.push(
|
@@ -843,8 +868,18 @@ var ForklaunchExpressLikeApplication = class extends ForklaunchExpressLikeRouter
|
|
843
868
|
}
|
844
869
|
};
|
845
870
|
|
871
|
+
// src/http/guards/isForklaunchRequest.ts
|
872
|
+
function isForklaunchRequest(request) {
|
873
|
+
return request != null && typeof request === "object" && "contractDetails" in request;
|
874
|
+
}
|
875
|
+
|
876
|
+
// src/http/guards/isPath.ts
|
877
|
+
function isPath(path) {
|
878
|
+
return path.startsWith("/");
|
879
|
+
}
|
880
|
+
|
846
881
|
// src/http/handlers/typedHandler.ts
|
847
|
-
function typedHandler(_schemaValidator,
|
882
|
+
function typedHandler(_schemaValidator, pathOrContractMethod, contractMethodOrContractDetails, contractDetailsOrHandler, ...handlerArray) {
|
848
883
|
let contractDetails;
|
849
884
|
let handlers;
|
850
885
|
if (typeof contractMethodOrContractDetails === "string") {
|
@@ -864,6 +899,7 @@ function typedHandler(_schemaValidator, _pathOrContractDetails, contractMethodOr
|
|
864
899
|
}
|
865
900
|
return {
|
866
901
|
_typedHandler: true,
|
902
|
+
_path: isPath(pathOrContractMethod) ? pathOrContractMethod : void 0,
|
867
903
|
contractDetails,
|
868
904
|
handlers
|
869
905
|
};
|
@@ -910,11 +946,11 @@ var put = (_schemaValidator, path, contractDetails, ...handlers) => {
|
|
910
946
|
};
|
911
947
|
|
912
948
|
// src/http/handlers/trace.ts
|
913
|
-
var
|
949
|
+
var trace2 = (_schemaValidator, path, contractDetails, ...handlers) => {
|
914
950
|
return typedHandler(_schemaValidator, path, "trace", contractDetails, ...handlers);
|
915
951
|
};
|
916
952
|
|
917
|
-
// src/http/
|
953
|
+
// src/http/httpStatusCodes.ts
|
918
954
|
var HTTPStatuses = {
|
919
955
|
/**
|
920
956
|
* The initial part of a request has been received and has not yet been
|
@@ -1897,6 +1933,324 @@ var getCodeForStatus = (status) => {
|
|
1897
1933
|
};
|
1898
1934
|
var httpStatusCodes_default = HTTPStatuses;
|
1899
1935
|
|
1936
|
+
// src/http/middleware/response/parse.middleware.ts
|
1937
|
+
import {
|
1938
|
+
prettyPrintParseErrors as prettyPrintParseErrors2
|
1939
|
+
} from "@forklaunch/validator";
|
1940
|
+
function parse2(req, res, next) {
|
1941
|
+
const { headers, responses } = res.responseSchemas;
|
1942
|
+
const parsedResponse = req.schemaValidator.parse(
|
1943
|
+
responses?.[res.statusCode],
|
1944
|
+
res.bodyData
|
1945
|
+
);
|
1946
|
+
const parsedHeaders = req.schemaValidator.parse(
|
1947
|
+
headers ?? req.schemaValidator.unknown,
|
1948
|
+
res.getHeaders()
|
1949
|
+
);
|
1950
|
+
const parseErrors = [];
|
1951
|
+
if (!parsedHeaders.ok) {
|
1952
|
+
const headerErrors = prettyPrintParseErrors2(parsedHeaders.errors, "Header");
|
1953
|
+
if (headerErrors) {
|
1954
|
+
parseErrors.push(headerErrors);
|
1955
|
+
}
|
1956
|
+
}
|
1957
|
+
if (!parsedResponse.ok) {
|
1958
|
+
const responseErrors = prettyPrintParseErrors2(
|
1959
|
+
parsedResponse.errors,
|
1960
|
+
"Response"
|
1961
|
+
);
|
1962
|
+
if (responseErrors) {
|
1963
|
+
parseErrors.push(responseErrors);
|
1964
|
+
}
|
1965
|
+
}
|
1966
|
+
if (parseErrors.length > 0) {
|
1967
|
+
switch (req.contractDetails.options?.responseValidation) {
|
1968
|
+
default:
|
1969
|
+
case "error":
|
1970
|
+
next?.(new Error(`Invalid response:
|
1971
|
+
${parseErrors.join("\n\n")}`));
|
1972
|
+
break;
|
1973
|
+
case "warning":
|
1974
|
+
console.warn(`Invalid response:
|
1975
|
+
${parseErrors.join("\n\n")}`);
|
1976
|
+
break;
|
1977
|
+
case "none":
|
1978
|
+
break;
|
1979
|
+
}
|
1980
|
+
}
|
1981
|
+
next?.();
|
1982
|
+
}
|
1983
|
+
|
1984
|
+
// src/http/telemetry/pinoLogger.ts
|
1985
|
+
import { isNever } from "@forklaunch/common";
|
1986
|
+
import { trace as trace3 } from "@opentelemetry/api";
|
1987
|
+
import { logs } from "@opentelemetry/api-logs";
|
1988
|
+
import pino from "pino";
|
1989
|
+
function mapSeverity(level) {
|
1990
|
+
switch (level) {
|
1991
|
+
case "silent":
|
1992
|
+
return 0;
|
1993
|
+
case "trace":
|
1994
|
+
return 1;
|
1995
|
+
case "debug":
|
1996
|
+
return 5;
|
1997
|
+
case "info":
|
1998
|
+
return 9;
|
1999
|
+
case "warn":
|
2000
|
+
return 13;
|
2001
|
+
case "error":
|
2002
|
+
return 17;
|
2003
|
+
case "fatal":
|
2004
|
+
return 21;
|
2005
|
+
default:
|
2006
|
+
isNever(level);
|
2007
|
+
return 0;
|
2008
|
+
}
|
2009
|
+
}
|
2010
|
+
var PinoLogger = class _PinoLogger {
|
2011
|
+
pinoLogger;
|
2012
|
+
meta;
|
2013
|
+
constructor(level, meta = {}) {
|
2014
|
+
this.pinoLogger = pino({
|
2015
|
+
level: level || "info",
|
2016
|
+
formatters: {
|
2017
|
+
level(label) {
|
2018
|
+
return { level: label };
|
2019
|
+
}
|
2020
|
+
},
|
2021
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
2022
|
+
transport: {
|
2023
|
+
target: "pino-pretty",
|
2024
|
+
options: { colorize: true }
|
2025
|
+
}
|
2026
|
+
});
|
2027
|
+
this.meta = meta;
|
2028
|
+
}
|
2029
|
+
log(level, msg, meta = {}) {
|
2030
|
+
const activeSpan = trace3.getActiveSpan();
|
2031
|
+
if (activeSpan) {
|
2032
|
+
const activeSpanContext = activeSpan.spanContext();
|
2033
|
+
meta.trace_id = activeSpanContext.traceId;
|
2034
|
+
meta.span_id = activeSpanContext.spanId;
|
2035
|
+
meta.trace_flags = activeSpanContext.traceFlags;
|
2036
|
+
if (!meta.api_name) {
|
2037
|
+
meta = { ...meta, ...activeSpan?.attributes };
|
2038
|
+
}
|
2039
|
+
}
|
2040
|
+
this.pinoLogger[level](msg);
|
2041
|
+
logs.getLogger(process.env.OTEL_SERVICE_NAME ?? "unknown").emit({
|
2042
|
+
severityText: level,
|
2043
|
+
severityNumber: mapSeverity(level),
|
2044
|
+
body: msg,
|
2045
|
+
attributes: { ...this.meta, ...meta }
|
2046
|
+
});
|
2047
|
+
}
|
2048
|
+
error(msg, meta = {}) {
|
2049
|
+
this.log("error", msg, meta);
|
2050
|
+
}
|
2051
|
+
info(msg, meta = {}) {
|
2052
|
+
this.log("info", msg, meta);
|
2053
|
+
}
|
2054
|
+
debug(msg, meta = {}) {
|
2055
|
+
this.log("debug", msg, meta);
|
2056
|
+
}
|
2057
|
+
warn(msg, meta = {}) {
|
2058
|
+
this.log("warn", msg, meta);
|
2059
|
+
}
|
2060
|
+
trace(msg, meta = {}) {
|
2061
|
+
this.log("trace", msg, meta);
|
2062
|
+
}
|
2063
|
+
child(meta = {}) {
|
2064
|
+
return new _PinoLogger(this.pinoLogger.level, { ...this.meta, ...meta });
|
2065
|
+
}
|
2066
|
+
getBaseLogger() {
|
2067
|
+
return this.pinoLogger;
|
2068
|
+
}
|
2069
|
+
};
|
2070
|
+
function logger(level, meta = {}) {
|
2071
|
+
return new PinoLogger(level, meta);
|
2072
|
+
}
|
2073
|
+
|
2074
|
+
// src/http/telemetry/recordMetric.ts
|
2075
|
+
import {
|
2076
|
+
ATTR_HTTP_REQUEST_METHOD as ATTR_HTTP_REQUEST_METHOD3,
|
2077
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE as ATTR_HTTP_RESPONSE_STATUS_CODE3,
|
2078
|
+
ATTR_HTTP_ROUTE as ATTR_HTTP_ROUTE3,
|
2079
|
+
ATTR_SERVICE_NAME as ATTR_SERVICE_NAME3
|
2080
|
+
} from "@opentelemetry/semantic-conventions";
|
2081
|
+
|
2082
|
+
// src/services/configInjector.ts
|
2083
|
+
import { extractArgumentNames as extractArgumentNames2, isNever as isNever2 } from "@forklaunch/common";
|
2084
|
+
import {
|
2085
|
+
prettyPrintParseErrors as prettyPrintParseErrors3
|
2086
|
+
} from "@forklaunch/validator";
|
2087
|
+
|
2088
|
+
// src/services/getEnvVar.ts
|
2089
|
+
function getEnvVar(name) {
|
2090
|
+
const value = process.env[name];
|
2091
|
+
return value;
|
2092
|
+
}
|
2093
|
+
|
2094
|
+
// src/http/telemetry/openTelemetryCollector.ts
|
2095
|
+
import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
|
2096
|
+
import {
|
2097
|
+
metrics
|
2098
|
+
} from "@opentelemetry/api";
|
2099
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
2100
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
2101
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
2102
|
+
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
2103
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
2104
|
+
import { Resource } from "@opentelemetry/resources";
|
2105
|
+
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
2106
|
+
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
2107
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
2108
|
+
import {
|
2109
|
+
ATTR_SERVICE_NAME as ATTR_SERVICE_NAME2
|
2110
|
+
} from "@opentelemetry/semantic-conventions";
|
2111
|
+
import dotenv from "dotenv";
|
2112
|
+
var OpenTelemetryCollector = class {
|
2113
|
+
// scoped creation and create this in middleware when api execute. Also add correlation id
|
2114
|
+
constructor(serviceName, level, metricDefinitions) {
|
2115
|
+
this.serviceName = serviceName;
|
2116
|
+
this.logger = logger(level ?? "info");
|
2117
|
+
this.metrics = {};
|
2118
|
+
for (const [metricId, metricType] of Object.entries(
|
2119
|
+
metricDefinitions ?? {}
|
2120
|
+
)) {
|
2121
|
+
switch (metricType) {
|
2122
|
+
case "counter":
|
2123
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createCounter(metricId);
|
2124
|
+
break;
|
2125
|
+
case "gauge":
|
2126
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createGauge(metricId);
|
2127
|
+
break;
|
2128
|
+
case "histogram":
|
2129
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createHistogram(metricId);
|
2130
|
+
break;
|
2131
|
+
case "upDownCounter":
|
2132
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createUpDownCounter(metricId);
|
2133
|
+
break;
|
2134
|
+
case "observableCounter":
|
2135
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableCounter(metricId);
|
2136
|
+
break;
|
2137
|
+
case "observableGauge":
|
2138
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableGauge(metricId);
|
2139
|
+
break;
|
2140
|
+
case "observableUpDownCounter":
|
2141
|
+
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableUpDownCounter(metricId);
|
2142
|
+
break;
|
2143
|
+
}
|
2144
|
+
}
|
2145
|
+
this.log("info", "OpenTelemetry (Traces + Logs + Metrics) started");
|
2146
|
+
}
|
2147
|
+
logger;
|
2148
|
+
metrics;
|
2149
|
+
log(level, msg, meta = {}) {
|
2150
|
+
this.logger.log(level, msg, meta);
|
2151
|
+
}
|
2152
|
+
info(msg, meta = {}) {
|
2153
|
+
this.logger.info(msg, meta);
|
2154
|
+
}
|
2155
|
+
error(msg, meta = {}) {
|
2156
|
+
this.logger.error(msg, meta);
|
2157
|
+
}
|
2158
|
+
warn(msg, meta = {}) {
|
2159
|
+
this.logger.warn(msg, meta);
|
2160
|
+
}
|
2161
|
+
debug(msg, meta = {}) {
|
2162
|
+
this.logger.debug(msg, meta);
|
2163
|
+
}
|
2164
|
+
trace(msg, meta = {}) {
|
2165
|
+
this.logger.trace(msg, meta);
|
2166
|
+
}
|
2167
|
+
getMetric(metricId) {
|
2168
|
+
return this.metrics[metricId];
|
2169
|
+
}
|
2170
|
+
};
|
2171
|
+
dotenv.config({ path: getEnvVar("ENV_FILE_PATH") });
|
2172
|
+
new NodeSDK({
|
2173
|
+
resource: new Resource({
|
2174
|
+
[ATTR_SERVICE_NAME2]: getEnvVar("OTEL_SERVICE_NAME")
|
2175
|
+
}),
|
2176
|
+
traceExporter: new OTLPTraceExporter({
|
2177
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/traces`
|
2178
|
+
}),
|
2179
|
+
metricReader: new PeriodicExportingMetricReader({
|
2180
|
+
exporter: new OTLPMetricExporter({
|
2181
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/metrics`
|
2182
|
+
}),
|
2183
|
+
exportIntervalMillis: 5e3
|
2184
|
+
}),
|
2185
|
+
logRecordProcessors: [
|
2186
|
+
new BatchLogRecordProcessor(
|
2187
|
+
new OTLPLogExporter({
|
2188
|
+
url: `${getEnvVar("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318"}/v1/logs`
|
2189
|
+
})
|
2190
|
+
)
|
2191
|
+
],
|
2192
|
+
instrumentations: [
|
2193
|
+
new HttpInstrumentation({
|
2194
|
+
applyCustomAttributesOnSpan: (span, request) => {
|
2195
|
+
span.setAttribute(
|
2196
|
+
"service.name",
|
2197
|
+
getEnvVar("OTEL_SERVICE_NAME") ?? "unknown"
|
2198
|
+
);
|
2199
|
+
if (isForklaunchRequest(request)) {
|
2200
|
+
span.setAttribute("api.name", request.contractDetails?.name);
|
2201
|
+
}
|
2202
|
+
}
|
2203
|
+
}),
|
2204
|
+
new ExpressInstrumentation(),
|
2205
|
+
new HyperExpressInstrumentation()
|
2206
|
+
]
|
2207
|
+
}).start();
|
2208
|
+
var httpRequestsTotalCounter = metrics.getMeter(getEnvVar("OTEL_SERVICE_NAME") || "unknown").createCounter("http_requests_total", {
|
2209
|
+
description: "Number of HTTP requests"
|
2210
|
+
});
|
2211
|
+
|
2212
|
+
// src/http/telemetry/recordMetric.ts
|
2213
|
+
function recordMetric(req, res) {
|
2214
|
+
httpRequestsTotalCounter.add(1, {
|
2215
|
+
[ATTR_SERVICE_NAME3]: getEnvVar("OTEL_SERVICE_NAME"),
|
2216
|
+
[ATTR_API_NAME]: req.contractDetails?.name,
|
2217
|
+
[ATTR_CORRELATION_ID]: req.context.correlationId,
|
2218
|
+
[ATTR_HTTP_REQUEST_METHOD3]: req.method,
|
2219
|
+
[ATTR_HTTP_ROUTE3]: req.originalPath,
|
2220
|
+
[ATTR_HTTP_RESPONSE_STATUS_CODE3]: res.statusCode || 0
|
2221
|
+
});
|
2222
|
+
}
|
2223
|
+
|
2224
|
+
// src/http/middleware/response/enrichExpressLikeSend.middleware.ts
|
2225
|
+
function enrichExpressLikeSend(instance, req, res, originalSend, data, shouldEnrich) {
|
2226
|
+
let parseErrorSent;
|
2227
|
+
if (shouldEnrich) {
|
2228
|
+
recordMetric(req, res);
|
2229
|
+
if (res.statusCode === 404) {
|
2230
|
+
res.status(404);
|
2231
|
+
logger("error").error("Not Found");
|
2232
|
+
originalSend.call(instance, "Not Found");
|
2233
|
+
}
|
2234
|
+
parse2(req, res, (err) => {
|
2235
|
+
if (err) {
|
2236
|
+
let errorString = err.message;
|
2237
|
+
if (res.locals.errorMessage) {
|
2238
|
+
errorString += `
|
2239
|
+
------------------
|
2240
|
+
${res.locals.errorMessage}`;
|
2241
|
+
}
|
2242
|
+
logger("error").error(errorString);
|
2243
|
+
res.status(500);
|
2244
|
+
originalSend.call(instance, errorString);
|
2245
|
+
parseErrorSent = true;
|
2246
|
+
}
|
2247
|
+
});
|
2248
|
+
}
|
2249
|
+
if (!parseErrorSent) {
|
2250
|
+
originalSend.call(instance, data);
|
2251
|
+
}
|
2252
|
+
}
|
2253
|
+
|
1900
2254
|
// src/http/openApiV3Generator/openApiV3Generator.ts
|
1901
2255
|
function toUpperCase(str) {
|
1902
2256
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
@@ -2040,267 +2394,22 @@ function generateSwaggerDocument(schemaValidator, port, routers) {
|
|
2040
2394
|
return generateOpenApiDocument(port, tags, paths);
|
2041
2395
|
}
|
2042
2396
|
|
2043
|
-
// src/http/
|
2044
|
-
|
2045
|
-
|
2046
|
-
SEMATTRS_HTTP_METHOD,
|
2047
|
-
SEMATTRS_HTTP_ROUTE,
|
2048
|
-
SEMATTRS_HTTP_STATUS_CODE,
|
2049
|
-
SEMATTRS_HTTP_TARGET
|
2050
|
-
} from "@opentelemetry/semantic-conventions";
|
2051
|
-
|
2052
|
-
// src/http/tracing/pinoLogger.ts
|
2053
|
-
import pino from "pino";
|
2054
|
-
function pinoLogger(level) {
|
2055
|
-
return pino({
|
2056
|
-
level: level || "info",
|
2057
|
-
formatters: {
|
2058
|
-
level(label) {
|
2059
|
-
return { level: label };
|
2060
|
-
}
|
2061
|
-
},
|
2062
|
-
timestamp: pino.stdTimeFunctions.isoTime,
|
2063
|
-
transport: {
|
2064
|
-
target: "pino-pretty",
|
2065
|
-
options: { colorize: true }
|
2066
|
-
}
|
2067
|
-
});
|
2068
|
-
}
|
2069
|
-
|
2070
|
-
// src/http/tracing/emitLoggerError.ts
|
2071
|
-
var logger = pinoLogger("error");
|
2072
|
-
var emitLoggerError = (req, res, errorString) => {
|
2073
|
-
logs.getLogger(process.env.SERVICE_NAME ?? "unknown").emit({
|
2074
|
-
severityText: "ERROR",
|
2075
|
-
severityNumber: 17,
|
2076
|
-
body: errorString,
|
2077
|
-
attributes: {
|
2078
|
-
"service.name": process.env.SERVICE_NAME,
|
2079
|
-
"api.name": req.contractDetails?.name,
|
2080
|
-
[SEMATTRS_HTTP_TARGET]: req.path,
|
2081
|
-
[SEMATTRS_HTTP_ROUTE]: req.originalPath,
|
2082
|
-
[SEMATTRS_HTTP_METHOD]: req.method,
|
2083
|
-
[SEMATTRS_HTTP_STATUS_CODE]: res.statusCode
|
2084
|
-
}
|
2085
|
-
});
|
2086
|
-
logger.error(errorString);
|
2087
|
-
};
|
2088
|
-
|
2089
|
-
// src/http/tracing/openTelemetryCollector.ts
|
2090
|
-
import { HyperExpressInstrumentation } from "@forklaunch/opentelemetry-instrumentation-hyper-express";
|
2091
|
-
import {
|
2092
|
-
metrics,
|
2093
|
-
trace as trace2
|
2094
|
-
} from "@opentelemetry/api";
|
2095
|
-
import { logs as logs2 } from "@opentelemetry/api-logs";
|
2096
|
-
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
2097
|
-
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
2098
|
-
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
2099
|
-
import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
2100
|
-
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
2101
|
-
import { Resource } from "@opentelemetry/resources";
|
2102
|
-
import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
2103
|
-
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
2104
|
-
import { NodeSDK } from "@opentelemetry/sdk-node";
|
2105
|
-
import {
|
2106
|
-
ATTR_SERVICE_NAME
|
2107
|
-
} from "@opentelemetry/semantic-conventions";
|
2108
|
-
var OpenTelemetryCollector = class {
|
2109
|
-
// scoped creation and create this in middleware when api execute. Also add correlation id
|
2110
|
-
constructor(serviceName, level, metricDefinitions) {
|
2111
|
-
this.serviceName = serviceName;
|
2112
|
-
this.logger = pinoLogger(level ?? "info");
|
2113
|
-
this.metrics = {};
|
2114
|
-
for (const [metricId, metricType] of Object.entries(
|
2115
|
-
metricDefinitions ?? {}
|
2116
|
-
)) {
|
2117
|
-
switch (metricType) {
|
2118
|
-
case "counter":
|
2119
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createCounter(metricId);
|
2120
|
-
break;
|
2121
|
-
case "gauge":
|
2122
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createGauge(metricId);
|
2123
|
-
break;
|
2124
|
-
case "histogram":
|
2125
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createHistogram(metricId);
|
2126
|
-
break;
|
2127
|
-
case "upDownCounter":
|
2128
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createUpDownCounter(metricId);
|
2129
|
-
break;
|
2130
|
-
case "observableCounter":
|
2131
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableCounter(metricId);
|
2132
|
-
break;
|
2133
|
-
case "observableGauge":
|
2134
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableGauge(metricId);
|
2135
|
-
break;
|
2136
|
-
case "observableUpDownCounter":
|
2137
|
-
this.metrics[metricId] = metrics.getMeter(this.serviceName).createObservableUpDownCounter(metricId);
|
2138
|
-
break;
|
2139
|
-
}
|
2140
|
-
}
|
2141
|
-
this.log("info", "OpenTelemetry (Traces + Logs + Metrics) started");
|
2142
|
-
}
|
2143
|
-
logger;
|
2144
|
-
metrics;
|
2145
|
-
log(level, msg, meta = {}) {
|
2146
|
-
const activeSpan = trace2.getActiveSpan();
|
2147
|
-
if (activeSpan) {
|
2148
|
-
const activeSpanContext = activeSpan.spanContext();
|
2149
|
-
meta.trace_id = activeSpanContext.traceId;
|
2150
|
-
meta.span_id = activeSpanContext.spanId;
|
2151
|
-
meta.trace_flags = activeSpanContext.traceFlags;
|
2152
|
-
}
|
2153
|
-
if (!meta.api_name) {
|
2154
|
-
meta.api_name = activeSpan?.attributes["api.name"] ?? "undefined";
|
2155
|
-
}
|
2156
|
-
this.logger[level](msg);
|
2157
|
-
logs2.getLogger(this.serviceName).emit({
|
2158
|
-
severityText: level,
|
2159
|
-
body: msg,
|
2160
|
-
attributes: meta
|
2161
|
-
});
|
2162
|
-
}
|
2163
|
-
getMetric(metricId) {
|
2164
|
-
return this.metrics[metricId];
|
2165
|
-
}
|
2166
|
-
};
|
2167
|
-
new NodeSDK({
|
2168
|
-
resource: new Resource({
|
2169
|
-
[ATTR_SERVICE_NAME]: process.env.SERVICE_NAME
|
2170
|
-
}),
|
2171
|
-
traceExporter: new OTLPTraceExporter({
|
2172
|
-
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/traces`
|
2173
|
-
}),
|
2174
|
-
metricReader: new PeriodicExportingMetricReader({
|
2175
|
-
exporter: new OTLPMetricExporter({
|
2176
|
-
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/metrics`
|
2177
|
-
}),
|
2178
|
-
exportIntervalMillis: 5e3
|
2179
|
-
}),
|
2180
|
-
logRecordProcessors: [
|
2181
|
-
new BatchLogRecordProcessor(
|
2182
|
-
new OTLPLogExporter({
|
2183
|
-
url: `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318"}/v1/logs`
|
2184
|
-
})
|
2185
|
-
)
|
2186
|
-
],
|
2187
|
-
instrumentations: [
|
2188
|
-
new HttpInstrumentation(),
|
2189
|
-
new ExpressInstrumentation({
|
2190
|
-
requestHook: (span, info) => {
|
2191
|
-
span.setAttribute(
|
2192
|
-
"service.name",
|
2193
|
-
process.env.SERVICE_NAME ?? "unknown"
|
2194
|
-
);
|
2195
|
-
span.setAttribute("api.name", info.request.contractDetails?.name);
|
2196
|
-
}
|
2197
|
-
}),
|
2198
|
-
new HyperExpressInstrumentation()
|
2199
|
-
]
|
2200
|
-
}).start();
|
2201
|
-
var httpRequestsTotalCounter = metrics.getMeter(process.env.SERVICE_NAME || "unknown").createCounter("http_requests_total", {
|
2202
|
-
description: "Number of HTTP requests"
|
2203
|
-
});
|
2204
|
-
|
2205
|
-
// src/http/middleware/response/parse.middleware.ts
|
2206
|
-
import {
|
2207
|
-
prettyPrintParseErrors as prettyPrintParseErrors2
|
2208
|
-
} from "@forklaunch/validator";
|
2209
|
-
function parse2(req, res, next) {
|
2210
|
-
const { headers, responses } = res.responseSchemas;
|
2211
|
-
const parsedResponse = req.schemaValidator.parse(
|
2212
|
-
responses?.[res.statusCode],
|
2213
|
-
res.bodyData
|
2214
|
-
);
|
2215
|
-
const parsedHeaders = req.schemaValidator.parse(
|
2216
|
-
headers ?? req.schemaValidator.unknown,
|
2217
|
-
res.getHeaders()
|
2218
|
-
);
|
2219
|
-
const parseErrors = [];
|
2220
|
-
if (!parsedHeaders.ok) {
|
2221
|
-
const headerErrors = prettyPrintParseErrors2(parsedHeaders.errors, "Header");
|
2222
|
-
if (headerErrors) {
|
2223
|
-
parseErrors.push(headerErrors);
|
2224
|
-
}
|
2225
|
-
}
|
2226
|
-
if (!parsedResponse.ok) {
|
2227
|
-
const responseErrors = prettyPrintParseErrors2(
|
2228
|
-
parsedResponse.errors,
|
2229
|
-
"Response"
|
2230
|
-
);
|
2231
|
-
if (responseErrors) {
|
2232
|
-
parseErrors.push(responseErrors);
|
2233
|
-
}
|
2234
|
-
}
|
2235
|
-
if (parseErrors.length > 0) {
|
2236
|
-
switch (req.contractDetails.options?.responseValidation) {
|
2237
|
-
default:
|
2238
|
-
case "error":
|
2239
|
-
next?.(new Error(`Invalid response:
|
2240
|
-
${parseErrors.join("\n\n")}`));
|
2241
|
-
break;
|
2242
|
-
case "warning":
|
2243
|
-
console.warn(`Invalid response:
|
2244
|
-
${parseErrors.join("\n\n")}`);
|
2245
|
-
break;
|
2246
|
-
case "none":
|
2247
|
-
break;
|
2248
|
-
}
|
2249
|
-
}
|
2250
|
-
next?.();
|
2251
|
-
}
|
2252
|
-
|
2253
|
-
// src/http/utils/enrichExpressLikeSend.ts
|
2254
|
-
function enrichExpressLikeSend(instance, req, res, originalSend, data, shouldEnrich) {
|
2255
|
-
let parseErrorSent;
|
2256
|
-
if (shouldEnrich) {
|
2257
|
-
if (res.statusCode === 404) {
|
2258
|
-
res.status(404);
|
2259
|
-
emitLoggerError(req, res, "Not Found");
|
2260
|
-
originalSend.call(instance, "Not Found");
|
2261
|
-
}
|
2262
|
-
parse2(req, res, (err) => {
|
2263
|
-
if (err) {
|
2264
|
-
let errorString = err.message;
|
2265
|
-
if (res.locals.errorMessage) {
|
2266
|
-
errorString += `
|
2267
|
-
------------------
|
2268
|
-
${res.locals.errorMessage}`;
|
2269
|
-
}
|
2270
|
-
emitLoggerError(req, res, errorString);
|
2271
|
-
res.status(500);
|
2272
|
-
originalSend.call(instance, errorString);
|
2273
|
-
parseErrorSent = true;
|
2274
|
-
}
|
2275
|
-
});
|
2276
|
-
}
|
2277
|
-
if (!parseErrorSent) {
|
2278
|
-
originalSend.call(instance, data);
|
2279
|
-
}
|
2280
|
-
}
|
2281
|
-
|
2282
|
-
// src/http/utils/recordMetric.ts
|
2283
|
-
import {
|
2284
|
-
SEMATTRS_HTTP_METHOD as SEMATTRS_HTTP_METHOD3,
|
2285
|
-
SEMATTRS_HTTP_ROUTE as SEMATTRS_HTTP_ROUTE3,
|
2286
|
-
SEMATTRS_HTTP_STATUS_CODE as SEMATTRS_HTTP_STATUS_CODE3
|
2287
|
-
} from "@opentelemetry/semantic-conventions";
|
2288
|
-
function recordMetric(req, res) {
|
2289
|
-
httpRequestsTotalCounter.add(1, {
|
2290
|
-
"service.name": process.env.SERVICE_NAME ?? "unknown",
|
2291
|
-
"api.name": req.contractDetails?.name,
|
2292
|
-
[SEMATTRS_HTTP_METHOD3]: req.method,
|
2293
|
-
[SEMATTRS_HTTP_ROUTE3]: req.originalPath,
|
2294
|
-
[SEMATTRS_HTTP_STATUS_CODE3]: res.statusCode || 0
|
2295
|
-
});
|
2397
|
+
// src/http/telemetry/metricsDefinition.ts
|
2398
|
+
function metricsDefinitions(metrics2) {
|
2399
|
+
return metrics2;
|
2296
2400
|
}
|
2297
2401
|
export {
|
2402
|
+
ATTR_API_NAME,
|
2403
|
+
ATTR_CORRELATION_ID,
|
2404
|
+
ATTR_HTTP_REQUEST_METHOD,
|
2405
|
+
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
2406
|
+
ATTR_HTTP_ROUTE,
|
2407
|
+
ATTR_SERVICE_NAME,
|
2298
2408
|
ForklaunchExpressLikeApplication,
|
2299
2409
|
ForklaunchExpressLikeRouter,
|
2300
2410
|
HTTPStatuses,
|
2301
2411
|
OpenTelemetryCollector,
|
2302
2412
|
delete_,
|
2303
|
-
emitLoggerError,
|
2304
2413
|
enrichExpressLikeSend,
|
2305
2414
|
generateSwaggerDocument,
|
2306
2415
|
get,
|
@@ -2308,19 +2417,22 @@ export {
|
|
2308
2417
|
head,
|
2309
2418
|
httpRequestsTotalCounter,
|
2310
2419
|
isClientError,
|
2420
|
+
isForklaunchRequest,
|
2311
2421
|
isForklaunchRouter,
|
2312
2422
|
isInformational,
|
2313
2423
|
isRedirection,
|
2314
2424
|
isServerError,
|
2315
2425
|
isSuccessful,
|
2316
2426
|
isValidStatusCode,
|
2427
|
+
logger,
|
2428
|
+
metricsDefinitions,
|
2317
2429
|
middleware,
|
2318
2430
|
options,
|
2319
2431
|
patch,
|
2320
2432
|
post,
|
2321
2433
|
put,
|
2322
2434
|
recordMetric,
|
2323
|
-
trace,
|
2435
|
+
trace2 as trace,
|
2324
2436
|
typedHandler
|
2325
2437
|
};
|
2326
2438
|
//# sourceMappingURL=index.mjs.map
|