Skip to content

Commit 238561f

Browse files
authored
Handle newline within auto_explain query text with text format (#542)
* Handle newline within auto_explain query text with text format * Run workaround only with Heroku
1 parent 9ba775d commit 238561f

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

logs/analyze_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -4196,3 +4196,62 @@ func TestAnalyzeLogLines(t *testing.T) {
41964196
}
41974197
}
41984198
}
4199+
4200+
var testsHeroku = []testpair{{ // Heroku text explain + new line in Query Text (due to the query being too long)
4201+
[]state.LogLine{{
4202+
Content: "duration: 1681.452 ms plan:\n" +
4203+
"\tQuery Text: UPDATE pgbench_branches SET bbalance = bbalance +\n 2656 WHERE bid = 59;\n" +
4204+
"\tUpdate on public.pgbench_branches (cost=0.27..8.29 rows=1 width=370) (actual rows=0 loops=1)\n" +
4205+
"\t Buffers: shared hit=7\n" +
4206+
"\t -> Index Scan using pgbench_branches_pkey on public.pgbench_branches (cost=0.27..8.29 rows=1 width=370) (actual rows=1 loops=1)\n" +
4207+
"\t Output: bid, (bbalance + 2656), filler, ctid\n" +
4208+
"\t Index Cond: (pgbench_branches.bid = 59)",
4209+
}},
4210+
[]state.LogLine{{
4211+
Query: "UPDATE pgbench_branches SET bbalance = bbalance +\n 2656 WHERE bid = 59;",
4212+
Classification: pganalyze_collector.LogLineInformation_STATEMENT_AUTO_EXPLAIN,
4213+
ReviewedForSecrets: true,
4214+
SecretMarkers: []state.LogSecretMarker{{
4215+
ByteStart: 30,
4216+
ByteEnd: 469,
4217+
Kind: state.StatementTextLogSecret,
4218+
}},
4219+
}},
4220+
[]state.PostgresQuerySample{{
4221+
Query: "UPDATE pgbench_branches SET bbalance = bbalance +\n 2656 WHERE bid = 59;",
4222+
RuntimeMs: 1681.452,
4223+
HasExplain: true,
4224+
ExplainSource: pganalyze_collector.QuerySample_AUTO_EXPLAIN_EXPLAIN_SOURCE,
4225+
ExplainFormat: pganalyze_collector.QuerySample_TEXT_EXPLAIN_FORMAT,
4226+
ExplainOutputText: "Update on public.pgbench_branches (cost=0.27..8.29 rows=1 width=370) (actual rows=0 loops=1)\n" +
4227+
"\t Buffers: shared hit=7\n" +
4228+
"\t -> Index Scan using pgbench_branches_pkey on public.pgbench_branches (cost=0.27..8.29 rows=1 width=370) (actual rows=1 loops=1)\n" +
4229+
"\t Output: bid, (bbalance + 2656), filler, ctid\n" +
4230+
"\t Index Cond: (pgbench_branches.bid = 59)",
4231+
}},
4232+
},
4233+
}
4234+
4235+
func TestAnalyzeLogLinesHeroku(t *testing.T) {
4236+
t.Setenv("DYNO", "dummy")
4237+
t.Setenv("PORT", "dummy")
4238+
for _, pair := range testsHeroku {
4239+
l, s := logs.AnalyzeLogLines(pair.logLinesIn)
4240+
4241+
cfg := pretty.CompareConfig
4242+
cfg.SkipZeroFields = true
4243+
4244+
if diff := cfg.Compare(pair.logLinesOut, l); diff != "" {
4245+
t.Errorf("For %v: log lines diff: (-want +got)\n%s", pair.logLinesIn, diff)
4246+
}
4247+
if diff := cfg.Compare(pair.samplesOut, s); diff != "" {
4248+
t.Errorf("For %v: query samples diff: (-want +got)\n%s", pair.samplesOut, diff)
4249+
}
4250+
4251+
for idx, line := range pair.logLinesOut {
4252+
if !line.ReviewedForSecrets && line.LogLevel != pganalyze_collector.LogLineInformation_STATEMENT && line.LogLevel != pganalyze_collector.LogLineInformation_QUERY {
4253+
t.Errorf("Missing secret review for:\n%s %s\n", pair.logLinesIn[idx].LogLevel, pair.logLinesIn[idx].Content)
4254+
}
4255+
}
4256+
}
4257+
}

logs/querysample/querysample.go

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pganalyze/collector/logs/util"
1212
"github.com/pganalyze/collector/output/pganalyze_collector"
1313
"github.com/pganalyze/collector/state"
14+
cUtil "github.com/pganalyze/collector/util"
1415
)
1516

1617
func TransformAutoExplainToQuerySample(logLine state.LogLine, explainText string, queryRuntime string) (state.PostgresQuerySample, error) {
@@ -54,12 +55,22 @@ func transformExplainJSONToQuerySample(logLine state.LogLine, explainText string
5455
}
5556

5657
var autoExplainTextPlanDetailsRegexp = regexp.MustCompile(`^Query Text: (.+)\s+([\s\S]+)`)
58+
var herokuAutoExplainWithTabRegexp = regexp.MustCompile(`^Query Text: ([^\t]+)\t([\s\S]+)`)
5759

5860
func transformExplainTextToQuerySample(logLine state.LogLine, explainText string, queryRuntimeMs float64) (state.PostgresQuerySample, error) {
5961
explainParts := autoExplainTextPlanDetailsRegexp.FindStringSubmatch(explainText)
6062
if len(explainParts) != 3 {
6163
return state.PostgresQuerySample{}, fmt.Errorf("auto_explain output doesn't match expected format")
6264
}
65+
// If EXPLAIN output's first char is not a capital letter (e.g. not something like "Update on" or "Index Scan"),
66+
// likely it's hitting the Heroku's newline break in "Query Text:" chunk
67+
// Handle the separation of the query and the explain output text with the tab for these cases
68+
explainOutputFirstChar := explainParts[2][0]
69+
if cUtil.IsHeroku() && !(explainOutputFirstChar >= 'A' && explainOutputFirstChar <= 'Z') {
70+
if parts := herokuAutoExplainWithTabRegexp.FindStringSubmatch(explainText); len(parts) == 3 {
71+
explainParts = parts
72+
}
73+
}
6374
return state.PostgresQuerySample{
6475
Query: strings.TrimSpace(explainParts[1]),
6576
RuntimeMs: queryRuntimeMs,

0 commit comments

Comments
 (0)