Audit Log/Trail with Strapi

Adenle Abiodun
6 min readAug 18, 2020

--

Strapi Image

Strapi is a flexible, open-source Headless CMS that gives developers the freedom to choose their favorite tools and frameworks while also allowing editors to easily manage and distribute their content. By making the admin panel and API extensible through a plugin system, Strapi enables the world’s largest companies to accelerate content delivery while building beautiful digital experiences. You can read more on it here https://strapi.io/documentation/v3.x/getting-started/introduction.html#what-is-strapi

I have seen a lot of people having issues and raising concerns on how to create an audit log in your applications using Strapi. If you are a part of this group, this post is for you. You have come to the right place. Sit tight while I take you on the Audit trail journey with Strapi. lol :)

I will assume you have already set up your strapi application at this point and you run: `strapi develop` on your terminal to run the application in development mode on your localhost. If not, check out the official strapi documentation here [https://strapi.io/documentation/v3.x/getting-started/introduction.html] to learn how to get started with strapi and set up your application.

At the Plugins > Content-Builder Types section, Create a collection type called “Trails”. You can actually name it anything you want but for this example's sake, I will like to call it “Trails”.

And in the new trails collection, Create the following fields with the following types:

contentType: this will be a type of “text”, we will use this to store the type of activity going on

action: this will be a type of “text”, we will store the type of action that was performed in this activity

content: this will be a type of “JSON”, and we will store response the user got when this activity occurred. I would have called it response but I just changed my mind and decide to name it content.

author: this will be a type of “JSON”, and this stored the details of the user who performed the action.

request: this will be a type of “JSON” and captures any request body or payload that was sent to the server.

method: this will be a type of “text” that tels us if this was a GET, POST, etc.

route: this will be a type of “text” that captures the route that was called when this activity happened

params: this will be a type of “JSON” that captures any parameter that was passed into the route at the point of request.

statusCode: this will be a type of “number” that shows us the status of the request. for example, 200 means okay, 404 not found, etc.

Now that this collection type called “Trails” has been created, let's dive into the code and perform the not-so-magic audit trailing.

Now to the juicy, parts. lol :). We will be using the concept of middleware in strapi to get this up and running. You can check strapi documentation for more in-depth usage of middlewares. Don’t get your self worked up with the term “Middleware”. It's not as complex as it sounds.

Step1: Create an index.js file in your project from the root directory in this format.

middlewares/audit/index.js

Note: If you don't have a middleware folder in your project root, you need to create it, and you cant call it any other name. It should be “middlewares”. This is because, at load time, strapi looks for this folder to initialize your middleware if you call it any other name, this won't work.

In the index.js file, you need to initialize the middleware like this:

Strapi uses a ctx object. this object is the object that holds the async requests or transports via the strapi framework. It is a very useful component of this middleware as we really can't do much without it. Every request and response that happens, the ctx captures and stores data in key=>value pairs.

strapi.info is more like console.log. It is very good for debugging purposes.

You should see “Yayy!, audit middleware rocks!”

If you do, congrats! I’m done. Thank you for reading this post.

Okay, I’m just kidding, but you have done really well. So let's continue.

Next is to write some helper functions that will help us perform a few tasks.

The first is user security. there is a tendency for passwords to be transmitted via the requests/response from the server, so we are going to create a simple function that sanitizes and removes passwords from the audit trail.

This snippet above will be used to sanitize soon. Remember, we are still working in the index.js file of the audit middleware.

Next, we will create a function that helps to parse the kind of action that is occurring and this will be stored in the “action” field in the Trail content type we created in strapi. It takes the method and the path of the request in as parameters and with that returns the type of action we think the user will be trying to make via these routes.

We can create more conditions that need to be matched depending on the use case and the number of API routes that your server responds to.

Next, we will create a function that helps to parse the type of action that is occurring and this will be stored in the “contentType” field in the Trail content type we created in strapi. It takes in only the path of the request in, as a parameter, and with that returns the type of request that we think the user will be trying to make via these routes.

Again, we can create more conditions that need to be matched depending on the use case and the number of API routes that your server responds to.

Do you feel tired at this point? You can make it, don’t give up. I believe in you. We are getting to the interesting part. You are already halfway there.

Now, this is where we begin to capture details to be stored in the audit trail.

Lets first capture trail from Authenticated users.

For this group, we will first write a condition to check if ctx.state && ctx.state.user exists as seen from line 51, if this is true, it means the request is made by an authenticated user.

so we can create an “entry” object that captures all the fields we stated in the Trail model. We then use our helper functions getActionType, getContentType to populate action and contentType field as stated in our model.

You can console the ctx, ctx.state, ctx.request and ctx.response using strapi.info() to see all the available data that can be captured.

Once this “entry” object has been properly captured, we can then insert it into our “Trails” model that was created. When this model was created, strapi automatically created a `service` for it. then we can just call this method:

strapi.services.trails.create()

to write to the trails model the “entry” object that has been captured. This can be seen in lines 73 and 74.

on lines 68 and 69, I write a condition to skip capturing trails on trails. Funny right? Well, I personally just don't want to create a trail whenever the trails are views so we don't have too many irrelevant trail of trails.

But depending on your use case, you can decide to capture it all.

For the non-authenticated users, we just crate same object and follow the same method for authenticated users. The only difference will be the way we capture the “Author” field as seen in the final snippet below.

After this, go to the admin dashboard and see all your trails.

Yaayyyyy… we made it.

Now you can see every request in and out of your application with a single pane of your detective glasses.

Sidebar: I need emojis, anyone how I can add some to my posts?

Now you have your trail working.

Yes! you made it.

Thank you for reading.

--

--