green_log 0.1.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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack"
4
+
5
+ module GreenLog
6
+
7
+ module Rack
8
+
9
+ # Structured request logging.
10
+ class RequestLogging
11
+
12
+ def initialize(app, logger, log_request_bodies: false)
13
+ @app = app
14
+ @logger = logger
15
+ @log_request_bodies = log_request_bodies
16
+ end
17
+
18
+ def call(env)
19
+ started_at = Time.now
20
+ status, headers, _body = @app.call(env)
21
+ ensure
22
+ status ||= 500
23
+ headers ||= {}
24
+ duration = Time.now - started_at
25
+ log(
26
+ request: ::Rack::Request.new(env),
27
+ response: Response.new(status, headers, duration),
28
+ )
29
+ end
30
+
31
+ Response = Struct.new(:status, :headers, :duration)
32
+
33
+ protected
34
+
35
+ attr_reader :logger
36
+ attr_reader :log_request_bodies
37
+
38
+ def log(request:, response:)
39
+ logger.info(
40
+ "#{request.request_method} #{request.path} #{response.status}",
41
+ request: request_details(request),
42
+ response: response_details(response),
43
+ )
44
+ end
45
+
46
+ def request_details(request)
47
+ {
48
+ method: request.request_method,
49
+ scheme: request.scheme,
50
+ host: request.host,
51
+ path: request.path,
52
+ query: request.query_string,
53
+ remote_user: request.env["REMOTE_USER"],
54
+ remote_addr: request.ip,
55
+ }.merge(optional_request_details(request))
56
+ end
57
+
58
+ def optional_request_details(request)
59
+ return {} unless log_request_bodies
60
+
61
+ {
62
+ body: request_body(request),
63
+ }
64
+ end
65
+
66
+ def request_body(request)
67
+ original_position = request.body.pos
68
+ request.body.rewind
69
+ request.body.read
70
+ ensure
71
+ request.body.seek(original_position)
72
+ end
73
+
74
+ def response_details(response)
75
+ {
76
+ status: response.status.to_i,
77
+ length: response.headers["Content-Length"].to_i,
78
+ duration: response.duration.round(3),
79
+ }
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GreenLog
4
+
5
+ # Levels of severity.
6
+ module Severity
7
+
8
+ # Low-level information, mostly for developers.
9
+ DEBUG = 0
10
+ # Generic (useful) information about system operation.
11
+ INFO = 1
12
+ # A warning.
13
+ WARN = 2
14
+ # A handleable error condition.
15
+ ERROR = 3
16
+ # An unhandleable error that results in a program crash.
17
+ FATAL = 4
18
+
19
+ NAMES = %i[DEBUG INFO WARN ERROR FATAL].freeze
20
+
21
+ class << self
22
+
23
+ def name(severity)
24
+ NAMES[severity]
25
+ end
26
+
27
+ def resolve(arg)
28
+ value = _resolve(arg)
29
+ return value if value && (DEBUG..FATAL).cover?(value)
30
+
31
+ raise ArgumentError, "invalid severity: #{arg.inspect}"
32
+ end
33
+
34
+ private
35
+
36
+ def _resolve(arg)
37
+ case arg
38
+ when Integer
39
+ arg
40
+ when Symbol, String
41
+ NAMES.index(arg.to_sym.upcase)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "green_log/severity"
4
+
5
+ module GreenLog
6
+
7
+ # Log middleware that filters by severity.
8
+ class SeverityFilter
9
+
10
+ def initialize(downstream, threshold:)
11
+ @downstream = downstream
12
+ @severity_threshold = GreenLog::Severity.resolve(threshold)
13
+ end
14
+
15
+ attr_reader :downstream
16
+ attr_reader :severity_threshold
17
+
18
+ def <<(entry)
19
+ return if entry.severity < severity_threshold
20
+
21
+ downstream << entry
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "green_log/severity"
4
+
5
+ module GreenLog
6
+
7
+ # Common logic for deriving a #severity_threshold.
8
+ module SeverityThresholdSupport
9
+
10
+ def severity_threshold
11
+ return downstream.severity_threshold if downstream.respond_to?(:severity_threshold)
12
+
13
+ GreenLog::Severity::DEBUG
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "green_log/entry"
4
+ require "green_log/severity"
5
+
6
+ module GreenLog
7
+
8
+ # A simple log formatter, aimed at humans.
9
+ class SimpleWriter
10
+
11
+ def initialize(dest)
12
+ @dest = dest
13
+ end
14
+
15
+ attr_reader :dest
16
+
17
+ def <<(entry)
18
+ raise ArgumentError, "GreenLog::Entry expected" unless entry.is_a?(GreenLog::Entry)
19
+
20
+ output = [
21
+ format_part(entry, :severity),
22
+ format_part(entry, :context),
23
+ "--",
24
+ format_part(entry, :message),
25
+ format_part(entry, :data),
26
+ ].compact.join(" ") + "\n"
27
+
28
+ output << format_exception(entry.exception)
29
+
30
+ dest << output
31
+ end
32
+
33
+ protected
34
+
35
+ def format_part(entry, part)
36
+ value = entry.public_send(part)
37
+ return nil if value.nil?
38
+
39
+ send("format_#{part}", value)
40
+ end
41
+
42
+ def format_severity(severity)
43
+ Severity.name(severity)[0].upcase
44
+ end
45
+
46
+ def format_data(data)
47
+ return nil if data.empty?
48
+
49
+ "[" + each_part_of(data).to_a.join(" ") + "]"
50
+ end
51
+
52
+ alias format_context format_data
53
+
54
+ def format_message(message)
55
+ message
56
+ end
57
+
58
+ def format_exception(exception)
59
+ return "" if exception.nil?
60
+
61
+ result = " ! #{exception.class.name}: #{exception.message}\n"
62
+ exception.backtrace.each do |line|
63
+ result += " #{line}\n"
64
+ end
65
+ result
66
+ end
67
+
68
+ private
69
+
70
+ def each_part_of(data, prefix = nil, &block)
71
+ return enum_for(:each_part_of, data, prefix) unless block_given?
72
+
73
+ data.each do |k, v|
74
+ label = [prefix, k].compact.join(".")
75
+ if v.is_a?(Hash)
76
+ each_part_of(v, label, &block)
77
+ else
78
+ yield "#{label}=#{v.inspect}"
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GreenLog
4
+
5
+ VERSION = "0.1.0"
6
+
7
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: green_log
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Williams
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-02-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: values
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.12.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.12.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.77'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.77'
111
+ description:
112
+ email:
113
+ - mike.williams@greensync.com.au
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - README.md
119
+ - bin/console
120
+ - bin/setup
121
+ - doc/adr/0001-record-architecture-decisions.md
122
+ - doc/adr/0002-avoid-global-configuration.md
123
+ - doc/adr/0003-decouple-generation-and-handling.md
124
+ - doc/adr/0004-use-stacked-handlers-to-solve-many-problems.md
125
+ - doc/adr/0005-restrict-data-types-allowable-in-log-entries.md
126
+ - doc/adr/0006-use-lock-free-io.md
127
+ - examples/multi-threaded
128
+ - examples/try-it
129
+ - green_log.gemspec
130
+ - lib/green_log.rb
131
+ - lib/green_log/classic_logger.rb
132
+ - lib/green_log/contextualizer.rb
133
+ - lib/green_log/core_refinements.rb
134
+ - lib/green_log/entry.rb
135
+ - lib/green_log/json_writer.rb
136
+ - lib/green_log/logger.rb
137
+ - lib/green_log/rack/request_logging.rb
138
+ - lib/green_log/severity.rb
139
+ - lib/green_log/severity_filter.rb
140
+ - lib/green_log/severity_threshold_support.rb
141
+ - lib/green_log/simple_writer.rb
142
+ - lib/green_log/version.rb
143
+ homepage: https://github.com/greensync/green_log
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 3.0.3
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Structured logging for cloud-native systems.
166
+ test_files: []