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 isSQLCommenter. 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 seeconfig.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 theSQLCommenter format. This can be changed viaconfig.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 185
attr_accessor :prepend_comment, : # :nodoc:
.prepend_comment (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 185
attr_accessor :prepend_comment, : # :nodoc:
.taggings (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 184
attr_reader :, :taggings, : # :nodoc:
.taggings=(taggings) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 187
def taggings=(taggings) # :nodoc: @taggings = taggings.freeze @handlers = rebuild_handlers end
.tags (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 184
attr_reader :, :taggings, : # :nodoc:
.tags=(tags) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 192
def () # :nodoc: @tags = .freeze @handlers = rebuild_handlers end
.tags_formatter (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 184
attr_reader :, :taggings, : # :nodoc:
.tags_formatter=(format) (rw)
# File 'activerecord/lib/active_record/query_logs.rb', line 197
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 265
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 202
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 216
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 282
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 303
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 227
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 220
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 251
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 238
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 246
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 316
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 295
def uncached_comment(extra_context, formatter) content = tag_content(extra_context, formatter) if content.present? "/*#{escape_sql_comment(content)}*/" end end