@uoa/lambda-tracing 1.0.0-beta.3 → 1.1.0

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/dist/uoaHttps.js CHANGED
@@ -22,6 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
25
34
  Object.defineProperty(exports, "__esModule", { value: true });
26
35
  const https = __importStar(require("https"));
27
36
  const api_1 = require("@opentelemetry/api");
@@ -35,17 +44,175 @@ function request(...args) {
35
44
  return https.request(args[0], args[1]);
36
45
  }
37
46
  }
38
- function get(...args) {
39
- if (args[2]) {
40
- api_1.propagation.inject(api_1.context.active(), args[1].headers);
41
- return https.get(args[0], args[1], args[2]);
42
- }
43
- else {
44
- api_1.propagation.inject(api_1.context.active(), args[0].headers);
45
- return https.get(args[0], args[1]);
46
- }
47
+ function doGetRequest(hostname, path, headers) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ return new Promise(function (resolve, reject) {
50
+ const options = {
51
+ "method": "GET",
52
+ "hostname": hostname,
53
+ "path": path,
54
+ "headers": headers
55
+ };
56
+ api_1.propagation.inject(api_1.context.active(), options.headers);
57
+ const req = https.request(options, function (response) {
58
+ const chunks = [];
59
+ response.on("data", function (chunk) {
60
+ chunks.push(chunk);
61
+ });
62
+ response.on("end", function () {
63
+ let body = Buffer.concat(chunks);
64
+ body = JSON.parse(body.toString());
65
+ resolve(body);
66
+ });
67
+ response.on("error", function (e) {
68
+ reject(e);
69
+ });
70
+ });
71
+ req.end();
72
+ });
73
+ });
74
+ }
75
+ function doPostRequest(hostname, path, headers, data) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ return new Promise(function (resolve, reject) {
78
+ const options = {
79
+ "method": "POST",
80
+ "hostname": hostname,
81
+ "path": path,
82
+ "headers": headers
83
+ };
84
+ api_1.propagation.inject(api_1.context.active(), options.headers);
85
+ const req = https.request(options, function (response) {
86
+ const chunks = [];
87
+ response.on("data", function (chunk) {
88
+ chunks.push(chunk);
89
+ });
90
+ response.on("end", function () {
91
+ if (response.statusCode == 204) {
92
+ //204 is no content, and most JSON parses blow up on an empty string
93
+ resolve(null);
94
+ }
95
+ else {
96
+ let body = Buffer.concat(chunks);
97
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
98
+ body = JSON.parse(body.toString());
99
+ }
100
+ else {
101
+ body = body.toString();
102
+ }
103
+ resolve(body);
104
+ }
105
+ });
106
+ response.on("error", function (e) {
107
+ reject(e);
108
+ });
109
+ });
110
+ if (data) {
111
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
112
+ //We serialize using JSON.stringify as a default, or if a JSON is specified
113
+ req.write(JSON.stringify(data));
114
+ }
115
+ else {
116
+ // If another content-type is specified however, we respect the serialization provided
117
+ req.write(data);
118
+ }
119
+ }
120
+ req.end();
121
+ });
122
+ });
123
+ }
124
+ function doPutRequest(hostname, path, headers, data) {
125
+ return __awaiter(this, void 0, void 0, function* () {
126
+ return new Promise(function (resolve, reject) {
127
+ const options = {
128
+ "method": "PUT",
129
+ "hostname": hostname,
130
+ "path": path,
131
+ "headers": headers
132
+ };
133
+ api_1.propagation.inject(api_1.context.active(), options.headers);
134
+ const req = https.request(options, function (response) {
135
+ const chunks = [];
136
+ response.on("data", function (chunk) {
137
+ chunks.push(chunk);
138
+ });
139
+ response.on("end", function () {
140
+ if (response.statusCode == 204) {
141
+ //204 is no content, and most JSON parses blow up on an empty string
142
+ resolve(null);
143
+ }
144
+ else {
145
+ let body = Buffer.concat(chunks);
146
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
147
+ body = JSON.parse(body.toString());
148
+ }
149
+ else {
150
+ body = body.toString();
151
+ }
152
+ resolve(body);
153
+ }
154
+ });
155
+ response.on("error", function (e) {
156
+ reject(e);
157
+ });
158
+ });
159
+ if (data) {
160
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
161
+ //We serialize using JSON.stringify as a default, or if a JSON is specified
162
+ req.write(JSON.stringify(data));
163
+ }
164
+ else {
165
+ // If another content-type is specified however, we respect the serialization provided
166
+ req.write(data);
167
+ }
168
+ }
169
+ req.end();
170
+ });
171
+ });
172
+ }
173
+ function doDeleteRequest(hostname, path, headers) {
174
+ return __awaiter(this, void 0, void 0, function* () {
175
+ return new Promise(function (resolve, reject) {
176
+ const options = {
177
+ "method": "DELETE",
178
+ "hostname": hostname,
179
+ "path": path,
180
+ "headers": headers
181
+ };
182
+ api_1.propagation.inject(api_1.context.active(), options.headers);
183
+ const req = https.request(options, function (response) {
184
+ const chunks = [];
185
+ response.on("data", function (chunk) {
186
+ chunks.push(chunk);
187
+ });
188
+ response.on("end", function () {
189
+ if (response.statusCode == 204) {
190
+ //204 is no content, and most JSON parses blow up on an empty string
191
+ resolve(null);
192
+ }
193
+ else {
194
+ let body = Buffer.concat(chunks);
195
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
196
+ body = JSON.parse(body.toString());
197
+ }
198
+ else {
199
+ body = body.toString();
200
+ }
201
+ resolve(body);
202
+ }
203
+ });
204
+ response.on("error", function (e) {
205
+ reject(e);
206
+ });
207
+ });
208
+ req.end();
209
+ });
210
+ });
47
211
  }
