Module: ActiveRecord::QueryLogs
| Relationships & Source Files | |
| Namespace Children | |
|
Modules:
| |
|
Classes:
| |
| Defined in: | activerecord/lib/active_record/query_logs.rb, activerecord/lib/active_record/query_logs_formatter.rb |
Overview
Automatically append comments to SQL queries with runtime information tags. This can be used to trace troublesome SQL statements back to the application code that generated these statements.
Query logs can be enabled via Rails configuration in config/application.rb or an initializer:
config.active_record. = true
By default the name of the application, the name and action of the controller, or the name of the job are logged. The default format is SQLCommenter. The tags shown in a query comment can be configured via Rails configuration:
config.active_record. = [ :application, :controller, :action, :job ]
Active Record defines default tags available for use:
applicationpidsocketdb_hostdatabasesource_location
WARNING: Calculating the source_location of a query can be slow, so you should consider its impact if using it in a production environment.
Also see config.active_record.verbose_query_logs.
Action Controller adds default tags when loaded:
controlleractionnamespaced_controller
Active Job adds default tags when loaded:
job
New comment tags can be defined by adding them in a ::Hash to the tags ::Array. Tags can have dynamic content by
setting a Proc or lambda value in the ::Hash, and can reference any value stored by Rails in the context object.
::ActiveSupport::CurrentAttributes can be used to store application values. Tags with nil values are
omitted from the query comment.
context includes the following keys:
controller- current Action Controller instance, if availablejob- current Active Job instance, if availableconnection- current database connectionsql- current SQL query
Escaping is performed on the string returned, however untrusted user input should not be used.
Example:
config.active_record. = [
:namespaced_controller,
:action,
:job,
{
request_id: ->(context) { context[:controller]&.request&.request_id },
job_id: ->(context) { context[:job]&.job_id },
tenant_id: -> { Current.tenant&.id },
static: "value",
},
]
WARNING: SQL query can contain sensitive data and using :sql directly as a query log tag
is unsafe because it will log the query unfiltered.
Example:
# unsafe
config.active_record. = [:sql]
# safe
config.active_record. = [sql_length: ->(context) { context[:sql].length } ]
By default the name of the application, the name and action of the controller, or the name of the job are logged using the SQLCommenter format. This can be changed via config.active_record.query_log_tags_format
The format can also be overridden per connection pool through the query_log_tags
key of a database.yml entry. Connections checked out from that pool use the
configured format, while setting query_log_tags to false instead opts the
pool out of tagging entirely. Pools without any explicit configuration fall back
to the global defaults:
production:
primary:
database: primary
analytics:
database: analytics
:
format: sqlcommenter
replica:
database: replica
replica: true
: false
Tag comments can be prepended to the query:
config.active_record. = true
Whether the comment is prepended can also be overridden per connection pool with a
.prepend_comment key under query_log_tags in a database.yml entry. Connections
checked out from that pool use the configured value, while pools without any explicit
configuration fall back to the global default:
production:
primary:
database: primary
analytics:
database: analytics
:
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:
config.active_record. = true
Class Attribute Summary
- .cache_query_log_tags rw Internal use only
- .prepend_comment rw Internal use only
- .taggings rw Internal use only
- .taggings=(taggings) rw Internal use only
- .tags rw Internal use only
- .tags=(tags) rw Internal use only
- .tags_formatter rw Internal use only
- .tags_formatter=(format) rw Internal use only
Class Method Summary
- .build_handler(name, handler = nil) private
-
.comment(extra_context)
private
Returns an SQL comment
::Stringcontaining the query log tags. - .escape_sql_comment(content) private
- .formatter_for(format) private
- .rebuild_handlers private
- .resolve_formatter(connection) private
- .resolve_prepend_comment(connection) private
- .tag_content(extra_context, formatter) private
- .uncached_comment(extra_context, formatter) private
- .call(sql, connection) Internal use only
- .clear_cache Internal use only
- .query_source_location Internal use only
Class Attribute Details
.cache_query_log_tags (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 168
attr_accessor :prepend_comment, : # :nodoc:
.prepend_comment (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 168
attr_accessor :prepend_comment, : # :nodoc:
.taggings (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 167
attr_reader :, :taggings, : # :nodoc:
.taggings=(taggings) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 170
def taggings=(taggings) # :nodoc: @taggings = taggings.freeze @handlers = rebuild_handlers end
.tags (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 167
attr_reader :, :taggings, : # :nodoc:
.tags=(tags) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 175
def () # :nodoc: @tags = .freeze @handlers = rebuild_handlers end
.tags_formatter (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 167
attr_reader :, :taggings, : # :nodoc:
.tags_formatter=(format) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 180
def (format) # :nodoc: @formatter = formatter_for(format) @tags_formatter = format end
Class Method Details
.build_handler(name, handler = nil) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 248
def build_handler(name, handler = nil) handler ||= @taggings[name] if handler.nil? GetKeyHandler.new(name) elsif handler.respond_to?(:call) if handler.arity == 0 ZeroArityHandler.new(handler) else handler end else IdentityHandler.new(handler) end end
.call(sql, connection)
# File 'activerecord/lib/active_record/query_logs.rb', line 185
def call(sql, connection) # :nodoc: return sql if connection.pool.db_config. == false comment = self.comment(sql: sql, connection: connection) if comment.blank? sql elsif resolve_prepend_comment(connection) "#{comment} #{sql}" else "#{sql} #{comment}" end end
.clear_cache
# File 'activerecord/lib/active_record/query_logs.rb', line 199
def clear_cache # :nodoc: self.cached_comments = nil end
.comment(extra_context) (private)
Returns an SQL comment ::String containing the query log tags.
Sets and returns a cached comment if .cache_query_log_tags is true.
# File 'activerecord/lib/active_record/query_logs.rb', line 265
def comment(extra_context) formatter = resolve_formatter(extra_context[:connection]) if cache = (self.cached_comments ||= {}) cache.fetch(formatter) do cache[formatter] = uncached_comment(extra_context, formatter) end else uncached_comment(extra_context, formatter) end end
.escape_sql_comment(content) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 286
def escape_sql_comment(content) # Sanitize a string to appear within an SQL comment # For compatibility, this also surrounding "/*+", "/*", and "*/" # characters, possibly with single surrounding space. # Then follows that by replacing any internal "*/" or "/ *" with # "* /" or "/ *" comment = content.to_s.dup comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") comment.gsub!("*/", "* /") comment.gsub!("/*", "/ *") comment end
.formatter_for(format) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 210
def formatter_for(format) case format when :legacy LegacyFormatter when :sqlcommenter SQLCommenter else raise ArgumentError, "Formatter is unsupported: #{format}" end end
.query_source_location
# File 'activerecord/lib/active_record/query_logs.rb', line 203
def query_source_location # :nodoc: LogSubscriber.backtrace_cleaner.first_clean_frame end
.rebuild_handlers (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 234
def rebuild_handlers handlers = [] @tags.each do |i| if i.is_a?(Hash) i.each do |k, v| handlers << [k, build_handler(k, v)] end else handlers << [i, build_handler(i)] end end handlers.sort_by! { |(key, _)| key.to_s } end
.resolve_formatter(connection) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 221
def resolve_formatter(connection) if (format = connection.pool.db_config.) formatter_for(format) else @formatter end end
.resolve_prepend_comment(connection) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 229
def resolve_prepend_comment(connection) prepend = connection.pool.db_config. prepend.nil? ? prepend_comment : prepend end
.tag_content(extra_context, formatter) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 299
def tag_content(extra_context, formatter) context = ActiveSupport::ExecutionContext.to_h context.reverse_merge!(extra_context) pairs = @handlers.filter_map do |(key, handler)| val = handler.call(context) formatter.format(key, val) unless val.nil? end formatter.join(pairs) end
.uncached_comment(extra_context, formatter) (private)
[ GitHub ]# File 'activerecord/lib/active_record/query_logs.rb', line 278
def uncached_comment(extra_context, formatter) content = tag_content(extra_context, formatter) if content.present? "/*#{escape_sql_comment(content)}*/" end end