Repository: rocketjob/rails_semantic_logger
Branch: master
Commit: 7b05bf1b3460
Files: 117
Total size: 154.1 KB
Directory structure:
gitextract_hgorv55i/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rubocop.yml
├── Appraisals
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── TESTING.md
├── gemfiles/
│ ├── rails_6.1.gemfile
│ ├── rails_7.0.gemfile
│ ├── rails_7.0b.gemfile
│ ├── rails_7.1.1.gemfile
│ ├── rails_7.1.gemfile
│ ├── rails_7.1b.gemfile
│ ├── rails_7.2.gemfile
│ ├── rails_8.0.gemfile
│ └── rails_8.1.gemfile
├── lib/
│ ├── rails_semantic_logger/
│ │ ├── action_controller/
│ │ │ └── log_subscriber.rb
│ │ ├── action_mailer/
│ │ │ └── log_subscriber.rb
│ │ ├── action_view/
│ │ │ └── log_subscriber.rb
│ │ ├── active_job/
│ │ │ └── log_subscriber.rb
│ │ ├── active_record/
│ │ │ └── log_subscriber.rb
│ │ ├── delayed_job/
│ │ │ └── plugin.rb
│ │ ├── engine.rb
│ │ ├── extensions/
│ │ │ ├── action_cable/
│ │ │ │ └── tagged_logger_proxy.rb
│ │ │ ├── action_controller/
│ │ │ │ └── live.rb
│ │ │ ├── action_dispatch/
│ │ │ │ └── debug_exceptions.rb
│ │ │ ├── action_view/
│ │ │ │ └── streaming_template_renderer.rb
│ │ │ ├── active_job/
│ │ │ │ └── logging.rb
│ │ │ ├── active_model_serializers/
│ │ │ │ └── logging.rb
│ │ │ ├── active_support/
│ │ │ │ ├── log_subscriber.rb
│ │ │ │ ├── logger.rb
│ │ │ │ └── tagged_logging.rb
│ │ │ ├── mongoid/
│ │ │ │ └── config.rb
│ │ │ ├── rack/
│ │ │ │ └── server.rb
│ │ │ ├── rackup/
│ │ │ │ └── server.rb
│ │ │ ├── rails/
│ │ │ │ └── server.rb
│ │ │ └── sidekiq/
│ │ │ └── sidekiq.rb
│ │ ├── options.rb
│ │ ├── rack/
│ │ │ └── logger.rb
│ │ ├── sidekiq/
│ │ │ ├── defaults.rb
│ │ │ ├── job_logger.rb
│ │ │ └── loggable.rb
│ │ └── version.rb
│ └── rails_semantic_logger.rb
├── rails_semantic_logger.gemspec
└── test/
├── action_controller_test.rb
├── action_mailer_test.rb
├── active_job_test.rb
├── active_record_test.rb
├── controllers/
│ ├── articles_controller_test.rb
│ ├── dashboard_controller_test.rb
│ └── welcome_controller_test.rb
├── dummy/
│ ├── README.rdoc
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ ├── javascripts/
│ │ │ │ ├── application.js
│ │ │ │ └── articles.js
│ │ │ └── stylesheets/
│ │ │ ├── application.css
│ │ │ ├── articles.css
│ │ │ └── welcome.css
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ ├── application_metal_controller.rb
│ │ │ ├── articles_controller.rb
│ │ │ ├── dashboard_controller.rb
│ │ │ └── welcome_controller.rb
│ │ ├── helpers/
│ │ │ ├── application_helper.rb
│ │ │ ├── articles_helper.rb
│ │ │ └── welcome_helper.rb
│ │ ├── jobs/
│ │ │ ├── bad_job.rb
│ │ │ └── simple_job.rb
│ │ ├── mailers/
│ │ │ └── .gitkeep
│ │ ├── models/
│ │ │ ├── .gitkeep
│ │ │ └── sample.rb
│ │ └── views/
│ │ ├── articles/
│ │ │ └── new.html.erb
│ │ ├── layouts/
│ │ │ └── application.html.erb
│ │ └── welcome/
│ │ └── index.html.erb
│ ├── bin/
│ │ ├── bundle
│ │ ├── puma
│ │ ├── rails
│ │ ├── rake
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers/
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── inflections.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── payload_collector.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ ├── sidekiq.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── routes.rb
│ │ └── secrets.yml
│ ├── config.ru
│ ├── db/
│ │ ├── migrate/
│ │ │ └── 20170525020551_create_samples.rb
│ │ └── schema.rb
│ ├── lib/
│ │ └── assets/
│ │ └── .gitkeep
│ ├── log/
│ │ └── .gitkeep
│ ├── public/
│ │ ├── 404.html
│ │ ├── 422.html
│ │ └── 500.html
│ ├── script/
│ │ └── rails
│ └── test/
│ └── fixtures/
│ └── samples.yml
├── payload_collector.rb
├── rails_test.rb
├── sidekiq_test.rb
└── test_helper.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: reidmorrison
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
### Environment
Provide at least:
* Ruby Version.
* Rails Version.
* Semantic Logger Version.
* Rails Semantic Logger Version.
* Other Application/framework names and versions (e.g. Puma, etc.).
* Rails configuration. Only need the settings related to Rails Semantic Logger and Semantic Logger.
* Full Stack Trace, if an exception is being raised.
Note:
* Issues reported here should be related to monkey patches applied to Rails to make it use Semantic Logger.
* For other logging and appender related issues, please report the issue at [Semantic Logger](https://github.com/reidmorrison/semantic_logger/issues.
### Expected Behavior
* Describe your expectation of how Semantic Logger should behave, perhaps by showing how the builtin Rails logger behaves.
* Provide a standalone Ruby script or a link to an example repository that helps reproduce the issue.
### Actual Behavior
* Describe or show the actual behavior.
* Provide text or screen capture showing the behavior.
### Pull Request
* Consider submitting a Pull Request with a fix for the issue.
* This is particularly helpful when running newer Rails versions, since we are not running it yet.
* Or, even a Pull request that only includes a test that reproduces the problem.
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### Issue # (if available)
### Description of changes
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
================================================
FILE: .github/workflows/ci.yml
================================================
name: build
on:
- push
- pull_request
jobs:
test:
name: "Test: Rails ${{ matrix.rails }} on Ruby ${{ matrix.ruby }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- rails: "6.1"
ruby: "3.2"
- rails: "7.0"
ruby: "3.2"
- rails: "7.0b"
ruby: "3.2"
- rails: "7.1"
ruby: "3.2"
- rails: "7.1b"
ruby: "3.2"
- rails: "7.1.1"
ruby: "3.2"
- rails: "7.2"
ruby: "3.3"
- rails: "8.0"
ruby: "3.4"
- rails: "8.1"
ruby: "3.4"
env:
BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile
DISPLAY: ":99.0"
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
# runs 'bundle install' and caches installed gems automatically
bundler-cache: true
- name: Gemfile
run: echo $BUNDLE_GEMFILE
- name: Ruby Version
run: ruby --version
- name: Run Tests
run: bundle exec rake test
================================================
FILE: .gitignore
================================================
.bundle/
*.log
pkg/
test/dummy/tmp/
test/dummy/db/test.sqlite3-*
test/dummy/.sass-cache
*.gem
/.idea
tags
*.DS_Store
Gemfile.lock
/gemfiles/*.lock
*.sqlite3
.rakeTasks
TODO.md
.tool-versions
test/dummy/db/test.sqlite3
mise.toml
================================================
FILE: .rubocop.yml
================================================
AllCops:
Exclude:
- ".git/**/*"
- "docs/**/*"
- "gemfiles/*"
NewCops: enable
TargetRubyVersion: 2.5
#
# RuboCop built-in settings.
# For documentation on all settings see: https://docs.rubocop.org/en/stable
#
# Trailing periods.
Layout/DotPosition:
EnforcedStyle: trailing
# Turn on auto-correction of equals alignment.
Layout/EndAlignment:
AutoCorrect: true
# Prevent accidental windows line endings
Layout/EndOfLine:
EnforcedStyle: lf
# Use a table layout for hashes
Layout/HashAlignment:
EnforcedHashRocketStyle: table
EnforcedColonStyle: table
# Soften limits
Layout/LineLength:
Max: 128
Exclude:
- "**/test/**/*"
# Match existing layout
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: no_space
# TODO: Soften Limits for phase 1 only
Metrics/AbcSize:
Max: 40
# Support long block lengths for tests
Metrics/BlockLength:
Exclude:
- "test/**/*"
- "**/*/cli.rb"
AllowedMethods:
- "aasm"
- "included"
# Soften limits
Metrics/ClassLength:
Max: 250
Exclude:
- "test/**/*"
# TODO: Soften Limits for phase 1 only
Metrics/CyclomaticComplexity:
Max: 15
# Soften limits
Metrics/MethodLength:
Max: 50
# Soften limits
Metrics/ModuleLength:
Max: 250
Metrics/ParameterLists:
CountKeywordArgs: false
# TODO: Soften Limits for phase 1 only
Metrics/PerceivedComplexity:
Max: 21
# Initialization Vector abbreviation
Naming/MethodParameterName:
AllowedNames: [ "iv", "_", "io", "ap", "id", "_id" ]
# Does not allow Symbols to load
Security/YAMLLoad:
AutoCorrect: false
# Needed for testing DateTime
Style/DateTime:
Exclude: [ "test/**/*" ]
# TODO: Soften Limits for phase 1 only
Style/Documentation:
Enabled: false
# One line methods
Style/EmptyMethod:
EnforcedStyle: expanded
# Ruby 3 compatibility feature
Style/FrozenStringLiteralComment:
Enabled: false
Style/NumericPredicate:
AutoCorrect: true
# Incorrectly changes job.fail to job.raise
Style/SignalException:
Enabled: false
# Since English may not be loaded, cannot force its use.
Style/SpecialGlobalVars:
Enabled: false
# Make it easier for developers to move between Elixir and Ruby.
Style/StringLiterals:
EnforcedStyle: double_quotes
================================================
FILE: Appraisals
================================================
appraise "rails_6.1" do
gem "rails", "~> 6.1.0"
gem "sidekiq", "~> 5.2"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.0" do
gem "rails", "~> 7.0.0"
gem "sidekiq", "~> 6.2.0"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.0b" do
gem "rails", "~> 7.0.0"
gem "sidekiq", "~> 6.5"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.1.1" do
gem "rails", "7.1.1"
gem "sidekiq", "~> 7.0.9"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.1" do
gem "rails", "~> 7.1.0"
gem "sidekiq", "~> 7.1.6"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.1b" do
gem "rails", "~> 7.1.0"
gem "sidekiq", "~> 7.3.0"
gem "sqlite3", "~> 1.4"
end
appraise "rails_7.2" do
gem "rails", "~> 7.2.0"
gem "sidekiq", "~> 7.2.4"
end
appraise "rails_8.0" do
gem "rails", "~> 8.0.0"
gem "sidekiq", "~> 7.2.4"
end
appraise "rails_8.1" do
gem "rails", "~> 8.1.1"
gem "sidekiq", "~> 7.2.4"
end
================================================
FILE: Gemfile
================================================
source "https://rubygems.org"
gemspec
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 8.1.0"
gem "sidekiq", "~> 7.2.4"
gem "sqlite3"
gem "rubocop"
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2012, 2013, 2014, 2015 Reid Morrison
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# Rails Semantic Logger
[](https://rubygems.org/gems/rails_semantic_logger) [](https://github.com/reidmorrison/rails_semantic_logger/actions?query=workflow%3Abuild) [](https://rubygems.org/gems/rails_semantic_logger) [](http://opensource.org/licenses/Apache-2.0) 
Rails Semantic Logger replaces the Rails default logger with [Semantic Logger](https://logger.rocketjob.io/)
When any large Rails application is deployed to production one of the first steps is to move to centralized logging, so that logs can be viewed and searched from a central location.
Centralized logging quickly falls apart when trying to consume the current human readable log files:
- Log entries often span multiple lines, resulting in unrelated log lines in the centralized logging system. For example, stack traces.
- Complex Regular Expressions are needed to parse the text lines and make them machine readable. For example to build queries, or alerts that are looking for specific elements in the message.
- Writing searches, alerts, or dashboards based on text logs is incredibly brittle, since a small change to the text logged can often break the parsing of those logs.
- Every log entry often has a completely different format, making it difficult to make consistent searches against the data.
For these and many other reasons switching to structured logging, or logs in JSON format, in testing and production makes centralized logging incredibly powerful.
For example, adding these lines to `config/application.rb` and removing any other log overrides from other environments, will switch automatically to structured logging when running inside Kubernetes:
~~~ruby
# Setup structured logging
config.semantic_logger.application = "my_application"
config.semantic_logger.environment = ENV["STACK_NAME"] || Rails.env
config.log_level = ENV["LOG_LEVEL"] || :info
# Switch to JSON Logging output to stdout when running on Kubernetes
if ENV["LOG_TO_CONSOLE"] || ENV["KUBERNETES_SERVICE_HOST"]
config.rails_semantic_logger.add_file_appender = false
config.semantic_logger.add_appender(io: $stdout, formatter: :json)
end
~~~
Then configure the centralized logging system to tell it that the data is in JSON format, so that it will parse it for you into a hierarchy.
For example, the following will instruct [Observe](https://www.observeinc.com/) to parse the JSON data and create machine readable data from it:
~~~ruby
interface "log", "log":log
make_col event:parse_json(log)
make_col
time:parse_isotime(event.timestamp),
application:string(event.application),
environment:string(event.environment),
duration:duration_ms(event.duration_ms),
level:string(event.level),
name:string(event.name),
message:string(event.message),
named_tags:event.named_tags,
payload:event.payload,
metric:string(event.metric),
metric_amount:float64(event.metric_amount),
tags:array(event.tags),
exception:event.exception,
host:string(event.host),
pid:int64(event.pid),
thread:string(event.thread),
file:string(event.file),
line:int64(event.line),
dimensions:event.dimensions,
backtrace:array(event.backtrace),
level_index:int64(event.level_index)
set_valid_from(time)
drop_col timestamp, log, event, stream
rename_col timestamp:time
~~~
Now queries can be built to drill down into each of these fields, including `payload` which is a nested object.
For example to find all failed Sidekiq job calls where the causing exception class name is `NoMethodError`:
~~~ruby
filter environment = "uat2"
filter level = "error"
filter metric = "sidekiq.job.perform"
filter (string(exception.cause.name) = "NoMethodError")
~~~
Example: create a dashboard showing the duration of all successful Sidekiq jobs:
~~~ruby
filter environment = "production"
filter level = "info"
filter metric = "sidekiq.job.perform"
timechart duration:avg(duration), group_by(name)
~~~
Example: create a dashboard showing the queue latency of all Sidekiq jobs.
The queue latency is the time between when the job was enqueued and when it was started:
~~~ruby
filter environment = "production"
filter level = "info"
filter metric = "sidekiq.queue.latency"
timechart latency:avg(metric_amount/1000), group_by(string(named_tags.queue))
~~~
* http://github.com/reidmorrison/rails_semantic_logger
## Documentation
For complete documentation see: https://logger.rocketjob.io/rails
## Upgrading to Semantic Logger V4.16 - Sidekiq Metrics Support
Rails Semantic Logger now supports Sidekiq metrics.
Below are the metrics that are now available when the JSON logging format is used:
- `sidekiq.job.perform`
- The duration of each Sidekiq job.
- `duration` contains the time in milliseconds that the job took to run.
- `sidekiq.queue.latency`
- The time between when a Sidekiq job was enqueued and when it was started.
- `metric_amount` contains the time in milliseconds that the job was waiting in the queue.
## Upgrading to Semantic Logger v4.15 & V4.16 - Sidekiq Support
Rails Semantic Logger introduces direct support for Sidekiq v4, v5, v6, and v7.
Please remove any previous custom patches or configurations to make Sidekiq work with Semantic Logger.
To see the complete list of patches being made, and to contribute your own changes, see: [Sidekiq Patches](https://github.com/reidmorrison/rails_semantic_logger/blob/master/lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb)
## Upgrading to Semantic Logger v4.4
With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
workaround for Ruby 2.5 crashes is no longer needed.
I.e. Please remove the following line if being called anywhere:
~~~ruby
SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
~~~
## New Versions of Rails, etc.
The primary purpose of the Rails Semantic Logger gem is to patch other gems, primarily Rails, to make them support structured logging though Semantic Logger.
When new versions of Rails and other gems are published they often make changes to the internals, so the existing patches stop working.
Rails Semantic Logger survives only when someone in the community upgrades to a newer Rails or other supported libraries, runs into problems,
and then contributes the fix back to the community by means of a pull request.
Additionally, when new popular gems come out, we rely only the community to supply the necessary patches in Rails Semantic Logger to make those gems support structured logging.
## Supported Platforms
For the complete list of supported Ruby and Rails versions, see the [Testing file](https://github.com/reidmorrison/rails_semantic_logger/blob/master/.github/workflows/ci.yml).
## Author
[Reid Morrison](https://github.com/reidmorrison)
[Contributors](https://github.com/reidmorrison/rails_semantic_logger/graphs/contributors)
## Versioning
This project uses [Semantic Versioning](http://semver.org/).
================================================
FILE: Rakefile
================================================
# Setup bundler to avoid having to run bundle exec all the time.
require "rubygems"
require "bundler/setup"
require "rake/testtask"
require_relative "lib/rails_semantic_logger/version"
task :gem do
system "gem build rails_semantic_logger.gemspec"
end
task publish: :gem do
system "git tag -a v#{RailsSemanticLogger::VERSION} -m 'Tagging #{RailsSemanticLogger::VERSION}'"
system "git push --tags"
system "gem push rails_semantic_logger-#{RailsSemanticLogger::VERSION}.gem"
system "rm rails_semantic_logger-#{RailsSemanticLogger::VERSION}.gem"
end
Rake::TestTask.new(:test) do |t|
t.pattern = "test/**/*_test.rb"
t.verbose = true
t.warning = false
end
# By default run tests against all appraisals
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
require "appraisal"
task default: :appraisal
else
task default: :test
end
================================================
FILE: TESTING.md
================================================
## Installation
Install all needed gems to run the tests:
appraisal install
The gems are installed into the global gem list.
The Gemfiles in the `gemfiles` folder are also re-generated.
## Run Tests
For all supported Rails/ActiveRecord versions:
rake
Or for specific rails version:
appraisal rails_4.2 rake
Or for one particular test file:
appraisal rails_5.0 ruby test/controllers/articles_controller_test.rb
Or down to one test case:
appraisal rails_5.0 ruby test/controllers/articles_controller_test.rb -n "/shows new article/"
================================================
FILE: gemfiles/rails_6.1.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 6.1.0"
gem "sidekiq", "~> 5.2"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.0.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 7.0.0"
gem "sidekiq", "~> 6.2.0"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.0b.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 7.0.0"
gem "sidekiq", "~> 6.5"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.1.1.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "7.1.1"
gem "sidekiq", "~> 7.0.9"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.1.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 7.1.0"
gem "sidekiq", "~> 7.1.6"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.1b.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 7.1.0"
gem "sidekiq", "~> 7.3.0"
gem "sqlite3", "~> 1.4"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_7.2.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 7.2.0"
gem "sidekiq", "~> 7.2.4"
gem "sqlite3"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_8.0.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 8.0.0"
gem "sidekiq", "~> 7.2.4"
gem "sqlite3"
gem "rubocop"
gemspec path: "../"
================================================
FILE: gemfiles/rails_8.1.gemfile
================================================
# This file was generated by Appraisal
source "https://rubygems.org"
gem "appraisal"
gem "puma"
gem "active_model_serializers"
gem "amazing_print"
gem "minitest"
gem "minitest-rails"
gem "rake"
gem "sprockets", "< 4.0"
gem "rails", "~> 8.1.1"
gem "sidekiq", "~> 7.2.4"
gem "sqlite3"
gem "rubocop"
gemspec path: "../"
================================================
FILE: lib/rails_semantic_logger/action_controller/log_subscriber.rb
================================================
module RailsSemanticLogger
module ActionController
class LogSubscriber < ActiveSupport::LogSubscriber
INTERNAL_PARAMS = %w[controller action format _method only_path].freeze
class << self
attr_accessor :action_message_format
end
# Log as debug to hide Processing messages in production
def start_processing(event)
controller_logger(event).debug { action_message("Processing", event.payload) }
end
def process_action(event)
controller_logger(event).info do
payload = event.payload.dup
# Unused, but needed for Devise 401 status code monkey patch to still work.
::ActionController::Base.log_process_action(payload)
params = payload[:params]
if params.is_a?(Hash) || params.is_a?(::ActionController::Parameters)
# According to PR https://github.com/reidmorrison/rails_semantic_logger/pull/37/files
# params is not always a Hash.
payload[:params] = params.to_unsafe_h unless params.is_a?(Hash)
payload[:params] = params.except(*INTERNAL_PARAMS)
if payload[:params].empty?
payload.delete(:params)
elsif params["file"]
# When logging to JSON the entire tempfile is logged, so convert it to a string.
payload[:params]["file"] = params["file"].inspect
end
end
format = payload[:format]
payload[:format] = format.to_s.upcase if format.is_a?(Symbol)
payload[:path] = extract_path(payload[:path]) if payload.key?(:path)
exception = payload.delete(:exception)
if payload[:status].nil? && exception.present?
exception_class_name = exception.first
payload[:status] = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
# Rounds off the runtimes. For example, :view_runtime, :mongo_runtime, etc.
payload.keys.each do |key|
payload[key] = payload[key].to_f.round(2) if key.to_s =~ /(.*)_runtime/
end
# Rails 6+ includes allocation count
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
payload[:status_message] = ::Rack::Utils::HTTP_STATUS_CODES[payload[:status]] if payload[:status].present?
# Causes excessive log output with Rails 5 RC1
payload.delete(:headers)
# Causes recursion in Rails 6.1.rc1
payload.delete(:request)
payload.delete(:response)
{
message: action_message("Completed", event.payload),
duration: event.duration,
payload: payload
}
end
end
def halted_callback(event)
controller_logger(event).info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" }
end
def send_file(event)
controller_logger(event).info(message: "Sent file", payload: {path: event.payload[:path]}, duration: event.duration)
end
def redirect_to(event)
controller_logger(event).info(message: "Redirected to", payload: {location: event.payload[:location]})
end
def send_data(event)
controller_logger(event).info(message: "Sent data",
payload: {file_name: event.payload[:filename]},
duration: event.duration)
end
def unpermitted_parameters(event)
controller_logger(event).debug do
unpermitted_keys = event.payload[:keys]
"Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.join(', ')}"
end
end
%w[write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page].each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
def #{method}(event)
# enable_fragment_cache_logging as of Rails 5
return if ::ActionController::Base.respond_to?(:enable_fragment_cache_logging) && !::ActionController::Base.enable_fragment_cache_logging
controller_logger(event).info do
key_or_path = event.payload[:key] || event.payload[:path]
{message: "#{method.to_s.humanize} \#{key_or_path}", duration: event.duration}
end
end
METHOD
end
private
# Returns the logger for the supplied event.
# Returns ActionController::Base.logger if no controller is present
def controller_logger(event)
controller = event.payload[:controller]
return ::ActionController::Base.logger unless controller
controller.constantize.logger || ::ActionController::Base.logger
rescue NameError
::ActionController::Base.logger
end
def extract_path(path)
index = path.index("?")
index ? path[0, index] : path
end
def action_message(message, payload)
if self.class.action_message_format
self.class.action_message_format.call(message, payload)
else
"#{message} ##{payload[:action]}"
end
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/action_mailer/log_subscriber.rb
================================================
require "active_support/log_subscriber"
require "action_mailer"
module RailsSemanticLogger
module ActionMailer
class LogSubscriber < ::ActiveSupport::LogSubscriber
def deliver(event)
ex = event.payload[:exception_object]
message_id = event.payload[:message_id]
duration = event.duration.round(1)
if ex
log_with_formatter event: event, log_duration: true, level: :error do |_fmt|
{
message: "Error delivering mail #{message_id} (#{duration}ms)",
exception: ex
}
end
else
message =
if event.payload[:perform_deliveries]
"Delivered mail #{message_id} (#{duration}ms)"
else
"Skipped delivery of mail #{message_id} as `perform_deliveries` is false"
end
log_with_formatter event: event, log_duration: true do |_fmt|
{message: message}
end
end
end
# An email was generated.
def process(event)
mailer = event.payload[:mailer]
action = event.payload[:action]
duration = event.duration.round(1)
log_with_formatter event: event do |_fmt|
{message: "#{mailer}##{action}: processed outbound mail in #{duration}ms"}
end
end
private
class EventFormatter
def initialize(event:, log_duration: false)
@event = event
@log_duration = log_duration
end
def mailer
event.payload[:mailer]
end
def payload
{}.tap do |h|
h[:event_name] = event.name
h[:mailer] = mailer
h[:action] = action
h[:message_id] = event.payload[:message_id]
h[:perform_deliveries] = event.payload[:perform_deliveries]
h[:subject] = event.payload[:subject]
h[:to] = event.payload[:to]
h[:from] = event.payload[:from]
h[:bcc] = event.payload[:bcc]
h[:cc] = event.payload[:cc]
h[:date] = date
h[:duration] = event.duration.round(2) if log_duration?
h[:args] = formatted_args
end
end
def date
if event.payload[:date].respond_to?(:to_time)
event.payload[:date].to_time.utc
elsif event.payload[:date].is_a?(String)
Time.parse(date).utc
end
end
private
attr_reader :event
def mailer
event.payload[:mailer]
end
def action
event.payload[:action]
end
def formatted_args
if defined?(mailer.constantize.log_arguments?) && !mailer.constantize.log_arguments?
""
elsif event.payload[:args].present?
JSON.pretty_generate(event.payload[:args].map { |arg| format(arg) })
end
end
def format(arg)
case arg
when Hash
arg.transform_values { |value| format(value) }
when Array
arg.map { |value| format(value) }
when GlobalID::Identification
begin
arg.to_global_id
rescue StandardError
arg
end
else
arg
end
end
def log_duration?
@log_duration
end
end
def log_with_formatter(level: :info, **kw_args)
fmt = EventFormatter.new(**kw_args)
msg = yield fmt
logger.public_send(level, **msg, payload: fmt.payload)
end
def logger
::ActionMailer::Base.logger
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/action_view/log_subscriber.rb
================================================
require "active_support/log_subscriber"
module RailsSemanticLogger
module ActionView
# Output Semantic logs from Action View.
class LogSubscriber < ActiveSupport::LogSubscriber
VIEWS_PATTERN = %r{^app/views/}.freeze
class << self
attr_reader :logger
attr_accessor :rendered_log_level
end
def initialize
@rails_root = nil
super
end
def render_template(event)
return unless should_log?
payload = {
template: from_rails_root(event.payload[:identifier])
}
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
logger.measure(
self.class.rendered_log_level,
"Rendered",
payload: payload,
duration: event.duration
)
end
def render_partial(event)
return unless should_log?
payload = {
partial: from_rails_root(event.payload[:identifier])
}
payload[:within] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
payload[:cache] = event.payload[:cache_hit] unless event.payload[:cache_hit].nil?
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
logger.measure(
self.class.rendered_log_level,
"Rendered",
payload: payload,
duration: event.duration
)
end
def render_collection(event)
return unless should_log?
identifier = event.payload[:identifier] || "templates"
payload = {
template: from_rails_root(identifier),
count: event.payload[:count]
}
payload[:cache_hits] = event.payload[:cache_hits] if event.payload[:cache_hits]
payload[:allocations] = event.allocations if event.respond_to?(:allocations)
logger.measure(
self.class.rendered_log_level,
"Rendered",
payload: payload,
duration: event.duration
)
end
def start(name, id, payload)
if ["render_template.action_view", "render_layout.action_view"].include?(name) && should_log?
qualifier = " layout" if name == "render_layout.action_view"
payload = {template: from_rails_root(payload[:identifier])}
payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
logger.send(self.class.rendered_log_level, message: "Rendering#{qualifier}", payload: payload)
end
super
end
if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
class Start
def start(name, _id, payload)
return unless %w[render_template.action_view render_layout.action_view].include?(name)
qualifier = " layout" if name == "render_layout.action_view"
payload = {template: from_rails_root(payload[:identifier])}
payload[:within] = from_rails_root(payload[:layout]) if payload[:layout]
logger.debug(message: "Rendering#{qualifier}", payload: payload)
end
def finish(name, id, payload)
end
private
def from_rails_root(string)
string = string.sub(rails_root, "")
string.sub!(VIEWS_PATTERN, "")
string
end
def rails_root
@root ||= "#{Rails.root}/"
end
def logger
@logger ||= SemanticLogger["ActionView"]
end
end
def self.attach_to(*)
ActiveSupport::Notifications.unsubscribe("render_template.action_view")
ActiveSupport::Notifications.unsubscribe("render_layout.action_view")
ActiveSupport::Notifications.subscribe("render_template.action_view",
RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
ActiveSupport::Notifications.subscribe("render_layout.action_view",
RailsSemanticLogger::ActionView::LogSubscriber::Start.new)
super
end
end
private
@logger = SemanticLogger["ActionView"]
@rendered_log_level = :debug
EMPTY = "".freeze
def should_log?
logger.send("#{self.class.rendered_log_level}?")
end
def from_rails_root(string)
string = string.sub(rails_root, EMPTY)
string.sub!(VIEWS_PATTERN, EMPTY)
string
end
def rails_root
@rails_root ||= "#{Rails.root}/"
end
def logger
self.class.logger
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/active_job/log_subscriber.rb
================================================
require "active_job"
module RailsSemanticLogger
module ActiveJob
class LogSubscriber < ::ActiveSupport::LogSubscriber
def enqueue(event)
ex = event.payload[:exception_object]
if ex
log_with_formatter level: :error, event: event do |fmt|
{
message: "Failed enqueuing #{fmt.job_info} (#{ex.class} (#{ex.message})",
exception: ex
}
end
elsif event.payload[:aborted]
log_with_formatter level: :info, event: event do |fmt|
{message: "Failed enqueuing #{fmt.job_info}, a before_enqueue callback halted the enqueuing execution."}
end
else
log_with_formatter event: event do |fmt|
{message: "Enqueued #{fmt.job_info}"}
end
end
end
def enqueue_at(event)
ex = event.payload[:exception_object]
if ex
log_with_formatter level: :error, event: event do |fmt|
{
message: "Failed enqueuing #{fmt.job_info} (#{ex.class} (#{ex.message})",
exception: ex
}
end
elsif event.payload[:aborted]
log_with_formatter level: :info, event: event do |fmt|
{message: "Failed enqueuing #{fmt.job_info}, a before_enqueue callback halted the enqueuing execution."}
end
else
log_with_formatter event: event do |fmt|
{message: "Enqueued #{fmt.job_info} at #{fmt.scheduled_at}"}
end
end
end
def perform_start(event)
log_with_formatter event: event do |fmt|
{message: "Performing #{fmt.job_info}"}
end
end
def perform(event)
ex = event.payload[:exception_object]
if ex
log_with_formatter event: event, log_duration: true, level: :error do |fmt|
{
message: "Error performing #{fmt.job_info} in #{event.duration.round(2)}ms",
exception: ex
}
end
else
log_with_formatter event: event, log_duration: true do |fmt|
{message: "Performed #{fmt.job_info} in #{event.duration.round(2)}ms"}
end
end
end
private
class EventFormatter
def initialize(event:, log_duration: false)
@event = event
@log_duration = log_duration
end
def job_info
"#{job.class.name} (Job ID: #{job.job_id}) to #{queue_name}"
end
def payload
{}.tap do |h|
h[:event_name] = event.name
h[:adapter] = adapter_name
h[:queue] = job.queue_name
h[:job_class] = job.class.name
h[:job_id] = job.job_id
h[:provider_job_id] = job.try(:provider_job_id) # Not available in Rails 4.2
h[:duration] = event.duration.round(2) if log_duration?
h[:arguments] = formatted_args
end
end
def queue_name
adapter_name + "(#{job.queue_name})"
end
def scheduled_at
Time.at(event.payload[:job].scheduled_at).utc
end
private
attr_reader :event
def job
event.payload[:job]
end
def adapter_name
event.payload[:adapter].class.name.demodulize.remove("Adapter")
end
def formatted_args
if defined?(job.class.log_arguments?) && !job.class.log_arguments?
""
else
JSON.pretty_generate(job.arguments.map { |arg| format(arg) })
end
end
def format(arg)
case arg
when String
arg.encode("UTF-8", invalid: :replace, undef: :replace)
when Hash
arg.transform_values { |value| format(value) }
when Array
arg.map { |value| format(value) }
when GlobalID::Identification
begin
arg.to_global_id
rescue StandardError
arg
end
else
arg
end
end
def log_duration?
@log_duration
end
end
def log_with_formatter(level: :info, **kw_args)
fmt = EventFormatter.new(**kw_args)
msg = yield fmt
logger.public_send(level, **msg, payload: fmt.payload)
end
def logger
::ActiveJob::Base.logger
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/active_record/log_subscriber.rb
================================================
module RailsSemanticLogger
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
IGNORE_PAYLOAD_NAMES = %w[SCHEMA EXPLAIN].freeze
class << self
attr_reader :logger
end
# Rails 7.1 stopped using runtime in log subscribers
if Rails.version.to_f < 7.1
def self.runtime=(value)
::ActiveRecord::RuntimeRegistry.sql_runtime = value
end
def self.runtime
::ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
end
def self.reset_runtime
rt = runtime
self.runtime = 0
rt
end
end
def sql(event)
self.class.runtime += event.duration if self.class.respond_to?(:runtime)
return unless logger.debug?
payload = event.payload
name = payload[:name]
return if IGNORE_PAYLOAD_NAMES.include?(name)
log_payload = {sql: payload[:sql]}
log_payload[:binds] = bind_values(payload) unless (payload[:binds] || []).empty?
log_payload[:allocations] = event.allocations if event.respond_to?(:allocations)
log_payload[:cached] = event.payload[:cached]
log_payload[:async] = true if event.payload[:async]
log = {
message: name,
payload: log_payload,
duration: event.duration
}
# Log the location of the query itself.
if logger.send(:level_index) >= SemanticLogger.backtrace_level_index
log[:backtrace] = SemanticLogger::Utils.strip_backtrace(caller)
end
logger.debug(log)
end
private
@logger = SemanticLogger["ActiveRecord"]
# When multiple values are received for a single bound field, it is converted into an array
def add_bind_value(binds, key, value)
key = key.downcase.to_sym unless key.nil?
if rails_filter_params_include?(key)
value = "[FILTERED]"
elsif binds.key?(key)
value = (Array(binds[key]) << value)
end
binds[key] = value
end
def rails_filter_params_include?(key)
filter_parameters = Rails.configuration.filter_parameters
return filter_parameters.first.match? key if filter_parameters.first.is_a? Regexp
filter_parameters.include? key
end
def logger
self.class.logger
end
#
# Rails 3,4,5 hell trying to get the bind values
#
def bind_values_v3(payload)
binds = {}
payload[:binds].each do |col, v|
if col
add_bind_value(binds, col.name, v)
else
binds[nil] = v
end
end
binds
end
def bind_values_v4(payload)
binds = {}
payload[:binds].each do |col, v|
attr_name, value = render_bind(col, v)
add_bind_value(binds, attr_name, value)
end
binds
end
def bind_values_v5_0_0(payload)
binds = {}
payload[:binds].each do |attr|
attr_name, value = render_bind(attr)
add_bind_value(binds, attr_name, value)
end
binds
end
def bind_values_v5_0_3(payload)
binds = {}
casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
payload[:binds].zip(casted_params).map do |attr, value|
attr_name, value = render_bind(attr, value)
add_bind_value(binds, attr_name, value)
end
binds
end
def bind_values_v5_1_5(payload)
binds = {}
casted_params = type_casted_binds(payload[:type_casted_binds])
payload[:binds].zip(casted_params).map do |attr, value|
attr_name, value = render_bind(attr, value)
add_bind_value(binds, attr_name, value)
end
binds
end
def bind_values_v6_1(payload)
binds = {}
casted_params = type_casted_binds(payload[:type_casted_binds])
payload[:binds].each_with_index do |attr, i|
attr_name, value = render_bind(attr, casted_params[i])
add_bind_value(binds, attr_name, value)
end
binds
end
def render_bind_v4_2(column, value)
if column
if column.binary?
# This specifically deals with the PG adapter that casts bytea columns into a Hash.
value = value[:value] if value.is_a?(Hash)
value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
end
[column.name, value]
else
[nil, value]
end
end
def render_bind_v5_0_0(attribute)
value =
if attribute.type.binary? && attribute.value
if attribute.value.is_a?(Hash)
"<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
else
"<#{attribute.value.bytesize} bytes of binary data>"
end
else
attribute.value_for_database
end
[attribute.name, value]
end
def render_bind_v5_0_3(attr, value)
if attr.is_a?(Array)
attr = attr.first
elsif attr.type.binary? && attr.value
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
end
[attr&.name, value]
end
def render_bind_v6_1(attr, value)
case attr
when ActiveModel::Attribute
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" if attr.type.binary? && attr.value
when Array
attr = attr.first
else
attr = nil
end
[attr&.name || :nil, value]
end
def type_casted_binds_v5_0_3(binds, casted_binds)
casted_binds || ::ActiveRecord::Base.connection.type_casted_binds(binds)
end
def type_casted_binds_v5_1_5(casted_binds)
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
end
if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 2 # 5.0.0 - 5.0.2
alias bind_values bind_values_v5_0_0
alias render_bind render_bind_v5_0_0
elsif Rails::VERSION::MAJOR == 5 &&
((Rails::VERSION::MINOR.zero? && Rails::VERSION::TINY <= 6) ||
(Rails::VERSION::MINOR == 1 && Rails::VERSION::TINY <= 4)) # 5.0.3 - 5.0.6 && 5.1.0 - 5.1.4
alias bind_values bind_values_v5_0_3
alias render_bind render_bind_v5_0_3
alias type_casted_binds type_casted_binds_v5_0_3
elsif (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR > 0) ||
Rails::VERSION::MAJOR >= 7 # ~> 6.1.0 && >= 7.x.x
alias bind_values bind_values_v6_1
alias render_bind render_bind_v6_1
alias type_casted_binds type_casted_binds_v5_1_5
elsif Rails::VERSION::MAJOR >= 5 # ~> 5.1.5 && ~> 5.0.7 && 6.x.x
alias bind_values bind_values_v5_1_5
alias render_bind render_bind_v5_0_3
alias type_casted_binds type_casted_binds_v5_1_5
elsif Rails.version.to_i >= 4 # 4.x
alias bind_values bind_values_v4
alias render_bind render_bind_v4_2
else # 3.x
alias bind_values bind_values_v3
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/delayed_job/plugin.rb
================================================
module RailsSemanticLogger
module DelayedJob
class Plugin < Delayed::Plugin
callbacks do |lifecycle|
lifecycle.before(:execute) do |_job|
::SemanticLogger.reopen
end
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/engine.rb
================================================
require "rails"
require "rails_semantic_logger/options"
module RailsSemanticLogger
class Engine < ::Rails::Engine
# Make the SemanticLogger config available in the Rails application config
#
# Example: Add the MongoDB logging appender in the Rails environment
# initializer in file config/environments/development.rb
#
# Rails::Application.configure do
# # Add the MongoDB logger appender only once Rails is initialized
# config.after_initialize do
# appender = SemanticLogger::Appender::Mongo.new(
# uri: 'mongodb://127.0.0.1:27017/test'
# )
# config.semantic_logger.add_appender(appender: appender)
# end
# end
config.semantic_logger = ::SemanticLogger
config.rails_semantic_logger = RailsSemanticLogger::Options.new
# Initialize SemanticLogger. In a Rails environment it will automatically
# insert itself above the configured rails logger to add support for its
# additional features
# Replace Rails logger initializer
Rails::Application::Bootstrap.initializers.delete_if { |i| i.name == :initialize_logger }
initializer :initialize_logger, group: :all do
config = Rails.application.config
# Set the default log level based on the Rails config
SemanticLogger.default_level = config.log_level
if defined?(Rails::Rack::Logger) && config.rails_semantic_logger.semantic
config.middleware.swap(Rails::Rack::Logger, RailsSemanticLogger::Rack::Logger, config.log_tags)
end
# Existing loggers are ignored because servers like trinidad supply their
# own file loggers which would result in duplicate logging to the same log file
Rails.logger = config.logger =
begin
if config.rails_semantic_logger.add_file_appender
path = config.paths["log"].first
FileUtils.mkdir_p(File.dirname(path)) unless File.exist?(File.dirname(path))
# Add the log file to the list of appenders
# Use the colorized formatter if Rails colorized logs are enabled
ap_options = config.rails_semantic_logger.ap_options
formatter = config.rails_semantic_logger.format
formatter = {color: {ap: ap_options}} if (formatter == :default) && (config.colorize_logging != false)
# Set internal logger to log to file only, in case another appender experiences errors during writes
appender = SemanticLogger::Appender::File.new(path, formatter: formatter)
appender.name = "SemanticLogger"
SemanticLogger::Processor.logger = appender
# Check for previous file or stdout loggers
SemanticLogger.appenders.each do |app|
next unless app.is_a?(SemanticLogger::Appender::File) || app.is_a?(SemanticLogger::Appender::IO)
app.formatter = formatter
end
SemanticLogger.add_appender(file_name: path, formatter: formatter, filter: config.rails_semantic_logger.filter)
end
SemanticLogger[Rails]
rescue StandardError => e
# If not able to log to file, log to standard error with warning level only
SemanticLogger.default_level = :warn
SemanticLogger::Processor.logger = SemanticLogger::Appender::IO.new($stderr)
SemanticLogger.add_appender(io: $stderr)
logger = SemanticLogger[Rails]
logger.warn(
"Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " \
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed.",
e
)
logger
end
# Replace Rails loggers
%i[active_record action_controller action_mailer action_view].each do |name|
ActiveSupport.on_load(name) { include SemanticLogger::Loggable }
end
ActiveSupport.on_load(:action_cable) { self.logger = SemanticLogger["ActionCable"] }
end
# Before any initializers run, but after the gems have been loaded
config.before_initialize do
if config.respond_to?(:assets) && defined?(Rails::Rack::Logger) && config.rails_semantic_logger.semantic
config.rails_semantic_logger.quiet_assets = true if config.assets.quiet
# Otherwise Sprockets can't find the Rails::Rack::Logger middleware
config.assets.quiet = false
end
# Replace the Mongo Loggers
Mongoid.logger = SemanticLogger[Mongoid] if defined?(Mongoid)
Moped.logger = SemanticLogger[Moped] if defined?(Moped)
Mongo::Logger.logger = SemanticLogger[Mongo] if defined?(Mongo::Logger)
# Replace the Resque Logger
Resque.logger = SemanticLogger[Resque] if defined?(Resque) && Resque.respond_to?(:logger=)
# Replace the Sidekiq logger
if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
::Sidekiq.configure_client do |config|
config.logger = ::SemanticLogger[::Sidekiq]
end
::Sidekiq.configure_server do |config|
config.logger = ::SemanticLogger[::Sidekiq]
if config.respond_to?(:options)
config.options[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
else
config[:job_logger] = RailsSemanticLogger::Sidekiq::JobLogger
end
# Add back the default console logger unless already added
SemanticLogger.add_appender(io: $stdout, formatter: :color) unless SemanticLogger.appenders.console_output?
# Replace default error handler when present
existing = RailsSemanticLogger::Sidekiq::Defaults.delete_default_error_handler(config.error_handlers)
config.error_handlers << RailsSemanticLogger::Sidekiq::Defaults::ERROR_HANDLER if existing
end
if defined?(::Sidekiq::Job) && (::Sidekiq::VERSION.to_i != 5)
::Sidekiq::Job.singleton_class.prepend(RailsSemanticLogger::Sidekiq::Loggable)
else
::Sidekiq::Worker.singleton_class.prepend(RailsSemanticLogger::Sidekiq::Loggable)
end
end
# Replace the Sidetiq logger
Sidetiq.logger = SemanticLogger[Sidetiq] if defined?(Sidetiq) && Sidetiq.respond_to?(:logger=)
# Replace the DelayedJob logger
if defined?(Delayed::Worker)
Delayed::Worker.logger = SemanticLogger[Delayed::Worker]
Delayed::Worker.plugins << RailsSemanticLogger::DelayedJob::Plugin
end
# Replace the Bugsnag logger
Bugsnag.configure(false) { |config| config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
# Set the IOStreams PGP logger
IOStreams::Pgp.logger = SemanticLogger["IOStreams::Pgp"] if defined?(IOStreams)
end
# After any initializers run, but after the gems have been loaded
config.after_initialize do
config = Rails.application.config
# Replace the Bugsnag logger
Bugsnag.configure(false) { |bugsnag_config| bugsnag_config.logger = SemanticLogger[Bugsnag] } if defined?(Bugsnag)
# Rails Patches
require("rails_semantic_logger/extensions/action_cable/tagged_logger_proxy") if defined?(::ActionCable)
require("rails_semantic_logger/extensions/action_controller/live") if defined?(::ActionController::Live)
if defined?(::ActionDispatch::DebugExceptions)
require("rails_semantic_logger/extensions/action_dispatch/debug_exceptions")
end
if defined?(::ActionView::StreamingTemplateRenderer::Body)
require("rails_semantic_logger/extensions/action_view/streaming_template_renderer")
end
require("rails_semantic_logger/extensions/active_job/logging") if defined?(::ActiveJob)
require("rails_semantic_logger/extensions/active_model_serializers/logging") if defined?(::ActiveModelSerializers)
if config.rails_semantic_logger.semantic
# Active Job
if defined?(::ActiveJob::Logging::LogSubscriber)
RailsSemanticLogger.swap_subscriber(
::ActiveJob::Logging::LogSubscriber,
RailsSemanticLogger::ActiveJob::LogSubscriber,
:active_job
)
end
if defined?(::ActiveJob::LogSubscriber)
RailsSemanticLogger.swap_subscriber(
::ActiveJob::LogSubscriber,
RailsSemanticLogger::ActiveJob::LogSubscriber,
:active_job
)
end
# Active Record
if defined?(::ActiveRecord)
require "active_record/log_subscriber"
RailsSemanticLogger.swap_subscriber(
::ActiveRecord::LogSubscriber,
RailsSemanticLogger::ActiveRecord::LogSubscriber,
:active_record
)
end
# Rack
RailsSemanticLogger::Rack::Logger.started_request_log_level = :info if config.rails_semantic_logger.started
# Silence asset logging by applying a filter to the Rails logger itself, not any of the appenders.
if config.rails_semantic_logger.quiet_assets && config.respond_to?(:assets) && config.assets.prefix
assets_root = config.relative_url_root.to_s + config.assets.prefix
assets_regex = %r(\A/{0,2}#{assets_root})
RailsSemanticLogger::Rack::Logger.logger.filter = ->(log) { log.payload[:path] !~ assets_regex if log.payload }
end
# Action View
if defined?(::ActionView)
require "action_view/log_subscriber"
RailsSemanticLogger::ActionView::LogSubscriber.rendered_log_level = :info if config.rails_semantic_logger.rendered
RailsSemanticLogger.swap_subscriber(
::ActionView::LogSubscriber,
RailsSemanticLogger::ActionView::LogSubscriber,
:action_view
)
end
# Action Controller
if defined?(::ActionController)
require "action_controller/log_subscriber"
RailsSemanticLogger::ActionController::LogSubscriber.action_message_format = config.rails_semantic_logger.action_message_format
RailsSemanticLogger.swap_subscriber(
::ActionController::LogSubscriber,
RailsSemanticLogger::ActionController::LogSubscriber,
:action_controller
)
end
# Action Mailer
if defined?(::ActionMailer)
require "action_mailer/log_subscriber"
RailsSemanticLogger.swap_subscriber(
::ActionMailer::LogSubscriber,
RailsSemanticLogger::ActionMailer::LogSubscriber,
:action_mailer
)
end
if config.rails_semantic_logger.replace_sidekiq_logger && defined?(::Sidekiq)
require("rails_semantic_logger/extensions/sidekiq/sidekiq")
end
end
#
# Forking Frameworks
#
# Passenger provides the :starting_worker_process event for executing
# code after it has forked, so we use that and reconnect immediately.
if defined?(PhusionPassenger)
PhusionPassenger.on_event(:starting_worker_process) do |forked|
SemanticLogger.reopen if forked
end
end
# Re-open appenders after Resque has forked a worker
Resque.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Resque.after_fork)
# Re-open appenders after Spring has forked a process
Spring.after_fork { |_job| ::SemanticLogger.reopen } if defined?(Spring.after_fork)
# Re-open appenders after SolidQueue worker/dispatcher/scheduler has finished booting
SolidQueue.on_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_start)
SolidQueue.on_worker_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_worker_start)
SolidQueue.on_dispatcher_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_dispatcher_start)
SolidQueue.on_scheduler_start { ::SemanticLogger.reopen } if defined?(SolidQueue.on_scheduler_start)
console do |_app|
# Don't use a background thread for logging
SemanticLogger.sync!
# Add a stderr logger when running inside a Rails console unless one has already been added.
if config.rails_semantic_logger.console_logger && !SemanticLogger.appenders.console_output?
SemanticLogger.add_appender(io: STDERR, formatter: :color)
end
# Include method names on log entries in the console
SemanticLogger.backtrace_level = SemanticLogger.default_level
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb
================================================
require "action_cable/connection/tagged_logger_proxy"
module ActionCable
module Connection
class TaggedLoggerProxy
def tag(logger, &block)
current_tags = tags - logger.tags
logger.tagged(*current_tags, &block)
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/action_controller/live.rb
================================================
# Log actual exceptions, not a string representation
require "action_controller"
module ActionController
module Live
undef_method :log_error
def log_error(exception)
logger.fatal(exception)
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb
================================================
# Log actual exceptions, not a string representation
require "action_dispatch"
module ActionDispatch
class DebugExceptions
private
undef_method :log_error
if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
def log_error(request, wrapper)
Rails.application.deprecators.silence do
return if !log_rescued_responses?(request) && wrapper.rescue_response?
level = request.get_header("action_dispatch.debug_exception_log_level")
ActionController::Base.logger.log(level, wrapper.exception)
end
end
else
def log_error(_request, wrapper)
ActiveSupport::Deprecation.silence do
level = wrapper.respond_to?(:rescue_response?) && wrapper.rescue_response? ? :debug : :fatal
ActionController::Base.logger.log(level, wrapper.exception)
end
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/action_view/streaming_template_renderer.rb
================================================
# Log actual exceptions, not a string representation
require "action_view/renderer/streaming_template_renderer"
module ActionView
class StreamingTemplateRenderer
class Body
private
undef_method :log_error
def log_error(exception)
ActionView::Base.logger.fatal(exception)
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/active_job/logging.rb
================================================
# Patch ActiveJob logger
require "active_job/logging"
module ActiveJob
module Logging
include SemanticLogger::Loggable
private
undef_method :tag_logger
def tag_logger(*tags, &block)
if logger.respond_to?(:tagged)
logger.tagged(*tags, &block)
else
yield
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb
================================================
# Patch ActiveModelSerializers logger
require "active_model_serializers/logging"
module ActiveModelSerializers
module Logging
include SemanticLogger::Loggable
private
def tag_logger(*tags, &block)
logger.tagged(*tags, &block)
end
end
class SerializableResource
include SemanticLogger::Loggable
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb
================================================
if ActiveSupport::VERSION::STRING == "7.1.1"
require "active_support/log_subscriber"
module ActiveSupport
class LogSubscriber
# @override Rails 7.1
def silenced?(event)
native_log_level = @event_levels.fetch(event, ::Logger::Severity::FATAL)
logger.nil? || SemanticLogger::Levels.index(logger.level) > SemanticLogger::Levels.index(native_log_level)
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/active_support/logger.rb
================================================
require "active_support/logger"
module ActiveSupport
# More hacks to try and stop Rails from being it's own worst enemy.
class Logger
class << self
undef :logger_outputs_to?
# Prevent broadcasting since SemanticLogger already supports multiple loggers
if method_defined?(:broadcast)
undef :broadcast
def broadcast(_logger)
Module.new
end
end
end
# Prevent Console from trying to merge loggers
def self.logger_outputs_to?(*_args)
true
end
def self.new(*_args, **_kwargs)
SemanticLogger[self]
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/active_support/tagged_logging.rb
================================================
module ActiveSupport
module TaggedLogging
# Semantic Logger already does tagged logging
def self.new(logger)
logger
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/mongoid/config.rb
================================================
require "mongoid/config"
module Mongoid
module Config
private
# Remove log overrides
def set_log_levels
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/rack/server.rb
================================================
module RailsSemanticLogger
module Rack
module Server
def daemonize_app
super
SemanticLogger.reopen
end
end
end
end
Rack::Server.prepend(RailsSemanticLogger::Rack::Server)
================================================
FILE: lib/rails_semantic_logger/extensions/rackup/server.rb
================================================
module RailsSemanticLogger
module Rackup
module Server
def daemonize_app
super
SemanticLogger.reopen
end
end
end
end
Rackup::Server.prepend(RailsSemanticLogger::Rackup::Server)
================================================
FILE: lib/rails_semantic_logger/extensions/rails/server.rb
================================================
# Patch the Rails::Server log_to_stdout so that it logs via SemanticLogger
require "rails"
module Rails
class Server
private
undef_method :log_to_stdout if method_defined?(:log_to_stdout)
def log_to_stdout
wrapped_app # touch the app so the logger is set up
SemanticLogger.add_appender(io: $stdout, formatter: :color) unless SemanticLogger.appenders.console_output?
end
end
end
================================================
FILE: lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb
================================================
# Sidekiq patches
if Sidekiq::VERSION.to_i == 4
require "sidekiq/logging"
require "sidekiq/middleware/server/logging"
require "sidekiq/processor"
elsif Sidekiq::VERSION.to_i == 5
require "sidekiq/logging"
end
module Sidekiq
# Sidekiq v4 & v5
if defined?(::Sidekiq::Logging)
# Replace Sidekiq logging context
module Logging
def self.with_context(msg, &block)
SemanticLogger.tagged(msg, &block)
end
def self.job_hash_context(job_hash)
h = {jid: job_hash["jid"]}
h[:bid] = job_hash["bid"] if job_hash["bid"]
h[:queue] = job_hash["queue"] if job_hash["queue"]
h
end
end
end
# Sidekiq v4
if defined?(::Sidekiq::Middleware::Server::Logging)
# Convert string to machine readable format
class Processor
def log_context(job_hash)
h = {jid: job_hash["jid"]}
h[:bid] = job_hash["bid"] if job_hash["bid"]
h[:queue] = job_hash["queue"] if job_hash["queue"]
h
end
end
# Let Semantic Logger handle duration logging
module Middleware
module Server
class Logging
# rubocop:disable Style/ExplicitBlockArgument
def call(worker, item, queue)
SemanticLogger.tagged(queue: queue) do
if perform_messages_enabled?
worker.logger.info(
"Start #perform",
metric: "sidekiq.queue.latency",
metric_amount: job_latency_ms(item)
)
worker.logger.measure_info(
"Completed #perform",
on_exception_level: :error,
log_exception: :full,
metric: "sidekiq.job.perform"
) { yield }
else
yield
end
end
end
def perform_messages_enabled?
RailsSemanticLogger::Sidekiq::JobLogger.perform_messages != false
end
def job_latency_ms(job)
return unless job && job["enqueued_at"]
enqueued_at = job["enqueued_at"]
if enqueued_at.is_a?(Float)
# Sidekiq <= 7: seconds since epoch
(Time.now.to_f - enqueued_at) * 1000
else
# Sidekiq 8+: milliseconds since epoch
now = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
now - enqueued_at
end
end
end
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/options.rb
================================================
module RailsSemanticLogger
# Options for controlling Rails Semantic Logger behavior
#
# * Convert Action Controller and Active Record text messages to semantic data
#
# Rails -- Started -- { :ip => "127.0.0.1", :method => "GET", :path => "/dashboards/inquiry_recent_activity" }
# UserController -- Completed #index -- { :action => "index", :db_runtime => 54.64, :format => "HTML", :method => "GET", :mongo_runtime => 0.0, :path => "/users", :status => 200, :status_message => "OK", :view_runtime => 709.88 }
#
# config.rails_semantic_logger.semantic = true
#
# * Change Rack started message to debug so that it does not appear in production
#
# config.rails_semantic_logger.started = false
#
# * Change Processing message to debug so that it does not appear in production
#
# config.rails_semantic_logger.processing = false
#
# * Change Action View render log messages to debug so that they do not appear in production
#
# ActionView::Base -- Rendered data/search/_user.html.haml (46.7ms)
#
# config.rails_semantic_logger.rendered = false
#
# * Override the Amazing Print options for logging Hash data as text:
#
# Any valid Amazing Print option for rendering data.
# The defaults can changed be creating a `~/.aprc` file.
# See: https://github.com/amazing-print/amazing_print
#
# Note: The option :multiline is set to false if not supplied.
# Note: Has no effect if Amazing Print is not installed.
#
# config.rails_semantic_logger.ap_options = {multiline: false}
#
# * Whether to automatically add an environment specific log file appender.
# For Example: 'log/development.log'
#
# Note:
# When Semantic Logger fails to log to an appender it logs the error to an
# internal logger, which by default writes to STDERR.
# Example, change the default internal logger to log to stdout:
# SemanticLogger::Processor.logger = SemanticLogger::Appender::IO.new($stdout, level: :warn)
#
# config.rails_semantic_logger.add_file_appender = true
#
# * Silence asset logging
#
# config.rails_semantic_logger.quiet_assets = false
#
# * Disable automatic logging to stderr when running a Rails console.
#
# config.rails_semantic_logger.console_logger = false
#
# * Override the output format for the primary Rails log file.
#
# Valid options:
# * :default
# Plain text output with no color.
# * :color
# Plain text output with color.
# * :json
# JSON output format.
# * class
#
# * Proc
# A block that will be called to format the output.
# It is supplied with the `log` entry and should return the formatted data.
#
# Note:
# * `:default` is automatically changed to `:color` if `config.colorize_logging` is `true`.
#
# JSON Example, in `application.rb`:
# config.rails_semantic_logger.format = :json
#
# Custom Example, create `app/lib/my_formatter.rb`:
#
# # My Custom colorized formatter
# class MyFormatter < SemanticLogger::Formatters::Color
# # Return the complete log level name in uppercase
# def level
# "#{color}log.level.upcase#{color_map.clear}"
# end
# end
#
# # In application.rb:
# config.rails_semantic_logger.format = MyFormatter.new
#
#
# config.rails_semantic_logger.format = :default
#
# * Add a filter to the file logger [Regexp|Proc]
# RegExp: Only include log messages where the class name matches the supplied
# regular expression. All other messages will be ignored.
# Proc: Only include log messages where the supplied Proc returns true.
# The Proc must return true or false.
#
# config.rails_semantic_logger.filter = nil
#
# * named_tags: *DEPRECATED*
# Instead, supply a Hash to config.log_tags
# config.rails_semantic_logger.named_tags = nil
#
# * Change the message format of Action Controller action.
# A block that will be called to format the message.
# It is supplied with the `message` and `payload` and should return the formatted data.
#
# config.rails_semantic_logger.action_message_format = -> (message, payload) do
# "#{message} - #{payload[:controller]}##{payload[:action]}"
# end
#
# * Do not replace the Sidekiq logger with a Semantic Logger logger.
#
# config.rails_semantic_logger.replace_sidekiq_logger = false
class Options
attr_accessor :semantic, :started, :processing, :rendered, :ap_options, :add_file_appender,
:quiet_assets, :format, :named_tags, :filter, :console_logger, :action_message_format,
:replace_sidekiq_logger
# Setup default values
def initialize
@semantic = true
@started = false
@processing = false
@rendered = false
@ap_options = {multiline: false}
@add_file_appender = true
@quiet_assets = false
@format = :default
@named_tags = nil
@filter = nil
@console_logger = true
@action_message_format = nil
@replace_sidekiq_logger = true
end
end
end
================================================
FILE: lib/rails_semantic_logger/rack/logger.rb
================================================
require "active_support/core_ext/time/conversions"
require "active_support/core_ext/object/blank"
require "active_support/log_subscriber"
require "action_dispatch/http/request"
require "rack/body_proxy"
module RailsSemanticLogger
module Rack
class Logger < ActiveSupport::LogSubscriber
class << self
attr_reader :logger
attr_accessor :started_request_log_level
end
def initialize(app, taggers = nil)
@app = app
@taggers = taggers || []
end
def call(env)
request = ActionDispatch::Request.new(env)
# Check for named tags (Hash)
if @taggers && !@taggers.empty?
tags = @taggers.is_a?(Hash) ? compute_named_tags(request) : compute_tags(request)
logger.tagged(tags) { call_app(request, env) }
else
call_app(request, env)
end
end
private
@logger = SemanticLogger["Rack"]
@started_request_log_level = :debug
def call_app(request, env)
instrumenter = ActiveSupport::Notifications.instrumenter
if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
handle = instrumenter.build_handle "request.action_dispatch", request: request
instrumenter_finish = lambda {
handle.finish
}
handle.start
else
instrumenter_state = instrumenter.start "request.action_dispatch", request: request
instrumenter_finish = lambda {
instrumenter.finish_with_state(instrumenter_state, "request.action_dispatch", request: request)
}
end
logger.send(self.class.started_request_log_level) { started_request_message(request) }
status, headers, body = @app.call(env)
body = ::Rack::BodyProxy.new(body, &instrumenter_finish)
[status, headers, body]
rescue Exception
instrumenter_finish.call
raise
end
def started_request_message(request)
{
message: "Started",
payload: {
method: request.request_method,
path: request.filtered_path,
ip: request.remote_ip
}
}
end
def compute_tags(request)
@taggers.collect do |tag|
case tag
when Proc
tag.call(request)
when Symbol
request.send(tag)
else
tag
end
end
end
# Leave out any named tags with a nil value
def compute_named_tags(request)
tagged = {}
@taggers.each_pair do |tag, value|
resolved =
case value
when Proc
value.call(request)
when Symbol
request.send(value)
else
value
end
tagged[tag] = resolved unless resolved.nil?
end
tagged
end
def logger
self.class.logger
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/sidekiq/defaults.rb
================================================
module RailsSemanticLogger
module Sidekiq
module Defaults
# Prevent exception logging during standard error handling since the Job Logger below already logs the exception.
ERROR_HANDLER =
if ::Sidekiq::VERSION.to_f < 7.1 ||
(::Sidekiq::VERSION.to_f == 7.1 && ::Sidekiq::VERSION.split(".").last.to_i < 6)
lambda do |_ex, ctx|
unless ctx.empty?
job_hash = ctx[:job] || {}
klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
end
end
else
lambda do |_ex, ctx, _default_configuration|
unless ctx.empty?
job_hash = ctx[:job] || {}
klass = job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"]
logger = klass ? SemanticLogger[klass] : ::Sidekiq.logger
ctx[:context] ? logger.warn(ctx[:context], ctx) : logger.warn(ctx)
end
end
end
# Returns the default logger after removing from the supplied list.
# Returns [nil] when the default logger was not present.
def self.delete_default_error_handler(error_handlers)
return error_handlers.delete(::Sidekiq::Config::ERROR_HANDLER) if defined?(::Sidekiq::Config::ERROR_HANDLER)
return error_handlers.delete(::Sidekiq::DEFAULT_ERROR_HANDLER) if defined?(::Sidekiq::DEFAULT_ERROR_HANDLER)
return unless defined?(::Sidekiq::ExceptionHandler)
existing = error_handlers.find { |handler| handler.is_a?(::Sidekiq::ExceptionHandler::Logger) }
error_handlers.delete(existing) if existing
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/sidekiq/job_logger.rb
================================================
module RailsSemanticLogger
module Sidekiq
class JobLogger
class << self
attr_writer :perform_messages
def perform_messages
instance_variable_defined?(:@perform_messages) ? @perform_messages : true
end
end
# Sidekiq 6.5 does not take any arguments, whereas v7 is given a logger
def initialize(*_args)
end
def call(item, queue, &block)
klass = item["wrapped"] || item["class"]
logger = klass ? SemanticLogger[klass] : Sidekiq.logger
SemanticLogger.tagged(queue: queue) do
if perform_messages_enabled?
# Latency is the time between when the job was enqueued and when it started executing.
logger.info(
"Start #perform",
metric: "sidekiq.queue.latency",
metric_amount: job_latency_ms(item)
)
end
# Measure the duration of running the job
if perform_messages_enabled?
logger.measure_info(
"Completed #perform",
on_exception_level: :error,
log_exception: :full,
metric: "sidekiq.job.perform",
&block
)
else
yield if block_given?
end
end
end
def prepare(job_hash, &block)
level = job_hash["log_level"]
if level
SemanticLogger.silence(level) do
SemanticLogger.tagged(job_hash_context(job_hash), &block)
end
else
SemanticLogger.tagged(job_hash_context(job_hash), &block)
end
end
private
def perform_messages_enabled?
self.class.perform_messages != false
end
def job_hash_context(job_hash)
h = {jid: job_hash["jid"]}
h[:bid] = job_hash["bid"] if job_hash["bid"]
h[:tags] = job_hash["tags"] if job_hash["tags"]
h[:queue] = job_hash["queue"] if job_hash["queue"]
h
end
def job_latency_ms(job)
return unless job && job["enqueued_at"]
(Time.now.to_f - job["enqueued_at"].to_f) * 1000
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/sidekiq/loggable.rb
================================================
module RailsSemanticLogger
module Sidekiq
module Loggable
def included(base)
super
base.include(SemanticLogger::Loggable)
end
end
end
end
================================================
FILE: lib/rails_semantic_logger/version.rb
================================================
module RailsSemanticLogger
VERSION = "4.20.0".freeze
end
================================================
FILE: lib/rails_semantic_logger.rb
================================================
require "semantic_logger"
require "rails_semantic_logger/extensions/rails/server" if defined?(Rails::Server)
require "rails_semantic_logger/engine"
module RailsSemanticLogger
module ActionController
autoload :LogSubscriber, "rails_semantic_logger/action_controller/log_subscriber"
end
module ActionMailer
autoload :LogSubscriber, "rails_semantic_logger/action_mailer/log_subscriber"
end
module ActionView
autoload :LogSubscriber, "rails_semantic_logger/action_view/log_subscriber"
end
module ActiveJob
autoload :LogSubscriber, "rails_semantic_logger/active_job/log_subscriber"
end
module ActiveRecord
autoload :LogSubscriber, "rails_semantic_logger/active_record/log_subscriber"
end
module Rack
autoload :Logger, "rails_semantic_logger/rack/logger"
end
module DelayedJob
autoload :Plugin, "rails_semantic_logger/delayed_job/plugin"
end
module Sidekiq
autoload :Defaults, "rails_semantic_logger/sidekiq/defaults"
autoload :JobLogger, "rails_semantic_logger/sidekiq/job_logger"
autoload :Loggable, "rails_semantic_logger/sidekiq/loggable"
end
autoload :Options, "rails_semantic_logger/options"
# Swap an existing subscriber with a new one
def self.swap_subscriber(old_class, new_class, notifier)
subscribers = ActiveSupport::LogSubscriber.subscribers.select { |s| s.is_a?(old_class) }
subscribers.each { |subscriber| unattach(subscriber) }
new_class.attach_to(notifier)
end
def self.unattach(subscriber)
subscriber_patterns(subscriber).each do |pattern|
listeners_for(ActiveSupport::Notifications.notifier, pattern).each do |sub|
next unless sub.instance_variable_get(:@delegate) == subscriber
ActiveSupport::Notifications.unsubscribe(sub)
end
end
ActiveSupport::LogSubscriber.subscribers.delete(subscriber)
end
def self.subscriber_patterns(subscriber)
if subscriber.patterns.respond_to?(:keys)
subscriber.patterns.keys
else
subscriber.patterns
end
end
def self.listeners_for(notifier, pattern)
if notifier.respond_to?(:all_listeners_for) # Rails >= 7.1
notifier.all_listeners_for(pattern)
else
notifier.listeners_for(pattern)
end
end
private_class_method :listeners_for, :subscriber_patterns, :unattach
end
require("rails_semantic_logger/extensions/mongoid/config") if defined?(Mongoid)
require("rails_semantic_logger/extensions/active_support/logger") if defined?(ActiveSupport::Logger)
require("rails_semantic_logger/extensions/active_support/log_subscriber") if defined?(ActiveSupport::LogSubscriber)
begin
require "rackup"
rescue LoadError
# No need to do anything, will fall back to Rack
end
if defined?(Rackup::Server)
require("rails_semantic_logger/extensions/rackup/server")
elsif defined?(Rack::Server)
require("rails_semantic_logger/extensions/rack/server")
end
================================================
FILE: rails_semantic_logger.gemspec
================================================
$LOAD_PATH.push File.expand_path("lib", __dir__)
# Maintain your gem's version:
require "rails_semantic_logger/version"
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "rails_semantic_logger"
s.version = RailsSemanticLogger::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Reid Morrison"]
s.homepage = "https://logger.rocketjob.io"
s.summary = "Feature rich logging framework that replaces the Rails logger."
s.files = Dir["lib/**/*", "LICENSE.txt", "Rakefile", "README.md"]
s.license = "Apache-2.0"
s.required_ruby_version = ">= 2.5"
s.add_dependency "rack"
s.add_dependency "railties", ">= 5.1"
s.add_dependency "semantic_logger", "~> 4.16"
s.metadata = {
"bug_tracker_uri" => "https://github.com/reidmorrison/rails_semantic_logger/issues",
"documentation_uri" => "https://logger.rocketjob.io",
"source_code_uri" => "https://github.com/reidmorrison/rails_semantic_logger/tree/v#{RailsSemanticLogger::VERSION}",
"rubygems_mfa_required" => "true"
}
end
================================================
FILE: test/action_controller_test.rb
================================================
require_relative "test_helper"
class ActionControllerTest < Minitest::Test
describe "RailsSemanticLogger::ActionController::LogSubscriber" do
let(:subscriber) { RailsSemanticLogger::ActionController::LogSubscriber.new }
describe "#process_action" do
it "does not fail if params is not a Hash nor an instance of ActionController::Parameters" do
event = ActiveSupport::Notifications::Event.new(
"start_processing.action_controller",
5.seconds.ago,
Time.zone.now,
SecureRandom.uuid,
{
payload: "{}"
}
)
messages = semantic_logger_events do
subscriber.process_action(event)
end
assert_equal 1, messages.count, messages
end
end
end
end
================================================
FILE: test/action_mailer_test.rb
================================================
require_relative "test_helper"
class ActionMailerTest < Minitest::Test
class MyMailer < ActionMailer::Base
def some_email(opts)
mail(to: opts[:to], from: opts[:from], subject: opts[:subject], body: "Hello")
end
end
describe "ActionMailer" do
describe "#deliver" do
it "sets the ActionMailer logger" do
assert_kind_of SemanticLogger::Logger, MyMailer.logger
end
it "sends the email" do
MyMailer.some_email(to: "test@test.com", from: "test@test.com", subject: "test").deliver_now
end
it "writes log messages" do
messages = semantic_logger_events do
MyMailer.some_email(to: "test@test.com", from: "test@test.com", subject: "test").deliver_now
end
assert_equal 2, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :info,
name: "ActionMailer::Base",
message_includes: "ActionMailerTest::MyMailer#some_email: processed outbound mail",
payload_includes: {
event_name: "process.action_mailer",
mailer: "ActionMailerTest::MyMailer",
action: :some_email
}
)
assert_semantic_logger_event(
messages[1],
level: :info,
name: "ActionMailer::Base",
message_includes: Rails::VERSION::MAJOR >= 6 ? "Delivered mail" : "Skipped delivery",
payload_includes: {
event_name: "deliver.action_mailer",
mailer: "ActionMailerTest::MyMailer",
perform_deliveries: Rails::VERSION::MAJOR >= 6 ? true : nil,
subject: "test",
to: ["test@test.com"],
from: ["test@test.com"]
}
)
end
end
describe "Logging::LogSubscriber" do
before do
skip "Older rails does not support ActiveSupport::Notification" unless defined?(ActiveSupport::Notifications)
end
let(:subscriber) { RailsSemanticLogger::ActionMailer::LogSubscriber.new }
let(:event) do
ActiveSupport::Notifications::Event.new(event_name, 5.seconds.ago, Time.zone.now, SecureRandom.uuid, payload)
end
let(:payload) do
{
mailer: "MyMailer",
action: :some_email
}
end
let(:event_name) { "deliver.action_mailer" }
let(:mailer) do
MyMailer.some_email(to: "test@test.com", from: "test@test.com", subject: "test")
end
%i[deliver process].each do |method|
describe "##{method}" do
specify do
assert ActionMailer::Base.logger.info
subscriber.public_send(method, event)
end
end
end
describe "ActiveJob::Logging::LogSubscriber::EventFormatter" do
let(:formatter) do
RailsSemanticLogger::ActionMailer::LogSubscriber::EventFormatter.new(event: event, log_duration: true)
end
let(:event_name) { "deliver.action_mailer" }
describe "#payload" do
specify do
assert_equal(formatter.payload[:event_name], "deliver.action_mailer")
assert_equal(formatter.payload[:mailer], "MyMailer")
assert_equal(formatter.payload[:action], :some_email)
assert_kind_of(Float, formatter.payload[:duration])
end
end
end
end
end
end
================================================
FILE: test/active_job_test.rb
================================================
require_relative "test_helper"
class ActiveJobTest < Minitest::Test
if defined?(ActiveJob)
class MyJob < ActiveJob::Base
queue_as :my_jobs
def perform(record)
"Received: #{record}"
end
end
class SensitiveJob < ActiveJob::Base
queue_as :my_jobs
if Rails.version.to_f >= 6.1
self.log_arguments = false
else
def self.log_arguments?
false
end
end
def perform(record)
"Received: #{record}"
end
end
class TestModel
include GlobalID::Identification
def id
15
end
end
end
describe "ActiveJob" do
before do
skip "Older rails does not support ActiveJob" unless defined?(ActiveJob)
end
describe ".perform_now" do
it "sets the ActiveJob logger" do
assert_kind_of SemanticLogger::Logger, MyJob.logger
end
it "runs the job" do
MyJob.perform_now("hello")
end
end
describe "Logging::LogSubscriber" do
before do
skip "Older rails does not support ActiveSupport::Notification" unless defined?(ActiveSupport::Notifications)
end
let(:subscriber) { RailsSemanticLogger::ActiveJob::LogSubscriber.new }
let(:event) do
ActiveSupport::Notifications::Event.new(event_name, 5.seconds.ago, Time.zone.now, SecureRandom.uuid, payload)
end
let(:event_name) { "enqueue.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job
}
end
let(:job) do
MyJob.new(TestModel.new, 1, "string", foo: "bar")
end
%i[enqueue enqueue_at perform_start perform].each do |method|
describe "##{method}" do
specify do
job.stub(:scheduled_at, Time.zone.now.to_i) do
assert ActiveJob::Base.logger.info
subscriber.public_send(method, event)
end
end
end
end
describe "#perform with exception object" do
let(:event_name) { "perform.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job,
exception_object: ArgumentError.new("error")
}
end
it "logs messages" do
messages = semantic_logger_events do
subscriber.perform(event)
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :error,
name: "Rails",
message_includes: "Error performing ActiveJobTest::MyJob",
payload_includes: {
job_class: "ActiveJobTest::MyJob",
queue: "my_jobs",
event_name: "perform.active_job"
}
)
assert_includes messages[0].payload, :job_id
exception = messages[0].exception
assert exception.is_a?(ArgumentError)
assert_equal "error", exception.message
end
end
describe "#enqueue with exception object" do
let(:event_name) { "enqueue.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job,
exception_object: ArgumentError.new("error")
}
end
it "logs message" do
messages = semantic_logger_events do
subscriber.enqueue(event)
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :error,
name: "Rails",
message_includes: "Failed enqueuing ActiveJobTest::MyJob",
payload_includes: {
job_class: "ActiveJobTest::MyJob",
queue: "my_jobs",
event_name: "enqueue.active_job"
}
)
assert_includes messages[0].payload, :job_id
exception = messages[0].exception
assert exception.is_a?(ArgumentError)
assert_equal "error", exception.message
end
end
describe "#enqueue with throwing :abort" do
let(:event_name) { "enqueue.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job,
aborted: true
}
end
it "logs message" do
messages = semantic_logger_events do
subscriber.enqueue(event)
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :info,
name: "Rails",
message_includes: "Failed enqueuing ActiveJobTest::MyJob",
payload_includes: {
job_class: "ActiveJobTest::MyJob",
queue: "my_jobs",
event_name: "enqueue.active_job"
}
)
assert_match(/Failed enqueuing .*, a before_enqueue callback halted the enqueuing execution/, messages[0].message)
assert_includes messages[0].payload, :job_id
end
end
describe "#enqueue_at with exception object" do
let(:event_name) { "enqueue.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job,
exception_object: ArgumentError.new("error")
}
end
it "logs message" do
messages = semantic_logger_events do
subscriber.enqueue_at(event)
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :error,
name: "Rails",
message_includes: "Failed enqueuing ActiveJobTest::MyJob",
payload_includes: {
job_class: "ActiveJobTest::MyJob",
queue: "my_jobs",
event_name: "enqueue.active_job"
}
)
assert_includes messages[0].payload, :job_id
exception = messages[0].exception
assert exception.is_a?(ArgumentError)
assert_equal "error", exception.message
end
end
describe "#enqueue_at with throwing :abort" do
let(:event_name) { "enqueue.active_job" }
let(:payload) do
{
adapter: ActiveJob::QueueAdapters::InlineAdapter.new,
job: job,
aborted: true
}
end
it "logs message" do
messages = semantic_logger_events do
subscriber.enqueue_at(event)
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :info,
name: "Rails",
message_includes: "Failed enqueuing ActiveJobTest::MyJob",
payload_includes: {
job_class: "ActiveJobTest::MyJob",
queue: "my_jobs",
event_name: "enqueue.active_job"
}
)
assert_match(/Failed enqueuing .*, a before_enqueue callback halted the enqueuing execution/, messages[0].message)
assert_includes messages[0].payload, :job_id
end
end
describe "ActiveJob::Logging::LogSubscriber::EventFormatter" do
let(:formatter) do
RailsSemanticLogger::ActiveJob::LogSubscriber::EventFormatter.new(event: event, log_duration: true)
end
let(:event_name) { "perform.active_job" }
describe "#payload" do
specify do
assert_equal(formatter.payload[:event_name], "perform.active_job")
assert_equal(formatter.payload[:adapter], "Inline")
assert_equal(formatter.payload[:queue], "my_jobs")
assert_kind_of(String, formatter.payload[:job_id])
assert_kind_of(Float, formatter.payload[:duration])
end
describe "Show arguments in log" do
let(:job) do
MyJob.new(TestModel.new, 1, "string", foo: "bar")
end
specify do
assert_equal(formatter.payload[:job_class], "ActiveJobTest::MyJob")
arguments = <<~ARGS.chomp
[
"gid://dummy/ActiveJobTest::TestModel/15",
1,
"string",
{
"foo": "bar"
}
]
ARGS
assert_equal(formatter.payload[:arguments], arguments)
end
end
describe "Hide arguments from log" do
let(:job) do
SensitiveJob.new(TestModel.new, 1, "string", foo: "bar")
end
specify do
assert_equal(formatter.payload[:job_class], "ActiveJobTest::SensitiveJob")
assert_equal(formatter.payload[:arguments], "")
end
end
end
describe "#job_info" do
specify do
assert_match(/^ActiveJobTest::MyJob \(Job ID: [a-z\-0-9]+\) to Inline\(my_jobs\)$/, formatter.job_info)
end
end
describe "#queue_name" do
specify do
assert_equal(formatter.queue_name, "Inline(my_jobs)")
end
end
end
end
end
end
================================================
FILE: test/active_record_test.rb
================================================
require_relative "test_helper"
class ActiveRecordTest < Minitest::Test
describe "ActiveRecord" do
# Rails 5 has an extra space
let(:extra_space) { Rails::VERSION::MAJOR >= 6 ? "" : " " }
describe "logs" do
it "sql" do
expected_sql = "SELECT #{extra_space}\"samples\".* FROM \"samples\" ORDER BY \"samples\".\"id\" ASC LIMIT ?"
messages = semantic_logger_events do
Sample.first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
it "sql with query cache" do
expected_sql = "SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
messages = semantic_logger_events do
Sample.cache { 2.times { Sample.where(name: "foo").first } }
end
assert_equal 2, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {name: "foo", limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
assert_semantic_logger_event(
messages[1],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {name: "foo", limit: 1},
cached: true
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
it "single bind value" do
expected_sql =
if Rails.version.to_f >= 5.2
"SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
else
"SELECT \"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
end
messages = semantic_logger_events do
Sample.where(name: "Jack").first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {name: "Jack", limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
it "filtered bind value" do
filter_params_setting true, %i[name] do
expected_sql =
if Rails.version.to_f >= 5.2
"SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
else
"SELECT \"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
end
messages = semantic_logger_events do
Sample.where(name: "Jack").first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {name: "[FILTERED]", limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
end
it "filtered bind value when filter_parameters set as regex" do
filter_params_regex_setting true, %i[name] do
expected_sql =
if Rails.version.to_f >= 5.2
"SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
else
"SELECT \"samples\".* FROM \"samples\" WHERE \"samples\".\"name\" = ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
end
messages = semantic_logger_events do
Sample.where(name: "Jack").first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {name: "[FILTERED]", limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
end
it "multiple bind values" do
skip "Not applicable to older rails" if Rails.version.to_f <= 5.1
expected_sql = "SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"age\" BETWEEN ? AND ? ORDER BY \"samples\".\"id\" ASC LIMIT ?"
messages = semantic_logger_events do
Sample.where(age: 2..21).first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {age: [2, 21], limit: 1}
}
)
assert_instance_of Integer, messages[0].payload[:allocations] if Rails.version.to_i >= 6
end
it "works with an IN clause" do
skip "Not applicable to older rails" if Rails.version.to_f <= 5.1
expected_sql = "SELECT #{extra_space}\"samples\".* FROM \"samples\" WHERE \"samples\".\"age\" IN (?, ?) ORDER BY \"samples\".\"id\" ASC LIMIT ?"
messages = semantic_logger_events do
Sample.where(age: [2, 3]).first
end
assert_equal 1, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "ActiveRecord",
message: "Sample Load",
payload_includes: {
sql: expected_sql,
binds: {age: [2, 3], limit: 1}
}
)
end
end
describe "async queries" do
before do
skip "Not applicable to older rails" if Rails.version.to_f < 7.1
ActiveRecord::Base.asynchronous_queries_tracker.start_session
end
after do
ActiveRecord::Base.asynchronous_queries_tracker.finalize_session
end
it "marks async queries with async: true" do
expected_sql = 'SELECT COUNT(*) FROM "samples"'
messages = semantic_logger_events do
Sample.count
Sample.async_count.value
end
assert_equal 2, messages.count, messages
messages.each do |message|
assert_semantic_logger_event(
message,
level: :debug,
name: "ActiveRecord",
message: "Sample Count",
payload_includes: {sql: expected_sql}
)
end
# On Rails prior to 8.0.2, these assertions will mostly pass, but not always.
# https://github.com/rails/rails/pull/54344
skip "Older Rails has flakey async instrumentation" if Rails::VERSION::MAJOR < 8
refute messages[0].payload.key?(:async)
assert_equal true, messages[1].payload[:async]
end
end
# we could feasibly pull this back to rails 7.1. This update is related to rails 8.1
# https://github.com/reidmorrison/rails_semantic_logger/pull/276#issuecomment-3533151110
describe "runtime=" do
it "allow reads and writes to the runtime" do
unless RailsSemanticLogger::ActiveRecord::LogSubscriber.respond_to?(:runtime)
skip "runtime support ended with Rails v7.1"
end
RailsSemanticLogger::ActiveRecord::LogSubscriber.runtime = 5.0
assert_equal RailsSemanticLogger::ActiveRecord::LogSubscriber.runtime, 5.0
end
it "write to the runtime" do
unless RailsSemanticLogger::ActiveRecord::LogSubscriber.respond_to?(:runtime)
skip "runtime support ended with Rails v7.1"
end
initial_value = RailsSemanticLogger::ActiveRecord::LogSubscriber.runtime
RailsSemanticLogger::ActiveRecord::LogSubscriber.runtime = initial_value + 5000.0
assert_equal RailsSemanticLogger::ActiveRecord::LogSubscriber.runtime, initial_value + 5000.0
end
end
end
end
================================================
FILE: test/controllers/articles_controller_test.rb
================================================
require_relative "../test_helper"
class ArticlesControllerTest < ActionDispatch::IntegrationTest
describe ArticlesController do
let(:params) { {article: {text: "Text1", title: "Title1"}} }
describe "#new" do
it "shows new article" do
get article_url(:new)
assert_response :success
end
end
describe "#create" do
it "has no errors" do
post articles_url(params: params)
assert_response :success
end
it "successfully logs message" do
messages = semantic_logger_events do
post articles_url(params: params)
end
assert_equal 5, messages.count, messages
assert_semantic_logger_event(
messages[0],
message: "Started",
name: "Rack",
level: :debug,
payload: {
method: "POST",
path: "/articles?article%5Btext%5D=Text1&article%5Btitle%5D=Title1",
ip: "127.0.0.1"
}
)
assert_semantic_logger_event(
messages[1],
message: "Processing #create",
name: "ArticlesController",
level: :debug
)
assert_semantic_logger_event(
messages[2],
message: "Rendering",
name: "ActionView",
level: :debug,
payload: {
template: "text template"
}
)
assert_semantic_logger_event(
messages[3],
message: "Rendered",
name: "ActionView",
level: :debug
)
assert_semantic_logger_event(
messages[4],
message: "Completed #create",
name: "ArticlesController",
level: :info,
payload_includes: {
controller: "ArticlesController",
action: "create",
params: {
"article" => {
"text" => "Text1",
"title" => "Title1"
}
},
format: "HTML",
method: "POST",
path: "/articles",
status: 200,
status_message: "OK"
}
)
end
it "customize action message" do
old_action_message_format = RailsSemanticLogger::ActionController::LogSubscriber.action_message_format
RailsSemanticLogger::ActionController::LogSubscriber.action_message_format = -> (message, payload) do
"#{message} #{payload[:controller]}##{payload[:action]}"
end
messages = semantic_logger_events do
post articles_url(params: params)
end
assert_equal 5, messages.count, messages
assert_semantic_logger_event(
messages[0],
message: "Started"
)
assert_semantic_logger_event(
messages[1],
message: "Processing ArticlesController#create"
)
assert_semantic_logger_event(
messages[2],
message: "Rendering"
)
assert_semantic_logger_event(
messages[3],
message: "Rendered"
)
assert_semantic_logger_event(
messages[4],
message: "Completed ArticlesController#create",
)
ensure
RailsSemanticLogger::ActionController::LogSubscriber.action_message_format = old_action_message_format
end
end
describe "#show" do
it "raises and logs exception" do
# we're testing ActionDispatch::DebugExceptions in fact
messages = semantic_logger_events do
old_show = Rails.application.env_config["action_dispatch.show_exceptions"]
begin
Rails.application.env_config["action_dispatch.show_exceptions"] = :all
get article_url(:show)
rescue ActiveRecord::RecordNotFound => e
# expected
ensure
Rails.application.env_config["action_dispatch.show_exceptions"] = old_show
end
end
assert_equal 4, messages.count, messages
assert_kind_of ActiveRecord::RecordNotFound, messages[3].exception
end
it "raises and does not log exception when action_dispatch.log_rescued_responses is false" do
skip "Not applicable to older rails" if Rails.version.to_f < 7.1
# we're testing ActionDispatch::DebugExceptions here too
messages = semantic_logger_events do
old_show = Rails.application.env_config["action_dispatch.show_exceptions"]
old_log_rescued_responses = Rails.application.env_config["action_dispatch.log_rescued_responses"]
begin
Rails.application.env_config["action_dispatch.show_exceptions"] = :all
Rails.application.env_config["action_dispatch.log_rescued_responses"] = false
get article_url(:show)
rescue ActiveRecord::RecordNotFound => e
# expected
ensure
Rails.application.env_config["action_dispatch.show_exceptions"] = old_show
Rails.application.env_config["action_dispatch.log_rescued_responses"] = old_log_rescued_responses
end
end
assert_equal 3, messages.count, messages
end
end
end
end
================================================
FILE: test/controllers/dashboard_controller_test.rb
================================================
require_relative "../test_helper"
class DashboardControllerTest < ActionDispatch::IntegrationTest
describe DashboardController do
describe "#show" do
it "has no errors" do
get dashboard_url
assert_response :success
end
it "logs message" do
messages = semantic_logger_events do
get dashboard_url
end
assert_equal 3, messages.count, messages
assert_semantic_logger_event(
messages[0],
level: :debug,
name: "Rack",
message: "Started",
payload: {
method: "GET",
path: "/dashboard",
ip: "127.0.0.1"
}
)
assert_semantic_logger_event(
messages[1],
level: :debug,
name: "Rails",
message: "Processing #show",
payload: nil
)
assert_semantic_logger_event(
messages[2],
level: :info,
name: "Rails",
message: "Completed #show",
payload_includes: {
controller: "DashboardController",
action: "show",
format: "HTML",
method: "GET",
path: "/dashboard",
status: 200,
status_message: "OK"
}
)
end
it "does not break rails notifications" do
PayloadCollector.wrap do
get dashboard_url
end
payload = PayloadCollector.last
assert_equal payload[:params], {"controller" => "dashboard", "action" => "show"}
end
end
end
end
================================================
FILE: test/controllers/welcome_controller_test.rb
================================================
require_relative "../test_helper"
class WelcomeControllerTest < ActionDispatch::IntegrationTest
describe WelcomeController do
describe "#index" do
it "succeeds" do
get "/welcome/index"
assert_response :success
end
end
end
end
================================================
FILE: test/dummy/README.rdoc
================================================
== Welcome to Rails
Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the Model-View-Control pattern.
This pattern splits the view (also called the presentation) into "dumb"
templates that are primarily responsible for inserting pre-built data in between
HTML tags. The model contains the "smart" domain objects (such as Account,
Product, Person, Post) that holds all the business logic and knows how to
persist themselves to a database. The controller handles the incoming requests
(such as Save New Account, Update Product, Show Post) by manipulating the model
and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in
link:files/vendor/rails/activerecord/README.html.
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
Rails. You can read more about Action Pack in
link:files/vendor/rails/actionpack/README.html.
== Getting Started
1. At the command prompt, create a new Rails application:
<tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
2. Change directory to <tt>myapp</tt> and start the web server:
<tt>cd myapp; rails server</tt> (run with --help for options)
3. Go to http://localhost:3000/ and you'll see:
"Welcome aboard: You're riding Ruby on Rails!"
4. Follow the guidelines to start developing your application. You can find
the following resources handy:
* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
== Debugging Rails
Sometimes your application goes wrong. Fortunately there are a lot of tools that
will help you debug it and get it back on the rails.
First area to check is the application log files. Have "tail -f" commands
running on the server.log and development.log. Rails will automatically display
debugging and runtime information to these files. Debugging info will also be
shown in the browser on requests from 127.0.0.1.
You can also log your own messages directly into the log file from your code
using the Ruby logger class from inside your controllers. Example:
class WeblogController < ActionController::Base
def destroy
@weblog = Weblog.find(params[:id])
@weblog.destroy
logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
end
end
The result will be a message in your log file along the lines of:
Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
More information on how to use the logger is at http://www.ruby-doc.org/core/
Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
several books available online as well:
* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
These two books will bring you up to speed on the Ruby language and also on
programming in general.
== Debugger
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
resume execution! You need to install ruby-debug to run the server in debugging
mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
class WeblogController < ActionController::Base
def index
@posts = Post.all
debugger
end
end
So the controller will accept the action, run the first line, then present you
with a IRB prompt in the server window. Here you can do things like:
>> @posts.inspect
=> "[#<Post:0x14a6be8
@attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
#<Post:0x14a6620
@attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
>> @posts.first.title = "hello from a debugger"
=> "hello from a debugger"
...and even better, you can examine how your runtime objects actually work:
>> f = @posts.first
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
>> f.
Display all 152 possibilities? (y or n)
Finally, when you're ready to resume execution, you can enter "cont".
== Console
The console is a Ruby shell, which allows you to interact with your
application's domain model. Here you'll have all parts of the application
configured, just like it is when the application is running. You can inspect
domain models, change values, and save to the database. Starting the script
without arguments will launch it in the development environment.
To start the console, run <tt>rails console</tt> from the application
directory.
Options:
* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
made to the database.
* Passing an environment name as an argument will load the corresponding
environment. Example: <tt>rails console production</tt>.
To reload your controllers and models after launching the console run
<tt>reload!</tt>
More information about irb can be found at:
link:http://www.rubycentral.org/pickaxe/irb.html
== dbconsole
You can go to the command line of your database directly through <tt>rails
dbconsole</tt>. You would be connected to the database with the credentials
defined in database.yml. Starting the script without arguments will connect you
to the development database. Passing an argument will connect you to a different
database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
PostgreSQL and SQLite 3.
== Description of Contents
The default directory structure of a generated Ruby on Rails application:
|-- app
| |-- assets
| |-- images
| |-- javascripts
| `-- stylesheets
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
| `-- layouts
|-- config
| |-- environments
| |-- initializers
| `-- locales
|-- db
|-- doc
|-- lib
| `-- tasks
|-- log
|-- public
|-- script
|-- test
| |-- fixtures
| |-- functional
| |-- integration
| |-- performance
| `-- unit
|-- tmp
| |-- cache
| |-- pids
| |-- sessions
| `-- sockets
`-- vendor
|-- assets
`-- stylesheets
`-- plugins
app
Holds all the code that's specific to this particular application.
app/assets
Contains subdirectories for images, stylesheets, and JavaScript files.
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from
ApplicationController which itself descends from ActionController::Base.
app/models
Holds models that should be named like post.rb. Models descend from
ActiveRecord::Base by default.
app/views
Holds the template files for the view that should be named like
weblogs/index.html.erb for the WeblogsController#index action. All views use
eRuby syntax by default.
app/views/layouts
Holds the template files for layouts to be used with views. This models the
common header/footer method of wrapping views. In your views, define a layout
using the <tt>layout :default</tt> and create a file named default.html.erb.
Inside default.html.erb, call <% yield %> to render the view using this
layout.
app/helpers
Holds view helpers that should be named like weblogs_helper.rb. These are
generated for you automatically when using generators for controllers.
Helpers can be used to wrap functionality for your views into methods.
config
Configuration files for the Rails environment, the routing map, the database,
and other dependencies.
db
Contains the database schema in schema.rb. db/migrate contains all the
sequence of Migrations for your schema.
doc
This directory is where your application documentation will be stored when
generated using <tt>rake doc:app</tt>
lib
Application specific libraries. Basically, any kind of custom code that
doesn't belong under controllers, models, or helpers. This directory is in
the load path.
public
The directory available for the web server. Also contains the dispatchers and the
default HTML files. This should be set as the DOCUMENT_ROOT of your web
server.
script
Helper scripts for automation and generation.
test
Unit and functional tests along with fixtures. When using the rails generate
command, template test files will be generated for you and placed in this
directory.
vendor
External libraries that the application depends on. Also includes the plugins
subdirectory. If the app has frozen rails, those gems also go here, under
vendor/rails/. This directory is in the load path.
================================================
FILE: test/dummy/Rakefile
================================================
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path("config/application", __dir__)
Dummy::Application.load_tasks
================================================
FILE: test/dummy/app/assets/javascripts/application.js
================================================
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//# require jquery
//# require jquery_ujs
//# require_tree .
================================================
FILE: test/dummy/app/assets/javascripts/articles.js
================================================
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.
================================================
FILE: test/dummy/app/assets/stylesheets/application.css
================================================
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*/
================================================
FILE: test/dummy/app/assets/stylesheets/articles.css
================================================
/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/
================================================
FILE: test/dummy/app/assets/stylesheets/welcome.css
================================================
/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/
================================================
FILE: test/dummy/app/controllers/application_controller.rb
================================================
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
end
================================================
FILE: test/dummy/app/controllers/application_metal_controller.rb
================================================
class ApplicationMetalController < ActionController::Metal
MODULES = [
ActionController::Instrumentation,
AbstractController::Rendering,
ActionController::Rendering,
ActionController::Renderers::All
# Helpers::Controller
].freeze
MODULES.each do |mod|
include mod
end
end
================================================
FILE: test/dummy/app/controllers/articles_controller.rb
================================================
class ArticlesController < ApplicationController
def new
end
def create
render plain: params[:article].inspect
end
def show
raise ActiveRecord::RecordNotFound
end
end
================================================
FILE: test/dummy/app/controllers/dashboard_controller.rb
================================================
class DashboardController < ApplicationMetalController
def show
end
end
================================================
FILE: test/dummy/app/controllers/welcome_controller.rb
================================================
class WelcomeController < ApplicationController
def index
end
end
================================================
FILE: test/dummy/app/helpers/application_helper.rb
================================================
module ApplicationHelper
end
================================================
FILE: test/dummy/app/helpers/articles_helper.rb
================================================
module ArticlesHelper
end
================================================
FILE: test/dummy/app/helpers/welcome_helper.rb
================================================
module WelcomeHelper
end
================================================
FILE: test/dummy/app/jobs/bad_job.rb
================================================
class BadJob
include Sidekiq::Worker
sidekiq_options retry: false
def perform
raise ArgumentError, "This is a bad job"
end
end
================================================
FILE: test/dummy/app/jobs/simple_job.rb
================================================
class SimpleJob
include Sidekiq::Worker
def perform
"SimpleJob is working"
end
end
================================================
FILE: test/dummy/app/mailers/.gitkeep
================================================
================================================
FILE: test/dummy/app/models/.gitkeep
================================================
================================================
FILE: test/dummy/app/models/sample.rb
================================================
class Sample < ActiveRecord::Base
end
================================================
FILE: test/dummy/app/views/articles/new.html.erb
================================================
<h1>New Article</h1>
<%= form_for :article, url: articles_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :text %><br>
<%= f.text_area :text %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
================================================
FILE: test/dummy/app/views/layouts/application.html.erb
================================================
<!DOCTYPE html>
<html>
<head>
<title>Dummy</title>
<%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
================================================
FILE: test/dummy/app/views/welcome/index.html.erb
================================================
<h1>Welcome#index</h1>
<p>Find me in app/views/welcome/index.html.erb</p>
================================================
FILE: test/dummy/bin/bundle
================================================
#!/usr/bin/env ruby
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
load Gem.bin_path("bundler", "bundle")
================================================
FILE: test/dummy/bin/puma
================================================
#!/usr/bin/env ruby
#
# This file was generated by Bundler.
#
# The application 'puma' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath)
require "rubygems"
require "bundler/setup"
load Gem.bin_path("puma", "puma")
================================================
FILE: test/dummy/bin/rails
================================================
#!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__)
require_relative "../config/boot"
require "rails/commands"
================================================
FILE: test/dummy/bin/rake
================================================
#!/usr/bin/env ruby
require_relative "../config/boot"
require "rake"
Rake.application.run
================================================
FILE: test/dummy/bin/setup
================================================
#!/usr/bin/env ruby
require "pathname"
# path to your application root.
APP_ROOT = Pathname.new File.expand_path("..", __dir__)
Dir.chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file:
puts "== Installing dependencies =="
system "gem install bundler --conservative"
system "bundle check || bundle install"
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# system "cp config/database.yml.sample config/database.yml"
# end
puts "\n== Removing old logs and tempfiles =="
system "rm -f log/*"
system "rm -rf tmp/cache"
puts "\n== Restarting application server =="
system "touch tmp/restart.txt"
end
================================================
FILE: test/dummy/config/application.rb
================================================
require File.expand_path("boot", __dir__)
require "rails/all"
Bundler.require
module Dummy
class Application < Rails::Application
config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" if config.respond_to?(:load_defaults)
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
config.active_record.sqlite3.represent_boolean_as_integer = true if config.active_record.sqlite3
config.active_record.async_query_executor = :global_thread_pool if (Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR >= 1) || Rails::VERSION::MAJOR > 7
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
# Do not swallow errors in after_commit/after_rollback callbacks.
# config.active_record.raise_in_transactional_callbacks = true
config.semantic_logger.default_level = :trace
# Warning: Set to :error or higher in production to avoid performance issues.
config.semantic_logger.backtrace_level = :trace
# Test out Amazing Print
config.rails_semantic_logger.ap_options = {multiline: false, ruby19_syntax: true}
end
end
================================================
FILE: test/dummy/config/boot.rb
================================================
require "rubygems"
gemfile = File.expand_path("../../../Gemfile", __dir__)
if File.exist?(gemfile)
ENV["BUNDLE_GEMFILE"] = gemfile
require "bundler"
Bundler.setup
end
$LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
================================================
FILE: test/dummy/config/database.yml
================================================
# SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000
================================================
FILE: test/dummy/config/environment.rb
================================================
# Load the rails application
require File.expand_path("application", __dir__)
# Initialize the rails application
Dummy::Application.initialize!
================================================
FILE: test/dummy/config/environments/development.rb
================================================
Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Raise an error on page load if there are pending migrations.
# config.active_record.migration_error = :page_load
# Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
config.rails_semantic_logger.quiet_assets = true
config.rails_semantic_logger.ap_options = {multiline: true}
end
================================================
FILE: test/dummy/config/environments/production.rb
================================================
Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.perform_caching = true
config.cache_classes = true
config.eager_load = true
# Disable Rails's static asset server (Apache or nginx could already do this)
config.serve_static_files = true
# SSL is handled by the load balancer
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = false
# See everything in the log (default is :info)
config.log_level = :info
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
# Disable colorized logging
# config.colorize_logging = false
end
================================================
FILE: test/dummy/config/environments/test.rb
================================================
Dummy::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Configure static asset server for tests with Cache-Control for performance
# config.serve_static_assets = true
# Log error messages when you accidentally call methods on nil
config.whiny_nils = true
config.eager_load = false
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates
config.action_dispatch.show_exceptions = Rails::VERSION::MAJOR >= 7 ? :none : false
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
config.active_support.test_order = :random
end
================================================
FILE: test/dummy/config/initializers/backtrace_silencers.rb
================================================
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!
================================================
FILE: test/dummy/config/initializers/inflections.rb
================================================
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
#
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.acronym 'RESTful'
# end
================================================
FILE: test/dummy/config/initializers/mime_types.rb
================================================
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone
================================================
FILE: test/dummy/config/initializers/payload_collector.rb
================================================
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
PayloadCollector.append(event.payload)
end
================================================
FILE: test/dummy/config/initializers/secret_token.rb
================================================
# Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Dummy::Application.config.secret_token = "afd0f2e89c2a660f839bfb8e982358b6748c7fcd7c61c98b1edfc164539fae5beafece2906858801a4c7f5bbcd2f2e606db2a766630e1b0b1468f50ad6025630"
================================================
FILE: test/dummy/config/initializers/session_store.rb
================================================
# Be sure to restart your server when you modify this file.
Rails.application.config.session_store :cookie_store, key: "_dummy_session"
================================================
FILE: test/dummy/config/initializers/sidekiq.rb
================================================
# In tests we force Sidekiq into thinking it is running as a server,
# so it creates a stdout logger. Remove it here:
Rails.application.config.after_initialize do
SemanticLogger.appenders.delete_if { |appender| appender.is_a?(SemanticLogger::Appender::IO) } if Rails.env.test?
end
================================================
FILE: test/dummy/config/initializers/wrap_parameters.rb
================================================
# Be sure to restart your server when you modify this file.
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
# end
================================================
FILE: test/dummy/config/locales/en.yml
================================================
# Sample localization file for English. Add more files in this directory for other locales.
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
hello: "Hello world"
================================================
FILE: test/dummy/config/routes.rb
================================================
Dummy::Application.routes.draw do
get "welcome/index"
resources :articles
resource :dashboard, controller: :dashboard
root "welcome#index"
end
================================================
FILE: test/dummy/config/secrets.yml
================================================
# Be sure to restart your server when you modify this file.
# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
development:
secret_key_base: 72c4f52428cb36cd0afb2cf6e36a8a67fa39f8fd2396f533b85e42441feafdcdcac4f7629f3e1c5b846b1c452ad6e3ecfe368e913b71cd147d4536b35406b2fd
test:
secret_key_base: 29dc974bc32244b62fd69259cd4f9ff7223a4cfe9fe40ee948dd1e64a99cd6bbef4a5d75f1b106ef50fa750613542e52e21d7fd67695356cd529264f614c1c3d
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
================================================
FILE: test/dummy/config.ru
================================================
# This file is used by Rack-based servers to start the application.
require File.expand_path("config/environment", __dir__)
run Dummy::Application
================================================
FILE: test/dummy/db/migrate/20170525020551_create_samples.rb
================================================
class CreateSamples < ActiveRecord::Migration
def change
create_table :samples do |t|
t.string :name
t.integer :age
t.date :joined
t.timestamps null: false
end
end
end
================================================
FILE: test/dummy/db/schema.rb
================================================
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20_170_525_020_551) do
create_table "samples", force: :cascade do |t|
t.string "name"
t.integer "age"
t.date "joined"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
================================================
FILE: test/dummy/lib/assets/.gitkeep
================================================
================================================
FILE: test/dummy/log/.gitkeep
================================================
================================================
FILE: test/dummy/public/404.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
</body>
</html>
================================================
FILE: test/dummy/public/422.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
</body>
</html>
================================================
FILE: test/dummy/public/500.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
</div>
</body>
</html>
================================================
FILE: test/dummy/script/rails
================================================
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
APP_PATH = File.expand_path("../config/application", __dir__)
require File.expand_path("../config/boot", __dir__)
require "rails/commands"
================================================
FILE: test/dummy/test/fixtures/samples.yml
================================================
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
name: MyString
age: 1
joined: 2017-05-24
two:
name: MyString
age: 1
joined: 2017-05-24
================================================
FILE: test/payload_collector.rb
================================================
class PayloadCollector
class << self
def wrap
@store = true
yield
ensure
@store = false
end
def append(payload)
data.append(payload) if @store
end
def last
data.last
end
def flush
@data = []
end
private
def data
@data ||= []
end
end
end
================================================
FILE: test/rails_test.rb
================================================
require_relative "test_helper"
class RailsTest < Minitest::Test
describe "Rails" do
describe ".logger" do
it "replaces the Rails logger" do
assert_kind_of SemanticLogger::Logger, Rails.logger
end
it "uses the colorized formatter" do
assert_kind_of SemanticLogger::Formatters::Color, SemanticLogger.appenders.first.formatter
end
it "is compatible with Rails logger" do
assert_nil Rails.logger.formatter
Rails.logger.formatter = "blah"
assert_equal "blah", Rails.logger.formatter
end
end
end
end
================================================
FILE: test/sidekiq_test.rb
================================================
require_relative "test_helper"
class SidekiqTest < Minitest::Test
# Cannot use inline testing since it bypasses the Sidekiq logging calls.
describe Sidekiq::Worker do
let(:job) { SimpleJob }
let(:args) { [] }
describe "#logger" do
it "has its own logger with the same name as the job" do
assert_kind_of SemanticLogger::Logger, SimpleJob.logger
assert_kind_of SemanticLogger::Logger, job.logger
assert_equal job.logger.name, job.name
refute_same Sidekiq.logger, job.logger
end
end
describe "#perform" do
let(:config) { Sidekiq.default_configuration }
let(:msg) { Sidekiq.dump_json({"class" => job.to_s, "args" => args, "enqueued_at" => 1.minute.ago}) }
let(:uow) { Sidekiq::BasicFetch::UnitOfWork.new("queue:default", msg) }
if Sidekiq::VERSION.to_i == 6 && Sidekiq::VERSION.to_f < 6.5
let(:processor) do
mgr = Minitest::Mock.new
opts = Sidekiq.options
opts[:fetch] = Sidekiq::BasicFetch.new(opts)
Sidekiq::Processor.new(mgr, opts)
end
elsif Sidekiq::VERSION.to_i == 6
let(:processor) do
config = Sidekiq
config[:fetch] = Sidekiq::BasicFetch.new(config)
Sidekiq::Processor.new(config) { |*args| }
end
elsif Sidekiq::VERSION.to_i < 7
let(:processor) do
opts = Sidekiq.options
mgr = Minitest::Mock.new
mgr.expect(:options, opts)
mgr.expect(:options, opts)
mgr.expect(:options, opts)
Sidekiq::Processor.new(mgr)
end
else
let(:processor) { Sidekiq::Processor.new(config.default_capsule) { |*args| } }
end
it "a simple job" do
# SimpleJob.perform_async
messages = semantic_logger_events do
processor.send(:process, uow)
end
assert_equal 2, messages.count, -> { messages.collect(&:to_h).ai }
assert_semantic_logger_event(
messages[0],
level: :info,
name: "SimpleJob",
message: "Start #perform",
metric: "sidekiq.queue.latency",
named_tags: {jid: nil, queue: "default"}
)
assert messages[0].metric_amount.is_a?(Float)
assert_semantic_logger_event(
messages[1],
level: :info,
name: "SimpleJob",
message: "Completed #perform",
metric: "sidekiq.job.perform",
named_tags: {jid: nil, queue: "default"}
)
assert messages[1].duration.is_a?(Float)
end
describe "with Bad Job" do
let(:job) { BadJob }
it "a job that raises an exception" do
# BadJob.perform_async
messages = semantic_logger_events do
assert_raises ArgumentError do
processor.send(:process, uow)
end
end
assert_equal 3, messages.count, -> { messages.collect(&:to_h).ai }
assert_semantic_logger_event(
messages[0],
level: :info,
name: "BadJob",
message: "Start #perform",
metric: "sidekiq.queue.latency",
named_tags: {jid: nil, queue: "default"},
exception: :nil
)
assert messages[0].metric_amount.is_a?(Float)
assert_semantic_logger_event(
messages[1],
level: :error,
name: "BadJob",
message: "Completed #perform",
metric: "sidekiq.job.perform",
named_tags: {jid: nil, queue: "default"},
exception: ArgumentError
)
assert messages[1].duration.is_a?(Float)
assert_semantic_logger_event(
messages[2],
level: :warn,
name: "BadJob",
message: "Job raised exception",
payload_includes: {context: "Job raised exception"},
exception: :nil
)
assert_equal messages[2].payload[:job]["class"], "BadJob"
assert_equal messages[2].payload[:job]["args"], []
end
end
end
end
end
================================================
FILE: test/test_helper.rb
================================================
ENV["RAILS_ENV"] ||= "test"
ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] = "1"
# Load first so Sidekiq thinks it is running as a server instance
require "sidekiq/cli"
if defined?(Sidekiq::DEFAULT_ERROR_HANDLER)
# Set by Sidekiq CLI at startup
Sidekiq.options[:error_handlers] << Sidekiq::DEFAULT_ERROR_HANDLER
end
require_relative "dummy/config/environment"
require "rails/test_help"
require "minitest/autorun"
require "minitest/rails"
require "amazing_print"
require_relative "payload_collector"
# Include the complete backtrace?
Minitest.backtrace_filter = Minitest::BacktraceFilter.new if ENV["BACKTRACE"].present?
Rails.backtrace_cleaner.remove_silencers!
# Add Semantic Logger helpers for Minitest
Minitest::Test.include SemanticLogger::Test::Minitest
ActionMailer::Base.delivery_method = :test
def filter_params_setting(value, user_defined_params, &block)
original_value = Rails.configuration.filter_parameters
Rails.configuration.filter_parameters = user_defined_params
block.call
ensure
Rails.configuration.filter_parameters = original_value
end
def filter_params_regex_setting(value, user_defined_params, &block)
original_value = Rails.configuration.filter_parameters
Rails.configuration.filter_parameters += user_defined_params
filter_params_regex = Rails.configuration.filter_parameters.map do |key|
"(?i:#{key})"
end.join("|")
Rails.configuration.filter_parameters = [/(?-mix:#{filter_params_regex})/]
block.call
ensure
Rails.configuration.filter_parameters = original_value
end
gitextract_hgorv55i/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── .rubocop.yml
├── Appraisals
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── TESTING.md
├── gemfiles/
│ ├── rails_6.1.gemfile
│ ├── rails_7.0.gemfile
│ ├── rails_7.0b.gemfile
│ ├── rails_7.1.1.gemfile
│ ├── rails_7.1.gemfile
│ ├── rails_7.1b.gemfile
│ ├── rails_7.2.gemfile
│ ├── rails_8.0.gemfile
│ └── rails_8.1.gemfile
├── lib/
│ ├── rails_semantic_logger/
│ │ ├── action_controller/
│ │ │ └── log_subscriber.rb
│ │ ├── action_mailer/
│ │ │ └── log_subscriber.rb
│ │ ├── action_view/
│ │ │ └── log_subscriber.rb
│ │ ├── active_job/
│ │ │ └── log_subscriber.rb
│ │ ├── active_record/
│ │ │ └── log_subscriber.rb
│ │ ├── delayed_job/
│ │ │ └── plugin.rb
│ │ ├── engine.rb
│ │ ├── extensions/
│ │ │ ├── action_cable/
│ │ │ │ └── tagged_logger_proxy.rb
│ │ │ ├── action_controller/
│ │ │ │ └── live.rb
│ │ │ ├── action_dispatch/
│ │ │ │ └── debug_exceptions.rb
│ │ │ ├── action_view/
│ │ │ │ └── streaming_template_renderer.rb
│ │ │ ├── active_job/
│ │ │ │ └── logging.rb
│ │ │ ├── active_model_serializers/
│ │ │ │ └── logging.rb
│ │ │ ├── active_support/
│ │ │ │ ├── log_subscriber.rb
│ │ │ │ ├── logger.rb
│ │ │ │ └── tagged_logging.rb
│ │ │ ├── mongoid/
│ │ │ │ └── config.rb
│ │ │ ├── rack/
│ │ │ │ └── server.rb
│ │ │ ├── rackup/
│ │ │ │ └── server.rb
│ │ │ ├── rails/
│ │ │ │ └── server.rb
│ │ │ └── sidekiq/
│ │ │ └── sidekiq.rb
│ │ ├── options.rb
│ │ ├── rack/
│ │ │ └── logger.rb
│ │ ├── sidekiq/
│ │ │ ├── defaults.rb
│ │ │ ├── job_logger.rb
│ │ │ └── loggable.rb
│ │ └── version.rb
│ └── rails_semantic_logger.rb
├── rails_semantic_logger.gemspec
└── test/
├── action_controller_test.rb
├── action_mailer_test.rb
├── active_job_test.rb
├── active_record_test.rb
├── controllers/
│ ├── articles_controller_test.rb
│ ├── dashboard_controller_test.rb
│ └── welcome_controller_test.rb
├── dummy/
│ ├── README.rdoc
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ ├── javascripts/
│ │ │ │ ├── application.js
│ │ │ │ └── articles.js
│ │ │ └── stylesheets/
│ │ │ ├── application.css
│ │ │ ├── articles.css
│ │ │ └── welcome.css
│ │ ├── controllers/
│ │ │ ├── application_controller.rb
│ │ │ ├── application_metal_controller.rb
│ │ │ ├── articles_controller.rb
│ │ │ ├── dashboard_controller.rb
│ │ │ └── welcome_controller.rb
│ │ ├── helpers/
│ │ │ ├── application_helper.rb
│ │ │ ├── articles_helper.rb
│ │ │ └── welcome_helper.rb
│ │ ├── jobs/
│ │ │ ├── bad_job.rb
│ │ │ └── simple_job.rb
│ │ ├── mailers/
│ │ │ └── .gitkeep
│ │ ├── models/
│ │ │ ├── .gitkeep
│ │ │ └── sample.rb
│ │ └── views/
│ │ ├── articles/
│ │ │ └── new.html.erb
│ │ ├── layouts/
│ │ │ └── application.html.erb
│ │ └── welcome/
│ │ └── index.html.erb
│ ├── bin/
│ │ ├── bundle
│ │ ├── puma
│ │ ├── rails
│ │ ├── rake
│ │ └── setup
│ ├── config/
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments/
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers/
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── inflections.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── payload_collector.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ ├── sidekiq.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales/
│ │ │ └── en.yml
│ │ ├── routes.rb
│ │ └── secrets.yml
│ ├── config.ru
│ ├── db/
│ │ ├── migrate/
│ │ │ └── 20170525020551_create_samples.rb
│ │ └── schema.rb
│ ├── lib/
│ │ └── assets/
│ │ └── .gitkeep
│ ├── log/
│ │ └── .gitkeep
│ ├── public/
│ │ ├── 404.html
│ │ ├── 422.html
│ │ └── 500.html
│ ├── script/
│ │ └── rails
│ └── test/
│ └── fixtures/
│ └── samples.yml
├── payload_collector.rb
├── rails_test.rb
├── sidekiq_test.rb
└── test_helper.rb
SYMBOL INDEX (248 symbols across 52 files)
FILE: lib/rails_semantic_logger.rb
type RailsSemanticLogger (line 5) | module RailsSemanticLogger
type ActionController (line 6) | module ActionController
type ActionMailer (line 10) | module ActionMailer
type ActionView (line 14) | module ActionView
type ActiveJob (line 18) | module ActiveJob
type ActiveRecord (line 22) | module ActiveRecord
type Rack (line 26) | module Rack
type DelayedJob (line 30) | module DelayedJob
type Sidekiq (line 34) | module Sidekiq
function swap_subscriber (line 43) | def self.swap_subscriber(old_class, new_class, notifier)
function unattach (line 50) | def self.unattach(subscriber)
function subscriber_patterns (line 62) | def self.subscriber_patterns(subscriber)
function listeners_for (line 70) | def self.listeners_for(notifier, pattern)
FILE: lib/rails_semantic_logger/action_controller/log_subscriber.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type ActionController (line 2) | module ActionController
class LogSubscriber (line 3) | class LogSubscriber < ActiveSupport::LogSubscriber
method start_processing (line 11) | def start_processing(event)
method process_action (line 15) | def process_action(event)
method halted_callback (line 73) | def halted_callback(event)
method send_file (line 77) | def send_file(event)
method redirect_to (line 81) | def redirect_to(event)
method send_data (line 85) | def send_data(event)
method unpermitted_parameters (line 91) | def unpermitted_parameters(event)
method controller_logger (line 116) | def controller_logger(event)
method extract_path (line 125) | def extract_path(path)
method action_message (line 130) | def action_message(message, payload)
FILE: lib/rails_semantic_logger/action_mailer/log_subscriber.rb
type RailsSemanticLogger (line 4) | module RailsSemanticLogger
type ActionMailer (line 5) | module ActionMailer
class LogSubscriber (line 6) | class LogSubscriber < ::ActiveSupport::LogSubscriber
method deliver (line 7) | def deliver(event)
method process (line 33) | def process(event)
class EventFormatter (line 44) | class EventFormatter
method initialize (line 45) | def initialize(event:, log_duration: false)
method mailer (line 50) | def mailer
method payload (line 54) | def payload
method date (line 72) | def date
method mailer (line 84) | def mailer
method action (line 88) | def action
method formatted_args (line 92) | def formatted_args
method format (line 100) | def format(arg)
method log_duration? (line 117) | def log_duration?
method log_with_formatter (line 122) | def log_with_formatter(level: :info, **kw_args)
method logger (line 128) | def logger
FILE: lib/rails_semantic_logger/action_view/log_subscriber.rb
type RailsSemanticLogger (line 3) | module RailsSemanticLogger
type ActionView (line 4) | module ActionView
class LogSubscriber (line 6) | class LogSubscriber < ActiveSupport::LogSubscriber
method initialize (line 14) | def initialize
method render_template (line 19) | def render_template(event)
method render_partial (line 36) | def render_partial(event)
method render_collection (line 54) | def render_collection(event)
method start (line 74) | def start(name, id, payload)
class Start (line 87) | class Start
method start (line 88) | def start(name, _id, payload)
method finish (line 98) | def finish(name, id, payload)
method from_rails_root (line 103) | def from_rails_root(string)
method rails_root (line 109) | def rails_root
method logger (line 113) | def logger
method attach_to (line 118) | def self.attach_to(*)
method should_log? (line 137) | def should_log?
method from_rails_root (line 141) | def from_rails_root(string)
method rails_root (line 147) | def rails_root
method logger (line 151) | def logger
FILE: lib/rails_semantic_logger/active_job/log_subscriber.rb
type RailsSemanticLogger (line 3) | module RailsSemanticLogger
type ActiveJob (line 4) | module ActiveJob
class LogSubscriber (line 5) | class LogSubscriber < ::ActiveSupport::LogSubscriber
method enqueue (line 6) | def enqueue(event)
method enqueue_at (line 27) | def enqueue_at(event)
method perform_start (line 48) | def perform_start(event)
method perform (line 54) | def perform(event)
class EventFormatter (line 72) | class EventFormatter
method initialize (line 73) | def initialize(event:, log_duration: false)
method job_info (line 78) | def job_info
method payload (line 82) | def payload
method queue_name (line 95) | def queue_name
method scheduled_at (line 99) | def scheduled_at
method job (line 107) | def job
method adapter_name (line 111) | def adapter_name
method formatted_args (line 115) | def formatted_args
method format (line 123) | def format(arg)
method log_duration? (line 142) | def log_duration?
method log_with_formatter (line 147) | def log_with_formatter(level: :info, **kw_args)
method logger (line 153) | def logger
FILE: lib/rails_semantic_logger/active_record/log_subscriber.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type ActiveRecord (line 2) | module ActiveRecord
class LogSubscriber (line 3) | class LogSubscriber < ActiveSupport::LogSubscriber
method runtime= (line 12) | def self.runtime=(value)
method runtime (line 16) | def self.runtime
method reset_runtime (line 20) | def self.reset_runtime
method sql (line 27) | def sql(event)
method add_bind_value (line 60) | def add_bind_value(binds, key, value)
method rails_filter_params_include? (line 72) | def rails_filter_params_include?(key)
method logger (line 80) | def logger
method bind_values_v3 (line 88) | def bind_values_v3(payload)
method bind_values_v4 (line 100) | def bind_values_v4(payload)
method bind_values_v5_0_0 (line 109) | def bind_values_v5_0_0(payload)
method bind_values_v5_0_3 (line 118) | def bind_values_v5_0_3(payload)
method bind_values_v5_1_5 (line 128) | def bind_values_v5_1_5(payload)
method bind_values_v6_1 (line 138) | def bind_values_v6_1(payload)
method render_bind_v4_2 (line 148) | def render_bind_v4_2(column, value)
method render_bind_v5_0_0 (line 162) | def render_bind_v5_0_0(attribute)
method render_bind_v5_0_3 (line 177) | def render_bind_v5_0_3(attr, value)
method render_bind_v6_1 (line 187) | def render_bind_v6_1(attr, value)
method type_casted_binds_v5_0_3 (line 200) | def type_casted_binds_v5_0_3(binds, casted_binds)
method type_casted_binds_v5_1_5 (line 204) | def type_casted_binds_v5_1_5(casted_binds)
FILE: lib/rails_semantic_logger/delayed_job/plugin.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type DelayedJob (line 2) | module DelayedJob
class Plugin (line 3) | class Plugin < Delayed::Plugin
FILE: lib/rails_semantic_logger/engine.rb
type RailsSemanticLogger (line 4) | module RailsSemanticLogger
class Engine (line 5) | class Engine < ::Rails::Engine
FILE: lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb
type ActionCable (line 3) | module ActionCable
type Connection (line 4) | module Connection
class TaggedLoggerProxy (line 5) | class TaggedLoggerProxy
method tag (line 6) | def tag(logger, &block)
FILE: lib/rails_semantic_logger/extensions/action_controller/live.rb
type ActionController (line 4) | module ActionController
type Live (line 5) | module Live
function log_error (line 7) | def log_error(exception)
FILE: lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb
type ActionDispatch (line 4) | module ActionDispatch
class DebugExceptions (line 5) | class DebugExceptions
method log_error (line 10) | def log_error(request, wrapper)
method log_error (line 19) | def log_error(_request, wrapper)
FILE: lib/rails_semantic_logger/extensions/action_view/streaming_template_renderer.rb
type ActionView (line 4) | module ActionView
class StreamingTemplateRenderer (line 5) | class StreamingTemplateRenderer
class Body (line 6) | class Body
method log_error (line 10) | def log_error(exception)
FILE: lib/rails_semantic_logger/extensions/active_job/logging.rb
type ActiveJob (line 4) | module ActiveJob
type Logging (line 5) | module Logging
function tag_logger (line 11) | def tag_logger(*tags, &block)
FILE: lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb
type ActiveModelSerializers (line 4) | module ActiveModelSerializers
type Logging (line 5) | module Logging
function tag_logger (line 10) | def tag_logger(*tags, &block)
class SerializableResource (line 15) | class SerializableResource
FILE: lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb
type ActiveSupport (line 4) | module ActiveSupport
class LogSubscriber (line 5) | class LogSubscriber
method silenced? (line 7) | def silenced?(event)
FILE: lib/rails_semantic_logger/extensions/active_support/logger.rb
type ActiveSupport (line 3) | module ActiveSupport
class Logger (line 5) | class Logger
method broadcast (line 12) | def broadcast(_logger)
method logger_outputs_to? (line 19) | def self.logger_outputs_to?(*_args)
method new (line 23) | def self.new(*_args, **_kwargs)
FILE: lib/rails_semantic_logger/extensions/active_support/tagged_logging.rb
type ActiveSupport (line 1) | module ActiveSupport
type TaggedLogging (line 2) | module TaggedLogging
function new (line 4) | def self.new(logger)
FILE: lib/rails_semantic_logger/extensions/mongoid/config.rb
type Mongoid (line 3) | module Mongoid
type Config (line 4) | module Config
function set_log_levels (line 8) | def set_log_levels
FILE: lib/rails_semantic_logger/extensions/rack/server.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type Rack (line 2) | module Rack
type Server (line 3) | module Server
function daemonize_app (line 4) | def daemonize_app
FILE: lib/rails_semantic_logger/extensions/rackup/server.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type Rackup (line 2) | module Rackup
type Server (line 3) | module Server
function daemonize_app (line 4) | def daemonize_app
FILE: lib/rails_semantic_logger/extensions/rails/server.rb
type Rails (line 4) | module Rails
class Server (line 5) | class Server
method log_to_stdout (line 9) | def log_to_stdout
FILE: lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb
type Sidekiq (line 10) | module Sidekiq
type Logging (line 14) | module Logging
function with_context (line 15) | def self.with_context(msg, &block)
function job_hash_context (line 19) | def self.job_hash_context(job_hash)
class Processor (line 31) | class Processor
method log_context (line 32) | def log_context(job_hash)
type Middleware (line 41) | module Middleware
type Server (line 42) | module Server
class Logging (line 43) | class Logging
method call (line 45) | def call(worker, item, queue)
method perform_messages_enabled? (line 66) | def perform_messages_enabled?
method job_latency_ms (line 70) | def job_latency_ms(job)
FILE: lib/rails_semantic_logger/options.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
class Options (line 115) | class Options
method initialize (line 121) | def initialize
FILE: lib/rails_semantic_logger/rack/logger.rb
type RailsSemanticLogger (line 7) | module RailsSemanticLogger
type Rack (line 8) | module Rack
class Logger (line 9) | class Logger < ActiveSupport::LogSubscriber
method initialize (line 15) | def initialize(app, taggers = nil)
method call (line 20) | def call(env)
method call_app (line 37) | def call_app(request, env)
method started_request_message (line 61) | def started_request_message(request)
method compute_tags (line 72) | def compute_tags(request)
method compute_named_tags (line 86) | def compute_named_tags(request)
method logger (line 103) | def logger
FILE: lib/rails_semantic_logger/sidekiq/defaults.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type Sidekiq (line 2) | module Sidekiq
type Defaults (line 3) | module Defaults
function delete_default_error_handler (line 29) | def self.delete_default_error_handler(error_handlers)
FILE: lib/rails_semantic_logger/sidekiq/job_logger.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type Sidekiq (line 2) | module Sidekiq
class JobLogger (line 3) | class JobLogger
method perform_messages (line 7) | def perform_messages
method initialize (line 13) | def initialize(*_args)
method call (line 16) | def call(item, queue, &block)
method prepare (line 45) | def prepare(job_hash, &block)
method perform_messages_enabled? (line 58) | def perform_messages_enabled?
method job_hash_context (line 62) | def job_hash_context(job_hash)
method job_latency_ms (line 70) | def job_latency_ms(job)
FILE: lib/rails_semantic_logger/sidekiq/loggable.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
type Sidekiq (line 2) | module Sidekiq
type Loggable (line 3) | module Loggable
function included (line 4) | def included(base)
FILE: lib/rails_semantic_logger/version.rb
type RailsSemanticLogger (line 1) | module RailsSemanticLogger
FILE: test/action_controller_test.rb
class ActionControllerTest (line 3) | class ActionControllerTest < Minitest::Test
FILE: test/action_mailer_test.rb
class ActionMailerTest (line 3) | class ActionMailerTest < Minitest::Test
class MyMailer (line 4) | class MyMailer < ActionMailer::Base
method some_email (line 5) | def some_email(opts)
FILE: test/active_job_test.rb
class ActiveJobTest (line 3) | class ActiveJobTest < Minitest::Test
class MyJob (line 5) | class MyJob < ActiveJob::Base
method perform (line 8) | def perform(record)
class SensitiveJob (line 13) | class SensitiveJob < ActiveJob::Base
method log_arguments? (line 19) | def self.log_arguments?
method perform (line 24) | def perform(record)
class TestModel (line 29) | class TestModel
method id (line 32) | def id
FILE: test/active_record_test.rb
class ActiveRecordTest (line 3) | class ActiveRecordTest < Minitest::Test
FILE: test/controllers/articles_controller_test.rb
class ArticlesControllerTest (line 3) | class ArticlesControllerTest < ActionDispatch::IntegrationTest
FILE: test/controllers/dashboard_controller_test.rb
class DashboardControllerTest (line 3) | class DashboardControllerTest < ActionDispatch::IntegrationTest
FILE: test/controllers/welcome_controller_test.rb
class WelcomeControllerTest (line 3) | class WelcomeControllerTest < ActionDispatch::IntegrationTest
FILE: test/dummy/app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: test/dummy/app/controllers/application_metal_controller.rb
class ApplicationMetalController (line 1) | class ApplicationMetalController < ActionController::Metal
FILE: test/dummy/app/controllers/articles_controller.rb
class ArticlesController (line 1) | class ArticlesController < ApplicationController
method new (line 2) | def new
method create (line 5) | def create
method show (line 9) | def show
FILE: test/dummy/app/controllers/dashboard_controller.rb
class DashboardController (line 1) | class DashboardController < ApplicationMetalController
method show (line 2) | def show
FILE: test/dummy/app/controllers/welcome_controller.rb
class WelcomeController (line 1) | class WelcomeController < ApplicationController
method index (line 2) | def index
FILE: test/dummy/app/helpers/application_helper.rb
type ApplicationHelper (line 1) | module ApplicationHelper
FILE: test/dummy/app/helpers/articles_helper.rb
type ArticlesHelper (line 1) | module ArticlesHelper
FILE: test/dummy/app/helpers/welcome_helper.rb
type WelcomeHelper (line 1) | module WelcomeHelper
FILE: test/dummy/app/jobs/bad_job.rb
class BadJob (line 1) | class BadJob
method perform (line 6) | def perform
FILE: test/dummy/app/jobs/simple_job.rb
class SimpleJob (line 1) | class SimpleJob
method perform (line 4) | def perform
FILE: test/dummy/app/models/sample.rb
class Sample (line 1) | class Sample < ActiveRecord::Base
FILE: test/dummy/config/application.rb
type Dummy (line 7) | module Dummy
class Application (line 8) | class Application < Rails::Application
FILE: test/dummy/db/migrate/20170525020551_create_samples.rb
class CreateSamples (line 1) | class CreateSamples < ActiveRecord::Migration
method change (line 2) | def change
FILE: test/payload_collector.rb
class PayloadCollector (line 1) | class PayloadCollector
method wrap (line 3) | def wrap
method append (line 10) | def append(payload)
method last (line 14) | def last
method flush (line 18) | def flush
method data (line 24) | def data
FILE: test/rails_test.rb
class RailsTest (line 3) | class RailsTest < Minitest::Test
FILE: test/sidekiq_test.rb
class SidekiqTest (line 3) | class SidekiqTest < Minitest::Test
FILE: test/test_helper.rb
function filter_params_setting (line 27) | def filter_params_setting(value, user_defined_params, &block)
function filter_params_regex_setting (line 35) | def filter_params_regex_setting(value, user_defined_params, &block)
Condensed preview — 117 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (172K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 68,
"preview": "# These are supported funding model platforms\n\ngithub: reidmorrison\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 1248,
"preview": "### Environment\n\nProvide at least:\n* Ruby Version.\n* Rails Version.\n* Semantic Logger Version.\n* Rails Semantic Logger V"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 173,
"preview": "### Issue # (if available)\n\n\n### Description of changes\n\n\nBy submitting this pull request, I confirm that my contributio"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1211,
"preview": "name: build\n\non:\n - push\n - pull_request\n\njobs:\n test:\n name: \"Test: Rails ${{ matrix.rails }} on Ruby ${{ matrix."
},
{
"path": ".gitignore",
"chars": 230,
"preview": ".bundle/\n*.log\npkg/\ntest/dummy/tmp/\ntest/dummy/db/test.sqlite3-*\ntest/dummy/.sass-cache\n*.gem\n/.idea\ntags\n*.DS_Store\nGem"
},
{
"path": ".rubocop.yml",
"chars": 2206,
"preview": "AllCops:\n Exclude:\n - \".git/**/*\"\n - \"docs/**/*\"\n - \"gemfiles/*\"\n NewCops: enable\n TargetRubyVersion: 2.5\n\n#"
},
{
"path": "Appraisals",
"chars": 899,
"preview": "appraise \"rails_6.1\" do\n gem \"rails\", \"~> 6.1.0\"\n gem \"sidekiq\", \"~> 5.2\"\n gem \"sqlite3\", \"~> 1.4\"\nend\n\nappraise \"rai"
},
{
"path": "Gemfile",
"chars": 271,
"preview": "source \"https://rubygems.org\"\n\ngemspec\n\ngem \"appraisal\"\ngem \"puma\"\n\ngem \"active_model_serializers\"\ngem \"amazing_print\"\ng"
},
{
"path": "LICENSE.txt",
"chars": 11361,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 7309,
"preview": "# Rails Semantic Logger\n[](https://rubygems.org/ge"
},
{
"path": "Rakefile",
"chars": 848,
"preview": "# Setup bundler to avoid having to run bundle exec all the time.\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nrequire \"ra"
},
{
"path": "TESTING.md",
"chars": 565,
"preview": "## Installation\n\nInstall all needed gems to run the tests:\n\n appraisal install\n\nThe gems are installed into the globa"
},
{
"path": "gemfiles/rails_6.1.gemfile",
"chars": 328,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.0.gemfile",
"chars": 330,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.0b.gemfile",
"chars": 328,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.1.1.gemfile",
"chars": 327,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.1.gemfile",
"chars": 330,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.1b.gemfile",
"chars": 330,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_7.2.gemfile",
"chars": 320,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_8.0.gemfile",
"chars": 320,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "gemfiles/rails_8.1.gemfile",
"chars": 320,
"preview": "# This file was generated by Appraisal\n\nsource \"https://rubygems.org\"\n\ngem \"appraisal\"\ngem \"puma\"\ngem \"active_model_seri"
},
{
"path": "lib/rails_semantic_logger/action_controller/log_subscriber.rb",
"chars": 5235,
"preview": "module RailsSemanticLogger\n module ActionController\n class LogSubscriber < ActiveSupport::LogSubscriber\n INTERN"
},
{
"path": "lib/rails_semantic_logger/action_mailer/log_subscriber.rb",
"chars": 3819,
"preview": "require \"active_support/log_subscriber\"\nrequire \"action_mailer\"\n\nmodule RailsSemanticLogger\n module ActionMailer\n cl"
},
{
"path": "lib/rails_semantic_logger/action_view/log_subscriber.rb",
"chars": 4795,
"preview": "require \"active_support/log_subscriber\"\n\nmodule RailsSemanticLogger\n module ActionView\n # Output Semantic logs from "
},
{
"path": "lib/rails_semantic_logger/active_job/log_subscriber.rb",
"chars": 4499,
"preview": "require \"active_job\"\n\nmodule RailsSemanticLogger\n module ActiveJob\n class LogSubscriber < ::ActiveSupport::LogSubscr"
},
{
"path": "lib/rails_semantic_logger/active_record/log_subscriber.rb",
"chars": 7294,
"preview": "module RailsSemanticLogger\n module ActiveRecord\n class LogSubscriber < ActiveSupport::LogSubscriber\n IGNORE_PAY"
},
{
"path": "lib/rails_semantic_logger/delayed_job/plugin.rb",
"chars": 232,
"preview": "module RailsSemanticLogger\n module DelayedJob\n class Plugin < Delayed::Plugin\n callbacks do |lifecycle|\n "
},
{
"path": "lib/rails_semantic_logger/engine.rb",
"chars": 12601,
"preview": "require \"rails\"\nrequire \"rails_semantic_logger/options\"\n\nmodule RailsSemanticLogger\n class Engine < ::Rails::Engine\n "
},
{
"path": "lib/rails_semantic_logger/extensions/action_cable/tagged_logger_proxy.rb",
"chars": 267,
"preview": "require \"action_cable/connection/tagged_logger_proxy\"\n\nmodule ActionCable\n module Connection\n class TaggedLoggerProx"
},
{
"path": "lib/rails_semantic_logger/extensions/action_controller/live.rb",
"chars": 225,
"preview": "# Log actual exceptions, not a string representation\nrequire \"action_controller\"\n\nmodule ActionController\n module Live\n"
},
{
"path": "lib/rails_semantic_logger/extensions/action_dispatch/debug_exceptions.rb",
"chars": 912,
"preview": "# Log actual exceptions, not a string representation\nrequire \"action_dispatch\"\n\nmodule ActionDispatch\n class DebugExcep"
},
{
"path": "lib/rails_semantic_logger/extensions/action_view/streaming_template_renderer.rb",
"chars": 333,
"preview": "# Log actual exceptions, not a string representation\nrequire \"action_view/renderer/streaming_template_renderer\"\n\nmodule "
},
{
"path": "lib/rails_semantic_logger/extensions/active_job/logging.rb",
"chars": 330,
"preview": "# Patch ActiveJob logger\nrequire \"active_job/logging\"\n\nmodule ActiveJob\n module Logging\n include SemanticLogger::Log"
},
{
"path": "lib/rails_semantic_logger/extensions/active_model_serializers/logging.rb",
"chars": 340,
"preview": "# Patch ActiveModelSerializers logger\nrequire \"active_model_serializers/logging\"\n\nmodule ActiveModelSerializers\n module"
},
{
"path": "lib/rails_semantic_logger/extensions/active_support/log_subscriber.rb",
"chars": 414,
"preview": "if ActiveSupport::VERSION::STRING == \"7.1.1\"\n require \"active_support/log_subscriber\"\n\n module ActiveSupport\n class"
},
{
"path": "lib/rails_semantic_logger/extensions/active_support/logger.rb",
"chars": 611,
"preview": "require \"active_support/logger\"\n\nmodule ActiveSupport\n # More hacks to try and stop Rails from being it's own worst ene"
},
{
"path": "lib/rails_semantic_logger/extensions/active_support/tagged_logging.rb",
"chars": 150,
"preview": "module ActiveSupport\n module TaggedLogging\n # Semantic Logger already does tagged logging\n def self.new(logger)\n "
},
{
"path": "lib/rails_semantic_logger/extensions/mongoid/config.rb",
"chars": 138,
"preview": "require \"mongoid/config\"\n\nmodule Mongoid\n module Config\n private\n\n # Remove log overrides\n def set_log_levels\n"
},
{
"path": "lib/rails_semantic_logger/extensions/rack/server.rb",
"chars": 212,
"preview": "module RailsSemanticLogger\n module Rack\n module Server\n def daemonize_app\n super\n SemanticLogger."
},
{
"path": "lib/rails_semantic_logger/extensions/rackup/server.rb",
"chars": 218,
"preview": "module RailsSemanticLogger\n module Rackup\n module Server\n def daemonize_app\n super\n SemanticLogge"
},
{
"path": "lib/rails_semantic_logger/extensions/rails/server.rb",
"chars": 413,
"preview": "# Patch the Rails::Server log_to_stdout so that it logs via SemanticLogger\nrequire \"rails\"\n\nmodule Rails\n class Server\n"
},
{
"path": "lib/rails_semantic_logger/extensions/sidekiq/sidekiq.rb",
"chars": 2547,
"preview": "# Sidekiq patches\nif Sidekiq::VERSION.to_i == 4\n require \"sidekiq/logging\"\n require \"sidekiq/middleware/server/logging"
},
{
"path": "lib/rails_semantic_logger/options.rb",
"chars": 5403,
"preview": "module RailsSemanticLogger\n # Options for controlling Rails Semantic Logger behavior\n #\n # * Convert Action Controlle"
},
{
"path": "lib/rails_semantic_logger/rack/logger.rb",
"chars": 3065,
"preview": "require \"active_support/core_ext/time/conversions\"\nrequire \"active_support/core_ext/object/blank\"\nrequire \"active_suppor"
},
{
"path": "lib/rails_semantic_logger/sidekiq/defaults.rb",
"chars": 1832,
"preview": "module RailsSemanticLogger\n module Sidekiq\n module Defaults\n # Prevent exception logging during standard error "
},
{
"path": "lib/rails_semantic_logger/sidekiq/job_logger.rb",
"chars": 2187,
"preview": "module RailsSemanticLogger\n module Sidekiq\n class JobLogger\n class << self\n attr_writer :perform_message"
},
{
"path": "lib/rails_semantic_logger/sidekiq/loggable.rb",
"chars": 178,
"preview": "module RailsSemanticLogger\n module Sidekiq\n module Loggable\n def included(base)\n super\n base.incl"
},
{
"path": "lib/rails_semantic_logger/version.rb",
"chars": 59,
"preview": "module RailsSemanticLogger\n VERSION = \"4.20.0\".freeze\nend\n"
},
{
"path": "lib/rails_semantic_logger.rb",
"chars": 2892,
"preview": "require \"semantic_logger\"\nrequire \"rails_semantic_logger/extensions/rails/server\" if defined?(Rails::Server)\nrequire \"ra"
},
{
"path": "rails_semantic_logger.gemspec",
"chars": 1178,
"preview": "$LOAD_PATH.push File.expand_path(\"lib\", __dir__)\n\n# Maintain your gem's version:\nrequire \"rails_semantic_logger/version\""
},
{
"path": "test/action_controller_test.rb",
"chars": 785,
"preview": "require_relative \"test_helper\"\n\nclass ActionControllerTest < Minitest::Test\n describe \"RailsSemanticLogger::ActionContr"
},
{
"path": "test/action_mailer_test.rb",
"chars": 3471,
"preview": "require_relative \"test_helper\"\n\nclass ActionMailerTest < Minitest::Test\n class MyMailer < ActionMailer::Base\n def so"
},
{
"path": "test/active_job_test.rb",
"chars": 9623,
"preview": "require_relative \"test_helper\"\n\nclass ActiveJobTest < Minitest::Test\n if defined?(ActiveJob)\n class MyJob < ActiveJo"
},
{
"path": "test/active_record_test.rb",
"chars": 9127,
"preview": "require_relative \"test_helper\"\n\nclass ActiveRecordTest < Minitest::Test\n describe \"ActiveRecord\" do\n # Rails 5 has a"
},
{
"path": "test/controllers/articles_controller_test.rb",
"chars": 5285,
"preview": "require_relative \"../test_helper\"\n\nclass ArticlesControllerTest < ActionDispatch::IntegrationTest\n describe ArticlesCon"
},
{
"path": "test/controllers/dashboard_controller_test.rb",
"chars": 1678,
"preview": "require_relative \"../test_helper\"\n\nclass DashboardControllerTest < ActionDispatch::IntegrationTest\n describe DashboardC"
},
{
"path": "test/controllers/welcome_controller_test.rb",
"chars": 268,
"preview": "require_relative \"../test_helper\"\n\nclass WelcomeControllerTest < ActionDispatch::IntegrationTest\n describe WelcomeContr"
},
{
"path": "test/dummy/README.rdoc",
"chars": 9208,
"preview": "== Welcome to Rails\n\nRails is a web-application framework that includes everything needed to create\ndatabase-backed web "
},
{
"path": "test/dummy/Rakefile",
"chars": 266,
"preview": "#!/usr/bin/env rake\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistran"
},
{
"path": "test/dummy/app/assets/javascripts/application.js",
"chars": 641,
"preview": "// This is a manifest file that'll be compiled into application.js, which will include all the files\n// listed below.\n//"
},
{
"path": "test/dummy/app/assets/javascripts/articles.js",
"chars": 147,
"preview": "// Place all the behaviors and hooks related to the matching controller here.\n// All this logic will automatically be av"
},
{
"path": "test/dummy/app/assets/stylesheets/application.css",
"chars": 545,
"preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
},
{
"path": "test/dummy/app/assets/stylesheets/articles.css",
"chars": 128,
"preview": "/*\n Place all the styles related to the matching controller here.\n They will automatically be included in application."
},
{
"path": "test/dummy/app/assets/stylesheets/welcome.css",
"chars": 128,
"preview": "/*\n Place all the styles related to the matching controller here.\n They will automatically be included in application."
},
{
"path": "test/dummy/app/controllers/application_controller.rb",
"chars": 97,
"preview": "class ApplicationController < ActionController::Base\n protect_from_forgery with: :exception\nend\n"
},
{
"path": "test/dummy/app/controllers/application_metal_controller.rb",
"chars": 305,
"preview": "class ApplicationMetalController < ActionController::Metal\n MODULES = [\n ActionController::Instrumentation,\n Abst"
},
{
"path": "test/dummy/app/controllers/articles_controller.rb",
"chars": 189,
"preview": "class ArticlesController < ApplicationController\n def new\n end\n\n def create\n render plain: params[:article].inspec"
},
{
"path": "test/dummy/app/controllers/dashboard_controller.rb",
"chars": 76,
"preview": "class DashboardController < ApplicationMetalController\n def show\n end\nend\n"
},
{
"path": "test/dummy/app/controllers/welcome_controller.rb",
"chars": 70,
"preview": "class WelcomeController < ApplicationController\n def index\n end\nend\n"
},
{
"path": "test/dummy/app/helpers/application_helper.rb",
"chars": 29,
"preview": "module ApplicationHelper\nend\n"
},
{
"path": "test/dummy/app/helpers/articles_helper.rb",
"chars": 26,
"preview": "module ArticlesHelper\nend\n"
},
{
"path": "test/dummy/app/helpers/welcome_helper.rb",
"chars": 25,
"preview": "module WelcomeHelper\nend\n"
},
{
"path": "test/dummy/app/jobs/bad_job.rb",
"chars": 141,
"preview": "class BadJob\n include Sidekiq::Worker\n\n sidekiq_options retry: false\n\n def perform\n raise ArgumentError, \"This is "
},
{
"path": "test/dummy/app/jobs/simple_job.rb",
"chars": 94,
"preview": "class SimpleJob\n include Sidekiq::Worker\n\n def perform\n \"SimpleJob is working\"\n end\nend\n"
},
{
"path": "test/dummy/app/mailers/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/app/models/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/app/models/sample.rb",
"chars": 38,
"preview": "class Sample < ActiveRecord::Base\nend\n"
},
{
"path": "test/dummy/app/views/articles/new.html.erb",
"chars": 286,
"preview": "<h1>New Article</h1>\n\n<%= form_for :article, url: articles_path do |f| %>\n <p>\n <%= f.label :title %><br>\n "
},
{
"path": "test/dummy/app/views/layouts/application.html.erb",
"chars": 232,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Dummy</title>\n <%= stylesheet_link_tag \"application\", :media => \"all\" %>\n <%"
},
{
"path": "test/dummy/app/views/welcome/index.html.erb",
"chars": 74,
"preview": "<h1>Welcome#index</h1>\n<p>Find me in app/views/welcome/index.html.erb</p>\n"
},
{
"path": "test/dummy/bin/bundle",
"chars": 125,
"preview": "#!/usr/bin/env ruby\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../Gemfile\", __dir__)\nload Gem.bin_path(\"bundler\", \"bund"
},
{
"path": "test/dummy/bin/puma",
"chars": 362,
"preview": "#!/usr/bin/env ruby\n#\n# This file was generated by Bundler.\n#\n# The application 'puma' is installed as part of a gem, an"
},
{
"path": "test/dummy/bin/rails",
"chars": 141,
"preview": "#!/usr/bin/env ruby\nAPP_PATH = File.expand_path(\"../config/application\", __dir__)\nrequire_relative \"../config/boot\"\nrequ"
},
{
"path": "test/dummy/bin/rake",
"chars": 90,
"preview": "#!/usr/bin/env ruby\nrequire_relative \"../config/boot\"\nrequire \"rake\"\nRake.application.run\n"
},
{
"path": "test/dummy/bin/setup",
"chars": 733,
"preview": "#!/usr/bin/env ruby\nrequire \"pathname\"\n\n# path to your application root.\nAPP_ROOT = Pathname.new File.expand_path(\"..\", "
},
{
"path": "test/dummy/config/application.rb",
"chars": 1782,
"preview": "require File.expand_path(\"boot\", __dir__)\n\nrequire \"rails/all\"\n\nBundler.require\n\nmodule Dummy\n class Application < Rail"
},
{
"path": "test/dummy/config/boot.rb",
"chars": 236,
"preview": "require \"rubygems\"\ngemfile = File.expand_path(\"../../../Gemfile\", __dir__)\n\nif File.exist?(gemfile)\n ENV[\"BUNDLE_GEMFIL"
},
{
"path": "test/dummy/config/database.yml",
"chars": 576,
"preview": "# SQLite version 3.x\n# gem install sqlite3\n#\n# Ensure the SQLite 3 gem is defined in your Gemfile\n# gem 'sqlite3'\n"
},
{
"path": "test/dummy/config/environment.rb",
"chars": 145,
"preview": "# Load the rails application\nrequire File.expand_path(\"application\", __dir__)\n\n# Initialize the rails application\nDummy:"
},
{
"path": "test/dummy/config/environments/development.rb",
"chars": 1737,
"preview": "Dummy::Application.configure do\n # Settings specified here will take precedence over those in config/application.rb.\n\n "
},
{
"path": "test/dummy/config/environments/production.rb",
"chars": 1105,
"preview": "Dummy::Application.configure do\n # Settings specified here will take precedence over those in config/application.rb\n\n "
},
{
"path": "test/dummy/config/environments/test.rb",
"chars": 1375,
"preview": "Dummy::Application.configure do\n # Settings specified here will take precedence over those in config/application.rb\n\n "
},
{
"path": "test/dummy/config/initializers/backtrace_silencers.rb",
"chars": 404,
"preview": "# Be sure to restart your server when you modify this file.\n\n# You can add backtrace silencers for libraries that you're"
},
{
"path": "test/dummy/config/initializers/inflections.rb",
"chars": 533,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new inflection rules using the following format\n# (al"
},
{
"path": "test/dummy/config/initializers/mime_types.rb",
"chars": 205,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Add new mime types for use in respond_to blocks:\n# Mime::"
},
{
"path": "test/dummy/config/initializers/payload_collector.rb",
"chars": 187,
"preview": "ActiveSupport::Notifications.subscribe \"process_action.action_controller\" do |*args|\n event = ActiveSupport::Notificati"
},
{
"path": "test/dummy/config/initializers/secret_token.rb",
"chars": 496,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Your secret key for verifying the integrity of signed coo"
},
{
"path": "test/dummy/config/initializers/session_store.rb",
"chars": 137,
"preview": "# Be sure to restart your server when you modify this file.\n\nRails.application.config.session_store :cookie_store, key: "
},
{
"path": "test/dummy/config/initializers/sidekiq.rb",
"chars": 283,
"preview": "# In tests we force Sidekiq into thinking it is running as a server,\n# so it creates a stdout logger. Remove it here:\nRa"
},
{
"path": "test/dummy/config/initializers/wrap_parameters.rb",
"chars": 517,
"preview": "# Be sure to restart your server when you modify this file.\n\n# This file contains settings for ActionController::ParamsW"
},
{
"path": "test/dummy/config/locales/en.yml",
"chars": 214,
"preview": "# Sample localization file for English. Add more files in this directory for other locales.\n# See https://github.com/sve"
},
{
"path": "test/dummy/config/routes.rb",
"chars": 154,
"preview": "Dummy::Application.routes.draw do\n get \"welcome/index\"\n\n resources :articles\n\n resource :dashboard, controller: :dash"
},
{
"path": "test/dummy/config/secrets.yml",
"chars": 964,
"preview": "# Be sure to restart your server when you modify this file.\n\n# Your secret key is used for verifying the integrity of si"
},
{
"path": "test/dummy/config.ru",
"chars": 148,
"preview": "# This file is used by Rack-based servers to start the application.\n\nrequire File.expand_path(\"config/environment\", __di"
},
{
"path": "test/dummy/db/migrate/20170525020551_create_samples.rb",
"chars": 205,
"preview": "class CreateSamples < ActiveRecord::Migration\n def change\n create_table :samples do |t|\n t.string :name\n t"
},
{
"path": "test/dummy/db/schema.rb",
"chars": 991,
"preview": "# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the mig"
},
{
"path": "test/dummy/lib/assets/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/log/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "test/dummy/public/404.html",
"chars": 728,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>The page you were looking for doesn't exist (404)</title>\n <style type=\"text/css"
},
{
"path": "test/dummy/public/422.html",
"chars": 711,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>The change you wanted was rejected (422)</title>\n <style type=\"text/css\">\n bo"
},
{
"path": "test/dummy/public/500.html",
"chars": 643,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>We're sorry, but something went wrong (500)</title>\n <style type=\"text/css\">\n "
},
{
"path": "test/dummy/script/rails",
"chars": 285,
"preview": "#!/usr/bin/env ruby\n# This command will automatically be run when you run \"rails\" with Rails 3 gems installed from the r"
},
{
"path": "test/dummy/test/fixtures/samples.yml",
"chars": 197,
"preview": "# Read about fixtures at\n# http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html\n\none:\n name: MyString\n age:"
},
{
"path": "test/payload_collector.rb",
"chars": 336,
"preview": "class PayloadCollector\n class << self\n def wrap\n @store = true\n yield\n ensure\n @store = false\n "
},
{
"path": "test/rails_test.rb",
"chars": 585,
"preview": "require_relative \"test_helper\"\n\nclass RailsTest < Minitest::Test\n describe \"Rails\" do\n describe \".logger\" do\n i"
},
{
"path": "test/sidekiq_test.rb",
"chars": 4246,
"preview": "require_relative \"test_helper\"\n\nclass SidekiqTest < Minitest::Test\n # Cannot use inline testing since it bypasses the S"
},
{
"path": "test/test_helper.rb",
"chars": 1529,
"preview": "ENV[\"RAILS_ENV\"] ||= \"test\"\nENV[\"DISABLE_DATABASE_ENVIRONMENT_CHECK\"] = \"1\"\n# Load first so Sidekiq thinks it is running"
}
]
About this extraction
This page contains the full source code of the rocketjob/rails_semantic_logger GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 117 files (154.1 KB), approximately 40.8k tokens, and a symbol index with 248 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.