48
212
  module.exports = {
49
213
  request,
50
- get
214
+ doGetRequest,
215
+ doPostRequest,
216
+ doPutRequest,
217
+ doDeleteRequest
51
218
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uoa/lambda-tracing",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.1.0",
4
4
  "description": "Library for logging & distributed tracing in UoA Lambda projects",
5
5
  "repository": {
6
6
  "type": "git",
package/readme.md CHANGED
@@ -1,4 +1,128 @@
1
1
  ![npm (scoped)](https://img.shields.io/npm/v/@uoa/lambda-tracing)
2
2
  # UOA Lambda Tracing Library
3
+ This library contains functions to enable distributed tracing & logging in University of Auckland AWS Lambda projects.
4
+
5
+ ## Usage
6
+ ### Installation
7
+ Install the library using the command
8
+ ```
9
+ npm install @uoa/lambda-tracing
10
+ ```
11
+
12
+ ### Setup
13
+ In your src folder, create a new file `tracing-wrapper.ts`\
14
+ In this, add the following code
15
+ ```
16
+ import {initializeTracing} from "@uoa/lambda-tracing";
17
+
18
+ initializeTracing();
19
+ ```
20
+
21
+ In your lambda environment.yml file, add the following `NODE_OPTIONS` environment variable in the properties map
22
+ ```
23
+ lambda:
24
+ properties:
25
+ NODE_OPTIONS: --require src/tracing-wrapper
26
+ ```
27
+
28
+ The above ensures that the `tracing-wrapper.ts` code will be loaded before your lambda app starts, which will
29
+ perform the required setup for logging and distributed tracing.
30
+
31
+ ### Logging
32
+ Whenever you want to use the logging provided in this library, simply import the logger using
33
+ ```
34
+ const logger = require('@uoa/lambda-tracing/logging')(module);
35
+ ```
36
+ Note: `(module)` must be passed as a parameter to this import so that the logger knows where it has been called from.
37
+
38
+ Once the logger has been imported, you can log any info using
39
+ ```
40
+ logger.info('Hello Logger!');
41
+ ```
42
+
43
+ **Logging Levels**
44
+
45
+ At UoA, we have four logging levels available to use. Ordered by decreasing priority, these are:
46
+
47
+ |Level| Logger usage |
48
+ |-----|--------------------|
49
+ |ERROR|```logger.error()```|
50
+ |WARN |```logger.warn()``` |
51
+ |INFO |```logger.info()``` |
52
+ |DEBUG|```logger.debug()```|
53
+
54
+ By default, any log at the `INFO` level or higher will be logged. However, this can be changed by adding another
55
+ environment variable `loggingLevel` in the lambda properties map of the environment.yml file. The value of this
56
+ specifies the lowest level that will be logged.
57
+ ```
58
+ lambda:
59
+ properties:
60
+ NODE_OPTIONS: --require src/tracing-wrapper
61
+ loggingLevel: WARN
62
+ ```
63
+ ```
64
+ logger.debug('Will not be logged');
65
+ logger.info('Will not be logged');
66
+ logger.warn('Will be logged');
67
+ logger.error('Will be logged');
68
+ ```
69
+
70
+ **Logging format**
71
+
72
+ By default, logs will be produced in the following format:
73
+ ```
74
+ date [thread] level class - [[[traceId,spanId,info]]] message
75
+ ```
76
+
77
+ This can also be changed by adding another environment variable `loggingPattern` in the lambda properties map
78
+ of the environment.yml file.
79
+ ```
80
+ lambda:
81
+ properties:
82
+ NODE_OPTIONS: --require src/tracing-wrapper
83
+ loggingPattern: "%date - %message | %level | %class"
84
+ ```
85
+ Valid logging pattern placeholders are as follows:
86
+
87
+ |Placeholder|Replacement value|Example value|
88
+ |-----------|-----------------|-----|
89
+ |%date|Date time when the message is logged in ISO8601 format|2022-05-29T21:31:11.250Z|
90
+ |%thread|Unused, this is only present in the default pattern to match with UoA logging standards. Value will always be ```-```|-|
91
+ |%level|Log level|INFO|
92
+ |%class|The module which logged the message|src.handle-service|
93
+ |%traceId|Unique Id for the request|5c0c783c965a684842608f10422fdf2c|
94
+ |%spanId|Unique Id for the current lambda invocation|6a8b025511ac1191|
95
+ |%info|Extra information to help with filtering the logs|lambdaRequestId:dc2f1b60-96af-4869-9b75-036f9ddcdb33|
96
+ |%message|The logged message|Hello Logger!|
97
+
98
+ ### Distributed Tracing
99
+ There are two headers that can be passed in API calls to your lambda project using this library: ```X-B3-TraceId``` and ```X-B3-Info```.
100
+ If passed to a lambda project using this library, the values of %traceId and %info in the logging pattern will be the corresponding header values.
101
+
102
+ `X-B3-TraceId` should be a 32 character lowercase hex encoded string.\
103
+ If the passed traceId is less than 32
104
+ characters, it will be left padded with 0's to get to a length of 32.\
105
+ If this header is not passed, it will be randomly generated.
106
+
107
+ `X-B3-Info` can be any string value to provide extra information in your logs.\
108
+ If this header is not passed, it will be set as `lambdaRequestId:requestId` where `requestId` is the id of the current lambda invocation.
109
+
110
+ **Header Propagation**
111
+
112
+ To propagate the ```X-B3-TraceId``` and ```X-B3-Info``` headers to other APIs, you can import and use the uoaHttps module:
113
+ ```
114
+ const uoaHttps = require('@uoa/lambda-tracing/uoaHttps');
115
+ ```
116
+
117
+ The `uoaHttps` module exposes some functions to perform the primary HTTP operations. These will inject the tracing headers into requests before they are made:
118
+
119
+ ```
120
+ doGetRequest(hostname, path, headers): Promise
121
+ doPostRequest(hostname, path, headers, data): Promise
122
+ doPutRequest(hostname, path, headers, data): Promise
123
+ doDeleteRequest(hostname, path, headers): Promise
124
+ ```
125
+
126
+ There is also another function `request()` exposed in this module in case header propagation with operations other than the basic GET, POST, PUT, and DELETE are required.\
127
+ The usage of this is the same as the one provided by the Node https library (see specs [here](https://nodejs.org/api/https.html#httpsrequestoptions-callback)).
3
128
 
4
- This library contains functions to enable distributed tracing & logging in Auckland University AWS Lambda projects.
package/uoaHttps.ts CHANGED
@@ -16,19 +16,183 @@ function request(...args: any[]): http.ClientRequest {
16
16
  }
17
17
  }
18
18
 
19
- function get(options: RequestOptions | string | URL, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
20
- function get(url: string | URL, options: RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
21
- function get(...args: any[]): http.ClientRequest {
22
- if (args[2]) {
23
- propagation.inject(context.active(), args[1].headers);
24
- return https.get(args[0], args[1], args[2]);
25
- } else {
26
- propagation.inject(context.active(), args[0].headers);
27
- return https.get(args[0], args[1]);
28
- }
19
+ async function doGetRequest(hostname: string, path: string, headers: any) {
20
+ return new Promise(function (resolve, reject) {
21
+ const options = {
22
+ "method": "GET",
23
+ "hostname": hostname,
24
+ "path": path,
25
+ "headers": headers
26
+ }
27
+ propagation.inject(context.active(), options.headers);
28
+
29
+ const req = https.request(options, function (response) {
30
+ const chunks: any[] = [];
31
+
32
+ response.on("data", function (chunk) {
33
+ chunks.push(chunk);
34
+ });
35
+
36
+ response.on("end", function () {
37
+ let body = Buffer.concat(chunks);
38
+ body = JSON.parse(body.toString());
39
+ resolve(body);
40
+ });
41
+
42
+ response.on("error", function (e) {
43
+ reject(e);
44
+ })
45
+ });
46
+
47
+ req.end();
48
+ });
49
+ }
50
+
51
+ async function doPostRequest(hostname: string, path: string, headers: any, data: any) {
52
+ return new Promise(function (resolve, reject) {
53
+ const options = {
54
+ "method": "POST",
55
+ "hostname": hostname,
56
+ "path": path,
57
+ "headers": headers
58
+ }
59
+ propagation.inject(context.active(), options.headers);
60
+
61
+ const req = https.request(options, function (response) {
62
+ const chunks: any[] = [];
63
+
64
+ response.on("data", function (chunk) {
65
+ chunks.push(chunk);
66
+ });
67
+
68
+ response.on("end", function () {
69
+ if (response.statusCode == 204) {
70
+ //204 is no content, and most JSON parses blow up on an empty string
71
+ resolve(null);
72
+ } else {
73
+ let body: any = Buffer.concat(chunks);
74
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
75
+ body = JSON.parse(body.toString());
76
+ } else {
77
+ body = body.toString();
78
+ }
79
+ resolve(body);
80
+ }
81
+ });
82
+
83
+ response.on("error", function (e) {
84
+ reject(e);
85
+ })
86
+ });
87
+
88
+ if (data) {
89
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
90
+ //We serialize using JSON.stringify as a default, or if a JSON is specified
91
+ req.write(JSON.stringify(data));
92
+ } else {
93
+ // If another content-type is specified however, we respect the serialization provided
94
+ req.write(data);
95
+ }
96
+ }
97
+ req.end();
98
+ });
99
+ }
100
+
101
+ async function doPutRequest(hostname: string, path: string, headers: any, data: any) {
102
+ return new Promise(function (resolve, reject) {
103
+ const options = {
104
+ "method": "PUT",
105
+ "hostname": hostname,
106
+ "path": path,
107
+ "headers": headers
108
+ }
109
+ propagation.inject(context.active(), options.headers);
110
+
111
+ const req = https.request(options, function (response) {
112
+ const chunks: any[] = [];
113
+
114
+ response.on("data", function (chunk) {
115
+ chunks.push(chunk);
116
+ });
117
+
118
+ response.on("end", function () {
119
+ if (response.statusCode == 204) {
120
+ //204 is no content, and most JSON parses blow up on an empty string
121
+ resolve(null);
122
+ } else {
123
+ let body: any = Buffer.concat(chunks);
124
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
125
+ body = JSON.parse(body.toString());
126
+ } else {
127
+ body = body.toString();
128
+ }
129
+ resolve(body);
130
+ }
131
+ });
132
+
133
+ response.on("error", function (e) {
134
+ reject(e);
135
+ })
136
+ });
137
+
138
+ if (data) {
139
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
140
+ //We serialize using JSON.stringify as a default, or if a JSON is specified
141
+ req.write(JSON.stringify(data));
142
+ } else {
143
+ // If another content-type is specified however, we respect the serialization provided
144
+ req.write(data);
145
+ }
146
+ }
147
+ req.end();
148
+ });
149
+ }
150
+
151
+ async function doDeleteRequest(hostname: string, path: string, headers: any) {
152
+ return new Promise(function (resolve, reject) {
153
+ const options = {
154
+ "method": "DELETE",
155
+ "hostname": hostname,
156
+ "path": path,
157
+ "headers": headers
158
+ }
159
+ propagation.inject(context.active(), options.headers);
160
+
161
+ const req = https.request(options, function (response) {
162
+ const chunks: any[] = [];
163
+
164
+ response.on("data", function (chunk) {
165
+ chunks.push(chunk);
166
+ });
167
+
168
+ response.on("end", function () {
169
+ if (response.statusCode == 204) {
170
+ //204 is no content, and most JSON parses blow up on an empty string
171
+ resolve(null);
172
+ } else {
173
+ let body: any = Buffer.concat(chunks);
174
+ if (!('Content-Type' in options.headers) || options.headers['Content-Type'] === 'application/json') {
175
+ body = JSON.parse(body.toString());
176
+ } else {
177
+ body = body.toString();
178
+ }
179
+ resolve(body);
180
+ }
181
+ });
182
+
183
+ response.on("error", function (e) {
184
+ reject(e);
185
+ })
186
+ });
187
+
188
+ req.end();
189
+ });
29
190
  }
30
191
 
31
192
  module.exports = {
32
193
  request,
33
- get
194
+ doGetRequest,
195
+ doPostRequest,
196
+ doPutRequest,
197
+ doDeleteRequest
34
198
  }