Logidze Documentation

repository·master·Indexed 23 days ago

https://github.com/palkan/logidze

Logidze is a tool for logging database record changes in PostgreSQL using triggers, storing history as JSONB data. It provides a high-performance alternative to Ruby-based auditing gems for Rails applications. Key features include version retrieval, diffing, undo/redo capabilities, detached storage mode to prevent table bloat, and support for PostgreSQL partitioned tables. It requires PostgreSQL >= 10.0 and supports Ruby ~> 2.7 and Rails >= 6.0 (with legacy support for Rails 4.2 and 5.x).

Tokens
7.1K
Snippets
13
Records
49
Agent score
82%

What's inside Logidze

  1. Limit log size and filter tracked columns

    master

    You can customize how Logidze tracks changes during model generation using the following flags:

    • --limit=N: Limits the number of log entries stored (default is unlimited).
    • --only=col1,col2: Tracks changes only for the specified columns.
    • --except=col1,col2: Tracks changes for all columns except the specified ones.
  2. Use Logidze with partitioned tables

    master

    Logidze supports PostgreSQL partitioned tables:

    • PostgreSQL 13+: Works automatically without extra configuration.
    • PostgreSQL 11/12: Requires using _after triggers. Use the --after-trigger option during generation.

    Note: Logidze does not support partitioned tables on PostgreSQL 10. If a partition changes during an update, the record changes are written as a full snapshot.

  3. Configure Logidze for an Active Record model

    master

    To enable change tracking for a specific model, use the logidze:model generator. This creates a migration to add a log_data::jsonb column to the table and adds the has_logidze declaration to your model file.

    If your project has an unconventional structure, you can specify the model file path explicitly using the --path option.

    # Standard model setup
    bundle exec rails generate logidze:model Post
    bundle exec rails db:migrate
    
    # Setup with custom path
    bundle exec rails generate logidze:model Post --path "app/models/custom/post.rb"
  4. Store history data in a separate table (Detached Mode)

    master

    To prevent table bloat in your main tables, you can configure Logidze to store history data in a separate table instead of the log_data column in the origin table.

    Steps to setup detached mode:

    1. Generate the shared logidze_data table (run once per project): bundle exec rails generate logidze:migration:logs
    2. Generate your model with the --detached option: bundle exec rails generate logidze:model Post --detached

    Global Configuration: You can set all models to use detached mode by configuring the initializer:

    # config/initializers/logidze.rb
    Logidze.log_data_placement = :detached

    Note: Using --detached mode may slightly decrease performance.

    # 1. Create the shared logs table
    bundle exec rails generate logidze:migration:logs
    
    # 2. Create a model with detached storage
    bundle exec rails generate logidze:model Post --detached
    # config/initializers/logidze.rb
    Logidze.log_data_placement = :detached
  5. Upgrade Logidze core functions

    master

    To upgrade Logidze and update the core logdize_logger database function, run the following command. This updates the function without needing to modify your existing tables or triggers.

    If you are using fx, you can omit the --update flag; the resulting migration will contain only the updated functions.

    bundle exec rails generate logidze:install --update
  6. Backfill data for Logidze models

    master

    When adding Logidze to an existing table, you may want to create an initial snapshot of the current data so that the log starts from the current state rather than a diff.

    Options:

    • Via Generator: Use the --backfill flag to include an UPDATE statement in the migration that populates the log_data column.
    • Via SQL: Manually run an UPDATE query using logidze_snapshot(to_jsonb(t)).
    • Via Ruby API: Use Model.create_logidze_snapshot or model_instance.create_logidze_snapshot!.

    A snapshot is only created if the log_data column is currently null.

  7. Install Logidze

    master

    To install Logidze in your Rails application, add the gem to your Gemfile and run the installation generator to set up the required PostgreSQL extensions and trigger functions.

    Requirements:

    • Ruby ~> 2.7
    • Rails >= 6.0 (for Rails 4.2 use version <=0.12.0, for Rails 5.x use version <= 1.2.3)
    • PostgreSQL >= 10.0

    Note: Because Logidze uses database-level functions and triggers, you must use the :sql schema format in your application.rb to ensure the schema dump includes these objects.

  8. Configure Logidze timestamps

    master

    By default, Logidze uses the record's updated_at field for version timestamps. If the column is missing, it falls back to statement_timestamp().

    You can customize this behavior using the --timestamp_column option:

    • --timestamp_column <column_name>: Use a specific column for timestamps.
    • --timestamp_column nil: Always use statement_timestamp() (disables automatic column inference).
  9. Use Logidze with schema.rb

    master

    By default, Logidze requires config.active_record.schema_format = :sql. However, if you include the fx gem in your project, Logidze can integrate with it to allow you to continue using schema.rb for your database schema dump.

    When running Logidze generators, you can explicitly control the fx behavior:

    • Use --fx to enforce fx usage if Logidze fails to detect it.
    • Use --no-fx to prevent Logidze from using fx even if it is present in the bundle.
  10. Track metadata and responsibility

    master

    You can attach arbitrary metadata (like IP addresses) or a responsible_id (like a current_user.id) to your log entries.

    Metadata

    Wrap your save logic in a Logidze.with_meta block. Pass the metadata as a Hash (do not use keyword arguments in Ruby 3.0+).

    Responsibility

    Wrap your save logic in a Logidze.with_responsible block to track who performed the change.

    Important Implementation Details

    • Transactions: By default, these methods wrap the block in a DB transaction. If using around_action in Rails, use transactional: false to avoid unexpected behavior.
    • Connection Pooling: If using PgBouncer, avoid transactional: false as metadata is set at the connection level and might leak or be lost if the connection changes.
    • After Commit: Metadata will not be captured in after_commit callbacks or touch: true associations because those execute after the with_meta block has finished.
    # Track metadata
    Logidze.with_meta({ip: request.ip}) do
      post.save!
    end
    
    # Track responsibility
    Logidze.with_responsible(user.id) do
      post.save!
    end
    
    # Retrieve responsibility
    # (You must implement the lookup logic yourself)
    class Post < ActiveRecord::Base
      has_logidze
    
      def whodunnit
        id = log_data.responsible_id
        User.find(id) if id.present?
      end
    end
  11. Update Logidze settings for a specific model

    master

    If you need to update Logidze settings (such as column whitelists/blacklists) for a specific model, use the logidze:model generator with the --update flag. You can use --only to specify a whitelist of columns or --except to specify a blacklist. Use --name to provide a custom migration name.

    Example: Updating Post to only track title, body, and rating with a custom migration name.

    bundle exec rails generate logidze:model Post --update --only=title,body,rating --name add_only_filter_to_posts_log_data
  12. Optimize performance with ignore_log_data

    master

    By default, ActiveRecord selects all columns, including the log_data column. If log_data is large (e.g., not compacted), this can significantly slow down queries due to PostgreSQL's TOAST mechanism.

    How to optimize

    1. Ignore by default: Set Logidze.ignore_log_data_by_default = true in an initializer or config.logidze.ignore_log_data_by_default = true in application.rb.
    2. Ignore per model: Use has_logidze ignore_log_data: true in your model.
    3. Load on demand: If you need the log data for a record that was loaded without it, call record.reload_log_data.
    4. Eager load: Use the .with_log_data scope to include the column in a single query (e.g., User.all.with_log_data).