Logo

dev-resources.site

for different kinds of informations.

Uptime Monitoring with Firebase

Published at
4/4/2023
Categories
firebase
cloudfunctions
firestore
Author
nicomartin
Categories
3 categories in total
firebase
open
cloudfunctions
open
firestore
open
Author
10 person written this
nicomartin
open
Uptime Monitoring with Firebase

In this second step of the series, I assume that the Firebase project has already been initialized and is ready. Otherwise I recommend to work through the steps from the first part.

The database

I usually like to start with the database design. This helps a lot to have a structured understanding of the application.
Firestore is a NoSQL database that stores data in documents inside collections. One very cool feature is that you can also create subcollections for expressing hierarchical data structures.

In my Project I basically need two Datatypes. The UptimeEntries and the UptimeRequests. An UptimeEntry will be created when we check the site and it will then have one (if the first request is ok) or multiple (if the first request fails) UptimeRequests.

Both have a pretty strict structure defined in TypeScript interfaces:

export interface UptimeRequest {
  id?: string;
  url: string;
  ok: boolean;
  statusCode: number;
  duration: number;
  started: firebaseAdmin.firestore.Timestamp;
  ended: firebaseAdmin.firestore.Timestamp;
}

export interface UptimeEntry {
  id?: string;
  url: string;
  initialResponseOk: boolean;
  responseOk: boolean;
  downtimeMillis: number;
  created: firebaseAdmin.firestore.Timestamp;
  latestCheck: firebaseAdmin.firestore.Timestamp;
}
Enter fullscreen mode Exit fullscreen mode

The idea is now that I will have two collections. The first will just be "entries", a list of UptimeEntry and the second will be a subcollection with the path "entries/{entryId}/requests". This means that one UptimeEntry can have multiple UptimeRequests.

I really like to abstract things away. So for all the database communication I created just one class with a handfull of methods:

import * as firebaseAdmin from "firebase-admin";
firebaseAdmin.initializeApp();

const firestore = firebaseAdmin.firestore();

class Firestore {
  collectionEntries = () => {
    // returns the "entries" collection reference
  };

  collectionRequests = (entryId: string) => {
    // returns the "entries/{entryId}/requests" collection reference
  };

  createEntry = async (data: UptimeEntry) => {
    // creates a new UptimeEntry
  };

  getAllEntries = async () => {
    // returns all UptimeEntries
  };

  getEntry = async (entryId: string) => {
    // returns an UptimeEntry by ID
  };

  update = async (entryId: string, entry: Partial<UptimeEntry>) => {
    // updates an UptimeEntry by ID
  };

  getLatestEntry = async () => {
    // returns the UptimeEntry
  };

  addRequest = async (entryId: string, request: UptimeRequest) => {
    // adds an UptimeRequest to an UptimeEntry
  };
}
Enter fullscreen mode Exit fullscreen mode

The exact implementation of the class can be found here: https://github.com/nico-martin/uptime-slackbot/blob/main/functions/src/utils/Firestore.ts

Check the status

Now that I have my database adapter I can finally start with the monitoring.

Here I actually need two functionalities of Firebase cloud functions. First, I want to periodically (every 5 minutes) check the status of my website.
Second, if a request fails, I want to retry the request until the site is back online.

So my first function should run in a 5 minute interval. Here we can use the functions.pubsub.schedule Function:

const scheduleUptime = functions.pubsub
  .schedule("every 5 minutes")
  .onRun(async () => {
    // ..
  });

export default scheduleUptime;
Enter fullscreen mode Exit fullscreen mode

Inside the function we are going through a couple of steps:

  1. we need to make sure that there is not already a failed request/retry ongoing. So we will get the latest entry from the DB. If there is a latest entry and the latest entry is still not ok, don't need to continue (because there already is an ongoing request/retry).
  2. after that we will run the request to the URL, if it is not ok, we know that we have downtime
  3. After that we will add our Entry to the DB and we can also assign the Request to the Entry
const scheduleUptime = functions.pubsub
  .schedule("every 5 minutes")
  .onRun(async () => {
    const latest = await db.getLatestEntry();

    if (latest && !latest.responseOk) {
      return;
    }
    const check = await createRequest();

    if (!check.ok) {
      functions.logger.log(
        `Uptime Monitor is DOWN: ${check.url} - StatusCode: ${check.statusCode}`
      );
    }

    const createdId = await db.createEntry({
      url: check.url,
      initialResponseOk: check.ok,
      responseOk: check.ok,
      created: firebaseAdmin.firestore.Timestamp.now(),
      latestCheck: firebaseAdmin.firestore.Timestamp.now(),
      downtimeMillis: 0,
    });

    await db.addRequest(createdId, check);
    return;
  });
Enter fullscreen mode Exit fullscreen mode

https://github.com/nico-martin/uptime-slackbot/blob/main/functions/src/scheduleUptime.ts

Recheck if downtime detected

