Full Code of aserafin/grape_logging for AI

master 9f35f74bd5c5 cached
39 files
62.8 KB
17.3k tokens
110 symbols
1 requests
Download .txt
Repository: aserafin/grape_logging
Branch: master
Commit: 9f35f74bd5c5
Files: 39
Total size: 62.8 KB

Directory structure:
gitextract_w93mw737/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── RELEASING.md
├── Rakefile
├── bin/
│   ├── console
│   └── setup
├── grape_logging.gemspec
├── lib/
│   ├── grape_logging/
│   │   ├── formatters/
│   │   │   ├── default.rb
│   │   │   ├── json.rb
│   │   │   ├── lograge.rb
│   │   │   ├── logstash.rb
│   │   │   └── rails.rb
│   │   ├── loggers/
│   │   │   ├── base.rb
│   │   │   ├── client_env.rb
│   │   │   ├── filter_parameters.rb
│   │   │   ├── request_headers.rb
│   │   │   └── response.rb
│   │   ├── middleware/
│   │   │   └── request_logger.rb
│   │   ├── multi_io.rb
│   │   ├── reporters/
│   │   │   ├── active_support_reporter.rb
│   │   │   └── logger_reporter.rb
│   │   ├── timings.rb
│   │   ├── util/
│   │   │   └── parameter_filter.rb
│   │   └── version.rb
│   └── grape_logging.rb
└── spec/
    ├── lib/
    │   └── grape_logging/
    │       ├── formatters/
    │       │   └── rails_spec.rb
    │       ├── loggers/
    │       │   ├── client_env_spec.rb
    │       │   ├── filter_parameters_spec.rb
    │       │   ├── request_headers_spec.rb
    │       │   └── response_spec.rb
    │       └── middleware/
    │           └── request_logger_spec.rb
    └── spec_helper.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
  push:
    branches: [master]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ruby-version: ["3.0", "3.1", "3.2", "3.3", "3.4", "4.0"]
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: ${{ matrix.ruby-version }}
          bundler-cache: true
      - name: Install dependencies
        run: bundle install --jobs 4 --retry 3
      - name: Run tests
        run: bundle exec rake spec
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v6
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: "4.0"
          bundler-cache: true
      - name: Install dependencies
        run: bundle install
      - name: Run RuboCop
        run: bundle exec rake rubocop


================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
.rspec
.idea/

================================================
FILE: .rubocop.yml
================================================
inherit_from: .rubocop_todo.yml

AllCops:
  NewCops: enable
  TargetRubyVersion: 3.0

Metrics/BlockLength:
  CountAsOne: [array, hash, heredoc, method_call]

Metrics/ClassLength:
  CountAsOne: [array, hash, heredoc, method_call]

Metrics/MethodLength:
  CountAsOne: [array, hash, heredoc, method_call]
  Max: 20


================================================
FILE: .rubocop_todo.yml
================================================
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2025-07-10 10:25:51 UTC using RuboCop version 1.77.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 3
# Configuration parameters: EnforcedStyle, AllowedGems, Include.
# SupportedStyles: Gemfile, gems.rb, gemspec
# Include: **/*.gemspec, **/Gemfile, **/gems.rb
Gemspec/DevelopmentDependencies:
  Exclude:
    - "grape_logging.gemspec"

# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Severity, Include.
# Include: **/*.gemspec
Gemspec/RequireMFA:
  Exclude:
    - "grape_logging.gemspec"

# Offense count: 1
# Configuration parameters: Severity, Include.
# Include: **/*.gemspec
Gemspec/RequiredRubyVersion:
  Exclude:
    - "grape_logging.gemspec"

# Offense count: 1
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
Lint/DuplicateBranch:
  Exclude:
    - "lib/grape_logging/util/parameter_filter.rb"

# Offense count: 1
# Configuration parameters: AllowedParentClasses.
Lint/MissingSuper:
  Exclude:
    - "lib/grape_logging/loggers/filter_parameters.rb"

# Offense count: 3
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
  Max: 38

# Offense count: 8
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
# AllowedMethods: refine
Metrics/BlockLength:
  Max: 90

# Offense count: 3
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
  Max: 18

# Offense count: 2
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
  Max: 19

# Offense count: 2
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
# SupportedStyles: snake_case, normalcase, non_integer
# AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
Naming/VariableNumber:
  Exclude:
    - "spec/lib/grape_logging/formatters/rails_spec.rb"

# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: MinBranchesCount.
Style/CaseLikeIf:
  Exclude:
    - "lib/grape_logging/formatters/default.rb"
    - "lib/grape_logging/formatters/logstash.rb"
    - "lib/grape_logging/formatters/rails.rb"

# Offense count: 17
# Configuration parameters: AllowedConstants.
Style/Documentation:
  Enabled: false

# Offense count: 29
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: always, always_true, never
Style/FrozenStringLiteralComment:
  Enabled: false

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/GlobalStdStream:
  Exclude:
    - "lib/grape_logging/reporters/logger_reporter.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: literals, strict
Style/MutableConstant:
  Exclude:
    - "lib/grape_logging/version.rb"

# Offense count: 10
Style/OpenStructUse:
  Exclude:
    - "spec/lib/grape_logging/loggers/client_env_spec.rb"
    - "spec/lib/grape_logging/loggers/filter_parameters_spec.rb"
    - "spec/lib/grape_logging/loggers/request_headers_spec.rb"
    - "spec/lib/grape_logging/loggers/response_spec.rb"

# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try!
Style/SafeNavigation:
  Exclude:
    - "lib/grape_logging/formatters/rails.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/SlicingWithRange:
  Exclude:
    - "lib/grape_logging/loggers/request_headers.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: RequireEnglish, EnforcedStyle.
# SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names
Style/SpecialGlobalVars:
  Exclude:
    - "spec/spec_helper.rb"

# Offense count: 3
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
  Exclude:
    - "lib/grape_logging/formatters/json.rb"
    - "lib/grape_logging/formatters/lograge.rb"
    - "lib/grape_logging/formatters/logstash.rb"

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments.
# AllowedMethods: define_method
Style/SymbolProc:
  Exclude:
    - "lib/grape_logging/util/parameter_filter.rb"

# Offense count: 11
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
# URISchemes: http, https
Layout/LineLength:
  Max: 203


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [3.0.1] - Unreleased

### Changed or Fixed or Added

### Changed
- Move dev dependencies to Gemfile
- Use zeitwerk to load gem

[3.0.1]: https://github.com/aserafin/grape_logging/compare/v3.0.0...master

## [3.0.0] - 2025-08-07

