Supercharge Wiremock Logging: A Deep Dive into Custom Database Logging

Wiremock, a beloved tool for mocking HTTP APIs, offers robust functionality. But when it comes to logging, its default options might leave you wanting more. This blog post empowers you to craft a custom notifier that transcends these limitations, enabling you to store intricate request and response data directly within a database of your choice.

Why Custom Database Logging Reigns Supreme

  • Unveiling the Depths of API Interactions: Gone are the days of rudimentary logs. With custom database logging, you can meticulously store even the most granular details of request and response objects. This empowers you to comprehensively analyze API behavior, identify patterns, and gain invaluable insights into how your APIs are being utilized.
  • Fortifying Your Audit Trail: Regulatory compliance or internal governance might necessitate a centralized log for auditing purposes. A custom database notifier fulfills this need by meticulously recording every API interaction within your database. This provides a tamper-proof, auditable record that simplifies compliance and bolsters security.
  • Streamlining Debugging Efforts: When API issues arise, debugging can become a tedious endeavor. Custom database logging offers a lifeline. By meticulously storing request and response data, you can reconstruct the exact sequence of events that led to the problem. This pinpoints the root cause with laser focus, saving you precious time and frustration.

Harnessing the Power of the Notifier Interface

Wiremock's built-in Notifier interface acts as the cornerstone for extending logging capabilities. This interface outlines a set of methods that correspond to various events that occur during Wiremock's operation. By creating a class that implements this interface, we can craft a custom notifier that seamlessly integrates with Wiremock's logging framework.

Let's understand the default consolenotifier class:

WireMockServer wireMockServer = new WireMockServer(WireMockConfiguration.options()
.port(57167).usingFilesUnderDirectory("src/main/resources")
.notifier(new ConsoleNotifier(true)));
Purpose: The ConsoleNotifier class is responsible for sending notifications and updates to the console or terminal window during the operation of WireMock.
Implementation: It implements the Notifier interface, which defines methods for sending various types of notifications such as request received, response generated, and mapping added, updated, or removed.

Console Output: The class utilizes standard output (e.g., System.out.println) to print notifications to the console.

Usage: Developers typically configure WireMock to use the ConsoleNotifier class to receive real-time updates about API interactions and mapping changes directly in the console.

Example Usage:
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;

public class WireMockExample {

public static void main(String[] args) {
// Configure WireMock with ConsoleNotifier
WireMockConfiguration wireMockConfig = WireMockConfiguration.options()
.notifier(new ConsoleNotifier(true)); // Enable verbose logging

// Start WireMock server
WireMockServer wireMockServer = new WireMockServer(wireMockConfig);
wireMockServer.start();

// Your WireMock mappings and stubbing configurations go here

// Stop WireMock server
wireMockServer.stop();
}
}

Crafting a Database Logging Powerhouse

Roadmap for constructing a DatabaseLoggingNotifier class. Here's a closer look at the key steps involved:

  1. Extending the Notifier Interface: This forms the foundation of our custom notifier. By inheriting the interface, we gain access to core logging functionalities offered by Wiremock.

  2. Establishing the Database Connection: To interact with the database, our notifier class needs the necessary credentials. This includes the JDBC URL, username, and password for the target database. These connection details are typically stored as variables within the class.

  3. Building the logRequestResponse Method: This is the heart of our custom notifier. It takes center stage when Wiremock receives a request and generates a response. Here's what this method accomplishes:

    • Acquiring Request and Response Objects: The method receives the request and response objects as parameters.
    • Constructing the SQL INSERT Statement: Leveraging the data extracted from the request and response objects, the method meticulously crafts an SQL INSERT statement. This statement is designed to insert the relevant data into a predefined table within your database.
    • Storing Data in the Database: The notifier utilizes a JDBC Connector (e.g., MySQL Connector) to establish a connection to the database. Once connected, it executes the SQL INSERT statement, permanently etching the request and response data into the database table.
Note: Don't forget to import the database connector library. In below example we are using MYSQL database for which connector used is mysql-connector
import com.github.tomakehurst.wiremock.common.Notifier;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DatabaseLoggingNotifier implements Notifier {

private final String jdbcUrl;
private final String username;
private final String password;
private final SimpleDateFormat dateFormat;

public DatabaseLoggingNotifier(String jdbcUrl, String username, String password) {
this.jdbcUrl = jdbcUrl;
this.username = username;
this.password = password;
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
}

@Override
public void info(String message) {
// Ignore other types of messages
}

@Override
public void error(String message) {
// Ignore other types of messages
}

@Override
public void error(String s, Throwable throwable) {

}

public void logRequestResponse(Request request, Response response) {
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
String timestamp = dateFormat.format(new Date());

String insertQuery = "INSERT INTO request_response_log (timestamp, request_url, request_method, request_body, " +
"response_status_code, response_body) VALUES (?, ?, ?, ?, ?, ?)";
try (PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) {
preparedStatement.setString(1, timestamp);
preparedStatement.setString(2, request.getUrl());
preparedStatement.setString(3, request.getMethod().toString());
preparedStatement.setString(4, request.getBodyAsString());
preparedStatement.setInt(5, response.getStatus());
preparedStatement.setString(6, response.getBodyAsString());
preparedStatement.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();

}
}
}

Database Structure

Here we are using MYSQL database. We can use any database for logging, just the compatible connector need to be used to create the connection. Below is the MYSQL database structure used in above example.


CREATE TABLE `request_response_log` (
  `id` int NOT NULL AUTO_INCREMENT,
  `timestamp` timestamp NULL DEFAULT NULL,
  `request_url` varchar(255) DEFAULT NULL,
  `request_method` varchar(10) DEFAULT NULL,
  `request_body` text,
  `response_status_code` int DEFAULT NULL,
  `response_body` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Setting the Stage for Integration

  1. Initializing the Database Logging Notifier:

    • We begin by creating an instance of the DatabaseLoggingNotifier class. This class serves as the bridge between Wiremock and your database.
    • To establish a connection, you'll need to provide the JDBC URL, username, and password for your chosen database. We will be using a MySQL database on localhost.
  2. Instructing Wiremock to Use the Notifier:

    • We need to explicitly tell it to leverage our custom notifier for logging purposes. This involves registering the notifier instance with the Wiremock server.
    • The WireServer.addMockServiceListener method is used to register a listener that's notified whenever a request is received and a response is about to be generated.
public static void main(String[] args) {

DatabaseLoggingNotifier notifier = new DatabaseLoggingNotifier(
"jdbc:mysql://localhost:3306/wiremock_logs?useSSL=false",
"root",
"Yourpassword"
);
WireMockServer wireMockServer = new WireMockServer(WireMockConfiguration.options()

.port(57167).usingFilesUnderDirectory("src/main/resources")
.notifier(notifier));
wireMockServer.addMockServiceRequestListener(notifier::logRequestResponse);
//new ConsoleNotifier(true)
wireMockServer.start();

int port = wireMockServer.port();
System.out.println(port);
configureFor("localhost",57167);
new V3stubs().pathregexstub();


}

Understanding the Registration Process

  • Breaking Down WireServer.addMockServiceListener:

    • This method belongs to the WireServer class, the heart of Wiremock's standalone server functionality.
    • It allows you to register a custom listener that will be notified during crucial events in Wiremock's request-response cycle. These events include receiving a request, processing it against stubs, and preparing the response.
  • The Power of Method References:

    • When registering the listener, we provide a method reference to the logRequestResponse method within your DatabaseLoggingNotifier class.
    • This essentially tells Wiremock, "Whenever there's an interesting event, call this specific method on my custom notifier object."

The Magic Behind logRequestResponse

  1. Retrieving Request and Response Objects:

    • The logRequestResponse method receives the request and response objects as parameters. These objects encapsulate all the juicy details of the interaction, including URLs, methods, headers, bodies, and status codes.
  2. Constructing the SQL INSERT Statement:

    • By leveraging the data extracted from the request and response objects, the method meticulously crafts an SQL INSERT statement. This statement is designed to insert relevant data into the predefined table within your database.
  3. Storing Data in the Database:

    • The notifier utilizes a JDBC Connector (e.g., MySQL Connector) to establish a connection to the database. Once connected, it executes the SQL INSERT statement, permanently etching the request and response data into the database table.

Witnessing the Integration in Action

Here are the key takeaways:

      Verification through Testing:

    • After integrating the custom notifier, it's crucial to test its functionality. Send GET and POST requests to predefined stubs and verifying that the corresponding request and response data are successfully logged in the database table.

    Importance of the Notifier Instance:

  • Ensure you're using the newly created database logging notifier instance when registering the listener with Wiremock. Otherwise, Wiremock might not use your custom logging logic.

Comments

Popular posts from this blog

How to Run WireMock as a Standalone Server: A Step-by-Step Guide

Setting Up a Remote Wiremock Instance on AWS: A Comprehensive Guide

API MOCKING/SERVICE VIRTUALIZATION: HOW DOES IT WORK?