xyeger 0.3.4 → 1.0.0.rc

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: 6c68d166158a0f8b44ff3478dc3d56b3bd2cc9fc
4
- data.tar.gz: 66012ae7e1fb5de36f145a53f12985311a60f5ce
3
+ metadata.gz: e0455bfab0a7b4d2b8a48cdf8ddd6d0fd70a9102
4
+ data.tar.gz: e53aa11125c2073b0016e7bc91fa1d37f2105958
5
5
  SHA512:
6
- metadata.gz: 5c638e78858363d24b1a6684fe4941c9ba52e85a2ec12adc23899c8cab86efc016995b6518a0437cfbf7ba1c58d565878f59ace87170806e3302a2c8158cad43
7
- data.tar.gz: f818798916ace388014c23ee135e180193b8936cad86eee90dc99e5ccbcc061d310fc0281f79998de159ab61c174efed1cb9a39a0cb2daa20ef227205cb50d88
6
+ metadata.gz: b92dca5942df0257ac51ff47d031ec86ce757810c0ae1444350b9ace0551da175ca99c14de456d1cfe8301a33c2163c665b638cf46882c8dcac4603910ca2377
7
+ data.tar.gz: 0c6297439b58999e9c5a218ce773a30987394444282c8b9f6f5160e311318ae9938485db524d71a74586e0eccf2989ca2ac5a3f8a4a353ba7eee7314fdefc67c
data/.rubocop.yml ADDED
@@ -0,0 +1,65 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ DisplayCopNames: true
4
+ Exclude:
5
+ - bin/*
6
+ - vendor/**/*
7
+ - spec/spec_helper.rb
8
+ - spec/rails_helper.rb
9
+
10
+ Metrics/LineLength:
11
+ Max: 120
12
+
13
+ Metrics/ParameterLists:
14
+ Enabled: false
15
+
16
+ Metrics/BlockLength:
17
+ Exclude:
18
+ - spec/**/*
19
+ - xyeger.gemspec
20
+
21
+ Style/Alias:
22
+ EnforcedStyle: prefer_alias_method
23
+
24
+ Style/AlignParameters:
25
+ EnforcedStyle: with_fixed_indentation
26
+
27
+ Style/BlockDelimiters:
28
+ EnforcedStyle: braces_for_chaining
29
+
30
+ Style/Documentation:
31
+ Enabled: false
32
+
33
+ Style/DoubleNegation:
34
+ Enabled: false
35
+
36
+ Style/FirstParameterIndentation:
37
+ EnforcedStyle: consistent
38
+
39
+ Style/FrozenStringLiteralComment:
40
+ Enabled: false
41
+
42
+ Style/GuardClause:
43
+ Enabled: false
44
+
45
+ Style/EmptyMethod:
46
+ EnforcedStyle: expanded
47
+
48
+ Style/IfUnlessModifier:
49
+ Enabled: false
50
+
51
+ Style/IndentArray:
52
+ EnforcedStyle: consistent
53
+
54
+ Style/IndentHash:
55
+ EnforcedStyle: consistent
56
+
57
+ Style/ModuleFunction:
58
+ Enabled: false
59
+
60
+ Style/RescueModifier:
61
+ Exclude:
62
+ - spec/**/*
63
+
64
+ Style/TrailingUnderscoreVariable:
65
+ Enabled: false
data/Gemfile.lock ADDED
@@ -0,0 +1,102 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xyeger (0.3.4)
5
+ actionpack (>= 4)
6
+ activesupport (>= 4)
7
+ railties (>= 4)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actionpack (5.1.2)
13
+ actionview (= 5.1.2)
14
+ activesupport (= 5.1.2)
15
+ rack (~> 2.0)
16
+ rack-test (~> 0.6.3)
17
+ rails-dom-testing (~> 2.0)
18
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
19
+ actionview (5.1.2)
20
+ activesupport (= 5.1.2)
21
+ builder (~> 3.1)
22
+ erubi (~> 1.4)
23
+ rails-dom-testing (~> 2.0)
24
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
25
+ activesupport (5.1.2)
26
+ concurrent-ruby (~> 1.0, >= 1.0.2)
27
+ i18n (~> 0.7)
28
+ minitest (~> 5.1)
29
+ tzinfo (~> 1.1)
30
+ ast (2.3.0)
31
+ builder (3.2.3)
32
+ concurrent-ruby (1.0.5)
33
+ diff-lcs (1.3)
34
+ erubi (1.6.1)
35
+ i18n (0.8.6)
36
+ loofah (2.0.3)
37
+ nokogiri (>= 1.5.9)
38
+ method_source (0.8.2)
39
+ mini_portile2 (2.2.0)
40
+ minitest (5.10.3)
41
+ nokogiri (1.8.0)
42
+ mini_portile2 (~> 2.2.0)
43
+ parallel (1.11.2)
44
+ parser (2.4.0.0)
45
+ ast (~> 2.2)
46
+ powerpack (0.1.1)
47
+ rack (2.0.3)
48
+ rack-test (0.6.3)
49
+ rack (>= 1.0)
50
+ rails-dom-testing (2.0.3)
51
+ activesupport (>= 4.2.0)
52
+ nokogiri (>= 1.6)
53
+ rails-html-sanitizer (1.0.3)
54
+ loofah (~> 2.0)
55
+ railties (5.1.2)
56
+ actionpack (= 5.1.2)
57
+ activesupport (= 5.1.2)
58
+ method_source
59
+ rake (>= 0.8.7)
60
+ thor (>= 0.18.1, < 2.0)
61
+ rainbow (2.2.2)
62
+ rake
63
+ rake (10.5.0)
64
+ rspec (3.6.0)
65
+ rspec-core (~> 3.6.0)
66
+ rspec-expectations (~> 3.6.0)
67
+ rspec-mocks (~> 3.6.0)
68
+ rspec-core (3.6.0)
69
+ rspec-support (~> 3.6.0)
70
+ rspec-expectations (3.6.0)
71
+ diff-lcs (>= 1.2.0, < 2.0)
72
+ rspec-support (~> 3.6.0)
73
+ rspec-mocks (3.6.0)
74
+ diff-lcs (>= 1.2.0, < 2.0)
75
+ rspec-support (~> 3.6.0)
76
+ rspec-support (3.6.0)
77
+ rubocop (0.49.1)
78
+ parallel (~> 1.10)
79
+ parser (>= 2.3.3.1, < 3.0)
80
+ powerpack (~> 0.1)
81
+ rainbow (>= 1.99.1, < 3.0)
82
+ ruby-progressbar (~> 1.7)
83
+ unicode-display_width (~> 1.0, >= 1.0.1)
84
+ ruby-progressbar (1.8.1)
85
+ thor (0.19.4)
86
+ thread_safe (0.3.6)
87
+ tzinfo (1.2.3)
88
+ thread_safe (~> 0.1)
89
+ unicode-display_width (1.3.0)
90
+
91
+ PLATFORMS
92
+ ruby
93
+
94
+ DEPENDENCIES
95
+ bundler (~> 1.14)
96
+ rake (~> 10.0)
97
+ rspec (~> 3.0)
98
+ rubocop
99
+ xyeger!
100
+
101
+ BUNDLED WITH
102
+ 1.15.1
data/README.md CHANGED
@@ -1,6 +1,49 @@
1
1
  # Xyeger
