full_request_logger 0.1

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: bbae2d623039dd9c1b3f1f956adcb5769a4eae0aaae14b2bcaa2f1272fd3d8ac
4
+ data.tar.gz: 8799755a93425af26cf02bb6fa38f53cc0087ff1cea0318845f26326dc0a5b0c
5
+ SHA512:
6
+ metadata.gz: 3d92ba086bb4f520f6b8e97143c7c82dbc69d7c8c1dd03fd27de96faa5d773c6cbc7a85d4f8e3db002422a8682ee6bf5d5202c4a078e2d3a7d79eb835882e5b5
7
+ data.tar.gz: 0e4bd4e58a52d3eac3d1c09ecda279c0aaa547cfeaf4802135bc82958ecd188bfe9728a8b82ea890b29d4b2c61eb90f0a4f05ec9e44647786c39846f98f5b1c1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'byebug'
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2019 Basecamp
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,45 @@
1
+ # Full Request Logger
2
+
3
+ Easy access to full request logs via a web UI. The recorder attaches to the existing Rails.logger instance,
4
+ and captures a copy of each log line into a per-thread buffer. When the request is over, the middleware makes
5
+ the recorder flush all the log lines that were recorded for that request as a compressed batch to an expiring Redis key.
6
+
7
+ Thus you no longer have to grep through log files or wrestle with logging pipelines to instantly see all the
8
+ log lines relevant to a request you just made. This is ideal for when you're testing a feature in the wild with
9
+ production-levels of data, which may reveal performance or other issues that you didn't catch in development.
10
+
11
+ ## Installation
12
+
13
+ ```ruby
14
+ # Gemfile
15
+ gem 'full_request_logger'
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ Add to development.rb and/or production.rb. Default time-to-live (TTL) for reach recorded request is 10 minutes,
21
+ and the default Redis storage is assumed to live on localhost, but both can be overwritten. Only configuration needed
22
+ is the enabled setting.
23
+
24
+ ```ruby
25
+ config.full_request_logger.enabled = true
26
+ config.full_request_logger.ttl = 1.hour
27
+ config.full_request_logger.redis = { host: "127.0.0.1", port: 36379, timeout: 1 }
28
+ ```
29
+
30
+ The request logs access can be protected behind http basic by adding the following credentials
31
+ (using `./bin/rails credentials:edit --environment production`):
32
+
33
+ ```
34
+ full_request_logger:
35
+ name: someone
36
+ password: something
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ Access request logs via `/rails/conductor/full_request_logger/request_logs/:id` where id is the X-Request-Id.
42
+
43
+ ## License
44
+
45
+ Full Request Logger is released under the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |test|
6
+ test.libs << "test"
7
+ test.test_files = FileList["test/*_test.rb"]
8
+ test.warning = false
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,20 @@
1
+ module Rails
2
+ class Conductor::FullRequestLogger::RequestLogsController < ActionController::Base
3
+ if credentials = FullRequestLogger.credentials
4
+ http_basic_authenticate_with credentials
5
+ end
6
+
7
+ layout "rails/conductor"
8
+
9
+ def show
10
+ if @logs = FullRequestLogger::Recorder.instance.retrieve(params[:id])
11
+ respond_to do |format|
12
+ format.html
13
+ format.text { send_data @logs, disposition: :attachment, filename: "#{params[:id]}.log" }
14
+ end
15
+ else
16
+ head :not_found
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>Rails Conductor: <%= yield :title %></title>
4
+ </head>
5
+ <body>
6
+ <%= yield %>
7
+ </body>
8
+ </html>
@@ -0,0 +1,5 @@
1
+ <h1>Request: <%= params[:id] %></h1>
2
+
3
+ <p><%= link_to "Download", rails_conductor_request_log_path(params[:id], format: :text) %></p>
4
+
5
+ <pre><%= @logs %></pre>
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ scope "rails/conductor/full_request_logger/", module: "rails/conductor/full_request_logger" do
5
+ resources :request_logs, only: :show, as: :rails_conductor_request_logs
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'full_request_logger'
3
+ s.version = '0.1'
4
+ s.authors = 'David Heinemeier Hansson'
5
+ s.email = 'david@basecamp.com'
6
+ s.summary = 'Make full request logs accessible via web UI'
7
+ s.homepage = 'https://github.com/basecamp/full_request_logger'
8
+ s.license = 'MIT'
9
+
10
+ s.required_ruby_version = '>= 2.6.0'
11
+
12
+ s.add_dependency 'activesupport', '>= 6.0.0'
13
+
14
+ s.add_development_dependency 'bundler', '~> 1.17'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- test/*`.split("\n")
18
+ end
@@ -0,0 +1,12 @@
1
+ require "full_request_logger/engine"
2
+
3
+ module FullRequestLogger
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Recorder
7
+
8
+ mattr_accessor :ttl
9
+ mattr_accessor :redis
10
+ mattr_accessor :enabled
11
+ mattr_accessor :credentials
12
+ end
@@ -0,0 +1,32 @@
1
+ require "rails/engine"
2
+ require "full_request_logger/middleware"
3
+
4
+ module FullRequestLogger
5
+ class Engine < Rails::Engine
6
+ isolate_namespace FullRequestLogger
7
+ config.eager_load_namespaces << FullRequestLogger
8
+
9
+ config.full_request_logger = ActiveSupport::OrderedOptions.new
10
+
11
+ initializer "full_request_logger.middleware" do
12
+ config.app_middleware.insert_after ::ActionDispatch::RequestId, FullRequestLogger::Middleware
13
+ end
14
+
15
+ initializer "full_request_logger.configs" do
16
+ config.after_initialize do |app|
17
+ FullRequestLogger.enabled = app.config.full_request_logger.enabled || false
18
+ FullRequestLogger.ttl = app.config.full_request_logger.ttl || 10.minutes
19
+ FullRequestLogger.redis = app.config.full_request_logger.redis || {}
20
+ FullRequestLogger.credentials = app.config.full_request_logger.credentials || app.credentials.full_request_logger
21
+ end
22
+ end
23
+
24
+ initializer "full_request_logger.recoder_attachment" do
25
+ config.after_initialize do |app|
26
+ if app.config.full_request_logger.enabled
27
+ FullRequestLogger::Recorder.instance.attach_to Rails.logger
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ module FullRequestLogger
2
+ class Middleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ @app.call(env).tap do
9
+ if FullRequestLogger.enabled
10
+ Recorder.instance.flush ActionDispatch::Request.new(env).request_id
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ class FullRequestLogger::Recorder
6
+ attr_reader :redis
7
+
8
+ def self.instance
9
+ @instance ||= new
10
+ end
11
+
12
+ def initialize
13
+ @redis = Redis.new FullRequestLogger.redis
14
+ end
15
+
16
+ def attach_to(logger)
17
+ logger.extend ActiveSupport::Logger.broadcast(
18
+ ActiveSupport::Logger.new(self)
19
+ )
20
+ end
21
+
22
+ def write(message)
23
+ messages << remove_ansi_colors(message)
24
+ end
25
+
26
+ def log
27
+ messages.join.strip
28
+ end
29
+
30
+ def flush(request_id)
31
+ if (log_to_be_flushed = log).present?
32
+ redis.setex \
33
+ request_key(request_id),
34
+ FullRequestLogger.ttl,
35
+ compress(log_to_be_flushed)
36
+ end
37
+ ensure
38
+ messages.clear
39
+ end
40
+
41
+ def retrieve(request_id)
42
+ if log = redis.get(request_key(request_id))
43
+ uncompress(log).force_encoding("utf-8")
44
+ end
45
+ end
46
+
47
+ # no-op needed for Logger to treat this as a valid log device
48
+ def close
49
+ redis.disconnect!
50
+ end
51
+
52
+ private
53
+ def messages
54
+ Thread.current[:full_request_logger_messages] ||= []
55
+ end
56
+
57
+ def remove_ansi_colors(message)
58
+ message.remove(/\e\[\d+m/)
59
+ end
60
+
61
+ def request_key(id)
62
+ "full_request_logger/requests/#{id}"
63
+ end
64
+
65
+ def compress(text)
66
+ Zlib::Deflate.deflate(text)
67
+ end
68
+
69
+ def uncompress(text)
70
+ Zlib::Inflate.inflate(text)
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ require 'active_support'
2
+ require 'active_support/testing/autorun'
3
+
4
+ require 'full_request_logger/record'
5
+
6
+ class RecorderTest < ActiveSupport::TestCase
7
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: full_request_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - David Heinemeier Hansson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 6.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.17'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.17'
41
+ description:
42
+ email: david@basecamp.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - Gemfile
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/controllers/rails/conductor/full_request_logger/request_logs_controller.rb
52
+ - app/views/layouts/rails/conductor.html.erb
53
+ - app/views/rails/conductor/full_request_logger/request_logs/show.html.erb
54
+ - config/routes.rb
55
+ - full_request_logger.gemspec
56
+ - lib/full_request_logger.rb
57
+ - lib/full_request_logger/engine.rb
58
+ - lib/full_request_logger/middleware.rb
59
+ - lib/full_request_logger/recorder.rb
60
+ - test/recorder_test.rb
61
+ homepage: https://github.com/basecamp/full_request_logger
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 2.6.0
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubygems_version: 3.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Make full request logs accessible via web UI
84
+ test_files:
85
+ - test/recorder_test.rb