Skip to content

Commit 3d5f6c1

Browse files
authored
PagerDuty Notification Improvements (6.3) (#24558)
* PagerDuty Notification Improvements (#24504) * Add customizable incident title and fully configurable incident key * MessageFactory tests * changelog * Remove unused StreamService (cherry picked from commit b216af7) * Fix merge conflict resolution errors
1 parent aa012e4 commit 3d5f6c1

File tree

12 files changed

+422
-105
lines changed

12 files changed

+422
-105
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type = "a"
2+
message = "Added multiple PagerDuty Notification improvements: custom incident title, fully customizable incident key, and use Replay Search URL for link instead of generic stream search."
3+
4+
issues = ["24328"]
5+
pulls = ["24504"]

graylog2-server/src/main/java/org/graylog/integrations/pagerduty/PagerDutyNotification.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.graylog.integrations.pagerduty;
1818

1919
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import jakarta.inject.Inject;
2021
import org.graylog.events.notifications.EventNotification;
2122
import org.graylog.events.notifications.EventNotificationContext;
2223
import org.graylog.events.notifications.EventNotificationException;
@@ -29,8 +30,6 @@
2930
import org.graylog2.notifications.NotificationService;
3031
import org.graylog2.plugin.system.NodeId;
3132

32-
import jakarta.inject.Inject;
33-
3433
import java.io.IOException;
3534
import java.util.List;
3635
import java.util.Locale;
@@ -71,7 +70,7 @@ public void execute(EventNotificationContext ctx) throws EventNotificationExcept
7170
try {
7271
PagerDutyResponse response = pagerDutyClient.enqueue(payloadString);
7372
List<String> errors = response.getErrors();
74-
if (errors != null && errors.size() > 0) {
73+
if (errors != null && !errors.isEmpty()) {
7574
throw new IllegalStateException(
7675
"There was an error triggering the PagerDuty event, details: " + errors);
7776
}

graylog2-server/src/main/java/org/graylog/integrations/pagerduty/PagerDutyNotificationConfig.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.fasterxml.jackson.annotation.JsonTypeName;
2323
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2424
import com.google.auto.value.AutoValue;
25+
import jakarta.annotation.Nullable;
2526
import org.graylog.events.contentpack.entities.EventNotificationConfigEntity;
2627
import org.graylog.events.event.EventDto;
2728
import org.graylog.events.notifications.EventNotificationConfig;
@@ -33,6 +34,7 @@
3334

3435
import java.net.URI;
3536
import java.net.URISyntaxException;
37+
import java.util.Optional;
3638

3739
/**
3840
* Configuration class for Pager Duty notifications.
@@ -51,6 +53,8 @@ public abstract class PagerDutyNotificationConfig implements EventNotificationCo
5153
static final String FIELD_KEY_PREFIX = "key_prefix";
5254
static final String FIELD_CLIENT_NAME = "client_name";
5355
static final String FIELD_CLIENT_URL = "client_url";
56+
static final String FIELD_PAGER_DUTY_TITLE = "pager_duty_title";
57+
static final String FIELD_INCIDENT_KEY = "incident_key";
5458

5559
@JsonProperty(FIELD_ROUTING_KEY)
5660
public abstract String routingKey();
@@ -67,6 +71,12 @@ public abstract class PagerDutyNotificationConfig implements EventNotificationCo
6771
@JsonProperty(FIELD_CLIENT_URL)
6872
public abstract String clientUrl();
6973

74+
@JsonProperty(FIELD_PAGER_DUTY_TITLE)
75+
public abstract Optional<String> pagerDutyTitle();
76+
77+
@JsonProperty(FIELD_INCIDENT_KEY)
78+
public abstract Optional<String> incidentKey();
79+
7080
@JsonIgnore
7181
public JobTriggerData toJobTriggerData(EventDto dto) {
7282
return EventNotificationExecutionJob.Data.builder().eventDto(dto).build();
@@ -87,8 +97,8 @@ public ValidationResult validate() {
8797
else if (routingKey().length() != 32) {
8898
validation.addError(FIELD_ROUTING_KEY, "Routing Key must be 32 characters long.");
8999
}
90-
if (customIncident() && keyPrefix().isEmpty()) {
91-
validation.addError(FIELD_KEY_PREFIX, "Incident Key Prefix cannot be empty when Custom Incident Key is selected.");
100+
if (customIncident() && keyPrefix().isEmpty() && incidentKey().isEmpty()) {
101+
validation.addError(FIELD_KEY_PREFIX, "Incident Key or Incident Key Prefix must be provided when Custom Incident Key is selected.");
92102
}
93103
if (clientName().isEmpty()) {
94104
validation.addError(FIELD_CLIENT_NAME, "Client Name cannot be empty.");
@@ -122,19 +132,25 @@ public static PagerDutyNotificationConfig.Builder create() {
122132
}
123133

124134
@JsonProperty(FIELD_ROUTING_KEY)
125-
public abstract PagerDutyNotificationConfig.Builder routingKey(String routingKey);
135+
public abstract Builder routingKey(String routingKey);
126136

127137
@JsonProperty(FIELD_CUSTOM_INCIDENT)
128-
public abstract PagerDutyNotificationConfig.Builder customIncident(boolean customIncident);
138+
public abstract Builder customIncident(boolean customIncident);
129139

130140
@JsonProperty(FIELD_KEY_PREFIX)
131-
public abstract PagerDutyNotificationConfig.Builder keyPrefix(String keyPrefix);
141+
public abstract Builder keyPrefix(String keyPrefix);
132142

133143
@JsonProperty(FIELD_CLIENT_NAME)
134-
public abstract PagerDutyNotificationConfig.Builder clientName(String clientName);
144+
public abstract Builder clientName(String clientName);
135145

136146
@JsonProperty(FIELD_CLIENT_URL)
137-
public abstract PagerDutyNotificationConfig.Builder clientUrl(String clientUrl);
147+
public abstract Builder clientUrl(String clientUrl);
148+
149+
@JsonProperty(FIELD_PAGER_DUTY_TITLE)
150+
public abstract Builder pagerDutyTitle(@Nullable String pagerDutyTitle);
151+
152+
@JsonProperty(FIELD_INCIDENT_KEY)
153+
public abstract Builder incidentKey(@Nullable String incidentKey);
138154

139155
public abstract PagerDutyNotificationConfig build();
140156
}
@@ -149,6 +165,8 @@ public EventNotificationConfigEntity toContentPackEntity(
149165
.keyPrefix(ValueReference.of(keyPrefix()))
150166
.clientName(ValueReference.of(clientName()))
151167
.clientUrl(ValueReference.of(clientUrl()))
168+
.pagerDutyTitle(ValueReference.ofNullable(pagerDutyTitle().orElse(null)))
169+
.incidentKey(ValueReference.ofNullable(incidentKey().orElse(null)))
152170
.build();
153171
}
154172
}

graylog2-server/src/main/java/org/graylog/integrations/pagerduty/PagerDutyNotificationConfigEntity.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
import com.fasterxml.jackson.annotation.JsonTypeName;
2222
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2323
import com.google.auto.value.AutoValue;
24+
import jakarta.annotation.Nullable;
2425
import org.graylog.events.contentpack.entities.EventNotificationConfigEntity;
2526
import org.graylog.events.notifications.EventNotificationConfig;
2627
import org.graylog2.contentpacks.model.entities.EntityDescriptor;
2728
import org.graylog2.contentpacks.model.entities.references.ValueReference;
2829

2930
import java.util.Map;
31+
import java.util.Optional;
3032

3133
/**
3234
* Configuration entity for PagerDuty notification events.
@@ -55,6 +57,12 @@ public abstract class PagerDutyNotificationConfigEntity implements EventNotifica
5557
@JsonProperty(PagerDutyNotificationConfig.FIELD_CLIENT_URL)
5658
public abstract ValueReference clientUrl();
5759

60+
@JsonProperty(PagerDutyNotificationConfig.FIELD_PAGER_DUTY_TITLE)
61+
public abstract Optional<ValueReference> pagerDutyTitle();
62+
63+
@JsonProperty(PagerDutyNotificationConfig.FIELD_INCIDENT_KEY)
64+
public abstract Optional<ValueReference> incidentKey();
65+
5866
public static Builder builder() {
5967
return Builder.create();
6068
}
@@ -84,6 +92,12 @@ public static Builder create() {
8492
@JsonProperty(PagerDutyNotificationConfig.FIELD_CLIENT_URL)
8593
public abstract Builder clientUrl(ValueReference clientUrl);
8694

95+
@JsonProperty(PagerDutyNotificationConfig.FIELD_PAGER_DUTY_TITLE)
96+
public abstract Builder pagerDutyTitle(@Nullable ValueReference pagerDutyTitle);
97+
98+
@JsonProperty(PagerDutyNotificationConfig.FIELD_INCIDENT_KEY)
99+
public abstract Builder incidentKey(@Nullable ValueReference incidentKey);
100+
87101
public abstract PagerDutyNotificationConfigEntity build();
88102
}
89103

@@ -97,6 +111,8 @@ public EventNotificationConfig toNativeEntity(
97111
.keyPrefix(keyPrefix().asString(parameters))
98112
.clientName(clientName().asString(parameters))
99113
.clientUrl(clientUrl().asString(parameters))
114+
.pagerDutyTitle(pagerDutyTitle().map(vr -> vr.asString(parameters)).orElse(null))
115+
.incidentKey(incidentKey().map(vr -> vr.asString(parameters)).orElse(null))
100116
.build();
101117
}
102118
}

graylog2-server/src/main/java/org/graylog/integrations/pagerduty/client/MessageFactory.java

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,30 @@
1616
*/
1717
package org.graylog.integrations.pagerduty.client;
1818

19+
import com.floreysoft.jmte.Engine;
1920
import com.google.common.collect.ImmutableList;
21+
import jakarta.inject.Inject;
22+
import jakarta.inject.Named;
2023
import org.apache.commons.lang3.StringUtils;
2124
import org.graylog.events.notifications.EventNotificationContext;
2225
import org.graylog.events.notifications.EventNotificationModelData;
2326
import org.graylog.events.notifications.EventNotificationService;
24-
import org.graylog.events.processor.EventDefinitionDto;
25-
import org.graylog.events.processor.aggregation.AggregationEventProcessorConfig;
27+
import org.graylog.events.notifications.TemplateModelProvider;
2628
import org.graylog.integrations.pagerduty.PagerDutyNotificationConfig;
2729
import org.graylog.integrations.pagerduty.dto.Link;
2830
import org.graylog.integrations.pagerduty.dto.PagerDutyMessage;
2931
import org.graylog2.plugin.MessageSummary;
30-
import org.graylog2.plugin.streams.Stream;
31-
import org.graylog2.streams.StreamService;
32-
33-
import jakarta.inject.Inject;
32+
import org.graylog2.web.customization.CustomizationConfig;
33+
import org.joda.time.DateTimeZone;
3434

3535
import java.net.MalformedURLException;
36-
import java.net.URL;
36+
import java.net.URI;
37+
import java.net.URISyntaxException;
3738
import java.util.Arrays;
3839
import java.util.HashMap;
3940
import java.util.List;
4041
import java.util.Locale;
4142
import java.util.Map;
42-
import java.util.stream.Collectors;
4343

4444
/**
4545
* Factory class for PagerDuty messages, heavily based on the works of the cited authors.
@@ -51,79 +51,82 @@
5151
* @author Edgar Molina
5252
*/
5353
public class MessageFactory {
54-
private static final List<String> PAGER_DUTY_PRIORITIES = Arrays.asList("info", "warning", "critical");
54+
private static final List<String> PAGER_DUTY_PRIORITIES = Arrays.asList("info", "warning", "critical", "critical");
5555

56-
private final StreamService streamService;
5756
private final EventNotificationService eventNotificationService;
57+
private final CustomizationConfig customizationConfig;
58+
private final Engine templateEngine;
59+
private final TemplateModelProvider templateModelProvider;
5860

5961
@Inject
60-
MessageFactory(StreamService streamService, EventNotificationService eventNotificationService) {
61-
this.streamService = streamService;
62+
MessageFactory(EventNotificationService eventNotificationService,
63+
CustomizationConfig customizationConfig,
64+
@Named("JsonSafe") Engine jsonTemplateEngine,
65+
TemplateModelProvider templateModelProvider) {
6266
this.eventNotificationService = eventNotificationService;
67+
this.customizationConfig = customizationConfig;
68+
this.templateEngine = jsonTemplateEngine;
69+
this.templateModelProvider = templateModelProvider;
6370
}
6471

6572
public PagerDutyMessage createTriggerMessage(EventNotificationContext ctx) {
6673
final ImmutableList<MessageSummary> backlog = eventNotificationService.getBacklogForEvent(ctx);
6774
final EventNotificationModelData modelData = EventNotificationModelData.of(ctx, backlog);
6875
final PagerDutyNotificationConfig config = (PagerDutyNotificationConfig) ctx.notificationConfig();
76+
final Map<String, Object> messageModel = getCustomMessageModel(ctx, backlog);
6977

70-
String eventTitle = modelData.eventDefinitionTitle();
78+
final String eventTitle = config.pagerDutyTitle()
79+
.map(customTitle -> templateEngine.transform(customTitle, messageModel))
80+
.orElse(modelData.eventDefinitionTitle());
7181
String eventPriority = PAGER_DUTY_PRIORITIES.get(0);
7282
int priority = ctx.eventDefinition().get().priority() - 1;
73-
if (priority >= 0 && priority <= 2) {
83+
if (priority >= 0 && priority <= 3) {
7484
eventPriority = PAGER_DUTY_PRIORITIES.get(priority);
7585
}
7686

77-
List<Link> streamLinks =
78-
streamService
79-
.loadByIds(modelData.event().sourceStreams())
80-
.stream()
81-
.map(stream -> buildStreamWithUrl(stream, ctx, config))
82-
.collect(Collectors.toList());
87+
final List<Link> replayLink;
88+
try {
89+
final String replayUrl = StringUtils.appendIfMissing(
90+
config.clientUrl(), "/") + "alerts/" + modelData.event().id() + "/replay-search";
91+
replayLink = List.of(new Link(new URI(replayUrl).toURL(), "Replay Event"));
92+
} catch (URISyntaxException | MalformedURLException e) {
93+
throw new IllegalStateException("Error when building the event replay URL.", e);
94+
}
8395

8496
String dedupKey = "";
8597
if (config.customIncident()) {
86-
dedupKey = String.format(Locale.ROOT,
87-
"%s/%s/%s", config.keyPrefix(), modelData.event().sourceStreams(), eventTitle);
98+
final String formattedPrefix = templateEngine.transform(config.keyPrefix(), messageModel);
99+
final String prefixedIncidentKey = String.format(Locale.ROOT,
100+
"%s/%s/%s", formattedPrefix, modelData.event().sourceStreams(), eventTitle);
101+
// Use the custom incident key if provided, otherwise fall back to the prefixed key.
102+
dedupKey = config.incidentKey()
103+
.map(incidentKeyTemplate -> templateEngine.transform(incidentKeyTemplate, messageModel))
104+
.orElse(prefixedIncidentKey);
88105
}
89106

90-
91-
Map<String, String> payload = new HashMap<String, String>();
92-
payload.put("summary", modelData.event().message());
93-
payload.put("source", "Graylog:" + modelData.event().sourceStreams());
107+
Map<String, Object> payload = new HashMap<>();
108+
payload.put("summary", config.pagerDutyTitle()
109+
.map(customTitle -> templateEngine.transform(customTitle, messageModel))
110+
.orElse(modelData.event().message()));
111+
payload.put("source", customizationConfig.productName() + ":" + modelData.event().sourceStreams());
94112
payload.put("severity", eventPriority);
95113
payload.put("timestamp", modelData.event().eventTimestamp().toString());
96114
payload.put("component", "GraylogAlerts");
97115
payload.put("group", modelData.event().sourceStreams().toString());
98116
payload.put("class", "alerts");
117+
payload.put("custom_details", ctx.event().fields());
99118

100119
return new PagerDutyMessage(
101120
config.routingKey(),
102121
"trigger",
103122
dedupKey,
104123
config.clientName(),
105124
config.clientUrl(),
106-
streamLinks,
125+
replayLink,
107126
payload);
108127
}
109128

110-
private Link buildStreamWithUrl(Stream stream, EventNotificationContext ctx, PagerDutyNotificationConfig config) {
111-
final String graylogUrl = config.clientUrl();
112-
String streamUrl =
113-
StringUtils.appendIfMissing(graylogUrl, "/") + "streams/" + stream.getId() + "/search";
114-
115-
if (ctx.eventDefinition().isPresent()) {
116-
EventDefinitionDto eventDefinitionDto = ctx.eventDefinition().get();
117-
if (eventDefinitionDto.config() instanceof AggregationEventProcessorConfig) {
118-
String query =
119-
((AggregationEventProcessorConfig) eventDefinitionDto.config()).query();
120-
streamUrl += "?q=" + query;
121-
}
122-
}
123-
try {
124-
return new Link(new URL(streamUrl), stream.getTitle());
125-
} catch (MalformedURLException e) {
126-
throw new IllegalStateException("Error when building the stream link URL.", e);
127-
}
129+
private Map<String, Object> getCustomMessageModel(EventNotificationContext ctx, List<MessageSummary> backlog) {
130+
return templateModelProvider.of(ctx, backlog, DateTimeZone.UTC, Map.of("type", PagerDutyNotificationConfig.TYPE_NAME));
128131
}
129132
}

graylog2-server/src/main/java/org/graylog/integrations/pagerduty/dto/PagerDutyMessage.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.fasterxml.jackson.annotation.JsonInclude;
2020
import com.fasterxml.jackson.annotation.JsonProperty;
21+
2122
import java.util.List;
2223
import java.util.Map;
2324

@@ -40,7 +41,7 @@ public class PagerDutyMessage {
4041
@JsonProperty("links")
4142
private final List<Link> links;
4243
@JsonProperty("payload")
43-
private final Map<String, String> payload;
44+
private final Map<String, Object> payload;
4445

4546
public PagerDutyMessage(
4647
String routingKey,
@@ -49,7 +50,7 @@ public PagerDutyMessage(
4950
String client,
5051
String clientUrl,
5152
List<Link> links,
52-
Map<String, String> payload) {
53+
Map<String, Object> payload) {
5354
this.routingKey = routingKey;
5455
this.eventAction = eventAction;
5556
this.dedupKey = dedupKey;
@@ -83,7 +84,7 @@ public List<Link> getLinks() {
8384
return links;
8485
}
8586

86-
public Map<String, String> getPayload() {
87+
public Map<String, Object> getPayload() {
8788
return payload;
8889
}
8990
}

0 commit comments

Comments
 (0)