diff --git a/README.md b/README.md index 7482bef..a1676cf 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,60 @@ Support [IBM MQ](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.0.0/com - [IBM MQ](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.0.0/com.ibm.mq.helphome.v90.doc/WelcomePagev9r0.htm) #### Configuration -All settings have to be set in mq-java-exporter\src\main\resources\exporter_config.yaml. -- MQ connection information. Describes MQ connection information. -- Prometheus connection information. Describes Prometheus connection information. -- Monitoring objects. Sets names of objects, that have to be monitored: queues, channels. +All connection and monitoring settings have to be set in exporter_config.yaml file. +Below is an example of a filled configuration file with all possible fields: +```yaml +# MQ connection information ------------------------------- +qmgrConnectionParams: +# Queue manager name. + qmgrName: QM +# Queue manager host. + qmgrHost: hostname +# Queue manager connection port. + qmgrPort: 1414 +# Queue manager connection channel. + qmgrChannel: SYSTEM.DEF.SVRCONN +# Username, which will be used for connection (optional). + user: mqm +# Password, which will be used for connection (optional). + password: mqm +# Use MQCSP for connection? + mqscp: false -1. Fill exporter_config.yaml with your enviroments configuration. +# Prometheus connection information ------------------------------- +prometheusEndpointParams: +# URL and port which will be used to expose metrics for Prometheus. + url: /metrics + port: 8080 + +# Monitoring objects ---------------------------------- +# This block refers to collecting of additional metrics. +# If there are any queues, channels or listeners in the config file below, +# these metrics may be useful for you. (More info about additional metrics is located +# under "MQ PCF API specific statistics" section. +PCFParameters: +# Collect additional metrics? If false, all settings in this section below are ignored. + sendPCFCommands: true +# Use wildcards? If yes, only one PCF command will be send, matching all objects on queue manager. Otherwise, each +# object will be monitored by separate PCF command. + usePCFWildcards: true +# Interval in seconds between sending PCF commands. + scrapeInterval: 10 + +# Monitored queues. +queues: + - QUEUE1 + - QUEUE2 + +# Monitored listeners. +listeners: + - LISTENER01 + +# Monitored channels. +channels: + - MANAGEMENT.CHANNEL +``` #### Build 1. Download current repository. @@ -79,6 +126,7 @@ mvn package ```shell java -jar mq_exporter.jar /opt/mq_exporter/exporter_config.yaml ``` +The only input parameter is the path to your configuration file. ## Metrics #### Platform central processing units diff --git a/pom.xml b/pom.xml index 88846a8..fdc57b2 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ 9.0.0.1 1.23 0.6.0 + 2.11.2 /lib/com.ibm.mq.allclient.jar UTF-8 UTF-8 @@ -41,6 +42,16 @@ simpleclient_common ${prometheus.version} + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + webspheremq_exporter @@ -61,6 +72,10 @@ maven-jar-plugin 3.1.1 + + **/exporter_config.yaml + **/log4j.properties + diff --git a/src/main/java/ru/cinimex/exporter/Config.java b/src/main/java/ru/cinimex/exporter/Config.java index 31b8207..d01764a 100644 --- a/src/main/java/ru/cinimex/exporter/Config.java +++ b/src/main/java/ru/cinimex/exporter/Config.java @@ -1,5 +1,7 @@ package ru.cinimex.exporter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.yaml.snakeyaml.Yaml; import java.io.BufferedReader; @@ -14,6 +16,7 @@ * Class is used for parsing config file. */ public class Config { + private static final Logger logger = LogManager.getLogger(Config.class); private String qmgrName; private String qmgrHost; private int qmgrPort; @@ -37,7 +40,7 @@ public Config(String path) { try { br = new BufferedReader(new FileReader(rawFile)); } catch (FileNotFoundException e) { - System.err.println(String.format("Unable to locate config file. Make sure %s is valid path.", path)); + logger.error("Unable to locate config file. Make sure \"{}\" is a valid path.", path); System.exit(1); } LinkedHashMap config = file.load(br); @@ -59,6 +62,7 @@ public Config(String path) { this.sendPCFCommands = (boolean) pcfParameters.get("sendPCFCommands"); this.usePCFWildcards = (boolean) pcfParameters.get("usePCFWildcards"); this.scrapeInterval = (Integer) pcfParameters.get("scrapeInterval"); + logger.info("Successfully parsed configuration file!"); } public boolean useMqscp() { diff --git a/src/main/java/ru/cinimex/exporter/ExporterLauncher.java b/src/main/java/ru/cinimex/exporter/ExporterLauncher.java index fdee878..9575c36 100644 --- a/src/main/java/ru/cinimex/exporter/ExporterLauncher.java +++ b/src/main/java/ru/cinimex/exporter/ExporterLauncher.java @@ -6,6 +6,8 @@ import com.ibm.mq.MQTopic; import com.ibm.mq.constants.MQConstants; import com.ibm.mq.pcf.PCFMessage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import ru.cinimex.exporter.mq.MQConnection; import ru.cinimex.exporter.mq.MQObject; import ru.cinimex.exporter.mq.MQSubscriberManager; @@ -25,46 +27,51 @@ * Main class of mq exporter tool. Parses config, scans topics, starts subscribers. */ public class ExporterLauncher { + private static final Logger logger = LogManager.getLogger(ExporterLauncher.class); private static final String topicString = "$SYS/MQ/INFO/QMGR/%s/Monitor/METADATA/CLASSES"; private static final int getMsgOpt = MQConstants.MQGMO_WAIT | MQConstants.MQGMO_COMPLETE_MSG | MQConstants.MQGMO_SYNCPOINT; - public static void main(String[] args) throws MQException, IOException { + public static void main(String[] args) { if (args.length == 0) { - System.err.println("It seems that you forgot to specify the path to the config file."); + logger.error("It seems like you forgot to specify path to the config file."); System.exit(1); } Config config = new Config(args[0]); - try { - ArrayList elements = getAllPublishedMetrics(config); - ArrayList monitoringTypes = new ArrayList<>(); - ArrayList objects = new ArrayList<>(); - if (config.sendPCFCommands()) { - if (config.getQueues() != null && config.getQueues().size() > 0) { - monitoringTypes.add(MQObject.MQType.QUEUE); - for (String queueName : config.getQueues()) { - objects.add(new MQObject(queueName, MQObject.MQType.QUEUE)); - } + ArrayList elements = getAllPublishedMetrics(config); + ArrayList monitoringTypes = new ArrayList<>(); + ArrayList objects = new ArrayList<>(); + + if (config.sendPCFCommands()) { + if (config.getQueues() != null && config.getQueues().size() > 0) { + monitoringTypes.add(MQObject.MQType.QUEUE); + for (String queueName : config.getQueues()) { + objects.add(new MQObject(queueName, MQObject.MQType.QUEUE)); + logger.debug("Queue {} was added for additional monitoring.", queueName); } - if (config.getChannels() != null && config.getChannels().size() > 0) { - monitoringTypes.add(MQObject.MQType.CHANNEL); - for (String channelName : config.getChannels()) { - objects.add(new MQObject(channelName, MQObject.MQType.CHANNEL)); - } + } + if (config.getChannels() != null && config.getChannels().size() > 0) { + monitoringTypes.add(MQObject.MQType.CHANNEL); + for (String channelName : config.getChannels()) { + objects.add(new MQObject(channelName, MQObject.MQType.CHANNEL)); + logger.debug("Channel {} was added for additional monitoring.", channelName); } - if (config.getListeners() != null && config.getListeners().size() > 0) { - monitoringTypes.add(MQObject.MQType.LISTENER); - for (String listenerName : config.getListeners()) { - objects.add(new MQObject(listenerName, MQObject.MQType.LISTENER)); - } + } + if (config.getListeners() != null && config.getListeners().size() > 0) { + monitoringTypes.add(MQObject.MQType.LISTENER); + for (String listenerName : config.getListeners()) { + objects.add(new MQObject(listenerName, MQObject.MQType.LISTENER)); + logger.debug("Listener {} was added for additional monitoring.", listenerName); } } - MetricsManager.initMetrics(elements, monitoringTypes); - MQSubscriberManager manager = new MQSubscriberManager(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp()); - manager.runSubscribers(elements, objects, config.sendPCFCommands(), config.usePCFWildcards(), config.getScrapeInterval()); + } + MetricsManager.initMetrics(elements, monitoringTypes); + MQSubscriberManager manager = new MQSubscriberManager(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp()); + manager.runSubscribers(elements, objects, config.sendPCFCommands(), config.usePCFWildcards(), config.getScrapeInterval()); + try { new HTTPServer(new InetSocketAddress("0.0.0.0", config.getEndpPort()), config.getEndpURL(), Registry.getRegistry(), false); - } catch (Exception e) { - System.err.println(e.getMessage()); + } catch (IOException e) { + logger.error("Error occurred during expanding endpoint for Prometheus: ", e); } } @@ -81,9 +88,8 @@ private static ArrayList getAllPublishedMetrics(Config config) { MQGetMessageOptions gmo = new MQGetMessageOptions(); gmo.options = getMsgOpt; gmo.waitInterval = 30000; - connection.establish(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp()); - try { + connection.establish(config.getQmgrHost(), config.getQmgrPort(), config.getQmgrChannel(), config.getQmgrName(), config.getUser(), config.getPassword(), config.useMqscp()); topic = connection.createTopic(String.format(topicString, config.getQmgrName())); MQMessage msg = getEmptyMessage(); topic.get(msg, gmo); @@ -103,8 +109,8 @@ private static ArrayList getAllPublishedMetrics(Config config) { elements.addAll(PCFDataParser.getPCFElements(pcfResponse)); } } - } catch (Exception e) { - System.err.println(e.getMessage()); + } catch (MQException | IOException e) { + logger.error("Failed!", e); } finally { try { if (topic != null && topic.isOpen()) { @@ -112,7 +118,7 @@ private static ArrayList getAllPublishedMetrics(Config config) { } connection.close(); } catch (MQException e) { - System.err.println(String.format("Error occured during disconnecting from topic %s. Error: %s", topic.toString(), e.getStackTrace())); + logger.error("Error occurred during disconnecting from topic {}. Error: ", topic.toString(), e); } } return elements; diff --git a/src/main/java/ru/cinimex/exporter/mq/MQConnection.java b/src/main/java/ru/cinimex/exporter/mq/MQConnection.java index 4d3375c..63c80a3 100644 --- a/src/main/java/ru/cinimex/exporter/mq/MQConnection.java +++ b/src/main/java/ru/cinimex/exporter/mq/MQConnection.java @@ -5,6 +5,8 @@ import com.ibm.mq.MQTopic; import com.ibm.mq.constants.CMQC; import com.ibm.mq.constants.MQConstants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.Hashtable; @@ -12,6 +14,7 @@ * Class represents MQ connection. */ public class MQConnection { + private static final Logger logger = LogManager.getLogger(MQConnection.class); private Hashtable connectionProperties; private MQQueueManager queueManager; @@ -58,13 +61,9 @@ protected static Hashtable createMQConnectionParams(String host, * @param password - password, which is required to establish connection with queue manager (optional). * @param useMQCSP - flag, which indicates, if MQCSP auth should be used. */ - public void establish(String host, int port, String channel, String qmName, String user, String password, boolean useMQCSP) { + public void establish(String host, int port, String channel, String qmName, String user, String password, boolean useMQCSP) throws MQException { connectionProperties = createMQConnectionParams(host, port, channel, user, password, useMQCSP); - try { - queueManager = new MQQueueManager(qmName, connectionProperties); - } catch (MQException e) { - System.err.println(e.getMessage()); - } + queueManager = new MQQueueManager(qmName, connectionProperties); } @@ -74,12 +73,8 @@ public void establish(String host, int port, String channel, String qmName, Stri * @param qmNqme - queue manager's name. * @param connectionProperties - prepared structure with all parameters transformed into queue manager's format. See {@link #createMQConnectionParams(String, int, String, String, String, boolean)} for more info. */ - public void establish(String qmNqme, Hashtable connectionProperties) { - try { - queueManager = new MQQueueManager(qmNqme, connectionProperties); - } catch (MQException e) { - System.err.println(e.getMessage()); - } + public void establish(String qmNqme, Hashtable connectionProperties) throws MQException { + queueManager = new MQQueueManager(qmNqme, connectionProperties); } /** @@ -90,7 +85,7 @@ public void close() { try { queueManager.disconnect(); } catch (MQException e) { - System.err.println(e.getMessage()); + logger.error("Failed!", e); } } } @@ -108,6 +103,7 @@ public MQTopic createTopic(String topic) throws MQException { /** * Returns MQQueueManager object. + * * @return - MQQueueManager object. */ public MQQueueManager getQueueManager() { diff --git a/src/main/java/ru/cinimex/exporter/mq/MQObject.java b/src/main/java/ru/cinimex/exporter/mq/MQObject.java index 0cf7497..a9f2c8c 100644 --- a/src/main/java/ru/cinimex/exporter/mq/MQObject.java +++ b/src/main/java/ru/cinimex/exporter/mq/MQObject.java @@ -2,11 +2,14 @@ import com.ibm.mq.constants.MQConstants; import com.ibm.mq.pcf.PCFMessage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; /** * Class represents MQObject (Queue, channel or listener). It stores object type and all PCFParameters, required for correct request. */ public class MQObject { + private static final Logger logger = LogManager.getLogger(MQObject.class); private String name; private MQType type; private PCFMessage PCFCmd; @@ -41,8 +44,11 @@ public MQObject(String name, MQType type) { PCFCmd.addParameter(MQConstants.MQCACH_CHANNEL_NAME, name); //PCF command would try to retrieve statistics about channel with specific name PCFHeader = MQConstants.MQIACH_CHANNEL_STATUS;//the only statistics we want to know about channel is it's status. break; - default: - //TODO:Exception + default: { + logger.error("Unknown type for MQObject: {}", type.name()); + throw new RuntimeException("Unable to create new MQObject. Received unexpected MQObject type: " + type.name()); + } + } } @@ -50,7 +56,7 @@ public MQObject(String name, MQType type) { * This method returns MQConstant int code, which represents name for input object. * * @param type - object type. - * @return - integer code. + * @return - integer code. Returns -1 if code wasn't found. */ public static int objectNameCode(MQObject.MQType type) { int code = -1; @@ -108,7 +114,5 @@ public PCFMessage getPCFCmd() { /** * This enum represents all supported MQObject types. */ - public enum MQType { - QUEUE, CHANNEL, LISTENER - } + public enum MQType {QUEUE, CHANNEL, LISTENER} } diff --git a/src/main/java/ru/cinimex/exporter/mq/MQPCFSubscriber.java b/src/main/java/ru/cinimex/exporter/mq/MQPCFSubscriber.java index 19f65e8..96555fe 100644 --- a/src/main/java/ru/cinimex/exporter/mq/MQPCFSubscriber.java +++ b/src/main/java/ru/cinimex/exporter/mq/MQPCFSubscriber.java @@ -5,6 +5,8 @@ import com.ibm.mq.pcf.PCFException; import com.ibm.mq.pcf.PCFMessage; import com.ibm.mq.pcf.PCFMessageAgent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import ru.cinimex.exporter.prometheus.metrics.MetricsManager; import ru.cinimex.exporter.prometheus.metrics.MetricsReference; @@ -17,6 +19,7 @@ * retrieve specific statistics, which couldn't be retrieved by MQTopicSubscriber. */ public class MQPCFSubscriber implements Runnable { + private static final Logger logger = LogManager.getLogger(MQPCFSubscriber.class); private MQConnection connection; private String queueManagerName; private MQObject object; @@ -55,16 +58,15 @@ public MQPCFSubscriber(String queueManagerName, Hashtable connec * @param connectionProperties - map with all required connection params. */ private void establishMQConnection(String queueManagerName, Hashtable connectionProperties) { - if (connection == null) { - connection = new MQConnection(); - connection.establish(queueManagerName, connectionProperties); - } - this.queueManagerName = queueManagerName; - //TODO: error handling try { + if (connection == null) { + connection = new MQConnection(); + connection.establish(queueManagerName, connectionProperties); + } + this.queueManagerName = queueManagerName; this.agent = new PCFMessageAgent(connection.getQueueManager()); } catch (MQException e) { - e.printStackTrace(); + logger.error("Error occurred during establishing connection with queue manager: ", e); } } @@ -114,14 +116,12 @@ private void updateMetricsWithWildcards(PCFMessage[] pcfResponse) { updateMetricWithoutWildcards(directPCFResponse[0], objectName); } catch (PCFException e) { //This error means, that channel has status "inactive". + logger.warn("Channel {} is possibly inactive.", objectName); if (e.reasonCode == MQConstants.MQRCCF_CHL_STATUS_NOT_FOUND) { MetricsManager.updateMetric(MetricsReference.getMetricName(object.getType()), MetricsReference.getMetricValue(object.getType(), MQConstants.MQCHS_INACTIVE), queueManagerName, objectName); } - } catch (MQException e) { - //TODO: error handling - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException | MQException e) { + logger.error("Error occurred during sending PCF command: ", e); } } } @@ -130,6 +130,7 @@ private void updateMetricsWithWildcards(PCFMessage[] pcfResponse) { @Override public void run() { try { + logger.debug("Sending PCF command for object type {} with name {}...", object.getType(), object.getName()); PCFMessage[] pcfResponse = agent.send(object.getPCFCmd()); if (!objects.isEmpty()) { updateMetricsWithWildcards(pcfResponse); @@ -138,15 +139,14 @@ public void run() { updateMetricWithoutWildcards(response, object.getName()); } } + logger.debug("PCF response for object type {} with name {} was processed successfully.", object.getType(), object.getName()); } catch (PCFException e) { if (e.reasonCode == MQConstants.MQRCCF_CHL_STATUS_NOT_FOUND) { + logger.warn("Channel {} is possibly inactive.", object.getName()); MetricsManager.updateMetric(MetricsReference.getMetricName(object.getType()), MetricsReference.getMetricValue(object.getType(), MQConstants.MQCHS_INACTIVE), queueManagerName, object.getName()); } - } catch (MQException e) { - //TODO: error handling - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (MQException | IOException e) { + logger.error("Error occurred during sending PCF command: ", e); } } diff --git a/src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java b/src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java index 300a329..b41d238 100644 --- a/src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java +++ b/src/main/java/ru/cinimex/exporter/mq/MQSubscriberManager.java @@ -1,5 +1,8 @@ package ru.cinimex.exporter.mq; +import com.ibm.mq.MQException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import ru.cinimex.exporter.mq.pcf.PCFElement; import java.util.ArrayList; @@ -12,7 +15,7 @@ * Class is used to manage work of all subscribers. */ public class MQSubscriberManager { - + private static final Logger logger = LogManager.getLogger(MQSubscriberManager.class); private Hashtable connectionProperties; private String queueManagerName; private ArrayList subscribers; @@ -36,28 +39,41 @@ public MQSubscriberManager(String host, int port, String channel, String qmName, /** * Creates pool with subscribers and starts them. * - * @param elements - elements, received via MQ monitoring topics. - * @param objects - objects, retrieved from configuration file. + * @param elements - elements, received via MQ monitoring topics. + * @param objects - objects, retrieved from configuration file. * @param sendPCFCommands - this flag indicates, if we should send additional PCF commands (To get queues max depth, channels and listeners statuses). * @param usePCFWildcards - this flag indicates, if we should use wildcards (uses only 1 connection per MQObject type, but longer response processing). - * @param interval - interval in seconds, at which additional PCF commands are sent. + * @param interval - interval in seconds, at which additional PCF commands are sent. */ public void runSubscribers(ArrayList elements, ArrayList objects, boolean sendPCFCommands, boolean usePCFWildcards, int interval) { + logger.info("Launching subscribers..."); subscribers = new ArrayList<>(); int i = 0; for (PCFElement element : elements) { if (!element.requiresMQObject()) { - subscribers.add(i, new Thread(new MQTopicSubscriber(element, queueManagerName, connectionProperties, queueManagerName))); - subscribers.get(i).start(); - i++; + try { + subscribers.add(i, new Thread(new MQTopicSubscriber(element, queueManagerName, connectionProperties, queueManagerName))); + logger.debug("Starting subscriber for {}...", element.getTopicString()); + subscribers.get(i).start(); + logger.debug("Subscriber for {} was started.", element.getTopicString()); + i++; + } catch (MQException e) { + logger.error("Error during creating topic subscriber: ", e); + } } else { for (MQObject object : objects) { if (object.getType() == MQObject.MQType.QUEUE) { PCFElement objElement = new PCFElement(element.getTopicString(), element.getRows()); objElement.formatTopicString(object.getName()); - subscribers.add(i, new Thread(new MQTopicSubscriber(objElement, queueManagerName, connectionProperties, queueManagerName, object.getName()))); - subscribers.get(i).start(); - i++; + try { + subscribers.add(i, new Thread(new MQTopicSubscriber(objElement, queueManagerName, connectionProperties, queueManagerName, object.getName()))); + logger.debug("Starting subscriber for {}...", objElement.getTopicString()); + subscribers.get(i).start(); + logger.debug("Subscriber for {} was started.", objElement.getTopicString()); + i++; + } catch (MQException e) { + logger.error("Error during creating topic subscriber: ", e); + } } } } @@ -81,28 +97,49 @@ public void runSubscribers(ArrayList elements, ArrayList o case LISTENER: listeners.add(object); break; + default: + logger.error("Error during parsing objects list: Unknown object type! Make sure it is one of: {}", MQObject.MQType.values()); } } - MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, queues); - subscribers.add(i++, new Thread(subscriber)); - executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); - - subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, channels); - subscribers.add(i++, new Thread(subscriber)); - executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); - - subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, listeners); - subscribers.add(i++, new Thread(subscriber)); - executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); + if (queues.size() > 0) { + MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, queues); + subscribers.add(i++, new Thread(subscriber)); + logger.debug("Starting subscriber for sending direct PCF commands about queues max depth..."); + executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); + logger.debug("Subscriber for sending direct PCF commands about queues max depth successfully " + "started."); + } + if (channels.size() > 0) { + MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, channels); + subscribers.add(i++, new Thread(subscriber)); + logger.debug("Starting subscriber for sending direct PCF commands about channels statuses..."); + executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); + logger.debug("Subscriber for sending direct PCF commands about channels statuses successfully " + "started."); + } + if (listeners.size() > 0) { + MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, listeners); + subscribers.add(i++, new Thread(subscriber)); + logger.debug("Starting subscriber for sending direct PCF commands about listeners statuses..."); + executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); + logger.debug("Subscriber for sending direct PCF commands about listeners statuses successfully " + "started."); + } } else { for (MQObject object : objects) { MQPCFSubscriber subscriber = new MQPCFSubscriber(queueManagerName, connectionProperties, object); subscribers.add(i++, new Thread(subscriber)); + logger.debug("Starting subscriber for sending direct PCF commands to retrieve statistics about " + "object with type {} and name {}.", object.getType().name(), object.getName()); executor.scheduleAtFixedRate(subscriber, 0, interval, TimeUnit.SECONDS); + logger.debug("Subscriber for sending direct PCF commands to retrieve statistics about " + "object with type {} and name {} successfully started.", object.getType().name(), object.getName()); } } } + if (subscribers.size() > 0) { + logger.info("Successfully launched {} subscribers!", subscribers.size()); + } else { + logger.warn("Didn't launch any subscriber. Exporter finishes it's work!", subscribers.size()); + System.exit(1); + } } + } diff --git a/src/main/java/ru/cinimex/exporter/mq/MQTopicSubscriber.java b/src/main/java/ru/cinimex/exporter/mq/MQTopicSubscriber.java index c6c42a2..341e7f7 100644 --- a/src/main/java/ru/cinimex/exporter/mq/MQTopicSubscriber.java +++ b/src/main/java/ru/cinimex/exporter/mq/MQTopicSubscriber.java @@ -5,6 +5,8 @@ import com.ibm.mq.MQMessage; import com.ibm.mq.MQTopic; import com.ibm.mq.constants.MQConstants; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import ru.cinimex.exporter.mq.pcf.PCFDataParser; import ru.cinimex.exporter.mq.pcf.PCFElement; import ru.cinimex.exporter.prometheus.metrics.MetricsManager; @@ -19,65 +21,67 @@ * MQTopicSubscriber is used to subscribe to specific topic. */ public class MQTopicSubscriber implements Runnable { + private static final Logger logger = LogManager.getLogger(MQTopicSubscriber.class); + private MQTopic topic; + private PCFElement element; + private MQConnection connection; + private String[] labels; - private MQTopic topic; - private PCFElement element; - private MQConnection connection; - private String[] labels; - - /** - * Subscriber constructor - * - * @param element - PCF message data, which is required for parsing statistics. - * @param labels - labels array, which should be used for metrics. - */ - public MQTopicSubscriber(PCFElement element, String queueManagerName, - Hashtable connectionProperties, String... labels) { - this.element = element; - if (connection == null) { - connection = new MQConnection(); - connection.establish(queueManagerName, connectionProperties); - } - this.labels = labels; + /** + * Subscriber constructor + * + * @param element - PCF message data, which is required for parsing statistics. + * @param labels - labels array, which should be used for metrics. + */ + public MQTopicSubscriber(PCFElement element, String queueManagerName, Hashtable connectionProperties, String... labels) throws MQException { + this.element = element; + if (connection == null) { + connection = new MQConnection(); + connection.establish(queueManagerName, connectionProperties); } + this.labels = labels; + } - /** - * Starts subscriber. - */ - public void run() { - try { - topic = connection.createTopic(element.getTopicString()); - MQGetMessageOptions gmo = new MQGetMessageOptions(); - gmo.options = MQConstants.MQGMO_WAIT | MQConstants.MQGMO_COMPLETE_MSG; - gmo.waitInterval = 12000; - while (true) { - try { - MQMessage msg = new MQMessage(); - topic.get(msg, gmo); - HashMap receivedMetrics = PCFDataParser.getParsedData(PCFDataParser.convertToPCF(msg)); - Iterator> it = receivedMetrics.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pair = it.next(); - MetricsManager.updateMetric(MetricsReference.getMetricName(element.getMetricDescription(pair.getKey()), element.requiresMQObject(), element.getRowDatatype(pair.getKey().intValue())), pair.getValue(), labels); - it.remove(); - } - } catch (MQException e) { - if (e.getReason() == MQConstants.MQRC_NO_MSG_AVAILABLE) - System.out.println("No messages in " + element.getTopicString()); + /** + * Starts subscriber. + */ + public void run() { + try { + topic = connection.createTopic(element.getTopicString()); + MQGetMessageOptions gmo = new MQGetMessageOptions(); + gmo.options = MQConstants.MQGMO_WAIT | MQConstants.MQGMO_COMPLETE_MSG; + gmo.waitInterval = 12000; + while (true) { + try { + MQMessage msg = new MQMessage(); + logger.debug("Waiting for message on {} ...", element.getTopicString()); + topic.get(msg, gmo); + logger.debug("Message received on {}", element.getTopicString()); + HashMap receivedMetrics = PCFDataParser.getParsedData(PCFDataParser.convertToPCF(msg)); + Iterator> it = receivedMetrics.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pair = it.next(); + MetricsManager.updateMetric(MetricsReference.getMetricName(element.getMetricDescription(pair.getKey()), element.requiresMQObject(), element.getRowDatatype(pair.getKey().intValue())), pair.getValue(), labels); + it.remove(); } + } catch (MQException e) { + if (e.getReason() == MQConstants.MQRC_NO_MSG_AVAILABLE) + logger.warn("No messages found in {}", element.getTopicString()); + else + logger.error("Error occurred during retrieving message from {}: ", element.getTopicString(), e); } - } catch (MQException e) { - System.out.println("An error occured while trying to get queue object " + element.getTopicString()); - System.err.println(e.getMessage()); - } finally { - if (topic != null && topic.isOpen()) { - try { - topic.close(); - connection.close(); - } catch (MQException e) { - System.err.println(e.getMessage()); - } + } + } catch (MQException e) { + logger.error("Error occurred during establishing connection with topic {}", element.getTopicString(), e); + } finally { + if (topic != null && topic.isOpen()) { + try { + topic.close(); + connection.close(); + } catch (MQException e) { + logger.error("Error occurred during disconnecting from topic {}. Error: ", topic.toString(), e); } } } } +} diff --git a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFClass.java b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFClass.java index d2c4fd0..43707a3 100644 --- a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFClass.java +++ b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFClass.java @@ -1,10 +1,14 @@ package ru.cinimex.exporter.mq.pcf; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** * This class represents PCFMessage parameter, which could be received from * $SYS/MQ/INFO/QMGR/{QMGR_NAME}/Monitor/METADATA/CLASSES MQ topic. */ public class PCFClass { + private static final Logger logger = LogManager.getLogger(PCFClass.class); private int monitorId; private int monitorFlag; private String monitorName; @@ -21,11 +25,13 @@ public class PCFClass { * @param topicString - MQ topic, where detailed information is published (each class has it's own topic) */ protected PCFClass(int monitorId, int monitorFlag, String monitorName, String monitorDesc, String topicString) { - //TODO: add null-checks and -1 checks (warnings, i guess) this.monitorId = monitorId; this.monitorFlag = monitorFlag; this.monitorName = monitorName; this.monitorDesc = monitorDesc; + if (topicString == null) { + logger.warn("Topic string is empty: ", this.toString()); + } this.topicString = topicString; } diff --git a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFDataParser.java b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFDataParser.java index 1f616a3..0599274 100644 --- a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFDataParser.java +++ b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFDataParser.java @@ -4,6 +4,8 @@ import com.ibm.mq.MQMessage; import com.ibm.mq.constants.MQConstants; import com.ibm.mq.pcf.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.IOException; import java.util.ArrayList; @@ -14,6 +16,7 @@ * Class PCFDataParser contains only static methods and was created to simplify work with PCF messages. */ public class PCFDataParser { + private static final Logger logger = LogManager.getLogger(PCFDataParser.class); /** * Method parses PCFMessage, which contains info about all monitoring classes @@ -53,7 +56,7 @@ public static ArrayList getPCFClasses(PCFMessage pcfMessage) { topicString = groupParam.getStringValue(); break; default: - //TODO:add warning + logger.warn("Unknown parameter type was found while parsing PCFClass! Will be ignored. {} = {}", groupParam.getParameterName(), groupParam.getStringValue()); break; } @@ -96,7 +99,7 @@ public static ArrayList getPCFTypes(PCFMessage pcfMessage) { topicString = groupParam.getStringValue(); break; default: - //TODO:add warning + logger.warn("Unknown parameter type was found while parsing PCFType! Will be ignored." + " {} = {}", groupParam.getParameterName(), groupParam.getStringValue()); break; } @@ -140,7 +143,7 @@ public static ArrayList getPCFElements(PCFMessage pcfMessage) { rowDesc = groupParam.getStringValue(); break; default: - //TODO:add warning + logger.warn("Unknown parameter type was found while parsing PCFElement! Will be " + "ignored." + " {} = {}", groupParam.getParameterName(), groupParam.getStringValue()); break; } @@ -171,7 +174,6 @@ public static HashMap getParsedData(PCFMessage pcfMessage) { HashMap data = new HashMap(); while (params.hasMoreElements()) { PCFParameter param = params.nextElement(); - //TODO: Add other types for parsing (look for example here: https://www.ibm.com/developerworks/community/blogs/messaging/entry/A_first_look_at_MQ_Resource_USeage_handling_using_Java?lang=en). switch (param.getParameter()) { case MQConstants.MQCA_Q_MGR_NAME: break; @@ -199,6 +201,10 @@ public static HashMap getParsedData(PCFMessage pcfMessage) { data.put(statistic.getParameter(), new Double(statistic.getLongValue())); break; } + default: { + logger.warn("Unknown parameter type was found while parsing PCF monitoring data! Will be " + "ignored." + " {} = {}", param.getParameterName(), param.getStringValue()); + break; + } } } @@ -213,13 +219,10 @@ public static HashMap getParsedData(PCFMessage pcfMessage) { * @return - converted PCFMessage */ public static PCFMessage convertToPCF(MQMessage message) { - //TODO: add error processing try { return new PCFMessage(message); - } catch (MQException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + } catch (MQException | IOException e) { + logger.error("Unable to convert MQMessage to PCFMessage: ", e); } return null; } diff --git a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFElement.java b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFElement.java index e84efb9..6332f3d 100644 --- a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFElement.java +++ b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFElement.java @@ -1,5 +1,8 @@ package ru.cinimex.exporter.mq.pcf; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.ArrayList; /** @@ -7,6 +10,7 @@ * $SYS/MQ/INFO/QMGR/{QMGR_NAME}/Monitor/{CLASS}/{TYPE} MQ topic. */ public class PCFElement { + private static final Logger logger = LogManager.getLogger(PCFElement.class); private final ArrayList rows; private String sourceTopicString; private String topicString; @@ -25,6 +29,9 @@ public PCFElement(String topicString, ArrayList rows) { this.topicString = topicString; this.rows = rows; this.mqObjectRequired = topicString.contains("%s"); + if (topicString == null) { + logger.warn("Topic string is empty: ", this.toString()); + } } diff --git a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFType.java b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFType.java index 3c895c4..adee0db 100644 --- a/src/main/java/ru/cinimex/exporter/mq/pcf/PCFType.java +++ b/src/main/java/ru/cinimex/exporter/mq/pcf/PCFType.java @@ -1,10 +1,14 @@ package ru.cinimex.exporter.mq.pcf; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + /** * This class represents PCFMessage parameter, which could be received from * $SYS/MQ/INFO/QMGR/{QMGR_NAME}/Monitor/METADATA/{CLASS}/TYPES MQ topic. */ public class PCFType { + private static final Logger logger = LogManager.getLogger(PCFType.class); private String monitorName; private String monitorDesc; private String topicString; @@ -17,10 +21,12 @@ public class PCFType { * @param topicString - MQ topic, where detailed information is published (each type has it's own topic) */ protected PCFType(String monitorName, String monitorDesc, String topicString) { - //TODO: add null-checks and -1 checks (warnings, i guess) this.monitorName = monitorName; this.monitorDesc = monitorDesc; this.topicString = topicString; + if(topicString == null){ + logger.warn("Topic string is empty: ", this.toString()); + } } /** diff --git a/src/main/java/ru/cinimex/exporter/prometheus/HTTPServer.java b/src/main/java/ru/cinimex/exporter/prometheus/HTTPServer.java index 8fdbc1a..7f14912 100644 --- a/src/main/java/ru/cinimex/exporter/prometheus/HTTPServer.java +++ b/src/main/java/ru/cinimex/exporter/prometheus/HTTPServer.java @@ -5,6 +5,9 @@ import com.sun.net.httpserver.HttpServer; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.common.TextFormat; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import ru.cinimex.exporter.ExporterLauncher; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -20,6 +23,7 @@ import java.util.zip.GZIPOutputStream; public class HTTPServer { + private static final Logger logger = LogManager.getLogger(HTTPServer.class); protected final HttpServer server; protected final ExecutorService executorService; @@ -35,6 +39,7 @@ public HTTPServer(InetSocketAddress addr, String url, CollectorRegistry registry executorService = Executors.newFixedThreadPool(5, NamedDaemonThreadFactory.defaultThreadFactory(daemon)); server.setExecutor(executorService); start(daemon); + logger.info("Endpoint {} on port {} successfully expanded!", url, addr.getPort()); } @@ -118,6 +123,7 @@ static class HTTPMetricHandler implements HttpHandler { public void handle(HttpExchange t) throws IOException { + logger.debug("Received request from Prometheus."); String query = t.getRequestURI().getRawQuery(); ByteArrayOutputStream response = this.response.get(); @@ -142,6 +148,7 @@ public void handle(HttpExchange t) throws IOException { response.writeTo(t.getResponseBody()); } t.close(); + logger.debug("Data was sent to Prometheus."); } } diff --git a/src/main/java/ru/cinimex/exporter/prometheus/metrics/MetricsManager.java b/src/main/java/ru/cinimex/exporter/prometheus/metrics/MetricsManager.java index 8777297..985ed5b 100644 --- a/src/main/java/ru/cinimex/exporter/prometheus/metrics/MetricsManager.java +++ b/src/main/java/ru/cinimex/exporter/prometheus/metrics/MetricsManager.java @@ -1,5 +1,7 @@ package ru.cinimex.exporter.prometheus.metrics; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import ru.cinimex.exporter.mq.MQObject; import ru.cinimex.exporter.mq.pcf.PCFElement; import ru.cinimex.exporter.mq.pcf.PCFElementRow; @@ -11,6 +13,7 @@ * Class is used to manage work of all metrics. */ public class MetricsManager { + private static final Logger logger = LogManager.getLogger(MetricsManager.class); private static HashMap metrics; /** @@ -20,6 +23,7 @@ public class MetricsManager { * @param types - Array, which contains all MQObject types, which should be monitored via direct PCFCommands. */ public static void initMetrics(ArrayList elements, ArrayList types) { + logger.debug("Preparing to initialize metrics. {} metrics will be received from MQ topics and {} metrics will be received via direct PCF commands.", elements.size(), types.size()); metrics = new HashMap(); for (PCFElement element : elements) { for (PCFElementRow row : element.getRows()) { @@ -35,21 +39,24 @@ public static void initMetrics(ArrayList elements, ArrayList QUEUE_MANAGER_METRICS_REFERENCE = new HashMap() { { @@ -233,13 +236,14 @@ private static String generateMetricName(String description, boolean requiresObj metricName = metricName.concat("_delta"); break; } + default: { + logger.warn("Unknown metric type: {}", MQConstants.lookup(dataType, "MQIAMO_")); + } } - //TODO:Add warning! - System.out.println("Unknown metric name! Generated new name " + metricName + " from description " + description); + logger.warn("Unknown metric name! Generated new name '{}' from description '{}'", metricName, description); return metricName; } - //TODO: add error processing, think about returnValue possible values. public static double getMetricValue(MQObject.MQType type, int value) { double returnValue = -1; switch (type) { @@ -253,6 +257,7 @@ public static double getMetricValue(MQObject.MQType type, int value) { returnValue = listenerStatus.get(value); break; default: + logger.warn("Unknown metric type {}.", type.name()); break; } return returnValue; diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 0000000..0fd2365 --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,42 @@ +status = warn +name= exporter_log_configuration + +# Give directory path where log files should get stored +property.basePath = ./log/ + +# ConsoleAppender will print logs on console +appender.console.type = Console +appender.console.name = consoleLogger +appender.console.target = SYSTEM_OUT +appender.console.layout.type = PatternLayout + +# Specify the pattern of the logs +appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%c] [%M] [%l] - %msg%n + + +# RollingFileAppender will print logs in file which can be rotated based on time or size +appender.rolling.type = RollingFile +appender.rolling.name = fileLogger +appender.rolling.fileName= ${basePath}app.log +appender.rolling.filePattern= ${basePath}app_%d{yyyyMMdd}.log +appender.rolling.layout.type = PatternLayout +appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} %level [%t] [%c] [%M] [%l] - %msg%n +appender.rolling.policies.type = Policies + +# Rotate log file each day and keep 30 days worth +appender.rolling.policies.time.type = TimeBasedTriggeringPolicy +appender.rolling.policies.time.interval = 1 +appender.rolling.policies.time.modulate = true +appender.rolling.strategy.type = DefaultRolloverStrategy +appender.rolling.strategy.delete.type = Delete +appender.rolling.strategy.delete.basePath = ${basePath} +appender.rolling.strategy.delete.maxDepth = 1 +appender.rolling.strategy.delete.ifLastModified.type = IfLastModified +# Delete files older than 30 days +appender.rolling.strategy.delete.ifLastModified.age = 30d + +# Configure root logger for logging error logs in classes which are in package other than above specified package +rootLogger.level = info +rootLogger.additivity = false +rootLogger.appenderRef.rolling.ref = fileLogger +rootLogger.appenderRef.console.ref = consoleLogger \ No newline at end of file