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