xyeger 0.3.4 → 1.0.0.rc

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: 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