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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58717f46fed6f5a99834472829baef870624e67d
4
- data.tar.gz: efbaf0fcdb8c50a0d5193ecd1be1afcc03d4fa19
3
+ metadata.gz: 26c6ec05bee75b2cdaf5cc763dec166cfad96ce5
4
+ data.tar.gz: 845ef8d271804c24f62195cefb13dae6dd961be9
5
5
  SHA512:
6
- metadata.gz: 62433f9172394016bc78a9b49a6c42f90827e925e1b5b5e9704367782705f821c69ff43afc5feb7a25f1eb87ff4a9e4084ad4d5b1e264d19a0c0a4e3feb1b9d7
7
- data.tar.gz: fe2281a9a05f805402aab31da3add62067a718e58c0cc7c28bd6731e587301b7cd9dd5f4d86672303b5e93d332ec9a6acc1e11ff38db7ff288774dfd76b668b4
6
+ metadata.gz: 195a21b0f8ea0a2ac55c8b44f042116b32e2012bc18df2c781863041898ec1360ce0b45a14c3795d156830960ea3b7d076c919f4e75ea5de2f185d6c72966e14
7
+ data.tar.gz: 5f8c95fb86e68753e1da356fabf093c89bc3d7fb78b84ad29c569ca7352090546418e4ffdaf76f55508dc760621bb0b7239d15efa186c86ce72e18432b25f4f2
@@ -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.0.0]
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.0.0]: https://github.com/FundingCircle/loga/compare/v2.0.0.pre.3...v2.0.0
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
@@ -4,5 +4,6 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :test do
7
- gem 'codeclimate-test-reporter', require: false
7
+ gem 'simplecov'
8
+ gem 'codeclimate-test-reporter', '~> 1.0.0', require: false
8
9
  end
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
- - [GELF Output example](#gelf-output-example)
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 adjusts based on the environment:
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` logs requests
124
+ - `Loga::Rack::Logger` logs requests
123
125
 
124
- ## GELF Output Example
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
- `GET /ok`
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": "2b99e3d3-3ee2-4781-972b-782682f57648",
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": "2b99e3d3-3ee2-4781-972b-782682f57648",
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.2.5 bundle exec rubocop
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: /.*/
@@ -5,7 +5,8 @@ source "https://rubygems.org"
5
5
  gem "rails", "~> 3.2.0"
6
6
 
7
7
  group :test do
8
- gem "codeclimate-test-reporter", :require => false
8
+ gem "simplecov"
9
+ gem "codeclimate-test-reporter", "~> 1.0.0", :require => false
9
10
  end
10
11
 
11
12
  gemspec :path => "../"
@@ -5,7 +5,8 @@ source "https://rubygems.org"
5
5
  gem "rails", "~> 4.0.0"
6
6
 
7
7
  group :test do
8
- gem "codeclimate-test-reporter", :require => false
8
+ gem "simplecov"
9
+ gem "codeclimate-test-reporter", "~> 1.0.0", :require => false
9
10
  end
10
11
 
11
12
  gemspec :path => "../"
@@ -5,7 +5,8 @@ source "https://rubygems.org"
5
5
  gem "rails", "~> 5.0.0"
6
6
 
7
7
  group :test do
8
- gem "codeclimate-test-reporter", :require => false
8
+ gem "simplecov"
9
+ gem "codeclimate-test-reporter", "~> 1.0.0", :require => false
9
10
  end
10
11
 
11
12
  gemspec :path => "../"
@@ -5,7 +5,8 @@ source "https://rubygems.org"
5
5
  gem "sinatra", "~> 1.4.0"
6
6
 
7
7
  group :test do
8
- gem "codeclimate-test-reporter", :require => false
8
+ gem "simplecov"
9
+ gem "codeclimate-test-reporter", "~> 1.0.0", :require => false
9
10
  end
10
11
 
11
12
  gemspec :path => "../"
@@ -3,7 +3,8 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  group :test do
6
- gem "codeclimate-test-reporter", :require => false
6
+ gem "simplecov"
7
+ gem "codeclimate-test-reporter", "~> 1.0.0", :require => false
7
8
  end
8
9
 
9
10
  gemspec :path => "../"
@@ -3,7 +3,6 @@ require 'loga/tagged_logging'
3
3
  require 'loga/configuration'
4
4
  require 'loga/utilities'
5
5
  require 'loga/event'
6
- require 'loga/formatter'
7
6
  require 'loga/parameter_filter'
8
7
  require 'loga/rack/logger'
9
8
  require 'loga/rack/request'
@@ -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(*DEFAULT_KEYS)
29
- attr_reader :logger, :service_version
30
- private_constant :DEFAULT_KEYS
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
- DEFAULT_KEYS.each do |attribute|
38
- public_send("#{attribute}=", options[attribute])
39
- end
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
- raise ConfigurationError, 'Service name cannot be blank' if service_name.blank?
42
- raise ConfigurationError, 'Device cannot be blank' if device.blank?
37
+ validate
43
38
 
44
- @service_version = initialize_service_version
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
- Formatter.new(
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
- active_support_simple_formatter
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Loga
2
- VERSION = '2.0.0'.freeze
2
+ VERSION = '2.1.0'.freeze
3
3
  end
@@ -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) do
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).to be_a(Loga::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
- context 'when RACK_ENV is production', if: ENV['RACK_ENV'].eql?('development') do
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
- expect(last_log_entry)
76
- .to eq("#{time_anchor.iso8601(3)} GET /ok?username=yoshi 200 in 0ms\n")
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("#{time_anchor.iso8601(3)} GET /new 302 in 0ms\n")
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(:log_entry_match) do
89
- %r{GET /error 500 in 0ms.undefined method `name' for nil:NilClass..+sinatra_spec}m
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 match(log_entry_match)
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
@@ -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
- CodeClimate::TestReporter.start if ENV.fetch('CODECLIMATE_REPO_TOKEN', nil)
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::Formatter)
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(ActiveSupport::Logger::SimpleFormatter)
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
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Loga::Formatter do
3
+ describe Loga::Formatters::GELFFormatter do
4
4
  let(:service_name) { 'loga' }
5
5
  let(:service_version) { '725e032a' }
6
6
  let(:host) { 'www.example.com' }
@@ -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.0.0
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-18 00:00:00.000000000 Z
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/formatter.rb
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/formatter_spec.rb
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/formatter_spec.rb
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
@@ -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