query_owl 0.2.0 → 0.4.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 +4 -4
- data/README.md +42 -0
- data/app/assets/stylesheets/query_owl/_01_base.css +23 -0
- data/app/assets/stylesheets/query_owl/_02_layout.css +5 -0
- data/app/assets/stylesheets/query_owl/_03_table.css +29 -0
- data/app/assets/stylesheets/query_owl/_04_badges.css +13 -0
- data/app/controllers/query_owl/slow_queries_controller.rb +28 -0
- data/app/helpers/query_owl/application_helper.rb +9 -0
- data/app/views/layouts/query_owl/application.html.erb +13 -0
- data/app/views/query_owl/slow_queries/index.html.erb +34 -0
- data/config/routes.rb +1 -0
- data/lib/generators/query_owl/install/install_generator.rb +15 -0
- data/lib/generators/query_owl/install/templates/initializer.rb +42 -0
- data/lib/query_owl/configuration.rb +12 -1
- data/lib/query_owl/event_store.rb +59 -0
- data/lib/query_owl/file_logger.rb +24 -0
- data/lib/query_owl/middleware.rb +12 -5
- data/lib/query_owl/notifiers/console.rb +38 -0
- data/lib/query_owl/notifiers/logger.rb +9 -0
- data/lib/query_owl/notifiers/stdout.rb +9 -0
- data/lib/query_owl/request_context.rb +17 -0
- data/lib/query_owl/version.rb +1 -1
- data/lib/query_owl.rb +6 -0
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9dd0e38c57b1b0c94a99a0ffb22f6ecc71f0e28c6d4d6d041dac57bc6e5bf626
|
|
4
|
+
data.tar.gz: dd6c94c94314dc38da3a757d0e4147dc349ae8842f10e0e29f9fa7fc2e0727ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: be09a0350d981ffcd6e2b5b4e57825daf3d4aca7a0747d0f3579188df22ddc5f26babeab619c24e1d47c0e86ebda1dc1dce99ee65ce32cc01ff1cef1f997a95e
|
|
7
|
+
data.tar.gz: 6d499e2d7972b3252f6eccaae8e415f742e2e28b579c08d9e53f2c7377a998a6c5b7be1a46bc3f4189d353258fe7dc0dd01f5df29eb4986d7851c07ad1179fca
|
data/README.md
CHANGED
|
@@ -14,6 +14,7 @@ A leaner alternative to Bullet. QueryOwl detects N+1 queries, slow queries, and
|
|
|
14
14
|
- [Installation](#installation)
|
|
15
15
|
- [Configuration](#configuration)
|
|
16
16
|
- [Log Output](#log-output)
|
|
17
|
+
- [Dashboard Endpoint](#dashboard-endpoint)
|
|
17
18
|
- [Manual Testing in the Dummy App](#manual-testing-in-the-dummy-app)
|
|
18
19
|
- [Roadmap](#roadmap)
|
|
19
20
|
- [Contributing](#contributing)
|
|
@@ -67,6 +68,8 @@ QueryOwl.configure do |config|
|
|
|
67
68
|
config.backtrace_lines = 5 # number of backtrace frames to capture
|
|
68
69
|
config.backtrace_filter = ->(line) { line.start_with?("app/") } # optional custom filter
|
|
69
70
|
config.raise_on_n_plus_one = false # set true in CI to raise instead of log
|
|
71
|
+
config.event_store_size = 100 # ring buffer capacity
|
|
72
|
+
config.dashboard_enabled = Rails.env.development? # HTML view on/off
|
|
70
73
|
end
|
|
71
74
|
```
|
|
72
75
|
|
|
@@ -89,6 +92,45 @@ When a problem is detected, QueryOwl writes a structured line to `Rails.logger`:
|
|
|
89
92
|
|
|
90
93
|
---
|
|
91
94
|
|
|
95
|
+
## Dashboard Endpoint
|
|
96
|
+
|
|
97
|
+
Mount the engine in your host app's routes to enable the JSON endpoint:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
# config/routes.rb
|
|
101
|
+
mount QueryOwl::Engine => "/rails"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Then browse the HTML dashboard or query JSON at `GET /rails/slow_queries`:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
GET /rails/slow_queries # HTML dashboard (browser)
|
|
108
|
+
GET /rails/slow_queries.json # JSON array
|
|
109
|
+
GET /rails/slow_queries?type=n_plus_one
|
|
110
|
+
GET /rails/slow_queries?type=slow_query
|
|
111
|
+
GET /rails/slow_queries?type=unused_eager_load
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The HTML view is enabled when `config.dashboard_enabled` is `true` (default in development); returns `403` otherwise. The JSON endpoint is always available.
|
|
115
|
+
|
|
116
|
+
The JSON response is an array of event objects, newest first, up to `config.event_store_size` entries:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
[
|
|
120
|
+
{
|
|
121
|
+
"type": "n_plus_one",
|
|
122
|
+
"sql": "SELECT * FROM posts WHERE user_id = ?",
|
|
123
|
+
"count": 5,
|
|
124
|
+
"backtrace": ["app/controllers/posts_controller.rb:12"],
|
|
125
|
+
"recorded_at": "2026-06-15T18:00:00.000Z"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
[↑ Back to top](#table-of-contents)
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
92
134
|
## Manual Testing in the Dummy App
|
|
93
135
|
|
|
94
136
|
The gem ships with a minimal Rails app in `spec/dummy/` for manual verification.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #f8f9fa;
|
|
5
|
+
--surface: #ffffff;
|
|
6
|
+
--border: #dee2e6;
|
|
7
|
+
--text: #212529;
|
|
8
|
+
--muted: #6c757d;
|
|
9
|
+
--radius: 6px;
|
|
10
|
+
--shadow: 0 1px 3px rgba(0,0,0,.08);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
line-height: 1.5;
|
|
17
|
+
color: var(--text);
|
|
18
|
+
background: var(--bg);
|
|
19
|
+
padding: 2rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.qo-monospace { font-family: ui-monospace, "SFMono-Regular", Menlo, monospace; font-size: 12px; }
|
|
23
|
+
.qo-muted { color: var(--muted); font-size: 12px; }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.qo-table {
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
background: var(--surface);
|
|
5
|
+
border-radius: var(--radius);
|
|
6
|
+
overflow: hidden;
|
|
7
|
+
box-shadow: var(--shadow);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.qo-table th {
|
|
11
|
+
text-align: left;
|
|
12
|
+
padding: 0.6rem 1rem;
|
|
13
|
+
background: #f0f0f0;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
font-size: 11px;
|
|
16
|
+
text-transform: uppercase;
|
|
17
|
+
letter-spacing: .04em;
|
|
18
|
+
color: var(--muted);
|
|
19
|
+
border-bottom: 1px solid var(--border);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.qo-table td {
|
|
23
|
+
padding: 0.6rem 1rem;
|
|
24
|
+
border-bottom: 1px solid #eee;
|
|
25
|
+
vertical-align: top;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.qo-table tr:last-child td { border-bottom: none; }
|
|
29
|
+
.qo-table tr:hover td { background: #fafafa; }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.qo-badge {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
padding: 2px 8px;
|
|
4
|
+
border-radius: 3px;
|
|
5
|
+
font-size: 11px;
|
|
6
|
+
font-weight: 600;
|
|
7
|
+
text-transform: uppercase;
|
|
8
|
+
white-space: nowrap;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.qo-badge--n_plus_one { background: #fde8e8; color: #c0392b; }
|
|
12
|
+
.qo-badge--slow_query { background: #fef3e2; color: #e67e22; }
|
|
13
|
+
.qo-badge--unused_eager_load { background: #e8f4fd; color: #2980b9; }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module QueryOwl
|
|
2
|
+
class SlowQueriesController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :null_session
|
|
4
|
+
layout "query_owl/application"
|
|
5
|
+
helper QueryOwl::ApplicationHelper
|
|
6
|
+
|
|
7
|
+
before_action :check_dashboard_enabled, if: -> { request.format.html? }
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
filters = request.query_parameters
|
|
11
|
+
events = EventStore.all
|
|
12
|
+
events = events.select { |e| e[:type].to_s == filters["type"] } if filters["type"].present?
|
|
13
|
+
events = events.select { |e| e[:controller] == filters["controller"] } if filters["controller"].present?
|
|
14
|
+
events = events.select { |e| e[:action] == filters["action"] } if filters["action"].present?
|
|
15
|
+
|
|
16
|
+
respond_to do |format|
|
|
17
|
+
format.json { render json: events }
|
|
18
|
+
format.html { @events = events.reverse }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def check_dashboard_enabled
|
|
25
|
+
head :forbidden unless QueryOwl.config.dashboard_enabled
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>QueryOwl</title>
|
|
7
|
+
<link rel="icon" href="data:,">
|
|
8
|
+
<%= inline_styles %>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<%= yield %>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div class="qo-header">
|
|
2
|
+
<h1>QueryOwl</h1>
|
|
3
|
+
<p>Last <%= @events.length %> detected event<%= "s" unless @events.length == 1 %> (newest first)</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if @events.empty? %>
|
|
7
|
+
<p class="qo-empty">No events detected yet.</p>
|
|
8
|
+
<% else %>
|
|
9
|
+
<table class="qo-table">
|
|
10
|
+
<thead>
|
|
11
|
+
<tr>
|
|
12
|
+
<th>Type</th>
|
|
13
|
+
<th>SQL / Details</th>
|
|
14
|
+
<th>Info</th>
|
|
15
|
+
<th>Recorded At</th>
|
|
16
|
+
<th>Backtrace</th>
|
|
17
|
+
</tr>
|
|
18
|
+
</thead>
|
|
19
|
+
<tbody>
|
|
20
|
+
<% @events.each do |event| %>
|
|
21
|
+
<tr>
|
|
22
|
+
<td><span class="qo-badge qo-badge--<%= event[:type] %>"><%= event[:type].to_s.tr("_", " ") %></span></td>
|
|
23
|
+
<td class="qo-monospace"><%= event[:sql] || "#{event[:model]}##{event[:association]}" %></td>
|
|
24
|
+
<td class="qo-muted">
|
|
25
|
+
<% if event[:count] %>count: <%= event[:count] %><% end %>
|
|
26
|
+
<% if event[:duration_ms] %><%= event[:duration_ms] %>ms<% end %>
|
|
27
|
+
</td>
|
|
28
|
+
<td class="qo-muted"><%= event[:recorded_at]&.strftime("%H:%M:%S") %></td>
|
|
29
|
+
<td class="qo-monospace qo-muted"><%= Array(event[:backtrace]).first %></td>
|
|
30
|
+
</tr>
|
|
31
|
+
<% end %>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
|
|
3
|
+
module QueryOwl
|
|
4
|
+
module Generators
|
|
5
|
+
class InstallGenerator < Rails::Generators::Base
|
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
|
7
|
+
|
|
8
|
+
desc "Creates a QueryOwl initializer in config/initializers."
|
|
9
|
+
|
|
10
|
+
def copy_initializer
|
|
11
|
+
template "initializer.rb", "config/initializers/query_owl.rb"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
QueryOwl.configure do |config|
|
|
2
|
+
# Enable or disable all QueryOwl tracking.
|
|
3
|
+
# Defaults to true in development, false elsewhere.
|
|
4
|
+
# config.enabled = Rails.env.development?
|
|
5
|
+
|
|
6
|
+
# Flag N+1 queries when the same SQL pattern fires this many times per request.
|
|
7
|
+
# config.n_plus_one_threshold = 2
|
|
8
|
+
|
|
9
|
+
# Flag individual queries that take longer than this (in milliseconds).
|
|
10
|
+
# config.slow_query_threshold_ms = 100
|
|
11
|
+
|
|
12
|
+
# Log level for QueryOwl warnings (:debug, :info, or :warn).
|
|
13
|
+
# config.log_level = :warn
|
|
14
|
+
|
|
15
|
+
# Number of backtrace frames captured per query.
|
|
16
|
+
# config.backtrace_lines = 5
|
|
17
|
+
|
|
18
|
+
# Custom backtrace filter — a callable that receives a line and returns true to keep it.
|
|
19
|
+
# Defaults to stripping gem paths and QueryOwl internals.
|
|
20
|
+
# config.backtrace_filter = ->(line) { line.start_with?("app/") }
|
|
21
|
+
|
|
22
|
+
# Raise QueryOwl::NPlusOneError instead of logging when an N+1 is detected.
|
|
23
|
+
# Useful in CI test suites where silent warnings are easy to miss.
|
|
24
|
+
# config.raise_on_n_plus_one = false
|
|
25
|
+
|
|
26
|
+
# Maximum number of events retained in the in-memory ring buffer.
|
|
27
|
+
# config.event_store_size = 100
|
|
28
|
+
|
|
29
|
+
# Enable the HTML dashboard at GET /slow_queries (when the engine is mounted).
|
|
30
|
+
# Defaults to true in development, false elsewhere.
|
|
31
|
+
# config.dashboard_enabled = Rails.env.development?
|
|
32
|
+
|
|
33
|
+
# Append each detected event as a JSON line to this file path.
|
|
34
|
+
# Disabled by default (nil). Useful for persistence across restarts.
|
|
35
|
+
# config.log_file = Rails.root.join("log/query_owl.log").to_s
|
|
36
|
+
|
|
37
|
+
# Notifiers receive each detected event via #call(event).
|
|
38
|
+
# Defaults to [QueryOwl::Notifiers::Logger] which writes to Rails.logger.
|
|
39
|
+
# Use Console for TTY-aware colorized output (yellow: N+1, red: slow query).
|
|
40
|
+
# Use Stdout for non-request contexts (jobs, Rake tasks).
|
|
41
|
+
# config.notifiers = [QueryOwl::Notifiers::Console.new]
|
|
42
|
+
end
|
|
@@ -5,7 +5,15 @@ module QueryOwl
|
|
|
5
5
|
|
|
6
6
|
attr_reader :log_level, :backtrace_filter
|
|
7
7
|
attr_accessor :enabled, :slow_query_threshold_ms, :n_plus_one_threshold, :backtrace_lines,
|
|
8
|
-
:raise_on_n_plus_one
|
|
8
|
+
:raise_on_n_plus_one, :event_store_size, :dashboard_enabled, :log_file
|
|
9
|
+
|
|
10
|
+
def notifiers
|
|
11
|
+
@notifiers ||= [Notifiers::Logger.new]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def notifiers=(arr)
|
|
15
|
+
@notifiers = arr
|
|
16
|
+
end
|
|
9
17
|
|
|
10
18
|
def initialize
|
|
11
19
|
@enabled = Rails.env.development?
|
|
@@ -15,6 +23,9 @@ module QueryOwl
|
|
|
15
23
|
@backtrace_lines = 5
|
|
16
24
|
@backtrace_filter = DEFAULT_BACKTRACE_FILTER
|
|
17
25
|
@raise_on_n_plus_one = false
|
|
26
|
+
@event_store_size = 100
|
|
27
|
+
@dashboard_enabled = Rails.env.development?
|
|
28
|
+
@log_file = nil
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
def log_level=(level)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module QueryOwl
|
|
2
|
+
module EventStore
|
|
3
|
+
class << self
|
|
4
|
+
def push(event)
|
|
5
|
+
mutex.synchronize do
|
|
6
|
+
ensure_buffer_size
|
|
7
|
+
buffer[@write_pos] = event.merge(recorded_at: Time.now)
|
|
8
|
+
@write_pos = (@write_pos + 1) % capacity
|
|
9
|
+
@stored = [@stored + 1, capacity].min
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def all
|
|
14
|
+
mutex.synchronize do
|
|
15
|
+
stored = @stored || 0
|
|
16
|
+
return [] if stored.zero?
|
|
17
|
+
|
|
18
|
+
if stored < capacity
|
|
19
|
+
buffer.first(stored)
|
|
20
|
+
else
|
|
21
|
+
buffer[@write_pos..] + buffer[0...@write_pos]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear
|
|
27
|
+
mutex.synchronize { reset! }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def size
|
|
31
|
+
mutex.synchronize { @stored || 0 }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def mutex
|
|
37
|
+
@mutex ||= Mutex.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def capacity
|
|
41
|
+
QueryOwl.config.event_store_size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ensure_buffer_size
|
|
45
|
+
reset! if @write_pos.nil? || @buffer.nil? || @buffer.size != capacity
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def buffer
|
|
49
|
+
@buffer ||= Array.new(capacity)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def reset!
|
|
53
|
+
@buffer = Array.new(capacity)
|
|
54
|
+
@write_pos = 0
|
|
55
|
+
@stored = 0
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module QueryOwl
|
|
4
|
+
class FileLogger
|
|
5
|
+
class << self
|
|
6
|
+
def append(events)
|
|
7
|
+
return if events.empty?
|
|
8
|
+
|
|
9
|
+
path = QueryOwl.config.log_file
|
|
10
|
+
return unless path
|
|
11
|
+
|
|
12
|
+
File.open(path, "a") do |f|
|
|
13
|
+
events.each { |e| f.puts(JSON.generate(serializable(e))) }
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def serializable(event)
|
|
20
|
+
event.transform_values { |v| v.is_a?(Symbol) ? v.to_s : v }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/query_owl/middleware.rb
CHANGED
|
@@ -18,13 +18,20 @@ module QueryOwl
|
|
|
18
18
|
EagerLoadTracker.start!
|
|
19
19
|
@app.call(env)
|
|
20
20
|
ensure
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
params = env["action_dispatch.request.path_parameters"] || {}
|
|
22
|
+
RequestContext.set(controller: params[:controller], action: params[:action], path: env["PATH_INFO"])
|
|
23
|
+
queries = QueryTracker.stop!
|
|
24
|
+
eager_data = EagerLoadTracker.stop!
|
|
25
|
+
context = RequestContext.current
|
|
26
|
+
RequestContext.clear
|
|
27
|
+
events = (Detector.detect_n_plus_one(queries) +
|
|
24
28
|
Detector.detect_slow_queries(queries) +
|
|
25
|
-
Detector.detect_unused_eager_loads(eager_data)
|
|
26
|
-
|
|
29
|
+
Detector.detect_unused_eager_loads(eager_data))
|
|
30
|
+
.map { |e| e.merge(context) }
|
|
31
|
+
events.each { |event| QueryOwl.config.notifiers.each { |notifier| notifier.call(event) } }
|
|
27
32
|
Logger.log_summary(events)
|
|
33
|
+
events.each { |e| EventStore.push(e) }
|
|
34
|
+
FileLogger.append(events)
|
|
28
35
|
raise_on_n_plus_one!(events) if QueryOwl.config.raise_on_n_plus_one
|
|
29
36
|
end
|
|
30
37
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module QueryOwl
|
|
2
|
+
module Notifiers
|
|
3
|
+
class Console
|
|
4
|
+
YELLOW = "\e[33m"
|
|
5
|
+
RED = "\e[31m"
|
|
6
|
+
RESET = "\e[0m"
|
|
7
|
+
|
|
8
|
+
def call(event)
|
|
9
|
+
line = format(event)
|
|
10
|
+
line = apply_color(event[:type], line) if $stdout.tty?
|
|
11
|
+
$stdout.puts line
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def format(event)
|
|
17
|
+
case event[:type]
|
|
18
|
+
when :n_plus_one
|
|
19
|
+
"#{QueryOwl::Logger::PREFIX} n_plus_one #{event[:sql]} ×#{event[:count]}"
|
|
20
|
+
when :slow_query
|
|
21
|
+
"#{QueryOwl::Logger::PREFIX} slow_query #{event[:sql]} #{event[:duration_ms]}ms"
|
|
22
|
+
when :unused_eager_load
|
|
23
|
+
"#{QueryOwl::Logger::PREFIX} unused_eager_load #{event[:model]}##{event[:association]}"
|
|
24
|
+
else
|
|
25
|
+
"#{QueryOwl::Logger::PREFIX} #{event[:type]}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def apply_color(type, text)
|
|
30
|
+
case type
|
|
31
|
+
when :n_plus_one then "#{YELLOW}#{text}#{RESET}"
|
|
32
|
+
when :slow_query then "#{RED}#{text}#{RESET}"
|
|
33
|
+
else text
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module QueryOwl
|
|
2
|
+
module RequestContext
|
|
3
|
+
class << self
|
|
4
|
+
def set(controller:, action:, path:)
|
|
5
|
+
Thread.current[:query_owl_request_context] = { controller: controller, action: action, path: path }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def current
|
|
9
|
+
Thread.current[:query_owl_request_context] || {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clear
|
|
13
|
+
Thread.current[:query_owl_request_context] = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/query_owl/version.rb
CHANGED
data/lib/query_owl.rb
CHANGED
|
@@ -2,8 +2,14 @@ require "query_owl/version"
|
|
|
2
2
|
require "query_owl/configuration"
|
|
3
3
|
require "query_owl/query_tracker"
|
|
4
4
|
require "query_owl/eager_load_tracker"
|
|
5
|
+
require "query_owl/event_store"
|
|
5
6
|
require "query_owl/detector"
|
|
6
7
|
require "query_owl/logger"
|
|
8
|
+
require "query_owl/file_logger"
|
|
9
|
+
require "query_owl/notifiers/logger"
|
|
10
|
+
require "query_owl/notifiers/stdout"
|
|
11
|
+
require "query_owl/notifiers/console"
|
|
12
|
+
require "query_owl/request_context"
|
|
7
13
|
require "query_owl/middleware"
|
|
8
14
|
require "query_owl/engine"
|
|
9
15
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: query_owl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -34,19 +34,35 @@ files:
|
|
|
34
34
|
- MIT-LICENSE
|
|
35
35
|
- README.md
|
|
36
36
|
- Rakefile
|
|
37
|
+
- app/assets/stylesheets/query_owl/_01_base.css
|
|
38
|
+
- app/assets/stylesheets/query_owl/_02_layout.css
|
|
39
|
+
- app/assets/stylesheets/query_owl/_03_table.css
|
|
40
|
+
- app/assets/stylesheets/query_owl/_04_badges.css
|
|
37
41
|
- app/controllers/query_owl/application_controller.rb
|
|
42
|
+
- app/controllers/query_owl/slow_queries_controller.rb
|
|
43
|
+
- app/helpers/query_owl/application_helper.rb
|
|
38
44
|
- app/jobs/query_owl/application_job.rb
|
|
39
45
|
- app/mailers/query_owl/application_mailer.rb
|
|
40
46
|
- app/models/query_owl/application_record.rb
|
|
47
|
+
- app/views/layouts/query_owl/application.html.erb
|
|
48
|
+
- app/views/query_owl/slow_queries/index.html.erb
|
|
41
49
|
- config/routes.rb
|
|
50
|
+
- lib/generators/query_owl/install/install_generator.rb
|
|
51
|
+
- lib/generators/query_owl/install/templates/initializer.rb
|
|
42
52
|
- lib/query_owl.rb
|
|
43
53
|
- lib/query_owl/configuration.rb
|
|
44
54
|
- lib/query_owl/detector.rb
|
|
45
55
|
- lib/query_owl/eager_load_tracker.rb
|
|
46
56
|
- lib/query_owl/engine.rb
|
|
57
|
+
- lib/query_owl/event_store.rb
|
|
58
|
+
- lib/query_owl/file_logger.rb
|
|
47
59
|
- lib/query_owl/logger.rb
|
|
48
60
|
- lib/query_owl/middleware.rb
|
|
61
|
+
- lib/query_owl/notifiers/console.rb
|
|
62
|
+
- lib/query_owl/notifiers/logger.rb
|
|
63
|
+
- lib/query_owl/notifiers/stdout.rb
|
|
49
64
|
- lib/query_owl/query_tracker.rb
|
|
65
|
+
- lib/query_owl/request_context.rb
|
|
50
66
|
- lib/query_owl/version.rb
|
|
51
67
|
- lib/tasks/query_owl_tasks.rake
|
|
52
68
|
homepage: https://github.com/eclectic-coding/query_owl
|