2
2
 
3
- ## Installation
3
+ ## Table of content
4
+
5
+ - [Basic Usage](#basic-usage)
6
+ - [Installation](#installation)
7
+ - [Configuration](#configuration)
8
+ - [Context](#context)
9
+ - [Context Resolver](#context-resolver)
10
+ - [Message Resolver](#message-resolver)
11
+ - [Formatters](#formatters)
12
+ - [Base Formatter](#base-formatter)
13
+ - [JSON Formatter](#json-formatter)
14
+ - [Values Formatter](#values-formatter)
15
+ - [Text Formatter](#text-formatter)
16
+ - [Custom Formatter](#custom-formatter)
17
+ - [Integrations](#integrations)
18
+ - [Rails](#rails)
19
+ - [Grape](#grape)
20
+ - [Sidekiq](#sidekiq)
21
+ - [Sentry](#sentry)
22
+ - [Plain ruby app](#plain-ruby-app)
23
+ ## Basic Usage
24
+ Xyeger Logger was created to be suitable with ElasticSearch logs, so when you log anything you will get JSON representation of your message.
25
+
26
+ ```ruby
27
+ Xyeger.configure {}
28
+ logger = Logger.new(STDOUT)
29
+ logger.extend(Xyeger::Logger)
30
+ logger.info('Some message')
31
+ ```
32
+
33
+ ```json
34
+ {
35
+ "hostname": "",
36
+ "pid": 61915,
37
+ "app": "",
38
+ "env": "",
39
+ "level": "INFO",
40
+ "time": "2017-08-03 16:24:57 +0300",
41
+ "message": "Some message",
42
+ "context": {}
43
+ }
44
+ ```
45
+
46
+ ### Installation
4
47
 
5
48
  Add this line to your application's Gemfile:
6
49
 
@@ -16,183 +59,227 @@ Or install it yourself as:
16
59
 
17
60
  $ gem install xyeger
18
61
 
19
- ## Usage
20
-
21
62
  ### Configuration
22
63
 
23
64
  Add environments:
65
+
24
66
  ```bash
25
- XYEGER_HOSTNAME='LoggerApplication' #f.e.: rails, sidekiq, sneakers
67
+ XYEGER_HOSTNAME='localhost' #f.e.: rails, sidekiq, sneakers
68
+ XYEGER_APPNAME='some_service'
69
+ XYEGER_ENV='staging'
26
70
  ```
27
71
 
28
72
  Add into initializer file:
29
- ```ruby
30
- #config/initializers/xyeger.rb
31
- Xyeger.configure do |config|
32
- config.filter_parameters = Rails.application.config.filter_parameters
33
- end
34
- ```
35
- | Formatter | Description | Options |
36
- | ---------------------------- | ---------------- | ------- |
37
- | `Xyeger::Formatters::Base` | | colored |
38
- | `Xyeger::Formatters::Json` | default format | colored |
39
- | `Xyeger::Formatters::Values` | show only values | colored |
40
73
 
41
- Default options:
42
74
  ```ruby
43
75
  #config/initializers/xyeger.rb
44
76
  Xyeger.configure do |config|
45
- config.output = STDOUT
46
- config.formatter = Xyeger::Formatters::Values.new
47
- config.filter_parameters = Rails.application.config.filter_parameters
48
- config.hostname = ENV['XYEGER_HOSTNAME']
49
- config.app = Rails.application.class.parent_name
50
- config.env = Rails.env
77
+ config.output = STDOUT # default to STDOUT
78
+ config.formatter = MyCustomFormatter # default to Xyeger::Formatters::Json.new
79
+ config.app = '' # default to ENV['XYEGER_APPNAME'] or emtpy string
80
+ config.env = Rails.env # default to ENV['XYEGER_ENV'] or empty string
81
+ config.context_resolver = MyContextResolver # ContextResolver class
82
+ config.message_resolver = MyMessageResolver # MessageResolver class
51
83
  end
52
84
  ```
53
85
 
54
- For colored output use option `colored` (gem [Paint](https://github.com/janlelis/paint))
86
+ ### Context
87
+ It was found out that just JSON-looking logs does not solve the problem of tracking request flow. So context was added.
88
+
55
89
  ```ruby
56
- config.xyeger.formatter = Xyeger::Formatters::Values.new(colored: true)
90
+ Xyeger.add_context(flow_id: SecureRandom.uuid)
91
+ logger.debug('With Context')
57
92
  ```
58
93
 
59
- ## Output results
60
-
61
- ### Http request
62
- ```bash
63
- curl localhost:3000 -G -d a=3
64
- ```
65
94
  ```json
66
95
  {
67
- "logger": "puma: cluster worker 1: 29117 [cryptopay]",
68
- "pid": 29141,
69
- "app": "Cryptopay",
70
- "env": "development",
71
- "level": "INFO",
72
- "time": "2017-06-29T15:42:14.761+03:00",
73
- "caller": null,
74
- "msg": "Lograge message",
75
- "context":
76
- {"method": "GET",
77
- "path": "/",
78
- "format": "*/*",
79
- "controller": "PagesController",
80
- "action": "index",
81
- "status": 200,
82
- "duration": 1852.79,
83
- "view": 703.55,
84
- "db": 29.07,
85
- "params": {"a": "3"}}
96
+ "hostname": "",
97
+ "pid": 61915,
98
+ "app": "",
99
+ "env": "",
100
+ "level": "DEBUG",
101
+ "time": "2017-08-03 16:26:38 +0300",
102
+ "message": "With Context",
103
+ "context": {
104
+ "flow_id": "6821a053-ad20-4d58-905a-25a923f7a412"
105
+ }
86
106
  }
87
107
  ```
88
108
 
89
- ### Raise an error
90
- ```json
91
- {
92
- "logger":"puma",
93
- "pid":24886,
94
- "app":"Cryptopay",
95
- "env":"development",
96
- "level":"ERROR",
97
- "time":"2017-06-29T15:17:07.736+03:00",
98
- "caller":"/home/vsevolod/work/cryptopay/cryptopay/app/services/ticker_service.rb:159",
99
- "msg": "StandardError",
100
- "context":
101
- {
102
- "class": "TickerService::TickerNotFound",
103
- "error": "Active ticker not found EUR/USD"
104
- }
105
- }
109
+ ### Context Resolver
110
+
111
+ You are not wired to add only hash object to context, but in that keys you might want to create your own `ContextResolver`, which expect `self.call(object)` method and return hash-representation at the end.
112
+
113
+ ```ruby
114
+ class MyContextResolver
115
+ def self.call(object)
116
+ case object
117
+ when User
118
+ { user_id: object.uuid }
119
+ end
120
+ end
121
+ end
122
+
123
+ Xyeger.configure { config.context_resolver = MyContextResolver }
124
+
125
+ user = User.first
126
+ Xyeger.add_context(user)
127
+ ```
128
+
129
+ ### Message Resolver
130
+
131
+ Same way as it is done in context resolver you might find useful to create your own `MessageResolver`, which expect `self.call(message, progname)` method to be implemented and return string at the end.
132
+
133
+ ```ruby
134
+ class MessageResolver
135
+ def self.call(message, progname)
136
+ message =
137
+ case message
138
+ when ::StandardError
139
+ [message.class.name, message].compact.join(' ')
140
+ else
141
+ message.to_s
142
+ end
143
+ [progname, message].compact.join(' ')
144
+ end
145
+ end
146
+
147
+ Xyeger.configure { config.message_resolver = MyContextResolver }
148
+
149
+ begin
150
+ raise
151
+ rescue => error
152
+ Xyeger.add_context(error)
153
+ raise error
154
+ end
106
155
  ```
107
156
 
108
- ### Manual log usage
157
+
158
+ ## Formatters
159
+ By default you have following formatters
160
+
161
+ | Formatter | Description |
162
+ | ---------------------------- | ---------------- |
163
+ | `Xyeger::Formatters::Base` | |
164
+ | `Xyeger::Formatters::Json` | default formater |
165
+ | `Xyeger::Formatters::Values` | show only values |
166
+ | `Xyeger::Formatters::Text` | show text form |
167
+
168
+ ### Base formatter
169
+ All built-in formatters inherit from `Xyeger::Formatters::Base`, which prepare result hash with following keys: `hostname`, `pid`, `app`, `env`, `level`, `time`, `message`, `context`.
170
+
171
+ ### JSON Formatter
172
+ Default representation of logs:
173
+
109
174
  ```ruby
110
- Rails.logger.info('Logger message', { content: '1', params: 'b' })
175
+ logger.formatter = Xyeger::Formatters::Json.new # default formatter
111
176
  ```
112
177
  ```json
113
178
  {
114
- "logger":"bin/rails",
115
- "pid":22796,
116
- "app":"Cryptopay",
117
- "env":"development",
118
- "level":"INFO",
119
- "time":"2017-06-30T13:45:22.070+03:00",
120
- "caller":null,
121
- "message":"Logger message",
122
- "context":
123
- {
124
- "content":"1",
125
- "params":"b"
179
+ "hostname": "",
180
+ "pid": 61915,
181
+ "app": "",
182
+ "env": "",
183
+ "level": "DEBUG",
184
+ "time": "2017-08-03 16:26:38 +0300",
185
+ "message": "With Context ",
186
+ "context": {
187
+ "flow_id": "6821a053-ad20-4d58-905a-25a923f7a412"
126
188
  }
127
189
  }
190
+ ```
128
191
 
192
+ ### Values Formatter
193
+ All the values of Base formatter hash concatenated with `space` delimiter.
194
+
195
+ ```ruby
196
+ logger.formatter = Xyeger::Formatters::Values.new
197
+ ```
129
198
  ```
199
+ 61915 DEBUG 2017-08-03 17:00:07 +0300 With Context {:flow_id=>"6821a053-ad20-4d58-905a-25a923f7a412"}
200
+ ```
201
+
202
+ ### Text Formatter
203
+ Context represented via list of key-value. Message is just string at the end
130
204
 
131
- ## Grape usage
132
- WIth ```gem 'grape_logging'```
133
205
  ```ruby
134
- class Root < Grape::API
135
- # Message: {
136
- # "status":200,
137
- # "time":
138
- # {
139
- # "total":86.84,
140
- # "db":15.27,
141
- # "view":71.57
142
- # },
143
- # "method":"GET",
144
- # "path":"/api/v2/tickers",
145
- # "params":{"password":"[FILTERED]","a":"b"},
146
- # "ip":"127.0.0.1",
147
- # "ua":"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"
148
- #}
149
- formatter = Xyeger::Formatters::Json.new(
150
- message: ->(message, _context) { "#{message[:method]} #{message[:path]}" },
151
- context: ->(message, _context) { message }
152
- )
153
-
154
- use GrapeLogging::Middleware::RequestLogger,
155
- logger: logger,
156
- formatter: formatter,
157
- include: [
158
- GrapeLogging::Loggers::Response.new,
159
- GrapeLogging::Loggers::FilterParameters.new,
160
- GrapeLogging::Loggers::ClientEnv.new
161
- ]
162
- end
206
+ logger.formatter = Xyeger::Formatters::Text.new
207
+ ```
163
208
  ```
209
+ [2017-08-03 16:59:51 +0300] [DEBUG] flow_id=6821a053-ad20-4d58-905a-25a923f7a412 With Context
210
+ ```
211
+
212
+ ### Custom Formatter
213
+ You are free to implement your own formatters, but things to be mentioned: if you need all default keys accessible inside your formatter, you have to inherit from `Xyeger::Formatters::Base` formatter. See `lib/xyeger/formatters` directory for more info.
164
214
 
165
- ## Sidekiq usage
215
+ ## Integrations
216
+ You can easily use `Xyeger` with different type of applications and here are some built in integrations
217
+
218
+ ### Rails
219
+ Integration with `rails` add `Xyeger::Rails::FlowIdMiddlare` in application middlware stack so it will automatically add end-to-end identifier(`:fid`) inside context for each request and clear it out when request is ended.
220
+
221
+ ### Grape
222
+ Grape has it's own logger inside. Most of the time you create extra module to specify logging preferences. It may look something like that.
166
223
 
167
224
  ```ruby
168
- # config/initializers/xyeger.rb
169
- Sidekiq::Logging.logger.formatter = Xyeger::Formatters::SidekiqJson.new
225
+ module API
226
+ module Helpers
227
+ module Logger
228
+ extend ActiveSupport::Concern
229
+
230
+ included do
231
+ logger.extend(Xyeger::Logger)
232
+ logger.formatter = Xyeger.config.formatter
233
+
234
+ use GrapeLogging::Middleware::RequestLogger,
235
+ logger: logger,
236
+ formatter: logger.formatter,
237
+ include: [
238
+ GrapeLogging::Loggers::Response.new,
239
+ GrapeLogging::Loggers::FilterParameters.new,
240
+ GrapeLogging::Loggers::ClientEnv.new
241
+ ]
242
+ end
243
+ end
244
+ end
245
+ end
170
246
  ```
171
247
 
172
- ## TaggedLogging example
248
+ So inside your application you just include `API::Helpers::Logger`
173
249
 
174
250
  ```ruby
175
- Rails.logger.tagged('main', 'object_id') { Rails.logger.info('Message text', { id: 33 }) }
251
+ module API
252
+ class Root < Grape::API
253
+ include API::Helpers::Logger
254
+ end
255
+ end
176
256
  ```
177
- ```json
178
- {
179
- "pid" : 9210,
180
- "app" : "Cryptopay",
181
- "env" : "development",
182
- "level" : "INFO",
183
- "time" : "2017-07-18T19:42:53.373+03:00",
184
- "message" : "Message text",
185
- "context" : {
186
- "id" : 33
187
- },
188
- "tags" : [
189
- "main",
190
- "object_id"
191
- ]
192
- }
257
+
258
+ ### Sidekiq
259
+ Sidekiq integration is really useful too. It store `fid` inside job and you can track logs even for retries. You don't have to do anything extra.
260
+
261
+ ### Sentry
262
+ Sentry integration is hardcoded inside gem. So when you add anything inside context of Xyeger, it also means that you add something to Sentry context.
263
+
264
+ ### Plain ruby app
265
+ For plain ruby application you have implement several steps:
266
+
267
+ ```ruby
268
+ # Configure your Xyeger
269
+ Xyeger.configure {}
270
+
271
+ # Create instance of some logger
272
+ logger = Logger.new(STDOUT)
273
+
274
+ # Extend your logger with Xyeger::Logger
275
+ logger.extend(Xyeger::Logger)
276
+
277
+ # You are ready to go
278
+ logger.info('Some message')
193
279
  ```
194
280
 
195
281
 
282
+
196
283
  ## License
197
284
 
198
285
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "xyeger"
3
+ require 'bundler/setup'
4
+ require 'xyeger'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "xyeger"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
data/lib/xyeger/config.rb CHANGED
@@ -1,21 +1,17 @@
1
1
  module Xyeger
2
2
  class Config
3
- attr_accessor :output,
4
- :formatter,
5
- :filter_parameters,
6
- :filter,
7
- :hostname,
8
- :app,
9
- :env
3
+ attr_accessor :output, :formatter, :hostname, :app, :env, :context_resolver, :message_resolver
4
+ attr_reader :context
10
5
 
11
6
  def initialize
12
7
  @output = STDOUT
13
8
  @formatter = Xyeger::Formatters::Json.new
14
- @filter_parameters = Rails.application.config.filter_parameters
15
-
16
- @hostname = ENV['XYEGER_HOSTNAME']
17
- @app = Rails.application.class.parent_name
18
- @env = Rails.env
9
+ @hostname = ENV['XYEGER_HOSTNAME'] || ''
10
+ @app = ENV['XYEGER_APPNAME'] || ''
11
+ @env = ENV['XYEGER_ENV'] || ''
12
+ @context = Xyeger::Context
13
+ @context_resolver = Xyeger::ContextResolver
14
+ @message_resolver = Xyeger::MessageResolver
19
15
  end
20
16
  end
21
17
  end
@@ -0,0 +1,19 @@
1
+ module Xyeger
2
+ class Context
3
+ def self.current
4
+ Thread.current[:xyeger_context] ||= {}
5
+ end
6
+
7
+ def self.clear_context
8
+ Thread.current[:xyeger_context] = nil
9
+ end
10
+
11
+ def self.add_context(context = {})
12
+ return unless context
13
+ context = Xyeger.config.context_resolver.call(context)
14
+ ::Raven.tags_context(context) if defined?(::Raven)
15
+ Thread.current[:xyeger_context] ||= {}
16
+ Thread.current[:xyeger_context].merge!(context || {})
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Xyeger
2
+ class ContextResolver
3
+ def self.call(value)
4
+ case value
5
+ when Hash
6
+ value
7
+ else
8
+ {}
9
+ end
10
+ end
11
+ end
12
+ end
@@ -5,23 +5,30 @@ module Xyeger
5
5
 
6
6
  UNCOLORIZE_REGEXP = /\e\[([;\d]+)?m/
7
7
 
8
- attr_reader :attributes, :colors
8
+ attr_reader :attributes
9
9
 
10
10
  def initialize(attributes = {})
11
11
  @attributes = attributes
12
12
  @tags = []
13
- @colors = Array.new(9) { Paint.random } if attributes[:colored]
14
13
  end
15
14
 
16
15
  def call(severity, timestamp, context, message)
17
- message, context = prepare(message, context)
18
- message = uncolorize(message) unless attributes[:colored]
16
+ message = uncolorize(message)
19
17
 
20
- context = filter_context(context)
18
+ result = prepare(severity, timestamp, context, message)
19
+ result[:tags] = current_tags if current_tags.any?
20
+
21
+ if attributes[:except]&.any?
22
+ result.except(*attributes[:except])
23
+ else
24
+ result
25
+ end
26
+ end
21
27
 
22
- result = {
28
+ def prepare(severity, timestamp, context, message)
29
+ {
23
30
  hostname: Xyeger.config.hostname,
24
- pid: $$,
31
+ pid: $$, # rubocop:disable Style/SpecialGlobalVars
25
32
  app: Xyeger.config.app,
26
33
  env: Xyeger.config.env,
27
34
  level: severity,
@@ -29,39 +36,6 @@ module Xyeger
29
36
  message: message,
30
37
  context: context
31
38
  }
32
-
33
- result[:tags] = current_tags if current_tags.any?
34
- colored(result) if attributes[:colored]
35
-
36
- result
37
- end
38
-
39
- private def prepare(message, context)
40
- new_message = attributes[:message].call(message, context) if attributes[:message]
41
- new_context = attributes[:context].call(message, context) if attributes[:context]
42
-
43
- return [new_message, new_context] if attributes[:message] || attributes[:context]
44
-
45
- case message
46
- when LogrageRaw
47
- ['Lograge', message.data]
48
- when ::StandardError
49
- ['StandardError', { class: message.class.name, error: message.to_s }]
50
- else
51
- [message.to_s, context]
52
- end
53
- end
54
-
55
- private def colored(result)
56
- result.each_with_index do |(key, value), index|
57
- result[key] = Paint[value, colors[index]]
58
- end
59
- end
60
-
61
- private def filter_context(context)
62
- return context unless Xyeger.config.filter && context.is_a?(Hash)
63
-
64
- Xyeger.config.filter.filter(context)
65
39
  end
66
40
 
67
41
  def uncolorize(message)
@@ -1,9 +1,10 @@
1
+ require 'json'
2
+
1
3
  module Xyeger
2
4
  module Formatters
3
5
  class Json < Base
4
6
  def call(*args)
5
7
  result = super(*args)
6
-
7
8
  result.compact.to_json + "\n"
8
9
  end
9
10
  end
@@ -0,0 +1,20 @@
1
+ module Xyeger
2
+ module Formatters
3
+ class Text < Base
4
+ def call(*args)
5
+ result = super(*args)
6
+ arr = []
7
+ arr << "[#{result[:time]}]"
8
+ arr << "[#{result[:level]}]".ljust(8)
9
+
10
+ context = result[:context]
11
+ arr << "[#{context[:fid]}] " if context[:fid]
12
+
13
+ arr << context.except(:fid).map { |k, v| "#{k}=#{v}" }.join(' ')
14
+ arr << result[:message]
15
+
16
+ "#{arr.join(' ').gsub(/ +/, ' ')}\n"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -4,7 +4,7 @@ module Xyeger
4
4
  def call(*args)
5
5
  result = super(*args)
6
6
 
7
- result.values.join(' ') + "\n"
7
+ result.values.join(' ').gsub(/ +/, ' ') + "\n"
8
8
  end
9
9
  end
10
10
  end
@@ -1,11 +1,4 @@
1
- module Xyeger
2
- module Formatters
3
- extend ActiveSupport::Autoload
4
-
5
- autoload :Base
6
- autoload :Json
7
- autoload :Values
8
- autoload :LogrageRaw
9
- autoload :SidekiqJson
10
- end
11
- end
1
+ require 'xyeger/formatters/base'
2
+ require 'xyeger/formatters/json'
3
+ require 'xyeger/formatters/text'
4
+ require 'xyeger/formatters/values'
@@ -0,0 +1,35 @@
1
+ require 'rails'
2
+
3
+ module Xyeger
4
+ module Rails
5
+ class FlowIdMiddleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ request = ActionDispatch::Request.new(env)
12
+ fid = request.request_id || SecureRandom.uuid
13
+ Xyeger.add_context(fid: fid)
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
18
+
19
+ class Railtie < ::Rails::Railtie
20
+ initializer 'xyeger.configure_rails_initialization' do |app|
21
+ app.config.middleware.insert_after(
22
+ ActionDispatch::RequestId, Xyeger::Middlewares::ClearContext
23
+ )
24
+ app.config.middleware.insert_after(
25
+ ActionDispatch::RequestId, Xyeger::Rails::FlowIdMiddleware
26
+ )
27
+ end
28
+
29
+ config.after_initialize do
30
+ ::Rails.logger = ActiveSupport::Logger.new(STDOUT)
31
+ ::Rails.logger.extend(Xyeger::Logger)
32
+ ::ActiveSupport.on_load(:action_record) { self.logger = ::Rails.logger }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require 'sidekiq'
2
+
3
+ module Xyeger
4
+ module Sidekiq
5
+ module LoggingPatch
6
+ def with_job_hash_context(job_hash)
7
+ # If we're using a wrapper class, like ActiveJob, use the 'wrapped'
8
+ # attribute to expose the underlying thing.
9
+ klass = job_hash['wrapped'] || job_hash['class']
10
+ jid = job_hash['jid']
11
+ fid = job_hash['fid'] || Xyeger.context.current[:fid] || SecureRandom.uuid
12
+
13
+ Xyeger.add_context(worker: klass, fid: fid, jid: jid)
14
+
15
+ yield
16
+ ensure
17
+ Xyeger.clear_context
18
+ end
19
+ end
20
+
21
+ class FlowIdMiddleware
22
+ def call(_worker_class, msg, _queue, _redis_pool)
23
+ msg['fid'] = Xyeger.context.current[:fid]
24
+ yield
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ ::Sidekiq.logger = Rails.logger
31
+
32
+ ::Sidekiq::Logging.singleton_class.prepend(Xyeger::Sidekiq::LoggingPatch)
33
+
34
+ ::Sidekiq.configure_client do |config|
35
+ config.client_middleware do |chain|
36
+ chain.add(Xyeger::Sidekiq::FlowIdMiddleware)
37
+ end
38
+ end
data/lib/xyeger/logger.rb CHANGED
@@ -1,24 +1,24 @@
1
1
  module Xyeger
2
2
  module Logger
3
3
  def self.extended(base)
4
- base.formatter = Xyeger.config.formatter if Xyeger.config
4
+ base.formatter = Xyeger.config.formatter
5
5
  end
6
6
 
7
- ActiveSupport::Logger::Severity.constants.each do |severity|
8
- define_method severity.downcase do |message=nil, context=nil, &block|
9
- context, message = message, context if message.is_a?(Hash)
10
-
11
- if block
12
- result = block.call
7
+ def add(severity, message = nil, progname = nil, &block)
8
+ message = prepare(message, progname, &block)
9
+ super(severity, message, Xyeger.context.current)
10
+ end
13
11
 
14
- if result.is_a?(Hash)
15
- context = result
16
- else
17
- message = result
18
- end
12
+ def prepare(message, progname, &block)
13
+ if message.nil?
14
+ if block_given?
15
+ message = yield(block)
16
+ else
17
+ message = progname
18
+ progname = nil
19
19
  end
20
- add(ActiveSupport::Logger::Severity.const_get(severity), message, context)
21
20
  end
21
+ Xyeger.config.message_resolver.call(message, progname)
22
22
  end
23
23
  end
24
24
  end
@@ -0,0 +1,14 @@
1
+ module Xyeger
2
+ class MessageResolver
3
+ def self.call(message, progname)
4
+ message =
5
+ case message
6
+ when ::StandardError
7
+ [message.class.name, message].compact.join(' ')
8
+ else
9
+ message.to_s
10
+ end
11
+ [progname, message].compact.join(' ')
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Xyeger
2
+ module Middlewares
3
+ class ClearContext
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ @app.call(env)
10
+ ensure
11
+ Xyeger.clear_context
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module Xyeger
2
- VERSION = '0.3.4'.freeze
2
+ VERSION = '1.0.0.rc'.freeze
3
3
  end
data/lib/xyeger.rb CHANGED
@@ -1,48 +1,30 @@
1
- require 'active_support/logger'
2
1
  require 'active_support/tagged_logging'
3
- require 'active_support/dependencies/autoload'
4
- require 'active_support/ordered_options'
5
- require 'paint'
6
- require 'lograge'
2
+ require 'forwardable'
7
3
  require 'xyeger/version'
4
+ require 'xyeger/config'
5
+ require 'xyeger/context'
6
+ require 'xyeger/context_resolver'
7
+ require 'xyeger/message_resolver'
8
+ require 'xyeger/formatters'
9
+ require 'xyeger/logger'
10
+ require 'xyeger/middlewares/clear_context'
11
+ require 'xyeger/integrations/rails' if defined?(::Rails)
12
+ require 'xyeger/integrations/sidekiq' if defined?(::Sidekiq)
8
13
 
9
14
  module Xyeger
10
15
  module_function
11
16
 
12
- extend ActiveSupport::Autoload
13
-
14
- autoload :Config
15
- autoload :Logger
16
- autoload :Formatters
17
-
18
17
  class << self
19
18
  attr_reader :config
19
+ extend Forwardable
20
20
 
21
21
  def configure
22
- @config ||= Xyeger::Config.new()
23
-
22
+ @config ||= Xyeger::Config.new
24
23
  yield(@config)
25
-
26
- if @config.filter_parameters
27
- @config.filter ||= ActionDispatch::Http::ParameterFilter.new(@config.filter_parameters)
28
- end
29
- Xyeger.setup
30
- end
31
- end
32
-
33
- def setup
34
- app = Rails.application
35
- setup_lograge(app)
36
-
37
- Rails.logger.extend(Logger)
38
- end
39
-
40
- def setup_lograge(app)
41
- app.config.lograge.formatter = -> (data) { Formatters::LogrageRaw.new(data: data) }
42
- app.config.lograge.custom_options = lambda do |event|
43
- { params: event.payload[:params]&.except('controller', 'action', 'format', 'id') }
44
24
  end
45
25
 
46
- Lograge.setup(app)
26
+ def_delegator :config, :context
27
+ def_delegator :context, :add_context
28
+ def_delegator :context, :clear_context
47
29
  end
48
30
  end
data/xyeger.gemspec CHANGED
@@ -5,10 +5,10 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'xyeger/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "xyeger"
8
+ spec.name = 'xyeger'
9
9
  spec.version = Xyeger::VERSION
10
- spec.authors = ["Avramov Vsevolod"]
11
- spec.email = ["vsevolod@cryptopay.me"]
10
+ spec.authors = ['Avramov Vsevolod']
11
+ spec.email = ['vsevolod@cryptopay.me']
12
12
 
13
13
  spec.summary = 'Uniq logger for project'
14
14
  spec.description = 'Uniq logger for project'
@@ -20,26 +20,23 @@ Gem::Specification.new do |spec|
20
20
  if spec.respond_to?(:metadata)
21
21
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
22
  else
23
- raise "RubyGems 2.0 or newer is required to protect against " \
24
- "public gem pushes."
23
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
24
+ 'public gem pushes.'
25
25
  end
26
26
 
27
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
28
  f.match(%r{^(test|spec|features)/})
29
29
  end
30
- spec.bindir = "exe"
30
+ spec.bindir = 'exe'
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
- spec.require_paths = ["lib"]
33
-
34
- spec.add_dependency 'lograge', '>= 0.4'
35
- spec.add_dependency 'paint', '~> 2.0'
32
+ spec.require_paths = ['lib']
36
33
 
37
34
  spec.add_runtime_dependency 'activesupport', '>= 4'
38
35
  spec.add_runtime_dependency 'actionpack', '>= 4'
39
36
  spec.add_runtime_dependency 'railties', '>= 4'
40
37
 
41
- spec.add_development_dependency "bundler", "~> 1.14"
42
- spec.add_development_dependency "rake", "~> 10.0"
43
- spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency 'bundler', '~> 1.14'
39
+ spec.add_development_dependency 'rake', '~> 10.0'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
44
41
  spec.add_development_dependency 'rubocop'
45
42
  end
metadata CHANGED
@@ -1,43 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xyeger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 1.0.0.rc
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avramov Vsevolod
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-18 00:00:00.000000000 Z
11
+ date: 2017-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: lograge
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0.4'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0.4'
27
- - !ruby/object:Gem::Dependency
28
- name: paint
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '2.0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '2.0'
41
13
  - !ruby/object:Gem::Dependency
42
14
  name: activesupport
43
15
  requirement: !ruby/object:Gem::Requirement
@@ -144,8 +116,9 @@ extensions: []
144
116
  extra_rdoc_files: []
145
117
  files:
146
118
  - ".gitignore"
147
- - CODE_OF_CONDUCT.md
119
+ - ".rubocop.yml"
148
120
  - Gemfile
121
+ - Gemfile.lock
149
122
  - LICENSE.txt
150
123
  - README.md
151
124
  - Rakefile
@@ -153,14 +126,18 @@ files:
153
126
  - bin/setup
154
127
  - lib/xyeger.rb
155
128
  - lib/xyeger/config.rb
129
+ - lib/xyeger/context.rb
130
+ - lib/xyeger/context_resolver.rb
156
131
  - lib/xyeger/formatters.rb
157
132
  - lib/xyeger/formatters/base.rb
158
133
  - lib/xyeger/formatters/json.rb
159
- - lib/xyeger/formatters/key_value.rb
160
- - lib/xyeger/formatters/lograge_raw.rb
161
- - lib/xyeger/formatters/sidekiq_json.rb
134
+ - lib/xyeger/formatters/text.rb
162
135
  - lib/xyeger/formatters/values.rb
136
+ - lib/xyeger/integrations/rails.rb
137
+ - lib/xyeger/integrations/sidekiq.rb
163
138
  - lib/xyeger/logger.rb
139
+ - lib/xyeger/message_resolver.rb
140
+ - lib/xyeger/middlewares/clear_context.rb
164
141
  - lib/xyeger/version.rb
165
142
  - xyeger.gemspec
166
143
  homepage: https://github.com/vsevolod/xyeger
@@ -179,12 +156,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
156
  version: '0'
180
157
  required_rubygems_version: !ruby/object:Gem::Requirement
181
158
  requirements:
182
- - - ">="
159
+ - - ">"
183
160
  - !ruby/object:Gem::Version
184
- version: '0'
161
+ version: 1.3.1
185
162
  requirements: []
186
163
  rubyforge_project:
187
- rubygems_version: 2.6.12
164
+ rubygems_version: 2.6.10
188
165
  signing_key:
189
166
  specification_version: 4
190
167
  summary: Uniq logger for project
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at vsevolod@cryptopay.me. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
@@ -1,11 +0,0 @@
1
- module Xyeger
2
- module Formatters
3
- class KeyValue < Base
4
- def call(*args)
5
- result = super(*args)
6
-
7
- result.compact.to_json + "\n"
8
- end
9
- end
10
- end
11
- end
@@ -1,6 +0,0 @@
1
- module Xyeger
2
- module Formatters
3
- class LogrageRaw < ActiveSupport::OrderedOptions
4
- end
5
- end
6
- end
@@ -1,26 +0,0 @@
1
- module Xyeger
2
- module Formatters
3
- class SidekiqJson < Base
4
- def call(severity, timestamp, context, message)
5
- context, message = prepare_message(message) unless context
6
-
7
- super(severity, timestamp, context, message).merge(
8
- tid: "TID-#{Thread.current.object_id.to_s(36)}",
9
- sidekiq_context: sidekiq_context
10
- ).to_json + "\n"
11
- end
12
-
13
- def sidekiq_context
14
- c = Thread.current[:sidekiq_context]
15
- c.join(' ') if c&.any?
16
- end
17
-
18
- def prepare_message(message)
19
- json = JSON.parse(message)
20
- [filter_context(json), 'Sidekiq']
21
- rescue JSON::ParserError => e
22
- [nil, message]
23
- end
24
- end
25
- end
26
- end