loga 2.0.0 → 2.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 +4 -4
- data/CHANGELOG.md +10 -2
- data/Gemfile +2 -1
- data/README.md +52 -8
- data/circle.yml +2 -1
- data/gemfiles/rails32.gemfile +2 -1
- data/gemfiles/rails40.gemfile +2 -1
- data/gemfiles/rails50.gemfile +2 -1
- data/gemfiles/sinatra14.gemfile +2 -1
- data/gemfiles/unit.gemfile +2 -1
- data/lib/loga.rb +0 -1
- data/lib/loga/configuration.rb +24 -43
- data/lib/loga/event.rb +0 -11
- data/lib/loga/formatters/gelf_formatter.rb +106 -0
- data/lib/loga/formatters/simple_formatter.rb +46 -0
- data/lib/loga/version.rb +1 -1
- data/spec/integration/rails/railtie_spec.rb +3 -8
- data/spec/integration/sinatra_spec.rb +41 -10
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/loga/configuration_spec.rb +2 -10
- data/spec/unit/loga/event_spec.rb +0 -30
- data/spec/unit/loga/{formatter_spec.rb → formatters/gelf_formatter_spec.rb} +1 -1
- data/spec/unit/loga/formatters/simple_formatter_spec.rb +127 -0
- metadata +8 -5
- data/lib/loga/formatter.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 26c6ec05bee75b2cdaf5cc763dec166cfad96ce5
|
4
|
+
data.tar.gz: 845ef8d271804c24f62195cefb13dae6dd961be9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 195a21b0f8ea0a2ac55c8b44f042116b32e2012bc18df2c781863041898ec1360ce0b45a14c3795d156830960ea3b7d076c919f4e75ea5de2f185d6c72966e14
|
7
|
+
data.tar.gz: 5f8c95fb86e68753e1da356fabf093c89bc3d7fb78b84ad29c569ca7352090546418e4ffdaf76f55508dc760621bb0b7239d15efa186c86ce72e18432b25f4f2
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
|
-
## [2.
|
7
|
+
## [2.1.0]
|
8
|
+
## [2.1.0.pre.1]
|
9
|
+
### Changed
|
10
|
+
- Replace `ActiveSupport::Logger::SimpleFormatter` with `Loga::Formatters::SimpleFormatter`
|
11
|
+
when using simple format. The formatter adds level, timestamp, pid and tags prepended to the message
|
12
|
+
|
13
|
+
## [2.0.0] - 2016-10-27
|
8
14
|
## [2.0.0.pre.3]
|
9
15
|
## [2.0.0.pre.2]
|
10
16
|
## [2.0.0.pre1]
|
@@ -42,7 +48,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
42
48
|
### Changed
|
43
49
|
- Silence ActionDispatch::DebugExceptions' logger
|
44
50
|
|
45
|
-
[2.
|
51
|
+
[2.1.0]: https://github.com/FundingCircle/loga/compare/v2.0.0...v2.0.0
|
52
|
+
[2.1.0.pre.1]: https://github.com/FundingCircle/loga/compare/v2.0.0...v2.1.0.pre.1
|
53
|
+
[2.0.0]: https://github.com/FundingCircle/loga/compare/v1.4.0...v2.0.0
|
46
54
|
[2.0.0.pre.3]: https://github.com/FundingCircle/loga/compare/v2.0.0.pre.2...v2.0.0.pre.3
|
47
55
|
[2.0.0.pre.2]: https://github.com/FundingCircle/loga/compare/v2.0.0.pre1...v2.0.0.pre.2
|
48
56
|
[2.0.0.pre1]: https://github.com/FundingCircle/loga/compare/v1.4.0...v2.0.0.pre1
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -17,7 +17,9 @@ Includes:
|
|
17
17
|
- [Reduced logs](#reduced-logs)
|
18
18
|
- [Request log tags](#request-log-tags)
|
19
19
|
- [Sinatra](#sinatra)
|
20
|
-
- [
|
20
|
+
- [Output example](#output-example)
|
21
|
+
- [GELF Format](#gelf-format)
|
22
|
+
- [Simple Format](#simple-format)
|
21
23
|
- [Road map](#road-map)
|
22
24
|
- [Contributing](#contributing)
|
23
25
|
- [Running tests](#running-tests)
|
@@ -45,7 +47,7 @@ end
|
|
45
47
|
|
46
48
|
Loga hooks into the Rails logger initialization process and defines its own logger for all environments.
|
47
49
|
|
48
|
-
The logger configuration
|
50
|
+
The logger configuration is adjusted based on the environment:
|
49
51
|
|
50
52
|
| | Production | Test | Development | Others |
|
51
53
|
|--------|------------|--------------|-------------|--------|
|
@@ -119,13 +121,23 @@ You can now use `Loga.logger` or assign it to your existing logger.
|
|
119
121
|
The above configuration also inserts two middleware:
|
120
122
|
|
121
123
|
- `Loga::Rack::RequestId` makes the request id available to the request logger
|
122
|
-
- `Loga::Rack::Logger`
|
124
|
+
- `Loga::Rack::Logger` logs requests
|
123
125
|
|
124
|
-
|
126
|
+
You can easily switch between formats by using the `LOGA_FORMAT`
|
127
|
+
environment variable. The `format` key in the options takes precedence over the
|
128
|
+
environment variable therefore it must be removed.
|
129
|
+
|
130
|
+
```
|
131
|
+
LOGA_FORMAT=simple rackup
|
132
|
+
```
|
133
|
+
|
134
|
+
## Output Example
|
135
|
+
|
136
|
+
### GELF Format
|
125
137
|
|
126
138
|
Rails request logger: (includes controller/action name):
|
127
139
|
|
128
|
-
`
|
140
|
+
`curl localhost:3000/ok -X GET -H "X-Request-Id: 12345"`
|
129
141
|
|
130
142
|
```json
|
131
143
|
{
|
@@ -133,7 +145,7 @@ Rails request logger: (includes controller/action name):
|
|
133
145
|
"_request.method": "GET",
|
134
146
|
"_request.path": "/ok",
|
135
147
|
"_request.params": {},
|
136
|
-
"_request.request_id": "
|
148
|
+
"_request.request_id": "12345",
|
137
149
|
"_request.request_ip": "127.0.0.1",
|
138
150
|
"_request.user_agent": null,
|
139
151
|
"_request.controller": "ApplicationController#ok",
|
@@ -141,7 +153,7 @@ Rails request logger: (includes controller/action name):
|
|
141
153
|
"_type": "request",
|
142
154
|
"_service.name": "my_app",
|
143
155
|
"_service.version": "1.0",
|
144
|
-
"_tags": "
|
156
|
+
"_tags": "12345",
|
145
157
|
"short_message": "GET /ok 200 in 0ms",
|
146
158
|
"timestamp": 1450150205.123,
|
147
159
|
"host": "example.com",
|
@@ -164,7 +176,7 @@ Loga.logger.info('I love Loga')
|
|
164
176
|
{
|
165
177
|
"_service.name": "my_app",
|
166
178
|
"_service.version": "v1.0.0",
|
167
|
-
"_tags": "",
|
179
|
+
"_tags": "12345",
|
168
180
|
"host": "example.com",
|
169
181
|
"level": 6,
|
170
182
|
"short_message": "I love Loga",
|
@@ -173,6 +185,38 @@ Loga.logger.info('I love Loga')
|
|
173
185
|
}
|
174
186
|
```
|
175
187
|
|
188
|
+
### Simple Format
|
189
|
+
|
190
|
+
Request logger:
|
191
|
+
|
192
|
+
`curl localhost:3000/ok -X GET -H "X-Request-Id: 12345"`
|
193
|
+
|
194
|
+
Rails
|
195
|
+
|
196
|
+
```
|
197
|
+
I, [2016-11-15T16:05:03.614081+00:00 #1][12345] Started GET "/ok" for ::1 at 2016-11-15 16:05:03 +0000
|
198
|
+
I, [2016-11-15T16:05:03.620176+00:00 #1][12345] Processing by ApplicationController#ok as HTML
|
199
|
+
I, [2016-11-15T16:05:03.624807+00:00 #1][12345] Rendering text template
|
200
|
+
I, [2016-11-15T16:05:03.624952+00:00 #1][12345] Rendered text template (0.0ms)
|
201
|
+
I, [2016-11-15T16:05:03.625137+00:00 #1][12345] Completed 200 OK in 5ms (Views: 4.7ms)
|
202
|
+
```
|
203
|
+
|
204
|
+
Sinatra
|
205
|
+
|
206
|
+
```
|
207
|
+
I, [2016-11-15T16:10:08.645521+00:00 #1][12345] GET /ok 200 in 0ms type=request data={:request=>{"status"=>200, "method"=>"GET", "path"=>"/ok", "params"=>{}, "request_id"=>"12345", "request_ip"=>"127.0.0.1", "user_agent"=>nil, "duration"=>0}}
|
208
|
+
```
|
209
|
+
|
210
|
+
Logger output:
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
Loga.logger.info('I love Loga')
|
214
|
+
```
|
215
|
+
|
216
|
+
```
|
217
|
+
I, [2015-12-15T09:30:05.123000+06:00 #999] I love Loga
|
218
|
+
```
|
219
|
+
|
176
220
|
## Road map
|
177
221
|
|
178
222
|
Consult the [milestones](https://github.com/FundingCircle/loga/milestones).
|
data/circle.yml
CHANGED
@@ -10,7 +10,8 @@ test:
|
|
10
10
|
- RACK_ENV=production rvm-exec 2.2.5 bundle exec appraisal rspec
|
11
11
|
- RACK_ENV=development rvm-exec 2.3.1 bundle exec appraisal rspec
|
12
12
|
- RACK_ENV=production rvm-exec 2.3.1 bundle exec appraisal rspec
|
13
|
-
- rvm-exec 2.
|
13
|
+
- rvm-exec 2.3.1 bundle exec rubocop
|
14
|
+
- rvm-exec 2.3.1 bundle exec codeclimate-test-reporter
|
14
15
|
deployment:
|
15
16
|
gemfury:
|
16
17
|
tag: /.*/
|
data/gemfiles/rails32.gemfile
CHANGED
data/gemfiles/rails40.gemfile
CHANGED
data/gemfiles/rails50.gemfile
CHANGED
data/gemfiles/sinatra14.gemfile
CHANGED
data/gemfiles/unit.gemfile
CHANGED
data/lib/loga.rb
CHANGED
data/lib/loga/configuration.rb
CHANGED
@@ -1,48 +1,42 @@
|
|
1
1
|
require 'active_support/core_ext/object/blank'
|
2
2
|
require 'active_support/version'
|
3
|
+
require 'loga/formatters/gelf_formatter'
|
4
|
+
require 'loga/formatters/simple_formatter'
|
3
5
|
require 'loga/service_version_strategies'
|
4
6
|
require 'logger'
|
5
7
|
require 'socket'
|
6
8
|
|
7
9
|
module Loga
|
8
10
|
class Configuration
|
9
|
-
DEFAULT_KEYS = %i(
|
10
|
-
device
|
11
|
-
filter_exceptions
|
12
|
-
filter_parameters
|
13
|
-
format
|
14
|
-
host
|
15
|
-
level
|
16
|
-
service_name
|
17
|
-
service_version
|
18
|
-
sync
|
19
|
-
tags
|
20
|
-
).freeze
|
21
|
-
|
22
11
|
FRAMEWORK_EXCEPTIONS = %w(
|
23
12
|
ActionController::RoutingError
|
24
13
|
ActiveRecord::RecordNotFound
|
25
14
|
Sinatra::NotFound
|
26
15
|
).freeze
|
27
16
|
|
28
|
-
attr_accessor
|
29
|
-
|
30
|
-
|
17
|
+
attr_accessor :device, :filter_exceptions, :filter_parameters, :format,
|
18
|
+
:host, :level, :service_name, :service_version, :sync, :tags
|
19
|
+
attr_reader :logger
|
31
20
|
|
32
21
|
def initialize(user_options = {}, framework_options = {})
|
33
22
|
options = default_options.merge(framework_options)
|
34
23
|
.merge(environment_options)
|
35
24
|
.merge(user_options)
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
|
26
|
+
self.device = options[:device]
|
27
|
+
self.filter_exceptions = options[:filter_exceptions]
|
28
|
+
self.filter_parameters = options[:filter_parameters]
|
29
|
+
self.format = options[:format]
|
30
|
+
self.host = options[:host]
|
31
|
+
self.level = options[:level]
|
32
|
+
self.service_name = options[:service_name]
|
33
|
+
self.service_version = options[:service_version] || ServiceVersionStrategies.call
|
34
|
+
self.sync = options[:sync]
|
35
|
+
self.tags = options[:tags]
|
40
36
|
|
41
|
-
|
42
|
-
raise ConfigurationError, 'Device cannot be blank' if device.blank?
|
37
|
+
validate
|
43
38
|
|
44
|
-
@
|
45
|
-
@logger = initialize_logger
|
39
|
+
@logger = initialize_logger
|
46
40
|
end
|
47
41
|
|
48
42
|
def format=(name)
|
@@ -59,6 +53,11 @@ module Loga
|
|
59
53
|
|
60
54
|
private
|
61
55
|
|
56
|
+
def validate
|
57
|
+
raise ConfigurationError, 'Service name cannot be blank' if service_name.blank?
|
58
|
+
raise ConfigurationError, 'Device cannot be blank' if device.blank?
|
59
|
+
end
|
60
|
+
|
62
61
|
def default_options
|
63
62
|
{
|
64
63
|
device: STDOUT,
|
@@ -76,10 +75,6 @@ module Loga
|
|
76
75
|
{ format: ENV['LOGA_FORMAT'].presence }.reject { |_, v| v.nil? }
|
77
76
|
end
|
78
77
|
|
79
|
-
def initialize_service_version
|
80
|
-
service_version || ServiceVersionStrategies.call
|
81
|
-
end
|
82
|
-
|
83
78
|
def initialize_logger
|
84
79
|
device.sync = sync
|
85
80
|
logger = Logger.new(device)
|
@@ -100,27 +95,13 @@ module Loga
|
|
100
95
|
|
101
96
|
def assign_formatter
|
102
97
|
if format == :gelf
|
103
|
-
|
98
|
+
Formatters::GELFFormatter.new(
|
104
99
|
service_name: service_name,
|
105
100
|
service_version: service_version,
|
106
101
|
host: host,
|
107
102
|
)
|
108
103
|
else
|
109
|
-
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def active_support_simple_formatter
|
114
|
-
case ActiveSupport::VERSION::MAJOR
|
115
|
-
when 3
|
116
|
-
require 'active_support/core_ext/logger'
|
117
|
-
Logger::SimpleFormatter.new
|
118
|
-
when 4..5
|
119
|
-
require 'active_support/logger'
|
120
|
-
ActiveSupport::Logger::SimpleFormatter.new
|
121
|
-
else
|
122
|
-
raise Loga::ConfigurationError,
|
123
|
-
"ActiveSupport #{ActiveSupport::VERSION::MAJOR} is unsupported"
|
104
|
+
Formatters::SimpleFormatter.new
|
124
105
|
end
|
125
106
|
end
|
126
107
|
end
|
data/lib/loga/event.rb
CHANGED
@@ -10,17 +10,6 @@ module Loga
|
|
10
10
|
@type = opts[:type]
|
11
11
|
end
|
12
12
|
|
13
|
-
def to_s
|
14
|
-
output = ["#{timestamp.iso8601(3)} #{message}"]
|
15
|
-
if exception
|
16
|
-
output.push exception.to_s
|
17
|
-
output.push exception.backtrace.join("\n")
|
18
|
-
end
|
19
|
-
output.join("\n")
|
20
|
-
end
|
21
|
-
|
22
|
-
alias inspect to_s
|
23
|
-
|
24
13
|
private
|
25
14
|
|
26
15
|
# Guard against Encoding::UndefinedConversionError
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Loga
|
5
|
+
module Formatters
|
6
|
+
class GELFFormatter < Logger::Formatter
|
7
|
+
include TaggedLogging::Formatter
|
8
|
+
|
9
|
+
GELF_VERSION = '1.1'.freeze
|
10
|
+
SYSLOG_LEVEL_MAPPING = {
|
11
|
+
'DEBUG' => 7,
|
12
|
+
'INFO' => 6,
|
13
|
+
'WARN' => 4,
|
14
|
+
'ERROR' => 3,
|
15
|
+
'FATAL' => 2,
|
16
|
+
'UNKNOWN' => 1,
|
17
|
+
}.freeze
|
18
|
+
DEFAULT_TYPE = 'default'.freeze
|
19
|
+
|
20
|
+
def initialize(opts)
|
21
|
+
@service_name = opts.fetch(:service_name)
|
22
|
+
@service_version = opts.fetch(:service_version)
|
23
|
+
@host = opts.fetch(:host)
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(severity, time, _progname, message)
|
27
|
+
event = build_event(time, message)
|
28
|
+
payload = format_additional_fields(event.data)
|
29
|
+
|
30
|
+
payload[:short_message] = event.message
|
31
|
+
payload[:timestamp] = compute_timestamp(event.timestamp)
|
32
|
+
payload[:host] = @host
|
33
|
+
payload[:level] = compute_level(severity)
|
34
|
+
payload[:version] = GELF_VERSION
|
35
|
+
|
36
|
+
"#{payload.to_json}\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def build_event(time, message)
|
42
|
+
event = case message
|
43
|
+
when Loga::Event
|
44
|
+
message
|
45
|
+
else
|
46
|
+
Loga::Event.new(message: message)
|
47
|
+
end
|
48
|
+
|
49
|
+
event.timestamp ||= time
|
50
|
+
event.data ||= {}
|
51
|
+
event.data.tap do |hash|
|
52
|
+
hash.merge! compute_exception(event.exception)
|
53
|
+
hash.merge! compute_type(event.type)
|
54
|
+
# Overwrite hash with Loga's additional fields
|
55
|
+
hash.merge! loga_additional_fields
|
56
|
+
end
|
57
|
+
event
|
58
|
+
end
|
59
|
+
|
60
|
+
def compute_timestamp(timestamp)
|
61
|
+
(timestamp.to_f * 1000).floor / 1000.0
|
62
|
+
end
|
63
|
+
|
64
|
+
def compute_level(severity)
|
65
|
+
SYSLOG_LEVEL_MAPPING[severity]
|
66
|
+
end
|
67
|
+
|
68
|
+
def format_additional_fields(fields)
|
69
|
+
fields.each_with_object({}) do |(main_key, values), hash|
|
70
|
+
if values.is_a?(Hash)
|
71
|
+
values.each do |sub_key, sub_values|
|
72
|
+
hash["_#{main_key}.#{sub_key}"] = sub_values
|
73
|
+
end
|
74
|
+
else
|
75
|
+
hash["_#{main_key}"] = values
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def compute_exception(exception)
|
81
|
+
return {} unless exception
|
82
|
+
{
|
83
|
+
exception: {
|
84
|
+
klass: exception.class.to_s,
|
85
|
+
message: exception.message,
|
86
|
+
backtrace: exception.backtrace.first(10).join("\n"),
|
87
|
+
},
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def compute_type(type)
|
92
|
+
type ? { type: type } : {}
|
93
|
+
end
|
94
|
+
|
95
|
+
def loga_additional_fields
|
96
|
+
{
|
97
|
+
service: {
|
98
|
+
name: @service_name,
|
99
|
+
version: @service_version,
|
100
|
+
},
|
101
|
+
tags: current_tags.join(' '),
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Loga
|
2
|
+
module Formatters
|
3
|
+
class SimpleFormatter < Logger::Formatter
|
4
|
+
include TaggedLogging::Formatter
|
5
|
+
|
6
|
+
FORMAT = "%s, [%s #%d]%s %s\n".freeze
|
7
|
+
|
8
|
+
def call(severity, time, _progname, object)
|
9
|
+
FORMAT % [
|
10
|
+
severity[0..0],
|
11
|
+
time.iso8601(6),
|
12
|
+
Process.pid,
|
13
|
+
tags,
|
14
|
+
compute_message(object),
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def compute_message(object)
|
21
|
+
case object
|
22
|
+
when Loga::Event
|
23
|
+
compute_event_message(object)
|
24
|
+
else
|
25
|
+
msg2str(object)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def compute_event_message(event)
|
30
|
+
components = [event.message]
|
31
|
+
|
32
|
+
%i(type data exception).each do |attr|
|
33
|
+
if event.public_send(attr)
|
34
|
+
components.push "#{attr}=#{event.public_send(attr)}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
components.join(' ')
|
39
|
+
end
|
40
|
+
|
41
|
+
def tags
|
42
|
+
current_tags.empty? ? '' : "[#{current_tags.join(' ')}]"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/loga/version.rb
CHANGED
@@ -6,13 +6,7 @@ RSpec.describe Loga::Railtie do
|
|
6
6
|
|
7
7
|
context 'development', if: Rails.env.development? do
|
8
8
|
describe 'loga_initialize_logger' do
|
9
|
-
let(:formatter)
|
10
|
-
if ActiveSupport::VERSION::MAJOR == 3
|
11
|
-
Logger::SimpleFormatter
|
12
|
-
else
|
13
|
-
ActiveSupport::Logger::SimpleFormatter
|
14
|
-
end
|
15
|
-
end
|
9
|
+
let(:formatter) { Loga::Formatters::SimpleFormatter }
|
16
10
|
|
17
11
|
it 'assign Loga logger to Rails logger' do
|
18
12
|
expect(Loga.logger).to equal(Rails.logger)
|
@@ -31,7 +25,8 @@ RSpec.describe Loga::Railtie do
|
|
31
25
|
end
|
32
26
|
|
33
27
|
it 'configures Loga with a structured formatter' do
|
34
|
-
expect(Loga.configuration.logger.formatter)
|
28
|
+
expect(Loga.configuration.logger.formatter)
|
29
|
+
.to be_a(Loga::Formatters::GELFFormatter)
|
35
30
|
end
|
36
31
|
|
37
32
|
it 'disables colorized logging' do
|
@@ -62,37 +62,68 @@ RSpec.describe 'Structured logging with Sinatra', timecop: true do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
|
65
|
+
# rubocop:disable Metrics/LineLength
|
66
|
+
context 'when RACK_ENV is development', if: ENV['RACK_ENV'].eql?('development') do
|
66
67
|
let(:format) { :simple }
|
67
68
|
let(:last_log_entry) do
|
68
69
|
io.rewind
|
69
70
|
io.read
|
70
71
|
end
|
72
|
+
let(:data) do
|
73
|
+
{
|
74
|
+
'status' => 200,
|
75
|
+
'method' => 'GET',
|
76
|
+
'path' => '/ok',
|
77
|
+
'params' => { 'username'=>'yoshi' },
|
78
|
+
'request_id' => '700a6a01',
|
79
|
+
'request_ip' => '127.0.0.1',
|
80
|
+
'user_agent' => nil,
|
81
|
+
'duration' => 0,
|
82
|
+
}
|
83
|
+
end
|
84
|
+
let(:data_as_text) { "data=#{{ request: data }.inspect}" }
|
85
|
+
let(:time_pid_tags) { '[2015-12-15T09:30:05.123000+06:00 #999][700a6a01 TEST_TAG]' }
|
86
|
+
|
87
|
+
before do
|
88
|
+
allow(Process).to receive(:pid).and_return(999)
|
89
|
+
end
|
71
90
|
|
72
91
|
context 'get request' do
|
73
92
|
it 'logs the request' do
|
74
|
-
get '/ok', username: 'yoshi'
|
75
|
-
|
76
|
-
|
93
|
+
get '/ok', { username: 'yoshi' }, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
94
|
+
|
95
|
+
expect(last_log_entry).to eq("I, #{time_pid_tags} GET /ok?username=yoshi 200 in 0ms type=request #{data_as_text}\n")
|
77
96
|
end
|
78
97
|
end
|
79
98
|
|
80
99
|
context 'request with redirect' do
|
100
|
+
let(:data) do
|
101
|
+
super().merge(
|
102
|
+
'status' => 302,
|
103
|
+
'path' => '/new',
|
104
|
+
'params' => {},
|
105
|
+
)
|
106
|
+
end
|
81
107
|
it 'specifies the original path' do
|
82
|
-
get '/new'
|
83
|
-
expect(last_log_entry).to eql("#{
|
108
|
+
get '/new', {}, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
109
|
+
expect(last_log_entry).to eql("I, #{time_pid_tags} GET /new 302 in 0ms type=request #{data_as_text}\n")
|
84
110
|
end
|
85
111
|
end
|
86
112
|
|
87
113
|
context 'when the request raises an exception' do
|
88
|
-
let(:
|
89
|
-
|
114
|
+
let(:data) do
|
115
|
+
super().merge(
|
116
|
+
'status' => 500,
|
117
|
+
'path' => '/error',
|
118
|
+
'params' => {},
|
119
|
+
)
|
90
120
|
end
|
91
121
|
|
92
122
|
it 'logs the request with the exception' do
|
93
|
-
get '/error'
|
94
|
-
expect(last_log_entry).to
|
123
|
+
get '/error', {}, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
124
|
+
expect(last_log_entry).to eql("E, #{time_pid_tags} GET /error 500 in 0ms type=request #{data_as_text} exception=undefined method `name' for nil:NilClass\n")
|
95
125
|
end
|
96
126
|
end
|
97
127
|
end
|
128
|
+
# rubocop:enable Metrics/LineLength
|
98
129
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'codeclimate-test-reporter'
|
2
1
|
require 'pry'
|
3
2
|
require 'support/helpers'
|
4
3
|
require 'support/timecop_shared'
|
5
4
|
require 'support/request_spec'
|
6
5
|
require 'rack/test'
|
6
|
+
require 'simplecov'
|
7
7
|
|
8
|
-
|
8
|
+
SimpleCov.start
|
9
9
|
|
10
10
|
class Socket
|
11
11
|
def self.gethostname
|
@@ -150,7 +150,7 @@ describe Loga::Configuration do
|
|
150
150
|
let(:formatter) { subject.logger.formatter }
|
151
151
|
|
152
152
|
it 'uses the GELF formatter' do
|
153
|
-
expect(subject.logger.formatter).to be_a(Loga::
|
153
|
+
expect(subject.logger.formatter).to be_a(Loga::Formatters::GELFFormatter)
|
154
154
|
end
|
155
155
|
|
156
156
|
it 'strips the service name' do
|
@@ -162,15 +162,7 @@ describe Loga::Configuration do
|
|
162
162
|
let(:options) { super().merge(format: :simple) }
|
163
163
|
|
164
164
|
it 'uses the SimpleFormatter' do
|
165
|
-
expect(subject.logger.formatter).to be_a(
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
context 'when the ActiveSupport::VERSION is unsupported' do
|
170
|
-
it 'raises an error' do
|
171
|
-
stub_const('ActiveSupport::VERSION::MAJOR', 1)
|
172
|
-
expect { described_class.new(options) }
|
173
|
-
.to raise_error(Loga::ConfigurationError, 'ActiveSupport 1 is unsupported')
|
165
|
+
expect(subject.logger.formatter).to be_a(Loga::Formatters::SimpleFormatter)
|
174
166
|
end
|
175
167
|
end
|
176
168
|
end
|
@@ -17,34 +17,4 @@ RSpec.describe Loga::Event, timecop: true do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
describe '#to_s' do
|
22
|
-
let(:opts) { { message: 'Hello World', timestamp: Time.now } }
|
23
|
-
subject { described_class.new(opts) }
|
24
|
-
|
25
|
-
context 'when exception' do
|
26
|
-
let(:exception) do
|
27
|
-
instance_double(StandardError, to_s: 'Some Message', backtrace: ['file'])
|
28
|
-
end
|
29
|
-
let(:opts) { super().merge(exception: exception) }
|
30
|
-
it 'outputs the message with exception' do
|
31
|
-
expect(subject.to_s)
|
32
|
-
.to eql("#{time_anchor.iso8601(3)} Hello World\nSome Message\nfile")
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
context 'when no exception' do
|
37
|
-
it 'outputs the message' do
|
38
|
-
expect(subject.to_s).to eql("#{time_anchor.iso8601(3)} Hello World")
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe '#inspect' do
|
44
|
-
subject { described_class.new message: 'Hey Siri', timestamp: Time.now }
|
45
|
-
|
46
|
-
it 'aliases to to_s' do
|
47
|
-
expect(subject.to_s).to eql(subject.inspect)
|
48
|
-
end
|
49
|
-
end
|
50
20
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'loga/formatters/simple_formatter'
|
3
|
+
|
4
|
+
# rubocop:disable Metrics/LineLength
|
5
|
+
describe Loga::Formatters::SimpleFormatter do
|
6
|
+
before { allow(Process).to receive(:pid).and_return(999) }
|
7
|
+
|
8
|
+
describe '#call(severity, time, _progname, message)' do
|
9
|
+
subject { super().call(severity, time_anchor, nil, message) }
|
10
|
+
|
11
|
+
let(:severity) { 'INFO' }
|
12
|
+
let(:message) { 'Tree house magic' }
|
13
|
+
let(:time_pid) { '[2015-12-15T09:30:05.123000+06:00 #999]' }
|
14
|
+
|
15
|
+
context 'when the message parameter is a String' do
|
16
|
+
specify do
|
17
|
+
expect(subject).to eq("I, #{time_pid} Tree house magic\n")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when the message parameter is a nil' do
|
22
|
+
let(:message) { nil }
|
23
|
+
specify do
|
24
|
+
expect(subject).to eq("I, #{time_pid} nil\n")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when message parameter is a Hash' do
|
29
|
+
let(:message) { { record: 'Wooden house' } }
|
30
|
+
|
31
|
+
specify do
|
32
|
+
expect(subject).to eq("I, #{time_pid} {:record=>\"Wooden house\"}\n")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when the message parameter is a Loga::Event' do
|
37
|
+
let(:options) { { message: 'Hello World' } }
|
38
|
+
let(:message) { Loga::Event.new(options) }
|
39
|
+
|
40
|
+
it 'the short_message is the Event message' do
|
41
|
+
expect(subject).to eq("I, #{time_pid} Hello World\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when the event has a timestamp' do
|
45
|
+
it 'uses the event timestamp'
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when the Event has a type' do
|
49
|
+
let(:options) { { message: 'Hello World', type: 'request' } }
|
50
|
+
|
51
|
+
specify do
|
52
|
+
expect(subject).to eq("I, #{time_pid} Hello World type=request\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when the Event has an exception' do
|
57
|
+
let(:backtrace) { %w(a b) }
|
58
|
+
let(:exception) do
|
59
|
+
StandardError.new('Foo Error').tap { |e| e.set_backtrace backtrace }
|
60
|
+
end
|
61
|
+
let(:options) { { exception: exception } }
|
62
|
+
|
63
|
+
it 'outputs the exception'
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when the event has data' do
|
67
|
+
let(:options) do
|
68
|
+
{
|
69
|
+
data: {
|
70
|
+
admin: true,
|
71
|
+
user: {
|
72
|
+
email: 'hello@world.com',
|
73
|
+
},
|
74
|
+
},
|
75
|
+
message: 'Hello World',
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
specify do
|
80
|
+
expect(subject).to eq("I, #{time_pid} Hello World data={:admin=>true, :user=>{:email=>\"hello@world.com\"}}\n")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when the event has data and a type' do
|
85
|
+
let(:options) do
|
86
|
+
{
|
87
|
+
data: { ssl: true },
|
88
|
+
message: 'Hello World',
|
89
|
+
type: 'request',
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
specify do
|
94
|
+
expect(subject).to eq("I, #{time_pid} Hello World type=request data={:ssl=>true}\n")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when tags are available' do
|
100
|
+
let(:tags) { %w(USER_54321 EmailWorker) }
|
101
|
+
|
102
|
+
before do
|
103
|
+
allow_any_instance_of(described_class).to receive(:current_tags).and_return(tags)
|
104
|
+
end
|
105
|
+
|
106
|
+
specify do
|
107
|
+
expect(subject).to eq("I, #{time_pid}[USER_54321 EmailWorker] #{message}\n")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
{
|
112
|
+
'DEBUG' => 'D',
|
113
|
+
'INFO' => 'I',
|
114
|
+
'WARN' => 'W',
|
115
|
+
'ERROR' => 'E',
|
116
|
+
'FATAL' => 'F',
|
117
|
+
'UNKNOWN' => 'U',
|
118
|
+
}.each do |ruby_severity, formatted_severity|
|
119
|
+
context "with severity #{ruby_severity}" do
|
120
|
+
let(:severity) { ruby_severity }
|
121
|
+
|
122
|
+
specify { expect(subject).to match(/^#{formatted_severity},/) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
# rubocop:enable Metrics/LineLength
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loga
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Funding Circle
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -222,7 +222,8 @@ files:
|
|
222
222
|
- lib/loga/ext/rails/rack/debug_exceptions.rb
|
223
223
|
- lib/loga/ext/rails/rack/logger.rb
|
224
224
|
- lib/loga/ext/rails/rack/logger3.rb
|
225
|
-
- lib/loga/
|
225
|
+
- lib/loga/formatters/gelf_formatter.rb
|
226
|
+
- lib/loga/formatters/simple_formatter.rb
|
226
227
|
- lib/loga/parameter_filter.rb
|
227
228
|
- lib/loga/rack/logger.rb
|
228
229
|
- lib/loga/rack/request.rb
|
@@ -337,7 +338,8 @@ files:
|
|
337
338
|
- spec/support/timecop_shared.rb
|
338
339
|
- spec/unit/loga/configuration_spec.rb
|
339
340
|
- spec/unit/loga/event_spec.rb
|
340
|
-
- spec/unit/loga/
|
341
|
+
- spec/unit/loga/formatters/gelf_formatter_spec.rb
|
342
|
+
- spec/unit/loga/formatters/simple_formatter_spec.rb
|
341
343
|
- spec/unit/loga/parameter_filter_spec.rb
|
342
344
|
- spec/unit/loga/rack/logger_spec.rb
|
343
345
|
- spec/unit/loga/rack/request_spec.rb
|
@@ -473,7 +475,8 @@ test_files:
|
|
473
475
|
- spec/support/timecop_shared.rb
|
474
476
|
- spec/unit/loga/configuration_spec.rb
|
475
477
|
- spec/unit/loga/event_spec.rb
|
476
|
-
- spec/unit/loga/
|
478
|
+
- spec/unit/loga/formatters/gelf_formatter_spec.rb
|
479
|
+
- spec/unit/loga/formatters/simple_formatter_spec.rb
|
477
480
|
- spec/unit/loga/parameter_filter_spec.rb
|
478
481
|
- spec/unit/loga/rack/logger_spec.rb
|
479
482
|
- spec/unit/loga/rack/request_spec.rb
|
data/lib/loga/formatter.rb
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
require 'json'
|
3
|
-
|
4
|
-
module Loga
|
5
|
-
class Formatter < Logger::Formatter
|
6
|
-
include TaggedLogging::Formatter
|
7
|
-
|
8
|
-
GELF_VERSION = '1.1'.freeze
|
9
|
-
SYSLOG_LEVEL_MAPPING = {
|
10
|
-
'DEBUG' => 7,
|
11
|
-
'INFO' => 6,
|
12
|
-
'WARN' => 4,
|
13
|
-
'ERROR' => 3,
|
14
|
-
'FATAL' => 2,
|
15
|
-
'UNKNOWN' => 1,
|
16
|
-
}.freeze
|
17
|
-
DEFAULT_TYPE = 'default'.freeze
|
18
|
-
|
19
|
-
def initialize(opts)
|
20
|
-
@service_name = opts.fetch(:service_name)
|
21
|
-
@service_version = opts.fetch(:service_version)
|
22
|
-
@host = opts.fetch(:host)
|
23
|
-
end
|
24
|
-
|
25
|
-
def call(severity, time, _progname, message)
|
26
|
-
event = build_event(time, message)
|
27
|
-
payload = format_additional_fields(event.data)
|
28
|
-
|
29
|
-
payload[:short_message] = event.message
|
30
|
-
payload[:timestamp] = compute_timestamp(event.timestamp)
|
31
|
-
payload[:host] = @host
|
32
|
-
payload[:level] = compute_level(severity)
|
33
|
-
payload[:version] = GELF_VERSION
|
34
|
-
|
35
|
-
"#{payload.to_json}\n"
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def build_event(time, message)
|
41
|
-
event = case message
|
42
|
-
when Loga::Event
|
43
|
-
message
|
44
|
-
else
|
45
|
-
Loga::Event.new(message: message)
|
46
|
-
end
|
47
|
-
|
48
|
-
event.timestamp ||= time
|
49
|
-
event.data ||= {}
|
50
|
-
event.data.tap do |hash|
|
51
|
-
hash.merge! compute_exception(event.exception)
|
52
|
-
hash.merge! compute_type(event.type)
|
53
|
-
# Overwrite hash with Loga's additional fields
|
54
|
-
hash.merge! loga_additional_fields
|
55
|
-
end
|
56
|
-
event
|
57
|
-
end
|
58
|
-
|
59
|
-
def compute_timestamp(timestamp)
|
60
|
-
(timestamp.to_f * 1000).floor / 1000.0
|
61
|
-
end
|
62
|
-
|
63
|
-
def compute_level(severity)
|
64
|
-
SYSLOG_LEVEL_MAPPING[severity]
|
65
|
-
end
|
66
|
-
|
67
|
-
def format_additional_fields(fields)
|
68
|
-
fields.each_with_object({}) do |(main_key, values), hash|
|
69
|
-
if values.is_a?(Hash)
|
70
|
-
values.each do |sub_key, sub_values|
|
71
|
-
hash["_#{main_key}.#{sub_key}"] = sub_values
|
72
|
-
end
|
73
|
-
else
|
74
|
-
hash["_#{main_key}"] = values
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def compute_exception(exception)
|
80
|
-
return {} unless exception
|
81
|
-
{
|
82
|
-
exception: {
|
83
|
-
klass: exception.class.to_s,
|
84
|
-
message: exception.message,
|
85
|
-
backtrace: exception.backtrace.first(10).join("\n"),
|
86
|
-
},
|
87
|
-
}
|
88
|
-
end
|
89
|
-
|
90
|
-
def compute_type(type)
|
91
|
-
type ? { type: type } : {}
|
92
|
-
end
|
93
|
-
|
94
|
-
def loga_additional_fields
|
95
|
-
{
|
96
|
-
service: {
|
97
|
-
name: @service_name,
|
98
|
-
version: @service_version,
|
99
|
-
},
|
100
|
-
tags: current_tags.join(' '),
|
101
|
-
}
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|