有没有办法为 CloudWatch 日志组过滤器生成 AWS 控制台 URL?
Is there a way to generate the AWS Console URLs for CloudWatch Log Group filters?
我想将我的用户直接发送到特定的日志组并进行过滤,但我需要能够生成正确的 URL 格式。比如这个URL
https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/
%252Fmy%252Flog%252Fgroup%252Fgoes%252Fhere/log-events/FfilterPatternD5Bincoming_ip2CBuser_name2CBuser_ipB2CBtimestamp2CBrequestB213DB22GETB2Fhealth_checks2FallB*222CBstatus_codeB3DB5*B7C7CBstatus_codeB3DB4292CBbytes2CBurl2CBuser_agent5DstartD-172800000
将带您进入名为 /my/log/group/goes/here
的日志组,并过滤过去 2 天内使用此模式的消息:
[incoming_ip, user_name, user_ip , timestamp, request != "GET /health_checks/all *", status_code = 5* || status_code = 429, bytes, url, user_agent]
我可以解码部分 URL 但我不知道其他一些字符应该是什么(见下文),但这看起来不像任何标准 HTML编码给我。有人知道这种 URL 格式的 encoder/decoder 吗?
%252F == /
2C == ,
5B == [
5D == ]
3D == =
21 == !
22 == "
2F == _
7C == |
B == +
== &
D == =
F == ?
我创建了一些似乎满足 CloudWatch URL 解析器的 Ruby 代码。我不确定为什么您必须双重转义某些内容,然后将 %
替换为 $
其他内容。我猜这背后有一些原因,但我想不出一个好的方法来做到这一点,所以我只是蛮力强迫它。如果你有更好的东西,或者知道他们为什么这样做,请添加评论。
注意:我测试过的 filter
有点基础,我不确定如果您真的喜欢它可能需要更改什么。
# Basic URL that is the same across all requests
url = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/'
# CloudWatch log group
log_group = '/aws/my/log/group'
# Either specify the instance you want to search or leave it out to search all instances
instance = '/log-events/i-xxxxxxxxxxxx'
OR
instance = '/log-events'
# The filter to apply.
filter = '[incoming_ip, user_name, user_ip , timestamp, request, status_code = 5*, bytes, url, user_agent]'
# Start time. There might be an End time as well but my queries haven't used
# that yet so I'm not sure how it's formatted. It should be pretty similar
# though.
hours = 48
start = "&start=-#{hours*60*60*1000}"
# This will get you the final URL
final = url + CGI.escape(CGI.escape(log_group)) + instance + 'FfilterPatternD' + CGI.escape(CGI.escape(filter)).gsub('%','$') + CGI.escape(start).gsub('%','$')
我不得不做类似的事情来为 lambda 生成返回 link 的日志,并做了以下骇人听闻的事情来创建 link:
const link = `https://${process.env.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups/log-group/${process.env.AWS_LAMBDA_LOG_GROUP_NAME.replace(/\//g, '2F')}/log-events/${process.env.AWS_LAMBDA_LOG_STREAM_NAME.replace('$', '24').replace('[', '5B').replace(']', '5D').replace(/\//g, '2F')}`
我的一个同事发现编码没什么特别的。 它是标准 URI percent encoding 但应用了两次 (2x)。在 javascript 中,您可以使用 encodeURIComponent
函数进行测试:
let inp = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/'
console.log(encodeURIComponent(inp))
console.log(encodeURIComponent(encodeURIComponent(inp)))
这段 javascript 在第二个编码阶段产生了预期的输出:
https%3A%2F%2Fconsole.aws.amazon.com%2Fcloudwatch%2Fhome%3Fregion%3Dus-east-1%23logsV2%3Alog-groups%2Flog-group%2F
https%253A%252F%252Fconsole.aws.amazon.com%252Fcloudwatch%252Fhome%253Fregion%253Dus-east-1%2523logsV2%253Alog-groups%252Flog-group%252F
注意
至少有些位使用双重编码,但不是全部 link。否则所有特殊字符在双重编码后都会占用4个字符,但有些仍然只占用2个字符。希望这有帮助 ;)
我的完整 Javascript 解决方案基于 @isaias-b 的回答,它还在日志上添加了时间戳过滤器:
const logBaseUrl = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group';
const encode = text => encodeURIComponent(text).replace(/%/g, '$');
const awsEncode = text => encodeURIComponent(encodeURIComponent(text)).replace(/%/g, '$');
const encodeTimestamp = timestamp => encode('?start=') + awsEncode(new Date(timestamp).toJSON());
const awsLambdaLogBaseUrl = `${logBaseUrl}/${awsEncode('/aws/lambda/')}`;
const logStreamUrl = (logGroup, logStream, timestamp) =>
`${awsLambdaLogBaseUrl}${logGroup}/log-events/${awsEncode(logStream)}${timestamp ? encodeTimestamp(timestamp) : ''}`;
基于@Pål Brattberg 的回答的 Python 解决方案:
cloudwatch_log_template = "https://{AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region={AWS_REGION}#logsV2:log-groups/log-group/{LOG_GROUP_NAME}/log-events/{LOG_STREAM_NAME}"
log_url = cloudwatch_log_template.format(
AWS_REGION=AWS_REGION, LOG_GROUP_NAME=CLOUDWATCH_LOG_GROUP, LOG_STREAM_NAME=LOG_STREAM_NAME
)
如果您使用任何非法字符,请确保先替换非法字符(参见 OP)。
有点晚了,但这里有一个 python 实现
def get_cloud_watch_search_url(search, log_group, log_stream, region=None,):
"""Return a properly formatted url string for search cloud watch logs
search = "{$.message: "You are amazing"}
log_group = Is the group of message you want to search
log_stream = The stream of logs to search
"""
url = f'https://{region}.console.aws.amazon.com/cloudwatch/home?region={region}'
def aws_encode(value):
"""The heart of this is that AWS likes to quote things twice with some substitution"""
value = urllib.parse.quote_plus(value)
value = re.sub(r"\+", " ", value)
return re.sub(r"%", "$", urllib.parse.quote_plus(value))
bookmark = '#logsV2:log-groups'
bookmark += '/log-group/' + aws_encode(log_group)
bookmark += "/log-events/" + log_stream
bookmark += re.sub(r"%", "$", urllib.parse.quote("?filterPattern="))
bookmark += aws_encode(search)
return url + bookmark
这样您就可以快速验证它。
>>> real = 'https://us-west-2.console.aws.amazon.com/cloudwatch/home?region=us-west-2#logsV2:log-groups/log-group/2Fapp2Fdjango/log-events/productionFfilterPatternD7B24.msg3D2225s25s+messages+to+25s+pk3D25d...227D'
>>> constructed = get_cloud_watch_search_url(None, search='{$.msg="%s%s messages to %s pk=%d..."}', log_group='/app/django', log_stream='production', region='us-west-2')
>>> real == constructed
True
我最近在想生成 cloudwatch insights 时遇到了这个问题URL。以下打字稿版本:
export function getInsightsUrl(
start: Date,
end: Date,
query: string,
sourceGroup: string,
region = "us-east-1"
) {
const p = (m: string) => escape(m);
// encodes inner values
const s = (m: string) => escape(m).replace(/\%/gi, "*");
const queryDetail =
p(`~(end~'`) +
s(end.toISOString()) +
p(`~start~'`) +
s(start.toISOString()) +
p(`~timeType~'ABSOLUTE~tz~'UTC~editorString~'`) +
s(query) +
p(`~isLiveTail~false~queryId~'`) +
s(v4()) +
p(`~source~(~'`) +
s(sourceGroup) +
p(`))`);
return (
`https://console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights` +
escape("?queryDetail=" + queryDetail).replace(/\%/gi, "$")
);
}
我最近在想要生成 cloudwatch 见解时遇到了这个问题 URL。 PHP 以下版本:
<?php
function getInsightsUrl($region = 'ap-northeast-1') {
//
$start = now()->subMinutes(2)->format('Y-m-d\TH:i:s.v\Z');
$end = now()->addMinutes(2)->format('Y-m-d\TH:i:s.v\Z');
$filter = 'INFO';
$logStream = 'xxx_backend_web';
$sourceGroup = '/ecs/xxx_backend_prod';
// $sourceGroup = '/aws/ecs/xxx_backend~\'/ecs/xxx_backend_dev'; // multiple source group
$query =
"fields @timestamp, @message \n" .
"| sort @timestamp desc\n" .
"| filter @logStream like '$logStream'\n" .
"| filter @message like '$filter'\n" .
"| limit 20";
$queryDetail = urlencode(
("~(end~'") .
($end) .
("~start~'") .
($start) .
("~timeType~'ABSOLUTE~tz~'Local~editorString~'") .
($query) .
("~isLiveTail~false~queryId~'") .
("~source~(~'") .
($sourceGroup) .
("))")
);
$queryDetail = preg_replace('/\%/', '$', urlencode("?queryDetail=" . $queryDetail));
return
"https://console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights"
. $queryDetail;
}
首先我要感谢其他人提供的线索。进一步完整解释了如何构建 Log Insights 链接。
总的来说,它只是一个对象结构的奇怪编码连接,其工作方式如下:
?queryDetail=
之后的部分是对象表示,{}
表示为~()
对象被向下移动到原始值,后者转换如下:
encodeURIComponent(value)
以便所有特殊字符都转换为 %xx
replace(/%/g, "*")
以便此编码不受顶级编码的影响
- 如果值类型是
string
- 它是 前缀 不匹配的单引号
举例说明:
"Hello world" -> "Hello%20world" -> "Hello*20world" -> "'Hello*20world"
使用 ~
加入转换后的基元数组,并放入 ~()
构造
然后,在基元转换完成后 - 使用“~”连接对象。
在该字符串之后是 escape()
d(请注意,不是 encodeURIComponent()
被调用,因为它不会在 JS 中转换 ~
)。
之后添加?queryDetail=
。
最后这个字符串我们 encodeURIComponent()
ed 并作为顶部的樱桃 - %
替换为 $
。
让我们看看它在实践中是如何工作的。假设这些是我们的查询参数:
const expression = `fields @timestamp, @message
| filter @message not like 'example'
| sort @timestamp asc
| limit 100`;
const logGroups = ["/application/sample1", "/application/sample2"];
const queryParameters = {
end: 0,
start: -3600,
timeType: "RELATIVE",
unit: "seconds",
editorString: expression,
isLiveTrail: false,
source: logGroups,
};
首先转换图元:
const expression = "'fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20'example'*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100";
const logGroups = ["'*2Fapplication*2Fsample1", "'*2Fapplication*2Fsample2"];
const queryParameters = {
end: 0,
start: -3600,
timeType: "'RELATIVE",
unit: "'seconds",
editorString: expression,
isLiveTrail: false,
source: logGroups,
};
然后,使用 ~
连接对象,所以我们有对象表示字符串:
const objectString = "~(end~0~start~-3600~timeType~'RELATIVE~unit~'seconds~editorString~'fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20'example'*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100~isLiveTrail~false~source~(~'*2Fapplication*2Fsample1~'*2Fapplication*2Fsample2))"
现在我们escape()
它:
const escapedObject = "%7E%28end%7E0%7Estart%7E-3600%7EtimeType%7E%27RELATIVE%7Eunit%7E%27seconds%7EeditorString%7E%27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20%27example%27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100%7EisLiveTrail%7Efalse%7Esource%7E%28%7E%27*2Fapplication*2Fsample1%7E%27*2Fapplication*2Fsample2%29%29"
现在我们附加 ?queryDetail=
前缀:
const withQueryDetail = "?queryDetail=%7E%28end%7E0%7Estart%7E-3600%7EtimeType%7E%27RELATIVE%7Eunit%7E%27seconds%7EeditorString%7E%27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20%27example%27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100%7EisLiveTrail%7Efalse%7Esource%7E%28%7E%27*2Fapplication*2Fsample1%7E%27*2Fapplication*2Fsample2%29%29"
最后我们对其进行 URLencode 并将 %
替换为 $
和 vois la:
const result = "FqueryDetailD7E28end7E07Estart7E-36007EtimeType7E27RELATIVE7Eunit7E27seconds7EeditorString7E27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*2027example27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*201007EisLiveTrail7Efalse7Esource7E287E27*2Fapplication*2Fsample17E27*2Fapplication*2Fsample22929"
当然也可以反向操作
这就是所有人。玩得开心,保重并尽量避免自己做这种奇怪的事情。 :)
由于 Python 贡献与日志组相关,而不与日志洞察力相关,因此这是我的贡献。我想我可以用内部函数做得更好,但这是一个很好的起点:
from datetime import datetime, timedelta
import re
from urllib.parse import quote
def get_aws_cloudwatch_log_insights(query_parameters, aws_region):
def quote_string(input_str):
return f"""{quote(input_str, safe="~()'*").replace('%', '*')}"""
def quote_list(input_list):
quoted_list = ""
for item in input_list:
if isinstance(item, str):
item = f"'{item}"
quoted_list += f"~{item}"
return f"({quoted_list})"
params = []
for key, value in query_parameters.items():
if key == "editorString":
value = "'" + quote(value)
value = value.replace('%', '*')
elif isinstance(value, str):
value = "'" + value
if isinstance(value, bool):
value = str(value).lower()
elif isinstance(value, list):
value = quote_list(value)
params += [key, str(value)]
object_string = quote_string("~(" + "~".join(params) + ")")
scaped_object = quote(object_string, safe="*").replace("~", "%7E")
with_query_detail = "?queryDetail=" + scaped_object
result = quote(with_query_detail, safe="*").replace("%", "$")
final_url = f"https://{aws_region}.console.aws.amazon.com/cloudwatch/home?region={aws_region}#logsV2:logs-insights{result}"
return final_url
示例:
aws_region = "eu-west-1"
query = """fields @timestamp, @message
| filter @message not like 'example'
| sort @timestamp asc
| limit 100"""
log_groups = ["/application/sample1", "/application/sample2"]
query_parameters = {
"end": datetime.utcnow().isoformat(timespec='milliseconds') + "Z",
"start": (datetime.utcnow() - timedelta(days=2)).isoformat(timespec='milliseconds') + "Z",
"timeType": "ABSOLUTE",
"unit": "seconds",
"editorString": query,
"isLiveTrail": False,
"source": log_groups,
}
print(get_aws_cloudwatch_log_insights(query_parameters, aws_region))
我想将我的用户直接发送到特定的日志组并进行过滤,但我需要能够生成正确的 URL 格式。比如这个URL
https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/
%252Fmy%252Flog%252Fgroup%252Fgoes%252Fhere/log-events/FfilterPatternD5Bincoming_ip2CBuser_name2CBuser_ipB2CBtimestamp2CBrequestB213DB22GETB2Fhealth_checks2FallB*222CBstatus_codeB3DB5*B7C7CBstatus_codeB3DB4292CBbytes2CBurl2CBuser_agent5DstartD-172800000
将带您进入名为 /my/log/group/goes/here
的日志组,并过滤过去 2 天内使用此模式的消息:
[incoming_ip, user_name, user_ip , timestamp, request != "GET /health_checks/all *", status_code = 5* || status_code = 429, bytes, url, user_agent]
我可以解码部分 URL 但我不知道其他一些字符应该是什么(见下文),但这看起来不像任何标准 HTML编码给我。有人知道这种 URL 格式的 encoder/decoder 吗?
%252F == /
2C == ,
5B == [
5D == ]
3D == =
21 == !
22 == "
2F == _
7C == |
B == +
== &
D == =
F == ?
我创建了一些似乎满足 CloudWatch URL 解析器的 Ruby 代码。我不确定为什么您必须双重转义某些内容,然后将 %
替换为 $
其他内容。我猜这背后有一些原因,但我想不出一个好的方法来做到这一点,所以我只是蛮力强迫它。如果你有更好的东西,或者知道他们为什么这样做,请添加评论。
注意:我测试过的 filter
有点基础,我不确定如果您真的喜欢它可能需要更改什么。
# Basic URL that is the same across all requests
url = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/'
# CloudWatch log group
log_group = '/aws/my/log/group'
# Either specify the instance you want to search or leave it out to search all instances
instance = '/log-events/i-xxxxxxxxxxxx'
OR
instance = '/log-events'
# The filter to apply.
filter = '[incoming_ip, user_name, user_ip , timestamp, request, status_code = 5*, bytes, url, user_agent]'
# Start time. There might be an End time as well but my queries haven't used
# that yet so I'm not sure how it's formatted. It should be pretty similar
# though.
hours = 48
start = "&start=-#{hours*60*60*1000}"
# This will get you the final URL
final = url + CGI.escape(CGI.escape(log_group)) + instance + 'FfilterPatternD' + CGI.escape(CGI.escape(filter)).gsub('%','$') + CGI.escape(start).gsub('%','$')
我不得不做类似的事情来为 lambda 生成返回 link 的日志,并做了以下骇人听闻的事情来创建 link:
const link = `https://${process.env.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups/log-group/${process.env.AWS_LAMBDA_LOG_GROUP_NAME.replace(/\//g, '2F')}/log-events/${process.env.AWS_LAMBDA_LOG_STREAM_NAME.replace('$', '24').replace('[', '5B').replace(']', '5D').replace(/\//g, '2F')}`
我的一个同事发现编码没什么特别的。 它是标准 URI percent encoding 但应用了两次 (2x)。在 javascript 中,您可以使用 encodeURIComponent
函数进行测试:
let inp = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group/'
console.log(encodeURIComponent(inp))
console.log(encodeURIComponent(encodeURIComponent(inp)))
这段 javascript 在第二个编码阶段产生了预期的输出:
https%3A%2F%2Fconsole.aws.amazon.com%2Fcloudwatch%2Fhome%3Fregion%3Dus-east-1%23logsV2%3Alog-groups%2Flog-group%2F
https%253A%252F%252Fconsole.aws.amazon.com%252Fcloudwatch%252Fhome%253Fregion%253Dus-east-1%2523logsV2%253Alog-groups%252Flog-group%252F
注意
至少有些位使用双重编码,但不是全部 link。否则所有特殊字符在双重编码后都会占用4个字符,但有些仍然只占用2个字符。希望这有帮助 ;)
我的完整 Javascript 解决方案基于 @isaias-b 的回答,它还在日志上添加了时间戳过滤器:
const logBaseUrl = 'https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logsV2:log-groups/log-group';
const encode = text => encodeURIComponent(text).replace(/%/g, '$');
const awsEncode = text => encodeURIComponent(encodeURIComponent(text)).replace(/%/g, '$');
const encodeTimestamp = timestamp => encode('?start=') + awsEncode(new Date(timestamp).toJSON());
const awsLambdaLogBaseUrl = `${logBaseUrl}/${awsEncode('/aws/lambda/')}`;
const logStreamUrl = (logGroup, logStream, timestamp) =>
`${awsLambdaLogBaseUrl}${logGroup}/log-events/${awsEncode(logStream)}${timestamp ? encodeTimestamp(timestamp) : ''}`;
基于@Pål Brattberg 的回答的 Python 解决方案:
cloudwatch_log_template = "https://{AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region={AWS_REGION}#logsV2:log-groups/log-group/{LOG_GROUP_NAME}/log-events/{LOG_STREAM_NAME}"
log_url = cloudwatch_log_template.format(
AWS_REGION=AWS_REGION, LOG_GROUP_NAME=CLOUDWATCH_LOG_GROUP, LOG_STREAM_NAME=LOG_STREAM_NAME
)
如果您使用任何非法字符,请确保先替换非法字符(参见 OP)。
有点晚了,但这里有一个 python 实现
def get_cloud_watch_search_url(search, log_group, log_stream, region=None,):
"""Return a properly formatted url string for search cloud watch logs
search = "{$.message: "You are amazing"}
log_group = Is the group of message you want to search
log_stream = The stream of logs to search
"""
url = f'https://{region}.console.aws.amazon.com/cloudwatch/home?region={region}'
def aws_encode(value):
"""The heart of this is that AWS likes to quote things twice with some substitution"""
value = urllib.parse.quote_plus(value)
value = re.sub(r"\+", " ", value)
return re.sub(r"%", "$", urllib.parse.quote_plus(value))
bookmark = '#logsV2:log-groups'
bookmark += '/log-group/' + aws_encode(log_group)
bookmark += "/log-events/" + log_stream
bookmark += re.sub(r"%", "$", urllib.parse.quote("?filterPattern="))
bookmark += aws_encode(search)
return url + bookmark
这样您就可以快速验证它。
>>> real = 'https://us-west-2.console.aws.amazon.com/cloudwatch/home?region=us-west-2#logsV2:log-groups/log-group/2Fapp2Fdjango/log-events/productionFfilterPatternD7B24.msg3D2225s25s+messages+to+25s+pk3D25d...227D'
>>> constructed = get_cloud_watch_search_url(None, search='{$.msg="%s%s messages to %s pk=%d..."}', log_group='/app/django', log_stream='production', region='us-west-2')
>>> real == constructed
True
我最近在想生成 cloudwatch insights 时遇到了这个问题URL。以下打字稿版本:
export function getInsightsUrl(
start: Date,
end: Date,
query: string,
sourceGroup: string,
region = "us-east-1"
) {
const p = (m: string) => escape(m);
// encodes inner values
const s = (m: string) => escape(m).replace(/\%/gi, "*");
const queryDetail =
p(`~(end~'`) +
s(end.toISOString()) +
p(`~start~'`) +
s(start.toISOString()) +
p(`~timeType~'ABSOLUTE~tz~'UTC~editorString~'`) +
s(query) +
p(`~isLiveTail~false~queryId~'`) +
s(v4()) +
p(`~source~(~'`) +
s(sourceGroup) +
p(`))`);
return (
`https://console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights` +
escape("?queryDetail=" + queryDetail).replace(/\%/gi, "$")
);
}
我最近在想要生成 cloudwatch 见解时遇到了这个问题 URL。 PHP 以下版本:
<?php
function getInsightsUrl($region = 'ap-northeast-1') {
//
$start = now()->subMinutes(2)->format('Y-m-d\TH:i:s.v\Z');
$end = now()->addMinutes(2)->format('Y-m-d\TH:i:s.v\Z');
$filter = 'INFO';
$logStream = 'xxx_backend_web';
$sourceGroup = '/ecs/xxx_backend_prod';
// $sourceGroup = '/aws/ecs/xxx_backend~\'/ecs/xxx_backend_dev'; // multiple source group
$query =
"fields @timestamp, @message \n" .
"| sort @timestamp desc\n" .
"| filter @logStream like '$logStream'\n" .
"| filter @message like '$filter'\n" .
"| limit 20";
$queryDetail = urlencode(
("~(end~'") .
($end) .
("~start~'") .
($start) .
("~timeType~'ABSOLUTE~tz~'Local~editorString~'") .
($query) .
("~isLiveTail~false~queryId~'") .
("~source~(~'") .
($sourceGroup) .
("))")
);
$queryDetail = preg_replace('/\%/', '$', urlencode("?queryDetail=" . $queryDetail));
return
"https://console.aws.amazon.com/cloudwatch/home?region=${region}#logsV2:logs-insights"
. $queryDetail;
}
首先我要感谢其他人提供的线索。进一步完整解释了如何构建 Log Insights 链接。
总的来说,它只是一个对象结构的奇怪编码连接,其工作方式如下:
?queryDetail=
之后的部分是对象表示,{}
表示为~()
对象被向下移动到原始值,后者转换如下:
encodeURIComponent(value)
以便所有特殊字符都转换为%xx
replace(/%/g, "*")
以便此编码不受顶级编码的影响- 如果值类型是
string
- 它是 前缀 不匹配的单引号
举例说明:
"Hello world" -> "Hello%20world" -> "Hello*20world" -> "'Hello*20world"
使用
~
加入转换后的基元数组,并放入~()
构造
然后,在基元转换完成后 - 使用“~”连接对象。
在该字符串之后是 escape()
d(请注意,不是 encodeURIComponent()
被调用,因为它不会在 JS 中转换 ~
)。
之后添加?queryDetail=
。
最后这个字符串我们 encodeURIComponent()
ed 并作为顶部的樱桃 - %
替换为 $
。
让我们看看它在实践中是如何工作的。假设这些是我们的查询参数:
const expression = `fields @timestamp, @message
| filter @message not like 'example'
| sort @timestamp asc
| limit 100`;
const logGroups = ["/application/sample1", "/application/sample2"];
const queryParameters = {
end: 0,
start: -3600,
timeType: "RELATIVE",
unit: "seconds",
editorString: expression,
isLiveTrail: false,
source: logGroups,
};
首先转换图元:
const expression = "'fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20'example'*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100";
const logGroups = ["'*2Fapplication*2Fsample1", "'*2Fapplication*2Fsample2"];
const queryParameters = {
end: 0,
start: -3600,
timeType: "'RELATIVE",
unit: "'seconds",
editorString: expression,
isLiveTrail: false,
source: logGroups,
};
然后,使用 ~
连接对象,所以我们有对象表示字符串:
const objectString = "~(end~0~start~-3600~timeType~'RELATIVE~unit~'seconds~editorString~'fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20'example'*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100~isLiveTrail~false~source~(~'*2Fapplication*2Fsample1~'*2Fapplication*2Fsample2))"
现在我们escape()
它:
const escapedObject = "%7E%28end%7E0%7Estart%7E-3600%7EtimeType%7E%27RELATIVE%7Eunit%7E%27seconds%7EeditorString%7E%27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20%27example%27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100%7EisLiveTrail%7Efalse%7Esource%7E%28%7E%27*2Fapplication*2Fsample1%7E%27*2Fapplication*2Fsample2%29%29"
现在我们附加 ?queryDetail=
前缀:
const withQueryDetail = "?queryDetail=%7E%28end%7E0%7Estart%7E-3600%7EtimeType%7E%27RELATIVE%7Eunit%7E%27seconds%7EeditorString%7E%27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*20%27example%27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*20100%7EisLiveTrail%7Efalse%7Esource%7E%28%7E%27*2Fapplication*2Fsample1%7E%27*2Fapplication*2Fsample2%29%29"
最后我们对其进行 URLencode 并将 %
替换为 $
和 vois la:
const result = "FqueryDetailD7E28end7E07Estart7E-36007EtimeType7E27RELATIVE7Eunit7E27seconds7EeditorString7E27fields*20*40timestamp*2C*20*40message*0A*20*20*20*20*7C*20filter*20*40message*20not*20like*2027example27*0A*20*20*20*20*7C*20sort*20*40timestamp*20asc*0A*20*20*20*20*7C*20limit*201007EisLiveTrail7Efalse7Esource7E287E27*2Fapplication*2Fsample17E27*2Fapplication*2Fsample22929"
当然也可以反向操作
这就是所有人。玩得开心,保重并尽量避免自己做这种奇怪的事情。 :)
由于 Python 贡献与日志组相关,而不与日志洞察力相关,因此这是我的贡献。我想我可以用内部函数做得更好,但这是一个很好的起点:
from datetime import datetime, timedelta
import re
from urllib.parse import quote
def get_aws_cloudwatch_log_insights(query_parameters, aws_region):
def quote_string(input_str):
return f"""{quote(input_str, safe="~()'*").replace('%', '*')}"""
def quote_list(input_list):
quoted_list = ""
for item in input_list:
if isinstance(item, str):
item = f"'{item}"
quoted_list += f"~{item}"
return f"({quoted_list})"
params = []
for key, value in query_parameters.items():
if key == "editorString":
value = "'" + quote(value)
value = value.replace('%', '*')
elif isinstance(value, str):
value = "'" + value
if isinstance(value, bool):
value = str(value).lower()
elif isinstance(value, list):
value = quote_list(value)
params += [key, str(value)]
object_string = quote_string("~(" + "~".join(params) + ")")
scaped_object = quote(object_string, safe="*").replace("~", "%7E")
with_query_detail = "?queryDetail=" + scaped_object
result = quote(with_query_detail, safe="*").replace("%", "$")
final_url = f"https://{aws_region}.console.aws.amazon.com/cloudwatch/home?region={aws_region}#logsV2:logs-insights{result}"
return final_url
示例:
aws_region = "eu-west-1"
query = """fields @timestamp, @message
| filter @message not like 'example'
| sort @timestamp asc
| limit 100"""
log_groups = ["/application/sample1", "/application/sample2"]
query_parameters = {
"end": datetime.utcnow().isoformat(timespec='milliseconds') + "Z",
"start": (datetime.utcnow() - timedelta(days=2)).isoformat(timespec='milliseconds') + "Z",
"timeType": "ABSOLUTE",
"unit": "seconds",
"editorString": query,
"isLiveTrail": False,
"source": log_groups,
}
print(get_aws_cloudwatch_log_insights(query_parameters, aws_region))