active_record-sql_analyzer 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGES.md +8 -0
- data/Gemfile +2 -2
- data/active_record-sql_analyzer.gemspec +2 -1
- data/lib/active_record/sql_analyzer/backtrace_filter.rb +1 -1
- data/lib/active_record/sql_analyzer/cli.rb +1 -1
- data/lib/active_record/sql_analyzer/cli_processor.rb +1 -2
- data/lib/active_record/sql_analyzer/compact_logger.rb +4 -3
- data/lib/active_record/sql_analyzer/configuration.rb +4 -4
- data/lib/active_record/sql_analyzer/monkeypatches/query.rb +1 -1
- data/lib/active_record/sql_analyzer/redacted_logger.rb +1 -1
- data/lib/active_record/sql_analyzer/version.rb +1 -1
- data/spec/active_record/sql_analyzer/background_processor_spec.rb +3 -3
- data/spec/active_record/sql_analyzer/cli_processor_spec.rb +10 -12
- data/spec/active_record/sql_analyzer/end_to_end_spec.rb +44 -35
- data/spec/active_record/sql_analyzer/redacted_logger_spec.rb +3 -3
- data/spec/support/db_connection.rb +0 -1
- metadata +6 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5d31d0e5425cf5bcfa0c272cda57fc401b65c76d2f995a76a86cbde30a7abd90
|
4
|
+
data.tar.gz: efc79b2899368bb3ab2aea189dd10b0ac3b183c93b2047ceef558bab8090e5d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0c27e575f069fc946d9b553f41c519d85017e7315d71f8b8b3b563720cd9ae2d7a9824edcfe0950dee8c4a5dc23b48bbdd993da3dfd03d4fe5ceeef86d9518f
|
7
|
+
data.tar.gz: 80cdcea62380e9f5397b98968a6f32cac07bf6f9872e72af7a99d82089af7fc1f3a7d3d054e94f6c61b0768c74f68b2757bdee90a6cc7ab794502438447e8bd0
|
data/CHANGES.md
ADDED
data/Gemfile
CHANGED
@@ -4,9 +4,9 @@ gem "pry-byebug"
|
|
4
4
|
gem "rake"
|
5
5
|
|
6
6
|
group :test do
|
7
|
-
gem "mysql2"
|
7
|
+
gem "mysql2"
|
8
8
|
gem "rspec", "~> 3.4"
|
9
|
-
gem "rubocop", "
|
9
|
+
gem "rubocop", "0.58.2"
|
10
10
|
gem "sql-parser", git: "https://github.com/nerdrew/sql-parser.git"
|
11
11
|
gem "timecop", "~> 0.8"
|
12
12
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
|
2
3
|
require File.expand_path('../lib/active_record/sql_analyzer/version', __FILE__)
|
3
4
|
Gem::Specification.new do |s|
|
4
5
|
s.authors = ['Zachary Anker', 'Gabriel Gilder']
|
@@ -15,7 +16,7 @@ Gem::Specification.new do |s|
|
|
15
16
|
s.require_paths = ['lib']
|
16
17
|
s.version = ActiveRecord::SqlAnalyzer::VERSION
|
17
18
|
|
18
|
-
s.add_dependency 'activerecord'
|
19
|
+
s.add_dependency 'activerecord'
|
19
20
|
|
20
21
|
s.add_development_dependency 'bundler', '~> 1.0'
|
21
22
|
end
|
@@ -23,7 +23,6 @@ module ActiveRecord
|
|
23
23
|
end
|
24
24
|
|
25
25
|
local_data
|
26
|
-
|
27
26
|
rescue => ex
|
28
27
|
puts "#{ex.class}: #{ex.message}"
|
29
28
|
puts ex.backtrace
|
@@ -73,7 +72,7 @@ module ActiveRecord
|
|
73
72
|
last_called, sha = line.split("|", 2)
|
74
73
|
last_called = Time.at(last_called.to_i).utc
|
75
74
|
|
76
|
-
local_usage[sha] ||= {"count" => 0}
|
75
|
+
local_usage[sha] ||= { "count" => 0 }
|
77
76
|
local_usage[sha]["count"] += 1
|
78
77
|
|
79
78
|
if !local_usage[sha]["last_called"] || local_usage[sha]["last_called"] < last_called
|
@@ -13,13 +13,14 @@ module ActiveRecord
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def log(event)
|
16
|
-
|
16
|
+
json = event.to_json
|
17
|
+
sha = json.hash
|
17
18
|
unless logged_shas.include?(sha)
|
18
|
-
definition_log_file.
|
19
|
+
definition_log_file.print("#{sha}|#{json}\n")
|
19
20
|
logged_shas << sha
|
20
21
|
end
|
21
22
|
|
22
|
-
log_file.
|
23
|
+
log_file.print("#{Time.now.to_i}|#{sha}\n")
|
23
24
|
end
|
24
25
|
|
25
26
|
def close
|
@@ -152,16 +152,16 @@ module ActiveRecord
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def setup_defaults
|
155
|
-
|
155
|
+
quoted_value_pattern = %{('([^\\\\']|\\\\.|'')*'|"([^\\\\"]|\\\\.|"")*")}
|
156
156
|
@options[:sql_redactors] = [
|
157
157
|
Redactor.new(/\n/, " "),
|
158
158
|
Redactor.new(/\s+/, " "),
|
159
159
|
Redactor.new(/IN \([^)]+\)/i, "IN ('[REDACTED]')"),
|
160
160
|
Redactor.new(/(\s|\b|`)(=|!=|>=|>|<=|<) ?(BINARY )?-?\d+(\.\d+)?/i, " = '[REDACTED]'"),
|
161
|
-
Redactor.new(/(\s|\b|`)(=|!=|>=|>|<=|<) ?(BINARY )?x?#{
|
161
|
+
Redactor.new(/(\s|\b|`)(=|!=|>=|>|<=|<) ?(BINARY )?x?#{quoted_value_pattern}/i, " = '[REDACTED]'"),
|
162
162
|
Redactor.new(/VALUES \(.+\)$/i, "VALUES ('[REDACTED]')"),
|
163
|
-
Redactor.new(/BETWEEN #{
|
164
|
-
Redactor.new(/LIKE #{
|
163
|
+
Redactor.new(/BETWEEN #{quoted_value_pattern} AND #{quoted_value_pattern}/i, "BETWEEN '[REDACTED]' AND '[REDACTED]'"),
|
164
|
+
Redactor.new(/LIKE #{quoted_value_pattern}/i, "LIKE '[REDACTED]'"),
|
165
165
|
Redactor.new(/ LIMIT \d+/i, ""),
|
166
166
|
Redactor.new(/ OFFSET \d+/i, ""),
|
167
167
|
Redactor.new(/INSERT INTO (`?\w+`?) \([^)]+\)/i, "INSERT INTO \\1 (REDACTED_COLUMNS)"),
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
|
32
32
|
if safe_sql =~ analyzer[:table_regex]
|
33
33
|
SqlAnalyzer.background_processor <<
|
34
|
-
|
34
|
+
_query_analyzer_private_query_stanza([query_analyzer_call], analyzer)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -4,7 +4,7 @@ module ActiveRecord
|
|
4
4
|
def filter_event(event)
|
5
5
|
# Determine if we're doing extended tracing or only the first
|
6
6
|
calls = event.delete(:calls).map do |call|
|
7
|
-
{sql: filter_sql(call[:sql]), caller: filter_caller(call[:caller])}
|
7
|
+
{ sql: filter_sql(call[:sql]), caller: filter_caller(call[:caller]) }
|
8
8
|
end
|
9
9
|
|
10
10
|
# De-duplicate redacted calls to avoid many transactions with looping "N+1" queries.
|
@@ -3,7 +3,7 @@ RSpec.describe ActiveRecord::SqlAnalyzer::BackgroundProcessor do
|
|
3
3
|
|
4
4
|
let(:instance) { described_class.new }
|
5
5
|
|
6
|
-
let(:event) { {calls: [{caller: "CALLER", sql: "SQL"}], logger: logger} }
|
6
|
+
let(:event) { { calls: [{ caller: "CALLER", sql: "SQL" }], logger: logger } }
|
7
7
|
|
8
8
|
let(:logger) do
|
9
9
|
Class.new do
|
@@ -23,8 +23,8 @@ RSpec.describe ActiveRecord::SqlAnalyzer::BackgroundProcessor do
|
|
23
23
|
|
24
24
|
before do
|
25
25
|
ActiveRecord::SqlAnalyzer.configure do |c|
|
26
|
-
c.backtrace_filter_proc
|
27
|
-
c.complex_sql_redactor_proc
|
26
|
+
c.backtrace_filter_proc(Proc.new { |lines| "BFP #{lines}" })
|
27
|
+
c.complex_sql_redactor_proc(Proc.new { |sql| "CSRP #{sql}" })
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -29,20 +29,18 @@ RSpec.describe ActiveRecord::SqlAnalyzer::CLIProcessor do
|
|
29
29
|
|
30
30
|
before do
|
31
31
|
write_logs(:foo,
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
)
|
32
|
+
{ sql: "F-SQL1", caller: "CALLER1", tag: true },
|
33
|
+
{ sql: "F-SQL2", caller: "CALLER2", tag: true },
|
34
|
+
{ sql: "F-SQL1", caller: "CALLER1", tag: true },
|
35
|
+
{ sql: "F-SQL3", caller: "CALLER3", tag: true },
|
36
|
+
{ sql: "F-SQL2", caller: "CALLER2", tag: true })
|
38
37
|
|
39
38
|
write_logs(:bar,
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
)
|
39
|
+
{ sql: "B-SQL1", caller: "CALLER1" },
|
40
|
+
{ sql: "B-SQL2", caller: "CALLER2" },
|
41
|
+
{ sql: "B-SQL3", caller: "CALLER3" },
|
42
|
+
{ sql: "B-SQL2", caller: "CALLER2" },
|
43
|
+
{ sql: "B-SQL1", caller: "CALLER1" })
|
46
44
|
end
|
47
45
|
|
48
46
|
subject(:process) do
|
@@ -36,7 +36,7 @@ RSpec.describe "End to End" do
|
|
36
36
|
before do
|
37
37
|
ActiveRecord::SqlAnalyzer.configure do |c|
|
38
38
|
c.logger_root_path tmp_dir
|
39
|
-
c.log_sample_proc
|
39
|
+
c.log_sample_proc(Proc.new { |_name| true })
|
40
40
|
|
41
41
|
c.add_analyzer(
|
42
42
|
name: :test_tag,
|
@@ -129,13 +129,15 @@ RSpec.describe "End to End" do
|
|
129
129
|
"BEGIN; " \
|
130
130
|
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
131
131
|
"SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; " \
|
132
|
-
"COMMIT;"
|
132
|
+
"COMMIT;"
|
133
|
+
)
|
133
134
|
|
134
135
|
expect(log_def_hash[transaction_executed_twice_sha]["sql"]).to eq(
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
136
|
+
"BEGIN; " \
|
137
|
+
"SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; " \
|
138
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
139
|
+
"COMMIT;"
|
140
|
+
)
|
139
141
|
end
|
140
142
|
|
141
143
|
it "Logs nested transactions correctly" do
|
@@ -148,10 +150,11 @@ RSpec.describe "End to End" do
|
|
148
150
|
|
149
151
|
transaction_executed_once_sha = log_reverse_hash[1]
|
150
152
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
153
|
+
"BEGIN; " \
|
154
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
155
|
+
"SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; " \
|
156
|
+
"COMMIT;"
|
157
|
+
)
|
155
158
|
end
|
156
159
|
|
157
160
|
it "Logs transactions with inserts correctly" do
|
@@ -162,10 +165,11 @@ RSpec.describe "End to End" do
|
|
162
165
|
|
163
166
|
transaction_executed_once_sha = log_reverse_hash[1]
|
164
167
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
168
|
+
"BEGIN; " \
|
169
|
+
"INSERT INTO matching_table (REDACTED_COLUMNS) VALUES ('[REDACTED]'); " \
|
170
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
171
|
+
"COMMIT;"
|
172
|
+
)
|
169
173
|
end
|
170
174
|
|
171
175
|
it "Logs mixed matching-nonmatching selects correctly" do
|
@@ -179,7 +183,8 @@ RSpec.describe "End to End" do
|
|
179
183
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
180
184
|
"BEGIN; " \
|
181
185
|
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
182
|
-
"COMMIT;"
|
186
|
+
"COMMIT;"
|
187
|
+
)
|
183
188
|
end
|
184
189
|
|
185
190
|
it "Logs transaction with repeated selects correctly" do
|
@@ -193,10 +198,11 @@ RSpec.describe "End to End" do
|
|
193
198
|
transaction_executed_once_sha = log_reverse_hash[1]
|
194
199
|
|
195
200
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
196
|
-
|
201
|
+
"BEGIN; " \
|
197
202
|
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
198
203
|
"SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; " \
|
199
|
-
"COMMIT;"
|
204
|
+
"COMMIT;"
|
205
|
+
)
|
200
206
|
end
|
201
207
|
|
202
208
|
it "Logs transaction with repeated inserts correctly" do
|
@@ -210,10 +216,11 @@ RSpec.describe "End to End" do
|
|
210
216
|
transaction_executed_once_sha = log_reverse_hash[1]
|
211
217
|
|
212
218
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
213
|
-
|
219
|
+
"BEGIN; " \
|
214
220
|
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
215
221
|
"INSERT INTO matching_table (REDACTED_COLUMNS) VALUES ('[REDACTED]'); " \
|
216
|
-
"COMMIT;"
|
222
|
+
"COMMIT;"
|
223
|
+
)
|
217
224
|
end
|
218
225
|
|
219
226
|
context "ActiveRecord generated transactions" do
|
@@ -240,10 +247,10 @@ RSpec.describe "End to End" do
|
|
240
247
|
NonMatching.create
|
241
248
|
|
242
249
|
expect(log_def_hash.map { |_hash, data| data["sql"] }).to match([
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
250
|
+
"INSERT INTO `matching_table` VALUES ()",
|
251
|
+
"BEGIN; INSERT INTO `matching_table` VALUES (); INSERT INTO `nonmatching_table` VALUES (); COMMIT;",
|
252
|
+
"SELECT `matching_table`.* FROM `matching_table` ORDER BY `matching_table`.`id` DESC"
|
253
|
+
])
|
247
254
|
end
|
248
255
|
end
|
249
256
|
|
@@ -256,10 +263,11 @@ RSpec.describe "End to End" do
|
|
256
263
|
transaction_executed_once_sha = log_reverse_hash[1]
|
257
264
|
|
258
265
|
expect(log_def_hash[transaction_executed_once_sha]["sql"]).to eq(
|
259
|
-
|
266
|
+
"BEGIN; " \
|
260
267
|
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; " \
|
261
268
|
"INSERT INTO nonmatching_table (REDACTED_COLUMNS) VALUES ('[REDACTED]'); " \
|
262
|
-
"COMMIT;"
|
269
|
+
"COMMIT;"
|
270
|
+
)
|
263
271
|
end
|
264
272
|
|
265
273
|
it "Does not log nonmatching-only queries" do
|
@@ -284,7 +292,7 @@ RSpec.describe "End to End" do
|
|
284
292
|
ActiveRecord::SqlAnalyzer.configure do |c|
|
285
293
|
times_called = 0
|
286
294
|
# Return true every other call, starting with the first call
|
287
|
-
c.log_sample_proc
|
295
|
+
c.log_sample_proc(Proc.new { |_name| (times_called += 1) % 2 == 1 })
|
288
296
|
end
|
289
297
|
end
|
290
298
|
|
@@ -294,7 +302,8 @@ RSpec.describe "End to End" do
|
|
294
302
|
|
295
303
|
expect(log_def_hash.size).to eq(1)
|
296
304
|
expect(log_def_hash.map { |_hash, query| query['sql'] }).to eq([
|
297
|
-
|
305
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'"
|
306
|
+
])
|
298
307
|
end
|
299
308
|
|
300
309
|
it "Samples some but not other whole transactions" do
|
@@ -309,20 +318,20 @@ RSpec.describe "End to End" do
|
|
309
318
|
end
|
310
319
|
|
311
320
|
expect(log_def_hash.map { |_hash, data| data["sql"] }).to match([
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
321
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'",
|
322
|
+
"BEGIN; "\
|
323
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]'; "\
|
324
|
+
"SELECT * FROM matching_table WHERE test_string = '[REDACTED]'; "\
|
325
|
+
"COMMIT;",
|
326
|
+
"SELECT * FROM matching_table WHERE id = '[REDACTED]' and id = '[REDACTED]'"
|
327
|
+
])
|
319
328
|
end
|
320
329
|
end
|
321
330
|
|
322
331
|
context "when sampling is disabled" do
|
323
332
|
before do
|
324
333
|
ActiveRecord::SqlAnalyzer.configure do |c|
|
325
|
-
c.log_sample_proc
|
334
|
+
c.log_sample_proc(Proc.new { |_name| false })
|
326
335
|
end
|
327
336
|
end
|
328
337
|
|
@@ -267,9 +267,9 @@ RSpec.describe ActiveRecord::SqlAnalyzer::RedactedLogger do
|
|
267
267
|
let(:event) do
|
268
268
|
{
|
269
269
|
calls: [{
|
270
|
-
|
271
|
-
|
272
|
-
|
270
|
+
caller: [],
|
271
|
+
sql: "INSERT INTO `boom` (`bam`, `foo`) VALUES ('howdy', 'dowdy')",
|
272
|
+
}]
|
273
273
|
}
|
274
274
|
end
|
275
275
|
|
@@ -19,7 +19,6 @@ class DBConnection
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.setup_db
|
22
|
-
ActiveRecord::Base.raise_in_transactional_callbacks = true
|
23
22
|
conn = ActiveRecord::Base.establish_connection(configuration)
|
24
23
|
conn.connection.execute <<-SQL
|
25
24
|
CREATE DATABASE IF NOT EXISTS ar_sql_analyzer_test
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_record-sql_analyzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zachary Anker
|
@@ -9,28 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-08-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "~>"
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '4.0'
|
21
18
|
- - ">="
|
22
19
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
20
|
+
version: '0'
|
24
21
|
type: :runtime
|
25
22
|
prerelease: false
|
26
23
|
version_requirements: !ruby/object:Gem::Requirement
|
27
24
|
requirements:
|
28
|
-
- - "~>"
|
29
|
-
- !ruby/object:Gem::Version
|
30
|
-
version: '4.0'
|
31
25
|
- - ">="
|
32
26
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
27
|
+
version: '0'
|
34
28
|
- !ruby/object:Gem::Dependency
|
35
29
|
name: bundler
|
36
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,6 +52,7 @@ files:
|
|
58
52
|
- ".rspec"
|
59
53
|
- ".rubocop.yml"
|
60
54
|
- ".travis.yml"
|
55
|
+
- CHANGES.md
|
61
56
|
- CONTRIBUTING.md
|
62
57
|
- Gemfile
|
63
58
|
- LICENSE.md
|
@@ -111,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
106
|
version: '0'
|
112
107
|
requirements: []
|
113
108
|
rubyforge_project:
|
114
|
-
rubygems_version: 2.
|
109
|
+
rubygems_version: 2.7.6
|
115
110
|
signing_key:
|
116
111
|
specification_version: 4
|
117
112
|
summary: Logs a subset of ActiveRecord queries and dumps them for analyses.
|