Firebase Cloud Function log aggregation

Reddit
Linkedin

You want to be aware of everything does happen in your app, right?

This is part 3 of a series of devops 101 with firebase cloud functions.

You can check the working repo here.

  1. Firebase Cloud Functions CI/CD with Cloud Build
  2. Firebase Cloud Functions and Slack notifications
  3. Firebase Cloud Functions Logging events
  4. Firestore Backups
  5. Firebase Disaster Recovery

It's time to talk about logging aggregation, we want to know everything that happens in our application and more important to be notified about it. So our goals for this part are: Create a small helper to help us to log events using Google Cloud Logging Understand how to log events can impact on the user experience.

Set for success

Before even start, we need to make sure that our project has admin access to Logging otherwise, we won't be able to copy anything. Let's do this:

Add Role

Go to the Google IAM and add the following roles.

advanced-permission-on-firebase firebase-sdk-iam firebase-iam-role

Let's add some code

Now we need to get our hands into the mug, let's start installing the dependencies we need:

npm install - save @google-cloud/logging

The following code will help us to create our custom logs, for this example, in particular, I will show you 2 cases to get you started with everything you need. Errors (most commonly used case) A custom Event (in this case we are simulating a purchase order that will happen on a background function)

The helper

const { Logging } = require('@google-cloud/logging')

const logging = new Logging()

export const report = (logName = `errors`) => (eventInfo, context = {}) => {
  const log = logging.log(logName)

  // This is common to all projects
  const metadata = {
    resource: {
      // This will place the logs into the cloud function filter
      type: 'cloud_function',
      labels: {
        function_name: process.env.FUNCTION_NAME,
        project: process.env.GCLOUD_PROJECT,
        region: process.env.FUNCTION_REGION
      },
    },
  }

  const event = {
    // If the event is an Error, this is a good place to use eventInfo.message
    message: eventInfo.message ? eventInfo.message : eventInfo, 
    context: Object.assign({}, context, {
      stack: eventInfo.stack ? eventInfo.stack : null,
    }),
  }

  return new Promise((resolve, reject) => {
    log.write(log.entry(metadata, event), (error: any) => {
      if (error) {
       reject(error)
      }
      resolve()
    })
  })
}

The custom Error Log

Now that we have the helper we can start creating each log we need. Let's start with error

/**
 * logs for error
 */
export const logErrors = (event, context = {}) => report(`errors`)(event, context)

It looks elegant, right?

So what is happening here? we are declaring an errors type to filter all our logs for error, you will find it more useful later. Our event is basically how we want to identify our error and lastly we can use the context to pass more information about the event. Pretty cool eh?

The next step is to wrap our function to use this, let's see an example with one http function, very straight to the point

/**
 * Example of catching errors
 */
export const someFunctionWithError = functions.https.onRequest(async (request, response) => {
  try {
    throw new Error(`I'm an sad error`)
    response.status(200).send({
      message: `I'm useless 😔`
    })
  } catch (error) {
    await logErrors(error)
    response.status(500).send({
      message: `Something went wrong, our developers were informed.`
    })
  }
});

Voilà, c'est ça ! now we can deploy our function using the cli tool

firebase deploy --only functions

After you execute it, you will get something like this:

function-error

If you go to the firebase functions logs, this is what you get.

firebase-error-log

That is usually what you get from the Firebase console, now let's see how we leveraged this using Google StackDriver Logging tool.

gcp-console.logs

Notice the different, in this case you have a more elaborate about your events, this should help you to make tracking easier.

Custom log case

It's time to make your custom logs, this can potentially help you to assemble your own reports or whatever information that will help you to make decisions. We are logging a custom purchase, on this case, everything something is created on the purchase collection, we are going to listen for this event and set the log to the purchases log. Let's see our elegant helper.

/**
 * custom log for purchase
 */
export const logPurchase =  (payload) => report(`purchase`)(`new purchase ${payload._id}`, payload)

And after this we can create a cloud function that looks like this:

/**
 * Example of a background function with custom loggings
 */
const purchaseRef = functions.firestore.document(`purchases/{doc}`)
export const processOrder = purchaseRef.onCreate(async (snap, context) => {
  const order = snap.data()
  try {
    // ... any business logic
    const payload = {
      _id: order._id,
      items: [`Chocolate ice cream`]
    }
    /**
     * be aware this is going to make the operation slower,
     * on this case if ok cause it is a background function
     * but if this would be a callable function for example,
     * this will make the operation more lengthy
     */
    return logPurchase(payload) 
  } catch (err) {
    return logErrors(err, { order })
  }
});

After creating a couple of records on the purchase collection you can come back to your logging page and check the following:

purchase-logs

How this impact the user?

Something that it's very important to mention is that if you are going to log custom events you need to consider all the implications that this will have on the user experience when you are executing an operation that holds your system, for example, a callable function or an http function, this will add and extra seconds you need to consider, basically, no because you have the power to log everything it does mean you should ;-)

How can we leverage this even more?

I like to extend this custom logging with other tools, maybe you can extend the slack notification system we build on the previous post to notify your sell team when a purchase is done.

Also maybe it is a good idea to extend your error logging to a more elaborate tracking system like Sentry. You could centralize all this logic on the logErrors functions. Anything else? I would love to hear it, please shoot me a line

You can check the working repo here.

Enjoyed this post? Receive the next one in your inbox!

I hand pick all the best resources about Firebase and GCP around the web.


Not bullshit, not spam, just good content, promised 😘.


Reddit
Linkedin