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 +4 -4
- data/assets/javascript/app.js +57 -10
- data/assets/javascript/components/message-info.hbs +6 -2
- data/assets/javascript/templates/index.hbs +1 -1
- data/assets/stylesheets/app.css +14 -0
- data/lib/logster/base_store.rb +6 -0
- data/lib/logster/configuration.rb +1 -1
- data/lib/logster/message.rb +20 -4
- data/lib/logster/middleware/viewer.rb +12 -0
- data/lib/logster/rails/railtie.rb +7 -0
- data/lib/logster/redis_store.rb +38 -0
- data/lib/logster/version.rb +1 -1
- data/test/logster/test_message.rb +10 -0
- data/test/logster/test_redis_store.rb +39 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d7bd5fb673384e34d116c5dd45509c905c9a8bc
|
4
|
+
data.tar.gz: 43cdc554929b6de1d025f8097fdeff9f5a6ebbdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d699901b60a63521435e5c2b867dde20589b92bf417c0f50cb4d38dc04b3cdd1a9fa2e3c8d36d894026814007a279ad5f62f2e819f3ab757a417042832102c72
|
7
|
+
data.tar.gz: b5a99a9d5602597aa38dd4182c18bfaaa6b0b1c040940cf9f8e9f90aefe254b42d5b613065309e76fe2c393675a6e69aec3b1ae0d3d614f466230e0927d7ad8d
|
data/assets/javascript/app.js
CHANGED
@@ -64,9 +64,8 @@ function buildArrayString(array) {
|
|
64
64
|
return '[' + buffer.join(', ') + ']';
|
65
65
|
}
|
66
66
|
|
67
|
-
function buildHashString(hash,
|
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 + "
|
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 + "
|
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(
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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">
|
data/assets/stylesheets/app.css
CHANGED
@@ -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%;
|
data/lib/logster/base_store.rb
CHANGED
@@ -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
|
data/lib/logster/message.rb
CHANGED
@@ -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
|
81
|
-
|
82
|
-
|
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
|
|
data/lib/logster/redis_store.rb
CHANGED
@@ -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"
|
data/lib/logster/version.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
11
|
+
date: 2015-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|