cat-a-logs 2.0.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/.babelrc ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "presets": [
3
+ "@babel/preset-env",
4
+ [
5
+ "@babel/preset-react",
6
+ {
7
+ "runtime": "automatic" // allows use of JSX w/out import React from "react" in every component file - BMA
8
+ }
9
+ ]
10
+ ],
11
+ "plugins": ["@babel/plugin-transform-runtime"]
12
+ }
13
+
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # cat-a-log
2
+ This function will help you create AWS Embedded Metric Format Logs and publish them to AWS Cloudwatch. EMF formatting will allow for chosen metrics to be automatically visualized in graphs for simplier debugging.
3
+
4
+ **What is Embedded Metric Formatting (EMF):**\
5
+ This is a JSON specification to communicate with Cloudwatch Logs to automatically extract values embedded in the structured log events.
6
+ EMF is especially great for applications that make logs and need custom metrics without more complexity or cost.
7
+ For more information please visit the link below
8
+
9
+ <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html" target="_blank">AWS Documentation on EMF Formatting</a>
10
+
11
+
12
+ **Why use cat-a-log?:**\
13
+ Why use a washing machine when you can do them by hand? Becuase it makes your job way easier! Leveraging AWS Lambda Powertools we can use the cat-a-log function to invoke and format logs into AWS Embedded Metric Format. By publishing these logs to AWS Cloudwatch, we are able to provide engineers with automatic metric visulaization to make the process of debugging logs much more efficient. Cat-a-log utilizies a cache to make effcient work of sending logs to Cloudwatch.
14
+
15
+
16
+ **How do I use cat-a-log?:**\
17
+ npm install our package using the command `npm install cat-a-logs` into your app.mjs file then capture the function and cache (we will talk about this later)
18
+ `import {cache, catalog} from "cat-a-logs/index.js";`
19
+
20
+ The catalog function below takes in the following arguments - lets look at each argument one at a time:
21
+
22
+ `function catalog(
23
+ trackedVariable: number | Array<number>,
24
+ metricName: string,
25
+ metricNamespace: string,
26
+ metricUnitLabel: string = "None",
27
+ CustomerDefinedDimension: { [key: string]: string } = {},
28
+ resolution: 1 | 60 = 60,
29
+ deploy: boolean = false
30
+ )`
31
+
32
+ - **trackedVariable**: This is a number that is dynamic and can change with each call - these numbers are reflected
33
+ - **metricName**:
34
+ - **metricNamespace**:
35
+ - **metricUnitLabel**: Can only be the following labels:
36
+ - Seconds | Microseconds | Milliseconds | Bytes | Kilobytes | Megabytes | Gigabytes | Terabytes | Bits | Kilobits | Megabits | Gigabits | Terabits | Percent | Count | Bytes/Second | Kilobytes/Second | Megabytes/Second | Gigabytes/Second | Terabytes/Second | Bits/Second | Kilobits/Second | Megabits/Second | Gigabits/Second | Terabits/Second | Count/Second | None\
37
+
38
+ To read more about Metric Datum see this <a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html" target="_blank">link</a>
39
+ - **CustomerDefinedDimension**:
40
+ - **resolution**:
41
+ - **deploy**:
42
+
43
+ **Structure of the files:**\
44
+ <a href="https://www.npmjs.com/package/cat-a-logs?activeTab=readme" target="_blank">Link to npm package</a>
45
+
46
+ `index.ts` is compiled to `index.js`. Important to compile `.ts` file to es6 js syntax using the `tsc —target es6 (filepath)` command
47
+ `app.mjs ` is a "pathway" to our lambda function. Here is where we will import catalog function and use it to involke our lambda function
48
+
49
+ index.ts lines 25-30 is checking to see if the value "level" || "message" || "sampling_rate" || "service" || "timestamp" ||"xray_trace_id"
50
+
51
+ logger.info gives you some information level is key and value is info
52
+
53
+
54
+ if you write name that it will overwrite the keys
55
+
56
+ **Tech Challenges**
57
+ Spent 3 days dealing with inconsistencies of ES6/CommonJS in our code before compiling .js in ES6
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node;
2
+ //Above (UNIX - shebang) used to indicate this is an executable file that will use node as its interpreter
3
+
4
+
package/client/App.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";
package/client/App.tsx ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ /* @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities; */
@@ -0,0 +1,261 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Logger } from "@aws-lambda-powertools/logger";
11
+ import { Ajv } from "ajv";
12
+ //cache entries are structured thusly: 'Namespace + Dimensions(Alphabetically)': EMFObject
13
+ const cache = {};
14
+ //catalog(kilos, "kilos" , "lambda-function-metrics", "Kilograms", {'functionVersion': $LATEST, 'testDimension': derp});
15
+ function catalog(trackedVariable_1, metricName_1, metricNamespace_1) {
16
+ return __awaiter(this, arguments, void 0, function* (trackedVariable, metricName, metricNamespace, metricUnitLabel = "None", CustomerDefinedDimension = {}, resolution = 60, deploy = false) {
17
+ //Check for any errors & validate inputs based on documentations
18
+ if (!cache)
19
+ throw new Error("cache is not found, please import cache from cat-a-log");
20
+ console.log(Object.keys(CustomerDefinedDimension).concat([metricName.toLowerCase()]));
21
+ const badKeys = ["level", "message", "sampling_rate", "service", "timestamp", "xray_trace_id"];
22
+ const yourKeys = Object.keys(CustomerDefinedDimension).concat([metricName.toLowerCase()]);
23
+ for (let i = 0; i < yourKeys.length; i++) {
24
+ if (badKeys.includes(yourKeys[i])) {
25
+ throw new Error("metricName, or Dimension names cannot be the same as these native logger keys: level || message || sampling_rate || service || timestamp || xray_trace_id");
26
+ }
27
+ }
28
+ if (Array.isArray(trackedVariable)) {
29
+ if (trackedVariable.length > 100)
30
+ throw new Error("metric value cannot have more than 100 elements");
31
+ }
32
+ if (Object.keys(CustomerDefinedDimension).length > 30) {
33
+ throw new Error("EMF has a limit of 30 user defined dimension keys per log");
34
+ }
35
+ const logger = new Logger({ serviceName: "serverlessAirline" });
36
+ // Ajv instance
37
+ const ajv = new Ajv();
38
+ // from AWS: EMF schema to test/validate against
39
+ const emfSchema = {
40
+ type: "object",
41
+ title: "Root Node",
42
+ required: ["_aws"],
43
+ properties: {
44
+ _aws: {
45
+ $id: "#/properties/_aws",
46
+ type: "object",
47
+ title: "Metadata",
48
+ required: ["Timestamp", "CloudWatchMetrics"],
49
+ properties: {
50
+ Timestamp: {
51
+ $id: "#/properties/_aws/properties/Timestamp",
52
+ type: "integer",
53
+ title: "The Timestamp Schema",
54
+ examples: [1565375354953],
55
+ },
56
+ CloudWatchMetrics: {
57
+ $id: "#/properties/_aws/properties/CloudWatchMetrics",
58
+ type: "array",
59
+ title: "MetricDirectives",
60
+ items: {
61
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items",
62
+ type: "object",
63
+ title: "MetricDirective",
64
+ required: ["Namespace", "Dimensions", "Metrics"],
65
+ properties: {
66
+ Namespace: {
67
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Namespace",
68
+ type: "string",
69
+ title: "CloudWatch Metrics Namespace",
70
+ examples: ["MyApp"],
71
+ pattern: "^(.*)$",
72
+ minLength: 1,
73
+ maxLength: 1024,
74
+ },
75
+ Dimensions: {
76
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions",
77
+ type: "array",
78
+ title: "The Dimensions Schema",
79
+ minItems: 1,
80
+ items: {
81
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions/items",
82
+ type: "array",
83
+ title: "DimensionSet",
84
+ minItems: 0,
85
+ maxItems: 30,
86
+ items: {
87
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions/items/items",
88
+ type: "string",
89
+ title: "DimensionReference",
90
+ examples: ["Operation"],
91
+ pattern: "^(.*)$",
92
+ minLength: 1,
93
+ maxLength: 250,
94
+ },
95
+ },
96
+ },
97
+ Metrics: {
98
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics",
99
+ type: "array",
100
+ title: "MetricDefinitions",
101
+ items: {
102
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items",
103
+ type: "object",
104
+ title: "MetricDefinition",
105
+ required: ["Name"],
106
+ properties: {
107
+ Name: {
108
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/Name",
109
+ type: "string",
110
+ title: "MetricName",
111
+ examples: ["ProcessingLatency"],
112
+ pattern: "^(.*)$",
113
+ minLength: 1,
114
+ maxLength: 1024,
115
+ },
116
+ Unit: {
117
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/Unit",
118
+ type: "string",
119
+ title: "MetricUnit",
120
+ examples: ["Milliseconds"],
121
+ pattern: "^(Seconds|Microseconds|Milliseconds|Bytes|Kilobytes|Megabytes|Gigabytes|Terabytes|Bits|Kilobits|Megabits|Gigabits|Terabits|Percent|Count|Bytes\\/Second|Kilobytes\\/Second|Megabytes\\/Second|Gigabytes\\/Second|Terabytes\\/Second|Bits\\/Second|Kilobits\\/Second|Megabits\\/Second|Gigabits\\/Second|Terabits\\/Second|Count\\/Second|None)$",
122
+ },
123
+ StorageResolution: {
124
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/StorageResolution",
125
+ type: "integer",
126
+ title: "StorageResolution",
127
+ examples: [60],
128
+ },
129
+ },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ },
137
+ },
138
+ };
139
+ const validateEmf = ajv.compile(emfSchema);
140
+ //sort customerDimensions key values in alphabetical order
141
+ const sortedDimensions = {};
142
+ for (let i = 0; i < Object.keys(CustomerDefinedDimension).sort().length; i++) {
143
+ sortedDimensions[Object.keys(CustomerDefinedDimension).sort()[i]] =
144
+ CustomerDefinedDimension[Object.keys(CustomerDefinedDimension).sort()[i]];
145
+ }
146
+ //if Object with Namespace and Dimensions already exists in Set
147
+ let check = cache[`${metricNamespace}${sortedDimensions}`];
148
+ if (check != undefined) {
149
+ //push the metrics object to Metrics array
150
+ cache[`${metricNamespace}${sortedDimensions}`]["_aws"]["CloudWatchMetrics"][0]["Metrics"].push({
151
+ Name: metricName,
152
+ Unit: metricUnitLabel,
153
+ StorageResolution: resolution,
154
+ });
155
+ //add key value to Log
156
+ check[`${metricName}`] = trackedVariable;
157
+ }
158
+ else {
159
+ // //create new Structured Log and add it to cachedStructuredLogs - BMA 1/18/25 removed to test Ajv
160
+ // cache[`${metricNamespace}${sortedDimensions}`] = Object.assign(
161
+ // NameSpace & Dimensions for EMF part don't exist yet. Initialize variable to capture EMF/aws key:value pair
162
+ const newEmfLog = Object.assign({
163
+ _aws: {
164
+ Timestamp: Date.now(),
165
+ CloudWatchMetrics: [
166
+ {
167
+ Namespace: metricNamespace,
168
+ Dimensions: [Object.keys(sortedDimensions)],
169
+ Metrics: [
170
+ {
171
+ Name: metricName,
172
+ Unit: metricUnitLabel,
173
+ StorageResolution: resolution,
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ [`${metricName}`]: trackedVariable,
180
+ }, CustomerDefinedDimension);
181
+ // Log the Unit value before validation
182
+ console.log("index.ts - Unit value before validation: ", newEmfLog._aws.CloudWatchMetrics[0].Metrics[0].Unit);
183
+ // validate the new EMF JSON schema against AWS EMF JSON schema before adding to cache object
184
+ const isValid = validateEmf(newEmfLog);
185
+ // //troubleshooting console.error in test
186
+ // console.log('index.ts - Validation result: ', isValid);
187
+ // troubleshooting console.error in emf test
188
+ // console.log('index.ts - Validation errors: ', validateEmf.errors);
189
+ // if it fails validation throw error
190
+ if (!isValid) {
191
+ console.error("An error occurred during EMF validation: ", validateEmf.errors);
192
+ throw new Error("Supplied/Proposed structured log does not comply with EMF schema");
193
+ }
194
+ // If it passes then add to cache object
195
+ cache[`${metricNamespace}${sortedDimensions}`] = newEmfLog;
196
+ }
197
+ if (deploy) {
198
+ //after last catalog function is invoked, send all cached logs with logger at once
199
+ for (let i = 0; i < Object.keys(cache).length; i++) {
200
+ logger.info(`Your EMF compliant Structured Metrics Log ${i + 1}`, cache[Object.keys(cache)[i]]);
201
+ }
202
+ //clear cache
203
+ console.log("BEFORE:", cache);
204
+ for (var member in cache)
205
+ delete cache[member];
206
+ console.log("AFTER:", cache);
207
+ }
208
+ });
209
+ }
210
+ export { cache, catalog };
211
+ /*Current Working logger invocation
212
+ logger.info("Your EMF compliant Structured Metrics Log",
213
+ Object.assign({
214
+ _aws: {
215
+ Timestamp: Date.now(),
216
+ CloudWatchMetrics: [
217
+ {
218
+ Namespace: metricNamespace,
219
+ Dimensions: [Object.keys(CustomerDefinedDimension)],
220
+ Metrics: [
221
+ {
222
+ Name: metricName,
223
+ Unit: metricUnitLabel,
224
+ StorageResolution: resolution,
225
+ }
226
+ ]
227
+ }
228
+ ]
229
+ },
230
+ [`${metricName}`]: trackedVariable,
231
+ },
232
+ CustomerDefinedDimension
233
+ )
234
+
235
+ )
236
+ */
237
+ //Old handler function
238
+ // export const handler = async (_event, _context): Promise<void> => {
239
+ // const testObj = {
240
+ // testguy: "hi",
241
+ // fool: 42,
242
+ // functionVersion: "$LATEST",
243
+ // _aws: {
244
+ // Timestamp: Date.now(),
245
+ // CloudWatchMetrics: [
246
+ // {
247
+ // Namespace: "lambda-function-metrics",
248
+ // Dimensions: [["functionVersion"]],
249
+ // Metrics: [
250
+ // {
251
+ // Name: "fool",
252
+ // Unit: "Milliseconds",
253
+ // StorageResolution: 60
254
+ // }
255
+ // ]
256
+ // }
257
+ // ]
258
+ // },
259
+ // }
260
+ // logger.info(JSON.stringify(testObj));
261
+ // };
@@ -0,0 +1,291 @@
1
+ import { Logger } from "@aws-lambda-powertools/logger";
2
+ import { Ajv } from "ajv";
3
+
4
+ //cache entries are structured thusly: 'Namespace + Dimensions(Alphabetically)': EMFObject
5
+ const cache: { [key: string]: any } = {};
6
+ //catalog(kilos, "kilos" , "lambda-function-metrics", "Kilograms", {'functionVersion': $LATEST, 'testDimension': derp});
7
+ async function catalog(
8
+ trackedVariable: number | Array<number>,
9
+ metricName: string,
10
+ metricNamespace: string,
11
+ metricUnitLabel: string = "None",
12
+ CustomerDefinedDimension: { [key: string]: string } = {},
13
+ resolution: 1 | 60 = 60,
14
+ deploy: boolean = false
15
+ ): Promise<void> {
16
+ //Check for any errors & validate inputs based on documentations
17
+ if (!cache)
18
+ throw new Error("cache is not found, please import cache from cat-a-log");
19
+ console.log(Object.keys(CustomerDefinedDimension).concat([metricName.toLowerCase()]));
20
+ const badKeys = ["level", "message", "sampling_rate", "service", "timestamp", "xray_trace_id"];
21
+ const yourKeys = Object.keys(CustomerDefinedDimension).concat([metricName.toLowerCase()]);
22
+ for(let i = 0; i < yourKeys.length; i++){
23
+ if(badKeys.includes(yourKeys[i])){
24
+ throw new Error(
25
+ "metricName, or Dimension names cannot be the same as these native logger keys: level || message || sampling_rate || service || timestamp || xray_trace_id"
26
+ );
27
+ }
28
+ }
29
+
30
+
31
+
32
+ if (Array.isArray(trackedVariable)) {
33
+ if (trackedVariable.length > 100)
34
+ throw new Error("metric value cannot have more than 100 elements");
35
+ }
36
+ if (Object.keys(CustomerDefinedDimension).length > 30) {
37
+ throw new Error(
38
+ "EMF has a limit of 30 user defined dimension keys per log"
39
+ );
40
+ }
41
+ const logger = new Logger({ serviceName: "serverlessAirline" });
42
+ // Ajv instance
43
+ const ajv = new Ajv();
44
+ // from AWS: EMF schema to test/validate against
45
+ const emfSchema = {
46
+ type: "object",
47
+ title: "Root Node",
48
+ required: ["_aws"],
49
+ properties: {
50
+ _aws: {
51
+ $id: "#/properties/_aws",
52
+ type: "object",
53
+ title: "Metadata",
54
+ required: ["Timestamp", "CloudWatchMetrics"],
55
+ properties: {
56
+ Timestamp: {
57
+ $id: "#/properties/_aws/properties/Timestamp",
58
+ type: "integer",
59
+ title: "The Timestamp Schema",
60
+ examples: [1565375354953],
61
+ },
62
+ CloudWatchMetrics: {
63
+ $id: "#/properties/_aws/properties/CloudWatchMetrics",
64
+ type: "array",
65
+ title: "MetricDirectives",
66
+ items: {
67
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items",
68
+ type: "object",
69
+ title: "MetricDirective",
70
+ required: ["Namespace", "Dimensions", "Metrics"],
71
+ properties: {
72
+ Namespace: {
73
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Namespace",
74
+ type: "string",
75
+ title: "CloudWatch Metrics Namespace",
76
+ examples: ["MyApp"],
77
+ pattern: "^(.*)$",
78
+ minLength: 1,
79
+ maxLength: 1024,
80
+ },
81
+ Dimensions: {
82
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions",
83
+ type: "array",
84
+ title: "The Dimensions Schema",
85
+ minItems: 1,
86
+ items: {
87
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions/items",
88
+ type: "array",
89
+ title: "DimensionSet",
90
+ minItems: 0,
91
+ maxItems: 30,
92
+ items: {
93
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Dimensions/items/items",
94
+ type: "string",
95
+ title: "DimensionReference",
96
+ examples: ["Operation"],
97
+ pattern: "^(.*)$",
98
+ minLength: 1,
99
+ maxLength: 250,
100
+ },
101
+ },
102
+ },
103
+ Metrics: {
104
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics",
105
+ type: "array",
106
+ title: "MetricDefinitions",
107
+ items: {
108
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items",
109
+ type: "object",
110
+ title: "MetricDefinition",
111
+ required: ["Name"],
112
+ properties: {
113
+ Name: {
114
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/Name",
115
+ type: "string",
116
+ title: "MetricName",
117
+ examples: ["ProcessingLatency"],
118
+ pattern: "^(.*)$",
119
+ minLength: 1,
120
+ maxLength: 1024,
121
+ },
122
+ Unit: {
123
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/Unit",
124
+ type: "string",
125
+ title: "MetricUnit",
126
+ examples: ["Milliseconds"],
127
+ pattern:
128
+ "^(Seconds|Microseconds|Milliseconds|Bytes|Kilobytes|Megabytes|Gigabytes|Terabytes|Bits|Kilobits|Megabits|Gigabits|Terabits|Percent|Count|Bytes\\/Second|Kilobytes\\/Second|Megabytes\\/Second|Gigabytes\\/Second|Terabytes\\/Second|Bits\\/Second|Kilobits\\/Second|Megabits\\/Second|Gigabits\\/Second|Terabits\\/Second|Count\\/Second|None)$",
129
+ },
130
+ StorageResolution: {
131
+ $id: "#/properties/_aws/properties/CloudWatchMetrics/items/properties/Metrics/items/properties/StorageResolution",
132
+ type: "integer",
133
+ title: "StorageResolution",
134
+ examples: [60],
135
+ },
136
+ },
137
+ },
138
+ },
139
+ },
140
+ },
141
+ },
142
+ },
143
+ },
144
+ },
145
+ };
146
+
147
+ const validateEmf = ajv.compile(emfSchema);
148
+ //sort customerDimensions key values in alphabetical order
149
+ const sortedDimensions: { [key: string]: string } = {};
150
+ for (
151
+ let i = 0;
152
+ i < Object.keys(CustomerDefinedDimension).sort().length;
153
+ i++
154
+ ) {
155
+ sortedDimensions[Object.keys(CustomerDefinedDimension).sort()[i]] =
156
+ CustomerDefinedDimension[Object.keys(CustomerDefinedDimension).sort()[i]];
157
+ }
158
+ //if Object with Namespace and Dimensions already exists in Set
159
+ let check = cache[`${metricNamespace}${sortedDimensions}`];
160
+ if (check != undefined) {
161
+ //push the metrics object to Metrics array
162
+ cache[`${metricNamespace}${sortedDimensions}`]["_aws"][
163
+ "CloudWatchMetrics"
164
+ ][0]["Metrics"].push({
165
+ Name: metricName,
166
+ Unit: metricUnitLabel,
167
+ StorageResolution: resolution,
168
+ });
169
+ //add key value to Log
170
+ check[`${metricName}`] = trackedVariable;
171
+ } else {
172
+ // //create new Structured Log and add it to cachedStructuredLogs - BMA 1/18/25 removed to test Ajv
173
+ // cache[`${metricNamespace}${sortedDimensions}`] = Object.assign(
174
+ // NameSpace & Dimensions for EMF part don't exist yet. Initialize variable to capture EMF/aws key:value pair
175
+ const newEmfLog = Object.assign(
176
+ {
177
+ _aws: {
178
+ Timestamp: Date.now(),
179
+ CloudWatchMetrics: [
180
+ {
181
+ Namespace: metricNamespace,
182
+ Dimensions: [Object.keys(sortedDimensions)],
183
+ Metrics: [
184
+ {
185
+ Name: metricName,
186
+ Unit: metricUnitLabel,
187
+ StorageResolution: resolution,
188
+ },
189
+ ],
190
+ },
191
+ ],
192
+ },
193
+ [`${metricName}`]: trackedVariable,
194
+ },
195
+ CustomerDefinedDimension
196
+ );
197
+
198
+ // Log the Unit value before validation
199
+ console.log(
200
+ "index.ts - Unit value before validation: ",
201
+ newEmfLog._aws.CloudWatchMetrics[0].Metrics[0].Unit
202
+ );
203
+
204
+ // validate the new EMF JSON schema against AWS EMF JSON schema before adding to cache object
205
+ const isValid = validateEmf(newEmfLog);
206
+ // //troubleshooting console.error in test
207
+ // console.log('index.ts - Validation result: ', isValid);
208
+ // troubleshooting console.error in emf test
209
+ // console.log('index.ts - Validation errors: ', validateEmf.errors);
210
+ // if it fails validation throw error
211
+ if (!isValid) {
212
+ console.error(
213
+ "An error occurred during EMF validation: ",
214
+ validateEmf.errors
215
+ );
216
+ throw new Error(
217
+ "Supplied/Proposed structured log does not comply with EMF schema"
218
+ );
219
+ }
220
+ // If it passes then add to cache object
221
+ cache[`${metricNamespace}${sortedDimensions}`] = newEmfLog;
222
+ }
223
+
224
+ if (deploy) {
225
+ //after last catalog function is invoked, send all cached logs with logger at once
226
+ for (let i = 0; i < Object.keys(cache).length; i++) {
227
+ logger.info(
228
+ `Your EMF compliant Structured Metrics Log ${i + 1}`,
229
+ cache[Object.keys(cache)[i]]
230
+ );
231
+ }
232
+ //clear cache
233
+ console.log("BEFORE:", cache);
234
+ for (var member in cache) delete cache[member];
235
+ console.log("AFTER:", cache);
236
+ }
237
+ }
238
+ export { cache, catalog };
239
+
240
+ /*Current Working logger invocation
241
+ logger.info("Your EMF compliant Structured Metrics Log",
242
+ Object.assign({
243
+ _aws: {
244
+ Timestamp: Date.now(),
245
+ CloudWatchMetrics: [
246
+ {
247
+ Namespace: metricNamespace,
248
+ Dimensions: [Object.keys(CustomerDefinedDimension)],
249
+ Metrics: [
250
+ {
251
+ Name: metricName,
252
+ Unit: metricUnitLabel,
253
+ StorageResolution: resolution,
254
+ }
255
+ ]
256
+ }
257
+ ]
258
+ },
259
+ [`${metricName}`]: trackedVariable,
260
+ },
261
+ CustomerDefinedDimension
262
+ )
263
+
264
+ )
265
+ */
266
+
267
+ //Old handler function
268
+ // export const handler = async (_event, _context): Promise<void> => {
269
+ // const testObj = {
270
+ // testguy: "hi",
271
+ // fool: 42,
272
+ // functionVersion: "$LATEST",
273
+ // _aws: {
274
+ // Timestamp: Date.now(),
275
+ // CloudWatchMetrics: [
276
+ // {
277
+ // Namespace: "lambda-function-metrics",
278
+ // Dimensions: [["functionVersion"]],
279
+ // Metrics: [
280
+ // {
281
+ // Name: "fool",
282
+ // Unit: "Milliseconds",
283
+ // StorageResolution: 60
284
+ // }
285
+ // ]
286
+ // }
287
+ // ]
288
+ // },
289
+ // }
290
+ // logger.info(JSON.stringify(testObj));
291
+ // };
@@ -0,0 +1,18 @@
1
+ <!-- Used by HTMLWebpackPlugin (per plugins config in webpack.config.js)
2
+ to build index.html with each webpack rebuild/rebundle -->
3
+
4
+ <!DOCTYPE html>
5
+ <html>
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
9
+ <title>%= htmlWebpackPlugin.options.title %</title>
10
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
11
+ </head>
12
+ <body>
13
+ <h1 class="text-4xl text-blue-700">My Webpack + Tailwind App</h1>
14
+ <script src="dist/bundle.js"></script>
15
+ <!-- Insert React App here -->
16
+ <div id="root"></div>
17
+ </body>
18
+ </html>