Created
January 2, 2024 22:09
-
-
Save gurpreetatwal/1af8fc51918ad1fbf93cdf2bfead7d1a to your computer and use it in GitHub Desktop.
CloudWatch Link Redirect Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Router from '@koa/router'; | |
import { isString } from 'type-guards'; | |
import { DateTime } from 'luxon'; | |
import { URLSearchParams } from 'url'; | |
import fs from 'fs/promises'; | |
const router = new Router(); | |
const aliases = {} as { [key: string]: string }; | |
const relativeTimeUnitMap = { | |
S: 'milliseconds', | |
s: 'seconds', | |
m: 'minutes', | |
h: 'hours', | |
d: 'days', | |
w: 'weeks', | |
M: 'months', | |
y: 'years', | |
}; | |
let indexHtml: string; | |
router.get('/', async (ctx, next) => { | |
if (!indexHtml) { | |
indexHtml = await fs.readFile('./index.html', 'utf8'); | |
} | |
ctx.body = indexHtml; | |
}); | |
router.get('/search', (ctx, next) => { | |
if (Object.keys(ctx.query).length === 0) { | |
ctx.redirect('/'); | |
return; | |
} | |
let { | |
group, | |
start_time, | |
end_time, | |
relative_time, | |
event_time, | |
event_window_duration, | |
filter, | |
} = ctx.query; | |
const parseTime = function (name: string, value: string) { | |
let dt; | |
// parse unix | |
if (/^\d+$/.test(value)) { | |
// this is a bit wacky... | |
const parseMethod = value.length >= 13 ? 'fromMillis' : 'fromSeconds'; | |
dt = DateTime[parseMethod](Number.parseInt(value), { zone: 'utc' }); | |
} | |
// parse sentry time format (long) | |
else if ( | |
/^[A-Za-z]{3} \d{1,2}, \d{4} \d{2}:\d{2}:\d{2}( UTC)?$/.test(value) | |
) { | |
dt = DateTime.fromFormat(value.replace(' UTC', ''), 'MMM d, yyyy HH:mm:ss'); | |
} | |
// parse sentry time format (short) | |
else if (/^[A-Z][a-z]{2} \d{1,2}, \d{2}:\d{2}$/.test(value)) { | |
dt = DateTime.fromFormat(value, 'MMM d, HH:mm'); | |
} | |
// parse Metabase time format | |
else if (/^[A-Z][a-z]+ \d{1,2}, \d{4}, \d{1,2}:\d{2}$/.test(value)) { | |
dt = DateTime.fromFormat(value, 'MMMM d, yyyy, H:mm'); | |
} | |
// parse Metabase time format (with seconds) | |
else if (/^[A-Z][a-z]+ \d{1,2}, \d{4}, \d{1,2}:\d{2}:\d{2}$/.test(value)) { | |
dt = DateTime.fromFormat(value, 'MMMM d, yyyy, H:mm:ss'); | |
} | |
// parse Metabase time format (with ms) | |
else if (/^[A-Z][a-z]+ \d{1,2}, \d{4}, \d{1,2}:\d{2}:\d{2}.\d{3}$/.test(value)) { | |
dt = DateTime.fromFormat(value, 'MMMM d, yyyy, H:mm:ss.SSS'); | |
} | |
// parse ISO | |
else { | |
dt = DateTime.fromISO(value, { zone: 'utc' }); | |
} | |
if (!dt.isValid) { | |
ctx.throw( | |
400, | |
`Invalid value for query parameter: "${name}". ${dt.invalidReason}: ${dt.invalidExplanation}`, | |
{ value }, | |
); | |
} | |
return dt; | |
}; | |
const parseRelativeTime = function (name: string, value: string) { | |
const match = value.match(/^(?<value>\d{0,2})(?<unit>[SsmhdwMy])$/u); | |
if (match === null || !match.groups) { | |
return ctx.throw( | |
400, | |
`Invalid value for query parameter: "${name}". Must match /^\\d{0,2}[SsmhdwMy]$/`, | |
); | |
} | |
const unit = (relativeTimeUnitMap as any)[match.groups.unit!]; | |
return { [unit]: match.groups.value }; | |
}; | |
if (!isString(group)) { | |
return ctx.throw(400, 'Missing required query parameter: "group"'); | |
} | |
ctx.assert( | |
(isString(event_time) && event_time.length > 0) || | |
(isString(relative_time) && relative_time.length > 0) || | |
(isString(start_time) && | |
start_time.length > 0 && | |
isString(end_time) && | |
end_time.length > 0), | |
400, | |
'Missing required "time" query parameters. Please specify event_time, relative_time, or start_time AND end_time.', | |
); | |
// 1. parse group | |
ctx.assert( | |
// https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_CreateLogGroup.html | |
/^[a-zA-Z0-9_\-\/.#]{1,512}$/u.test(group), | |
400, | |
'Invalid value for query parameter: "group"', | |
); | |
// if group does not start with slash, assume it is an alias or a platform cluster group | |
if (!group.startsWith('/')) { | |
// use custom alias if defined, if not assume it a platform cluster group | |
group = group in aliases ? aliases[group] : '/ecs/platform/' + group; | |
} | |
group = encodeURIComponent(group!).replace(/%/g, '$25'); | |
// 2. parse time | |
let start: DateTime; | |
let end: DateTime; | |
if (isString(relative_time) && relative_time.length > 0) { | |
start = DateTime.utc().minus( | |
parseRelativeTime('relative_time', relative_time), | |
); | |
end = DateTime.utc(); | |
} else if (isString(event_time) && event_time.length > 0) { | |
let duration = | |
isString(event_window_duration) && event_window_duration.length > 0 | |
? parseRelativeTime('event_window_duration', event_window_duration) | |
: { minutes: 10 }; | |
start = parseTime('event_time', event_time).minus(duration); | |
end = parseTime('event_time', event_time).plus(duration); | |
} else { | |
start = parseTime('start_time', start_time as string); | |
end = parseTime('end_time', end_time as string); | |
} | |
// wrap filter in quotes if it's a UUIDv4 | |
if ( | |
isString(filter) && | |
/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test( | |
filter, | |
) | |
) { | |
filter = `"${filter}"`; | |
} | |
const query = new URLSearchParams({ | |
filterPattern: filter, | |
start: String(start.toMillis()), | |
end: String(end.toMillis()), | |
}); | |
let url = | |
'https://us-west-2.console.aws.amazon.com/cloudwatch/home?region=us-west-2#logsV2:'; | |
url += `log-groups/log-group/${group}/log-events`; | |
url += encodeURIComponent('?' + query.toString()).replace(/%/g, '$'); | |
ctx.redirect(url); | |
return; | |
}); | |
export default router; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment