lograge_waittime 0.4.0

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: 77d84f316c43785cdea187ce0ee10ba3624c41492eeefdada55331ef3838cbef
4
+ data.tar.gz: 05be60d05146942b59b0b7db57dfa3d9ed876707d7d0f6ba173bdaa5f9a07109
5
+ SHA512:
6
+ metadata.gz: a7bf14b0ac00095c9a736351e50144d3a27874352c2eedb0d26cc0d8357a39ce7b0eebbf74cb4d8e45eb71c38db1f9fa1bdb9c583bbc4fb5dca676cfac1d1355
7
+ data.tar.gz: c29cca3a40d464a7fa8f278eb4586a411f4db1a81201ee41b7e4e66df80c24ce7c0d0067ee449218b1f796600d6f8d70e6f67334c06147ec24da3a7eb1b94679
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project adheres to [Semantic Versioning](http://semver.org/).
5
+
6
+ ## Unreleased
7
+
8
+ ## 0.4.0 - 2022-11-18
9
+
10
+ * Zeitwerk did not work seamlessly with the previous gem name, switching to `lograge_waittime`.
11
+ * Code simplified further.
12
+
13
+ ## 0.3.1 - 2022-11-18
14
+
15
+ - Actually bumping version.rb to match
16
+
17
+ ## 0.3.0 - 2022-11-18
18
+
19
+ - BREAKING: Rename this gem to "lograge-waittime". The old name was unwieldy and even had a typo to prove it.
20
+ - BREAKING: Removed exception silencing to focus the code more narrowly.
21
+ - Removing explicit testing of Rails before 5.2.
22
+ - Added CI testing of Rails version up to and including Rails 6.1.x with Ruby 3.0.x
23
+
24
+ ## 0.2.1 - 2018-07-01
25
+
26
+ - Loosening Rails version dependency, previously only allowed Rails 5.2+, now tested with Rails 4.2+
27
+
28
+ ## 0.2.0 - 2018-06-29
29
+
30
+ ### New features
31
+
32
+ - New gem, new project.
33
+ - Added `LogrageRailsRequestQueuing::SilenceExceptionLogging` to optionally quiet down exception logging.
34
+ - Added `LogrageRailsRequestQueuing::ExceptionDetails` to compactly report exception details in the log.
35
+ - Added support for Heroku request started timestamps that are given in ms not seconds.
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at laust@object.io. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Laust Rud Jacobsen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # `lograge_waittime`
2
+
3
+ [![Ruby](https://github.com/rud/lograge_waittime/actions/workflows/ruby.yml/badge.svg)](https://github.com/rud/lograge_waittime/actions/workflows/ruby.yml)
4
+
5
+ [Lograge](https://github.com/roidrage/lograge) makes Rails logging output a lot more more useful.
6
+
7
+ The log output for a request looks something like this:
8
+
9
+ ```
10
+ status=200 duration=58.33 view=40.43 db=15.26 controller=WelcomeController action=show
11
+ ```
12
+
13
+ This gem adds another field with how long the request waited to be processed, since arriving in the request queue in NGINX.
14
+ The unit is milliseconds, and represented as the `wait` value:
15
+
16
+ ```
17
+ status=200 duration=58.33 view=40.43 db=15.26 wait=3.14 controller=WelcomeController action=show
18
+ ```
19
+
20
+ Wait time or request queueing time is the time that passes between a request is received in the webserver (typically NGINX), and until it hits the Rails stack in a web worker.
21
+
22
+ Under normal load in production this value will be in the order of a few milliseconds.
23
+ However, if all Rails web-processes are busy, the number will quickly climb as individual requests are queued and waiting to be served.
24
+
25
+ It's one of the key numbers that are good to keep an eye on in monitoring and is very helpful to include when graphing response times over time.
26
+ Long wait times will feel like a sluggish site for end users, and even though you may be seeing short durations to process requests, if the wait-time is large, then the end-user experience will still be bad.
27
+
28
+ ## Docker demo setup
29
+
30
+ To quickly get a feel for the parts of the setup, a `docker-compose` sample configuration is included.
31
+ The setup is fairly simple: requests first hit an NGINX instance, then they are forwarded to the Rails app.
32
+ By looking at the log-output you can observe the request queueing time for each request as `wait`.
33
+
34
+ Try it out by running:
35
+
36
+ ```
37
+ docker-compose up
38
+ ```
39
+
40
+ Then you can visit [http://localhost:3030/echo](http://localhost:3030/echo) and you will now see the live `wait=` output in the Rails log, like this:
41
+
42
+ ```
43
+ method=GET path=/echo format=html controller=EchosController action=index status=200 duration=1.67 view=0.37 wait=1067.24
44
+ ```
45
+
46
+ This particular example was the first request to a backend not yet fully started.
47
+ This next request was through a warmed up stack:
48
+
49
+ ```
50
+ method=GET path=/echo format=html controller=EchosController action=index status=200 duration=0.60 view=0.28 wait=14.37
51
+ ```
52
+
53
+ ## Installation
54
+
55
+ ### NGINX change
56
+
57
+ In your NGINX config, add:
58
+ ```
59
+ proxy_set_header X-Request-Start "t=${msec}";
60
+ ```
61
+ to the relevant part of your server setup. According to [NGINX docs](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header), it can go in one of `http`, `server`, or `location`.
62
+
63
+ This adds a new header to all incoming requests, with current time in milliseconds as the value.
64
+ `msec` is supported from NGINX releases 1.2.6 and 1.3.9.
65
+
66
+ ### Rails app changes:
67
+
68
+ Execute:
69
+
70
+ ``` shell
71
+ bundle add lograge_waittime
72
+ ```
73
+
74
+ Then add it to your existing lograge initializer, typically in `config/initializers/lograge.rb`.
75
+ If you are just setting up lograge, please check that projects documentation for up to date information.
76
+
77
+ ``` ruby
78
+ Rails.application.configure do
79
+ config.lograge.enabled = true
80
+
81
+ # Keep emitting the verbose logging for easier debug
82
+ config.lograge.keep_original_rails_log = !Rails.env.production?
83
+
84
+ config.lograge.custom_options = lambda do |event|
85
+ custom_options = {}
86
+
87
+ # lograge_waittime setup:
88
+ queued_ms = RequestStore[:lograge_waittime].queued_ms
89
+ custom_options[:wait] = queued_ms.round(2) if queued_ms
90
+
91
+ custom_options
92
+ end
93
+ end
94
+ ```
95
+
96
+ After this is deployed, you now get the `"wait=.."` value added to the output when the value is available.
97
+ If you do not see the `"wait=.."` value in logging out, please double check you have added the new header in your NGINX config and deployed it.
98
+
99
+ ## Development
100
+
101
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
102
+
103
+ If you want to test using a specific Rails version locally, use this method:
104
+
105
+ ``` shell-interaction
106
+ export RAILS_VERSION="~> 5.1"
107
+ bundle update
108
+ bundle exec rake
109
+ ```
110
+
111
+ If you prefer a oneliner for testing a combination, try this:
112
+ ```
113
+ (asdf shell ruby 2.7.6; export RAILS_VERSION="~> 5.1.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
114
+ (asdf shell ruby 2.7.6; export RAILS_VERSION="~> 6.0.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
115
+ (asdf shell ruby 2.7.6; export RAILS_VERSION="~> 6.1.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
116
+ (asdf shell ruby 3.0.4; export RAILS_VERSION="~> 6.1.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
117
+ (asdf shell ruby 3.0.4; export RAILS_VERSION="~> 7.0.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
118
+ (asdf shell ruby 3.1.2; export RAILS_VERSION="~> 7.0.0"; rm Gemfile.lock; bundle install; bundle exec rake ci)
119
+ ```
120
+
121
+ It assumes you have [`asdf`](https://asdf-vm.com/) installed to manage your ruby versions, which is very convenient.
122
+
123
+
124
+ 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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+
126
+ ## Contributing
127
+
128
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rud/lograge_rails_request_queuing. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
129
+
130
+ ## License
131
+
132
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
133
+
134
+ ## Code of Conduct
135
+
136
+ Everyone interacting in the LogrageRailsRequestQueuing project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rud/lograge_rails_request_queuing/blob/master/CODE_OF_CONDUCT.md).
137
+
138
+ ## Historical note
139
+
140
+ Until 2022-11 this gem was called [lograge_rails_request_queuing](https://rubygems.org/gems/lograge_rails_request_queuing).
141
+ It was renamed to make it easier to talk about.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "bundler/setup"
5
+ rescue LoadError
6
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ end
8
+
9
+ require "rdoc/task"
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = "rdoc"
13
+ rdoc.title = "LogrageRailsRequestQueuing"
14
+ rdoc.options << "--line-numbers"
15
+ rdoc.rdoc_files.include("README.md")
16
+ rdoc.rdoc_files.include("lib/**/*.rb")
17
+ end
18
+
19
+ require "bundler/gem_tasks"
20
+
21
+ require "rake/testtask"
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << "lib"
25
+ t.libs << "test"
26
+ t.test_files = FileList["test/**/*_test.rb"]
27
+ t.verbose = true
28
+ end
29
+
30
+ desc "Run all tests"
31
+ task ci: :test
32
+
33
+ task default: :test
@@ -0,0 +1,13 @@
1
+ module LogrageWaittime
2
+ # Hook into Rails loading
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace LogrageWaittime
5
+
6
+ config.autoload_once_paths << File.expand_path("lib", __dir__)
7
+
8
+ initializer "lograge_waittime.add_waittime_middleware" do |app|
9
+ # As we use RequestStore, setup our middleware after that one
10
+ app.middleware.insert_after RequestStore::Middleware, LogrageWaittime::RequestStartedMiddleware
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,52 @@
1
+ module LogrageWaittime
2
+ class RequestQueuing
3
+ # How long was the request queued for, in milliseconds
4
+ attr_reader :queued_ms
5
+ attr_reader :request_started_at, :request_queued_raw
6
+
7
+ REQUEST_START_HEADER = "HTTP_X_REQUEST_START"
8
+ EARLIEST_REQUEST_DATE = Time.new(2000)
9
+
10
+ def initialize(env, request_started_at = Time.zone.now.to_f)
11
+ @request_queued_raw = request_start_header(env)
12
+ @request_started_at = request_started_at
13
+ @queued_ms = calculate_queued_ms
14
+ end
15
+
16
+ def request_queued_at
17
+ return if request_queued_float.blank?
18
+ @request_queued_at ||= [1000, 1].each do |divisor|
19
+ adjusted = Time.zone.at(request_queued_float / divisor)
20
+ return adjusted if adjusted > EARLIEST_REQUEST_DATE
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def calculate_queued_ms
27
+ return if request_queued_at.blank?
28
+
29
+ waiting_interval_secs = request_started_at - request_queued_at.to_f
30
+ return if waiting_interval_secs < 0 # clocks out of alignment
31
+
32
+ waiting_interval_secs * 1000
33
+ end
34
+
35
+ def request_queued_float
36
+ return nil if request_queued_raw.blank?
37
+
38
+ # convert values of the form:
39
+ # "t=1529578997.145"
40
+ # "1529578997145"
41
+ # to a Float:
42
+
43
+ if request_queued_raw =~ /(t=)?([.\d+]+)/
44
+ Float(Regexp.last_match(2))
45
+ end
46
+ end
47
+
48
+ def request_start_header(env)
49
+ env[REQUEST_START_HEADER]
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ module LogrageWaittime
2
+ # Track at what time the request handling starts
3
+ class RequestStartedMiddleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ ::RequestStore[:lograge_waittime] = ::LogrageWaittime::RequestQueuing.new(env)
10
+ @app.call env
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module LogrageWaittime
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "zeitwerk"
2
+ loader = Zeitwerk::Loader.for_gem
3
+ loader.setup # ready!
4
+
5
+ module LogrageWaittime
6
+ end
7
+
8
+ loader.eager_load
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lograge_waittime
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Laust Rud Jacobsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lograge
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: request_store
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: railties
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: zeitwerk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - laust@valuestream.io
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - CHANGELOG.md
77
+ - CODE_OF_CONDUCT.md
78
+ - MIT-LICENSE
79
+ - README.md
80
+ - Rakefile
81
+ - lib/lograge_waittime.rb
82
+ - lib/lograge_waittime/engine.rb
83
+ - lib/lograge_waittime/request_queuing.rb
84
+ - lib/lograge_waittime/request_started_middleware.rb
85
+ - lib/lograge_waittime/version.rb
86
+ homepage: https://github.com/rud/lograge_waittime
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.1.6
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Add webserver request queueing time to lograge output.
109
+ test_files: []