logster 2.1.1 → 2.1.2

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