SQL Queries

SQL logging is off by default. With config.integrations.enable_sql_logging = true, LogStruct subscribes to ActiveRecord's sql.active_record events and writes an app database line for each query that takes at least sql_slow_query_threshold milliseconds (100 by default). It detaches Rails' own SQL log lines while it's on.

Lines

Each query is one line at info level. These two were logged with sql_slow_query_threshold = 0 in the test environment, where bind values are logged:

{
  "src": "app",
  "evt": "database",
  "lvl": "info",
  "ts": "2026-10-17T03:21:04.676+00:00",
  "msg": "User Create executed",
  "sql": "INSERT INTO \"users\" (\"email\", \"name\", \"password_digest\", \"reset_token\", \"avatar\") VALUES (?, ?, ?, ?, ?) RETURNING \"id\"",
  "name": "User Create",
  "duration_ms": 0.18,
  "row_count": 1,
  "adapter": "SQLite3Adapter",
  "bind_params": [
    "[FILTERED]",
    "Test User",
    "[FILTERED]",
    "[FILTERED]",
    "<3 bytes of binary data>"
  ],
  "pool_size": 5,
  "active_count": 1,
  "op_type": "INSERT",
  "table_names": [
    "users"
  ],
  "prog": "Rails"
}
{
  "src": "app",
  "evt": "database",
  "lvl": "info",
  "ts": "2026-10-17T03:21:04.681+00:00",
  "msg": "User Load executed",
  "sql": "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"email\" = ? ORDER BY \"users\".\"id\" ASC LIMIT ?",
  "name": "User Load",
  "duration_ms": 0.04,
  "row_count": 1,
  "adapter": "SQLite3Adapter",
  "bind_params": [
    "[FILTERED]",
    1
  ],
  "pool_size": 5,
  "active_count": 1,
  "op_type": "SELECT",
  "table_names": [
    "users"
  ],
  "prog": "Rails"
}

Fields

FieldValue
msg<name> executed.
sqlThe SQL, scrubbed like any other string (email addresses, card numbers and so on), but not filtered.
nameActiveRecord's name for the query, such as User Load, or SQL Query when it has none.
duration_msHow long the query took, in milliseconds, to two decimal places.
row_countThe number of rows, when ActiveRecord reports it.
adapterThe connection adapter's class name, such as PostgreSQLAdapter.
bind_paramsThe bind values, filtered (see Bind values). Left out when sql_log_bind_params is off.
db_nameThe database name, from adapters that have current_database (PostgreSQL and MySQL).
pool_sizeThe size of the connection's pool.
active_countHow many connections in the pool are in use.
op_typeThe SQL's first word, in capitals (SELECT, INSERT, BEGIN, ...).
table_namesThe tables named after FROM, JOIN, UPDATE and INTO, found with a regular expression: for a schema-qualified name such as "public"."users" it's the schema's name.

Which queries are logged

  • Queries that take at least sql_slow_query_threshold milliseconds (default 100.0). With 0 or nil, every query is logged, transactions' BEGIN and COMMIT included.
  • Not logged: queries named SCHEMA or CACHE (query cache hits), queries on schema_migrations or ar_internal_metadata, and SHOW, DESCRIBE and EXPLAIN statements.

Rails' own SQL lines

While SQL logging is on, LogStruct detaches Rails' ActiveRecord::LogSubscriber and turns off ActiveRecord.verbose_query_logs (the "↳ caller" lines), so each query is logged once. With SQL logging off, Rails' own lines stay: debug-level lines with the query as text in msg, which aren't written at the info level that Rails' generated production config sets.

Bind values

  • sql_log_bind_params defaults to true unless Rails.env is production. An environment with another name, such as staging, logs bind values unless you turn it off.
  • Each value is filtered by its column name, like a request parameter: a column that matches your filter keys, LogStruct's default sensitive keys, Rails' filter_parameters or filter_keys_with_hashes (email by default) is written as [FILTERED]. In the lines above, that's email, password_digest and reset_token.
  • Binary columns are written as their size (<3 bytes of binary data>).
  • Other strings are written as [FILTERED] when they look like a secret: longer than 50 characters, 32 or more hex digits, 20 or more Base64 characters, a JWT, a Bearer token, or text containing password, secret, token, key or auth.
  • What's left is scrubbed like any other value.

Settings

  • config.integrations.enable_sql_logging (default false).
  • config.integrations.sql_slow_query_threshold, in milliseconds, as a Float (default 100.0).
  • config.integrations.sql_log_bind_params (default true unless Rails.env is production).

See Configuration for the other settings.

LogStruct.configure do |config|
  # Off by default
  config.integrations.enable_sql_logging = true

  # Log queries that take at least this many milliseconds, as a Float
  # (default: 100.0; 0 or nil logs every query)
  config.integrations.sql_slow_query_threshold = 250.0

  # Log bind values (default: true unless Rails.env is "production")
  config.integrations.sql_log_bind_params = false
end

Limits

  • The sql field isn't filtered. Values written into the SQL itself (a string condition such as where("token = '...'"), or every value when prepared_statements is off) are logged, scrubbed but not filtered.
  • A low threshold in a busy app writes a line for nearly every query.
Lines logged during a request have the request's req_id, so a query can be matched to the request that ran it.