-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunction.js
More file actions
167 lines (135 loc) · 4.09 KB
/
function.js
File metadata and controls
167 lines (135 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//ENV
import dotenv from 'dotenv';
dotenv.config();
const { NODE_ENV = "" } = process.env;
if (!NODE_ENV) throw new Error("NODE_ENV is required");
const SERVICE_NAME = process.env.SERVICE_NAME || 'my-service';
//DEPS
import { http } from '@google-cloud/functions-framework';
import { uid, timer } from 'ak-tools';
import path from 'path';
import { tmpdir } from 'os';
//TEMP DIR
let TEMP_DIR;
if (NODE_ENV === 'dev') TEMP_DIR = './tmp';
else TEMP_DIR = tmpdir();
TEMP_DIR = path.resolve(TEMP_DIR);
//LOGGING
import winston from 'winston';
import { LoggingWinston } from '@google-cloud/logging-winston';
const loggingCloud = new LoggingWinston({ prefix: NODE_ENV, logName: SERVICE_NAME, level: 'info' });
const loggingLocal = new winston.transports.Console({ level: 'debug' });
const loggingFile = new winston.transports.File({ filename: './tests/app.log', level: 'debug' });
const loggingFormats = [
winston.format.timestamp(),
winston.format.errors({ stack: true }),
NODE_ENV === 'production' ? winston.format.json() : winston.format.prettyPrint(),
];
if (NODE_ENV !== 'production') loggingFormats.push(winston.format.colorize({ all: true }));
const logger = winston.createLogger({
format: winston.format.combine(...loggingFormats),
transports: []
});
if (NODE_ENV === 'production') logger.add(loggingCloud);
if (NODE_ENV !== 'production') logger.add(loggingLocal);
if (NODE_ENV === 'test') logger.add(loggingFile);
const createLogger = function (data) {
const { runId = null, traceId = null, ...rest } = data;
const result = logger.child({
trace: {
runId,
traceId,
service: SERVICE_NAME
},
...rest
});
return result;
};
/**
* params for the service
* @typedef {Object} Params
* @property {string} [foo] - Description of foo
* @typedef {Params & Record<string, any>} Param any other key value pair
*/
/**
* service routes
* @typedef {'/' | string} Endpoints
*/
// http entry point
http('entry', async (req, res) => {
const runId = uid();
const reqData = { url: req.url, method: req.method, headers: req.headers, body: req.body, query: req.query, runId };
const traceContext = req.header('X-Cloud-Trace-Context');
const traceId = traceContext && traceContext.includes('/') ? traceContext.split('/')[0] : null;
reqData.traceId = traceId;
const auth = req.headers?.authorization?.toString(); //todo: validate auth if necessary
delete reqData.headers.authorization;
const log = createLogger({ reqData });
let response = {};
let status = 200;
const t = timer('job');
t.start();
try {
/** @type {Endpoints} */
const path = req.path || '/';
const { method } = req;
/** @type {Params} */
const body = req.body || {};
//add query params to body allowing params to be passed in either body or query
for (const key in req.query || {}) {
const value = req.query[key];
let cleanValue;
if (value === 'true') cleanValue = true;
else if (value === 'false') cleanValue = false;
else if (value === 'null') cleanValue = null;
else if (value === 'undefined') cleanValue = undefined;
else cleanValue = value;
body[key] = cleanValue;
}
log.info(`${req.path} START`);
//setup the job
const [job] = route(path);
const result = await job(body);
t.end();
const { delta, human } = t.report(false);
log.info(`${req.path} FINISH (${human})`, { duration: delta, status });
//finished
res.status(status);
response = result;
} catch (e) {
t.end();
const { delta, human } = t.report(false);
status = 500;
log.error(`${req.path} ERROR (${human})`, {
error: e.message,
stack: e.stack,
code: e.code || 'INTERNAL_ERROR',
duration: delta,
status,
request: reqData
});
response = {
error: {
message: e.message,
code: e.code || 'INTERNAL_ERROR'
}
};
res.status(status);
}
res.send(JSON.stringify(response));
});
async function pong(data) {
return Promise.resolve({ status: "ok", message: "service is alive", echo: data });
}
/**
* determine routes based on path in request
* @param {Endpoints} path
*/
function route(path) {
switch (path) {
case "/":
return [pong];
default:
throw new Error(`Invalid path: ${path}`);
}
}