Ir al contenido principal

Debugging Google Cloud Functions in Node

DISCLAIMER: This article focuses on Google Cloud Functions with HTTP signature, ie, those functions intended to be triggered by an HTTP request. In case of being interested in Cloud Functions being triggered by Cloud Storage, File Storage or Pub/Sub events, read Debugging Google Cloud Functions with event signature in Node.

Cloud Functions, the Function as a Service (FaaS) product of Google Cloud, is an attractive resource for those developing solutions in cloud environments. Similar in conception to AWS Lambdas or Azure Functions, they offer serverless processing at an affordable price.

From the developer point of view, they don't differ too much of a standard function contained in a program developed in Node, Python, Go or Java, except for debugging: Due to their ubiquitous nature debugging is not easy and most of the times is done following one of these two approaches:

  • Uploading a debug version, running it and then checking its output (logging and/or function output). This is the easiest way, though it comes with a bunch of associated problems: 
    • Just the last uploaded version of a function can be run, so there's not way of isolating the debugging version for testing purposes.
    • If the developer is working in a strict CI/CD environment, uploading a debugging version can imply creating a debug PR, merging and then deploying it... too tedious for just debugging.
  • Patching the code to allow local debugging. This option solves the above problems, but it implies debugging a code that is not that being uploaded, but a patched one. If patch is not properly removed chances are that the function that ran flawlessly in local got into problems when running in the Cloud.

Functions Framework

Google Functions Framework is a set of tools and libraries that help developers to, among other things, run and debug Cloud Functions locally.

Specifically for Node based Cloud Functions, to run your function locally you just need to:
  1. install the module @google-cloud/functions-framework and then 
  2. open a terminal and run the command functions-framework --source=<your javascript source base directory> --target=<your function name>

For instance, if you have created a Typescript helloWorld function, and tsc is storing the transpiled Javascript code in the ./dist directory (relative to the project path), you should run this command:

functions-framework --source=./dist --target=helloWorld

Note that, if functions-framework is not globally installed by npm, you should prefix the command with its path: 

./node_modules/@google-cloud/functions-framework \
--source=./dist --target=helloWorld

... or running it with npx:

npx @google-cloud/functions-framework \
--source=./dist --target=helloWorld

By running that command functions-framework will run your function in local. To allow sending data to the function, it will open an HTTP socket in the port 8080 of the loopback address (http://localhost:8080). So, following the above example, you could send data to your function running this curl command:

curl -X GET localhost:8080 \
-d '{"greet":"hello"}' -H 'content-type:application/json'

In that way you would send the JSON object {"greet":"hello"} to your function.

That explained allows running a Cloud Function locally, but how to debug it? Debugging can be done by combining node --inspect and the above functions-framework command in this way:

node --inspect \
functions-framework --source=./dist --target=helloWorld

Again, if functions-framework is not globally installed by npm, you should prefix the command with its path: 

node --inspect \
./node_modules/@google-cloud/functions-framework \
--source=./dist --target=helloWorld

Functions Framework and environmental variables

Google Functions Framework doesn't support natively the population of environmental variables in the function execution environment. However, it can be achieved by using third-party solutions, as env-cmd.

Once having env-cmd available in the project and the required env variables defined in the .env file with the format the documentation states, the above commands should be modified to invoke env-cmd in this way:

env-cmd node --inspect \
functions-framework --source=./dist --target=helloWorld
... or:

env-cmd node --inspect \
./node_modules/@google-cloud/functions-framework \
--source=./dist --target=helloWorld

Functions Framework and Visual Studio Code

That said can be implemented in a Visual Studio Code workspace so we can combine the local function run that functions-framework offers with the debugging capabilities of Visual Studio Code.

The gcfLocalDebug example shows a Typescript based helloWorld Google Cloud Function with HTTP signature. By properly setting the package.json npm scripts and then by creating the suitable launch configurations (see ./.vscode/launch.json) you can both running and debugging Google Cloud Functions locally using Visual Studio Code.

Note that, as usual in Visual Studio Code when dealing with Typescript debugging, it is required generating source maps when transpiling to Javascript. To do it, transpile with the --sourcemap option or set the sourceMap porperty in the tsconfig.json file to true:
{
   // tsconfig options
   "compilerOptions": {
      // tsconfig compiler options
      "sourceMap": true
   }
}

In this video the previous example is debugged. Notice how the launch configuration defined in  ./.vscode/launch.json is run and then how data is fetched to the function by running a curl comand in the terminal. 

As can be seen, execution stops when a breakpoint is reached and then the data sent from curl command can be watched. Finally, the data sent from the function is printed in the terminal by curl and then the console output is shown in the debug window.

Useful links

Comentarios

Entradas populares de este blog

Using Bitbucket app passwords with git on MacOS (OSX)

Learn how Bitbucket passwords are stored by git on MacOS.

ESlint: Ignore unused underscore variables

Some naming conventions promote the use of the underscore character (" _ ") for those variables that must be declared but are not being used. One common case is that in which a function signature contains some variables that will not be used, as for instance the Express error handlers: app.use(function(err, req, res, next) { console.error(err.stack); res.status(500).send('Something broke!'); }); In the above example only the arguments err and res are being used, though all four must be defined in the handler signature. Thus, following the naming convention of using underscores for those unused variables, we could recode it as: app.use(function(err, _, res, __) { console.error(err.stack); res.status(500).send('Something broke!'); }); Though it makes the function more readable, it comes with a problem if using ESlint: it will blame by declaring unused variables. error '_' is defined but never used error '__' is define...

GCloud: Changing the default project (in a nutshell)

gcloud command line tool allow us managing a list of Google Cloud projects, one of which is considered as the default project, so all the operations we perform via gcloud are performed on it. Available projects To check what projects are available via gcloud , run the command: $ gcloud projects list This command drops an output similar to this example: PROJECT_ID NAME PROJECT_NUMBER fooOrg-pr01 Project Bar 239785793387 fooOrg-pr02 Project Zaz 348328582382 ... You can check the available options in the GCloud SDK reference . Current default project As you can see, that output doesn't set what is the default project. To get it run this command: $ gcloud config get-value project The command output will show the ID of the default project. Following the above example, the output of the command could be fooOrg-pr01 to state that the default project is our Project Bar. Changing the current default project To change the default project, run this comman...