roda-enhanced_logger 0.3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 10033e09d39197a7f03ad6701bdb91cce5ed31888b160a2e46ac5c16917549eb
4
+ data.tar.gz: dc48be6ce4b9b65eab182baf4faeebc108a25e7037fc25146648b1583a8c9e7c
5
+ SHA512:
6
+ metadata.gz: 6e1bd2ea35322e882b794b740393f1377242fe7fd3fe9d2d2aff733ac3f36e910d11930a54639b3f50c73633013b1869c7592a7ca3556b14e88dd7a861b4bdc1
7
+ data.tar.gz: 2d1cf39e8cf2ce2b7c009edb32ad2f852869585f705045b8fb47c0018d0cbe846c68694e042db0c825e57fb81d15ec534c8881d62384dfc861c68f878f2be8cd
@@ -0,0 +1,86 @@
1
+ # Roda Enhanced Logger
2
+
3
+ A powerful logger for Roda with a few tricks up it's sleeve.
4
+
5
+ - Coloured output per level
6
+ - Structured output of query params
7
+ - Tracking of database query time and count
8
+ - Tracking of blocks which handle path segment
9
+ - Tracing missed requests
10
+ - Tracing all requests
11
+
12
+ ![Enhanced Logger Output](roda-enhanced_logger.png)
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem "roda-enhanced_logger"
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install roda-enhanced_logger
27
+
28
+ ## Usage
29
+
30
+ For basic usage, simply enable through the `plugin` mechanism.
31
+
32
+
33
+ ```ruby
34
+ class App < Roda
35
+ plugin :enhanced_logger
36
+ end
37
+ ```
38
+
39
+ If you serve assets through Roda, your logs might be fairly noisy, so you can
40
+ filter them.
41
+
42
+ ```ruby
43
+ plugin :enhanced_logger, filter: ->(path) { path.start_with?("/assets") }
44
+ ```
45
+
46
+ By default, EnhancedLogger will attempt to filter passwords and CSRF tokens,
47
+ but you can filter other fields too.
48
+
49
+ ```ruby
50
+ plugin :enhanced_logger, filtered_params: %w[api_key]
51
+ ```
52
+
53
+ If there's a `DB` constant defined for Sequel, EnhancedLogger will automatically
54
+ use it, but you can pass in a custom value if necessary.
55
+
56
+ ```ruby
57
+ plugin :enhanced_logger, db: Container[:db]
58
+ ```
59
+
60
+ During development, a 404 might catch you off guard for a path that you feel should
61
+ exist, so it's handy to trace missed routes to aide in debugging.
62
+
63
+ ```ruby
64
+ plugin :enhanced_logger, trace_missed: true
65
+ ```
66
+
67
+ Or always trace every request.
68
+
69
+ ```ruby
70
+ plugin :enhanced_logger, trace_all: true
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ Bug reports and pull requests are welcome on GitHub at https://github.com/adam12/roda-enhanced_logger.
76
+
77
+ I love pull requests! If you fork this project and modify it, please ping me to see
78
+ if your changes can be incorporated back into this project.
79
+
80
+ That said, if your feature idea is nontrivial, you should probably open an issue to
81
+ [discuss it](http://www.igvita.com/2011/12/19/dont-push-your-pull-requests/)
82
+ before attempting a pull request.
83
+
84
+ ## License
85
+
86
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,58 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "roda"
4
+
5
+ class Roda
6
+ module EnhancedLogger
7
+ ##
8
+ # Data collection for request in current thread
9
+ module Current
10
+ extend self
11
+
12
+ # Increment the accrued database time
13
+ # @param value [Numeric]
14
+ # the value to increment
15
+ # @return [Numeric]
16
+ # the updated value
17
+ def increment_accrued_database_time(value)
18
+ Thread.current[:accrued_database_time] ||= 0
19
+ Thread.current[:accrued_database_time] += value
20
+ end
21
+
22
+ # The accrued database time
23
+ # @return [Numeric]
24
+ def accrued_database_time
25
+ Thread.current[:accrued_database_time]
26
+ end
27
+
28
+ # Set accrued database time
29
+ # @param value [Numeric]
30
+ # the value to set
31
+ # @return [Numeric]
32
+ # the new value
33
+ def accrued_database_time=(value)
34
+ Thread.current[:accrued_database_time] = value
35
+ end
36
+
37
+ def increment_database_query_count(value = 1)
38
+ Thread.current[:database_query_count] ||= 0
39
+ Thread.current[:database_query_count] += value
40
+ end
41
+
42
+ def database_query_count
43
+ Thread.current[:database_query_count]
44
+ end
45
+
46
+ def database_query_count=(value)
47
+ Thread.current[:database_query_count] = value
48
+ end
49
+
50
+ # Reset the counters
51
+ def reset
52
+ self.accrued_database_time = nil
53
+ self.database_query_count = nil
54
+ true
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,135 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "roda"
4
+
5
+ class Roda
6
+ module EnhancedLogger
7
+ ##
8
+ # Logger instance for this application
9
+ class Instance
10
+ # Application root
11
+ attr_reader :root
12
+
13
+ # Log entries generated during request
14
+ attr_reader :log_entries
15
+
16
+ # Logger instance
17
+ attr_reader :logger
18
+
19
+ # Route matches during request
20
+ attr_reader :matches
21
+
22
+ attr_reader :timer
23
+ private :timer
24
+
25
+ # Callable object to filter log entries
26
+ attr_reader :filter
27
+
28
+ def initialize(logger, env, instance_id, root, filter)
29
+ @logger = logger
30
+ @root = root
31
+ @log_entries = []
32
+ @matches = []
33
+ @timer = Process.clock_gettime(Process::CLOCK_MONOTONIC)
34
+ @filter = filter || proc { false }
35
+ if env["enhanced_logger_id"].nil?
36
+ @primary = true
37
+ env["enhanced_logger_id"] = instance_id
38
+ else
39
+ @primary = false
40
+ end
41
+ end
42
+
43
+ # Add a matched route handler
44
+ def add_match(caller)
45
+ @matches << caller
46
+ end
47
+
48
+ # Add log entry for request
49
+ # @param status [Integer]
50
+ # status code for the response
51
+ # @param request [Roda::RodaRequest]
52
+ # request object
53
+ # @param trace [Boolean]
54
+ # tracing was enabled
55
+ def add(status, request, trace = false)
56
+ if (last_matched_caller = matches.last)
57
+ handler = format("%s:%d",
58
+ Pathname(last_matched_caller.path).relative_path_from(root),
59
+ last_matched_caller.lineno)
60
+ end
61
+
62
+ meth =
63
+ case status
64
+ when 400..499
65
+ :warn
66
+ when 500..599
67
+ :error
68
+ else
69
+ :info
70
+ end
71
+
72
+ data = {
73
+ duration: (Process.clock_gettime(Process::CLOCK_MONOTONIC) - timer).round(4),
74
+ status: status,
75
+ verb: request.request_method,
76
+ path: request.path,
77
+ remaining_path: request.remaining_path,
78
+ handler: handler,
79
+ params: request.params
80
+ }
81
+
82
+ if (db = Roda::EnhancedLogger::Current.accrued_database_time)
83
+ data[:db] = db.round(6)
84
+ end
85
+
86
+ if (query_count = Roda::EnhancedLogger::Current.database_query_count)
87
+ data[:db_queries] = query_count
88
+ end
89
+
90
+ if trace
91
+ matches.each do |match|
92
+ add_log_entry([meth, format(" %s (%s:%s)",
93
+ File.readlines(match.path)[match.lineno - 1].strip.sub(" do", ""),
94
+ Pathname(match.path).relative_path_from(root),
95
+ match.lineno)])
96
+ end
97
+ end
98
+
99
+ return if filter.call(request.path)
100
+
101
+ add_log_entry([meth, "#{request.request_method} #{request.path}", data])
102
+ end
103
+
104
+ # This instance is the primary logger
105
+ # @return [Boolean]
106
+ def primary?
107
+ @primary
108
+ end
109
+
110
+ # Drain the log entry queue, writing each to the logger at their respective level
111
+ # @return [Boolean]
112
+ def drain
113
+ return unless primary?
114
+
115
+ log_entries.each do |args|
116
+ logger.public_send(*args)
117
+ end
118
+
119
+ true
120
+ end
121
+
122
+ # Reset the counters for this thread
123
+ # @return [Boolean]
124
+ def reset
125
+ Roda::EnhancedLogger::Current.reset
126
+ end
127
+
128
+ private
129
+
130
+ def add_log_entry(record)
131
+ @log_entries << record
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,85 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "tty-logger"
4
+ require "roda/enhanced_logger/current"
5
+ require "roda/enhanced_logger/instance"
6
+
7
+ class Roda
8
+ module RodaPlugins
9
+ # The +enhanced_logger+ plugin provides a coloured, single line log
10
+ # entry for requests in a Roda application.
11
+ #
12
+ # Some interesting pieces of the log entry include which line matched the request,
13
+ # any time incurred by Sequel DB queries, and the remaining path that might have
14
+ # not been matched.
15
+ #
16
+ # It's mostly suitable in development but would likely be fine in production.
17
+ #
18
+ # @example Basic configuration
19
+ # plugin :enhanced_logger
20
+ #
21
+ # @example Filter requests to assets
22
+ # plugin :enahanced_logger, filter: ->(path) { path.start_with?("/assets") }
23
+ #
24
+ # @example Filter parameters
25
+ # plugin :enhanced_logger, filtered_params: %i[api_key]
26
+ #
27
+ module EnhancedLogger
28
+ DEFAULTS = {
29
+ db: nil,
30
+ log_time: false,
31
+ trace_missed: true,
32
+ trace_all: false,
33
+ filtered_params: %w[password password_confirmation _csrf],
34
+ handlers: [:console]
35
+ }.freeze
36
+
37
+ def self.load_dependencies(app, _opts = {})
38
+ app.plugin :hooks
39
+ app.plugin :match_hook
40
+ end
41
+
42
+ def self.configure(app, opts = {})
43
+ options = DEFAULTS.merge(opts)
44
+
45
+ logger = TTY::Logger.new { |config|
46
+ config.handlers = options[:handlers]
47
+ config.output = options.fetch(:output) { $stdout }
48
+ config.metadata = [:data, :time] if options[:log_time]
49
+ config.filters.data = options[:filtered_params].map(&:to_s)
50
+ config.filters.mask = "<FILTERED>"
51
+ }
52
+
53
+ root = Pathname(app.opts[:root] || Dir.pwd)
54
+
55
+ db = options[:db] || (defined?(DB) && DB)
56
+ db&.extension :enhanced_logger
57
+
58
+ app.match_hook do
59
+ callee = caller_locations.find { |location|
60
+ location.path.start_with?(root.to_s)
61
+ }
62
+
63
+ @_enhanced_logger_instance.add_match(callee)
64
+ end
65
+
66
+ app.before do
67
+ @_enhanced_logger_instance = Roda::EnhancedLogger::Instance.new(logger, env, object_id, root, options[:filter])
68
+ end
69
+
70
+ app.after do |status, _|
71
+ @_enhanced_logger_instance.add(
72
+ status,
73
+ request,
74
+ (options[:trace_missed] && status == 404) || options[:trace_all]
75
+ )
76
+
77
+ @_enhanced_logger_instance.drain
78
+ @_enhanced_logger_instance.reset
79
+ end
80
+ end
81
+ end
82
+
83
+ register_plugin :enhanced_logger, EnhancedLogger
84
+ end
85
+ end
@@ -0,0 +1,29 @@
1
+ # frozen-string-literal: true
2
+
3
+ require "sequel"
4
+
5
+ module EnhancedLogger
6
+ module Sequel
7
+ if ::Sequel::VERSION_NUMBER >= 50240
8
+ def skip_logging?
9
+ false
10
+ end
11
+ else
12
+ def self.extended(base)
13
+ return if base.loggers.any?
14
+
15
+ require "logger"
16
+ base.loggers = [Logger.new("/dev/null")]
17
+ end
18
+ end
19
+
20
+ def log_duration(duration, _message)
21
+ Roda::EnhancedLogger::Current.increment_accrued_database_time(duration)
22
+ Roda::EnhancedLogger::Current.increment_database_query_count
23
+
24
+ super
25
+ end
26
+ end
27
+
28
+ ::Sequel::Database.register_extension :enhanced_logger, EnhancedLogger::Sequel
29
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roda-enhanced_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Daniels
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: roda
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.19.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.19.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-logger
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0.3'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ description:
48
+ email: adam@mediadrive.ca
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - README.md
54
+ - lib/roda/enhanced_logger/current.rb
55
+ - lib/roda/enhanced_logger/instance.rb
56
+ - lib/roda/plugins/enhanced_logger.rb
57
+ - lib/sequel/extensions/enhanced_logger.rb
58
+ homepage: https://github.com/adam12/roda-enhanced_logger
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '2.1'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.1.4
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: An enhanced logger for Roda applications
81
+ test_files: []