Skip to content

Instantly share code, notes, and snippets.

@YooAppn
Last active May 16, 2020
Embed
What would you like to do?
google stackdriver logging helper command

Overview

gcloud logging helper command

💡 Requirement

  • gcloud sdk
  • gawk
  • gdate (macOS)
  • qq
  • fzf

🎁 install

# bash
source fzf-log-comp.zsh

# zsh
source fzf-log-comp.zsh

Usage

gaelog [options] [query]

QUERY:
  sql where statement. supported column row,message,log
    row:     log line number
    message: log message
    log:     raw message

  eg.
    log like '%POST%'
    message like '%Request%'
    row = 19

OPTIONS:

google platform:
  -a                  app engine name

severity:
  -e                  severity >= ERROR
  -w                  severity >= WARNING
  -d                  severity >= DEBUG

httpRequest.status:
  -r, -request-error  status >= 400
  -e, -server-error   status >= 500

logging options:

  -l                  log limit
  -it                 interactive mode
  -f                  appending log query
  -verbose

🛠 Customize

🧾 stackdriver logging filter

# in __log_query()

# write your default fileter
log_query="xxxx"

📝 default preview fields

# in __awk_cmd()

# add or remove fields
fields = "protoPayload.line protoPayload.appId protoPayload.method resource.type resource.labels timestamp";
#!/usr/bin/env bash
__log_query() {
local gcp_prj
local app_name
local log_query
local log_level
local http_status
local log_filter
local log_date
local date_cmd="date"
if [ "$(uname)" == "Darwin" ]; then
date_cmd="gdate"
fi
log_date=$($date_cmd +"%Y-%m-%d" -d "1 month ago")
gcp_prj=$1
app_name=$2
http_status=$3
log_level=$4
log_filter=$5
log_query="resource.type=gae_app
AND resource.labels.module_id=${app_name}
AND timestamp>=${log_date}
AND logName=(projects/${gcp_prj}/logs/stdout OR projects/${gcp_prj}/logs/stderr OR projects/${gcp_prj}/logs/appengine.googleapis.com%2Frequest_log)"
if [ ! -z "$http_status" ]; then
log_query+=" AND $http_status"
fi
if [ ! -z "$log_level" ]; then
log_query+=" AND $log_level"
fi
if [ ! -z "$log_filter" ]; then
log_query+=" AND $log_filter"
fi
echo $log_query
}
__awk_cmd() {
local cmd
local flg=$1
cmd='BEGIN {RS="-{3}\n"; FS="\n"; printf("row,message,log\n"); };'
if [ "$flg" = "all" ] ;then
cmd+='NR > 1 {
msg = "";
logMsg = "logMessage:";
txtPld = "textPayload:";
rows = split($0, lines, "\n");
for (r = 1; r <= rows; r++) {
idx = index(lines[r], logMsg);
if (idx != 0) {
msg = substr(lines[r], idx+length(logMsg));
}
idx = index(lines[r], txtPld);
if (idx != 0) {
msg = substr(lines[r], idx+length(txtPld));
}
}
gsub("\"", "", msg); gsub("\"", "", $0);
printf("%d,\"%s\",\"%s\"\n", NR-1, msg, $0);
}'
else
cmd+='NR > 1 {
txt = "";
msg = "";
logMsg = "logMessage:";
txtPld = "textPayload:";
fields = "protoPayload.line protoPayload.appId protoPayload.method resource.type resource.labels timestamp";
cols = split(fields,keys," ");
rows = split($0, lines, "\n");
for (i = 1; i <= cols; i++) {
for (r = 1; r <= rows; r++) {
if (index(lines[r], keys[i]) != 0) {
txt = txt lines[r]"\n";
}
idx = index(lines[r], logMsg);
if (idx != 0) {
msg = substr(lines[r], idx+length(logMsg));
}
idx = index(lines[r], txtPld);
if (idx != 0) {
msg = substr(lines[r], idx+length(txtPld));
}
}
}
gsub("\"", "", msg); gsub("\"", "", txt);
printf("%d,\"%s\",\"%s\"\n", NR-1, msg, txt);
}'
fi
echo $cmd
}
gaelog() {
local verbose
# gcp
local gcp_prj
local app_name
# logging
local log_query
local log_level
local http_status
local log_filter
local log_limit
local log_data
# log viewer
local is_full_txt
local is_interactive
local query
# default value
log_limit=100
query="n"
verbose=false
# parse arguments
#log level e:Error w:Warn d:Debug
#http status r:400 s:500
#-q query
#-f filter
#-a app
#-p prj
while [ $# -gt 0 ]
do
case "$1" in
-e) log_level="severity >= ERROR" ;;
-w) log_level="severity >= WARNING" ;;
-d) log_level="severity >= DEBUG" ;;
-r) http_status="httpRequest.status >= 400" ;;
-request-error) http_status="httpRequest.status >= 400" ;;
-s) http_status="httpRequest.status >= 500" ;;
-server-error) http_status="httpRequest.status >= 500" ;;
-f) shift; log_fileter=$1 ;;
-a) shift; app_name=$1 ;;
-all-log) is_full_txt=all ;;
-it) is_interactive="-it" ;;
-l) shift; log_limit=$1 ;;
-v) verbose=true ;;
* ) query=$1
esac
shift
done
gcp_prj=$(gcloud config get-value project)
# gcp_prj=your default
if [ -z "$app_name" ]; then
app_name=default
fi
log_query=$(__log_query "$gcp_prj" "$app_name" "$http_status" "$log_level" "$log_filter")
if [ "$verbose" = "true" ] ; then
echo "LOG QUERY : $log_query"
echo "GCLOUD : $gcp_prj app: $app_name"
echo "LOG OPTION : httpStatu: $http_status , Log Lv: $log_level , Filter: $log_filter"
echo "COMMAND : show all log item=$is_full_txt , interactive=$is_interactive"
fi
awk_cmd=$(__awk_cmd "$is_full_txt")
raw=$(gcloud logging read "$log_query" --limit $log_limit --format text | gawk "$awk_cmd")
if [ $? -ne 0 ]; then
return 1
fi
log_data=$(echo "$raw" | qq -ic -oh -q "SELECT row,message,log FROM stdin ORDER BY row")
while [ "$query" != "" -a "$query" != "q" ]
do
if [ "$query" = "n" ]; then
log_query="SELECT row || message as logs FROM stdin ORDER BY row"
else
log_query="SELECT row || message as logs FROM stdin WHERE $query ORDER BY row"
fi
row=$(echo "$log_data" | qq -oh -ic -q "$log_query" \
| fzf --preview-window=down:70% --preview "echo '$log_data' | qq -ic -q \"SELECT log FROM stdin WHERE row = {1}\"")
if [ "$is_interactive" = "-it" ]; then
read -p "next query [none query continue:'n', quit:'q' or <enter>] " query
else
query="q"
fi
done
}
# set ft=zsh
__log_query() {
local gcp_prj
local app_name
local log_query
local log_level
local http_status
local log_filter
local log_date
local check_os
local date_cmd
check_os=$(uname)
date_cmd="date"
if [ "$check_os" = "Darwin" ] ; then
date_cmd="gdate"
fi
log_date=$($date_cmd +"%Y-%m-%d" -d "1 month ago")
gcp_prj=$1
app_name=$2
http_status=$3
log_level=$4
log_filter=$5
log_query="resource.type=gae_app
AND resource.labels.module_id=${app_name}
AND timestamp>=${log_date}
AND logName=(projects/${gcp_prj}/logs/stdout OR projects/${gcp_prj}/logs/stderr OR projects/${gcp_prj}/logs/appengine.googleapis.com%2Frequest_log)"
if [ "$http_status" != "" ]; then
log_query+=" AND $http_status"
fi
if [ "$log_level" != "" ]; then
log_query+=" AND $log_level"
fi
if [ "$log_filter" != "" ]; then
log_query+=" AND $log_filter"
fi
echo $log_query
}
__awk_cmd() {
local cmd
local flg=$1
cmd='BEGIN {RS="-{3}\\n"; FS="\\n"; printf("row,message,log\\n"); };'
if [ "$flg" = "all" ] ;then
cmd+='NR > 1 {
msg = "";
logMsg = "logMessage:";
txtPld = "textPayload:";
rows = split($0, lines, "\\n");
for (r = 1; r <= rows; r++) {
idx = index(lines[r], logMsg);
if (idx != 0) {
msg = substr(lines[r], idx+length(logMsg));
}
idx = index(lines[r], txtPld);
if (idx != 0) {
msg = substr(lines[r], idx+length(txtPld));
}
}
gsub("\"", "", msg); gsub("\"", "", $0);
printf("%d,\"%s\",\"%s\"\\n", NR-1, msg, $0);
}'
else
cmd+='NR > 1 {
txt = "";
msg = "";
logMsg = "logMessage:";
txtPld = "textPayload:";
fields = "protoPayload.line protoPayload.appId protoPayload.method resource.type resource.labels timestamp";
cols = split(fields,keys," ");
rows = split($0, lines, "\\n");
for (i = 1; i <= cols; i++) {
for (r = 1; r <= rows; r++) {
if (index(lines[r], keys[i]) != 0) {
txt = txt lines[r]"\\n";
}
idx = index(lines[r], logMsg);
if (idx != 0) {
msg = substr(lines[r], idx+length(logMsg));
}
idx = index(lines[r], txtPld);
if (idx != 0) {
msg = substr(lines[r], idx+length(txtPld));
}
}
}
gsub("\"", "", msg); gsub("\"", "", txt);
printf("%d,\"%s\",\"%s\"\\n", NR-1, msg, txt);
}'
fi
echo $cmd
}
gaelog() {
local verbose
# gcp
local gcp_prj
local app_name
# logging
local log_query
local log_level
local http_status
local log_filter
local log_limit
local log_data
# log viewer
local is_full_txt
local is_interactive
local query
# default value
log_limit=100
query="n"
verbose=false
# parse arguments
#log level e:Error w:Warn d:Debug
#http status r:400 s:500
#-q query
#-f filter
#-a app
#-p prj
while [ $# -gt 0 ]
do
case "$1" in
-e) log_level="severity >= ERROR" ;;
-w) log_level="severity >= WARNING" ;;
-d) log_level="severity >= DEBUG" ;;
-r) http_status="httpRequest.status >= 400" ;;
-request-error) http_status="httpRequest.status >= 400" ;;
-s) http_status="httpRequest.status >= 500" ;;
-server-error) http_status="httpRequest.status >= 500" ;;
-f) shift; log_fileter=$1 ;;
-a) shift; app_name=$1 ;;
-all-log) is_full_txt=all ;;
-it) is_interactive="-it" ;;
-l) shift; log_limit=$1 ;;
-v) verbose=true ;;
* ) query=$1
esac
shift
done
gcp_prj=$(gcloud config get-value project)
# gcp_prj=your default
if [ -z "$app_name" ]; then
app_name="default"
fi
log_query=$(__log_query "$gcp_prj" "$app_name" "$http_status" "$log_level" "$log_filter")
if [ "$verbose" = "true" ] ; then
echo "LOG QUERY : $log_query"
echo "GCLOUD : $gcp_prj app: $app_name"
echo "LOG OPTION : httpStatus: $http_status , Log Lv: $log_level , Filter: $log_filter"
echo "COMMAND : show all log item=$is_full_txt , interactive=$is_interactive"
fi
awk_cmd=$(__awk_cmd "$is_full_txt")
raw=$(gcloud logging read "$log_query" --limit $log_limit --format text | gawk "$awk_cmd" )
if [ $? -ne 0 ]; then
return 1
fi
log_data=$(echo "$raw" | qq -ic -oh -q "SELECT row,message,log FROM stdin ORDER BY row")
while [[ "$query" != "" && "$query" != "q" ]]
do
if [ "$query" = "n" ]; then
log_query="SELECT row || message as logs FROM stdin ORDER BY row"
else
log_query="SELECT row || message as logs FROM stdin WHERE $query ORDER BY row"
fi
row=$(echo "$log_data" | qq -oh -ic -q "$log_query" \
| fzf --preview-window=down:70% --preview "echo '$log_data' | qq -ic -q \"SELECT log FROM stdin WHERE row = {1}\"")
if [ "$is_interactive" = "-it" ]; then
read "query?next query [none query continue:'n', quit:'q' or <enter>] "
else
query="q"
fi
done
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment