logster 0.8.4.6.pre → 0.8.4.7.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74e3d5019a8150cc107b5917c2466c6029bdc914
4
- data.tar.gz: 0038b79111731c1b7462390775522ebad7419d66
3
+ metadata.gz: 9d7bd5fb673384e34d116c5dd45509c905c9a8bc
4
+ data.tar.gz: 43cdc554929b6de1d025f8097fdeff9f5a6ebbdb
5
5
  SHA512:
6
- metadata.gz: 8a054e3703b0d7809ef4b702f2b022bd11aa93dbbbac5c081f146f677ee1470c67d85423d564b67b53ab1ee894da085eacd0bea91a1ac13f1aa2b12b89653692
7
- data.tar.gz: 2d4e310d6d31dcfd113c6937cc3ab7b04822c46686ee1e0d7a2f70a9c2f9553d04e28a98068365ae4263122d769063f023e765b856a11f4946758ed39e3d7523
6
+ metadata.gz: d699901b60a63521435e5c2b867dde20589b92bf417c0f50cb4d38dc04b3cdd1a9fa2e3c8d36d894026814007a279ad5f62f2e819f3ab757a417042832102c72
7
+ data.tar.gz: b5a99a9d5602597aa38dd4182c18bfaaa6b0b1c040940cf9f8e9f90aefe254b42d5b613065309e76fe2c393675a6e69aec3b1ae0d3d614f466230e0927d7ad8d
@@ -64,9 +64,8 @@ function buildArrayString(array) {
64
64
  return '[' + buffer.join(', ') + ']';
65
65
  }
66
66
 
67
- function buildHashString(hash, indent) {
67
+ function buildHashString(hash, recurse) {
68
68
  if (!hash) return '';
69
- if (!indent) indent = "";
70
69
 
71
70
  var buffer = [],
72
71
  hashes = [];
@@ -74,23 +73,24 @@ function buildHashString(hash, indent) {
74
73
  if (v === null) {
75
74
  buffer.push('null');
76
75
  } else if (Object.prototype.toString.call(v) === '[object Array]') {
77
- buffer.push(k + ": " + buildArrayString(v));
76
+ buffer.push("<tr><td>" + escape(k) + "</td><td>" + escape(buildArrayString(v)) + "</td></tr>");
78
77
  } else if (typeof v === "object") {
79
78
  hashes.push(k);
80
79
  } else {
81
- buffer.push(k + ": " + v);
80
+ buffer.push("<tr><td>" + k + "</td><td>" + v + "</td></tr>");
82
81
  }
83
82
  });
84
83
 
85
84
  if (_.size(hashes) > 0) {
86
85
  _.each(hashes, function(k1) {
87
86
  var v = hash[k1];
88
- buffer.push("");
89
- buffer.push(k1 + ":");
90
- buffer.push(buildHashString(v, indent + " "));
87
+ buffer.push("<tr><td></td><td><table>");
88
+ buffer.push("<td>" + k1 + "</td><td>" + buildHashString(v, true) + "</td>");
89
+ buffer.push("</table></td></tr>");
91
90
  });
92
91
  }
93
- return indent + buffer.join("\n" + indent);
92
+ var className = recurse?"": "env-table";
93
+ return "<table class='"+ className +"'>" + buffer.join("\n") + "</table>";
94
94
  }
95
95
 
96
96
  App.Message = Ember.Object.extend({
@@ -101,6 +101,10 @@ App.Message = Ember.Object.extend({
101
101
  this.set("expanded", true);
102
102
  },
103
103
 
104
+ solve: function() {
105
+ return App.ajax("/solve/" + this.get('key'), { type: "PUT" });
106
+ },
107
+
104
108
  "delete": function() {
105
109
  return App.ajax("/message/" + this.get('key'), { type: "DELETE" });
106
110
  },
@@ -144,10 +148,16 @@ App.Message = Ember.Object.extend({
144
148
  this.set('count', other.get('count'));
145
149
  },
146
150
 
147
- envDebug: function() {
151
+ canSolve: function() {
152
+ var backtrace = this.get("backtrace");
153
+ return this.get("env.application_version") && backtrace && (backtrace.length > 0);
154
+ }.property(),
155
+
156
+ envTable: function() {
148
157
  return buildHashString(this.get('env'));
149
158
  }.property("env"),
150
159
 
160
+
151
161
  rowClass: function() {
152
162
  switch (this.get("severity")) {
153
163
  case 0:
@@ -185,6 +195,13 @@ App.MessageCollection = Em.Object.extend({
185
195
  currentMessage: null,
186
196
  total: 0,
187
197
 
198
+ solve: function(message) {
199
+ var self = this;
200
+ message.solve().then(function(){
201
+ self.reload();
202
+ });
203
+ },
204
+
188
205
  "delete": function(message){
189
206
  var messages = this.get('messages');
190
207
  var idx = messages.indexOf(message);
@@ -319,6 +336,23 @@ App.MessageCollection = Em.Object.extend({
319
336
  });
320
337
 
321
338
 
339
+ (function(){
340
+ $.each(["","webkit","ms","moz","ms"], function(index, prefix){
341
+ var check = prefix + (prefix === "" ? "hidden" : "Hidden");
342
+ if(document[check] !== undefined ){
343
+ hiddenProperty = check;
344
+ }
345
+ });
346
+
347
+ App.isHidden = function() {
348
+ if (hiddenProperty !== undefined){
349
+ return document[hiddenProperty];
350
+ } else {
351
+ return !document.hasFocus;
352
+ }
353
+ };
354
+ })();
355
+
322
356
  App.IndexRoute = Em.Route.extend({
323
357
  model: function(){
324
358
  // TODO from preload json?
@@ -338,8 +372,13 @@ App.IndexRoute = Em.Route.extend({
338
372
  controller.set("initialized", true);
339
373
  model.reload();
340
374
 
375
+ var times = 0;
341
376
  this.refreshInterval = setInterval(function(){
342
- model.loadMore();
377
+ times += 1;
378
+ // refresh a lot less aggressively in background
379
+ if (!App.isHidden() || (times % 20 === 0)) {
380
+ model.loadMore();
381
+ }
343
382
  }, 3000);
344
383
  },
345
384
 
@@ -398,6 +437,11 @@ App.IndexController = Em.Controller.extend({
398
437
  removeMessage: function(msg) {
399
438
  var messages = this.get('model');
400
439
  messages.delete(msg);
440
+ },
441
+
442
+ solveMessage: function(msg) {
443
+ var messages = this.get('model');
444
+ messages.solve(msg);
401
445
  }
402
446
  },
403
447
 
@@ -689,6 +733,9 @@ App.MessageInfoComponent = Ember.Component.extend({
689
733
  },
690
734
  "remove": function(){
691
735
  this.sendAction("removeMessage", this.get('currentMessage'));
736
+ },
737
+ solve: function() {
738
+ this.sendAction("solveMessage", this.get('currentMessage'));
692
739
  }
693
740
  }
694
741
  });
@@ -17,11 +17,11 @@
17
17
  <pre>{{currentMessage.backtrace}}</pre>
18
18
  {{/tab-contents}}
19
19
  {{#if currentMessage.env}}
20
- {{#tab-contents name="env" hint="show environment" currentMessage=currentMessage}}
20
+ {{#tab-contents className="env" name="env" hint="show environment" currentMessage=currentMessage}}
21
21
  {{#if showTitle}}
22
22
  <h3>Env</h3>
23
23
  {{/if}}
24
- <pre>{{currentMessage.envDebug}}</pre>
24
+ {{{currentMessage.envTable}}}
25
25
  {{/tab-contents}}
26
26
  {{/if}}
27
27
  {{/tabbed-section}}
@@ -30,6 +30,10 @@
30
30
  <div class='message-actions'>
31
31
 
32
32
  {{#unless currentMessage.protected}}
33
+ {{#if currentMessage.canSolve}}
34
+ <button {{action 'solve'}} class="solve btn danger"><i class='fa fa-check-square-o'></i>Solve</button>
35
+ {{/if}}
36
+
33
37
  <button {{action 'remove'}} class="delete btn danger"><i class='fa fa-trash-o'></i>Delete</button>
34
38
 
35
39
  <button {{action 'protect'}} class="protect btn"><i class='fa fa-lock'></i>Protect</button>
@@ -23,7 +23,7 @@
23
23
  </div>
24
24
  {{panel-resizer}}
25
25
  <div id="bottom-panel">
26
- {{message-info currentMessage=currentMessage removeMessage="removeMessage"}}
26
+ {{message-info currentMessage=currentMessage removeMessage="removeMessage" solveMessage="solveMessage"}}
27
27
 
28
28
  <div class="action-panel">
29
29
  <label class="debug">
@@ -7,6 +7,20 @@ pre {
7
7
  font-family: 'Roboto Mono', Consolas, Monaco, Ubuntu Mono, monospace;
8
8
  }
9
9
 
10
+ table.env-table tbody tr td {
11
+ border-top: none;
12
+ line-height: 18px;
13
+ vertical-align: top;
14
+ }
15
+
16
+ table.env-table, table.env-table table {
17
+ border-spacing: 0;
18
+ border-collapse: collapse;
19
+ }
20
+
21
+ table.env-table td {
22
+ padding-right: 5px;
23
+ }
10
24
 
11
25
  tbody tr {
12
26
  width: 98%;
@@ -59,6 +59,12 @@ module Logster
59
59
  not_implemented
60
60
  end
61
61
 
62
+ # Solve a particular message, causing all old messages with matching version and backtrace
63
+ # to be deleted (report should delete any solved messages when called)
64
+ def solve(message_key)
65
+ not_implemented
66
+ end
67
+
62
68
  def report(severity, progname, msg, opts = {})
63
69
  return if (!msg || (String === msg && msg.empty?)) && skip_empty
64
70
  return if level && severity < level
@@ -1,6 +1,6 @@
1
1
  module Logster
2
2
  class Configuration
3
- attr_accessor :current_context, :allow_grouping, :environments
3
+ attr_accessor :current_context, :allow_grouping, :environments, :application_version
4
4
  attr_writer :subdirectory
5
5
 
6
6
  def initialize
@@ -17,6 +17,7 @@ module Logster
17
17
  HTTP_X_REAL_IP
18
18
  hostname
19
19
  process_id
20
+ application_version
20
21
  }
21
22
 
22
23
  attr_accessor :timestamp, :severity, :progname, :message, :key, :backtrace, :count, :env, :protected, :first_timestamp
@@ -77,9 +78,16 @@ module Logster
77
78
 
78
79
  def populate_from_env(env)
79
80
  env ||= {}
80
- env["hostname"] ||= self.class.hostname
81
- env["process_id"] ||= Process.pid
82
- @env = Message.populate_from_env(env)
81
+ @env = Message.populate_from_env(self.class.default_env.merge env)
82
+ end
83
+
84
+ def self.default_env
85
+ env = {
86
+ "hostname" => hostname,
87
+ "process_id" => Process.pid
88
+ }
89
+ env["application_version"] = Logster.config.application_version if Logster.config.application_version
90
+ env
83
91
  end
84
92
 
85
93
  # in its own method so it can be overridden
@@ -92,6 +100,14 @@ module Logster
92
100
  Digest::SHA1.hexdigest JSON.fast_generate grouping_hash
93
101
  end
94
102
 
103
+ # todo - memoize?
104
+ def solved_key
105
+ if (version=env["application_version"]) &&
106
+ (backtrace && backtrace.length > 0)
107
+ Digest::SHA1.hexdigest "#{version} #{backtrace}"
108
+ end
109
+ end
110
+
95
111
  def is_similar?(other)
96
112
  self.grouping_key == other.grouping_key
97
113
  end
@@ -111,7 +127,7 @@ module Logster
111
127
  # Not a web request
112
128
  return env
113
129
  end
114
- scrubbed = {}
130
+ scrubbed = default_env
115
131
  request = Rack::Request.new(env)
116
132
  params = {}
117
133
  request.params.each do |k,v|
@@ -75,6 +75,18 @@ module Logster
75
75
  end
76
76
  end
77
77
 
78
+ elsif resource =~ /\/solve\/([0-9a-f]+)$/
79
+ key = $1
80
+
81
+ message = Logster.store.get(key)
82
+ unless message
83
+ return [404, {}, ["Message not found"]]
84
+ end
85
+
86
+ Logster.store.solve(key)
87
+
88
+ return [301, {"Location" => "#{@logs_path}"}, []]
89
+
78
90
  elsif resource =~ /\/clear$/
79
91
  if env[REQUEST_METHOD] != "POST"
80
92
  return [405, {}, ["GET not allowed for /clear"]]
@@ -35,6 +35,13 @@ module Logster::Rails
35
35
 
36
36
  app.middleware.delete ActionDispatch::DebugExceptions
37
37
  app.config.colorize_logging = false
38
+
39
+ unless Logster.config.application_version
40
+ git_version = `cd #{Rails.root} && git rev-parse --short HEAD 2> /dev/null`
41
+ if git_version.present?
42
+ Logster.config.application_version = git_version.strip
43
+ end
44
+ end
38
45
  end
39
46
  end
40
47
 
@@ -14,6 +14,10 @@ module Logster
14
14
 
15
15
 
16
16
  def save(message)
17
+ if solved=message.solved_key
18
+ return true if @redis.hget(solved_key, solved)
19
+ end
20
+
17
21
  @redis.multi do
18
22
  @redis.hset(grouping_key, message.grouping_key, message.key)
19
23
  @redis.rpush(list_key, message.key)
@@ -55,6 +59,14 @@ module Logster
55
59
  @redis.llen(list_key)
56
60
  end
57
61
 
62
+ def solve(message_key)
63
+ if (message = get(message_key)) && (key = message.solved_key)
64
+ # add a time so we can expire it
65
+ @redis.hset(solved_key, key, Time.now.to_f.to_i)
66
+ end
67
+ clear_solved
68
+ end
69
+
58
70
  def latest(opts={})
59
71
  limit = opts[:limit] || 50
60
72
  severity = opts[:severity]
@@ -102,6 +114,7 @@ module Logster
102
114
  end
103
115
 
104
116
  def clear
117
+ @redis.del(solved_key)
105
118
  @redis.del(list_key)
106
119
  keys = @redis.smembers(protected_key) || []
107
120
  if keys.empty?
@@ -132,6 +145,7 @@ module Logster
132
145
  @redis.del(protected_key)
133
146
  @redis.del(hash_key)
134
147
  @redis.del(grouping_key)
148
+ @redis.del(solved_key)
135
149
  end
136
150
 
137
151
  def get(message_key)
@@ -157,8 +171,29 @@ module Logster
157
171
  end
158
172
  end
159
173
 
174
+ def solved
175
+ @redis.hkeys(solved_key) || []
176
+ end
177
+
160
178
  protected
161
179
 
180
+ def clear_solved(count = nil)
181
+
182
+ ignores = Set.new(@redis.hkeys(solved_key) || [])
183
+
184
+ if ignores.length > 0
185
+ start = count ? 0 - count : 0
186
+ message_keys = @redis.lrange(list_key, start, -1 ) || []
187
+
188
+ @redis.hmget(hash_key, message_keys).each do |json|
189
+ message = Message.from_json(json)
190
+ if ignores.include? message.solved_key
191
+ delete message
192
+ end
193
+ end
194
+ end
195
+ end
196
+
162
197
  def trim
163
198
  if @redis.llen(list_key) > max_backlog
164
199
  removed_keys = []
@@ -261,6 +296,9 @@ module Logster
261
296
 
262
297
  end
263
298
 
299
+ def solved_key
300
+ @solved_key ||= "__LOGSTER__SOLVED_MAP"
301
+ end
264
302
 
265
303
  def list_key
266
304
  @list_key ||= "__LOGSTER__LATEST"
@@ -1,3 +1,3 @@
1
1
  module Logster
2
- VERSION = "0.8.4.6.pre"
2
+ VERSION = "0.8.4.7.pre"
3
3
  end
@@ -18,7 +18,17 @@ class TestMessage < MiniTest::Test
18
18
 
19
19
  assert_equal(20, msg1.timestamp)
20
20
  assert_equal(10, msg1.first_timestamp)
21
+ end
22
+
23
+ def test_adds_application_version
24
+ Logster.config.application_version = "abc"
25
+ msg = Logster::Message.new(0, '', 'test', 10)
26
+ msg.populate_from_env({})
27
+
28
+ assert_equal("abc", msg.env["application_version"])
21
29
 
30
+ ensure
31
+ Logster.config.application_version = nil
22
32
  end
23
33
 
24
34
  end
@@ -235,6 +235,38 @@ class TestRedisStore < Minitest::Test
235
235
  assert_equal(1, @store.latest.count)
236
236
  end
237
237
 
238
+ def test_solve
239
+ Logster.config.application_version = "abc"
240
+
241
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
242
+ m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1")
243
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace2")
244
+
245
+ assert_equal(3, @store.latest.count)
246
+
247
+ @store.solve(m.key)
248
+
249
+ assert_equal(1, @store.latest.count)
250
+
251
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1")
252
+ @store.report(Logger::WARN, "application", "test error1", backtrace: "backtrace1", env: { "application_version" => "xyz"})
253
+
254
+ assert_equal(2, @store.latest.count)
255
+
256
+ ensure
257
+ Logster.config.application_version = nil
258
+ end
259
+
260
+ def test_clears_solved
261
+ m = @store.report(Logger::WARN, "application", "test error2", backtrace: "backtrace1", env: {"application_version" => "abc"})
262
+ @store.solve(m.key)
263
+
264
+ assert_equal(1, @store.solved.length)
265
+
266
+ @store.clear
267
+ assert_equal(0, @store.solved.length)
268
+ end
269
+
238
270
  def test_env
239
271
  env = Rack::MockRequest.env_for("/test").merge({
240
272
  "HTTP_HOST" => "www.site.com",
@@ -257,7 +289,13 @@ class TestRedisStore < Minitest::Test
257
289
  end
258
290
 
259
291
  @store.report(Logger::INFO, "test", "test", env: env)
260
- assert_equal(orig, @store.latest.last.env)
292
+
293
+ env = @store.latest.last.env
294
+
295
+ env.delete "hostname"
296
+ env.delete "process_id"
297
+
298
+ assert_equal(orig, env)
261
299
  end
262
300
 
263
301
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4.6.pre
4
+ version: 0.8.4.7.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - UI for viewing logs in Rack
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-14 00:00:00.000000000 Z
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler