A recent discussion about logging of client connections, both those that are blocked and those that succeed, got me thinking that you can capture all the data you need to create such a log with a combination of MQ Event messages and MQ Accounting records (on distributed queue managers). This means you can do this with MQEV. We’ve written about writing log files with the MQSCX control language before, but I thought it would be good to have an MQEV example of this too.
CHLAUTH blocking connections
The IBM MQ Queue manager emits an event message whenever it denies access to a channel connection as a result of matching a blocking CHLAUTH rule. This event message contains a number of very useful pieces of information to identify the channel that was blocked. In the case of a client channel connection these are:-
Field Description | MQ PCF Identifier | MQEV script response variable |
Channel Name | MQCACH_CHANNEL_NAME | event.CHANNEL |
Connection Name | MQCACH_CONNECTION_NAME | event.CONNAME |
SSL Peer Name | MQCACH_SSL_PEER_NAME | event.SSLPEER |
SSL Issuer Name | MQCA_SSL_CERT_ISSUER_NAME | event.SSLCERTI |
Client User Identifier | MQCACH_CLIENT_USER_ID | event.CLNTUSER |
Application Type | MQIA_APPL_TYPE | event.APPLTYPE |
Application Name | MQCACF_APPL_NAME | event.APPLNAME |
Accounting data recording all connections
If you enable accounting data collection, especially ACCTMQI, an accounting record is emitted by the queue manager each time a successful connection disconnects, and also periodically if connections are longer lived than the accounting interval. This record contains a number of very useful pieces of information to identify the application that connected. In the case of a client application these are:-
Field Description | MQ PCF Identifier | MQEV script response variable |
Application Name | MQCACF_APPL_NAME | data.ACCTMQI |
User Identifier | MQCACF_USER_IDENTIFIER | data.USERID |
Connection Name | MQCACH_CONNECTION_NAME | data.CONNAME |
Channel Name | MQCACH_CHANNEL_NAME | data.CHANNEL |
Remote Product | MQCACH_REMOTE_PRODUCT | data.RPRODUCT |
Remote Version | MQCACH_REMOTE_VERSION | data.RCVERSION |
With CHLAUTH and ACCTMQI enabled, you can direct MQEV to collect these emitted records and write the data out to a log file with the format of your choosing.
CONNECT 22:50:53 Channel: MQGEM.SVRCONN from '127.0.0.1' for Application: ':\mqm9300\bin64\amqsputc.exe' CONNECT 22:54:57 Channel: MQGEM.SVRCONN from '127.0.0.1' for Application: ':\mqm9300\bin64\amqsputc.exe' BLOCKED 22:55:01 Channel: MQGEM.SVRCONN.TLS from 'GEMWIN10 (127.0.0.1)' for Application: 'MQGem Software MO71'
client_log_2023_05_25.txt
In our example we create a new log file every day by creating the file name using the built-in date formatting function, and then each line we write in the file has a timestamp. This version of the function creates a line in the file suitable for a human to read, but also with eye-catching strings at the start of each line that could be used to sort the file (see example above). An alternative could be to write a CSV file instead of a text file and make the output a comma-separated format instead of just a text string and then you could import it into a spreadsheet later.
Here is an example of how that could look in your mqev.mqx script. First we show you the small function to write out the line to the file.
********************************************************************* * Function to log client channel connections (failed or successful) * Allow = 0 - blocked * Allow = 1 - warning of blocked, thus allowed * Allow = 2 - connection made ********************************************************************* func logClient(ChlName, IPAddr, ApplName, Allow, Time) @filename = "C:\logs\client_log_" + date(_time, "y_mm_d") + ".txt" * Open the logging file in append mode @hf = fopen(@filename, "a") if (@hf = -1) print "Error opening", @filename, "ErroNo=", _errno, _errnostr return endif * Build up the string to write to the logging file @logtime = date(Time, "H:M:S") @chldetails = "Channel: " + @ChlName + " from '" + @IPAddr + + "' for Application: '" + @ApplName + "'" if (@Allow = 2) fprint @hf, "CONNECT", @logtime, @chldetails endif if (@Allow = 1) fprint @hf, "WARNING", @logtime, @chldetails endif if (@Allow = 0) fprint @hf, "BLOCKED", @logtime, @chldetails endif fclose(@hf) endfunc
Having written our logging function, we can call this whenever we catch an appropriate event message, passing in the time the event happened as the time that should be written in the log file:
********************************************************************* * Function for processing an event * ********************************************************************* func MQEVEvent() if (event.EVTYPE = CHANNEL) ************************************ * Really blocked the connection * ************************************ if(event.EVREASON = CHLBLKNOAC OR + event.EVREASON = CHLBLKADDR OR + event.EVREASON = CHLBLKUSER) @Allow = 0 endif ************************************ * Just warned about the connection * ************************************ if(event.EVREASON = CHLBLKWNOAC OR + event.EVREASON = CHLBLKWADDR OR + event.EVREASON = CHLBLKWUSER) @Allow = 1 endif ************************************ * Only looking for client channels * ************************************ if (event.CLNTUSER != ' ') logClient(event.CHANNEL, event.CONNAME, event.APPLNAME, + @Allow, event.EVTIME) endif endif endfunc
And we can also call this whenever we catch an appropriate accounting record, weeding out the MCA channels (application names of runmqchl and amqrmppa, and only logging it on the first record (data.SEQNUM = 0) for a long-lived connection, and passing in the connection time (rather than the time the accounting record was created which is much later) as the time that should be written in the log file:
********************************************************************* * Function for processing an Accounting MQI message * ********************************************************************* func MQEVAcctMQI() if (data.CHANNEL AND + findstri(data.ACCTMQI, "runmqchl") = 0 AND + findstri(data.ACCTMQI, "amqrmmpa") = 0 AND + data.SEQNUM = 0) logClient(data.CHANNEL, data.CONNAME, data.ACCTMQI, 2, + data.CONNTI) endif endfunc
There is, of course, lots more you could do with this. The format of the output file is entirely up to you. If there are other things which are important to log, you can add those. The data available in each type of record that the queue manager emits is shown in the tables at the beginning of the post.
This example has specifically focused on logging client connections, but you could extend this to MCA channels as well by removing the code that ensures only client connections are being dealt with.
You could choose to keep or throw away the event messages and accounting records that you have used to create the file with. An example of discarding an accounting record after you have used it for what you needed is shown in this post.
If you keep the data you can come back to it later and create other files based on DISPLAY command output from MQEV, just as you can with DISPLAY command output from IBM MQ as discussed in this blog post.
Read more about MQEV, and download a copy, from the MQEV Web Page. If you don’t have a licence and would like to try out MQEV before deciding whether to buy then send an email to support@mqgem.com and a 1-month trial licence will be sent to you.