select_rails_log 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +13 -0
  3. data/CHANGELOG.md +22 -0
  4. data/CODE_OF_CONDUCT.md +132 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +93 -0
  7. data/Rakefile +16 -0
  8. data/exe/select_rails_log +5 -0
  9. data/images/boxplot.png +0 -0
  10. data/images/histgram.png +0 -0
  11. data/images/text.png +0 -0
  12. data/lib/select_rails_log/command_line_options.rb +71 -0
  13. data/lib/select_rails_log/constants.rb +36 -0
  14. data/lib/select_rails_log/extension.rb +59 -0
  15. data/lib/select_rails_log/filter/base_filter.rb +35 -0
  16. data/lib/select_rails_log/filter/controller_action_filter.rb +50 -0
  17. data/lib/select_rails_log/filter/duration_range_filter.rb +40 -0
  18. data/lib/select_rails_log/filter/http_method_filter.rb +26 -0
  19. data/lib/select_rails_log/filter/http_status_filter.rb +39 -0
  20. data/lib/select_rails_log/filter/logs_regexp_filter.rb +27 -0
  21. data/lib/select_rails_log/filter/params_regexp_filter.rb +27 -0
  22. data/lib/select_rails_log/filter/range_pattern.rb +32 -0
  23. data/lib/select_rails_log/filter/request_id_filter.rb +28 -0
  24. data/lib/select_rails_log/filter/time_range_filter.rb +43 -0
  25. data/lib/select_rails_log/filter.rb +16 -0
  26. data/lib/select_rails_log/options.rb +20 -0
  27. data/lib/select_rails_log/printer/base_printer.rb +117 -0
  28. data/lib/select_rails_log/printer/boxplot_printer.rb +83 -0
  29. data/lib/select_rails_log/printer/data_serializable.rb +43 -0
  30. data/lib/select_rails_log/printer/histgram_printer.rb +48 -0
  31. data/lib/select_rails_log/printer/json_printer.rb +43 -0
  32. data/lib/select_rails_log/printer/jsonl_printer.rb +27 -0
  33. data/lib/select_rails_log/printer/null_printer.rb +19 -0
  34. data/lib/select_rails_log/printer/raw_printer.rb +45 -0
  35. data/lib/select_rails_log/printer/statistics_printer.rb +84 -0
  36. data/lib/select_rails_log/printer/text_printer.rb +51 -0
  37. data/lib/select_rails_log/printer/tsv_printer.rb +54 -0
  38. data/lib/select_rails_log/printer.rb +17 -0
  39. data/lib/select_rails_log/runner.rb +151 -0
  40. data/lib/select_rails_log/scanner.rb +142 -0
  41. data/lib/select_rails_log/selector.rb +32 -0
  42. data/lib/select_rails_log/version.rb +5 -0
  43. data/lib/select_rails_log.rb +22 -0
  44. data/sig/select_rails_log.rbs +4 -0
  45. metadata +131 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2e895ebdfdf2cfb2a09dc6771092b2df4128994657b888ca2f1c9fa0abd8e707
