Go back to 8base Academy
May 5, 2022

Creating a Custom Logger for Serverless Functions

Sebastian Scholl
@SebScholl

Sebastian Scholl (00:04)
Hey everyone. Sebastian here from 8base. Today, I wanted to walk you guys through a tutorial, on how you can create a custom logger which essentially wraps your service functions and allows you to dynamically turn on or turn off verbose logging and really create any type of logger that you want to. Let's dive into it and start playing with some examples.

Sebastian Scholl (00:25)
I'm going to start here in a completely empty 8base functions directory. I could get here with the 8base command line tool by running 8base init, give it a project name, which I'm just calling here logger functions and then configure our workspace. I already had this little directory set up so I'm just going to run 8base configure and make sure that when I end up deploying these functions, they deploy to the appropriate workspace, which in this case is going to be the Sandbox Workspace.

Sebastian Scholl (00:56)
Now, inside of here, I don't have any functions yet. We're just going to go ahead and create a function. This could be any function type. You can call whatever you want. It doesn't really matter. I'm just going to go ahead and generate an 8base trigger function. This is going to be called beforeUserUpdate because that is the default function for triggers and JavaScript syntax.

Sebastian Scholl (01:20)
Let me go ahead and create that. Awesome. Added the declaration and it was beforeUserCreate. I'm just going to do this to update. Even though we could call the function whatever we want, I like that naming convention personally.

Sebastian Scholl (01:35)
Now, once that's created, we now have it here. We have our function. I'm going to erase or delete the example code here. We literally have a function that just returns the data it was given. We can make sure that everything is linked up and working by running the invoke-local command, the function name, and that mock and everything looks like it's working totally fine. We're set up.

Sebastian Scholl (02:04)
Now what we are going to try to do here is create a function that essentially wraps our serverless function so that then we can perform some type of custom logging and we're going to build it in a way to where we can dynamically toggle it on and off.

Sebastian Scholl (02:22)
This could be a useful tool for debugging. This could be a useful tool for sending logs to a third party system. There's lots of different use cases that you might want to apply this to.

Sebastian Scholl (02:36)
How we're going to start is we are going to first just write the made up code for how we want it to work in our function. I'm going to want to be able to import a function called logger, from a local directory called utils, and we're going to call it logger, and then I'm going to want to use that function to wrap my serverless function. I'm just going to reformat that a little bit. Cool.

Sebastian Scholl (03:10)
No matter what code I write in here now and the function that I normally use it, is now wrapped in this logger function. We need to make sure that the logger function returns the serverless function after performing any items or tasks it wants to do with the same arguments that we would expect this function to be given and without messing up the whole process.

Sebastian Scholl (03:34)
Let's go ahead now and create this Utils directory in that logger file. In the source, I'm actually doing the command line, I'm going to make a new directory in source called utils and I'm going to add a file to it source, utils called logger.js. Awesome. Based on this directory structure, I think we just have to go back up two levels. Yes, there we go. Cool.

Sebastian Scholl (04:05)
Now we are importing nothing from that file cause it's empty. Now we have to create a function which we're going to export a default function. We know the argument to be passed to it is the serverless function which is in our handler file so it takes in a function argument.

Sebastian Scholl (04:27)
Then inside of here, what we now need to do is make sure that any arguments being passed to the function are still making it all the way through. We can't. If we just were to return the function right now, we miss out on those arguments.

Sebastian Scholl (04:42)
Here, what we're going to do is, we're going to return another function, which spreads out the args or the arguments that are being received and then it invokes the serverless function with those arguments. With this little setup right here, actually it's pretty cool, we can go ahead and invoke that function again and see that nothing changed on the behavior of our function. It's being passed the serverless function, that function that it returned is being invoked, getting all the arguments from there and then we are once again invoking the serverless function with the arguments that were passed and everything lines up appropriately.

