Jmix Timers and Web Application Usage Logging
Introduction
Logging and auditing are important aspects of web application security and monitoring. The former, when it is done well, makes the latter easy. That is, if our web applications feature good logging, it will be easier for us to audit the activity of users and system components. When we can efficiently (without great programming effort and with computational efficiency) audit when a user opened a screen, when they pressed a button, when they created a record, when they updated a record, when they deleted a record, and when they exited a screen, it is straightforward to answer questions about application usage. The dashboard seen in Figure 1 was created based on usage data captured in the background as users interact with a web application.
This post is a first look at using the Timer facet of Jmix to facilitate the automated capture of application module usage. It is a short read with documented code demonstrating an uncomplicated way to add auditing to your application today. By the end you will know how to do the following:
- Add a logging service to your Spring Boot application
- Capture log events and push them to the database
- Ensure that the capture time is in the time of the user and not the database or application server.
The Logging Database Tables
We will put our logging events into a relational database. (In this post will will use PostgreSQL. However, you can use in RBDMS you like. Just make sure that you can find JDBC drivers that will work with your Spring Boot web application server.) We have four tables. They are shown in an ERD in the Figure 2.
Figure 2 — Jmix Logging ERD
The SQL script for creating these tables is shown in Listing 1.
CREATE TABLE IF NOT EXISTS bifref_application_component ( application_component_id character varying(255) NOT NULL, application_component_type character varying(255) NOT NULL, name character varying(255) NOT NULL, description character varying(1000) CONSTRAINT pk_bifref_application_component PRIMARY KEY (application_component_id) ); CREATE TABLE IF NOT EXISTS bifref_application_event( application_event_id uuid NOT NULL, application_event_type_id character varying(255) NOT NULL, username character varying(255) NOT NULL, event_start timestamp without time zone NOT NULL, event_stop timestamp without time zone NOT NULL, version integer NOT NULL, application_component_id character varying(255) NOT NULL, CONSTRAINT pk_bifref_application_event PRIMARY KEY (application_event_id), CONSTRAINT fk_bifref_application_component FOREIGN KEY (application_component_id) REFERENCES bifref_application_component (application_component_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT fk_bifref_application_event_type FOREIGN KEY (application_event_type_id) REFERENCES bifref_application_event_type (application_event_type_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ); CREATE TABLE IF NOT EXISTS bifref_application_event_type ( application_event_type_id character varying(255) NOT NULL, name character varying(255) NOT NULL, description character varying(1000), CONSTRAINT pk_bifref_application_event_type PRIMARY KEY (application_event_type_id) );
Listing 1 – SQL script for creating logging tables.
The SQL script in Listing 1 will help you create the tables required for this post. You can rename them to whatever you want but need to remember that the names provided are part of a working solution. Also, the tables were modeled in Jmix’s data model visual designer. (It makes it easier to maintain the design if we leverage the design features of the Jmix plugin.)
The Logging Service in Spring Boot
Logging of application events is handled by a service. In Listing 2 we provide the code for an application event logging service.
package com.bif.azurereference.app; import com.bif.azurereference.entity.ApplicationComponent; import com.bif.azurereference.entity.ApplicationEvent; import com.bif.azurereference.entity.ApplicationEventType; import com.bif.azurereference.entity.EnumerationApplicationComponentType; import io.jmix.core.DataManager; import io.jmix.core.NoResultException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.UUID; @Service("ApplicationEventLoggingService") public class ApplicationEventLoggingService { @Autowired private DataManager dataManager; public DataManager getDataManager() { return this.dataManager; } public ApplicationEvent logApplicationEvent( UUID uuidApplicationEventID, String stringApplicationEventTypeID, String stringApplicationComponentID, String stringApplicationComponentType, String stringUsername, LocalDateTime localDateTimeEventStart, LocalDateTime localDateTimeEventStop ) { /** * Step 1: Load referenced application event type. */ ApplicationEventType applicationEventType = null; try { applicationEventType = this.getDataManager().load(ApplicationEventType.class).id(stringApplicationEventTypeID).one(); } catch (NoResultException noResultException) { System.out.println("No event type found for " + stringApplicationEventTypeID); } // If the event type does not exist, create it. if (applicationEventType == null) { applicationEventType = new ApplicationEventType(); applicationEventType.setApplicationEventTypeID(stringApplicationEventTypeID); applicationEventType.setName(stringApplicationEventTypeID); applicationEventType.setDescription(stringApplicationEventTypeID); applicationEventType.setCreatedBy("admin"); applicationEventType.setCreatedDate(OffsetDateTime.now()); applicationEventType.setVersion(1); applicationEventType.setSortOrdinal(0); try { this.getDataManager().save(applicationEventType); } catch (Exception e) { throw new RuntimeException(e); } } /** * Step 2: Load referenced application component. */ ApplicationComponent applicationComponent = null; try { applicationComponent = this.getDataManager().load(ApplicationComponent.class).id(stringApplicationComponentID).one(); } catch (NoResultException noResultException) { System.out.println("No component found for " + stringApplicationComponentID); } // If the application component does not exist, create it. if (applicationComponent == null) { applicationComponent = new ApplicationComponent(); applicationComponent.setApplicationComponentID(stringApplicationComponentID); applicationComponent.setName(stringApplicationComponentID); applicationComponent.setDescription(stringApplicationComponentID); applicationComponent.setApplicationComponentType(EnumerationApplicationComponentType.fromId(stringApplicationComponentType)); applicationComponent.setCreatedBy("admin"); this.getDataManager().save(applicationComponent); } /** * Step 4: If a primary key was not passed, create it. */ if (uuidApplicationEventID == null) { uuidApplicationEventID = UUID.randomUUID(); } /** * Step 5: Create the object. */ ApplicationEvent applicationEvent = this.getDataManager().create(ApplicationEvent.class); applicationEvent.setApplicationEventID(uuidApplicationEventID); applicationEvent.setApplicationEventTypeID(applicationEventType); applicationEvent.setApplicationComponentID(applicationComponent); applicationEvent.setEventStart(localDateTimeEventStart); applicationEvent.setEventStop(localDateTimeEventStop); applicationEvent.setUsername(stringUsername); /** * Step 6: Save the object. */ this.getDataManager().save(applicationEvent); /** * Step 7: Return the object. */ return applicationEvent; } public ApplicationEvent updateApplicationEvent( ApplicationEvent applicationEvent ) { /** * Step 1: Get the application event identifier. */ UUID uuid = applicationEvent.getApplicationEventID(); /** * Step 2: Find the application event in the database. */ ApplicationEvent applicationEventTemp = this.getDataManager().load(ApplicationEvent.class).id(uuid).one(); applicationEventTemp.setEventStop(applicationEvent.getEventStop()); if (applicationEventTemp != null) { this.getDataManager().save(applicationEventTemp); } /** * Step 2: Return the object. */ return applicationEventTemp; } }
Listing 2 – Java Code for an application vent logging service.
The Timer and Capturing Usage Events
Timer is component of Jmix defined in the facets element an application screen. Specifically, it will be in the screen XML descriptor. The configuration of a Timer through the XML is done through three attributes: delay, autostart, and repeating.
Parameter Name | Required? | Desciption |
delay | Yes | This is the timer interval in milliseconds. |
autostart | No | indicates whether or not the timer will start automatically. The default setting is false; when autostart is set to false the timer will start only when its start() method is invoked. When it is set to true, the timer starts immediately after the screen opening. This parameter is optional. |
repeating | No | Turns on repeated executions of the timer. If the attribute is set to true, the timer runs in cycles at equal intervals defined in the delay attribute. Otherwise, the timer runs only once as many milliseconds as specified in the delay attribute after the timer start. This parameter is optional. |
Ensure that Captured Time is in the Time of the User
Conclusion
References