logster 0.8.4.6.pre → 0.8.4.7.pre

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