@unito/integration-sdk 2.1.3 → 2.2.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.
@@ -849,38 +849,38 @@ function extractFilters(req, res, next) {
849
849
  }
850
850
 
851
851
  function onFinish(req, res, next) {
852
- if (req.originalUrl !== '/health') {
853
- res.on('finish', function () {
854
- const error = res.locals.error;
855
- const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
856
- const durationInMs = (durationInNs / 1_000_000) | 0;
857
- const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
858
- const metadata = {
859
- duration: durationInNs,
860
- // Use reserved and standard attributes of Datadog
861
- // https://app.datadoghq.com/logs/pipelines/standard-attributes
862
- http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
863
- ...(error
864
- ? {
865
- error: {
866
- kind: error.message,
867
- stack: (error.originalError?.details?.stack ?? error.details?.stack),
868
- message: error.originalError?.message ?? error.message,
869
- },
870
- }
871
- : {}),
872
- };
873
- if ([404, 429].includes(res.statusCode)) {
874
- res.locals.logger.warn(message, metadata);
875
- }
876
- else if (res.statusCode >= 400) {
877
- res.locals.logger.error(message, metadata);
878
- }
879
- else {
880
- res.locals.logger.info(message, metadata);
881
- }
882
- });
883
- }
852
+ res.on('finish', function () {
853
+ const logger = res.locals.logger ?? new Logger();
854
+ const error = res.locals.error;
855
+ const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
856
+ const durationInMs = (durationInNs / 1_000_000) | 0;
857
+ const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
858
+ const metadata = {
859
+ duration: durationInNs,
860
+ // Use reserved and standard attributes of Datadog
861
+ // https://app.datadoghq.com/logs/pipelines/standard-attributes
862
+ http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
863
+ ...(error
864
+ ? {
865
+ error: {
866
+ kind: error.message,
867
+ stack: (error.originalError?.details?.stack ?? error.details?.stack),
868
+ message: error.originalError?.message ?? error.message,
869
+ },
870
+ }
871
+ : {}),
872
+ };
873
+ if ([404, 429].includes(res.statusCode)) {
874
+ logger.warn(message, metadata);
875
+ }
876
+ else if (res.statusCode >= 400) {
877
+ logger.error(message, metadata);
878
+ }
879
+ else if (req.originalUrl !== '/health') {
880
+ // Don't log successful health check requests
881
+ logger.info(message, metadata);
882
+ }
883
+ });
884
884
  next();
885
885
  }
886
886
 
@@ -975,6 +975,10 @@ function extractOperationDeadline(req, res, next) {
975
975
  next();
976
976
  }
977
977
 
978
+ function onHealthCheck(_req, res) {
979
+ res.status(200).json({});
980
+ }
981
+
978
982
  function printErrorMessage(message) {
979
983
  console.error();
980
984
  console.error(`\x1b[31m Oops! Something went wrong! \x1b[0m`);
@@ -1002,7 +1006,7 @@ class Integration {
1002
1006
  * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port.
1003
1007
  */
1004
1008
  constructor(options = {}) {
1005
- this.port = options.port || 9200;
1009
+ this.port = options.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 9200);
1006
1010
  this.handlers = [];
1007
1011
  }
1008
1012
  /**
@@ -1090,6 +1094,7 @@ class Integration {
1090
1094
  });
1091
1095
  // Instantiate application middlewares. These can throw, so they have an implicit dependency on the internal
1092
1096
  // middlewares such as the logger, the correlationId, and the error handling.
1097
+ app.get('/health', onHealthCheck);
1093
1098
  app.use(extractCredentials);
1094
1099
  app.use(extractSecrets);
1095
1100
  app.use(extractFilters);
@@ -14,6 +14,7 @@ import searchMiddleware from './middlewares/search.js';
14
14
  import selectsMiddleware from './middlewares/selects.js';
15
15
  import relationsMiddleware from './middlewares/relations.js';
16
16
  import signalMiddleware from './middlewares/signal.js';
17
+ import healthMiddleware from './middlewares/health.js';
17
18
  function printErrorMessage(message) {
18
19
  console.error();
19
20
  console.error(`\x1b[31m Oops! Something went wrong! \x1b[0m`);
@@ -41,7 +42,7 @@ export default class Integration {
41
42
  * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port.
42
43
  */
43
44
  constructor(options = {}) {
44
- this.port = options.port || 9200;
45
+ this.port = options.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 9200);
45
46
  this.handlers = [];
46
47
  }
47
48
  /**
@@ -129,6 +130,7 @@ export default class Integration {
129
130
  });
130
131
  // Instantiate application middlewares. These can throw, so they have an implicit dependency on the internal
131
132
  // middlewares such as the logger, the correlationId, and the error handling.
133
+ app.get('/health', healthMiddleware);
132
134
  app.use(credentialsMiddleware);
133
135
  app.use(secretsMiddleware);
134
136
  app.use(filtersMiddleware);
@@ -1,36 +1,37 @@
1
+ import { default as Logger } from '../resources/logger.js';
1
2
  function onFinish(req, res, next) {
2
- if (req.originalUrl !== '/health') {
3
- res.on('finish', function () {
4
- const error = res.locals.error;
5
- const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
6
- const durationInMs = (durationInNs / 1_000_000) | 0;
7
- const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
8
- const metadata = {
9
- duration: durationInNs,
10
- // Use reserved and standard attributes of Datadog
11
- // https://app.datadoghq.com/logs/pipelines/standard-attributes
12
- http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
13
- ...(error
14
- ? {
15
- error: {
16
- kind: error.message,
17
- stack: (error.originalError?.details?.stack ?? error.details?.stack),
18
- message: error.originalError?.message ?? error.message,
19
- },
20
- }
21
- : {}),
22
- };
23
- if ([404, 429].includes(res.statusCode)) {
24
- res.locals.logger.warn(message, metadata);
25
- }
26
- else if (res.statusCode >= 400) {
27
- res.locals.logger.error(message, metadata);
28
- }
29
- else {
30
- res.locals.logger.info(message, metadata);
31
- }
32
- });
33
- }
3
+ res.on('finish', function () {
4
+ const logger = res.locals.logger ?? new Logger();
5
+ const error = res.locals.error;
6
+ const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
7
+ const durationInMs = (durationInNs / 1_000_000) | 0;
8
+ const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
9
+ const metadata = {
10
+ duration: durationInNs,
11
+ // Use reserved and standard attributes of Datadog
12
+ // https://app.datadoghq.com/logs/pipelines/standard-attributes
13
+ http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
14
+ ...(error
15
+ ? {
16
+ error: {
17
+ kind: error.message,
18
+ stack: (error.originalError?.details?.stack ?? error.details?.stack),
19
+ message: error.originalError?.message ?? error.message,
20
+ },
21
+ }
22
+ : {}),
23
+ };
24
+ if ([404, 429].includes(res.statusCode)) {
25
+ logger.warn(message, metadata);
26
+ }
27
+ else if (res.statusCode >= 400) {
28
+ logger.error(message, metadata);
29
+ }
30
+ else if (req.originalUrl !== '/health') {
31
+ // Don't log successful health check requests
32
+ logger.info(message, metadata);
33
+ }
34
+ });
34
35
  next();
35
36
  }
36
37
  export default onFinish;
@@ -0,0 +1,3 @@
1
+ import { Request, Response } from 'express';
2
+ declare function onHealthCheck(_req: Request, res: Response): void;
3
+ export default onHealthCheck;
@@ -0,0 +1,4 @@
1
+ function onHealthCheck(_req, res) {
2
+ res.status(200).json({});
3
+ }
4
+ export default onHealthCheck;
@@ -46,7 +46,7 @@ describe('finish middleware', () => {
46
46
  eventHandler();
47
47
  assert.equal(expected, 'works!');
48
48
  });
49
- it('health', () => {
49
+ it('health success', () => {
50
50
  let expected = '';
51
51
  let eventHandler = () => { };
52
52
  const request = { originalUrl: '/health' };
@@ -68,4 +68,26 @@ describe('finish middleware', () => {
68
68
  eventHandler();
69
69
  assert.equal(expected, '');
70
70
  });
71
+ it('health failure', () => {
72
+ let expected = '';
73
+ let eventHandler = () => { };
74
+ const request = { originalUrl: '/health' };
75
+ const response = {
76
+ on: (_event, func) => {
77
+ eventHandler = func;
78
+ },
79
+ locals: {
80
+ requestStartTime: 0n,
81
+ logger: {
82
+ error: (_message) => {
83
+ expected = 'works!';
84
+ },
85
+ },
86
+ },
87
+ statusCode: 500,
88
+ };
89
+ onFinish(request, response, () => { });
90
+ eventHandler();
91
+ assert.equal(expected, 'works!');
92
+ });
71
93
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import onHealth from '../../src/middlewares/health.js';
4
+ describe('health middleware', () => {
5
+ it('on GET /health returns 200', () => {
6
+ const request = { originalUrl: '/' };
7
+ let actualStatus;
8
+ let actualBody;
9
+ const response = {
10
+ status: (status) => {
11
+ actualStatus = status;
12
+ return {
13
+ json: (body) => {
14
+ actualBody = body;
15
+ },
16
+ };
17
+ },
18
+ };
19
+ onHealth(request, response);
20
+ assert.deepEqual(actualStatus, 200);
21
+ assert.deepEqual(actualBody, {});
22
+ });
23
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unito/integration-sdk",
3
- "version": "2.1.3",
3
+ "version": "2.2.1",
4
4
  "description": "Integration SDK",
5
5
  "type": "module",
6
6
  "types": "dist/src/index.d.ts",
@@ -16,6 +16,7 @@ import searchMiddleware from './middlewares/search.js';
16
16
  import selectsMiddleware from './middlewares/selects.js';
17
17
  import relationsMiddleware from './middlewares/relations.js';
18
18
  import signalMiddleware from './middlewares/signal.js';
19
+ import healthMiddleware from './middlewares/health.js';
19
20
 
20
21
  function printErrorMessage(message: string) {
21
22
  console.error();
@@ -52,7 +53,7 @@ export default class Integration {
52
53
  * @param options The {@link Options} to configure the Integration instance. Can be used to override the default port.
53
54
  */
