julewire-rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +94 -0
  5. data/docs/advanced-configuration.md +17 -0
  6. data/docs/capture-and-filtering.md +102 -0
  7. data/docs/configuration.md +83 -0
  8. data/docs/development.md +21 -0
  9. data/docs/events-and-errors.md +46 -0
  10. data/docs/lifecycle.md +24 -0
  11. data/docs/request-logging.md +49 -0
  12. data/julewire-rails.gemspec +44 -0
  13. data/lib/generators/julewire/install_generator.rb +15 -0
  14. data/lib/generators/julewire/templates/julewire.rb +16 -0
  15. data/lib/julewire/rails/configuration.rb +74 -0
  16. data/lib/julewire/rails/context_body_proxy.rb +54 -0
  17. data/lib/julewire/rails/debug_exception_log_silencer.rb +53 -0
  18. data/lib/julewire/rails/doctor_app.rb +233 -0
  19. data/lib/julewire/rails/exception_severity.rb +27 -0
  20. data/lib/julewire/rails/lifecycle_hooks.rb +76 -0
  21. data/lib/julewire/rails/log_subscriber_silencer.rb +52 -0
  22. data/lib/julewire/rails/logger.rb +185 -0
  23. data/lib/julewire/rails/logger_outputs.rb +36 -0
  24. data/lib/julewire/rails/output_requirement.rb +38 -0
  25. data/lib/julewire/rails/parameter_filter_plan.rb +100 -0
  26. data/lib/julewire/rails/parameter_filter_processor.rb +117 -0
  27. data/lib/julewire/rails/railtie.rb +84 -0
  28. data/lib/julewire/rails/request_attributes.rb +126 -0
  29. data/lib/julewire/rails/request_completion.rb +120 -0
  30. data/lib/julewire/rails/request_context.rb +91 -0
  31. data/lib/julewire/rails/request_error_ownership.rb +63 -0
  32. data/lib/julewire/rails/request_fields.rb +61 -0
  33. data/lib/julewire/rails/request_lifecycle.rb +109 -0
  34. data/lib/julewire/rails/request_middleware.rb +130 -0
  35. data/lib/julewire/rails/request_summary_timeout_scheduler.rb +38 -0
  36. data/lib/julewire/rails/structured_event_record.rb +128 -0
  37. data/lib/julewire/rails/subscribers/controller_response.rb +118 -0
  38. data/lib/julewire/rails/subscribers/error.rb +86 -0
  39. data/lib/julewire/rails/subscribers/event.rb +118 -0
  40. data/lib/julewire/rails/subscribers/rendered_exception.rb +141 -0
  41. data/lib/julewire/rails/suppression.rb +29 -0
  42. data/lib/julewire/rails/version.rb +7 -0
  43. data/lib/julewire/rails.rb +37 -0
  44. data/lib/julewire-rails.rb +3 -0
  45. metadata +201 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 257bcc53a4cbe56820965e9fce773bda793b5c6f58e6bb336f8458809fe0a946
