Skip to content

Instantly share code, notes, and snippets.

@gurpreetatwal
Created January 2, 2024 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gurpreetatwal/1af8fc51918ad1fbf93cdf2bfead7d1a to your computer and use it in GitHub Desktop.
Save gurpreetatwal/1af8fc51918ad1fbf93cdf2bfead7d1a to your computer and use it in GitHub Desktop.
CloudWatch Link Redirect Code
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