loga 2.0.0 → 2.1.0

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