logcraft 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cebda0be923bafa1e3409178e92baa0c0161540b94655c1ccf0cc756ff7dbc2f
4
+ data.tar.gz: 35bcecda73c4bca768c4178fa5a0f1fe478d142b93f91f63ef8c8835dbc7d3f0
5
+ SHA512:
6
+ metadata.gz: 4ee4ea6eafd4e3b4ca5ff71b542baf38a58685ae639e083ece215a7e3f27d8c4b4fd08569f07e62d96602a5f37d7495478be90b7844adfa28b8caf3c6df7c829
7
+ data.tar.gz: 16f1d50ab12098b2e2ab341b4ff659f0504ff8282e8b78fe8cfe8a1ab8d8413e6d413b1ac4912e9e3af76080d7cc7407174a1b9a713b1922785180b562911f6a
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.0.rc] - 2022-06-26
8
+ ### Added
9
+ - Logcraft was rewritten from the ground up, based on its predecessor: [Ezlog](https://github.com/emartech/ezlog)
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement via https://github.com/zormandi/logcraft. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in logcraft.gemspec
6
+ gemspec
7
+
8
+ group :test do
9
+ gem 'rails', '~> 6.1'
10
+ gem 'rspec-rails', '~> 4.0'
11
+ gem 'sqlite3', '~> 1.4'
12
+ gem 'net-smtp', require: false
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Zoltan Ormandi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,331 @@
1
+ # Logcraft
2
+
3
+ [![Build Status](https://github.com/zormandi/logcraft/actions/workflows/main.yml/badge.svg)](https://github.com/zormandi/logcraft/actions/workflows/main.yml)
4
+
5
+ Logcraft is a zero-configuration structured logging library for pure Ruby or [Ruby on Rails](https://rubyonrails.org/) applications.
6
+ It is the successor to [Ezlog](https://github.com/emartech/ezlog) with which it shares its ideals but is reimagined and
7
+ reimplemented to be more versatile and much more thoroughly tested.
8
+
9
+ Logcraft's purpose is threefold:
10
+ 1. Make sure that our applications are logging in a concise and sensible manner; emitting no unnecessary "noise" but
11
+ containing all relevant and necessary information (like timing or a request ID).
12
+ 2. Make sure that all log messages are written to STDOUT in a machine-processable format (JSON).
13
+ 3. Achieving the above goals should require no configuration in the projects where the library is used.
14
+
15
+ Logcraft supports:
16
+ * [Ruby](https://www.ruby-lang.org) 2.6 and up (tested with 2.6, 2.7, 3.0 and 3.1)
17
+ * [Rails](https://rubyonrails.org/) 5 and up (tested with 5.2, 6.0 and 6.1)
18
+ * [Sidekiq](https://github.com/mperham/sidekiq) support is coming soon via a separate gem (_logcraft-sidekiq_)
19
+
20
+ Logcraft uses Tim Pease's wonderful [Logging](https://github.com/TwP/logging) gem under the hood for an all-purpose structured logging solution.
21
+
22
+ ## Installation
23
+
24
+ ### Rails
25
+
26
+ Add this line to your application's Gemfile:
27
+ ```ruby
28
+ gem 'logcraft'
29
+ ```
30
+
31
+ Although Logcraft sets up sensible defaults for all logging configuration settings, it leaves you the option to override these
32
+ settings manually in the way you're used to; via Rails's configuration mechanism. Unfortunately the Rails new project generator
33
+ automatically generates code for the production environment configuration that overrides some of these default settings.
34
+
35
+ For Logcraft to work properly, you need to delete or comment out the logging configuration options in the generated
36
+ `config/environments/production.rb` file.
37
+
38
+ ### Non-Rails applications
39
+
40
+ Add this line to your application's Gemfile:
41
+ ```ruby
42
+ gem 'logcraft'
43
+ ```
44
+ and call
45
+ ```ruby
46
+ Logcraft.initialize
47
+ ```
48
+ any time during your application's startup.
49
+
50
+ ## Usage
51
+
52
+ ### Structured logging
53
+
54
+ Any loggers created by your application (including the `Rails.logger`) will automatically be configured to write
55
+ messages in JSON format to the standard output. These loggers can handle a variety of message types:
56
+
57
+ * String
58
+ * Hash
59
+ * Exception
60
+ * any other object that can be coerced into a String
61
+
62
+ The logger also automatically adds some basic information to all messages, such as:
63
+ * name of the logger
64
+ * timestamp
65
+ * log level (as string)
66
+ * hostname
67
+ * PID
68
+
69
+ Examples:
70
+ ```ruby
71
+ logger = Logcraft.logger 'Application'
72
+
73
+ logger.info 'Log message'
74
+ # => {"timestamp":"2022-06-26T17:52:57.845+02:00","level":"INFO","logger":"Application","hostname":"Zoltans-iPro","pid":80422,"message":"Log message"}
75
+
76
+ logger.info message: 'User logged in', user_id: 42
77
+ # => {"timestamp":"2022-06-26T17:44:01.926+02:00","level":"INFO","logger":"Application","hostname":"MacbookPro.local","pid":80422,"message":"User logged in","user_id":42}
78
+
79
+ logger.warn error
80
+ # Formatted for better readability (the original is a single line string):
81
+ # => {
82
+ # "timestamp": "2022-06-26T17:46:42.418+02:00",
83
+ # "level": "WARN",
84
+ # "logger": "Application",
85
+ # "hostname": "MacbookPro.local",
86
+ # "pid": 80422,
87
+ # "message": "wrapping error",
88
+ # "error": {
89
+ # "class": "StandardError",
90
+ # "message": "wrapping error",
91
+ # "backtrace": [...],
92
+ # "cause": {
93
+ # "class": "RuntimeError",
94
+ # "message": "original error",
95
+ # "backtrace": [...]
96
+ # }
97
+ # }
98
+ # }
99
+ ```
100
+
101
+ #### Adding context information to log messages
102
+
103
+ Logcraft provides two helper methods which can be used to add context information to log messages:
104
+
105
+ * `within_log_context(context)`: Starts a new log context initialized with `context` and executes the provided block
106
+ within that context. Once execution is finished, the log context is cleaned up and the previous context (if any) is
107
+ reinstated. In practice, this means that every time we log something (within the block), the log message will include
108
+ the information that's in the current context. This can be useful for storing request-specific information
109
+ (request ID, user ID, ...) in the log context early on (for example in a middleware) and not have to worry about
110
+ including it every time we want to log a message.
111
+
112
+ Example:
113
+
114
+ ```ruby
115
+ within_log_context customer_id: 1234 do
116
+ logger.info 'test 1'
117
+ end
118
+ logger.info 'test 2'
119
+
120
+ #=> {...,"level":"INFO","customer_id":1234,"message":"test 1"}
121
+ #=> {...,"level":"INFO","message":"test 2"}
122
+ ```
123
+
124
+ * `add_to_log_context(context)`: Adds the provided `context` to the current log context but provides no mechanism for
125
+ removing it later. Only use this method if you are sure that you're working within a specific log context and that it
126
+ will be cleaned up later (e.g. by only using this method in a block passed to the previously explained
127
+ `within_log_context` method).
128
+
129
+ You can access these methods either in the global scope by calling them via `Logcraft.within_log_context` and
130
+ `Logcraft.add_to_log_context` or locally by including the `Logcraft::LogContextHelper` module into your class/module.
131
+
132
+ ### Rails logging
133
+
134
+ Logcraft automatically configures Rails to provide you with structured logging capability via the `Rails.logger`.
135
+ It also changes Rails's default logging configuration to be more concise and emit less "noise".
136
+
137
+ In more detail:
138
+ * The `Rails.logger` is set up to be a Logcraft logger with the name `Application`.
139
+ * Rails's default logging of uncaught errors is modified and instead of spreading the error message across several lines,
140
+ Logcraft log every uncaught error in 1 line (per error), including the error's name and context (stack trace, etc.).
141
+ * Most importantly, Rails's default request logging - which logs several lines per event during the processing of an action -
142
+ is replaced by Logcraft's own access log middleware. The end result is an access log that
143
+ * contains all relevant information (request ID, method, path, params, client IP, duration and response status code), and
144
+ * has 1 log line per request, logged at the end of the request.
145
+
146
+ Thanks to Mathias Meyer for writing [Lograge](https://github.com/roidrage/lograge), which inspired the solution.
147
+ If Logcraft is not your cup of tea but you're looking for a way to tame Rails's logging then be sure to check out
148
+ [Lograge](https://github.com/roidrage/lograge).
149
+
150
+ ```
151
+ GET /welcome?subsession_id=34ea8596f9764f475f81158667bc2654
152
+
153
+ With default Rails logging:
154
+
155
+ Started GET "/welcome?subsession_id=34ea8596f9764f475f81158667bc2654" for 127.0.0.1 at 2022-06-26 18:07:08 +0200
156
+ Processing by PagesController#welcome as HTML
157
+ Parameters: {"subsession_id"=>"34ea8596f9764f475f81158667bc2654"}
158
+ Rendering pages/welcome.html.haml within layouts/application
159
+ Rendered pages/welcome.html.haml within layouts/application (5.5ms)
160
+ Completed 200 OK in 31ms (Views: 27.3ms | ActiveRecord: 0.0ms)
161
+
162
+ With Logcraft:
163
+ {"timestamp":"2022-06-26T18:07:08.103+02:00","level":"INFO","logger":"AccessLog","hostname":"MacbookPro.local","pid":80908,"request_id":"9a43631b-284c-4677-9d08-9c1cc5c7d3a7","message":"GET /welcome?subsession_id=34ea8596f9764f475f81158667bc2654 - 200 (OK)","remote_ip":"127.0.0.1","method":"GET","path":"/welcome?subsession_id=34ea8596f9764f475f81158667bc2654","params":{"subsession_id":"34ea8596f9764f475f81158667bc2654","controller":"pages","action":"welcome"},"response_status_code":200,"duration":13,"duration_sec":0.013}
164
+
165
+ Formatted for readability:
166
+ {
167
+ "timestamp": "2022-06-26T18:07:08.103+02:00",
168
+ "level": "INFO",
169
+ "logger": "AccessLog",
170
+ "hostname": "MacbookPro.local",
171
+ "pid": 80908, "request_id": "9a43631b-284c-4677-9d08-9c1cc5c7d3a7",
172
+ "message": "GET /welcome?subsession_id=34ea8596f9764f475f81158667bc2654 - 200 (OK)",
173
+ "remote_ip": "127.0.0.1",
174
+ "method": "GET",
175
+ "path": "/welcome?subsession_id=34ea8596f9764f475f81158667bc2654",
176
+ "params": {
177
+ "subsession_id": "34ea8596f9764f475f81158667bc2654",
178
+ "controller": "pages",
179
+ "action": "welcome"
180
+ },
181
+ "response_status_code": 200,
182
+ "duration": 13,
183
+ "duration_sec": 0.013
184
+ }
185
+ ```
186
+
187
+ By default, Logcraft logs all request parameters as a hash (JSON object) under the `params` key. This is very convenient
188
+ in a structured logging system and makes it easy to search for specific request parameter values e.g. in ElasticSearch
189
+ (should you happen to store your logs there). Unfortunately, in some cases - such as when handling large forms - this
190
+ can create quite a bit of noise and impact the searchability of your logs negatively or pose a security risk or data policy
191
+ violation. You have the option to restrict the logging of certain parameters via configuration options (see the
192
+ Configuration section).
193
+
194
+ #### The log level
195
+
196
+ The logger's log level is determined as follows (in order of precedence):
197
+ * the log level set in the application's configuration (for Rails applications),
198
+ * the LOG_LEVEL environment variable, or
199
+ * `INFO` as the default log level if none of the above are set.
200
+
201
+ The following log levels are available: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`.
202
+
203
+ ## Configuration options
204
+
205
+ ### Rails
206
+
207
+ Logcraft provides the following configuration options for Rails:
208
+
209
+ | Option | Default value | Description |
210
+ |-------------------------------------------------|--------------------------|-------------------------------------------------------------------------------------------------------------|
211
+ | logcraft.initial_context | `{}` | A global log context that will be included in every log message. May include lambdas (see examples). |
212
+ | logcraft.layout_options | `{}` | Custom options for the log layout. Currently only the `level_formatter` option is supported (see examples). |
213
+ | logcraft.access_log.logger_name | `'AccessLog'` | The name of the logger emitting access log messages. |
214
+ | logcraft.access_log.exclude_paths | `[]` | A list of paths (array of strings or RegExps) not to include in the access log. |
215
+ | logcraft.access_log.log_only_whitelisted_params | `false` | If `true`, the access log will only contain whitelisted parameters. |
216
+ | logcraft.access_log.whitelisted_params | `[:controller, :action]` | The only parameters to be logged in the access log if whitelisting is enabled. |
217
+
218
+ Examples:
219
+ ```ruby
220
+ # Use these options in your Rails configuration files (e.g. application.rb)
221
+
222
+ # Set up a global context you want to see in every log message
223
+ config.logcraft.initial_context = {
224
+ environment: ENV['RAILS_ENV'],
225
+ timestamp_linux: -> { Time.current.to_i } # evaluated when emitting a log message
226
+ }
227
+
228
+ # Set up a custom log level formatter (e.g. Ougai-like numbers)
229
+ config.logcraft.layout_options = {
230
+ level_formatter: ->(level_number) { (level_number + 2) * 10 }
231
+ }
232
+ Rails.logger.error('Boom!')
233
+ # => {...,"level":50,"message":"Boom!"}
234
+
235
+ # Exclude healthcheck and monitoring URLs from your access log:
236
+ config.logcraft.exclude_paths = ['/healthcheck', %r(/monitoring/.*)]
237
+
238
+ # Make sure no sensitive data is logged by accident in the access log, so only log controller and action:
239
+ config.logcraft.log_only_whitelisted_params = true
240
+ ```
241
+
242
+ ### Non-Rails
243
+
244
+ The `initial_context` and `layout_options` configuration options (see above) are available to non-Rails projects
245
+ via Logcraft's initialization mechanism. You can also set the default log level this way.
246
+
247
+ ```ruby
248
+ Logcraft.initialize log_level: :info, initial_context: {}, layout_options: {}
249
+ ```
250
+
251
+ ## Integration with DataDog
252
+
253
+ You can set up tracing with [DataDog](https://www.datadoghq.com/) by providing an initial context to be included in every log message:
254
+
255
+ ```ruby
256
+ config.logcraft.initial_context = {
257
+ dd: -> do
258
+ return unless Datadog::Tracing.enabled?
259
+
260
+ correlation = Datadog::Tracing.correlation
261
+ {
262
+ trace_id: correlation.trace_id.to_s,
263
+ span_id: correlation.span_id.to_s,
264
+ env: correlation.env.to_s,
265
+ service: correlation.service.to_s,
266
+ version: correlation.version.to_s
267
+ }
268
+ end,
269
+ ddsource: ['ruby']
270
+ }
271
+ ```
272
+
273
+ ## RSpec support
274
+
275
+ Logcraft comes with built-in support for testing your logging activity using [RSpec](https://rspec.info/).
276
+ To enable spec support for Logcraft, put this line in your `spec_helper.rb` or `rails_helper.rb`:
277
+
278
+ ```ruby
279
+ require 'logcraft/rspec'
280
+ ```
281
+
282
+ What you get:
283
+ * Helpers
284
+ * `log_output` provides access to the complete log output (array of strings) in your specs
285
+ * `log_output_is_expected` shorthand for writing expectations for the log output
286
+ * Matchers
287
+ * `include_log_message` matcher for expecting a certain message in the log output
288
+ * `log` matcher for expecting an operation to log a certain message
289
+
290
+ ```ruby
291
+ # Check that the log output contains a certain message
292
+ expect(log_output).to include_log_message message: 'Test message'
293
+ log_output_is_expected.to include_log_message message: 'Test message'
294
+
295
+ # Check that the message is not present in the logs before the operation but is present after it
296
+ expect { operation }.to log message: 'Test message',
297
+ user_id: 123456
298
+
299
+ # Expect a certain log level
300
+ log_output_is_expected.to include_log_message(message: 'Test message').at_level(:info)
301
+ expect { operation }.to log(message: 'Test message').at_level(:info)
302
+ ```
303
+
304
+ ## Development
305
+
306
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
307
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
308
+
309
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
310
+ update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag
311
+ for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
312
+
313
+ ## Contributing
314
+
315
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zormandi/logcraft. This project is intended
316
+ to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
317
+ [code of conduct](https://github.com/zormandi/logcraft/blob/master/CODE_OF_CONDUCT.md).
318
+
319
+ ## Disclaimer
320
+
321
+ Logcraft is highly opinionated software and does in no way aim or claim to be useful for everyone.
322
+ Use at your own discretion.
323
+
324
+ ## License
325
+
326
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
327
+
328
+ ## Code of Conduct
329
+
330
+ Everyone interacting in the Logcraft project's codebases, issue trackers, chat rooms and mailing lists is expected
331
+ to follow the [code of conduct](https://github.com/zormandi/logcraft/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ def add_logcraft_options_to_application_configuration
7
+ project_root = File.dirname __FILE__
8
+ Dir.chdir(project_root + '/spec') do
9
+ app_config = File.readlines 'test-app/config/application.rb'
10
+ modified_config = app_config.each_with_object([]) do |line, config|
11
+ config << line
12
+ if line.include? 'config.load_defaults'
13
+ logcraft_config = File.readlines 'fixtures/test-app/config/logcraft_config.rb'
14
+ config.concat logcraft_config
15
+ end
16
+ end
17
+ File.write 'test-app/config/application.rb', modified_config.join
18
+ end
19
+ end
20
+
21
+ desc 'Generate sample Rails app for acceptance testing'
22
+ task :generate_rails_app do
23
+ project_root = File.dirname __FILE__
24
+ Dir.chdir(project_root + '/spec') do
25
+ FileUtils.rm_rf 'test-app'
26
+ system 'rails new test-app --database=sqlite3 --skip-gemfile --skip-git --skip-keeps --skip-action-mailer'\
27
+ '--skip-action-mailbox --skip-action-text --skip-active-job --skip-active-storage --skip-puma --skip-action-cable'\
28
+ '--skip-sprockets --skip-spring --skip-listen --skip-javascript --skip-turbolinks --skip-jbuilder --skip-test'\
29
+ '--skip-system-test --skip-bootsnap --skip-bundle --skip-webpack-install'
30
+ FileUtils.cp_r 'fixtures/test-app/.', 'test-app', remove_destination: true
31
+ add_logcraft_options_to_application_configuration
32
+ end
33
+ end
34
+
35
+ namespace :spec do
36
+ desc 'Run RSpec unit tests'
37
+ RSpec::Core::RakeTask.new(:unit) do |t|
38
+ t.exclude_pattern = 'spec/integration/*_spec.rb'
39
+ end
40
+
41
+ desc 'Run RSpec integration tests'
42
+ RSpec::Core::RakeTask.new(:integration) do |t|
43
+ t.pattern = 'spec/integration/*_spec.rb'
44
+ end
45
+ end
46
+
47
+ desc 'Run all RSpec examples'
48
+ RSpec::Core::RakeTask.new(:spec)
49
+
50
+ task default: :spec
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in logcraft.gemspec
6
+ gemspec path: '..'
7
+
8
+ group :test do
9
+ gem 'rails', '~> 5.2.0'
10
+ gem 'rspec-rails', '~> 4.0'
11
+ gem 'sqlite3', '~> 1.4'
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in logcraft.gemspec
6
+ gemspec path: '..'
7
+
8
+ group :test do
9
+ gem 'rails', '~> 6.0.0'
10
+ gem 'rspec-rails', '~> 4.0'
11
+ gem 'sqlite3', '~> 1.4'
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in logcraft.gemspec
6
+ gemspec path: '..'
7
+
8
+ group :test do
9
+ gem 'rails', '~> 6.1.0'
10
+ gem 'rspec-rails', '~> 4.0'
11
+ gem 'sqlite3', '~> 1.4'
12
+ gem 'net-smtp', require: false
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ module LogContextHelper
5
+ def within_log_context(context = {})
6
+ Logging.mdc.push context
7
+ yield
8
+ ensure
9
+ Logging.mdc.pop
10
+ end
11
+
12
+ def add_to_log_context(context)
13
+ Logging.mdc.update context
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Logcraft
6
+ class LogLayout < Logging::Layout
7
+ def initialize(context = {}, options = {})
8
+ @general_context = context
9
+ @level_formatter = options.fetch :level_formatter, ->(level) { Logging::LNAMES[level] }
10
+ end
11
+
12
+ def format(event)
13
+ log_entry = background_of(event).merge evaluated_general_context,
14
+ dynamic_log_context,
15
+ message_from(event.data)
16
+ MultiJson.dump(log_entry) + "\n"
17
+ end
18
+
19
+ private
20
+
21
+ def background_of(event)
22
+ {
23
+ 'timestamp' => event.time.iso8601(3),
24
+ 'level' => @level_formatter.call(event.level),
25
+ 'logger' => event.logger,
26
+ 'hostname' => Socket.gethostname,
27
+ 'pid' => Process.pid
28
+ }
29
+ end
30
+
31
+ def evaluated_general_context
32
+ @general_context.transform_values { |v| v.is_a?(Proc) ? v.call : v }
33
+ end
34
+
35
+ def dynamic_log_context
36
+ Logging.mdc.context
37
+ end
38
+
39
+ def message_from(payload)
40
+ case payload
41
+ when Hash
42
+ format_hash payload
43
+ when Exception
44
+ {'message' => payload.message, 'error' => format_exception(payload)}
45
+ else
46
+ {'message' => payload}
47
+ end
48
+ end
49
+
50
+ def format_hash(hash)
51
+ hash.transform_values { |v| v.is_a?(Exception) ? format_exception(v) : v }
52
+ end
53
+
54
+ def format_exception(exception)
55
+ error_hash = {'class' => exception.class.name,
56
+ 'message' => exception.message}
57
+ error_hash['backtrace'] = exception.backtrace.first(20) if exception.backtrace
58
+ error_hash['cause'] = format_cause(exception.cause) if exception.cause
59
+ error_hash
60
+ end
61
+
62
+ def format_cause(cause)
63
+ cause = cause.cause while cause.cause
64
+ format_exception cause
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ module Rails
5
+ module ActiveRecord
6
+ class LogSubscriber < ActiveSupport::LogSubscriber
7
+ def sql(event)
8
+ ::ActiveRecord::Base.logger.debug { log_message_from(event) }
9
+ end
10
+
11
+ private
12
+
13
+ def log_message_from(event)
14
+ basic_message_from(event).tap do |message|
15
+ params = params_from event
16
+ message[:params] = params if params.any?
17
+ end
18
+ end
19
+
20
+ def basic_message_from(event)
21
+ {
22
+ message: "SQL - #{event.payload[:name] || 'Query'} (#{event.duration.round(3)}ms)",
23
+ sql: event.payload[:sql],
24
+ duration: event.duration,
25
+ duration_sec: (event.duration / 1000.0).round(5)
26
+ }
27
+ end
28
+
29
+ def params_from(event)
30
+ return {} if event.payload.fetch(:binds, []).empty?
31
+
32
+ params = event.payload[:binds]
33
+ values = type_casted_values_from event
34
+ param_value_pairs = params.zip(values).map do |param, value|
35
+ [param.name, value_of(param, value)]
36
+ end
37
+
38
+ Hash[param_value_pairs]
39
+ rescue NoMethodError
40
+ params
41
+ end
42
+
43
+ def type_casted_values_from(event)
44
+ binds = event.payload[:type_casted_binds]
45
+ binds.respond_to?(:call) ? binds.call : binds
46
+ end
47
+
48
+ def value_of(param, value)
49
+ param.type.binary? ? '-binary data-' : value
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ module Rails
5
+ module ActiveRecord
6
+ autoload :LogSubscriber, 'logcraft/rails/active_record/log_subscriber'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module ActionDispatch
2
+ class DebugExceptions
3
+ def log_error_with_logcraft(request, wrapper)
4
+ logger = logger(request)
5
+ exception = wrapper.exception
6
+ logger.fatal exception
7
+ end
8
+
9
+ alias_method :original_log_error, :log_error
10
+ alias_method :log_error, :log_error_with_logcraft
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/version'
4
+
5
+ module Logcraft
6
+ module Rails
7
+ class LogSubscriptionHandler
8
+ class << self
9
+ def detach(subscriber_class, namespace)
10
+ case ::Rails::VERSION::MAJOR
11
+ when 5
12
+ subscriber = ::ActiveSupport::LogSubscriber.log_subscribers.find { |subscriber| subscriber.is_a? subscriber_class }
13
+ return unless subscriber
14
+
15
+ subscriber.patterns.each do |pattern|
16
+ ::ActiveSupport::Notifications.notifier.listeners_for(pattern).each do |listener|
17
+ ::ActiveSupport::Notifications.unsubscribe listener if listener.instance_variable_get('@delegate').is_a? subscriber_class
18
+ end
19
+ end
20
+
21
+ else
22
+ subscriber_class.detach_from namespace
23
+ end
24
+ end
25
+
26
+ def attach(subscriber_class, namespace)
27
+ subscriber_class.attach_to namespace
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ module Rails
5
+ class RequestIdLogger
6
+ include LogContextHelper
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ within_log_context request_id: env['action_dispatch.request_id'] do
14
+ @app.call env
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ module Rails
5
+ class RequestLogger
6
+ def initialize(app, logger, config)
7
+ @app = app
8
+ @logger = logger
9
+ @config = config
10
+ end
11
+
12
+ def call(env)
13
+ start_time = current_time_in_milliseconds
14
+ request = ActionDispatch::Request.new env
15
+
16
+ instrumentation_start request
17
+
18
+ status, headers, body = @app.call env
19
+ body = ::Rack::BodyProxy.new(body) do
20
+ instrumentation_finish request
21
+ log_request request, status, start_time
22
+ end
23
+
24
+ [status, headers, body]
25
+ rescue Exception => ex
26
+ instrumentation_finish request
27
+ log_request request, status_for_error(ex), start_time
28
+ raise
29
+ ensure
30
+ ActiveSupport::LogSubscriber.flush_all!
31
+ end
32
+
33
+ private
34
+
35
+ def current_time_in_milliseconds
36
+ Process.clock_gettime Process::CLOCK_MONOTONIC, :millisecond
37
+ end
38
+
39
+ def instrumentation_start(request)
40
+ instrumenter = ActiveSupport::Notifications.instrumenter
41
+ instrumenter.start 'request.action_dispatch', request: request
42
+ end
43
+
44
+ def instrumentation_finish(request)
45
+ instrumenter = ActiveSupport::Notifications.instrumenter
46
+ instrumenter.finish 'request.action_dispatch', request: request
47
+ end
48
+
49
+ def log_request(request, status, start_time)
50
+ return if path_ignored? request
51
+
52
+ end_time = current_time_in_milliseconds
53
+ message = {
54
+ message: '%s %s - %i (%s)' % [request.method, request.filtered_path, status, Rack::Utils::HTTP_STATUS_CODES[status]],
55
+ remote_ip: request.remote_ip,
56
+ method: request.method,
57
+ path: request.filtered_path,
58
+ params: params_to_log(request),
59
+ response_status_code: status,
60
+ duration: end_time - start_time,
61
+ duration_sec: (end_time - start_time) / 1000.0
62
+ }
63
+ @logger.info message
64
+ end
65
+
66
+ def path_ignored?(request)
67
+ @config.exclude_paths.any? do |pattern|
68
+ case pattern
69
+ when Regexp
70
+ pattern.match? request.path
71
+ else
72
+ pattern == request.path
73
+ end
74
+ end
75
+ end
76
+
77
+ def params_to_log(request)
78
+ if @config.log_only_whitelisted_params
79
+ request.filtered_parameters.slice *@config.whitelisted_params&.map(&:to_s)
80
+ else
81
+ request.filtered_parameters
82
+ end
83
+ end
84
+
85
+ def status_for_error(error)
86
+ ActionDispatch::ExceptionWrapper.status_code_for_exception error.class.name
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_controller'
4
+ require 'action_controller/log_subscriber'
5
+
6
+ module Logcraft
7
+ module Rails
8
+ autoload :ActiveRecord, 'logcraft/rails/active_record'
9
+ autoload :RequestIdLogger, 'logcraft/rails/request_id_logger'
10
+ autoload :RequestLogger, 'logcraft/rails/request_logger'
11
+ autoload :LogSubscriptionHandler, 'logcraft/rails/log_subscription_handler'
12
+ end
13
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module Logcraft
6
+ class Railtie < ::Rails::Railtie
7
+ config.logcraft = ActiveSupport::OrderedOptions.new
8
+ config.logcraft.initial_context = {}
9
+ config.logcraft.layout_options = {}
10
+
11
+ config.logcraft.access_log = ActiveSupport::OrderedOptions.new
12
+ config.logcraft.access_log.logger_name = 'AccessLog'
13
+ config.logcraft.access_log.exclude_paths = []
14
+ config.logcraft.access_log.log_only_whitelisted_params = false
15
+ config.logcraft.access_log.whitelisted_params = [:controller, :action]
16
+
17
+ initializer 'logcraft.initialize' do |app|
18
+ Logcraft.initialize log_level: app.config.log_level,
19
+ initial_context: app.config.logcraft.initial_context,
20
+ layout_options: app.config.logcraft.layout_options
21
+ end
22
+
23
+ initializer 'logcraft.configure_rails' do |app|
24
+ require 'logcraft/rails/extensions'
25
+ app.config.middleware.insert_before ::Rails::Rack::Logger,
26
+ Logcraft::Rails::RequestLogger,
27
+ Logcraft.logger(config.logcraft.access_log.logger_name),
28
+ config.logcraft.access_log
29
+ app.config.middleware.delete ::Rails::Rack::Logger
30
+ app.config.middleware.insert_after ::ActionDispatch::RequestId, Logcraft::Rails::RequestIdLogger
31
+ end
32
+
33
+ config.after_initialize do
34
+ Logcraft::Rails::LogSubscriptionHandler.detach ::ActionController::LogSubscriber, :action_controller
35
+ require 'action_view/log_subscriber' unless defined? ::ActionView::LogSubscriber
36
+ Logcraft::Rails::LogSubscriptionHandler.detach ::ActionView::LogSubscriber, :action_view
37
+ if defined? ::ActiveRecord
38
+ Logcraft::Rails::LogSubscriptionHandler.detach ::ActiveRecord::LogSubscriber, :active_record
39
+ Logcraft::Rails::LogSubscriptionHandler.attach Logcraft::Rails::ActiveRecord::LogSubscriber, :active_record
40
+ end
41
+ end
42
+
43
+ config.before_configuration do |app|
44
+ app.config.logger = Logcraft.logger 'Application'
45
+ app.config.log_level = ENV['LOG_LEVEL'] || :info
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ezlog
4
+ module RSpec
5
+ module Helpers
6
+ def log_output_is_expected
7
+ expect(log_output)
8
+ end
9
+
10
+ def log_output
11
+ @log_output.clone.readlines
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ RSpec::Matchers.define :include_log_message do |expected|
6
+ chain :at_level, :log_level
7
+
8
+ match do |actual|
9
+ actual.any? { |log_line| includes? log_line, expected_messages_from(expected) }
10
+ end
11
+
12
+ def includes?(log_line, messages)
13
+ return false unless includes_log_level? log_line
14
+ messages.all? { |message| log_line.include? message }
15
+ end
16
+
17
+ def includes_log_level?(log_line)
18
+ return true if log_level.nil?
19
+ log_line.include? log_level_string(log_level)
20
+ end
21
+
22
+ def log_level_string(log_level)
23
+ return 'WARN' if log_level == :warning
24
+ log_level.to_s.upcase
25
+ end
26
+
27
+ def expected_messages_from(object)
28
+ @expected_messages ||= case object
29
+ when Hash
30
+ object.map { |k, v| JSON.dump(k => v)[1...-1] }
31
+ when String
32
+ [object]
33
+ else
34
+ raise NotImplementedError, 'log expectation must be Hash or String'
35
+ end
36
+ end
37
+
38
+ failure_message do |actual|
39
+ error_message = "expected log output\n\t'#{actual.join('')}'\nto include log message\n\t'#{expected}'"
40
+ error_message += " at #{log_level} level" if log_level
41
+ error_message
42
+ end
43
+ end
44
+
45
+ RSpec::Matchers.define :log do
46
+ supports_block_expectations
47
+ chain :at_level, :log_level
48
+
49
+ failure_message do
50
+ error_message = "expected operation to log '#{expected}'"
51
+ error_message += " at #{log_level} level" if log_level
52
+ "#{error_message}\n\nactual log output:\n#{log_output.join('')}"
53
+ end
54
+
55
+ match do |operation|
56
+ raise 'log matcher only supports block expectations' unless operation.is_a? Proc
57
+ log_output_is_expected.not_to include_log_message(expected)
58
+ operation.call
59
+ log_output_is_expected.to include_log_message(expected).at_level(log_level)
60
+ end
61
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logging'
4
+ require 'rspec/logging_helper'
5
+ require_relative 'rspec/helpers'
6
+ require_relative 'rspec/matchers'
7
+ require_relative 'log_layout'
8
+
9
+ RSpec.configure do |config|
10
+ config.include Ezlog::RSpec::Helpers
11
+ config.before(:suite) do
12
+ Logging.appenders.string_io('__logcraft_stringio__', layout: Logging.logger.root.appenders.first&.layout || Logcraft::LogLayout.new)
13
+ config.capture_log_messages to: '__logcraft_stringio__'
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logcraft
4
+ VERSION = "1.0.0.rc"
5
+ end
data/lib/logcraft.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logging'
4
+
5
+ require 'logcraft/version'
6
+ require 'logcraft/railtie' if defined? Rails
7
+
8
+ module Logcraft
9
+ autoload :LogContextHelper, 'logcraft/log_context_helper'
10
+ autoload :LogLayout, 'logcraft/log_layout'
11
+ autoload :Rails, 'logcraft/rails'
12
+
13
+ extend LogContextHelper
14
+
15
+ def self.initialize(log_level: :info, initial_context: {}, layout_options: {})
16
+ Logging.logger.root.appenders = Logging.appenders.stdout layout: LogLayout.new(initial_context, layout_options)
17
+ Logging.logger.root.level = log_level
18
+ end
19
+
20
+ def self.logger(name, level = nil)
21
+ Logging::Logger[name].tap { |logger| logger.level = level if level }
22
+ end
23
+ end
data/logcraft.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/logcraft/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "logcraft"
7
+ spec.version = Logcraft::VERSION
8
+ spec.authors = ["Zoltan Ormandi"]
9
+ spec.email = ["zoltan.ormandi@gmail.com"]
10
+
11
+ spec.summary = "A zero-configuration logging solution for Ruby on Rails."
12
+ spec.homepage = "https://github.com/zormandi/logcraft"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.6.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/zormandi/logcraft"
18
+ spec.metadata["changelog_uri"] = "https://github.com/zormandi/logcraft/blob/master/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "logging", "~> 2.0"
32
+ spec.add_dependency "multi_json", "~> 1.14"
33
+
34
+ spec.add_development_dependency "bundler", "~> 2.0"
35
+ spec.add_development_dependency "rake", ">= 12.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logcraft
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.rc
5
+ platform: ruby
6
+ authors:
7
+ - Zoltan Ormandi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-06-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logging
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: multi_json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.14'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '12.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '12.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description:
84
+ email:
85
+ - zoltan.ormandi@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".rspec"
91
+ - CHANGELOG.md
92
+ - CODE_OF_CONDUCT.md
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - gemfiles/rails_5.2.gemfile
98
+ - gemfiles/rails_6.0.gemfile
99
+ - gemfiles/rails_6.1.gemfile
100
+ - lib/logcraft.rb
101
+ - lib/logcraft/log_context_helper.rb
102
+ - lib/logcraft/log_layout.rb
103
+ - lib/logcraft/rails.rb
104
+ - lib/logcraft/rails/active_record.rb
105
+ - lib/logcraft/rails/active_record/log_subscriber.rb
106
+ - lib/logcraft/rails/extensions.rb
107
+ - lib/logcraft/rails/log_subscription_handler.rb
108
+ - lib/logcraft/rails/request_id_logger.rb
109
+ - lib/logcraft/rails/request_logger.rb
110
+ - lib/logcraft/railtie.rb
111
+ - lib/logcraft/rspec.rb
112
+ - lib/logcraft/rspec/helpers.rb
113
+ - lib/logcraft/rspec/matchers.rb
114
+ - lib/logcraft/version.rb
115
+ - logcraft.gemspec
116
+ homepage: https://github.com/zormandi/logcraft
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ homepage_uri: https://github.com/zormandi/logcraft
121
+ source_code_uri: https://github.com/zormandi/logcraft
122
+ changelog_uri: https://github.com/zormandi/logcraft/blob/master/CHANGELOG.md
123
+ post_install_message:
124
+ rdoc_options: []
125
+ require_paths:
126
+ - lib
127
+ required_ruby_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: 2.6.0
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">"
135
+ - !ruby/object:Gem::Version
136
+ version: 1.3.1
137
+ requirements: []
138
+ rubygems_version: 3.1.6
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: A zero-configuration logging solution for Ruby on Rails.
142
+ test_files: []