Custom Event Source
There are times where you need more flexibility when sending an alert to an on-call team member. In some cases, you may need to include different details in the message, or change the alert level or notification priority. In those advanced use-cases, FireHydrant is equipped with the ability to define your own function to parse Signals the way you want them. You can think of these as lambdas that are run for each incoming event into your FireHydrant account, and emit an Event type.
How it works
When FireHydrant receives incoming payloads for the default list of sources we support, we convert them into a common data structure we call an Event. The order looks like this:
HTTP POST https://signals.firehydrant.com/v1/process/{orgkey}
- Parse incoming payload
- Transpose it into an Event object
- Store the Event, and fire any alerts that are relevant for it
For step 3, we have a common list of what we call “transposers” – these are the ones you find on the main page of event sources in FireHydrant. They work for most use cases you may have.
But when these pre-built transposers don’t suit your needs, you need a bit more horsepower. This is why we chose JavaScript to parse payloads that result, ultimately, into the Event data type. The flow changes to be:
HTTP POST https://signals.firehydrant.com/v1/relay/custom-source/{orgkey}
- Load custom JavaScript expression (based on
custom-source
in the URL) - Evaluate and execute the function with the incoming JSON payload
- Take the result of the
transpose
function we ran - Validate the
transpose
function returned a valid Event - Store the Signal, and fire any alerts that are relevant for it
Limitations
- As much as we’d love to give people as much time as they need to process signals, we do require that these scripts evaluate in under 50 milliseconds. Trust us, we don’t dilly dally on these.
- Scripts do not have access to any IO calls (filesystem, network)
- Scripts must be compatible with ECMAScript 5.1
Getting started
You can write your own transposer script to ensure that FireHydrant understands the payload. Create your own custom event source on Signals > Event Sources > Add custom event source.
Transposer function
A valid JavaScript transposer script must:
- Have the function name
transpose
. - Takes in 1
input
as a parameter. - Returns a valid Event Data Model object.
Custom event sources are not limited only to the transpose
function, either. You may define any valid Javascript function or expression and use them. For example, you may have a function to determine the Signal level that should be used.
const levelMap = {
"critical": 2,
"error": 1,
"warning": 1,
"info": 0,
"debug": 0
}
function payloadLevel(payload) {
if (!payload?.level) return 0;
return levelMap[payload.level.toLowerCase()] || 0;
}
Provided input structure
Transpose function takes in input
argument, which consist of request payload and body in the following structure:
{
headers: {
"Content-Type": "application/json"
},
data: {
// The request JSON payload here
}
}
Debugging
It’s very likely that, at some point, you’ll need to debug what’s going on with your custom script. In lieu of a console.log
function, we provide a debug
function that will automatically display the provided argument in the FireHydrant.
The debug
function is a variadic function as well. Meaning you can pass multiple arguments to it.There is a hard limit of 10 debug calls per execution of your script.
Note:
There is a hard limit of 10 debug calls per execution of your script.
Examples
The following are examples of default transpose
functions for different types of monitoring providers.
Alertmanager
/*
* Transpose a payload from a webhook into a signal.
*
* @returns {{summary: string, body: string, level: number, links: string[], images: string[], tags: string[], idempotency_key: string, status: number}} Signal object.
*/
function transpose(input) {
const payload = input.data;
const alerts = payload.alerts;
const commonAnnotations = payload.commonAnnotations;
const commonLabels = payload.commonLabels;
const groupLabels = payload.groupLabels;
const signal = {
summary: commonAnnotations.summary || "Alert from Alertmanager",
body: commonAnnotations.description || "No body provided",
links: alerts.map(a => ({href: a.generatorURL || payload.externalURL, text: a.labels.alertname})),
annotations: {
...(Object.keys(commonAnnotations).reduce((m, k) => {
m[`annotations-${k}`] = commonAnnotations[k];
return m;
}, {})),
...(Object.keys(commonLabels).reduce((m, k) => {
m[`labels-${k}`] = commonLabels[k];
return m;
}, {})),
...(Object.keys(groupLabels).reduce((m, k) => {
m[`group-by-${k}`] = groupLabels[k];
return m;
}, {})),
groupKey: payload.groupKey,
},
idempotency_key: md5(payload.groupKey),
}
return signal;
}
Datadog
const levelMap = {
"critical": 2,
"error": 1,
"warning": 1,
"info": 0,
"debug": 0
}
function payloadLevel(payload) {
if (!payload?.level) return 0;
return levelMap[payload.level.toLowerCase()] || 0;
}
function payloadStatus(s) {
if (!s) return 0;
return s === "Triggered" ? 0 : 1;
}
function payloadTags(tags) {
if (typeof tags === "string") {
return tags.split(",")
}
if (Array.isArray(tags)) {
return tags
}
return []
}
/*
* Transpose a payload from a webhook into a signal.
*
* @returns {{summary: string, body: string, level: number, links: string[], images: string[], tags: string[], idempotency_key: string, status: number}} Signal object.
*/
function transpose(input) {
const payload = input.data;
const signal = {
summary: payload?.summary || "Alert from Datadog",
body: payload?.body || "No body provided",
level: payloadLevel(payload?.level),
links: payload?.links || [],
images: (payload?.images || []).filter(i => !!i.src),
tags: payloadTags(payload?.tags),
idempotency_key: payload?.summary || "",
status: payloadStatus(payload?.status),
}
if (payload?.unique_key) {
signal.idempotency_key = payload?.unique_key;
}
return signal
}
Updated 16 days ago