54
55
  constructor(options: Options = {}) {
55
- this.port = options.port || 9200;
56
+ this.port = options.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 9200);
56
57
  this.handlers = [];
57
58
  }
58
59
 
@@ -147,6 +148,7 @@ export default class Integration {
147
148
 
148
149
  // Instantiate application middlewares. These can throw, so they have an implicit dependency on the internal
149
150
  // middlewares such as the logger, the correlationId, and the error handling.
151
+ app.get('/health', healthMiddleware);
150
152
  app.use(credentialsMiddleware);
151
153
  app.use(secretsMiddleware);
152
154
  app.use(filtersMiddleware);
@@ -14,39 +14,39 @@ declare global {
14
14
  }
15
15
 
16
16
  function onFinish(req: Request, res: Response, next: NextFunction) {
17
- if (req.originalUrl !== '/health') {
18
- res.on('finish', function () {
19
- const error = res.locals.error;
17
+ res.on('finish', function () {
18
+ const logger = res.locals.logger ?? new Logger();
19
+ const error = res.locals.error;
20
20
 
21
- const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
22
- const durationInMs = (durationInNs / 1_000_000) | 0;
21
+ const durationInNs = Number(process.hrtime.bigint() - res.locals.requestStartTime);
22
+ const durationInMs = (durationInNs / 1_000_000) | 0;
23
23
 
24
- const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
25
- const metadata: Metadata = {
26
- duration: durationInNs,
27
- // Use reserved and standard attributes of Datadog
28
- // https://app.datadoghq.com/logs/pipelines/standard-attributes
29
- http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
30
- ...(error
31
- ? {
32
- error: {
33
- kind: error.message,
34
- stack: (error.originalError?.details?.stack ?? error.details?.stack) as string,
35
- message: error.originalError?.message ?? error.message,
36
- },
37
- }
38
- : {}),
39
- };
24
+ const message = `${req.method} ${req.originalUrl} ${res.statusCode} - ${durationInMs} ms`;
25
+ const metadata: Metadata = {
26
+ duration: durationInNs,
27
+ // Use reserved and standard attributes of Datadog
28
+ // https://app.datadoghq.com/logs/pipelines/standard-attributes
29
+ http: { method: req.method, status_code: res.statusCode, url_details: { path: req.originalUrl } },
30
+ ...(error
31
+ ? {
32
+ error: {
33
+ kind: error.message,
34
+ stack: (error.originalError?.details?.stack ?? error.details?.stack) as string,
35
+ message: error.originalError?.message ?? error.message,
36
+ },
37
+ }
38
+ : {}),
39
+ };
40
40
 
41
- if ([404, 429].includes(res.statusCode)) {
42
- res.locals.logger.warn(message, metadata);
43
- } else if (res.statusCode >= 400) {
44
- res.locals.logger.error(message, metadata);
45
- } else {
46
- res.locals.logger.info(message, metadata);
47
- }
48
- });
49
- }
41
+ if ([404, 429].includes(res.statusCode)) {
42
+ logger.warn(message, metadata);
43
+ } else if (res.statusCode >= 400) {
44
+ logger.error(message, metadata);
45
+ } else if (req.originalUrl !== '/health') {
46
+ // Don't log successful health check requests
47
+ logger.info(message, metadata);
48
+ }
49
+ });
50
50
 
51
51
  next();
52
52
  }
@@ -0,0 +1,7 @@
1
+ import { Request, Response } from 'express';
2
+
3
+ function onHealthCheck(_req: Request, res: Response) {
4
+ res.status(200).json({});
5
+ }
6
+
7
+ export default onHealthCheck;
@@ -56,7 +56,7 @@ describe('finish middleware', () => {
56
56
  assert.equal(expected, 'works!');
57
57
  });
58
58
 
59
- it('health', () => {
59
+ it('health success', () => {
60
60
  let expected = '';
61
61
  let eventHandler: () => void = () => {};
62
62
 
@@ -81,4 +81,30 @@ describe('finish middleware', () => {
81
81
 
82
82
  assert.equal(expected, '');
83
83
  });
84
+
85
+ it('health failure', () => {
86
+ let expected = '';
87
+ let eventHandler: () => void = () => {};
88
+
89
+ const request = { originalUrl: '/health' } as express.Request;
90
+ const response = {
91
+ on: (_event, func: () => void) => {
92
+ eventHandler = func;
93
+ },
94
+ locals: {
95
+ requestStartTime: 0n,
96
+ logger: {
97
+ error: (_message: string) => {
98
+ expected = 'works!';
99
+ },
100
+ },
101
+ },
102
+ statusCode: 500,
103
+ } as express.Response;
104
+
105
+ onFinish(request, response, () => {});
106
+ eventHandler();
107
+
108
+ assert.equal(expected, 'works!');
109
+ });
84
110
  });
@@ -0,0 +1,31 @@
1
+ import { Request, Response } from 'express';
2
+ import assert from 'node:assert/strict';
3
+ import { describe, it } from 'node:test';
4
+
5
+ import onHealth from '../../src/middlewares/health.js';
6
+
7
+ describe('health middleware', () => {
8
+ it('on GET /health returns 200', () => {
9
+ const request = { originalUrl: '/' } as Request;
10
+
11
+ let actualStatus;
12
+ let actualBody;
13
+
14
+ const response = {
15
+ status: (status: number) => {
16
+ actualStatus = status;
17
+
18
+ return {
19
+ json: (body: object) => {
20
+ actualBody = body;
21
+ },
22
+ };
23
+ },
24
+ } as Response;
25
+
26
+ onHealth(request, response);
27
+
28
+ assert.deepEqual(actualStatus, 200);
29
+ assert.deepEqual(actualBody, {});
30
+ });
31
+ });