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.
- checksums.yaml +7 -0
- data/README.md +194 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/doc/adr/0001-record-architecture-decisions.md +19 -0
- data/doc/adr/0002-avoid-global-configuration.md +35 -0
- data/doc/adr/0003-decouple-generation-and-handling.md +30 -0
- data/doc/adr/0004-use-stacked-handlers-to-solve-many-problems.md +51 -0
- data/doc/adr/0005-restrict-data-types-allowable-in-log-entries.md +27 -0
- data/doc/adr/0006-use-lock-free-io.md +29 -0
- data/examples/multi-threaded +21 -0
- data/examples/try-it +21 -0
- data/green_log.gemspec +35 -0
- data/lib/green_log.rb +6 -0
- data/lib/green_log/classic_logger.rb +80 -0
- data/lib/green_log/contextualizer.rb +26 -0
- data/lib/green_log/core_refinements.rb +73 -0
- data/lib/green_log/entry.rb +102 -0
- data/lib/green_log/json_writer.rb +48 -0
- data/lib/green_log/logger.rb +91 -0
- data/lib/green_log/rack/request_logging.rb +86 -0
- data/lib/green_log/severity.rb +49 -0
- data/lib/green_log/severity_filter.rb +26 -0
- data/lib/green_log/severity_threshold_support.rb +18 -0
- data/lib/green_log/simple_writer.rb +85 -0
- data/lib/green_log/version.rb +7 -0
- metadata +166 -0
|
@@ -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
|
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: []
|