green_log 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []