/
query_logs.rb
138 lines (127 loc) · 4.07 KB
/
query_logs.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors_per_thread"
module ActiveRecord
# = Active Record Query Logs
#
# Automatically tag SQL queries with runtime information.
#
# Default tags available for use:
#
# * +application+
# * +pid+
# * +socket+
# * +db_host+
# * +database+
#
# _Action Controller and Active Job tags are also defined when used in Rails:_
#
# * +controller+
# * +action+
# * +job+
#
# The tags used in a query can be configured directly:
#
# ActiveRecord::QueryLogs.tags = [ :application, :controller, :action, :job ]
#
# or via Rails configuration:
#
# config.active_record.query_log_tags = [ :application, :controller, :action, :job ]
#
# To add new comment tags, add a hash to the tags array containing the keys and values you
# want to add to the comment. Dynamic content can be created by setting a proc or lambda value in a hash,
# and can reference any value stored in the +context+ object.
#
# Example:
#
# tags = [
# :application,
# {
# custom_tag: ->(context) { context[:controller]&.controller_name },
# custom_value: -> { Custom.value },
# }
# ]
# ActiveRecord::QueryLogs.tags = tags
#
# The QueryLogs +context+ can be manipulated via the +ActiveSupport::ExecutionContext.set+ method.
#
# Temporary updates limited to the execution of a block:
#
# ActiveSupport::ExecutionContext.set(foo: Bar.new) do
# posts = Post.all
# end
#
# Direct updates to a context value:
#
# ActiveSupport::ExecutionContext[:foo] = Bar.new
#
# Tag comments can be prepended to the query:
#
# ActiveRecord::QueryLogs.prepend_comment = true
#
# For applications where the content will not change during the lifetime of
# the request or job execution, the tags can be cached for reuse in every query:
#
# ActiveRecord::QueryLogs.cache_query_log_tags = true
#
# This option can be set during application configuration or in a Rails initializer:
#
# config.active_record.cache_query_log_tags = true
module QueryLogs
mattr_accessor :taggings, instance_accessor: false, default: {}
mattr_accessor :tags, instance_accessor: false, default: [ :application ]
mattr_accessor :prepend_comment, instance_accessor: false, default: false
mattr_accessor :cache_query_log_tags, instance_accessor: false, default: false
thread_mattr_accessor :cached_comment, instance_accessor: false
class << self
def call(sql) # :nodoc:
if prepend_comment
"#{self.comment} #{sql}"
else
"#{sql} #{self.comment}"
end.strip
end
def clear_cache # :nodoc:
self.cached_comment = nil
end
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
private
# Returns an SQL comment +String+ containing the query log tags.
# Sets and returns a cached comment if <tt>cache_query_log_tags</tt> is +true+.
def comment
if cache_query_log_tags
self.cached_comment ||= uncached_comment
else
uncached_comment
end
end
def uncached_comment
content = tag_content
if content.present?
"/*#{escape_sql_comment(content)}*/"
end
end
def escape_sql_comment(content)
content.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
end
def tag_content
context = ActiveSupport::ExecutionContext.to_h
tags.flat_map { |i| [*i] }.filter_map do |tag|
key, handler = tag
handler ||= taggings[key]
val = if handler.nil?
context[key]
elsif handler.respond_to?(:call)
if handler.arity == 0
handler.call
else
handler.call(context)
end
else
handler
end
"#{key}:#{val}" unless val.nil?
end.join(",")
end
end
end
end