Sebastian Scholl (05:28)
What's cool about that is now that gives us this space in here to play around with and do whatever we want. What do we want to do? The first thing we want to do is make sure that we can dynamically essentially turn on or off this function facility or this logger facility.

Sebastian Scholl (05:44)
I'm going to say if something and then we will have our logger logic script inside there. Now a good way to approach this in the console is actually use environment variables. In the 8base console you will be able to go into your environment variables and set them, turn them on and off, do whatever you need to do. What we will do here is, we access those envariables by process.ENV.VERBOSE_LOGGER.

Sebastian Scholl (06:18)
This would be how we access the environment variable. Now the environment variable that gets passed, even if we give it a value like true or false, it's going to be a string. Because it is a string, we can't just leave it like this for true or false. Because if there's any value set for the verbose logger, it will always go to true.

Sebastian Scholl (06:38)
What we can do instead is do a little regex, says true and make this case insensitive. Whether or not use like an uppercase true, first letter, upper case, or down case, it will just look for whether or not true is in the field and that's a regex so that gives us the test function on that statement and then we can pass it the value from our environment variable.

Sebastian Scholl (07:09)
Inside here console.log, we can just say logged something and now we can invoke that locally. Since we have no environment variable set right now, it should not console.log our statement. Run that, it didn't.

Sebastian Scholl (07:31)
Now instead of setting an environment variable, we can just give it a bang symbol because we know they'll be the opposite of whatever it is, run it again and it logged something. Cool. Now we know inside this logger, we can perform whatever logging actions that we want to perform. Cool.

Sebastian Scholl (07:53)
But now that we're in there, let's actually explore some of the arguments that we're being passed and see how we might want to log those out. I'm just going to, once again, console.log(args) and see what's being passed to this function. I invoke local and I can see, this first argument is the event argument that gets passed to our serverless functions when we used to working with them so it's that guy. The second one is the context argument which has our API module on it, our ability to invoke other functions. That's all there.

Sebastian Scholl (08:25)
What we're going to do is let's just unpack that really quickly, const[event] and then we don't really need to use the context argument. It's not necessary. We can actually just do that and get the event variable that we want to work with.

Sebastian Scholl (08:44)
Now once we have that, let's just say that what we want to do is when verbose logging's on we want to console.log out all the data being passed to the function. If a user is being updated and verbose logging's on, we want all the event data to be passed. That would apply to any function that we use it on. Whether it's a user update, a trigger function, or a task function, it doesn't really matter. Any data being passed to our function, we want to know those arguments that are coming in.

Sebastian Scholl (09:13)
How can we do that? We can Object.entries and then pass in the event.data. This will give us essentially an array of all the key value pairs on that data object. Let's reduce over those. .reduce then gives us the callback which we're going to put in there. I'm just going to say, callback then we're going to start off with an empty string. Actually what we'll do, we'll do a string like that and then just in here say that this is the data received. Cool.

Sebastian Scholl (09:58)
Now inside this callback function, I like to put it on a new line. Not that you need to, but I like to. The first argument pass to the reducer is the initial string or whatever the value being returned is. Here it's the data receive string and then it passes in the second argument, which would actually be the entry from there. We know that's an array and so we can unpack that away with K or the key and the value. It's there.

Sebastian Scholl (10:30)
Then now whatever we return in this function, is the next string value. Let's just build a little template for this thing. What we can do is we can say that... Let me put a bullet point there and we can say, key is and then, key with value of and value. Cool.

Sebastian Scholl (11:04)
Then that will give us, our let's just call it message, and then we will console.log out the message. Awesome. Cool. Once again I'm going to turn off the bang symbol, so this shouldn't run.

Sebastian Scholl (11:25)
I'm going to invoke the function. Awesome. We can see that our mock that we're using, which is right here, let's just add some more stuff to there foo1:bar1 and foo2:bar2. Cool.

Sebastian Scholl (11:45)
When we invoke that function, we can see that all the data is coming back but the logger is not on so nothing's happening. Now when we change it to turn it on and we run it, we can see that the... What do we got? We don't have the last one because I forgot to add the current string or the new string to the existing string. Let's just do str+=. That starts to concatenate that and build that up. Then we'll also add a new line to the end of each one.

Sebastian Scholl (12:23)
Let's try that again and boom, our data received. Our logger is working. Now what we are going to do that we have our logger, remember, we can use this on as many functions as we want. Now we have this whole facility, we can just bring it in.

Sebastian Scholl (12:41)
Let's make sure that we turn it off by default, once again. Now I'm going to run 8base deploy. I never like running 8base deploy directly because it's a bad practice, especially if you're in a production workspace. You should be committing your functions like a GitHub repo or some type of Git repository.

Sebastian Scholl (13:02)
However, when just doing demos and whatnot, it's totally fine. I'm going to run 8base deploy and I will jump back in once this function deploys. Cool. The function has successfully deployed and now what we're going to do is, we're going to jump into the 8base console, set this environment variable. I'm just going to copy this verbose logger variable here and then test out our function which we know in this case is running on the user update operation.

Sebastian Scholl (13:31)
I'm going to switch over here to my 8base console. First I'm going to go is set that environment variable. Settings, environment variables, verbose logger. Then let's start with it just being off, false. I'm going to add it and we're good. Now if I go to my functions, here I can see that the beforeUserUpdate function has been deployed and it currently has no logs.

Sebastian Scholl (13:57)
Let's go into our data, users. For this first user right here, I'm just going to update it to be John Smith instead of Sebastian Smith. I'm going to submit that change. Cool. The row was successfully updated. John Smith is the new name. We go to our functions, we look at the logs for this before function and we can see that nothing has changed. That's good. Our logger is off.

Sebastian Scholl (14:35)
Now let's go back to our environment variable and for whatever reason we want to say, we want to know what's being passed to it. We're trying to debug something, whatever it might be.

Sebastian Scholl (14:46)
Update the environment variable, go back to the data, go back to John, Mr. Smith. Let's just say that it's now Johnny Smithers and his status is now inactive. Cool. Now I save that change. Cool. The row is successfully updated.

Sebastian Scholl (15:22)
Now if I go back to this function. I can go there, I'm going to reload it. That should be coming in. Awesome. Now we can see that it console logged out all that information. The data received inactive, Johnny, Smithers, time zone, all that stuff that was passed to it. Should have gone on the new line. But I think maybe if I passed the string literal, it would have worked better. I'm not sure.

Sebastian Scholl (15:52)
However, what's important to note here is, now we have that dynamic way of toggling on and off this little function or facility that we built for our workspace. It's important to remember, if I go back to the function, that I can pretty much do anything I want in here now.

Sebastian Scholl (16:08)
If I want to send these types of logs or any type of information to a third party system, if I want to send myself an email, if something happens or something comes through, I can do... Send it off to Twilio whatever it is.

Sebastian Scholl (16:21)
I love doing stuff like this because it just really shows the flexibility of working with 8base. It helped us by bringing a ton of convention to how we deploy functions, generating those functions, the environment in which they're hosted all that stuff and I could just focus really on the code that mattered to me.

Sebastian Scholl (16:38)
I hope that this tutorial helped give you some ideas on how you can maybe more creatively use functions to handle some of these more nuanced use cases. We are, actually, currently building a logging facility into 8base so that would be more of a native solution for this.

Sebastian Scholl (16:55)
However, I always just like to remind our development community, that anything that 8base doesn't currently offer, you can build using 8base because we are really giving you the same tools that we use to build our platform. We're exposing those to you really in an almost uncontrolled manner. Super fun environment to work in and I hope that you found this video useful. See you in future videos.

Share this post on social media!

Ready to try 8base?

We're excited about helping you achieve amazing results.