A few utilities to standardise certain code blocks within the team. utilities include:
- Google Cloud Trace Instancing
- Google Stackdriver Logger enhanced by Bunyan and Google Error Reporting
- Firebase instancing and general CRUD Commands
- MySQL instancing and general CRUD Commands
- Centralised Error Handling in Express (via middleware with manual override: i.e. in routes)
Using npm:
# install
$ npm install gcp-cloud-utilities
#update
$ npm i gcp-cloud-utilities@latest
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { Tracer } = require("gcp-cloud-utilities");
let tracerInstance = new Tracer({
projectId: "YOUR-GOOGLE-PROJECT-ID",
keyPath: "PATH/TO/SERVICE-ACCOUNT.JSON",
plugins: {
http: true, // boolean: to include in constructor of NodeTracerProvider
https: true,
express: false,
mysql: false,
grpc: false,
dns: false,
graphql: false,
},
});
let tracer = tracerInstance.createTracer();
let api = tracerInstance.getApi();
This will forward 'traceparent' in header to subsequent services/projects
const axios = require("axios");
function clientDemoRequest() {
console.log("Starting client demo request");
const span = tracer.startSpan("clientDemoRequest()", {
parent: tracer.getCurrentSpan(),
kind: api.SpanKind.SERVER,
});
tracer.withSpan(span, async () => {
span.setAttribute("prop", "value");
span.setAttributes({
prop1: "value",
prop2: "value",
});
span.addEvent("sending request");
await axios.get("request/url").then((results) => {});
span.setStatus({ code: api.CanonicalCode.OK });
span.end();
// The process must remain alive for the duration of the exporter flush
// timeout or spans might be dropped
console.log("Client request complete, waiting to ensure spans flushed...");
setTimeout(() => {
console.log("Done 🎉");
}, 2000);
});
}
clientDemoRequest();
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { Logger } = require("gcp-cloud-utilities");
let loggerInstance = new Logger({
projectId: "YOUR-GOOGLE-PROJECT-ID",
keyPath: "PATH/TO/SERVICE-ACCOUNT.JSON",
serviceName: "default",
projectVersion: "1.0.8",
});
const constructorOptions = {
level: "info",
// add more option according to API reference
};
let logger = loggerInstance.createLogger(constructorOptions); // look at https://www.npmjs.com/package/bunyan#levels for log levels
let reporter = loggerInstance.createReporter();
logger.error(error); // submits error to Stackdriver
reporter.report(error); // reports error to Error Reporting
logger.error(new Error(error)); // submits error to both Stackdriver and Error Reporting
let labelObject = {
key: key,
clientId: clientId,
tripId: tripId,
originId: originId,
destinationId: destinationId,
};
// If you wish to view log entries inline with trace spans in the Stackdriver Trace Viewer. This means log entry shows in Cloud Logging, Error Reporting and Cloud Trace
logger.error(
await loggerInstance.getLoggerKey(
tracer.getCurrentSpan().spanContext.traceId,
labelObject
),
new Error("Error now logged inline with trace span")
);
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { MySql } = require("gcp-cloud-utilities");
// All connection configurations from the mysql repo is available
const connection = new MySql({
host: "localhost",
user: "root",
database: "accomodation",
password: "",
connectionLimit: 5,
});
// Example 1: Promised based execution
const mysql = connection.connect(true, true);
async function executeQuery() {
// simple query
await mysql
.query("SELECT * FROM `hp_accomodation` LIMIT 1")
.then(([results, fields]) => {
console.log(results);
})
.catch((error) => {});
console.log("finished");
}
executeQuery();
// Example 2: Promised based await queries
const mysql = connection.connect(true, true);
async function executeWithAwait() {
let results = await mysql.query("SELECT * FROM `hp_accomodation` LIMIT 1");
console.log(results);
console.log("finished");
}
executeWithAwait();
// Example 3: Promised based Pool Connections
const mysql = connection.pool(true);
console.log("created pool");
async function executeQueryInPool() {
await asyncForEach([1, 2], async (num) => {
let results = await mysql.query(`SELECT * FROM hp_accomodation LIMIT ?`, [
num,
]);
console.log(results);
});
console.log("finished");
mysql.end();
}
executeQueryInPool();
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
// Example 4: Async queries
const mysql = connection.connect(false, true);
async function executeWithAwait() {
mysql.query("SELECT * FROM `hp_accomodation`");
console.log(
"query was deployed and connection will automatically end() when finished"
);
}
executeWithAwait();
// Example 5: Pool based async queries in loop
const mysql = connection.pool(false);
console.log("created pool");
async function executeQueryInPool() {
await asyncForEach([1, 2], async (num) => {
mysql.query(`SELECT * FROM hp_accomodation LIMIT ?`, [num]);
console.log("query was deployed");
});
console.log("pool connection will close when becoming stale");
}
executeQueryInPool();
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
// With the above example, you can query the state of your deployed queries and close the pool on finalisation in order to preserve concurrency
// Remember, calling connection.pool(false) which equals createPool() from the repo, will establish a connection in itself
setTimeout(function () {
console.log(`All Connections ${mysql._allConnections.length}`);
console.log(`Acquiring Connections ${mysql._acquiringConnections.length}`);
console.log(`Free Connections ${mysql._freeConnections.length}`);
console.log(`Queue Connections ${mysql._connectionQueue.length}`);
}, 3000);
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { ErrorMiddleware } = require('gcp-cloud-utilities')
const apiName = require('./package.json').name;
const projectVersion = require('./package.json').version;
let errorMiddleware = new ErrorMiddleware(apiName, projectVersion);
// strictly to be used last in line before app.listin()
app.use((err, req, res, next) => {
errorMiddleware.errorResponse(err, req, res, next)
});
app.listen(process.env.PORT || 8080);
// -- automatically catch error in route and format it when passed to express error handler
const { catchAsync } = require('gcp-cloud-utilities')
app.use('/places', catchAsync(async (req, res, next) => {
new Error('Oeps, there is an error on this line...')
});
const { AppError } = require("gcp-cloud-utilities");
app.all("*", (req, res, next) => {
next(
new AppError(
`Can't find ${req.originalUrl} on this server!`, // Error Message
404 // specified error code, else will default to 500
)
);
});
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { TasksClient } = require('gcp-cloud-utilities');
// CLOOUDTASK CLIENT
const cloudTasksClient = new TasksClient({
context: tracer, // if needed
loggerInstance: loggerInstance // if needed
projectId: 'YOUR-GOOGLE-PROJECT-ID',
keyPath: 'PATH/TO/SERVICE-ACCOUNT.JSON',
});
// content of the task
const payload = {
prop: 'test-from-repo'
}
const time_in_seconds_from_now = (Math.round(new Date() / 1000)) + 300 // 5 minutes from now
cloudTasksClient.sendTask({
method: 'POST',
url: `https://url.com`,
body: payload,
queue: 'my-queue',
location: 'europe-west1'
spanName: 'custom span name', // specify if you want to execute underneath its own span
headers: {
key: 'value'
},
scheduleTime: time_in_seconds_from_now // specify if you want to schedule the task in the future (must be in seconds)
})
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { TasksClient, Workflow } = require('gcp-cloud-utilities');
let tracer = new Tracer({
projectId: 'YOUR-GOOGLE-PROJECT-ID',
keyPath: 'PATH/TO/SERVICE-ACCOUNT.JSON',
plugins: {
http: true, // boolean: to include in constructor of NodeTracerProvider
https: true,
express: false
}
}).createTracer()
var express = require("express");
var app = express();
app.post("/", async function(req, res, next) {
const workflow = new Workflow({
context: tracer, // tracer will be passed on to CloudTask construction (if needed)
loggerInstance: loggerInstance // if needed
projectId: 'YOUR-GOOGLE-PROJECT-ID',
keyPath: 'PATH/TO/SERVICE-ACCOUNT.JSON',
})
// content of the task
const payload = {
prop: 'test-from-repo'
}
const time_in_seconds_from_now = (Math.round(new Date() / 1000)) + 300 // 5 minutes from now
workflow.kickChampion([
{
service: 'http://example.com',
spanName: 'GET call 1', // specify if you want to execute underneath its own span
operation: {
method: 'GET',
body: payload, // in case of POST
queue: 'gcp-hotel-api',
location: 'europe-west1',
scheduleTime: time_in_seconds_from_now // specify if you want to schedule the task in the future (must be in seconds)
}
},
{
service: 'http://example.com',
spanName: 'GET call 2', // specify if you want to execute underneath its own span
operation: {
method: 'GET',
body: payload, // in case of POST
queue: 'gcp-hotel-api',
location: 'europe-west1'
}
}
])
res.send("done");
setInterval(function(){
console.log(workflow.getWorkflowQueue())
}, 5000);
});
var listener = app.listen(8080, function() {
console.log("Listening on port " + listener.address().port);
});
In order to gain the TypeScript typings (for intellisense / autocomplete) while using CommonJS imports with require()
use the following approach:
const { PubSubClient } = require("gcp-cloud-utilities");
let pubsub = new Tracer({
projectId: "YOUR-GOOGLE-PROJECT-ID",
keyPath: "PATH/TO/SERVICE-ACCOUNT.JSON",
}).init();
pubsub
.topic(topicName)
.publish(dataBuffer)
.then((messageId) => {
console.log(`Message ${messageId} published.`);
})
.catch((err) => {
console.error("ERROR:", err);
});