Building a Logging Connector with Mule XML SDK

Shubham Mude
Cloudwerx
Published in
8 min readMay 18, 2023

--

Connectors are reusable extensions to Mule runtime engine (Mule) that enable’s us to integrate a Mule app with third-party APIs, databases, and standard integration protocols.

MuleSoft provides many connectors to connect to various third-party systems, but in some cases the connector might not be available for connecting to a particular system or we may have a repetitive piece of functionality (error handling, custom logging, etc.) in our organization which can be reused in different projects. In such scenarios we can go ahead and build custom connectors.

Mule XML SDK: It can be used for creating custom modules, similar to the way you create a Mule app. We can use existing Mule components in the module. The framework simply adds a few xml tags such as <module>, <operation>, and <parameter> elements.

Operations: These are basically a set of parameters, the body, and the output. Operations are a type of function which takes multiple parameters, performs a given set of actions, and gives a single output.

Input parameters: A set of parameters that declares the type to be entered when calling the operation.

Body: Body is where the action is performed. It executes the sequence of components, like flows.

Output: It declares an output type for your XML SDK module.

Errors: It declares an error type that the XML SDK can raise in the body.

To create a logging connector using Mule XML SDK, we need to follow the below steps.

Step 1: Run maven archetype command to create a mule project.

To create the project for the connector, run the command below in the command prompt where the project needs to be created. Since there is currently no way to create connector projects from Anypoint Studio, we need to use a Maven archetype.

mvn archetype:generate -DarchetypeGroupId=org.mule.extensions -DarchetypeArtifactId=mule-extensions-xml-archetype -DarchetypeVersion=1.2.0 -DgroupId=<Replace with your org id> -DartifactId=global-logger -DmuleConnectorName=global-logger -DextensionName=global-logger -Dpackage=com.mule.extensions.globallogger

Step 2: Import Project to Anypoint Studio.

Import above project created into Anypoint Studio. Open Anypoint Studio >> File >> Import >> Select global-logger project >> Configure Project Name=global-logger

You’ll find the following xml files and folder structure. In this section, we’ll outline what you’ll find in each folder:

src/main/resources folder:

The resources folder contains a xml file with a sample custom connector module configuration code. We will replace this with logging connector code in further steps.

src/test/munit folder:

This folder contains a pre-written Munit test case for a sample custom connector. We will remove this Munit file for simplicity.

POM file:

Based on the archetype used to create the project, Maven generates the Project Object Model (POM) file. Maven uses the pom.xml file to keep track of all dependencies needed to build a project, including the dependencies version number and location. We have to add items to the POM file during the connector development process to pull in additional libraries.

Step 3: Change pom.xml

  1. Change the below configurations in pom to latest versions:
  • mule.version >> 4.4.0–20230123
  • mule.extensions.maven.plugin.version >> 1.4.1
  • munit.version >> 2.3.6
  • munit.extensions.maven.plugin.version >> 1.1.2

2. Change global logger version to 1.0.0

3. Add spring module mule-module-spring-config-ee dependency

<dependency>
<groupId>com.mulesoft.mule.runtime.modules</groupId>
<artifactId>mule-module-spring-config-ee</artifactId>
<version>${mule.version}</version>
<scope>provided</scope>
</dependency>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example.com</groupId>
<artifactId>global-logger</artifactId>
<packaging>mule-extension</packaging>
<version>1.0.0</version>

<name>global-logger</name>
<description>A Mule extension done with pure Mule DSL that depends in the runtime operations (set-payload, set-variable, etc.)</description>

<properties>
<mule.version>4.4.0-20230123</mule.version>
<mule.extensions.maven.plugin.version>1.4.1</mule.extensions.maven.plugin.version>

<munit.version>2.3.6</munit.version>
<munit.extensions.maven.plugin.version>1.1.2</munit.extensions.maven.plugin.version>
<munit.input.directory>src/test/munit</munit.input.directory>
<munit.output.directory>${basedir}/target/test-mule/munit</munit.output.directory>

<maven.resources.version>3.0.2</maven.resources.version>
</properties>

<dependencies>
<!--Needed to discover the 'xml-based' XmlExtensionLoader for smart connectors-->
<dependency>
<groupId>org.mule.runtime</groupId>
<artifactId>mule-module-extensions-xml-support</artifactId>
<version>${mule.version}</version>
<scope>provided</scope>
</dependency>
<!--MUnit Dependencies-->
<dependency>
<groupId>com.mulesoft.munit</groupId>
<artifactId>munit-runner</artifactId>
<version>${munit.version}</version>
<classifier>mule-plugin</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mulesoft.mule.runtime.modules</groupId>
<artifactId>mule-module-spring-config-ee</artifactId>
<version>${mule.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.mulesoft.munit</groupId>
<artifactId>munit-extensions-maven-plugin</artifactId>
<version>${munit.extensions.maven.plugin.version}</version>
<executions>
<execution>
<id>munit-extension-test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<dynamicPorts>
<dynamicPort>a.dynamic.port</dynamicPort>
</dynamicPorts>
<environmentVariables>
<MY_ENV>envVar</MY_ENV>
</environmentVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven.resources.version}</version>
<executions>
<execution>
<id>copy-munit-resources</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${munit.output.directory}</outputDirectory>
<resources>
<resource>
<directory>${munit.input.directory}</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>

<plugins>
<plugin>
<groupId>org.mule.runtime.plugins</groupId>
<artifactId>mule-extensions-maven-plugin</artifactId>
<version>${mule.extensions.maven.plugin.version}</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>com.mulesoft.munit</groupId>
<artifactId>munit-extensions-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Step 4: Edit module-global-logger.xml

Set logging Payload: Logging payload is created mapping information passed as a parameter to connector configuration. Refer payloadToLog variable.

○ Note: Sensitive information like customer details are masked while logging the payload. Refer payloadToLogWithMask variable.

Log Payload: According to log level configured as a request parameter the log will be printed in console log.

Define Operation: Operations are defined to log messages from a project.

Define log level type schema catalog XML: To create a drop down for Log level Enum values (“DEBUG”, “ERROR”, “INFO”, “TRACE”, “WARN”), add module-global-logger-catalog.xml and log-level-type-schema.json file. And set logLevel parameter type as log-level-type as per above screenshot.

Below are the XML files after above updates:

  • module-global-logger.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<module name="global-logger"
category="SELECT"
prefix="module-global-logger"
doc:description="This module relies in runtime provided components"

xmlns="http://www.mulesoft.org/schema/mule/module"
xmlns:mule="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:tns="http://www.mulesoft.org/schema/mule/module-global-logger"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/module http://www.mulesoft.org/schema/mule/module/current/mule-module.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/module-global-logger http://www.mulesoft.org/schema/mule/module-global-logger/current/mule-module-global-logger.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd">

<mule:sub-flow name="logging-main-flow" doc:id="d9d3d852-48c1-4162-864b-50b388523215" >
<ee:transform doc:name="Set payloadToLog" doc:id="7c6fa4b3-42a3-46f3-875e-71e4367b7f37" >
<ee:message >
</ee:message>
<ee:variables >
<ee:set-variable variableName="paylodToLog" ><![CDATA[%dw 2.0
output application/json skipNullOn="everywhere"
---
{
logLevel: vars.logLevel default "INFO",
timestamp: now(),
correlationId: if(isEmpty(vars.correlationId)) correlationId else vars.correlationId,
apiName: vars.apiName,
apilayerName: vars.apilayerName,
requestURI: vars.requestURI,
remoteAddress: vars.remoteAddress,
httpMethod: vars.httpMethod,
flowName: vars.flowName,
businessProcessName: vars.businessProcessName,
downstreamResponse: vars.downstreamResponse,
logMessage: vars.logMessage
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<ee:transform doc:name="Set payloadToLogWithMask" doc:id="c8f88ce6-6a95-4ab8-a678-edc3de415586">
<ee:variables>
<ee:set-variable variableName="payloadToLogWithMask" ><![CDATA[%dw 2.0
output application/json
import mapLeafValues from dw::util::Tree
var fieldsToMask = vars.maskParams
---
(if (fieldsToMask != null) vars.paylodToLog mapLeafValues ((value, path) -> if (fieldsToMask contains path[-1].selector) "*******" else value) else vars.paylodToLog)
]]></ee:set-variable>
</ee:variables>
</ee:transform>
<mule:choice doc:name="Log Level" doc:id="36800581-0b12-46ba-b562-200458617864">
<mule:when expression='#[upper(vars.logLevel) == "DEBUG"]'>
<mule:logger level="DEBUG" doc:name="Log DEBUG" doc:id="bfca84bb-3132-479c-b09d-0b10e632a6a9" message="#[vars.payloadToLogWithMask]"/>
</mule:when>
<mule:when expression='#[upper(vars.logLevel) == "ERROR"]'>
<mule:logger level="ERROR" doc:name="Log ERROR" doc:id="c1f5079a-34b4-45e1-8914-fcd7bf5e209a" message="#[vars.payloadToLogWithMask]"/>
</mule:when>
<mule:when expression='#[upper(vars.logLevel) == "TRACE"]'>
<mule:logger level="TRACE" doc:name="Log TRACE" doc:id="b9f31c58-cb01-42c0-a728-ae7f990d2595" message="#[vars.payloadToLogWithMask]"/>
</mule:when>
<mule:when expression='#[upper(vars.logLevel) == "WARN"]'>
<mule:logger level="WARN" doc:name="Log WARN" doc:id="81c1f6e0-30da-4588-8059-41160adc31d2" message="#[vars.payloadToLogWithMask]"/>
</mule:when>
<mule:otherwise>
<mule:logger level="INFO" doc:name="Log INFO" doc:id="370b9641-b8d1-4341-b8be-f0922029ee87" message="#[vars.payloadToLogWithMask]"/>
</mule:otherwise>
</mule:choice>
</mule:sub-flow>

<operation name="LogMessage" doc:description="This operation builds a standard message and logs into the cloudhub log file">
<parameters>
<parameter name="logLevel" type="log-level-type" displayName="Log Level" defaultValue="DEBUG" doc:description="Select the appropriate log level"/>
<parameter name="correlationId" type="string" displayName="Correlation Id" use="OPTIONAL" defaultValue="#[correlationId]" doc:description="Pass Correlation Id"/>
<parameter name="apiName" type="string" displayName="API Name" use="OPTIONAL" defaultValue="#[p('api.name')]" doc:description="API Name"/>
<parameter name="step" type="string" displayName="Step" use="OPTIONAL" doc:description="Step of log"/>
<parameter name="requestURI" type="any" displayName="Request URI" use="OPTIONAL" defaultValue="#[attributes.requestUri]" doc:description="HTTP Request URI"/>
<parameter name="remoteAddress" type="any" displayName="Remote Address" use="OPTIONAL" defaultValue="#[attributes.remoteAddress]" doc:description="IP Address of the calling System"/>
<parameter name="httpMethod" type="any" displayName="HTTP Method" use="OPTIONAL" defaultValue="#[attributes.method]" doc:description="HTTP Method"/>
<parameter name="flowName" type="any" displayName="Flow Name" use="OPTIONAL" defaultValue="#[flow.name]" doc:description="Logging flow Name or Errored flow name"/>
<parameter name="businessProcessName" type="string" displayName="Business Process Name" use="OPTIONAL" defaultValue="#[vars.businessProcessName]" doc:description="Business Process Name"/>
<parameter name="downstreamResponse" type="any" displayName="Downstream Response" use="OPTIONAL" doc:description="Downstream Response"/>
<parameter name="logMessage" type="any" displayName="Log Message" use="OPTIONAL" doc:description="Message to log like payload, queryParams, headers"/>
<parameter name="maskParams" type="string" displayName="Mask Parameters" use="OPTIONAL" defaultValue="#[p('mask.parameters')]" example="abc,xyz" doc:description="Pass parameters to mask"/>
</parameters>
<body>
<mule:async doc:name="Async" doc:id="1e9e0d64-07dc-4967-bdc9-2f03687e9d24" >
<mule:flow-ref doc:name="call-logging-main-flow" doc:id="d9d3d852-48c1-4162-864b-50b388523215" name="logging-main-flow"/>
</mule:async>
</body>
<errors>
<error type="ANY"/>
</errors>
</operation>

</module>
  • log-level-type-schema.json:
{
"type": "string",
"enum": ["DEBUG", "ERROR", "INFO", "TRACE", "WARN"]
}
  • module-global-logger-catalog.xml
<?xml version="1.0" encoding="UTF-8"?>
<catalogs xmlns="http://www.mulesoft.org/schema/mule/types" >

<catalog name="log-level-type" format="application/json">
<schema format="application/json+schema" location="./log-level-type-schema.json" />
</catalog>

</catalogs>

Step 5: Clean and install project

Now go to your global-logger workspace and run the below command to build a jar.

- mvn clean install

Step 6: Add dependency into a new project

Now go to your Mule application project where you need to add the logging connector and add the below dependency into the pom.xml dependencies section.

<dependency>
<groupId>[<<Replace with org id>>]</groupId>
<artifactId>global-logger</artifactId>
<version>1.0.0</version>
<classifier>mule-plugin</classifier>
</dependency>

The global-logger connector with Log Message operation is now available in your project.

Note: Masked Parameters should be passed as comma separated values (e.g. id, phone, etc.)

Conclusion:

In this blog, we learned how to create a custom logging connector in Mule 4. Global logging connector promotes reusability, maintainability and provides security to customer data by masking values.

Happy Learning!

Feel free to drop your queries on my below communication channels

Gmail: shubham.mude7078@gmail.com
Linkedin: https://www.linkedin.com/in/shubhammude/

Special Note: If you found our information valuable and would like to hear more about how Cloudwerx can help take your business to the next level, we’d love to hear from you. Get in touch with us at www.cloudwerx.co or hello@cloudwerx.co.

--

--