I have been trying to get my function app to work. What I am trying to do is integrate Azure Monitor Alerts in to posting in a Slack channel. This is done through a Webhook in Slack.
I can get it working in local environment when I post a x-www-form-urlencoded POST to the function app.
What I want to achieve now is to receive Azure Monitor Alerts in correct format, parse and adjust format to send to the Webhook.
I have big issues to receive the Alert and parse. Can anybody point out how to get the objects parsed correct?
I started with some code to receive POST, this work fine.
import com.microsoft.azure.functions.annotation.*;
import com.microsoft.azure.functions.*;
import java.util.Map;
import java.util.HashMap;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class AzureMonitorAlertFunction {
@FunctionName("AzureMonitorAlertFunction")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed an Azure Monitor alert.");
try {
// Get the alert payload from Azure Monitor
String alertPayload = request.getBody().orElse("");
// Format the alert payload into a Slack message
String slackMessage = "{ \"text\": \"New Azure Monitor Alert: " + alertPayload + "\" }";
// Send the message to Slack using the incoming webhook
String slackWebhookUrl = System.getenv("SLACK_WEBHOOK_URL");
sendSlackMessage(slackWebhookUrl, slackMessage);
return request.createResponseBuilder(HttpStatus.OK).body("Alert sent to Slack successfully!").build();
} catch (Exception e) {
// Log the error
context.getLogger().severe("Error processing Azure Monitor alert: " + e.getMessage());
// Return an error response
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing alert.").build();
}
}
private void sendSlackMessage(String webhookUrl, String message) throws Exception {
try {
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
writer.write(message);
writer.flush();
}
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
throw new RuntimeException("Failed to send alert to Slack. Response code: " + responseCode);
}
conn.disconnect();
} catch (Exception e) {
throw new RuntimeException("Error sending alert to Slack: " + e.getMessage());
}
}
}
Then when I add parsing to receive the Azure Monitor alert, I run into issues... Here's the code
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.azure.functions.annotation.*;
import com.microsoft.azure.functions.*;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonProperty;
public class AlertToSlack {
@FunctionName("AzureMonitorAlertFunction")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
context.getLogger().info("Java HTTP trigger processed an Azure Monitor alert.");
try {
// Get the alert payload from Azure Monitor
String alertPayload = request.getBody().orElse("");
// Parse the JSON payload into an AzureMonitorAlert object
ObjectMapper objectMapper = new ObjectMapper();
AzureMonitorAlert alert = objectMapper.readValue(alertPayload, AzureMonitorAlert.class);
// Format the alert details into a Slack message
String slackMessage = "{ \"text\": \"New Azure Monitor Alert: " +
"Rule: " + alert.getData().getEssentials().getAlertRule() + ", " +
"Severity: " + alert.getData().getEssentials().getSeverity() + "\" }";
// Send the message to Slack using the incoming webhook
String slackWebhookUrl = System.getenv("SLACK_WEBHOOK_URL");
sendSlackMessage(slackWebhookUrl, slackMessage);
return request.createResponseBuilder(HttpStatus.OK).body("Alert sent to Slack successfully!").build();
} catch (Exception e) {
// Log the error
context.getLogger().severe("Error processing Azure Monitor alert: " + e.getMessage());
// Return an error response
return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).body("Error processing alert.").build();
}
}
private void sendSlackMessage(String webhookUrl, String message) throws Exception {
try {
URL url = new URL(webhookUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream(); PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
writer.write(message);
writer.flush();
}
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
throw new RuntimeException("Failed to send alert to Slack. Response code: " + responseCode);
}
conn.disconnect();
} catch (Exception e) {
throw new RuntimeException("Error sending alert to Slack: " + e.getMessage());
}
}
}
class AzureMonitorAlert {
@JsonProperty("schemaId")
private String schemaId;
@JsonProperty("data")
private AlertData data;
@JsonProperty("alertId")
private String alertId;
// Getters and setters for schemaId and data
public String getSchemaId() {
return schemaId;
}
public void setSchemaId(String schemaId) {
this.schemaId = schemaId;
}
public AlertData getData() {
return data;
}
public void setData(AlertData data) {
this.data = data;
}
public String getAlertId() {
return alertId;
}
public void setAlertId(String alertId) {
this.alertId = alertId;
}
}
class AlertData {
@JsonProperty("essentials")
private AlertEssentials essentials;
// Getters and setters for essentials
public AlertEssentials getEssentials() {
return essentials;
}
public void setEssentials(AlertEssentials essentials) {
this.essentials = essentials;
}
}
class AlertEssentials {
@JsonProperty("alertRule")
private String alertRule;
@JsonProperty("severity")
private String severity;
// Getters and setters for alertRule and severity
public String getAlertRule() {
return alertRule;
}
public void setAlertRule(String alertRule) {
this.alertRule = alertRule;
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this.severity = severity;
}
}
Here's an example I tried to POST as raw format:
{
"schemaId": "AzureMonitorMetricAlert",
"data": {
"essentials": {
"alertId": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/microsoft.insights/metricAlerts/{alert-name}",
"alertRule": "alert-name",
"severity": "Sev1",
"signalType": "Metric",
"monitorCondition": "Fired",
"monitoringService": "Platform",
"targetResource": {
"resourceId": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/{resource-provider}/{resource-type}/{resource-name}",
"resourceName": "resource-name",
"resourceType": "resource-type"
},
"timestamp": "2023-09-13T12:34:56.789Z"
},
"result": {
"baseline": 100,
"currentValue": 120,
"dynamicThresholds": {
"lower": 80,
"upper": 150
},
"firedDateTime": "2023-09-13T12:34:56.789Z"
}
},
"context": {
"timestamp": "2023-09-13T12:34:56.789Z",
"id": "unique-id",
"name": "alert-name",
"description": "This is a sample alert description.",
"type": "Microsoft.Insights/metricAlerts",
"conditionType": "SingleResourceMultipleMetricCriteria",
"condition": {
"windowSize": "PT5M",
"allOf": [
{
"metricName": "metric-name",
"aggregation": "Average",
"operator": "GreaterThan",
"threshold": 100
}
]
},
"subscriptionId": "subscription-id",
"resourceGroupName": "resource-group"
}
}
And here's the error message I get:
[2023-09-13T09:26:43.816Z] Java HTTP trigger processed an Azure Monitor alert.
[2023-09-13T09:26:43.818Z] Error processing Azure Monitor alert: Unrecognized field "alertId" (class com.example.azurealerts.AlertEssentials), not marked as ignorable (2 known properties: "alertRule", "severity"])
at [Source: (String)"{
[2023-09-13T09:26:43.818Z] "schemaId": "AzureMonitorMetricAlert",
[2023-09-13T09:26:43.818Z] Function "AzureMonitorAlertFunction" (Id: 7c667744-9513-4cae-83aa-c24a485b9d6f) invoked by Java Worker
[2023-09-13T09:26:43.818Z] "data": {
[2023-09-13T09:26:43.818Z] "essentials": {
[2023-09-13T09:26:43.819Z] "alertId": "/subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/microsoft.insights/metricAlerts/{alert-name",
[2023-09-13T09:26:43.819Z] "alertRule": "alert-name",
[2023-09-13T09:26:43.819Z] Executed 'Functions.AzureMonitorAlertFunction' (Succeeded, Id=7c667744-9513-4cae-83aa-c24a485b9d6f, Duration=10ms)
[2023-09-13T09:26:43.819Z] "severity": "Sev1",
[2023-09-13T09:26:43.819Z] "signalType": "Metric",
[2023-09-13T09:26:43.819Z] "monitorCondition": "Fired",
[2023-09-13T09:26:43.819Z] "monitoringService": "Platform",
[2023-09-13T09:26:43.819Z] "targetResource": {
[2023-09-13T09:26:43.820Z] "resourceId": "/subscriptions/{subscription-id}/resourceGroups/{resource-group"[truncated 1055 chars]; line: 5, column: 19] (through reference chain: com.example.azurealerts.AzureMonitorAlert["data"]->com.example.azurealerts.AlertData["essentials"]->com.example.azurealerts.AlertEssentials["alertId"])
I added all JSON Object attributes i.e.:
import com.fasterxml.jackson.annotation.JsonProperty;
public class AlertEssentials {
@JsonProperty("alertId")
private String alertId;
@JsonProperty("alertRule")
private String alertRule;
@JsonProperty("severity")
private String severity;
@JsonProperty("signalType")
private String signalType;
@JsonProperty("monitorCondition")
private String monitorCondition;
@JsonProperty("monitoringService")
private String monitoringService;
@JsonProperty("alertTargetIDs")
private String[] alertTargetIDs;
@JsonProperty("configurationItems")
private String[] configurationItems;
@JsonProperty("originAlertId")
private String originAlertId;
@JsonProperty("firedDateTime")
private String firedDateTime;
@JsonProperty("description")
private String description;
@JsonProperty("essentialsVersion")
private String essentialsVersion;
@JsonProperty("alertContextVersion")
private String alertContextVersion;
// Getters and setters for all fields
public String getAlertId() {
return alertId;
}
public void setAlertId(String alertId) {
this.alertId = alertId;
}
public String getAlertRule() {
return alertRule;
}
public void setAlertRule(String alertRule) {
this.alertRule = alertRule;
}
public String getSeverity() {
return severity;
}
public void setSeverity(String severity) {
this.severity = severity;
}
public String getSignalType() {
return signalType;
}
public void setSignalType(String signalType) {
this.signalType = signalType;
}
public String getMonitorCondition() {
return monitorCondition;
}
public void setMonitorCondition(String monitorCondition) {
this.monitorCondition = monitorCondition;
}
public String getMonitoringService() {
return monitoringService;
}
public void setMonitoringService(String monitoringService) {
this.monitoringService = monitoringService;
}
public String[] getAlertTargetIDs() {
return alertTargetIDs;
}
public void setAlertTargetIDs(String[] alertTargetIDs) {
this.alertTargetIDs = alertTargetIDs;
}
public String[] getConfigurationItems() {
return configurationItems;
}
public void setConfigurationItems(String[] configurationItems) {
this.configurationItems = configurationItems;
}
public String getOriginAlertId() {
return originAlertId;
}
public void setOriginAlertId(String originAlertId) {
this.originAlertId = originAlertId;
}
public String getFiredDateTime() {
return firedDateTime;
}
public void setFiredDateTime(String firedDateTime) {
this.firedDateTime = firedDateTime;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getEssentialsVersion() {
return essentialsVersion;
}
public void setEssentialsVersion(String essentialsVersion) {
this.essentialsVersion = essentialsVersion;
}
public String getAlertContextVersion() {
return alertContextVersion;
}
public void setAlertContextVersion(String alertContextVersion) {
this.alertContextVersion = alertContextVersion;
}
}
And I think it might be working. I will update shortly
I had missed a lot of variables in my data model. I have not found any schemas for this yet so I will have to do this alert by alert because the json objects seem to change per alert.