io.uhndata.cards.slacknotifications.SlackNotificationsTask Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.uhndata.cards.slacknotifications;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.json.Json;
import javax.json.JsonObject;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.uhndata.cards.httprequests.HttpRequests;
import io.uhndata.cards.metrics.Metrics;
public class SlackNotificationsTask implements Runnable
{
private static final String SLACK_PERFORMANCE_URL = System.getenv("SLACK_PERFORMANCE_URL");
/** Default log. */
private static final Logger LOGGER = LoggerFactory.getLogger(SlackNotificationsTask.class);
private static final String LABEL_TODAY = "today";
private static final String LABEL_TOTAL = "total";
/** Provides access to resources. */
private final ResourceResolverFactory resolverFactory;
SlackNotificationsTask(final ResourceResolverFactory resolverFactory)
{
this.resolverFactory = resolverFactory;
}
private String buildNotificationLine(String prevValue, Map statMap, String name)
{
String notificationLine = prevValue;
if (notificationLine.length() > 0) {
notificationLine += "\n";
}
notificationLine += "*";
notificationLine += name;
notificationLine += "*";
notificationLine += " -- _Today_: ";
notificationLine += statMap.get(LABEL_TODAY);
notificationLine += ", _Total_: ";
notificationLine += statMap.get(LABEL_TOTAL);
return notificationLine;
}
private void postToSlack(String slackUrl, String msg, String color)
{
try {
JsonObject slackApiReq = Json.createObjectBuilder()
.add("attachments", Json.createArrayBuilder()
.add(Json.createObjectBuilder()
.add("text", msg)
.add("fallback", "Failed to generate a report :(")
.add("color", color)
)
)
.build();
HttpRequests.getPostResponse(slackUrl, slackApiReq.toString(), "application/json");
} catch (IOException e) {
LOGGER.warn("Failed to send performance update to Slack");
}
}
private List getLoggedEvents(ResourceResolver resolver)
{
// Get all the nt:file nodes under /LoggedEvents/
Iterator loggedEventsIter;
loggedEventsIter = resolver.findResources(
"SELECT n.* FROM [nt:file] AS n WHERE isdescendantnode(n, '/LoggedEvents')",
"JCR-SQL2"
);
List errorStackTraces = new ArrayList<>();
while (loggedEventsIter.hasNext()) {
Resource thisResource = loggedEventsIter.next();
Resource jcrContentResource = thisResource.getChild("jcr:content");
if (jcrContentResource == null) {
continue;
}
String thisStackTrace = jcrContentResource.getValueMap().get("jcr:data", "");
errorStackTraces.add(thisStackTrace);
}
return errorStackTraces;
}
private String generateStackTraceMessagePart(List errorStackTraces)
{
String slackNotificationString = "";
Iterator stackTracesIter = errorStackTraces.iterator();
// Include a separator message if there are any stack traces to be printed
if (stackTracesIter.hasNext()) {
slackNotificationString += "\n\n" + "The following errors were logged:";
}
// Include the stack traces text
while (stackTracesIter.hasNext()) {
slackNotificationString += "\n" + "```" + "\n" + stackTracesIter.next() + "\n" + "```";
}
return slackNotificationString;
}
@Override
public void run()
{
LOGGER.debug("Running SlackNotificationsTask");
try {
Map params = new HashMap<>();
params.put(ResourceResolverFactory.SUBSERVICE, "SlackNotifications");
ResourceResolver resolver = this.resolverFactory.getServiceResourceResolver(params);
Map> gatheredStatistics = new TreeMap<>();
// Get all the sling:Folder nodes under /Metrics/
Iterator metricsIter;
metricsIter = resolver.findResources(
"SELECT n.* FROM [sling:Folder] AS n WHERE isdescendantnode(n, '/Metrics')",
"JCR-SQL2"
);
while (metricsIter.hasNext()) {
Resource thisResource = metricsIter.next();
String thisJcrName = thisResource.getName();
String thisHumanName = Metrics.getHumanName(resolver, thisJcrName);
if (thisHumanName == null) {
continue;
}
Map thisMetricValue = Metrics.getAndReset(resolver, thisJcrName);
if (thisMetricValue == null) {
continue;
}
gatheredStatistics.put(thisHumanName, thisMetricValue);
}
// Get all the error stack traces under /LoggedEvents/
List errorStackTraces = getLoggedEvents(resolver);
resolver.close();
// Build the notification update string to be sent to Slack
String slackNotificationString = "";
for (String key : gatheredStatistics.keySet())
{
slackNotificationString = buildNotificationLine(
slackNotificationString,
gatheredStatistics.get(key),
key.replaceAll("^\\{\\d+\\}", "")
);
}
// Include any relevant stack traces
slackNotificationString += generateStackTraceMessagePart(errorStackTraces);
postToSlack(SLACK_PERFORMANCE_URL,
(slackNotificationString.length() == 0)
? "*ERROR*: Could not gather any performance statistics" : slackNotificationString,
(slackNotificationString.length() == 0 || errorStackTraces.size() > 0)
? "#f3db0e" : "#2eb886"
);
} catch (LoginException e) {
LOGGER.warn("Failed to results.next().getPath()");
}
}
}