4
+ data.tar.gz: 3573b4e999ac109ec1f4ab4f80141ad0a27f12d87849468074161e94c96c8d1f
5
+ SHA512:
6
+ metadata.gz: 654930df34fb73f8f2a8b599e1ccd966d06a0d73db00af1b4ac4e6edbaf3eca18bfa8c47adf6f314b73d20eceb76e9f6aaec839fa58df65744dab305966a3e4f
7
+ data.tar.gz: 16db8923eee46a3b948a5878ec2e1e4577562a42536a1b54e8a93c12b2944a50a207357927c6ac7226b873ee7f6ef8a8ae65c572e8e3f5cdd1d4cbc188338505
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ## Unreleased
2
+
3
+ ## 1.0.0 - 2026-06-21
4
+
5
+ - Initial release: Rails request summaries, structured events, error capture,
6
+ parameter filtering, dev tail/doctor app, and Julewire logger.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Alexander Grebennik
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,94 @@
1
+ # Julewire Rails
2
+
3
+ Rails integration for Julewire.
4
+
5
+ It installs a Rails-compatible logger, wraps Rack requests in Julewire
6
+ execution scopes, subscribes to Rails structured events, and records Rails error
7
+ reports. It targets Rails 8.1 and newer.
8
+
9
+ ## Quickstart
10
+
11
+ ```ruby
12
+ gem "julewire-rails"
13
+ ```
14
+
15
+ Configure a Julewire output or custom destination:
16
+
17
+ ```ruby
18
+ Julewire.configure do |config|
19
+ config.destinations.use(:default, output: $stdout)
20
+ config.level = :info
21
+
22
+ config.processors.prepend(
23
+ :rails_parameter_filter,
24
+ Rails.application.config.filter_parameters
25
+ )
26
+ end
27
+ ```
28
+
29
+ Or generate the initializer:
30
+
31
+ ```sh
32
+ bin/rails generate julewire:install
33
+ ```
34
+
35
+ Use normal Rails APIs:
36
+
37
+ ```ruby
38
+ Rails.logger.info("booted")
39
+ Rails.logger.warn(message: "retrying", event: "payment.retry", payment_id: 123)
40
+ ```
41
+
42
+ Default behavior:
43
+
44
+ - `Rails.logger.*` calls become Julewire point records.
45
+ - Each request gets one `request.completed` summary.
46
+ - Rails 8.1 structured events become machine point records.
47
+ - `Rails.error` reports become `rails.error` point records.
48
+ - Automatic request exceptions are owned by the request summary.
49
+ - Rails default text subscribers are silenced when Julewire installs the Rails
50
+ logger.
51
+
52
+ Request context stays small: request id, method, path, and remote IP. Rails
53
+ routing, status, timings, completion, and optional capture fields live under
54
+ `attributes.rails`.
55
+
56
+ Rails filters structured event payloads before Julewire receives them.
57
+ Whole-record filtering for `Rails.logger` payloads and optional captured fields
58
+ is a Julewire processor policy; use the registered `:rails_parameter_filter`
59
+ processor when Rails' `filter_parameters` should apply to the full Julewire
60
+ record.
61
+
62
+ ## Docs
63
+
64
+ - [Configuration](docs/configuration.md)
65
+ - [Advanced Configuration](docs/advanced-configuration.md)
66
+ - [Request Logging](docs/request-logging.md)
67
+ - [Events and Errors](docs/events-and-errors.md)
68
+ - [Capture and Filtering](docs/capture-and-filtering.md)
69
+ - [Lifecycle](docs/lifecycle.md)
70
+ - [Development](docs/development.md)
71
+
72
+ ## Local Doctor
73
+
74
+ Mount the doctor app in development when you want a tiny health and tail view:
75
+
76
+ ```ruby
77
+ tail = Julewire.dev!(tail: { capacity: 500 })
78
+ mount Julewire::Rails::DoctorApp.new(tail: tail) => "/julewire"
79
+ ```
80
+
81
+ The mounted app serves `/doctor`, `/doctor.json`, `/tail`, `/tail.json`, and a
82
+ reconnecting `/tail/events` stream for the live tail page.
83
+
84
+ The mounted app exposes health, tail buffers, and request paths. Gate it with
85
+ environment, auth, IP rules, or an internal network boundary.
86
+
87
+ Exclude the mount path from request summaries so the live tail does not capture
88
+ its own polling requests:
89
+
90
+ ```ruby
91
+ Julewire::Rails.configure do |config|
92
+ config.request_exclude_prefixes = ["/julewire"]
93
+ end
94
+ ```
@@ -0,0 +1,17 @@
1
+ # Advanced Configuration
2
+
3
+ These options are rarely needed for normal Rails apps.
4
+
5
+ | Option | Default | Purpose |
6
+ | --- | --- | --- |
7
+ | `logger_name` | `"Rails"` | Logger name on Rails logger records. |
8
+ | `lifecycle_hooks` | `true` | Install at-exit and Rails fork hooks. |
9
+ | `log_rescued_responses` | `:auto` | Control Rails raw rescued-response exception text. |
10
+ | `reported_exception_logs` | `:auto` | Control raw `ActionDispatch::DebugExceptions` report text. |
11
+ | `require_output` | `:warn` | Warn/raise when Julewire owns `Rails.logger` but has no output. |
12
+ | `shutdown_timeout` | `1` | Timeout used by lifecycle flush/close hooks. |
13
+ | `summary_event` | `"request.completed"` | Event name for request summaries. |
14
+
15
+ `log_rescued_responses` and `reported_exception_logs` should usually stay on
16
+ `:auto`. That lets Julewire suppress duplicate framework text when it owns the
17
+ Rails logger while keeping Rails' own behavior when it does not.
@@ -0,0 +1,102 @@
1
+ # Capture and Filtering
2
+
3
+ Header and body capture is off by default. Captured fields are summary
4
+ enrichment fields under `attributes.rails`.
5
+
6
+ Response body capture reads the buffered `ActionDispatch::Response` body from
7
+ `process_action.action_controller`. It does not consume or replace the Rack
8
+ response body. File and stream-like responses are skipped. Streaming response
9
+ bodies are not captured. Mounted Rack apps still get request summaries, but
10
+ header and body capture requires a Rails controller `process_action` event.
11
+
12
+ ```ruby
13
+ Julewire::Rails.configure do |config|
14
+ config.response_capture.body = true
15
+ config.response_capture.body_bytes = nil
16
+ config.response_capture.body_content_types = %w[application/json]
17
+ end
18
+ ```
19
+
20
+ Use `body = :json` to parse captured JSON into `request_body_json` or
21
+ `response_body_json` instead of storing the raw body string. Truncated bodies
22
+ are not parsed; invalid JSON records `*_body_parse_error`.
23
+ When `body_bytes` is `nil`, `:json` mode parses the full captured body before
24
+ core serialization limits apply.
25
+
26
+ Captured response summaries include:
27
+
28
+ - `response_body`
29
+ - `response_body_json`
30
+ - `response_body_parse_error`
31
+ - `response_body_bytes`
32
+ - `response_body_truncated`
33
+
34
+ `*_body_bytes` is the content length when the framework provides it; otherwise
35
+ it is the number of bytes observed while capturing.
36
+
37
+ Request and response headers use `false`, `true`, or an explicit header list:
38
+
39
+ ```ruby
40
+ Julewire::Rails.configure do |config|
41
+ config.request_capture.headers = true
42
+ config.response_capture.headers = %w[content-type x-request-id]
43
+ config.request_capture.body = true
44
+ config.request_capture.body_bytes = 65_536
45
+ end
46
+ ```
47
+
48
+ When header capture is `true`, Julewire omits common sensitive headers such as
49
+ `authorization`, `cookie`, `set-cookie`, `proxy-authorization`, and
50
+ `x-api-key`. Use an explicit header list if the application deliberately wants
51
+ one of those fields.
52
+
53
+ Captured request summaries can include:
54
+
55
+ - `request_headers`
56
+ - `response_headers`
57
+ - `request_body`
58
+ - `request_body_json`
59
+ - `request_body_parse_error`
60
+ - `request_body_bytes`
61
+ - `request_body_truncated`
62
+
63
+ Use processors before enabling broad header/body capture in an application with
64
+ sensitive data.
65
+
66
+ Body capture is gated by media type. The default captures `application/json`
67
+ and `application/*+json` vendor media types. `true` means every non-file,
68
+ buffered candidate. Binary media types such as `application/octet-stream`,
69
+ images, audio, video, fonts, zip, gzip, and PDF are skipped even when body
70
+ content types are set to `true`.
71
+
72
+ Rails filters hash-based `Rails.event` payloads before subscribers receive
73
+ them. Object event payloads are different: Rails passes the object through, and
74
+ subscribers that serialize it must filter the serialized fields. Julewire Rails
75
+ filters serialized object-event hashes with
76
+ `Rails.application.filter_parameters` by default.
77
+
78
+ For whole-record filtering, Rails' parameter filter can be installed as a
79
+ Julewire processor:
80
+
81
+ ```ruby
82
+ Julewire.configure do |config|
83
+ config.processors.prepend(
84
+ :rails_parameter_filter,
85
+ Rails.application.config.filter_parameters
86
+ )
87
+ end
88
+ ```
89
+
90
+ Record container fields such as `payload`, `attributes`, and `context` keep
91
+ their Julewire shape even when a filter matches the section name.
92
+
93
+ Filtering layers:
94
+
95
+ | Layer | Scope |
96
+ | --- | --- |
97
+ | Rails event filtering | Serialized object event payloads before the Julewire record is built. |
98
+ | `ParameterFilterProcessor` | Whole Julewire records using Rails parameter filter rules. |
99
+ | Custom processors | Whole Julewire records using application policy. |
100
+
101
+ If more than one whole-record processor is installed, normal Julewire processor
102
+ order decides precedence.
@@ -0,0 +1,83 @@
1
+ # Configuration
2
+
3
+ Rails config lives on `Julewire::Rails.config` and on
4
+ `config.julewire_rails` in a Rails application.
5
+
6
+ When Julewire owns `Rails.logger`, Rails logger calls are gated by
7
+ `Rails.logger.level`. They bypass core `Julewire.config.level` because the Rails
8
+ logger already applied its level check. Direct `Julewire.emit` and severity
9
+ helper calls still use core `Julewire.config.level`.
10
+
11
+ ## Default Path
12
+
13
+ These defaults are intended for a normal Rails app:
14
+
15
+ | Option | Default | Purpose |
16
+ | --- | --- | --- |
17
+ | `logger` | `true` | Install Julewire as `Rails.logger`. |
18
+ | `request_middleware` | `true` | Wrap Rack requests in Julewire executions. |
19
+ | `request_summary` | `true` | Emit one `request.completed` summary per request. |
20
+ | `request_context` | `true` | Add request id, method, path, and remote IP to context. |
21
+ | `structured_events` | `true` | Subscribe to selected Rails structured events. |
22
+ | `error_reports` | `true` | Subscribe to `Rails.error` reports. |
23
+ | `source` | `"rails"` | Source value on Rails records. |
24
+ | `silence_log_subscribers` | `:auto` | Silence Rails default text subscribers when Julewire owns `Rails.logger`. |
25
+ | `replace_rack_logger` | `true` | Replace `Rails::Rack::Logger` with Julewire request middleware. |
26
+
27
+ ## Common Knobs
28
+
29
+ | Option | Default | Purpose |
30
+ | --- | --- | --- |
31
+ | `carry_request_headers` | `false` | Explicit request-header list copied into `Julewire.carry`. |
32
+ | `request_exclude_prefixes` | `[]` | Request path prefixes that bypass Julewire request context and summaries. |
33
+ | `request_summary_timeout` | `30` | Seconds before warning that body close/completion is late. It does not finish the summary. |
34
+ | `request_capture.headers` | `false` | Add selected request headers to `attributes.rails` on summaries. |
35
+ | `request_capture.body` | `false` | Add request body fields to `attributes.rails` on summaries. |
36
+ | `response_capture.headers` | `false` | Add selected response headers to `attributes.rails` on summaries. |
37
+ | `response_capture.body` | `false` | Add response body fields to `attributes.rails` on summaries. |
38
+ | `filter_event_payloads` | `true` | Filter serialized object-event hashes with Rails parameter filters. |
39
+ | `rendered_exceptions` | `false` | Emit diagnostic rendered-exception point records in addition to summaries. |
40
+
41
+ Broad header capture (`*.headers = true`) skips common sensitive headers.
42
+ Explicit header lists are authoritative.
43
+
44
+ `structured_event_prefixes = nil` accepts all structured-event names. Use it
45
+ only when the app wants Julewire to consider every Rails event.
46
+
47
+ Capture sub-options:
48
+
49
+ | Option | Default | Purpose |
50
+ | --- | --- | --- |
51
+ | `*.headers` | `false` | `false`, `true`, or explicit header names. |
52
+ | `*.body` | `false` | `true` captures raw buffered bodies; `:json` parses bodies into `*_body_json`. |
53
+ | `*.body_bytes` | `65_536` | Body byte cap; `nil` means no cap. |
54
+ | `*.body_content_types` | `Julewire::Rack::Capture::BodyContentType::JSON_ONLY` | Media-type allowlist; `true` means all non-binary buffered bodies. |
55
+
56
+ ## Structured Events
57
+
58
+ | Option | Default | Purpose |
59
+ | --- | --- | --- |
60
+ | `structured_event_prefixes` | `["action_controller.", "action_dispatch.", "active_record."]` | Rails event prefixes. |
61
+ | `structured_event_names` | `[]` | Exact event names to emit. |
62
+ | `structured_event_exclude_prefixes` | `[]` | Prefixes to suppress. |
63
+ | `structured_event_exclude_names` | `[]` | Exact names to suppress. |
64
+
65
+ `action_view.` is opt-in because Rails exposes every render start, partial,
66
+ collection, layout, and template as structured events.
67
+
68
+ ```ruby
69
+ Julewire::Rails.configure do |config|
70
+ config.structured_event_names = %w[action_view.render_template]
71
+ end
72
+ ```
73
+
74
+ Example:
75
+
76
+ ```ruby
77
+ Julewire::Rails.configure do |config|
78
+ config.carry_request_headers = %w[traceparent tracestate]
79
+ config.request_exclude_prefixes = ["/julewire"]
80
+ config.response_capture.body = :json
81
+ config.response_capture.body_content_types = %w[application/json]
82
+ end
83
+ ```
@@ -0,0 +1,21 @@
1
+ # Development
2
+
3
+ Run the normal gem checks:
4
+
5
+ ```sh
6
+ bundle exec rake
7
+ ```
8
+
9
+ Run the Rails appraisal suite:
10
+
11
+ ```sh
12
+ bundle exec rake appraisal:test
13
+ BUNDLE_GEMFILE=gemfiles/rails_8_1.gemfile bundle exec rake test
14
+ BUNDLE_GEMFILE=gemfiles/rails_head.gemfile bundle exec rake test
15
+ ```
16
+
17
+ The real-stack tests live under `test/dummy` and exercise a booted Rails app,
18
+ default log-subscriber silencing, Rails structured events, logger calls, request
19
+ summaries, and body-capture media gates. Add future Rails versions by adding
20
+ another gemfile under `gemfiles/` and adding it to `RAILS_APPRAISAL_TARGETS`.
21
+ `rails_head` tracks the Rails repository's default branch.
@@ -0,0 +1,46 @@
1
+ # Events and Errors
2
+
3
+ Rails 8.1 structured events are subscribed through `Rails.event`. Controller
4
+ start/completion events enrich the request summary. Framework events such as SQL
5
+ and render notifications are emitted as point records.
6
+
7
+ Rails default text log subscribers are silenced by default when Julewire owns the
8
+ Rails logger. Anything that still reaches `Rails.logger` is treated as a logger
9
+ call, not parsed as duplicate framework text.
10
+
11
+ To keep Rails' default logger output and use Julewire only for request summaries
12
+ or structured-event records:
13
+
14
+ ```ruby
15
+ Julewire::Rails.configure do |config|
16
+ config.logger = false
17
+ config.request_middleware = true
18
+ config.structured_events = true
19
+ end
20
+ ```
21
+
22
+ When `request_summary` is enabled, rendered 4xx/5xx request errors are attached
23
+ to `request.completed`. The summary includes the Rails response status,
24
+ `error_class`, `rescue_response`, and `rescue_template`, plus the core-shaped
25
+ exception.
26
+
27
+ Request-error summary severity inherits Rails'
28
+ `action_dispatch.debug_exception_log_level`. Julewire does not derive this
29
+ severity from the status code.
30
+
31
+ Backtrace policy is owned by core:
32
+
33
+ ```ruby
34
+ Julewire.configure do |config|
35
+ config.error_backtrace_lines = 0
36
+ end
37
+ ```
38
+
39
+ Set `rendered_exceptions = true` only when you want an additional diagnostic
40
+ point record from `ActionDispatch::DebugExceptions` alongside the summary-owned
41
+ request error.
42
+
43
+ `Rails.error` remains a separate source for explicit application reports,
44
+ handled reports, jobs, and framework errors that are not owned by a request
45
+ summary. Julewire records those as `rails.error` and shapes the exception
46
+ through core's exception serializer.
data/docs/lifecycle.md ADDED
@@ -0,0 +1,24 @@
1
+ # Lifecycle
2
+
3
+ When `lifecycle_hooks` is enabled, the Railtie installs an at-exit drain hook
4
+ that flushes and closes Julewire with `shutdown_timeout`.
5
+
6
+ It also registers a Rails `ActiveSupport::ForkTracker` after-fork hook that:
7
+
8
+ - calls `Julewire.after_fork!`
9
+ - resets request-summary timeout scheduler state
10
+ - clears request-error ownership state
11
+
12
+ This covers Rails process forks that go through Rails' fork tracker.
13
+
14
+ Custom destinations still own queue, retry, delivery, and reopen behavior. The
15
+ Rails hook only gives them lifecycle opportunities.
16
+
17
+ `require_output` checks after Rails initializers that Julewire has at least one
18
+ configured destination when Julewire owns `Rails.logger`.
19
+
20
+ | Value | Behavior |
21
+ | --- | --- |
22
+ | `:warn` | Warn, but allow no-output mode. |
23
+ | `:raise` | Fail boot if no output is configured. |
24
+ | `false` | Do not check. |
@@ -0,0 +1,49 @@
1
+ # Request Logging
2
+
3
+ The request middleware replaces `Rails::Rack::Logger`, wraps each Rack request
4
+ in a Julewire execution scope, and emits one request summary when enabled.
5
+
6
+ Rails emits five Julewire record shapes:
7
+
8
+ | Source | Shape |
9
+ | --- | --- |
10
+ | Rails structured events | Point records such as `active_record.sql`. |
11
+ | `Rails.logger.*` | Point records with `event: "log"`. |
12
+ | Request boundary | One `request.completed` summary. |
13
+ | `Rails.error` | Explicit `rails.error` point records. |
14
+ | Rendered exception interceptor | Optional diagnostic point records. |
15
+
16
+ `Rails.logger` records intentionally do not honor user-supplied `kind`,
17
+ `execution`, `carry`, `attributes`, or `neutral` keys. Those keys are treated as
18
+ payload data so ordinary logger calls cannot forge execution identity,
19
+ propagation state, or formatter coordination fields.
20
+
21
+ Request ids, method, path, and remote IP stay in `context`. Rails-specific
22
+ routing and diagnostics such as controller, action, format, params, status,
23
+ filtered URL/path, timings, completion, and capture fields live under
24
+ `attributes.rails`.
25
+
26
+ Provider-neutral HTTP fields live in the record's `neutral` section for
27
+ formatters:
28
+
29
+ - `http.request.method`
30
+ - `url.full`
31
+ - `url.path`
32
+ - `user_agent.original`
33
+ - `client.address`
34
+ - `http.response.status_code`
35
+ - `http.response.body.size`
36
+
37
+ The request summary emits at Rack response completion when Rails/Rack exposes a
38
+ completion hook, or when the wrapped response body closes. If neither happens,
39
+ `request_summary_timeout` emits a `request.completion_timeout` warning point
40
+ with `completion_timeout_ms`. It does not finish the request summary from the
41
+ timeout thread; a later body close can still emit the normal summary.
42
+
43
+ `ActionController::Live` child-thread execution does not inherit Julewire
44
+ context by itself. Live needs an explicit bridge through Rails execution state.
45
+
46
+ Julewire Rails supports Rails' default
47
+ `config.action_dispatch.show_exceptions = :all`. `:rescuable` and `:none` let
48
+ some or all exceptions escape to the Rack server and are not supported modes for
49
+ Julewire Rails request logging.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/julewire/rails/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "julewire-rails"
7
+ spec.version = Julewire::Rails::VERSION
8
+ spec.authors = ["Alexander Grebennik"]
9
+ spec.email = ["slbug@users.noreply.github.com", "sl.bug.sl@gmail.com"]
10
+
11
+ spec.summary = "Rails logger and request runtime integration for Julewire."
12
+ spec.description = "Rails 8.1+ integration that routes Rails.logger through Julewire and wraps requests in " \
13
+ "execution scopes."
14
+ spec.homepage = "https://github.com/slbug/julewire"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 3.4"
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/slbug/julewire/tree/main/gems/rails"
19
+ spec.metadata["changelog_uri"] = "https://github.com/slbug/julewire/blob/main/gems/rails/CHANGELOG.md"
20
+
21
+ spec.metadata["rubygems_mfa_required"] = "true"
22
+
23
+ spec.files = Dir.chdir(__dir__) do
24
+ Dir[
25
+ "CHANGELOG.md",
26
+ "LICENSE.txt",
27
+ "README.md",
28
+ "docs/**/*.md",
29
+ "julewire-rails.gemspec",
30
+ "lib/**/*.rb"
31
+ ]
32
+ end
33
+ spec.executables = []
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_dependency "actionpack", ">= 8.1"
37
+ spec.add_dependency "actionview", ">= 8.1"
38
+ spec.add_dependency "julewire-core", ">= 1.0"
39
+ spec.add_dependency "julewire-rack", ">= 1.0"
40
+ spec.add_dependency "julewire-rails_support", ">= 1.0"
41
+ spec.add_dependency "logger", ">= 1.7"
42
+ spec.add_dependency "railties", ">= 8.1"
43
+ spec.add_dependency "zeitwerk", ">= 2.8.1"
44
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Julewire
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_initializer
11
+ template "julewire.rb", "config/initializers/julewire.rb"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ Julewire.configure do |config|
4
+ config.destinations.use(:default, output: $stdout) if config.destinations.empty?
5
+
6
+ config.processors.prepend(
7
+ :rails_parameter_filter,
8
+ Rails.application.config.filter_parameters
9
+ )
10
+ end
11
+
12
+ Julewire::Rails.configure do |config|
13
+ config.request_summary = true
14
+ config.structured_events = true
15
+ config.error_reports = true
16
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Julewire
4
+ module Rails
5
+ class Configuration
6
+ include Julewire::Core::Integration::Settings
7
+
8
+ setting :logger, default: true, predicate: true
9
+ setting :logger_name, default: "Rails"
10
+ setting :carry_request_headers, default: false, validate: :validate_carry_request_headers
11
+ setting :error_reports, default: true, predicate: true
12
+ setting :filter_event_payloads, default: true, predicate: true
13
+ setting :lifecycle_hooks, default: true, predicate: true
14
+ setting :log_rescued_responses, default: :auto
15
+ setting :reported_exception_logs, default: :auto
16
+ setting(:request_capture, validate: :validate_capture_settings) { Julewire::Rack::Capture::Settings.new }
17
+ setting :request_context, default: true, predicate: true
18
+ setting :request_exclude_prefixes, default: [], validate: :validate_request_exclude_prefixes
19
+ setting :request_middleware, default: true, predicate: true
20
+ setting :require_output, default: :warn
21
+ setting :request_summary, default: true, predicate: true
22
+ setting :request_summary_timeout, default: 30, validate: :validate_request_summary_timeout
23
+ setting :replace_rack_logger, default: true, predicate: true
24
+ setting(:response_capture, validate: :validate_capture_settings) { Julewire::Rack::Capture::Settings.new }
25
+ setting :rendered_exceptions, default: false, predicate: true
26
+ setting :silence_log_subscribers, default: :auto
27
+ setting :shutdown_timeout, default: 1
28
+ setting :source, default: "rails"
29
+ setting :structured_event_exclude_names, default: []
30
+ setting :structured_event_exclude_prefixes, default: []
31
+ setting :structured_event_names, default: []
32
+ setting :structured_event_prefixes, default: %w[action_controller. action_dispatch. active_record.]
33
+ setting :structured_events, default: true, predicate: true
34
+ setting :summary_event, default: "request.completed"
35
+
36
+ def controller_capture?
37
+ request_capture.enabled? || response_capture.enabled?
38
+ end
39
+
40
+ def silence_log_subscribers?
41
+ return logger? if silence_log_subscribers == :auto
42
+
43
+ !!silence_log_subscribers
44
+ end
45
+
46
+ private
47
+
48
+ def validate_carry_request_headers(value)
49
+ return value unless value == true
50
+
51
+ raise Error, "carry_request_headers must be an explicit header list"
52
+ end
53
+
54
+ def validate_request_summary_timeout(value)
55
+ return value if value.nil?
56
+ return value if value.is_a?(Numeric) && value.positive?
57
+
58
+ raise Error, "request_summary_timeout must be nil or a positive Numeric"
59
+ end
60
+
61
+ def validate_request_exclude_prefixes(value)
62
+ prefixes = Array(value)
63
+ return prefixes if prefixes.all? { it.is_a?(String) && it.start_with?("/") && !it.empty? }
64
+
65
+ raise Error, "request_exclude_prefixes must contain absolute path prefixes"
66
+ end
67
+
68
+ def validate_capture_settings(settings)
69
+ settings.validate!
70
+ settings
71
+ end
72
+ end
73
+ end
74
+ end