### Changed
- [#93](https://github.com/aserafin/grape_logging/pull/93) RequestLogger middleware to handle Grape 2.4 breaking change - [@devsigner](https://github.com/devsigner) and [@samsonjs](https://github.com/samsonjs).

[3.0.0]: https://github.com/aserafin/grape_logging/compare/v2.1.1...v3.0.0

## [2.1.1] - 2025-07-09

### Fixed
- [#92](https://github.com/aserafin/grape_logging/pull/92) Handle symbol param keys during filtering - [@samsonjs](https://github.com/samsonjs).

[2.1.1]: https://github.com/aserafin/grape_logging/compare/v2.1.0...v2.1.1

## [2.1.0] - 2025-07-09

### Added
- [#91](https://github.com/aserafin/grape_logging/pull/91) Add ActionDispatch request ID to logger arguments hash as `:request_id` - [@samsonjs](https://github.com/samsonjs).

[2.1.0]: https://github.com/aserafin/grape_logging/compare/v2.0.0...v2.1.0

## [2.0.0] - 2025-07-09

### Changed
- **BREAKING**: Updated to support Grape 2.1 and Rack 3.1
- Minimum supported Ruby version is now 3.0
- Replaced Travis CI with GitHub Actions for continuous integration
- Updated all README examples to use `insert_before` instead of `use` for proper middleware placement

### Fixed
- Fixed LoggerReporter to clone the logger parameter to prevent shared state issues (#77)
- Fixed view time precision issue by rounding to 2 decimal places
- Fixed invalid byte sequence handling for parameter keys by properly managing string encodings (#54)
- Fixed various typos in code comments and spec descriptions (#87)
- Fixed specs to work with Ruby 3.4's hash inspect format changes

### Documentation
- Clarified middleware placement requirements in README - must be inserted before Grape::Middleware::Error (#74)
- Added gem version badge to README

[2.0.0]: https://github.com/aserafin/grape_logging/compare/v1.8.4...v2.0.0

## [1.8.4] - 2021-10-29

### Fixed
- Rails 6 compatibility improvements
- Various bug fixes and dependency updates

[1.8.4]: https://github.com/aserafin/grape_logging/compare/v1.8.3...v1.8.4

## [1.8.3] - 2020-02-27

### Fixed
- Performance improvements
- Bug fixes for edge cases

[1.8.3]: https://github.com/aserafin/grape_logging/compare/v1.8.2...v1.8.3

## [1.8.2] - 2019-10-08

### Fixed
- Thread safety improvements
- Minor bug fixes

Note: This version was tagged as "v.1.8.2" (with extra dot)

[1.8.2]: https://github.com/aserafin/grape_logging/compare/v1.8.1...v.1.8.2

## [1.8.1] - 2019-08-07

### Fixed
- Bug fixes for parameter filtering
- Improved error handling

[1.8.1]: https://github.com/aserafin/grape_logging/compare/v1.8.0...v1.8.1

## [1.8.0] - 2019-05-30

### Added
- Rails formatter for better Rails integration
- Improved Rails instrumentation support

[1.8.0]: https://github.com/aserafin/grape_logging/compare/v1.7.0...v1.8.0

## [1.7.0] - 2017-11-09

### Added
- Logstash formatter for ELK stack integration
- Enhanced JSON formatting options

[1.7.0]: https://github.com/aserafin/grape_logging/compare/v1.6.0...v1.7.0

## [1.6.0] - 2017-07-20

### Added
- MultiIO support for logging to multiple destinations simultaneously
- Can now log to both file and STDOUT

[1.6.0]: https://github.com/aserafin/grape_logging/compare/v1.5.0...v1.6.0

## [1.5.0] - 2017-06-15

### Added
- Configurable log levels
- Better control over logging verbosity

[1.5.0]: https://github.com/aserafin/grape_logging/compare/v1.4.0...v1.5.0

## [1.4.0] - 2017-01-12

### Added
- FilterParameters logger for sensitive parameter filtering
- Automatic Rails filter_parameters integration when available

[1.4.0]: https://github.com/aserafin/grape_logging/compare/v1.3.0...v1.4.0

## [1.3.0] - 2016-12-08

### Added
- RequestHeaders logger for logging HTTP request headers
- ClientEnv logger for logging client IP and user agent

[1.3.0]: https://github.com/aserafin/grape_logging/compare/v1.2.1...v1.3.0

## [1.2.1] - 2016-04-14

### Added
- JSON formatter for structured logging
- Rails instrumentation support via ActiveSupport::Notifications

### Fixed
- Parameter handling improvements

[1.2.1]: https://github.com/aserafin/grape_logging/compare/v1.2.0...v1.2.1

## [1.2.0] - 2016-01-21

### Added
- Response logger for logging response details
- Improved parameter logging

### Changed
- Better integration with Grape middleware stack

[1.2.0]: https://github.com/aserafin/grape_logging/compare/v1.1.3...v1.2.0

## [1.1.3] - 2015-12-03

### Fixed
- Bug fixes for Grape 0.14 compatibility
- Improved error handling

[1.1.3]: https://github.com/aserafin/grape_logging/compare/v1.1.2...v1.1.3

## [1.1.2] - 2015-11-19

### Fixed
- Performance optimizations
- Minor bug fixes

[1.1.2]: https://github.com/aserafin/grape_logging/compare/v1.1.1...v1.1.2

## [1.1.1] - 2015-11-12

### Fixed
- Critical bug fix for middleware initialization

Note: This version was tagged as "v.1.1.1" (with extra dot)

[1.1.1]: https://github.com/aserafin/grape_logging/compare/v1.1.0...v.1.1.1

## [1.1.0] - 2015-11-09

### Added
- Pluggable logger architecture
- Support for custom loggers via include option
- Base logger class for extending functionality

### Changed
- Refactored middleware for better extensibility

[1.1.0]: https://github.com/aserafin/grape_logging/compare/v1.0.3...v1.1.0

## [1.0.3] - 2015-11-05

### Fixed
- Compatibility fixes for different Grape versions
- Bug fixes

[1.0.3]: https://github.com/aserafin/grape_logging/compare/v1.0.2...v1.0.3

## [1.0.2] - 2015-10-29

### Fixed
- Minor bug fixes and improvements

[1.0.2]: https://github.com/aserafin/grape_logging/compare/v1.0.1...v1.0.2

## [1.0.1] - 2015-10-22

### Fixed
- Initial bug fixes after 1.0 release

[1.0.1]: https://github.com/aserafin/grape_logging/compare/v1.0...v1.0.1

## [1.0] - 2015-10-15

### Added
- Initial release
- Request logging middleware for Grape APIs
- Basic request/response logging
- Configurable formatters
- Time tracking (total, db, view)


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to grape_logging

This project is work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues.

## Fork the Project

Fork the project on Github and check out your copy.

```
git clone https://github.com/contributor/grape_logging.git
cd grape_logging
git remote add upstream https://github.com/aserafin/grape_logging.git
```

## Create a Topic Branch

Make sure your fork is up-to-date and create a topic branch for your feature or bug fix.

```
git checkout master
git pull upstream master
git checkout -b my-feature-branch
```

## Bundle Install and Test

Ensure that you can build the project and run tests.

```
bundle install
bundle exec rake
```

## Write Tests

Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to the spec directory.

## Write Code

Implement your feature or bug fix.

Ruby style is enforced with RuboCop. Run `bundle exec rubocop` and fix any style issues highlighted.

Make sure that `bundle exec rake` completes without errors.

## Write Documentation

Document any external behavior in the README.md.

## Update Changelog

Add a line to Changelog.md under *Next Release*. Make it look like every other line, including your name and link to your Github account.

## Commit Changes

Make sure git knows your name and email address:

```
git config --global user.name "Your Name"
git config --global user.email "contributor@example.com"
```

Writing good commit logs is important. A commit log should describe what changed and why.

```
git add ...
git commit
```

## Push

```
git push origin my-feature-branch
```

## Make a Pull Request

Go to https://github.com/contributor/grape_logging and select your feature branch. Click the 'Pull Request' button and fill out the form.

We'll try to review pull requests within a few days but as this is maintained by a small group of volunteers there is no guarantee that we'll look at it within any time frame, or at all. There's no maintenance guarantee.

## Discuss and Update

You may get feedback or requests for changes to your pull request. This is a big part of the submission process so don't be discouraged!

Some things that will increase the chance that your pull request is accepted:

- Write tests.
- Follow the Ruby style guide.
- Write a good commit message.

If you'd like to discuss a feature or bug fix before starting work, please [create an issue](https://github.com/aserafin/grape_logging/issues) first. This helps ensure your contribution aligns with the project's direction and avoids duplicate efforts.

## Rebase

If you've been working on a change for a while, rebase with upstream/master.

```
git fetch upstream
git rebase upstream/master
git push origin my-feature-branch -f
```

## Be Patient

It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang in there!

## Thank You

Please do know that we really appreciate and value your time and work. We love you, really.


================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'

# Specify your gem's dependencies in grape_logging.gemspec
gemspec

gem 'rake', '~> 13.3'
gem 'rspec', '~> 3.5'

# This is pinned to an exact version otherwise we can't know which rules
# are in play at any given time in different environments.
gem 'rubocop', '1.77.0'


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2015 aserafin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# grape_logging

[![Gem Version](https://badge.fury.io/rb/grape_logging.svg)](https://badge.fury.io/rb/grape_logging)
[![CI](https://github.com/aserafin/grape_logging/actions/workflows/ci.yml/badge.svg)](https://github.com/aserafin/grape_logging/actions/workflows/ci.yml)

## Installation

Add this line to your application's Gemfile:

    gem 'grape_logging'

And then execute:

    $ bundle install

Or install it yourself as:

    $ gem install grape_logging

## Basic Usage

In your API file (somewhere on the top), insert grape logging middleware before grape error middleware. This is important due to the behaviour of `lib/grape/middleware/error.rb`, which manipulates the status of the response when there is an error.

```ruby
require 'grape_logging'
logger.formatter = GrapeLogging::Formatters::Default.new
insert_before Grape::Middleware::Error, GrapeLogging::Middleware::RequestLogger, { logger: logger }
```

**ProTip:** If your logger doesn't support setting formatter you can remove this line - it's optional

## Features

### Log Format

There are formatters provided for you, or you can provide your own.

#### `GrapeLogging::Formatters::Default`

    [2015-04-16 12:52:12 +0200] INFO -- 200 -- total=2.06 db=0.36 -- PATCH /api/endpoint params={"some_param"=>{"value_1"=>"123", "value_2"=>"456"}}

#### `GrapeLogging::Formatters::Json`

```json
{
  "date": "2015-04-16 12:52:12+0200",
  "severity": "INFO",
  "data": {
    "status": 200,
    "time": {
      "total": 2.06,
      "db": 0.36,
      "view": 1.70
    },
    "method": "PATCH",
    "path": "/api/endpoint",
    "params": {
      "value_1": "123",
      "value_2": "456"
    },
    "host": "localhost"
  }
}
```

#### `GrapeLogging::Formatters::Lograge`

    severity="INFO", duration=2.06, db=0.36, view=1.70, datetime="2015-04-16 12:52:12+0200", status=200, method="PATCH", path="/api/endpoint", params={}, host="localhost"

#### `GrapeLogging::Formatters::Logstash`

```json
{
  "@timestamp": "2015-04-16 12:52:12+0200",
  "severity": "INFO",
  "status": 200,
  "time": {
    "total": 2.06,
    "db": 0.36,
    "view": 1.70
  },
  "method": "PATCH",
  "path": "/api/endpoint",
  "params": {
    "value_1": "123",
    "value_2": "456"
  },
  "host": "localhost"
}
```

#### `GrapeLogging::Formatters::Rails`

Rails will print the "Started..." line:

    Started GET "/api/endpoint" for ::1 at 2015-04-16 12:52:12 +0200
      User Load (0.7ms)  SELECT "users".* FROM "users" WHERE  "users"."id" = $1
      ...

The `Rails` formatter adds the last line of the request, like a standard Rails request:

    Completed 200 OK in 349ms (Views: 250.1ms | DB: 98.63ms)

#### Custom

You can provide your own class that implements the `call` method returning a `String`:

```ruby
def call(severity, datetime, _, data)
   ...
end
```

You can change the formatter like so
```ruby
class MyAPI < Grape::API
  insert_before Grape::Middleware::Error, GrapeLogging::Middleware::RequestLogger, logger: logger, formatter: MyFormatter.new
end
```

If you prefer some other format I strongly encourage you to do pull request with new formatter class ;)

### Customising What Is Logged

You can include logging of other parts of the request / response cycle by including subclasses of `GrapeLogging::Loggers::Base`
```ruby
class MyAPI < Grape::API
  insert_before Grape::Middleware::Error,
    GrapeLogging::Middleware::RequestLogger,
    logger: logger,
    include: [ GrapeLogging::Loggers::Response.new,
               GrapeLogging::Loggers::FilterParameters.new,
               GrapeLogging::Loggers::ClientEnv.new,
               GrapeLogging::Loggers::RequestHeaders.new ]
end
```

#### FilterParameters
The `FilterParameters` logger will filter out sensitive parameters from your logs. If mounted inside rails, will use the `Rails.application.config.filter_parameters` by default. Otherwise, you must specify a list of keys to filter out.

#### ClientEnv
The `ClientEnv` logger will add `ip` and user agent `ua` in your log.

#### RequestHeaders
The `RequestHeaders` logger will add `request headers` in your log.

### Logging to file and STDOUT

You can log to file and STDOUT at the same time, you just need to assign new logger
```ruby
log_file = File.open('path/to/your/logfile.log', 'a')
log_file.sync = true
logger Logger.new GrapeLogging::MultiIO.new(STDOUT, log_file)
```

### Set the log level

You can control the level used to log. The default is `info`.

```ruby
class MyAPI < Grape::API
  insert_before Grape::Middleware::Error,
    GrapeLogging::Middleware::RequestLogger,
    logger: logger,
    log_level: 'debug'
end
```

### Logging via Rails instrumentation

You can choose to not pass the logger to ```grape_logging``` but instead send logs to Rails instrumentation in order to let Rails and its configured Logger do the log job, for example.
First, config ```grape_logging```, like that:
```ruby
class MyAPI < Grape::API
  insert_before Grape::Middleware::Error,
    GrapeLogging::Middleware::RequestLogger,
    instrumentation_key: 'grape_key',
    include: [ GrapeLogging::Loggers::Response.new,
               GrapeLogging::Loggers::FilterParameters.new ]
end
```

and then add an initializer in your Rails project:
```ruby
# config/initializers/instrumentation.rb

# Subscribe to grape request and log with Rails.logger
ActiveSupport::Notifications.subscribe('grape_key') do |name, starts, ends, notification_id, payload|
  Rails.logger.info payload
end
```

The idea come from here: https://gist.github.com/teamon/e8ae16ffb0cb447e5b49

### Logging exceptions

If you want to log exceptions you can do it like this
```ruby
class MyAPI < Grape::API
  rescue_from :all do |e|
    MyAPI.logger.error e
    #do here whatever you originally planned to do :)
  end
end
```
## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).

For maintainers releasing a new version, please see [RELEASING.md](RELEASING.md).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).


================================================
FILE: RELEASING.md
================================================
# Releasing grape_logging

There're no particular rules about when to release grape_logging. Release bug fixes frequently, features not so frequently and breaking API changes rarely.

### Pre-flight Checks

Run tests, check that all tests succeed locally.

```
bundle install
rake
```

Check that the last build succeeded in [GitHub Actions](https://github.com/aserafin/grape_logging/actions) for all supported platforms.

### Update Changelog

Change "Unreleased" in [CHANGELOG.md](https://github.com/aserafin/grape_logging/blob/master/CHANGELOG.md) to the new version and date:

```
## [1.8.5] - 2024-06-28

### Changed
- Description of changes

### Fixed
- Description of fixes

### Added
- Description of additions

[1.8.5]: https://github.com/aserafin/grape_logging/compare/v1.8.4...v1.8.5
```

Remove the line with "Your contribution here.", since there will be no more contributions to this release.

Only include the sections (Changed, Fixed, Added, etc.) that have actual changes.

Commit your changes.

```shell
git add CHANGELOG.md lib/grape_logging/version.rb
git commit -m "Preparing for release, 1.8.5."
git push
```

### Release on RubyGems and GitHub

#### Option 1: Automated (Recommended)

Use the combined task that releases the gem and creates a GitHub release:

```shell
rake github_release
```

This will:
1. Build and push the gem to RubyGems
2. Create and push the git tag
3. Create a GitHub release with auto-generated changelog

#### Option 2: Manual

First, release the gem:

```shell
rake release
```

Output will look something like:
```
grape_logging 1.8.5 built to pkg/grape_logging-1.8.5.gem.
Tagged v1.8.5.
Pushed git commits and tags.
Pushed grape_logging 1.8.5 to rubygems.org.
```

Then create the GitHub release on the web or using `gh`:

```
gh release create v1.8.5 --generate-notes --verify-tag
```

This uses GitHub's automatic changelog generation feature to create release notes from merged pull requests and commits since the last release.

### Prepare for the Next Version

Modify `lib/grape_logging/version.rb`, increment the version number (eg. change `1.8.5` to `1.8.6`).

```ruby
module GrapeLogging
  VERSION = '1.8.6'.freeze
end
```

Add the next release to [CHANGELOG.md](https://github.com/aserafin/grape_logging/blob/master/CHANGELOG.md).

```
## [1.8.6] - Unreleased

### Changed or Fixed or Added
- Your contribution here.

[1.8.6]: https://github.com/aserafin/grape_logging/compare/v1.8.5...master
```

Commit your changes.

```
git add CHANGELOG.md lib/grape_logging/version.rb
git commit -m "Bump version to 1.8.6."
git push
```


================================================
FILE: Rakefile
================================================
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'

RSpec::Core::RakeTask.new(:spec) do |spec|
  spec.rspec_opts = ['-fd -c']
  spec.pattern = FileList['spec/**/*_spec.rb']
end

RuboCop::RakeTask.new(:rubocop) do |t|
  t.patterns = ['lib/**/*.rb', 'spec/**/*.rb', 'Rakefile', 'Gemfile', 'grape_logging.gemspec']
end

task default: %i[spec rubocop]

desc 'Release gem and create GitHub release'
task github_release: :release do
  require 'grape_logging/version'

  version = "v#{GrapeLogging::VERSION}"

  # Check if gh CLI is available
  unless system('which gh > /dev/null 2>&1')
    puts "\n⚠️  GitHub CLI (gh) not found"
    puts 'To create a GitHub release with auto-generated changelog, install gh:'
    puts '  brew install gh  # macOS with Homebrew'
    puts '  # or visit: https://github.com/cli/cli#installation'
    puts "\nYou can manually create the release with:"
    puts "  gh release create v#{GrapeLogging::VERSION} --generate-notes"
    next
  end

  # Create GitHub release
  puts "\nCreating GitHub release #{version}..."

  if system('gh', 'release', 'create', version, '--generate-notes', '--verify-tag')
    puts "✅ GitHub release #{version} created successfully"
  else
    puts '❌ Failed to create GitHub release'
    puts 'You can manually create it with:'
    puts "  gh release create '#{version}' --generate-notes --verify-tag"
  end
end


================================================
FILE: bin/console
================================================
#!/usr/bin/env ruby

require 'bundler/setup'
require 'grape_logging'

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start

require 'irb'
IRB.start


================================================
FILE: bin/setup
================================================
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'

bundle install

# Do any other automated setup that you need to do here


================================================
FILE: grape_logging.gemspec
================================================
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'grape_logging/version'

Gem::Specification.new do |spec|
  spec.name          = 'grape_logging'
  spec.version       = GrapeLogging::VERSION
  spec.authors       = ['aserafin', 'Sami Samhuri']
  spec.email         = ['adrian@softmad.pl', 'sami@samhuri.net']

  spec.summary       = 'Out of the box request logging for Grape!'
  spec.description   = 'This gem provides simple request logging for Grape with just few lines ' \
                       'of code you have to put in your project! In return you will get response ' \
                       'codes, paths, parameters and more!'
  spec.homepage      = 'http://github.com/aserafin/grape_logging'
  spec.license       = 'MIT'

  spec.files         = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
  spec.bindir        = 'exe'
  spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
  spec.require_paths = ['lib']

  spec.add_dependency 'grape', '>= 2.4.0'
  spec.add_dependency 'rack'
  spec.add_dependency 'zeitwerk'
end


================================================
FILE: lib/grape_logging/formatters/default.rb
================================================
module GrapeLogging
  module Formatters
    class Default
      def call(severity, datetime, _, data)
        "[#{datetime}] #{severity} -- #{format(data)}\n"
      end

      def format(data)
        if data.is_a?(String)
          data
        elsif data.is_a?(Exception)
          format_exception(data)
        elsif data.is_a?(Hash)
          "#{data.delete(:status)} -- #{format_hash(data.delete(:time))} -- #{data.delete(:method)} #{data.delete(:path)} #{format_hash(data)}"
        else
          data.inspect
        end
      end

      private

      def format_hash(hash)
        hash.keys.sort.map { |key| "#{key}=#{hash[key]}" }.join(' ')
      end

      def format_exception(exception)
        backtrace_array = (exception.backtrace || []).map { |line| "\t#{line}" }
        "#{exception.message}\n#{backtrace_array.join("\n")}"
      end
    end
  end
end


================================================
FILE: lib/grape_logging/formatters/json.rb
================================================
module GrapeLogging
  module Formatters
    class Json
      def call(severity, datetime, _, data)
        {
          date: datetime,
          severity: severity,
          data: format(data)
        }.to_json + "\n"
      end

      private

      def format(data)
        if data.is_a?(String) || data.is_a?(Hash)
          data
        elsif data.is_a?(Exception)
          format_exception(data)
        else
          data.inspect
        end
      end

      def format_exception(exception)
        {
          exception: {
            message: exception.message
          }
        }
      end
    end
  end
end


================================================
FILE: lib/grape_logging/formatters/lograge.rb
================================================
module GrapeLogging
  module Formatters
    class Lograge
      def call(severity, datetime, _, data)
        time = data.delete :time
        attributes = {
          severity: severity,
          duration: time[:total],
          db: time[:db],
          view: time[:view],
          datetime: datetime.iso8601
        }.merge(data)
        ::Lograge.formatter.call(attributes) + "\n"
      end
    end
  end
end


================================================
FILE: lib/grape_logging/formatters/logstash.rb
================================================
module GrapeLogging
  module Formatters
    class Logstash
      def call(severity, datetime, _, data)
        {
          '@timestamp': datetime.iso8601,
          '@version': '1',
          severity: severity
        }.merge!(format(data)).to_json + "\n"
      end

      private

      def format(data)
        if data.is_a?(Hash)
          data
        elsif data.is_a?(String)
          { message: data }
        elsif data.is_a?(Exception)
          format_exception(data)
        else
          { message: data.inspect }
        end
      end

      def format_exception(exception)
        {
          exception: {
            message: exception.message
          }
        }
      end
    end
  end
end


================================================
FILE: lib/grape_logging/formatters/rails.rb
================================================
module GrapeLogging
  module Formatters
    class Rails
      def call(severity, datetime, _, data)
        if data.is_a?(String)
          "#{severity[0..0]} [#{datetime}] #{severity} -- : #{data}\n"
        elsif data.is_a?(Exception)
          "#{severity[0..0]} [#{datetime}] #{severity} -- : #{format_exception(data)}\n"
        elsif data.is_a?(Hash)
          format_hash(data)
        else
          "#{data.inspect}\n"
        end
      end

      private

      def format_exception(exception)
        backtrace_array = (exception.backtrace || []).map { |line| "\t#{line}" }

        [
          "#{exception.message} (#{exception.class})",
          backtrace_array.join("\n")
        ].reject { |line| line == '' }.join("\n")
      end

      def format_hash(hash)
        # Create Rails' single summary line at the end of every request, formatted like:
        # Completed 200 OK in 958ms (Views: 951.1ms | ActiveRecord: 3.8ms)
        # See: actionpack/lib/action_controller/log_subscriber.rb

        message = ''
        additions = []
        status = hash.delete(:status)
        params = hash.delete(:params)

        total_time = hash[:time] && hash[:time][:total] && hash[:time][:total].round(2)
        view_time  = hash[:time] && hash[:time][:view]  && hash[:time][:view].round(2)
        db_time    = hash[:time] && hash[:time][:db]    && hash[:time][:db].round(2)

        additions << "Views: #{view_time}ms" if view_time
        additions << "DB: #{db_time}ms"      if db_time

        message << "  Parameters: #{params.inspect}\n" if params

        message << "Completed #{status} #{::Rack::Utils::HTTP_STATUS_CODES[status]} in #{total_time}ms"
        message << " (#{additions.join(' | '.freeze)})" unless additions.empty?
        message << "\n"
        message << "\n" if defined?(::Rails.env) && ::Rails.env.development?

        message
      end
    end
  end
end


================================================
FILE: lib/grape_logging/loggers/base.rb
================================================
module GrapeLogging
  module Loggers
    class Base
      def parameters(_request, _response)
        {}
      end
    end
  end
end


================================================
FILE: lib/grape_logging/loggers/client_env.rb
================================================
module GrapeLogging
  module Loggers
    class ClientEnv < GrapeLogging::Loggers::Base
      def parameters(request, _)
        { ip: request.env['HTTP_X_FORWARDED_FOR'] || request.env['REMOTE_ADDR'], ua: request.env['HTTP_USER_AGENT'] }
      end
    end
  end
end


================================================
FILE: lib/grape_logging/loggers/filter_parameters.rb
================================================
module GrapeLogging
  module Loggers
    class FilterParameters < GrapeLogging::Loggers::Base
      AD_PARAMS = 'action_dispatch.request.parameters'.freeze

      def initialize(filter_parameters = nil, replacement = nil, exceptions = %w[controller action format])
        @filter_parameters = filter_parameters || (defined?(::Rails.application) ? ::Rails.application.config.filter_parameters : [])
        @replacement = replacement || '[FILTERED]'
        @exceptions = exceptions
      end

      def parameters(request, _)
        { params: safe_parameters(request) }
      end

      private

      def parameter_filter
        @parameter_filter ||= GrapeLogging::Util::ParameterFilter.new(@replacement, @filter_parameters)
      end

      def safe_parameters(request)
        # Now this logger can work also over Rails requests
        if request.params.empty?
          clean_parameters(request.env[AD_PARAMS] || {})
        else
          clean_parameters(request.params)
        end
      end

      def clean_parameters(parameters)
        original_encoding_map = build_encoding_map(parameters)
        params = transform_key_encoding(parameters, Hash.new { |h, _| [Encoding::ASCII_8BIT, h] })
        cleaned_params = parameter_filter.filter(params).except(*@exceptions)
        transform_key_encoding(cleaned_params, original_encoding_map)
      end

      def build_encoding_map(parameters)
        parameters.each_with_object({}) do |(k, v), h|
          key_str = k.to_s
          h[key_str.dup.force_encoding(Encoding::ASCII_8BIT)] = [key_str.encoding, v.is_a?(Hash) ? build_encoding_map(v) : nil]
        end
      end

      def transform_key_encoding(parameters, encoding_map)
        parameters.each_with_object({}) do |(k, v), h|
          key_str = k.to_s
          encoding, children_encoding_map = encoding_map[key_str]
          h[key_str.dup.force_encoding(encoding)] = v.is_a?(Hash) ? transform_key_encoding(v, children_encoding_map) : v
        end
      end
    end
  end
end


================================================
FILE: lib/grape_logging/loggers/request_headers.rb
================================================
module GrapeLogging
  module Loggers
    class RequestHeaders < GrapeLogging::Loggers::Base
      HTTP_PREFIX = 'HTTP_'.freeze

      def parameters(request, _)
        headers = {}

        request.env.each_pair do |k, v|
          next unless k.to_s.start_with? HTTP_PREFIX

          k = k[5..-1].split('_').each(&:capitalize!).join('-')
          headers[k] = v
        end

        { headers: headers }
      end
    end
  end
end


================================================
FILE: lib/grape_logging/loggers/response.rb
================================================
module GrapeLogging
  module Loggers
    class Response < GrapeLogging::Loggers::Base
      def parameters(_, response)
        response ? { response: serialized_response_body(response) } : {}
      end

      private

      # In some cases, response.body is not parseable by JSON.
      # For example, if you POST on a PUT endpoint, response.body is egal to """".
      # It's strange, but it's the Grape behavior...
      def serialized_response_body(response)
        if response.respond_to?(:body)
          # Rack responses
          begin
            response.body.map { |body| JSON.parse(body.to_s) }
          rescue StandardError # No reason to have "=> e" here when we don't use it..
            response.body
          end
        else
          # Error & Exception responses
          response
        end
      end
    end
  end
end


================================================
FILE: lib/grape_logging/middleware/request_logger.rb
================================================
module GrapeLogging
  module Middleware
    class RequestLogger < Grape::Middleware::Base
      if defined?(ActiveRecord)
        ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
          event = ActiveSupport::Notifications::Event.new(*args)
          GrapeLogging::Timings.append_db_runtime(event)
        end
      end

      # Persist response status & response (body)
      # to use int in parameters
      attr_accessor :response_status, :response_body

      def initialize(app, **options)
        super

        @included_loggers = @options[:include] || []
        @reporter =
          if options[:instrumentation_key]
            Reporters::ActiveSupportReporter.new(@options[:instrumentation_key])
          else
            Reporters::LoggerReporter.new(@options[:logger], @options[:formatter], @options[:log_level])
          end
      end

      def before
        reset_db_runtime
        start_time
        invoke_included_loggers(:before)
      end

      def after(status, response)
        stop_time

        # Response status
        @response_status = status
        @response_body   = response

        # Perform reporters
        @reporter.perform(collect_parameters)

        # Invoke loggers
        invoke_included_loggers(:after)
        nil
      end

      # Call stack and parse responses & status.
      #
      # @note Exceptions are logged as 500 status & re-raised.
      def call!(env)
        @env = env

        # Before hook
        before

        # Catch error
        error = catch(:error) do
          @app_response = @app.call(@env)
          nil
        rescue StandardError => e
          # Log as 500 + message
          after(e.respond_to?(:status) ? e.status : 500, e.message)

          # Re-raise exception
          raise e
        end

        # Get status & response from app_response
        # when no error occurs.
        if error
          # Call with error & response
          after(error[:status], error[:message])

          # Throw again
          throw(:error, error)
        else
          status, _, resp = *@app_response

          # Call after hook properly
          after(status, resp)
        end

        # Otherwise return original response
        @app_response
      end

      protected

      def parameters
        {
          status: response_status,
          time: {
            total: total_runtime,
            db: db_runtime,
            view: view_runtime
          },
          method: request.request_method,
          path: request.path,
          params: request.params,
          host: request.host,
          request_id: env['action_dispatch.request_id']
        }
      end

      private

      def request
        @request ||= ::Rack::Request.new(@env)
      end

      def total_runtime
        ((stop_time - start_time) * 1000).round(2)
      end

      def view_runtime
        (total_runtime - db_runtime).round(2)
      end

      def db_runtime
        GrapeLogging::Timings.db_runtime.round(2)
      end

      def reset_db_runtime
        GrapeLogging::Timings.reset_db_runtime
      end

      def start_time
        @start_time ||= Time.now
      end

      def stop_time
        @stop_time ||= Time.now
      end

      def collect_parameters
        parameters.tap do |params|
          @included_loggers.each do |logger|
            params.merge! logger.parameters(request, response_body) do |_, oldval, newval|
              oldval.respond_to?(:merge) ? oldval.merge(newval) : newval
            end
          end
        end
      end

      def invoke_included_loggers(method_name)
        @included_loggers.each do |logger|
          logger.send(method_name) if logger.respond_to?(method_name)
        end
      end
    end
  end
end


================================================
FILE: lib/grape_logging/multi_io.rb
================================================
module GrapeLogging
  class MultiIO
    def initialize(*targets)
      @targets = targets
    end

    def write(*args)
      @targets.each { |t| t.write(*args) }
    end

    def close
      @targets.each(&:close)
    end
  end
end


================================================
FILE: lib/grape_logging/reporters/active_support_reporter.rb
================================================
module GrapeLogging
  module Reporters
    class ActiveSupportReporter
      def initialize(instrumentation_key)
        @instrumentation_key = instrumentation_key
      end

      def perform(params)
        ActiveSupport::Notifications.instrument @instrumentation_key, params
      end
    end
  end
end


================================================
FILE: lib/grape_logging/reporters/logger_reporter.rb
================================================
module GrapeLogging
  module Reporters
    class LoggerReporter
      def initialize(logger, formatter, log_level)
        @logger = logger.clone || Logger.new(STDOUT)
        @log_level = log_level || :info
        @logger.formatter = formatter || @logger.formatter || GrapeLogging::Formatters::Default.new if @logger.respond_to?(:formatter=)
      end

      def perform(params)
        @logger.send(@log_level, params)
      end
    end
  end
end


================================================
FILE: lib/grape_logging/timings.rb
================================================
module GrapeLogging
  module Timings
    def self.db_runtime=(value)
      Thread.current[:grape_db_runtime] = value
    end

    def self.db_runtime
      Thread.current[:grape_db_runtime] ||= 0
    end

    def self.reset_db_runtime
      self.db_runtime = 0
    end

    def self.append_db_runtime(event)
      self.db_runtime += event.duration
    end
  end
end


================================================
FILE: lib/grape_logging/util/parameter_filter.rb
================================================
module GrapeLogging
  module Util
    if defined?(Rails.application)
      if Gem::Version.new(Rails.version) < Gem::Version.new('6.0.0')
        class ParameterFilter < ActionDispatch::Http::ParameterFilter
          def initialize(_replacement, filter_parameters)
            super(filter_parameters)
          end
        end
      else
        require 'active_support/parameter_filter'

        class ParameterFilter < ActiveSupport::ParameterFilter
          def initialize(_replacement, filter_parameters)
            super(filter_parameters)
          end
        end
      end
    else
      #
      # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb
      # we could depend on Rails specifically, but that would us way to hefty!
      #
      class ParameterFilter
        def initialize(replacement, filters = [])
          @replacement = replacement
          @filters = filters
        end

        def filter(params)
          compiled_filter.call(params)
        end

        private

        def compiled_filter
          @compiled_filter ||= CompiledFilter.compile(@replacement, @filters)
        end

        class CompiledFilter # :nodoc:
          def self.compile(replacement, filters)
            return ->(params) { params.dup } if filters.empty?

            strings = []
            regexps = []
            blocks = []

            filters.each do |item|
              case item
              when Proc
                blocks << item
              when Regexp
                regexps << item
              else
                strings << Regexp.escape(item.to_s)
              end
            end

            deep_regexps, regexps = regexps.partition { |r| r.to_s.include?('\\.'.freeze) }
            deep_strings, strings = strings.partition { |s| s.include?('\\.'.freeze) }

            regexps << Regexp.new(strings.join('|'.freeze), true) unless strings.empty?
            deep_regexps << Regexp.new(deep_strings.join('|'.freeze), true) unless deep_strings.empty?

            new replacement, regexps, deep_regexps, blocks
          end

          attr_reader :regexps, :deep_regexps, :blocks

          def initialize(replacement, regexps, deep_regexps, blocks)
            @replacement = replacement
            @regexps = regexps
            @deep_regexps = deep_regexps.any? ? deep_regexps : nil
            @blocks = blocks
          end

          def call(original_params, parents = [])
            filtered_params = {}

            original_params.each do |key, value|
              parents.push(key) if deep_regexps
              if regexps.any? { |r| key =~ r }
                value = @replacement
              elsif deep_regexps && (joined = parents.join('.')) && deep_regexps.any? { |r| joined =~ r }
                value = @replacement
              elsif value.is_a?(Hash)
                value = call(value, parents)
              elsif value.is_a?(Array)
                value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v }
              elsif blocks.any?
                key = key.dup if key.duplicable?
                value = value.dup if value.duplicable?
                blocks.each { |b| b.call(key, value) }
              end
              parents.pop if deep_regexps

              filtered_params[key] = value
            end

            filtered_params
          end
        end
      end
    end
  end
end


================================================
FILE: lib/grape_logging/version.rb
================================================
module GrapeLogging
  VERSION = '3.0.1'
end


================================================
FILE: lib/grape_logging.rb
================================================
require 'grape'
require 'rack/utils'
require 'zeitwerk'

# load zeitwerk
Zeitwerk::Loader.for_gem.tap do |loader|
  loader.inflector.inflect 'multi_io' => 'MultiIO'
  loader.setup
end

module GrapeLogging
end


================================================
FILE: spec/lib/grape_logging/formatters/rails_spec.rb
================================================
require 'spec_helper'

describe GrapeLogging::Formatters::Rails do
  let(:formatter) { described_class.new }
  let(:severity) { 'INFO' }
  let(:datetime) { Time.new('2018', '03', '02', '10', '35', '04', '+13:00') }

  let(:exception_data) { ArgumentError.new('Message') }
  let(:hash_data) do
    {
      status: 200,
      time: {
        total: 272.4,
        db: 40.63,
        view: 231.76999999999998
      },
      method: 'GET',
      path: '/api/endpoint',
      host: 'localhost'
    }
  end

  describe '#call' do
    context 'string data' do
      it 'returns a formatted string' do
        message = formatter.call(severity, datetime, nil, 'value')

        expect(message).to eq "I [2018-03-02 10:35:04 +1300] INFO -- : value\n"
      end
    end

    context 'exception data' do
      it 'returns a string with a backtrace' do
        exception_data.set_backtrace(caller)

        message = formatter.call(severity, datetime, nil, exception_data)
        lines = message.split("\n")

        expect(lines[0]).to eq 'I [2018-03-02 10:35:04 +1300] INFO -- : Message (ArgumentError)'
        expect(lines[1]).to include '.rb'
        expect(lines.size).to be > 1
      end
    end

    context 'hash data' do
      it 'returns a formatted string' do
        message = formatter.call(severity, datetime, nil, hash_data)

        expect(message).to eq "Completed 200 OK in 272.4ms (Views: 231.77ms | DB: 40.63ms)\n"
      end

      it 'includes params if included (from GrapeLogging::Loggers::FilterParameters)' do
        hash_data.merge!(
          params: {
            'some_param' => {
              value_1: '123',
              value_2: '456'
            }
          }
        )

        message = formatter.call(severity, datetime, nil, hash_data)
        lines = message.split("\n")

        expected_output =
          if RUBY_VERSION >= '3.4'
            '  Parameters: {"some_param" => {value_1: "123", value_2: "456"}}'
          else
            '  Parameters: {"some_param"=>{:value_1=>"123", :value_2=>"456"}}'
          end
        expect(lines.first).to eq expected_output
        expect(lines.last).to eq 'Completed 200 OK in 272.4ms (Views: 231.77ms | DB: 40.63ms)'
      end
    end

    context 'unhandled data' do
      it 'returns the #inspect string representation' do
        message = formatter.call(severity, datetime, nil, [1, 2, 3])

        expect(message).to eq "[1, 2, 3]\n"
      end
    end
  end
end


================================================
FILE: spec/lib/grape_logging/loggers/client_env_spec.rb
================================================
require 'spec_helper'

describe GrapeLogging::Loggers::ClientEnv do
  let(:ip) { '10.0.0.1' }
  let(:user_agent) { 'user agent' }
  let(:forwarded_for) { 'forwarded for' }
  let(:remote_addr) { 'remote address' }

  context 'forwarded for' do
    let(:mock_request) do
      instance_double(Rack::Request, env: {
                        'HTTP_X_FORWARDED_FOR' => forwarded_for
                      })
    end

    it 'sets the ip key' do
      expect(subject.parameters(mock_request, nil)).to eq(ip: forwarded_for, ua: nil)
    end

    it 'prefers the forwarded_for over the remote_addr' do
      mock_request.env['REMOTE_ADDR'] = remote_addr
      expect(subject.parameters(mock_request, nil)).to eq(ip: forwarded_for, ua: nil)
    end
  end

  context 'remote address' do
    let(:mock_request) do
      instance_double(Rack::Request, env: {
                        'REMOTE_ADDR' => remote_addr
                      })
    end

    it 'sets the ip key' do
      expect(subject.parameters(mock_request, nil)).to eq(ip: remote_addr, ua: nil)
    end
  end

  context 'user agent' do
    let(:mock_request) do
      instance_double(Rack::Request, env: {
                        'HTTP_USER_AGENT' => user_agent
                      })
    end

    it 'sets the ua key' do
      expect(subject.parameters(mock_request, nil)).to eq(ip: nil, ua: user_agent)
    end
  end
end


================================================
FILE: spec/lib/grape_logging/loggers/filter_parameters_spec.rb
================================================
require 'spec_helper'

describe GrapeLogging::Loggers::FilterParameters do
  let(:filtered_parameters) { %w[one four] }

  let(:mock_request) do
    instance_double(Rack::Request, params: {
                      'this_one' => 'this one',
                      'that_one' => 'one',
                      'two' => 'two',
                      'three' => 'three',
                      'four' => 'four',
                      "\xff" => 'invalid utf8'
                    })
  end

  let(:mock_request_with_deep_nesting) do
    deep_clone = -> { Marshal.load Marshal.dump mock_request.params }
    instance_double(Rack::Request,
                    params: deep_clone.call.merge(
                      'five' => deep_clone.call.merge(
                        deep_clone.call.merge({ 'six' => { 'seven' => 'seven', 'eight' => 'eight', 'one' => 'another one' } })
                      )
                    ))
  end

  let(:subject) do
    GrapeLogging::Loggers::FilterParameters.new filtered_parameters, replacement
  end

  let(:replacement) { nil }

  shared_examples 'filtering' do
    it 'filters out sensitive parameters' do
      expect(subject.parameters(mock_request, nil)).to eq(params: {
                                                            'this_one' => subject.instance_variable_get('@replacement'),
                                                            'that_one' => subject.instance_variable_get('@replacement'),
                                                            'two' => 'two',
                                                            'three' => 'three',
                                                            'four' => subject.instance_variable_get('@replacement'),
                                                            "\xff" => 'invalid utf8'
                                                          })
    end

    it 'deeply filters out sensitive parameters' do
      expect(subject.parameters(mock_request_with_deep_nesting, nil)).to eq(params: {
                                                                              'this_one' => subject.instance_variable_get('@replacement'),
                                                                              'that_one' => subject.instance_variable_get('@replacement'),
                                                                              'two' => 'two',
                                                                              'three' => 'three',
                                                                              'four' => subject.instance_variable_get('@replacement'),
                                                                              "\xff" => 'invalid utf8',
                                                                              'five' => {
                                                                                'this_one' => subject.instance_variable_get('@replacement'),
                                                                                'that_one' => subject.instance_variable_get('@replacement'),
                                                                                'two' => 'two',
                                                                                'three' => 'three',
                                                                                'four' => subject.instance_variable_get('@replacement'),
                                                                                "\xff" => 'invalid utf8',
                                                                                'six' => {
                                                                                  'seven' => 'seven',
                                                                                  'eight' => 'eight',
                                                                                  'one' => subject.instance_variable_get('@replacement')
                                                                                }
                                                                              }
                                                                            })
    end
  end

  context 'with default replacement' do
    it_behaves_like 'filtering'
  end

  context 'with custom replacement' do
    let(:replacement) { 'CUSTOM_REPLACEMENT' }
    it_behaves_like 'filtering'
  end

  context 'with symbol keys, which occur during automated testing' do
    let(:mock_request) { instance_double(Rack::Request, params: { sneaky_symbol: 'hey!' }) }

    it 'converts keys to strings' do
      expect(subject.parameters(mock_request, nil)).to eq(params: {
                                                            'sneaky_symbol' => 'hey!'
                                                          })
    end
  end
end


================================================
FILE: spec/lib/grape_logging/loggers/request_headers_spec.rb
================================================
require 'spec_helper'

describe GrapeLogging::Loggers::RequestHeaders do
  let(:mock_request) do
    instance_double(Rack::Request, env: { HTTP_REFERER: 'http://example.com', HTTP_ACCEPT: 'text/plain' })
  end

  let(:mock_request_with_unhandled_headers) do
    instance_double(Rack::Request, env: {
                      HTTP_REFERER: 'http://example.com',
                      'PATH_INFO' => '/api/v1/users'
                    })
  end

  let(:mock_request_with_long_headers) do
    instance_double(Rack::Request, env: {
                      HTTP_REFERER: 'http://example.com',
                      HTTP_USER_AGENT: 'Mozilla/5.0'
                    })
  end

  it 'strips HTTP_ from the parameter' do
    expect(subject.parameters(mock_request, nil)).to eq(
      {
        headers: { 'Referer' => 'http://example.com', 'Accept' => 'text/plain' }
      }
    )
  end

  it 'only handle things which start with HTTP_' do
    expect(subject.parameters(mock_request_with_unhandled_headers, nil)).to eq(
      {
        headers: { 'Referer' => 'http://example.com' }
      }
    )
  end

  it 'substitutes _ with -' do
    expect(subject.parameters(mock_request_with_long_headers, nil)).to eq(
      {
        headers: { 'Referer' => 'http://example.com', 'User-Agent' => 'Mozilla/5.0' }
      }
    )
  end
end


================================================
FILE: spec/lib/grape_logging/loggers/response_spec.rb
================================================
require 'spec_helper'

describe GrapeLogging::Loggers::Response do
  context 'with a parseable JSON body' do
    let(:response) do
      instance_double(Rack::Request, body: [{ one: 'two', three: { four: 5 } }])
    end

    it 'returns an array of parsed JSON objects' do
      expect(subject.parameters(nil, response)).to eq({ response: [response.body.first] })
    end
  end

  context 'with a body that is not parseable JSON' do
    let(:response) do
      instance_double(Rack::Request, body: 'this is a body')
    end

    it 'just returns the body' do
      expect(subject.parameters(nil, response)).to eq({ response: response.body })
    end
  end
end


================================================
FILE: spec/lib/grape_logging/middleware/request_logger_spec.rb
================================================
require 'spec_helper'
require 'rack'

describe GrapeLogging::Middleware::RequestLogger do
  let(:env) { { 'action_dispatch.request_id' => 'request-abc123' } }
  let(:subject) { request.send(request_method, path, env) }
  let(:app) { proc { [status, {}, ['response body']] } }
  let(:stack) { described_class.new app, **options }
  let(:request) { Rack::MockRequest.new(stack) }
  let(:options) { { include: [], logger: logger } }
  let(:logger) { double('logger') }
  let(:path) { '/' }
  let(:request_method) { 'get' }
  let(:status) { 200 }

  it 'logs to the logger' do
    expect(logger).to receive('info') do |arguments|
      expect(arguments[:status]).to eq 200
      expect(arguments[:method]).to eq 'GET'
      expect(arguments[:params]).to be_empty
      expect(arguments[:host]).to eq 'example.org'
      expect(arguments[:request_id]).to eq 'request-abc123'
      expect(arguments).to have_key :time
      expect(arguments[:time]).to have_key :total
      expect(arguments[:time]).to have_key :db
      expect(arguments[:time]).to have_key :view
    end
    subject
  end

  [301, 404, 500].each do |the_status|
    context "when the response status is #{the_status}" do
      let(:status) { the_status }
      it 'should log the correct status code' do
        expect(logger).to receive('info') do |arguments|
          expect(arguments[:status]).to eq the_status
        end
        subject
      end
    end
  end

  %w[info error debug].each do |level|
    context "with level #{level}" do
      it 'should log at correct level' do
        options[:log_level] = level
        expect(logger).to receive(level)
        subject
      end
    end
  end

  context 'with a nil response' do
    let(:app) { proc { [500, {}, nil] } }
    it 'should log "fail" instead of a status' do
      expect(Rack::MockResponse).to receive(:new) { nil }
      expect(logger).to receive('info') do |arguments|
        expect(arguments[:status]).to eq 500
      end
      subject
    end
  end

  context 'additional_loggers' do
    before do
      options[:include] << GrapeLogging::Loggers::RequestHeaders.new
      options[:include] << GrapeLogging::Loggers::ClientEnv.new
      options[:include] << GrapeLogging::Loggers::Response.new
      options[:include] << GrapeLogging::Loggers::FilterParameters.new(['replace_me'])
    end

    %w[get put post delete options head patch].each do |the_method|
      let(:request_method) { the_method }
      context "with HTTP method[#{the_method}]" do
        it 'should include additional information in the log' do
          expect(logger).to receive('info') do |arguments|
            expect(arguments).to have_key :headers
            expect(arguments).to have_key :ip
            expect(arguments).to have_key :response
          end
          subject
        end
      end
    end

    it 'should filter parameters in the log' do
      expect(logger).to receive('info') do |arguments|
        expect(arguments[:params]).to eq(
          'replace_me' => '[FILTERED]',
          'replace_me_too' => '[FILTERED]',
          'cant_touch_this' => 'should see'
        )
      end
      parameters = {
        'replace_me' => 'should not see',
        'replace_me_too' => 'should not see',
        'cant_touch_this' => 'should see'
      }
      request.post path, params: parameters
    end
  end
end


================================================
FILE: spec/spec_helper.rb
================================================
$:.unshift '.'

require 'lib/grape_logging'

RSpec.configure do |config|
  # rspec-expectations config goes here. You can use an alternate
  # assertion/expectation library such as wrong or the stdlib/minitest
  # assertions if you prefer.
  config.expect_with :rspec do |expectations|
    # This option will default to `true` in RSpec 4. It makes the `description`
    # and `failure_message` of custom matchers include text for helper methods
    # defined using `chain`, e.g.:
    #     be_bigger_than(2).and_smaller_than(4).description
    #     # => "be bigger than 2 and smaller than 4"
    # ...rather than:
    #     # => "be bigger than 2"
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end

  # rspec-mocks config goes here. You can use an alternate test double
  # library (such as bogus or mocha) by changing the `mock_with` option here.
  config.mock_with :rspec do |mocks|
    # Prevents you from mocking or stubbing a method that does not exist on
    # a real object. This is generally recommended, and will default to
    # `true` in RSpec 4.
    mocks.verify_partial_doubles = true
  end

  # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
  # have no way to turn it off -- the option exists only for backwards
  # compatibility in RSpec 3). It causes shared context metadata to be
  # inherited by the metadata hash of host groups and examples, rather than
  # triggering implicit auto-inclusion in groups with matching metadata.
  config.shared_context_metadata_behavior = :apply_to_host_groups

  # The settings below are suggested to provide a good initial experience
  # with RSpec, but feel free to customize to your heart's content.
  #   # This allows you to limit a spec run to individual examples or groups
  #   # you care about by tagging them with `:focus` metadata. When nothing
  #   # is tagged with `:focus`, all examples get run. RSpec also provides
  #   # aliases for `it`, `describe`, and `context` that include `:focus`
  #   # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
  #   config.filter_run_when_matching :focus
  #
  #   # Allows RSpec to persist some state between runs in order to support
  #   # the `--only-failures` and `--next-failure` CLI options. We recommend
  #   # you configure your source control system to ignore this file.
  #   config.example_status_persistence_file_path = "spec/examples.txt"
  #
  #   # Limits the available syntax to the non-monkey patched syntax that is
  #   # recommended. For more details, see:
  #   #   - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
  #   #   - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
  #   #   - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
  #   config.disable_monkey_patching!
  #
  #   # This setting enables warnings. It's recommended, but in some cases may
  #   # be too noisy due to issues in dependencies.
  #   config.warnings = true
  #
  #   # Many RSpec users commonly either run the entire suite or an individual
  #   # file, and it's useful to allow more verbose output when running an
  #   # individual spec file.
  #   if config.files_to_run.one?
  #     # Use the documentation formatter for detailed output,
  #     # unless a formatter has already been configured
  #     # (e.g. via a command-line flag).
  #     config.default_formatter = 'doc'
  #   end
  #
  #   # Print the 10 slowest examples and example groups at the
  #   # end of the spec run, to help surface which specs are running
  #   # particularly slow.
  #   config.profile_examples = 10
  #
  #   # Run specs in random order to surface order dependencies. If you find an
  #   # order dependency and want to debug it, you can fix the order by providing
  #   # the seed, which is printed after each run.
  #   #     --seed 1234
  #   config.order = :random
  #
  #   # Seed global randomization in this process using the `--seed` CLI option.
  #   # Setting this allows you to use `--seed` to deterministically reproduce
  #   # test failures related to randomization by passing the same `--seed` value
  #   # as the one that triggered the failure.
  #   Kernel.srand config.seed
end
Download .txt
gitextract_w93mw737/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── RELEASING.md
├── Rakefile
├── bin/
│   ├── console
│   └── setup
├── grape_logging.gemspec
├── lib/
│   ├── grape_logging/
│   │   ├── formatters/
│   │   │   ├── default.rb
│   │   │   ├── json.rb
│   │   │   ├── lograge.rb
│   │   │   ├── logstash.rb
│   │   │   └── rails.rb
│   │   ├── loggers/
│   │   │   ├── base.rb
│   │   │   ├── client_env.rb
│   │   │   ├── filter_parameters.rb
│   │   │   ├── request_headers.rb
│   │   │   └── response.rb
│   │   ├── middleware/
│   │   │   └── request_logger.rb
│   │   ├── multi_io.rb
│   │   ├── reporters/
│   │   │   ├── active_support_reporter.rb
│   │   │   └── logger_reporter.rb
│   │   ├── timings.rb
│   │   ├── util/
│   │   │   └── parameter_filter.rb
│   │   └── version.rb
│   └── grape_logging.rb
└── spec/
    ├── lib/
    │   └── grape_logging/
    │       ├── formatters/
    │       │   └── rails_spec.rb
    │       ├── loggers/
    │       │   ├── client_env_spec.rb
    │       │   ├── filter_parameters_spec.rb
    │       │   ├── request_headers_spec.rb
    │       │   └── response_spec.rb
    │       └── middleware/
    │           └── request_logger_spec.rb
    └── spec_helper.rb
Download .txt
SYMBOL INDEX (110 symbols across 18 files)

FILE: lib/grape_logging.rb
  type GrapeLogging (line 11) | module GrapeLogging

FILE: lib/grape_logging/formatters/default.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Formatters (line 2) | module Formatters
      class Default (line 3) | class Default
        method call (line 4) | def call(severity, datetime, _, data)
        method format (line 8) | def format(data)
        method format_hash (line 22) | def format_hash(hash)
        method format_exception (line 26) | def format_exception(exception)

FILE: lib/grape_logging/formatters/json.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Formatters (line 2) | module Formatters
      class Json (line 3) | class Json
        method call (line 4) | def call(severity, datetime, _, data)
        method format (line 14) | def format(data)
        method format_exception (line 24) | def format_exception(exception)

FILE: lib/grape_logging/formatters/lograge.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Formatters (line 2) | module Formatters
      class Lograge (line 3) | class Lograge
        method call (line 4) | def call(severity, datetime, _, data)

FILE: lib/grape_logging/formatters/logstash.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Formatters (line 2) | module Formatters
      class Logstash (line 3) | class Logstash
        method call (line 4) | def call(severity, datetime, _, data)
        method format (line 14) | def format(data)
        method format_exception (line 26) | def format_exception(exception)

FILE: lib/grape_logging/formatters/rails.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Formatters (line 2) | module Formatters
      class Rails (line 3) | class Rails
        method call (line 4) | def call(severity, datetime, _, data)
        method format_exception (line 18) | def format_exception(exception)
        method format_hash (line 27) | def format_hash(hash)

FILE: lib/grape_logging/loggers/base.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Loggers (line 2) | module Loggers
      class Base (line 3) | class Base
        method parameters (line 4) | def parameters(_request, _response)

FILE: lib/grape_logging/loggers/client_env.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Loggers (line 2) | module Loggers
      class ClientEnv (line 3) | class ClientEnv < GrapeLogging::Loggers::Base
        method parameters (line 4) | def parameters(request, _)

FILE: lib/grape_logging/loggers/filter_parameters.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Loggers (line 2) | module Loggers
      class FilterParameters (line 3) | class FilterParameters < GrapeLogging::Loggers::Base
        method initialize (line 6) | def initialize(filter_parameters = nil, replacement = nil, excepti...
        method parameters (line 12) | def parameters(request, _)
        method parameter_filter (line 18) | def parameter_filter
        method safe_parameters (line 22) | def safe_parameters(request)
        method clean_parameters (line 31) | def clean_parameters(parameters)
        method build_encoding_map (line 38) | def build_encoding_map(parameters)
        method transform_key_encoding (line 45) | def transform_key_encoding(parameters, encoding_map)

FILE: lib/grape_logging/loggers/request_headers.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Loggers (line 2) | module Loggers
      class RequestHeaders (line 3) | class RequestHeaders < GrapeLogging::Loggers::Base
        method parameters (line 6) | def parameters(request, _)

FILE: lib/grape_logging/loggers/response.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Loggers (line 2) | module Loggers
      class Response (line 3) | class Response < GrapeLogging::Loggers::Base
        method parameters (line 4) | def parameters(_, response)
        method serialized_response_body (line 13) | def serialized_response_body(response)

FILE: lib/grape_logging/middleware/request_logger.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Middleware (line 2) | module Middleware
      class RequestLogger (line 3) | class RequestLogger < Grape::Middleware::Base
        method initialize (line 15) | def initialize(app, **options)
        method before (line 27) | def before
        method after (line 33) | def after(status, response)
        method call! (line 51) | def call!(env)
        method parameters (line 90) | def parameters
        method request (line 108) | def request
        method total_runtime (line 112) | def total_runtime
        method view_runtime (line 116) | def view_runtime
        method db_runtime (line 120) | def db_runtime
        method reset_db_runtime (line 124) | def reset_db_runtime
        method start_time (line 128) | def start_time
        method stop_time (line 132) | def stop_time
        method collect_parameters (line 136) | def collect_parameters
        method invoke_included_loggers (line 146) | def invoke_included_loggers(method_name)

FILE: lib/grape_logging/multi_io.rb
  type GrapeLogging (line 1) | module GrapeLogging
    class MultiIO (line 2) | class MultiIO
      method initialize (line 3) | def initialize(*targets)
      method write (line 7) | def write(*args)
      method close (line 11) | def close

FILE: lib/grape_logging/reporters/active_support_reporter.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Reporters (line 2) | module Reporters
      class ActiveSupportReporter (line 3) | class ActiveSupportReporter
        method initialize (line 4) | def initialize(instrumentation_key)
        method perform (line 8) | def perform(params)

FILE: lib/grape_logging/reporters/logger_reporter.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Reporters (line 2) | module Reporters
      class LoggerReporter (line 3) | class LoggerReporter
        method initialize (line 4) | def initialize(logger, formatter, log_level)
        method perform (line 10) | def perform(params)

FILE: lib/grape_logging/timings.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Timings (line 2) | module Timings
      function db_runtime= (line 3) | def self.db_runtime=(value)
      function db_runtime (line 7) | def self.db_runtime
      function reset_db_runtime (line 11) | def self.reset_db_runtime
      function append_db_runtime (line 15) | def self.append_db_runtime(event)

FILE: lib/grape_logging/util/parameter_filter.rb
  type GrapeLogging (line 1) | module GrapeLogging
    type Util (line 2) | module Util
      class ParameterFilter (line 5) | class ParameterFilter < ActionDispatch::Http::ParameterFilter
        method initialize (line 6) | def initialize(_replacement, filter_parameters)
        method initialize (line 14) | def initialize(_replacement, filter_parameters)
        method initialize (line 25) | def initialize(replacement, filters = [])
        method filter (line 30) | def filter(params)
        method compiled_filter (line 36) | def compiled_filter
        class CompiledFilter (line 40) | class CompiledFilter # :nodoc:
          method compile (line 41) | def self.compile(replacement, filters)
          method initialize (line 70) | def initialize(replacement, regexps, deep_regexps, blocks)
          method call (line 77) | def call(original_params, parents = [])
      class ParameterFilter (line 13) | class ParameterFilter < ActiveSupport::ParameterFilter
        method initialize (line 6) | def initialize(_replacement, filter_parameters)
        method initialize (line 14) | def initialize(_replacement, filter_parameters)
        method initialize (line 25) | def initialize(replacement, filters = [])
        method filter (line 30) | def filter(params)
        method compiled_filter (line 36) | def compiled_filter
        class CompiledFilter (line 40) | class CompiledFilter # :nodoc:
          method compile (line 41) | def self.compile(replacement, filters)
          method initialize (line 70) | def initialize(replacement, regexps, deep_regexps, blocks)
          method call (line 77) | def call(original_params, parents = [])
      class ParameterFilter (line 24) | class ParameterFilter
        method initialize (line 6) | def initialize(_replacement, filter_parameters)
        method initialize (line 14) | def initialize(_replacement, filter_parameters)
        method initialize (line 25) | def initialize(replacement, filters = [])
        method filter (line 30) | def filter(params)
        method compiled_filter (line 36) | def compiled_filter
        class CompiledFilter (line 40) | class CompiledFilter # :nodoc:
          method compile (line 41) | def self.compile(replacement, filters)
          method initialize (line 70) | def initialize(replacement, regexps, deep_regexps, blocks)
          method call (line 77) | def call(original_params, parents = [])

FILE: lib/grape_logging/version.rb
  type GrapeLogging (line 1) | module GrapeLogging
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (69K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 879,
    "preview": "name: CI\non:\n  push:\n    branches: [master]\n  pull_request:\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n    "
  },
  {
    "path": ".gitignore",
    "chars": 100,
    "preview": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n.rspec\n.idea/"
  },
  {
    "path": ".rubocop.yml",
    "chars": 312,
    "preview": "inherit_from: .rubocop_todo.yml\n\nAllCops:\n  NewCops: enable\n  TargetRubyVersion: 3.0\n\nMetrics/BlockLength:\n  CountAsOne:"
  },
  {
    "path": ".rubocop_todo.yml",
    "chars": 5256,
    "preview": "# This configuration was generated by\n# `rubocop --auto-gen-config`\n# on 2025-07-10 10:25:51 UTC using RuboCop version 1"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 6082,
    "preview": "# Changelog\n\n## [3.0.1] - Unreleased\n\n### Changed or Fixed or Added\n\n### Changed\n- Move dev dependencies to Gemfile\n- Us"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 3088,
    "preview": "# Contributing to grape_logging\n\nThis project is work of many contributors. You're encouraged to submit pull requests, p"
  },
  {
    "path": "Gemfile",
    "chars": 300,
    "preview": "source 'https://rubygems.org'\n\n# Specify your gem's dependencies in grape_logging.gemspec\ngemspec\n\ngem 'rake', '~> 13.3'"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1075,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 aserafin\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "README.md",
    "chars": 6387,
    "preview": "# grape_logging\n\n[![Gem Version](https://badge.fury.io/rb/grape_logging.svg)](https://badge.fury.io/rb/grape_logging)\n[!"
  },
  {
    "path": "RELEASING.md",
    "chars": 2588,
    "preview": "# Releasing grape_logging\n\nThere're no particular rules about when to release grape_logging. Release bug fixes frequentl"
  },
  {
    "path": "Rakefile",
    "chars": 1403,
    "preview": "require 'bundler/gem_tasks'\nrequire 'rspec/core/rake_task'\nrequire 'rubocop/rake_task'\n\nRSpec::Core::RakeTask.new(:spec)"
  },
  {
    "path": "bin/console",
    "chars": 338,
    "preview": "#!/usr/bin/env ruby\n\nrequire 'bundler/setup'\nrequire 'grape_logging'\n\n# You can add fixtures and/or initialization code "
  },
  {
    "path": "bin/setup",
    "chars": 115,
    "preview": "#!/bin/bash\nset -euo pipefail\nIFS=$'\\n\\t'\n\nbundle install\n\n# Do any other automated setup that you need to do here\n"
  },
  {
    "path": "grape_logging.gemspec",
    "chars": 1135,
    "preview": "lib = File.expand_path('lib', __dir__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire 'grape_logging/ve"
  },
  {
    "path": "lib/grape_logging/formatters/default.rb",
    "chars": 873,
    "preview": "module GrapeLogging\n  module Formatters\n    class Default\n      def call(severity, datetime, _, data)\n        \"[#{dateti"
  },
  {
    "path": "lib/grape_logging/formatters/json.rb",
    "chars": 621,
    "preview": "module GrapeLogging\n  module Formatters\n    class Json\n      def call(severity, datetime, _, data)\n        {\n          d"
  },
  {
    "path": "lib/grape_logging/formatters/lograge.rb",
    "chars": 415,
    "preview": "module GrapeLogging\n  module Formatters\n    class Lograge\n      def call(severity, datetime, _, data)\n        time = dat"
  },
  {
    "path": "lib/grape_logging/formatters/logstash.rb",
    "chars": 711,
    "preview": "module GrapeLogging\n  module Formatters\n    class Logstash\n      def call(severity, datetime, _, data)\n        {\n       "
  },
  {
    "path": "lib/grape_logging/formatters/rails.rb",
    "chars": 1901,
    "preview": "module GrapeLogging\n  module Formatters\n    class Rails\n      def call(severity, datetime, _, data)\n        if data.is_a"
  },
  {
    "path": "lib/grape_logging/loggers/base.rb",
    "chars": 133,
    "preview": "module GrapeLogging\n  module Loggers\n    class Base\n      def parameters(_request, _response)\n        {}\n      end\n    e"
  },
  {
    "path": "lib/grape_logging/loggers/client_env.rb",
    "chars": 266,
    "preview": "module GrapeLogging\n  module Loggers\n    class ClientEnv < GrapeLogging::Loggers::Base\n      def parameters(request, _)\n"
  },
  {
    "path": "lib/grape_logging/loggers/filter_parameters.rb",
    "chars": 2006,
    "preview": "module GrapeLogging\n  module Loggers\n    class FilterParameters < GrapeLogging::Loggers::Base\n      AD_PARAMS = 'action_"
  },
  {
    "path": "lib/grape_logging/loggers/request_headers.rb",
    "chars": 436,
    "preview": "module GrapeLogging\n  module Loggers\n    class RequestHeaders < GrapeLogging::Loggers::Base\n      HTTP_PREFIX = 'HTTP_'."
  },
  {
    "path": "lib/grape_logging/loggers/response.rb",
    "chars": 846,
    "preview": "module GrapeLogging\n  module Loggers\n    class Response < GrapeLogging::Loggers::Base\n      def parameters(_, response)\n"
  },
  {
    "path": "lib/grape_logging/middleware/request_logger.rb",
    "chars": 3767,
    "preview": "module GrapeLogging\n  module Middleware\n    class RequestLogger < Grape::Middleware::Base\n      if defined?(ActiveRecord"
  },
  {
    "path": "lib/grape_logging/multi_io.rb",
    "chars": 233,
    "preview": "module GrapeLogging\n  class MultiIO\n    def initialize(*targets)\n      @targets = targets\n    end\n\n    def write(*args)\n"
  },
  {
    "path": "lib/grape_logging/reporters/active_support_reporter.rb",
    "chars": 306,
    "preview": "module GrapeLogging\n  module Reporters\n    class ActiveSupportReporter\n      def initialize(instrumentation_key)\n       "
  },
  {
    "path": "lib/grape_logging/reporters/logger_reporter.rb",
    "chars": 450,
    "preview": "module GrapeLogging\n  module Reporters\n    class LoggerReporter\n      def initialize(logger, formatter, log_level)\n     "
  },
  {
    "path": "lib/grape_logging/timings.rb",
    "chars": 366,
    "preview": "module GrapeLogging\n  module Timings\n    def self.db_runtime=(value)\n      Thread.current[:grape_db_runtime] = value\n   "
  },
  {
    "path": "lib/grape_logging/util/parameter_filter.rb",
    "chars": 3440,
    "preview": "module GrapeLogging\n  module Util\n    if defined?(Rails.application)\n      if Gem::Version.new(Rails.version) < Gem::Ver"
  },
  {
    "path": "lib/grape_logging/version.rb",
    "chars": 44,
    "preview": "module GrapeLogging\n  VERSION = '3.0.1'\nend\n"
  },
  {
    "path": "lib/grape_logging.rb",
    "chars": 209,
    "preview": "require 'grape'\nrequire 'rack/utils'\nrequire 'zeitwerk'\n\n# load zeitwerk\nZeitwerk::Loader.for_gem.tap do |loader|\n  load"
  },
  {
    "path": "spec/lib/grape_logging/formatters/rails_spec.rb",
    "chars": 2446,
    "preview": "require 'spec_helper'\n\ndescribe GrapeLogging::Formatters::Rails do\n  let(:formatter) { described_class.new }\n  let(:seve"
  },
  {
    "path": "spec/lib/grape_logging/loggers/client_env_spec.rb",
    "chars": 1375,
    "preview": "require 'spec_helper'\n\ndescribe GrapeLogging::Loggers::ClientEnv do\n  let(:ip) { '10.0.0.1' }\n  let(:user_agent) { 'user"
  },
  {
    "path": "spec/lib/grape_logging/loggers/filter_parameters_spec.rb",
    "chars": 4848,
    "preview": "require 'spec_helper'\n\ndescribe GrapeLogging::Loggers::FilterParameters do\n  let(:filtered_parameters) { %w[one four] }\n"
  },
  {
    "path": "spec/lib/grape_logging/loggers/request_headers_spec.rb",
    "chars": 1315,
    "preview": "require 'spec_helper'\n\ndescribe GrapeLogging::Loggers::RequestHeaders do\n  let(:mock_request) do\n    instance_double(Rac"
  },
  {
    "path": "spec/lib/grape_logging/loggers/response_spec.rb",
    "chars": 660,
    "preview": "require 'spec_helper'\n\ndescribe GrapeLogging::Loggers::Response do\n  context 'with a parseable JSON body' do\n    let(:re"
  },
  {
    "path": "spec/lib/grape_logging/middleware/request_logger_spec.rb",
    "chars": 3341,
    "preview": "require 'spec_helper'\nrequire 'rack'\n\ndescribe GrapeLogging::Middleware::RequestLogger do\n  let(:env) { { 'action_dispat"
  },
  {
    "path": "spec/spec_helper.rb",
    "chars": 4251,
    "preview": "$:.unshift '.'\n\nrequire 'lib/grape_logging'\n\nRSpec.configure do |config|\n  # rspec-expectations config goes here. You ca"
  }
]

About this extraction

This page contains the full source code of the aserafin/grape_logging GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (62.8 KB), approximately 17.3k tokens, and a symbol index with 110 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!