In most application scenarios, a frontend application produces logs that need to be collected and managed centrally. This is very helpful for being notified when an error occurs or for troubleshooting bugs.
The market offers many tools that centrally collect logs and let you manage them in a suitable container to support this process. Unfortunately, most of these services require paid subscriptions.
Moreover, for a small project, adding a layer like this can be overkill. But what about using Strapi as a backend for logging management? This way, you will have your log integrated into your Strapi backend without any additional cost.
This article explains how to configure Strapi to act as a backend for collecting logging and showing them to the developers. The result of this step-to-step guide is a replicable solution for ingesting logs from any frontend application and the javascript code for connecting.
So, let’s jump into the article.
Before you can jump into this content, you need to have a basic understanding of the following.
The first step before starting on the core of the tutorial is to install Strapi. You can create an online account or install it locally.
For this tutorial, it's equivalent. We will embrace the local installation option for giving you a single repo with source code that can be activated and tested locally with ease (link to the source code at the bottom of this article).
The first step is to create a file called *docker-compose.yml*
and put this content inside:
1 version: '3'
2 services:
3 strapi:
4 image: strapi/strapi
5 environment:
6 DATABASE_CLIENT: postgres
7 DATABASE_NAME: strapi
8 DATABASE_HOST: postgres
9 DATABASE_PORT: 5432
10 DATABASE_USERNAME: strapi
11 DATABASE_PASSWORD: strapi
12 volumes:
13 - ./app:/srv/app
14 ports:
15 - '1337:1337'
16 - '8080:8080'
17 depends_on:
18 - postgres
19 postgres:
20 image: postgres
21 environment:
22 POSTGRES_DB: strapi
23 POSTGRES_USER: strapi
24 POSTGRES_PASSWORD: strapi
25 volumes:
26 - ./data:/var/lib/postgresql/data
Then you can type the following command, and the environment will start:
1 docker-compose up
You will get a confirmation that the application is running in the console:
Then you can navigate to http://localhost:1337/admin. Just fill the form with your data and click "Let's start".
Inside the configuration panel, we have to activate some plugins. Just click on the Marketplace icon on the left menu.
Then you can activate the documentation plugin to test the APIs that we will create by clicking the Install button (once you have done this, the button text will change to "Already installed").
Now Strapi is ready to be configured! Let's continue to the next section!
In this section, we will see how to configure the Strapi backend for ingesting and storing logs. Of course, like a headless CMS, Strapi can manage every kind of data:
We have to explain how they are made. To do this, we will create a new Content-Type, by clicking the left icon menu "Content-Types Builder".
Then we can create a new content type by clicking "Create new collection type":
That we will call "Logs", like in the next image.
As you click "Continue," you will be allowed to add new fields to the content type. The first field to add is the "Message" one, which will store the log message. We will store it in a Long text format because we may have huge messages.
The first step is to choose the right type of field, like in the next image.
The next step is to set the field data by typing the name and the base settings, like in the next image.
This is just one of the fields we need. For this basic setup, the list of fields are:
message
, of type text (the one already added in the previous step)eventDate
that will contain the date of the logeventLevel
that can be INFO, DEBUG, or ERRORAfter adding all these fields, the final result should be something like what you see in the next picture.
Another important step is configuring the view to show the most recent logs on top of the list. This can be done easily by editing the view settings like in the next picture. As you can see, we can sort items by date
or id
descending.
Now we can add logs from the interface manually, but this is not our applicative behavior. We need an external system (our frontend application) to send logs by using APIs. Because we have installed the Documentation plugin, we can click on the "Documentation" item on the left menù and land on the documentation page.
Here we can find a JWT token that we can copy and use later on our app to authenticate web requests.
You can see it in the next picture.
Then, by clicking on "Open the documentation," we will land to the Swagger UI. We can find the "Logs" entity on the list and expand the section, unveiling the Log's APIs like shown in the next picture.
Expanding the POST method, we will see a payload prototype to send to the server to create a new log entry. This payload is shown in the next image.
After this step, the backend configuration is finished. In this section, we have seen how to configure the server-side part of this tutorial, and now it's time to move on to the client-side and configure a sample application for sending logs.
Since we have the backend service set up, we can start focusing on the client part. Well, this will be very easy and will take a few minutes. All the logic can be implemented by overriding the standard console definition.
In the next snippet of code, we can see the full implementation of the class:
1 var console=(function(oldCons){
2 return {
3 apiToken: "",
4 url: "",
5 httpPost(log){
6 var xmlhttp = new XMLHttpRequest();
7 xmlhttp.open("POST", this.url);
8 xmlhttp.setRequestHeader("Content-Type", "application/json");
9 xmlhttp.setRequestHeader("Authorization", "Bearer "+this.apiToken);
10 xmlhttp.send(JSON.stringify(log));
11 },
12 sendLog(level,input) {
13 var data={}
14 var message="";
15 message=input[0];
16 if(input.length>1){
17 input.splice(0,1);
18 data=input;
19 }
20 var log= {
21 "message": message,
22 "eventDate": new Date(),
23 "eventLevel": level,
24 "data":data
25 };
26 this.httpPost(log);
27 return log;
28 },
29 log: function(...text){
30 var log=this.sendLog("DEBUG",text);
31 oldCons.info(log);
32 },
33 info: function (...text) {
34 var log=this.sendLog("INFO",text);
35 oldCons.info(log);
36 },
37 warn: function (...text) {
38 var log=this.sendLog("WARNING",text);
39 oldCons.warn(log);
40 },
41 error: function (...text) {
42 var log=this.sendLog("ERROR",text);
43 oldCons.error(log);
44 }
45 };
46 }(window.console));
The library can be easily integrated into every javascript application because it is a simple code snippet with no dependencies. Overriding the console is very smart because every application or framework you have used before is used for logging purposes.
So, just by injecting this class in the frontend application, your backend will start receiving logs. Note that all the methods support multiple input parameters, so you can call logs by sending a message correlated with log data. Some sample usage of the library are listed in the next snippet:
1 console.log(msg,{
2 "mydata1":"myvalue1",
3 "mydata2":"myvalue2"
4 });
5 //or
6 console.error(msg);
Now that the javascript library is ready, we can start integrating it on a real-world application!
The first step is to create a new application. As the library written in the previous section is plain javascript, we could use any framework. In this example, we used Vue.js. For creating a new application, just run the following commands:
npm install vue
vue create hello-world
The command will create an empty application with a single component called "hello world". The next step is to run the application by typing:
npm run serve
The application will be available at the URL http://localhost:8081. Now that the application is up, we have to integrate the library inside the application. We can do that by simply linking the js
into the index.html
file or copying the code in a script tag of the same file. Then we have to configure the client like in the next piece of code.
1 <!DOCTYPE html>
2 <html lang="">
3 <head>
4 ... code omitted for brevity
5 <!-- <script src="<%= BASE_URL %>log.js"/> -->
6 <script>
7 console.url="http://localhost:1337/logs"
8 console.apiToken="my apy token";
9 window.console = console;
10 </script>
11 </head>
12 <body>
13 .. code omitted for brevity
14 </body>
15 </html>
Since now, our application will use the console library instead of the standard definition. The final step is to write a component that proves the connection with the backend. We can add to the components/HelloWorld.vue
the following template:
1 <template>
2 <div class="hello">
3 <h1>{{ msg }}</h1><br>
4 <input type="text" v-model="msg" /> <br><br>
5 <button v-on:click="log()">LOG</button> <br><br>
6 <button v-on:click="info()">INFO</button> <br><br>
7 <button v-on:click="error()">ERROR</button> <br><br>
8 <button v-on:click="warning()">WARNING</button> <br><br>
9 <button v-on:click="messageWithData()">DATA</button> <br><br>
10 </div>
11 </template>
Then we can add to the component the following method for cathing the button click events:
1 methods: {
2 log: function () {
3 console.log(this.msg);
4 },
5 info: function () {
6 console.info(this.msg);
7 },
8 error: function () {
9 console.error(this.msg);
10 },
11 warning: function () {
12 console.warn(this.msg);
13 },
14 messageWithData: function () {
15 console.log(this.msg,{
16 "mydata1":"myvalue1",
17 "mydata2":"myvalue2"
18 });
19 }
20 }
After the code is changed, the application will be automatically reloaded, and you will see the new application running like in the following image:
Then, we can play with buttons sending different messages to the server. You can finally go to the Strapi backend and open the logs section. You will see a list of logs, like in the following image.
Then you can enter one of the logs received for reading all the details. You will find something similar to what it's shown in the next image.
As you can see in the previous image, the data sent from the client is stored in Strapi, and you can send messages accompanied by data. That's a great solution for troubleshoot bugs.
Now that our tutorial is ended is time to think about what we have done and take conclusions!
The great benefit you have with Strapi compared to other headless solutions is that you can customize everything and work on-prem or in the cloud with real control of your application. The example we have seen in this tutorial is easy, but you can imagine evolving to match the real-world need.
You can set up a solution in minutes and, like in this example, add non-trivial features to your application in minutes. You can add more fields. You can accumulate logs before sending and implement a backend controller that processes multiple logs in a single go.
This is a starting point for implementing complex solutions. Because Strapi will support for sure any further need, the only limitation it's your fantasy.
You can download the source code from this code repository, both the Vue.js Frontend and Strapi Backend.
I'm CTO @Sintraconsulting and I worked as Senior Developer, Team Leader and architect in a very large set of enterprise projects. I had a master's degree in Robotic science and another master's degree in project management. My experience in technology extends on many technologies (java, PHP, .net) and platforms (Sharepoint, Liferay, Pimcore) other than techniques (Agile, DevOps, ALM). I’m also interested in Agile techniques, project management, and product development. I wite for the most important Medium publications (The Startup, Towards Data Science, Better Programming) and I have spoken physically or virtually in many countries (Uk, Jamaica, Italy, Belarus) about DevOps, opensource, and cloud transformation.