So now we know when our site is down. But what is missing is an indicator when our site is available again. I have tried different ideas back and forth. The following makes the most sense from my point of view.

const requestOnWrite = functions.firestore
  .document("uptime/{uptimeId}/requests/{requestId}")
  .onCreate(async (requestSnapshot, context) => {
    // ...
  });

export default requestOnWrite;
Enter fullscreen mode Exit fullscreen mode

In this function we now have several options.

  1. if the status of the request is ok and also the initial request was ok, we don't have to do anything.
  2. if the status of the request is ok we know that we are coming from a downtime and the page is now online again. This means that we can update the entry accordingly and log our message.
  3. if the status of the request is not ok we are still in a downtime and after a certain time we can start a new attempt.
const requestOnWrite = functions.firestore
  .document("uptime/{uptimeId}/requests/{requestId}")
  .onCreate(async (requestSnapshot, context) => {
    const uptimeEntry = await db.getEntry(context.params.uptimeId);
    const request = requestSnapshot.data() as UptimeRequest;
    if (request.ok && uptimeEntry.initialResponseOk) {
      // is first request of a successful uptime check
    } else if (request.ok) {
      // request successfull after retry
      uptimeEntry.latestCheck = request.started;
      const downtimeMillis = request.started.toMillis() - uptimeEntry.created.toMillis();
      uptimeEntry.responseOk = true;
      uptimeEntry.downtimeMillis = downtimeMillis;
      await db.update(context.params.uptimeId, uptimeEntry);
      functions.logger.log(`Uptime Monitor is UP: ${request.url}. It was down for ${formatSeconds(Math.round(downtimeMillis / 1000))}.`);
    } else {
      // request failed, create new request after 2 sec
      setTimeout(async () => {
        const check = await createRequest();
        await db.addRequest(uptimeEntry.id, check);
      }, 2000);
    }

    return;
  });

export default requestOnWrite;
Enter fullscreen mode Exit fullscreen mode

https://github.com/nico-martin/uptime-slackbot/blob/main/functions/src/requestOnWrite.ts

With this setup we are logging when our site is down and also when it is back up again. Please check the full source code on GitHub since I am also using some helper functions from functions/src/utils/helpers.ts:
https://github.com/nico-martin/uptime-slackbot/blob/main/functions/

Once your functions are done you can export them in your functions/src/index.ts:

export { default as scheduleUptime } from "./scheduleUptime";
export { default as requestOnWrite } from "./requestOnWrite";
Enter fullscreen mode Exit fullscreen mode

And with that you are now ready to deploy your functions:

npx firebase deploy --only functions
Enter fullscreen mode Exit fullscreen mode

Let's get ready for the last step where we create and implement our Slackbot.

firestore Article's
30 articles in total
Favicon
Dev Video Review: Firestore Data Structure, Limitations, and IMHO
Favicon
Do you need a No Code tool for Firebase?
Favicon
Firebase: The Ultimate Backend for Your CMS
Favicon
NgSysV2-10.1: Firestore CRUD templates
Favicon
NgSysV2-3.3: A Serious Svelte InfoSys: Firebase D/b rules and Login
Favicon
NgSysV2-3.4: A Serious Svelte InfoSys: Rules-friendly version
Favicon
NgSysV2-3.5: A Serious Svelte InfoSys: Client-Server Version
Favicon
Dive into the world of serverless - GCP Edition
Favicon
Visualizing Firebase Data: Unlocking the Power of Real-Time Insights
Favicon
Implementing Batch Write Operations in Firestore with Express
Favicon
Enforcing Firebase App Check for Firestore with Initialization Configuration
Favicon
Retrieving User Roles from Firestore in a Next.js Application
Favicon
How to Keep Your Custom Claims in Sync with Roles Stored in Firestore
Favicon
Scheduling Events in Firebase Firestore with Server Timestamps
Favicon
Using Google Cloud Firestore with Django's ORM
Favicon
Firebase Realtime Database vs Cloud Firestore
Favicon
Real-Time Data Handling with Firestore: Tracking Pending Orders
Favicon
How to programmatically backup your Firestore database with simple steps
Favicon
Understanding Real-Time Data with Firebase Firestore in JavaScript
Favicon
Enabling Offline Capabilities in Firebase with IndexedDB Persistence
Favicon
Querying Firestore for Capital Cities with JavaScript
Favicon
Explorando o Firebase: Uma Plataforma Poderosa para Desenvolvimento de Aplicativos
Favicon
Using Firestore in Apps Script
Favicon
What is Flutter, how do I get started ??!!!
Favicon
How to Stream Data From Firebase to BigQuery Easily
Favicon
Retrieve a list of data under specific user collection in flitter
Favicon
Tentando não ficar pobre antes de ficar rico criando uma Startup de serviços de inteligência artificial
Favicon
Trying to Maintain a Workable Budget Creating a Chatbot Using GPT and Vector Database
Favicon
Uptime Monitoring with Firebase
Favicon
Firestore data modeling

Featured ones: