logster 2.1.1 → 2.1.2

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.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +19 -19
  3. data/.rubocop.yml +1 -1
  4. data/.travis.yml +16 -16
  5. data/CHANGELOG.md +172 -169
  6. data/Gemfile +4 -4
  7. data/Guardfile +8 -8
  8. data/LICENSE.txt +22 -22
  9. data/README.md +99 -99
  10. data/Rakefile +21 -21
  11. data/assets/fonts/FontAwesome.otf +0 -0
  12. data/assets/fonts/fontawesome-webfont.eot +0 -0
  13. data/assets/fonts/fontawesome-webfont.svg +639 -639
  14. data/assets/fonts/fontawesome-webfont.ttf +0 -0
  15. data/assets/fonts/fontawesome-webfont.woff +0 -0
  16. data/assets/fonts/fontawesome-webfont.woff2 +0 -0
  17. data/assets/images/Icon-144_rounded.png +0 -0
  18. data/assets/images/Icon-144_square.png +0 -0
  19. data/assets/images/icon_144x144.png +0 -0
  20. data/assets/images/icon_64x64.png +0 -0
  21. data/assets/javascript/client-app.js +106 -100
  22. data/assets/stylesheets/client-app.css +1 -1
  23. data/build_client_app.sh +0 -0
  24. data/client-app/.editorconfig +20 -20
  25. data/client-app/.ember-cli +9 -9
  26. data/client-app/.eslintignore +19 -19
  27. data/client-app/.eslintrc.js +46 -46
  28. data/client-app/.gitignore +23 -23
  29. data/client-app/.travis.yml +27 -27
  30. data/client-app/.watchmanconfig +3 -3
  31. data/client-app/README.md +57 -57
  32. data/client-app/app/app.js +0 -0
  33. data/client-app/app/components/actions-menu.js +43 -37
  34. data/client-app/app/components/env-tab.js +80 -44
  35. data/client-app/app/components/message-info.js +0 -0
  36. data/client-app/app/components/message-row.js +0 -0
  37. data/client-app/app/components/panel-resizer.js +0 -0
  38. data/client-app/app/components/tab-contents.js +27 -27
  39. data/client-app/app/components/tabbed-section.js +0 -0
  40. data/client-app/app/components/time-formatter.js +0 -0
  41. data/client-app/app/components/update-time.js +0 -0
  42. data/client-app/app/controllers/index.js +0 -0
  43. data/client-app/app/controllers/show.js +0 -0
  44. data/client-app/app/index.html +29 -29
  45. data/client-app/app/initializers/app-init.js +67 -72
  46. data/client-app/app/lib/preload.js +20 -14
  47. data/client-app/app/lib/utilities.js +149 -140
  48. data/client-app/app/models/message-collection.js +0 -0
  49. data/client-app/app/models/message.js +100 -100
  50. data/client-app/app/resolver.js +0 -0
  51. data/client-app/app/router.js +0 -0
  52. data/client-app/app/routes/index.js +0 -0
  53. data/client-app/app/routes/show.js +0 -0
  54. data/client-app/app/styles/app.css +527 -521
  55. data/client-app/app/templates/application.hbs +2 -2
  56. data/client-app/app/templates/components/actions-menu.hbs +12 -12
  57. data/client-app/app/templates/components/env-tab.hbs +10 -10
  58. data/client-app/app/templates/components/message-info.hbs +41 -41
  59. data/client-app/app/templates/components/message-row.hbs +15 -15
  60. data/client-app/app/templates/components/panel-resizer.hbs +3 -3
  61. data/client-app/app/templates/components/tabbed-section.hbs +10 -10
  62. data/client-app/app/templates/components/time-formatter.hbs +1 -1
  63. data/client-app/app/templates/index.hbs +58 -58
  64. data/client-app/app/templates/show.hbs +7 -7
  65. data/client-app/config/environment.js +51 -51
  66. data/client-app/config/optional-features.json +3 -3
  67. data/client-app/config/targets.js +18 -18
  68. data/client-app/ember-cli-build.js +29 -29
  69. data/client-app/package-lock.json +11365 -11365
  70. data/client-app/package.json +56 -56
  71. data/client-app/testem.js +25 -25
  72. data/client-app/tests/index.html +34 -34
  73. data/client-app/tests/integration/components/env-tab-test.js +123 -73
  74. data/client-app/tests/integration/components/message-info-test.js +111 -26
  75. data/client-app/tests/test-helper.js +8 -8
  76. data/client-app/tests/unit/controllers/index-test.js +12 -12
  77. data/client-app/tests/unit/controllers/show-test.js +12 -12
  78. data/client-app/tests/unit/initializers/app-init-test.js +31 -31
  79. data/client-app/tests/unit/routes/index-test.js +11 -11
  80. data/client-app/tests/unit/routes/show-test.js +11 -11
  81. data/lib/examples/sidekiq_logster_reporter.rb +21 -21
  82. data/lib/logster.rb +54 -54
  83. data/lib/logster/base_store.rb +141 -141
  84. data/lib/logster/configuration.rb +26 -25
  85. data/lib/logster/defer_logger.rb +14 -14
  86. data/lib/logster/ignore_pattern.rb +65 -65
  87. data/lib/logster/logger.rb +113 -113
  88. data/lib/logster/message.rb +212 -212
  89. data/lib/logster/middleware/debug_exceptions.rb +26 -26
  90. data/lib/logster/middleware/reporter.rb +55 -55
  91. data/lib/logster/middleware/viewer.rb +222 -221
  92. data/lib/logster/rails/railtie.rb +63 -63
  93. data/lib/logster/redis_store.rb +566 -566
  94. data/lib/logster/scheduler.rb +54 -54
  95. data/lib/logster/version.rb +3 -3
  96. data/lib/logster/web.rb +14 -14
  97. data/logster.gemspec +35 -35
  98. data/test/examples/test_sidekiq_reporter_example.rb +46 -46
  99. data/test/fake_data/Gemfile +4 -4
  100. data/test/fake_data/generate.rb +10 -10
  101. data/test/logster/middleware/test_reporter.rb +19 -19
  102. data/test/logster/middleware/test_viewer.rb +96 -96
  103. data/test/logster/test_base_store.rb +147 -147
  104. data/test/logster/test_defer_logger.rb +34 -34
  105. data/test/logster/test_ignore_pattern.rb +41 -41
  106. data/test/logster/test_logger.rb +86 -86
  107. data/test/logster/test_message.rb +119 -119
  108. data/test/logster/test_redis_rate_limiter.rb +230 -230
  109. data/test/logster/test_redis_store.rb +720 -720
  110. data/test/test_helper.rb +38 -38
  111. data/vendor/assets/javascripts/logster.js.erb +39 -39
  112. metadata +1 -10
  113. data/client-app/app/components/tab-link.js +0 -5
  114. data/client-app/tests/integration/components/actions-menu-test.js +0 -26
  115. data/client-app/tests/integration/components/message-row-test.js +0 -26
  116. data/client-app/tests/integration/components/panel-resizer-test.js +0 -26
  117. data/client-app/tests/integration/components/tab-contents-test.js +0 -26
  118. data/client-app/tests/integration/components/tab-link-test.js +0 -26
  119. data/client-app/tests/integration/components/tabbed-section-test.js +0 -26
  120. data/client-app/tests/integration/components/time-formatter-test.js +0 -26
  121. data/client-app/tests/integration/components/update-time-test.js +0 -26
@@ -1,25 +1,26 @@
1
- module Logster
2
- class Configuration
3
- attr_accessor :current_context, :allow_grouping, :environments,
4
- :application_version, :web_title
5
-
6
- attr_writer :subdirectory
7
-
8
- def initialize
9
- # lambda |env,block|
10
- @current_context = lambda { |_, &block| block.call }
11
- @environments = [:development, :production]
12
- @subdirectory = nil
13
-
14
- @allow_grouping = false
15
-
16
- if defined?(::Rails) && defined?(::Rails.env) && ::Rails.env.production?
17
- @allow_grouping = true
18
- end
19
- end
20
-
21
- def subdirectory
22
- @subdirectory || '/logs'
23
- end
24
- end
25
- end
1
+ module Logster
2
+ class Configuration
3
+ attr_accessor :current_context, :allow_grouping, :environments,
4
+ :application_version, :web_title, :env_expandable_keys
5
+
6
+ attr_writer :subdirectory
7
+
8
+ def initialize
9
+ # lambda |env,block|
10
+ @current_context = lambda { |_, &block| block.call }
11
+ @environments = [:development, :production]
12
+ @subdirectory = nil
13
+ @env_expandable_keys = []
14
+
15
+ @allow_grouping = false
16
+
17
+ if defined?(::Rails) && defined?(::Rails.env) && ::Rails.env.production?
18
+ @allow_grouping = true
19
+ end
20
+ end
21
+
22
+ def subdirectory
23
+ @subdirectory || '/logs'
24
+ end
25
+ end
26
+ end
@@ -1,14 +1,14 @@
1
- require 'logster/scheduler'
2
-
3
- module Logster
4
- class DeferLogger < ::Logster::Logger
5
- private
6
-
7
- def report_to_store(severity, progname, message, opts = {})
8
- opts[:backtrace] ||= caller
9
- Logster::Scheduler.schedule do
10
- super(severity, progname, message, opts)
11
- end
12
- end
13
- end
14
- end
1
+ require 'logster/scheduler'
2
+
3
+ module Logster
4
+ class DeferLogger < ::Logster::Logger
5
+ private
6
+
7
+ def report_to_store(severity, progname, message, opts = {})
8
+ opts[:backtrace] ||= caller
9
+ Logster::Scheduler.schedule do
10
+ super(severity, progname, message, opts)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,65 +1,65 @@
1
- module Logster
2
- class IgnorePattern
3
-
4
- def initialize(message_pattern = nil, env_patterns = nil)
5
- @msg_match = message_pattern
6
- @env_match = env_patterns
7
- end
8
-
9
- def self.from_message_and_request_uri(msg, request)
10
- IgnorePattern.new(msg, REQUEST_URI: request)
11
- end
12
-
13
- def matches?(message)
14
- if @msg_match
15
- return false unless compare(message.message, @msg_match)
16
- end
17
-
18
- if @env_match
19
- return false unless compare(message.env, @env_match)
20
- end
21
-
22
- true
23
- end
24
-
25
- def to_s
26
- "<#Logster::IgnorePattern, msg_match: #{@msg_match.inspect}, env_match: #{@env_match.inspect}>"
27
- end
28
-
29
- private
30
-
31
- def compare(message, pattern)
32
- return false unless message && pattern
33
-
34
- case pattern
35
- when Regexp
36
- message.to_s =~ pattern
37
- when String
38
- message.to_s =~ Regexp.new(pattern, Regexp::IGNORECASE)
39
- when Hash
40
- if Hash === message
41
- compare_hash(message, pattern)
42
- else
43
- false
44
- end
45
- else
46
- false
47
- end
48
- end
49
-
50
- def compare_hash(message_hash, pattern_hash)
51
- return false unless message_hash
52
- pattern_hash.each do |key, value|
53
- return false unless compare(get_indifferent(message_hash, key), value)
54
- end
55
- true
56
- end
57
-
58
- def get_indifferent(hash, key)
59
- return hash[key] if hash[key]
60
- return hash[key.to_s] if hash[key.to_s]
61
- # no key.to_sym please, memory leak in Ruby < 2.2
62
- nil
63
- end
64
- end
65
- end
1
+ module Logster
2
+ class IgnorePattern
3
+
4
+ def initialize(message_pattern = nil, env_patterns = nil)
5
+ @msg_match = message_pattern
6
+ @env_match = env_patterns
7
+ end
8
+
9
+ def self.from_message_and_request_uri(msg, request)
10
+ IgnorePattern.new(msg, REQUEST_URI: request)
11
+ end
12
+
13
+ def matches?(message)
14
+ if @msg_match
15
+ return false unless compare(message.message, @msg_match)
16
+ end
17
+
18
+ if @env_match
19
+ return false unless compare(message.env, @env_match)
20
+ end
21
+
22
+ true
23
+ end
24
+
25
+ def to_s
26
+ "<#Logster::IgnorePattern, msg_match: #{@msg_match.inspect}, env_match: #{@env_match.inspect}>"
27
+ end
28
+
29
+ private
30
+
31
+ def compare(message, pattern)
32
+ return false unless message && pattern
33
+
34
+ case pattern
35
+ when Regexp
36
+ message.to_s =~ pattern
37
+ when String
38
+ message.to_s =~ Regexp.new(pattern, Regexp::IGNORECASE)
39
+ when Hash
40
+ if Hash === message
41
+ compare_hash(message, pattern)
42
+ else
43
+ false
44
+ end
45
+ else
46
+ false
47
+ end
48
+ end
49
+
50
+ def compare_hash(message_hash, pattern_hash)
51
+ return false unless message_hash
52
+ pattern_hash.each do |key, value|
53
+ return false unless compare(get_indifferent(message_hash, key), value)
54
+ end
55
+ true
56
+ end
57
+
58
+ def get_indifferent(hash, key)
59
+ return hash[key] if hash[key]
60
+ return hash[key.to_s] if hash[key.to_s]
61
+ # no key.to_sym please, memory leak in Ruby < 2.2
62
+ nil
63
+ end
64
+ end
65
+ end
@@ -1,113 +1,113 @@
1
- require 'logger'
2
-
3
- module Logster
4
- class Logger < ::Logger
5
- LOGSTER_ENV = "logster_env".freeze
6
-
7
- attr_accessor :store, :skip_store
8
- attr_reader :chained
9
-
10
- def initialize(store)
11
- super(nil)
12
- @store = store
13
- @override_levels = nil
14
- @chained = []
15
- @skip_store = false
16
- end
17
-
18
- def override_level=(val)
19
- tid = Thread.current.object_id
20
-
21
- ol = @override_levels
22
- if val.nil? && ol && ol.key?(tid)
23
- ol.delete(tid)
24
- @override_levels = nil if ol.length == 0
25
- elsif val
26
- (@override_levels ||= {})[tid] = val
27
- end
28
- end
29
-
30
- def chain(logger)
31
- @chained << logger
32
- end
33
-
34
- def add_to_chained(logger, severity, message, progname, opts = nil, &block)
35
- if logger.respond_to? :skip_store
36
- old = logger.skip_store
37
- logger.skip_store = @skip_store
38
- end
39
-
40
- if logger.is_a?(self.class)
41
- logger.add(severity, message, progname, opts, &block)
42
- else
43
- logger.add(severity, message, progname, &block)
44
- end
45
- ensure
46
- if logger.respond_to? :skip_store
47
- logger.skip_store = old
48
- end
49
- end
50
-
51
- def add(*args, &block)
52
- add_with_opts(*args, &block)
53
- end
54
-
55
- def level
56
- ol = @override_levels
57
- (ol && ol[Thread.current.object_id]) || @level
58
- end
59
-
60
- def add_with_opts(severity, message, progname = progname(), opts = nil, &block)
61
- if severity < level
62
- return true
63
- end
64
-
65
- # it is not fun losing messages cause encoding is bad
66
- # protect all messages by scrubbing if needed
67
- if message && !message.valid_encoding?
68
- message = message.scrub
69
- end
70
-
71
- if @chained
72
- i = 0
73
- # micro optimise for logging
74
- while i < @chained.length
75
- # TODO double yielding blocks
76
- begin
77
- add_to_chained(@chained[i], severity, message, progname, opts, &block)
78
- rescue => e
79
- # don't blow up if STDERR is somehow closed
80
- STDERR.puts "Failed to report message to chained logger #{e}" rescue nil
81
- end
82
- i += 1
83
- end
84
- end
85
-
86
- progname ||= @progname
87
- if message.nil?
88
- if block_given?
89
- message = yield
90
- else
91
- message = progname
92
- progname = @progname
93
- end
94
- end
95
-
96
- return if @skip_store
97
-
98
- opts ||= {}
99
- opts[:env] ||= Thread.current[LOGSTER_ENV]
100
-
101
- report_to_store(severity, progname, message, opts)
102
- rescue => e
103
- # don't blow up if STDERR is somehow closed
104
- STDERR.puts "Failed to report error: #{e} #{severity} #{message} #{progname}" rescue nil
105
- end
106
-
107
- private
108
-
109
- def report_to_store(severity, progname, message, opts = {})
110
- @store.report(severity, progname, message, opts)
111
- end
112
- end
113
- end
1
+ require 'logger'
2
+
3
+ module Logster
4
+ class Logger < ::Logger
5
+ LOGSTER_ENV = "logster_env".freeze
6
+
7
+ attr_accessor :store, :skip_store
8
+ attr_reader :chained
9
+
10
+ def initialize(store)
11
+ super(nil)
12
+ @store = store
13
+ @override_levels = nil
14
+ @chained = []
15
+ @skip_store = false
16
+ end
17
+
18
+ def override_level=(val)
19
+ tid = Thread.current.object_id
20
+
21
+ ol = @override_levels
22
+ if val.nil? && ol && ol.key?(tid)
23
+ ol.delete(tid)
24
+ @override_levels = nil if ol.length == 0
25
+ elsif val
26
+ (@override_levels ||= {})[tid] = val
27
+ end
28
+ end
29
+
30
+ def chain(logger)
31
+ @chained << logger
32
+ end
33
+
34
+ def add_to_chained(logger, severity, message, progname, opts = nil, &block)
35
+ if logger.respond_to? :skip_store
36
+ old = logger.skip_store
37
+ logger.skip_store = @skip_store
38
+ end
39
+
40
+ if logger.is_a?(self.class)
41
+ logger.add(severity, message, progname, opts, &block)
42
+ else
43
+ logger.add(severity, message, progname, &block)
44
+ end
45
+ ensure
46
+ if logger.respond_to? :skip_store
47
+ logger.skip_store = old
48
+ end
49
+ end
50
+
51
+ def add(*args, &block)
52
+ add_with_opts(*args, &block)
53
+ end
54
+
55
+ def level
56
+ ol = @override_levels
57
+ (ol && ol[Thread.current.object_id]) || @level
58
+ end
59
+
60
+ def add_with_opts(severity, message, progname = progname(), opts = nil, &block)
61
+ if severity < level
62
+ return true
63
+ end
64
+
65
+ # it is not fun losing messages cause encoding is bad
66
+ # protect all messages by scrubbing if needed
67
+ if message && !message.valid_encoding?
68
+ message = message.scrub
69
+ end
70
+
71
+ if @chained
72
+ i = 0
73
+ # micro optimise for logging
74
+ while i < @chained.length
75
+ # TODO double yielding blocks
76
+ begin
77
+ add_to_chained(@chained[i], severity, message, progname, opts, &block)
78
+ rescue => e
79
+ # don't blow up if STDERR is somehow closed
80
+ STDERR.puts "Failed to report message to chained logger #{e}" rescue nil
81
+ end
82
+ i += 1
83
+ end
84
+ end
85
+
86
+ progname ||= @progname
87
+ if message.nil?
88
+ if block_given?
89
+ message = yield
90
+ else
91
+ message = progname
92
+ progname = @progname
93
+ end
94
+ end
95
+
96
+ return if @skip_store
97
+
98
+ opts ||= {}
99
+ opts[:env] ||= Thread.current[LOGSTER_ENV]
100
+
101
+ report_to_store(severity, progname, message, opts)
102
+ rescue => e
103
+ # don't blow up if STDERR is somehow closed
104
+ STDERR.puts "Failed to report error: #{e} #{severity} #{message} #{progname}" rescue nil
105
+ end
106
+
107
+ private
108
+
109
+ def report_to_store(severity, progname, message, opts = {})
110
+ @store.report(severity, progname, message, opts)
111
+ end
112
+ end
113
+ end
@@ -1,212 +1,212 @@
1
- require 'digest/sha1'
2
- require 'securerandom'
3
-
4
- module Logster
5
-
6
- MAX_GROUPING_LENGTH = 50
7
-
8
- class Message
9
- LOGSTER_ENV = "_logster_env".freeze
10
- ALLOWED_ENV = %w{
11
- HTTP_HOST
12
- REQUEST_URI
13
- REQUEST_METHOD
14
- HTTP_USER_AGENT
15
- HTTP_ACCEPT
16
- HTTP_REFERER
17
- HTTP_X_FORWARDED_FOR
18
- HTTP_X_REAL_IP
19
- hostname
20
- process_id
21
- application_version
22
- }
23
-
24
- attr_accessor :timestamp, :severity, :progname, :message, :key, :backtrace, :count, :env, :protected, :first_timestamp
25
-
26
- def initialize(severity, progname, message, timestamp = nil, key = nil, count: 1)
27
- @timestamp = timestamp || get_timestamp
28
- @severity = severity
29
- @progname = progname
30
- @message = message
31
- @key = key || SecureRandom.hex
32
- @backtrace = nil
33
- @count = count || 1
34
- @protected = false
35
- @first_timestamp = nil
36
- end
37
-
38
- def to_h(exclude_env: false)
39
- h = {
40
- message: @message,
41
- progname: @progname,
42
- severity: @severity,
43
- timestamp: @timestamp,
44
- key: @key,
45
- backtrace: @backtrace,
46
- count: @count,
47
- protected: @protected
48
- }
49
-
50
- h[:first_timestamp] = @first_timestamp if @first_timestamp
51
- h[:env] = @env unless exclude_env
52
-
53
- h
54
- end
55
-
56
- def to_json(opts = nil)
57
- exclude_env = Hash === opts && opts.delete(:exclude_env)
58
- JSON.fast_generate(to_h(exclude_env: exclude_env), opts)
59
- end
60
-
61
- def self.from_json(json)
62
- parsed = ::JSON.parse(json)
63
- msg = new(parsed["severity"],
64
- parsed["progname"],
65
- parsed["message"],
66
- parsed["timestamp"],
67
- parsed["key"])
68
- msg.backtrace = parsed["backtrace"]
69
- msg.env = parsed["env"]
70
- msg.count = parsed["count"]
71
- msg.protected = parsed["protected"]
72
- msg.first_timestamp = parsed["first_timestamp"]
73
- msg
74
- end
75
-
76
- def self.hostname
77
- @hostname ||= `hostname`.strip! rescue "<unknown>"
78
- end
79
-
80
- def populate_from_env(env)
81
- env ||= {}
82
- if Array === env
83
- env = env.map do |single_env|
84
- self.class.default_env.merge(single_env)
85
- end
86
- else
87
- env = self.class.default_env.merge(env)
88
- end
89
- @env = Message.populate_from_env(env)
90
- end
91
-
92
- def self.default_env
93
- env = {
94
- "hostname" => hostname,
95
- "process_id" => Process.pid
96
- }
97
- env["application_version"] = Logster.config.application_version if Logster.config.application_version
98
- env
99
- end
100
-
101
- # in its own method so it can be overridden
102
- def grouping_hash
103
- return { message: self.message, severity: self.severity, backtrace: self.backtrace }
104
- end
105
-
106
- # todo - memoize?
107
- def grouping_key
108
- Digest::SHA1.hexdigest JSON.fast_generate grouping_hash
109
- end
110
-
111
- # todo - memoize?
112
- def solved_keys
113
- if Array === env
114
- versions = env.map { |single_env| single_env["application_version"] }
115
- else
116
- versions = env["application_version"]
117
- end
118
-
119
- if versions && backtrace && backtrace.length > 0
120
- versions = [versions] if String === versions
121
-
122
- versions.map do |version|
123
- Digest::SHA1.hexdigest "#{version} #{backtrace}"
124
- end
125
- end
126
- end
127
-
128
- def is_similar?(other)
129
- self.grouping_key == other.grouping_key
130
- end
131
-
132
- def merge_similar_message(other)
133
- self.first_timestamp ||= self.timestamp
134
- self.timestamp = [self.timestamp, other.timestamp].max
135
-
136
- self.count += other.count || 1
137
- return false if self.count > Logster::MAX_GROUPING_LENGTH
138
-
139
- other_env = JSON.load JSON.fast_generate other.env
140
- if Array === self.env
141
- Array === other_env ? self.env.concat(other_env) : self.env << other_env
142
- else
143
- Array === other_env ? self.env = [self.env, *other_env] : self.env = [self.env, other_env]
144
- end
145
- true
146
- end
147
-
148
- def self.populate_from_env(env)
149
- if Array === env
150
- env.map do |single_env|
151
- self.populate_env_helper(single_env)
152
- end
153
- else
154
- self.populate_env_helper(env)
155
- end
156
- end
157
-
158
- def self.populate_env_helper(env)
159
- env[LOGSTER_ENV] ||= begin
160
- unless env.include? "rack.input"
161
- # Not a web request
162
- return env
163
- end
164
- scrubbed = default_env
165
- request = Rack::Request.new(env)
166
- params = {}
167
- request.params.each do |k, v|
168
- if k.include? "password"
169
- params[k] = "[redacted]"
170
- elsif Array === v
171
- params[k] = v[0..20]
172
- else
173
- params[k] = v && v[0..100]
174
- end
175
- end
176
- scrubbed["params"] = params if params.length > 0
177
- ALLOWED_ENV.map { |k|
178
- scrubbed[k] = env[k] if env[k]
179
- }
180
- scrubbed
181
- end
182
- end
183
-
184
- def <=>(other)
185
- time = self.timestamp <=> other.timestamp
186
- return time if time && time != 0
187
-
188
- self.key <=> other.key
189
- end
190
-
191
- def =~(pattern)
192
- case pattern
193
- when Hash
194
- IgnorePattern.new(nil, pattern).matches? self
195
- when String
196
- IgnorePattern.new(pattern, nil).matches? self
197
- when Regexp
198
- IgnorePattern.new(pattern, nil).matches? self
199
- when IgnorePattern
200
- pattern.matches? self
201
- else
202
- nil
203
- end
204
- end
205
-
206
- protected
207
-
208
- def get_timestamp
209
- (Time.new.to_f * 1000).to_i
210
- end
211
- end
212
- end
1
+ require 'digest/sha1'
2
+ require 'securerandom'
3
+
4
+ module Logster
5
+
6
+ MAX_GROUPING_LENGTH = 50
7
+
8
+ class Message
9
+ LOGSTER_ENV = "_logster_env".freeze
10
+ ALLOWED_ENV = %w{
11
+ HTTP_HOST
12
+ REQUEST_URI
13
+ REQUEST_METHOD
14
+ HTTP_USER_AGENT
15
+ HTTP_ACCEPT
16
+ HTTP_REFERER
17
+ HTTP_X_FORWARDED_FOR
18
+ HTTP_X_REAL_IP
19
+ hostname
20
+ process_id
21
+ application_version
22
+ }
23
+
24
+ attr_accessor :timestamp, :severity, :progname, :message, :key, :backtrace, :count, :env, :protected, :first_timestamp
25
+
26
+ def initialize(severity, progname, message, timestamp = nil, key = nil, count: 1)
27
+ @timestamp = timestamp || get_timestamp
28
+ @severity = severity
29
+ @progname = progname
30
+ @message = message
31
+ @key = key || SecureRandom.hex
32
+ @backtrace = nil
33
+ @count = count || 1
34
+ @protected = false
35
+ @first_timestamp = nil
36
+ end
37
+
38
+ def to_h(exclude_env: false)
39
+ h = {
40
+ message: @message,
41
+ progname: @progname,
42
+ severity: @severity,
43
+ timestamp: @timestamp,
44
+ key: @key,
45
+ backtrace: @backtrace,
46
+ count: @count,
47
+ protected: @protected
48
+ }
49
+
50
+ h[:first_timestamp] = @first_timestamp if @first_timestamp
51
+ h[:env] = @env unless exclude_env
52
+
53
+ h
54
+ end
55
+
56
+ def to_json(opts = nil)
57
+ exclude_env = Hash === opts && opts.delete(:exclude_env)
58
+ JSON.fast_generate(to_h(exclude_env: exclude_env), opts)
59
+ end
60
+
61
+ def self.from_json(json)
62
+ parsed = ::JSON.parse(json)
63
+ msg = new(parsed["severity"],
64
+ parsed["progname"],
65
+ parsed["message"],
66
+ parsed["timestamp"],
67
+ parsed["key"])
68
+ msg.backtrace = parsed["backtrace"]
69
+ msg.env = parsed["env"]
70
+ msg.count = parsed["count"]
71
+ msg.protected = parsed["protected"]
72
+ msg.first_timestamp = parsed["first_timestamp"]
73
+ msg
74
+ end
75
+
76
+ def self.hostname
77
+ @hostname ||= `hostname`.strip! rescue "<unknown>"
78
+ end
79
+
80
+ def populate_from_env(env)
81
+ env ||= {}
82
+ if Array === env
83
+ env = env.map do |single_env|
84
+ self.class.default_env.merge(single_env)
85
+ end
86
+ else
87
+ env = self.class.default_env.merge(env)
88
+ end
89
+ @env = Message.populate_from_env(env)
90
+ end
91
+
92
+ def self.default_env
93
+ env = {
94
+ "hostname" => hostname,
95
+ "process_id" => Process.pid
96
+ }
97
+ env["application_version"] = Logster.config.application_version if Logster.config.application_version
98
+ env
99
+ end
100
+
101
+ # in its own method so it can be overridden
102
+ def grouping_hash
103
+ return { message: self.message, severity: self.severity, backtrace: self.backtrace }
104
+ end
105
+
106
+ # todo - memoize?
107
+ def grouping_key
108
+ Digest::SHA1.hexdigest JSON.fast_generate grouping_hash
109
+ end
110
+
111
+ # todo - memoize?
112
+ def solved_keys
113
+ if Array === env
114
+ versions = env.map { |single_env| single_env["application_version"] }
115
+ else
116
+ versions = env["application_version"]
117
+ end
118
+
119
+ if versions && backtrace && backtrace.length > 0
120
+ versions = [versions] if String === versions
121
+
122
+ versions.map do |version|
123
+ Digest::SHA1.hexdigest "#{version} #{backtrace}"
124
+ end
125
+ end
126
+ end
127
+
128
+ def is_similar?(other)
129
+ self.grouping_key == other.grouping_key
130
+ end
131
+
132
+ def merge_similar_message(other)
133
+ self.first_timestamp ||= self.timestamp
134
+ self.timestamp = [self.timestamp, other.timestamp].max
135
+
136
+ self.count += other.count || 1
137
+ return false if self.count > Logster::MAX_GROUPING_LENGTH
138
+
139
+ other_env = JSON.load JSON.fast_generate other.env
140
+ if Array === self.env
141
+ Array === other_env ? self.env.concat(other_env) : self.env << other_env
142
+ else
143
+ Array === other_env ? self.env = [self.env, *other_env] : self.env = [self.env, other_env]
144
+ end
145
+ true
146
+ end
147
+
148
+ def self.populate_from_env(env)
149
+ if Array === env
150
+ env.map do |single_env|
151
+ self.populate_env_helper(single_env)
152
+ end
153
+ else
154
+ self.populate_env_helper(env)
155
+ end
156
+ end
157
+
158
+ def self.populate_env_helper(env)
159
+ env[LOGSTER_ENV] ||= begin
160
+ unless env.include? "rack.input"
161
+ # Not a web request
162
+ return env
163
+ end
164
+ scrubbed = default_env
165
+ request = Rack::Request.new(env)
166
+ params = {}
167
+ request.params.each do |k, v|
168
+ if k.include? "password"
169
+ params[k] = "[redacted]"
170
+ elsif Array === v
171
+ params[k] = v[0..20]
172
+ else
173
+ params[k] = v && v[0..100]
174
+ end
175
+ end
176
+ scrubbed["params"] = params if params.length > 0
177
+ ALLOWED_ENV.map { |k|
178
+ scrubbed[k] = env[k] if env[k]
179
+ }
180
+ scrubbed
181
+ end
182
+ end
183
+
184
+ def <=>(other)
185
+ time = self.timestamp <=> other.timestamp
186
+ return time if time && time != 0
187
+
188
+ self.key <=> other.key
189
+ end
190
+
191
+ def =~(pattern)
192
+ case pattern
193
+ when Hash
194
+ IgnorePattern.new(nil, pattern).matches? self
195
+ when String
196
+ IgnorePattern.new(pattern, nil).matches? self
197
+ when Regexp
198
+ IgnorePattern.new(pattern, nil).matches? self
199
+ when IgnorePattern
200
+ pattern.matches? self
201
+ else
202
+ nil
203
+ end
204
+ end
205
+
206
+ protected
207
+
208
+ def get_timestamp
209
+ (Time.new.to_f * 1000).to_i
210
+ end
211
+ end
212
+ end