4
+ data.tar.gz: 3440ad80cde5de22eae7888543c7db28338e669ff5461b360afa762d7477c3d9
5
+ SHA512:
6
+ metadata.gz: 4dc6fc31f2398e95f191af9b9704ae542ff60e71dd03dafa8fd93bd3f563b11d78ce9293e288319b6ecd66b11452278fb9e72ab868f3507bf6f085a58d8c3a91
7
+ data.tar.gz: 54cb6c4879c49ab1196a089e8fbd4632b72e3c2c5077c4c1918b5d8365c105d18a27d2334068fca8024e249f8df9c4a7d4e41f8a2a9b89e238ab8cb396f9606b
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 3.1
7
+ NewCops: enable
8
+
9
+ Style/StringLiterals:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Style/StringLiteralsInInterpolation:
13
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.2.1] - 2025-01-18
4
+
5
+ - Multline log support
6
+ - Bug fix
7
+ - Refine stats
8
+ - Refectoring
9
+ - Add tests
10
+
11
+ ## [0.2.0] - 2024-12-15
12
+
13
+ - Multiple printer support
14
+ - Add printers
15
+ - jsonl
16
+ - tsv
17
+ - stats
18
+ - plot
19
+
20
+ ## [0.1.0] - 2024-12-12
21
+
22
+ - Import from gists
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 akira yamada
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,93 @@
1
+ # select_rails_log
2
+
3
+ select_rails_log is a tool for extracting request logs from Rails log files.
4
+
5
+ It can be used during development to identify bottlenecks in the application or for small-scale benchmarking.
6
+
7
+ The following extraction criteria can be specified:
8
+
9
+ * Request ID
10
+ * Controller name and action name
11
+ * HTTP method and status code
12
+ * Request date and time range
13
+ * Response time range
14
+ * String matching
15
+
16
+ The extracted logs can be formatted and output in the following formats:
17
+
18
+ * Text
19
+ * JSON, JSONL
20
+ * TSV
21
+ * Raw log
22
+
23
+ Additionally, the tool can aggregate and output metrics such as response times for the extracted requests:
24
+
25
+ * Percentiles by controller and action
26
+ * Box plots by controller and action
27
+ * Histograms for the overall extraction results
28
+
29
+ ## Installation
30
+
31
+ Install the gem by executing:
32
+
33
+ ```bash
34
+ gem install select_rails_log
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ select_rails_log can process logs that include timestamps and Request-IDs.
40
+
41
+ To enable this, first configure the appropriate settings file, such as `config/environments/development.rb`, as follows:
42
+
43
+ ```ruby
44
+ config.log_tags = [:request_id]
45
+ config.log_formatter = ::Logger::Formatter.new
46
+ ```
47
+
48
+ ### Sample session
49
+
50
+ Here are some usage examples.
51
+
52
+ To output a box plot for an overview, use the following command:
53
+
54
+ ```bash
55
+ select_rails_log log/development.log --boxplot
56
+ ```
57
+
58
+ The result will be output as follows:
59
+
60
+ ![boxplot](images/boxplot.png)
61
+
62
+ Next, let’s examine the response time distribution for ActiveStorage::Representations::RedirectController#show, which had significant variance:
63
+
64
+ ```bash
65
+ select_rails_log log/development.log -A 'ActiveStorage::Representations::RedirectController#show' --histgram
66
+ ```
67
+ ![histgram](images/histgram.png)
68
+
69
+ Finally, let’s take a closer look at requests that took particularly long, specifically those over 400ms:
70
+
71
+ ```bash
72
+ select_rails_log log/development.log -A 'ActiveStorage::Representations::RedirectController#show' -D 400..
73
+ ```
74
+
75
+ ![text](images/text.png)
76
+
77
+ ## Development
78
+
79
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
80
+
81
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on GitHub at https://github.com/arika/select_rails_log. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/arika/select_rails_log/blob/master/CODE_OF_CONDUCT.md).
86
+
87
+ ## License
88
+
89
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
90
+
91
+ ## Code of Conduct
92
+
93
+ Everyone interacting in the SelectRailsLog project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/arika/select_rails_log/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "select_rails_log"
5
+ SelectRailsLog.run
Binary file
Binary file
data/images/text.png ADDED
Binary file
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require_relative "options"
5
+
6
+ module SelectRailsLog
7
+ class CommandLineOptions < Options
8
+ class << self
9
+ attr_reader :initializers
10
+ end
11
+
12
+ attr_reader :parser
13
+
14
+ def initialize
15
+ super
16
+ @parser = OptionParser.new
17
+ @parser.banner += " [logfiles...]"
18
+ @extensions = {}
19
+ @extension_groups = {}
20
+ setup
21
+ end
22
+
23
+ def parse!(argv)
24
+ parser.parse!(argv)
25
+ end
26
+
27
+ def register(ext_name)
28
+ return if key?(ext_name)
29
+
30
+ self[ext_name] = Options.new
31
+ end
32
+
33
+ def add_option(ext_name, opt_name, *optparse_args, default: nil)
34
+ self[ext_name][opt_name] = nil
35
+
36
+ parser.on(*optparse_args) do |value|
37
+ value = default if value.nil?
38
+ self[ext_name][opt_name] = value
39
+ end
40
+ end
41
+
42
+ def extensions(group)
43
+ @extension_groups[group]
44
+ end
45
+
46
+ private
47
+
48
+ def setup
49
+ @extension_groups = {}
50
+ Extension.extensions.each do |ext_class|
51
+ ext_type = extension_type(ext_class)
52
+ @extension_groups[ext_type] ||= []
53
+ @extension_groups[ext_type] << ext_class
54
+
55
+ ext_class.option_initializers.each do |initializer|
56
+ initializer.call(self)
57
+ end
58
+ end
59
+ end
60
+
61
+ def extension_type(ext_class)
62
+ if ext_class < Filter::BaseFilter
63
+ :filter
64
+ elsif ext_class < Printer::BasePrinter
65
+ :printer
66
+ else
67
+ :other
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module SelectRailsLog
6
+ module Constants
7
+ DEFAULT_OUTPUT = Object.new
8
+
9
+ DEBUG = "DEBUG"
10
+
11
+ # data keys
12
+ ACTION = "action"
13
+ CLIENT = "client"
14
+ COMPLETED = "completed"
15
+ CONTROLLER = "controller"
16
+ DURATION = "duration"
17
+ HTTP_METHOD = "http_method"
18
+ HTTP_STATUS = "http_status"
19
+ ID = "id"
20
+ INTERVAL = "interval"
21
+ LOGS = "logs"
22
+ MESSAGE = "message"
23
+ PARAMETERS = "parameters"
24
+ PATH = "path"
25
+ PERFORMANCE = "performance"
26
+ PERFORMANCE_ACTIVE_RECORD = "ActiveRecord"
27
+ PERFORMANCE_ALLOCATIONS = "Allocations"
28
+ PERFORMANCE_VIEWS = "Views"
29
+ PID = "pid"
30
+ REQUEST_ID = "request_id"
31
+ RAW_LOGS = "raw_logs"
32
+ SEVERITY = "severity"
33
+ STARTED = "started"
34
+ TIME = "time"
35
+ end
36
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelectRailsLog
4
+ class Extension
5
+ @extensions = []
6
+
7
+ class << self
8
+ attr_reader :extensions, :extension_name
9
+
10
+ def option_initializers
11
+ @option_initializers ||= []
12
+ end
13
+
14
+ private
15
+
16
+ def option_initializer(&block)
17
+ option_initializers << block
18
+ end
19
+
20
+ def define_options(ext_name)
21
+ raise ArgumentError, "Extension #{ext_name} is already defined" if @extension_name
22
+
23
+ option_initializer do |opts|
24
+ opts.register(ext_name)
25
+ end
26
+
27
+ Extension.extensions << self
28
+ @extension_name = ext_name
29
+
30
+ yield
31
+ end
32
+
33
+ def option(opt_name, *optparse_args, default: nil)
34
+ ext_name = @extension_name
35
+
36
+ option_initializer do |opts|
37
+ opts.add_option(ext_name, opt_name, *optparse_args, default:)
38
+ end
39
+ end
40
+
41
+ def separator(str)
42
+ option_initializer do |opts|
43
+ opts.parser.separator(str)
44
+ end
45
+ end
46
+ end
47
+
48
+ attr_reader :options
49
+
50
+ def initialize(whole_options)
51
+ @whole_options = whole_options
52
+ @options = @whole_options[self.class.extension_name]
53
+ end
54
+
55
+ def runnable?
56
+ false
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelectRailsLog
4
+ module Filter
5
+ class BaseFilter < Extension
6
+ include Constants
7
+
8
+ class << self
9
+ def filter_type(filter_type = nil)
10
+ @filter_type ||= :line
11
+ return @filter_type unless filter_type
12
+
13
+ @filter_type = filter_type
14
+ end
15
+ end
16
+
17
+ define_options :base_filter do
18
+ separator ""
19
+ separator "filter options:"
20
+ end
21
+
22
+ def request_filter?
23
+ self.class.filter_type == :request
24
+ end
25
+
26
+ def line_filter?
27
+ self.class.filter_type == :line
28
+ end
29
+
30
+ def run(obj)
31
+ obj
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelectRailsLog
4
+ module Filter
5
+ class ControllerActionFilter < BaseFilter
6
+ filter_type :request
7
+
8
+ define_options :controller_action_filter do
9
+ option :pattern,
10
+ "--controller-action NAMEs", "-A", Array,
11
+ "Filter by controller and action names",
12
+ " ex: 'FooController#index,BarController,baz#show'"
13
+ end
14
+
15
+ def initialize(...)
16
+ super
17
+ @controller_actions = controller_actions(options[:pattern])
18
+ end
19
+
20
+ def runnable?
21
+ !!@controller_actions
22
+ end
23
+
24
+ def run(data)
25
+ @controller_actions.any? do |controller, action|
26
+ data[CONTROLLER] == controller &&
27
+ (action.nil? || data[ACTION] == action)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def controller_actions(names)
34
+ return unless names
35
+
36
+ names.map do |name|
37
+ controller, action = name.split("#", 2)
38
+ controller = classify(controller) << "Controller" unless controller.end_with?("Controller")
39
+ [controller, action]
40
+ end
41
+ end
42
+
43
+ def classify(name)
44
+ name.scan(%r{(?:/|[^_/]+)})
45
+ .map { |seg| seg == "/" ? "::" : seg.capitalize }
46
+ .join
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "range_pattern"
4
+
5
+ module SelectRailsLog
6
+ module Filter
7
+ class DurationRangeFilter < BaseFilter
8
+ include RangePattern
9
+
10
+ define_options :duration_range_filter do
11
+ option :pattern,
12
+ "--duration-range RANGE", "-D", String,
13
+ "Filter by duration range [ms]",
14
+ " range format is 'ms1..ms2', 'ms1...ms2', or 'ms,delta'.",
15
+ " ex: '10..200', '10...200', '100,10'"
16
+ end
17
+
18
+ def initialize(...)
19
+ super
20
+
21
+ pattern = options[:pattern]
22
+ return unless pattern
23
+
24
+ begin
25
+ @range = parse_range_pattern(pattern, &:to_i)
26
+ rescue ArgumentError
27
+ raise CommandLineOptionError, "invalid duration range pattern `#{pattern}`"
28
+ end
29
+ end
30
+
31
+ def runnable?
32
+ !!@range
33
+ end
34
+
35
+ def run(data)
36
+ @range.cover?(data[DURATION])
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelectRailsLog
4
+ module Filter
5
+ class HttpMethodFilter < BaseFilter
6
+ filter_type :request
7
+
8
+ define_options :http_method_filter do
9
+ option :http_method, "--http-method METHOD", "-M", String, "Filter by HTTP method"
10
+ end
11
+
12
+ def initialize(...)
13
+ super
14
+ @http_method = options[:http_method]&.upcase
15
+ end
16
+
17
+ def runnable?
18
+ !!@http_method
19
+ end
20
+
21
+ def run(data)
22
+ data[HTTP_METHOD] == @http_method
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelectRailsLog
4
+ module Filter
5
+ class HttpStatusFilter < BaseFilter
6
+ define_options :http_status_filter do
7
+ option :pattern,
8
+ "--http-statuses PATTERN", "-S", String,
9
+ "Filter by HTTP statuses",
10
+ " statuses format is 'st1,st2,...', '!st1,st2,...', or 'st1,...!st2,...'.",
11
+ " ex: '200,302', '3,20,!201,304', or '!4,5'"
12
+ end
13
+
14
+ def initialize(...)
15
+ super
16
+
17
+ @includes = @excludes = []
18
+ pattern = options[:pattern]
19
+ return unless pattern
20
+
21
+ m = /\A([,\d]+)?(?:!([,\d]+))?\z/.match(pattern)
22
+ raise CommandLineOptionError, "invalid HTTP statuses pattern `#{pattern}`" unless m
23
+
24
+ @includes = m[1] ? m[1].split(",") : []
25
+ @excludes = m[2] ? m[2].split(",") : []
26
+ end
27
+
28
+ def runnable?
29
+ @includes.any? || @excludes.any?
30
+ end
31
+
32
+ def run(data)
33
+ http_status = data[HTTP_STATUS]
34
+ @excludes.none? { |exc| http_status.index(exc)&.zero? } &&
35
+ (@includes.empty? || @includes.any? { |inc| http_status.index(inc)&.zero? })
36
+ end
37
+ end
38
+ end
39
+ end