logster 2.5.1 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/CHANGELOG.md +9 -0
- data/README.md +15 -1
- data/Rakefile +1 -0
- data/assets/javascript/client-app.js +204 -168
- data/assets/javascript/vendor.js +5132 -5833
- data/assets/stylesheets/client-app.css +1 -1
- data/client-app/.eslintrc.js +17 -5
- data/client-app/.travis.yml +4 -3
- data/client-app/app/app.js +5 -7
- data/client-app/app/components/actions-menu.js +24 -17
- data/client-app/app/components/back-trace.js +148 -0
- data/client-app/app/components/env-tab.js +16 -12
- data/client-app/app/components/message-info.js +84 -7
- data/client-app/app/components/message-row.js +13 -15
- data/client-app/app/components/panel-resizer.js +63 -45
- data/client-app/app/components/patterns-list.js +6 -6
- data/client-app/app/components/update-time.js +13 -13
- data/client-app/app/controllers/index.js +4 -2
- data/client-app/app/index.html +1 -1
- data/client-app/app/initializers/app-init.js +1 -1
- data/client-app/app/lib/decorators.js +11 -0
- data/client-app/app/lib/preload.js +14 -3
- data/client-app/app/lib/utilities.js +63 -36
- data/client-app/app/models/group.js +6 -1
- data/client-app/app/models/message-collection.js +9 -7
- data/client-app/app/models/message.js +25 -20
- data/client-app/app/router.js +4 -6
- data/client-app/app/styles/app.css +18 -4
- data/client-app/app/templates/components/actions-menu.hbs +6 -2
- data/client-app/app/templates/components/back-trace.hbs +8 -0
- data/client-app/app/templates/components/message-info.hbs +7 -2
- data/client-app/app/templates/index.hbs +4 -1
- data/client-app/config/environment.js +1 -1
- data/client-app/config/optional-features.json +4 -1
- data/client-app/ember-cli-build.js +2 -3
- data/client-app/package-lock.json +9712 -2884
- data/client-app/package.json +25 -22
- data/client-app/preload-json-manager.rb +62 -0
- data/client-app/testem.js +0 -1
- data/client-app/tests/index.html +1 -1
- data/client-app/tests/integration/components/back-trace-test.js +109 -0
- data/client-app/tests/integration/components/message-info-test.js +4 -3
- data/client-app/tests/integration/components/patterns-list-test.js +7 -2
- data/lib/logster.rb +1 -0
- data/lib/logster/base_store.rb +16 -9
- data/lib/logster/configuration.rb +12 -2
- data/lib/logster/defer_logger.rb +1 -1
- data/lib/logster/logger.rb +12 -0
- data/lib/logster/message.rb +89 -30
- data/lib/logster/middleware/viewer.rb +44 -8
- data/lib/logster/redis_store.rb +69 -51
- data/lib/logster/suppression_pattern.rb +1 -1
- data/lib/logster/version.rb +1 -1
- data/logster.gemspec +1 -1
- data/test/logster/middleware/test_viewer.rb +100 -0
- data/test/logster/test_base_store.rb +16 -0
- data/test/logster/test_defer_logger.rb +1 -1
- data/test/logster/test_message.rb +142 -54
- data/test/logster/test_redis_store.rb +99 -39
- metadata +11 -6
@@ -21,25 +21,23 @@ export default Component.extend({
|
|
21
21
|
return;
|
22
22
|
}
|
23
23
|
|
24
|
-
const
|
24
|
+
const topPanel = document.getElementById("top-panel");
|
25
|
+
if (!topPanel) return;
|
25
26
|
|
26
|
-
const
|
27
|
-
|
28
|
-
const scrollHeight = $topPanel[0].scrollHeight;
|
29
|
-
|
30
|
-
STICK_TO_BOTTOM = scrollHeight - 20 < height + scrollTop;
|
27
|
+
const height = parseFloat(getComputedStyle(topPanel).height);
|
28
|
+
STICK_TO_BOTTOM = topPanel.scrollHeight - 20 < height + topPanel.scrollTop;
|
31
29
|
CHECKED_BOTTOM = true;
|
32
30
|
},
|
33
31
|
|
34
32
|
didInsertElement() {
|
35
|
-
const
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
}
|
33
|
+
const topPanel = document.getElementById("top-panel");
|
34
|
+
if (!topPanel) return;
|
35
|
+
|
36
|
+
CHECKED_BOTTOM = false;
|
37
|
+
if (STICK_TO_BOTTOM) {
|
38
|
+
STICK_TO_BOTTOM = false;
|
39
|
+
topPanel.scrollTop =
|
40
|
+
topPanel.scrollHeight - parseFloat(getComputedStyle(topPanel).height);
|
41
|
+
}
|
44
42
|
}
|
45
43
|
});
|
@@ -1,74 +1,92 @@
|
|
1
1
|
import Component from "@ember/component";
|
2
|
+
import { scheduleOnce, throttle } from "@ember/runloop";
|
3
|
+
import { bound } from "client-app/lib/decorators";
|
4
|
+
|
2
5
|
const MOVE_EVENTS = ["touchmove", "mousemove"];
|
3
6
|
const UP_EVENTS = ["touchend", "mouseup"];
|
4
7
|
const DOWN_EVENTS = ["touchstart", "mousedown"];
|
5
8
|
|
6
9
|
export default Component.extend({
|
10
|
+
resizing: false,
|
7
11
|
classNames: ["divider"],
|
8
12
|
|
9
|
-
divideView(fromTop
|
10
|
-
const
|
11
|
-
const
|
12
|
-
const fromBottom = $win.height() - fromTop;
|
13
|
+
divideView(fromTop) {
|
14
|
+
const height = window.innerHeight;
|
15
|
+
const fromBottom = height - fromTop;
|
13
16
|
|
14
17
|
if (fromTop < 100 || fromTop + 170 > height) {
|
15
18
|
return;
|
16
19
|
}
|
17
20
|
|
18
|
-
this.divider.
|
21
|
+
this.divider.style.bottom = `${fromBottom - 5}px`;
|
19
22
|
this.events.trigger("panelResized", fromBottom);
|
20
23
|
},
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
this.
|
25
|
+
@bound
|
26
|
+
performDrag(e) {
|
27
|
+
throttle(this, this.throttledPerformDrag, e, 25);
|
28
|
+
},
|
25
29
|
|
26
|
-
|
27
|
-
|
30
|
+
throttledPerformDrag(e) {
|
31
|
+
if (this.resizing) {
|
32
|
+
this.divideView(
|
33
|
+
e.clientY || (e.touches && e.touches[0] && e.touches[0].clientY)
|
34
|
+
);
|
35
|
+
}
|
36
|
+
},
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
};
|
38
|
+
@bound
|
39
|
+
endDrag(/* e */) {
|
40
|
+
const overlay = document.getElementById("overlay");
|
41
|
+
if (overlay) {
|
42
|
+
overlay.parentElement.removeChild(overlay);
|
43
|
+
}
|
44
|
+
this.set("resizing", false);
|
37
45
|
|
38
|
-
|
39
|
-
|
40
|
-
|
46
|
+
if (localStorage) {
|
47
|
+
localStorage.logster_divider_bottom = parseInt(
|
48
|
+
this.divider.style.bottom,
|
49
|
+
10
|
50
|
+
);
|
51
|
+
}
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
}
|
53
|
+
MOVE_EVENTS.forEach(name =>
|
54
|
+
document.removeEventListener(name, this.performDrag)
|
55
|
+
);
|
56
|
+
UP_EVENTS.forEach(name => document.removeEventListener(name, this.endDrag));
|
57
|
+
},
|
48
58
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
59
|
+
@bound
|
60
|
+
dividerClickHandler(e) {
|
61
|
+
e.preventDefault(); // for disabling pull-down-to-refresh on mobile
|
62
|
+
const overlay = document.createElement("DIV");
|
63
|
+
overlay.id = "overlay";
|
64
|
+
document.body.appendChild(overlay);
|
65
|
+
this.set("resizing", true);
|
66
|
+
MOVE_EVENTS.forEach(name =>
|
67
|
+
document.addEventListener(name, this.performDrag)
|
68
|
+
);
|
69
|
+
UP_EVENTS.forEach(name => document.addEventListener(name, this.endDrag));
|
70
|
+
},
|
53
71
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
.on(MOVE_EVENTS.join(" "), _.throttle(performDrag, 25))
|
60
|
-
.on(UP_EVENTS.join(" "), endDrag);
|
72
|
+
didInsertElement() {
|
73
|
+
// inspired by http://plugins.jquery.com/misc/textarea.js
|
74
|
+
this.set("divider", document.querySelector(".divider"));
|
75
|
+
DOWN_EVENTS.forEach(name => {
|
76
|
+
this.divider.addEventListener(name, this.dividerClickHandler);
|
61
77
|
});
|
78
|
+
scheduleOnce("afterRender", this, "initialDivideView");
|
79
|
+
},
|
62
80
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
this.divideView(fromTop, $win);
|
68
|
-
});
|
81
|
+
initialDivideView() {
|
82
|
+
const amount = (localStorage && localStorage.logster_divider_bottom) || 300;
|
83
|
+
const fromTop = window.innerHeight - parseInt(amount, 10);
|
84
|
+
this.divideView(fromTop);
|
69
85
|
},
|
70
86
|
|
71
87
|
willDestroyElement() {
|
72
|
-
|
88
|
+
DOWN_EVENTS.forEach(name =>
|
89
|
+
this.divider.removeEventListener(name, this.dividerClickHandler)
|
90
|
+
);
|
73
91
|
}
|
74
92
|
});
|
@@ -28,7 +28,7 @@ export default Component.extend({
|
|
28
28
|
return ajax(`/patterns/${this.get("key")}.json`, { method, data });
|
29
29
|
},
|
30
30
|
|
31
|
-
|
31
|
+
finallyBlock(pattern) {
|
32
32
|
pattern.set("saving", false);
|
33
33
|
},
|
34
34
|
|
@@ -70,7 +70,7 @@ export default Component.extend({
|
|
70
70
|
pattern.destroy();
|
71
71
|
})
|
72
72
|
.catch(response => this.catchBlock(pattern, response))
|
73
|
-
.
|
73
|
+
.finally(() => this.finallyBlock(pattern));
|
74
74
|
}
|
75
75
|
},
|
76
76
|
|
@@ -80,8 +80,8 @@ export default Component.extend({
|
|
80
80
|
if (pattern.get("isNew")) {
|
81
81
|
promise = this.makeAPICall({
|
82
82
|
method: "POST",
|
83
|
-
pattern: pattern.
|
84
|
-
retroactive: pattern.retroactive
|
83
|
+
pattern: pattern.valueBuffer,
|
84
|
+
retroactive: !!pattern.retroactive
|
85
85
|
}).then(response => {
|
86
86
|
pattern.updateValue(response.pattern);
|
87
87
|
pattern.set("isNew", false);
|
@@ -102,7 +102,7 @@ export default Component.extend({
|
|
102
102
|
.catch(response => {
|
103
103
|
this.catchBlock(pattern, response);
|
104
104
|
})
|
105
|
-
.
|
105
|
+
.finally(() => this.finallyBlock(pattern));
|
106
106
|
},
|
107
107
|
|
108
108
|
resetCount(pattern) {
|
@@ -115,7 +115,7 @@ export default Component.extend({
|
|
115
115
|
pattern.set("count", 0);
|
116
116
|
})
|
117
117
|
.catch(response => this.catchBlock(pattern, response))
|
118
|
-
.
|
118
|
+
.finally(() => this.finallyBlock(pattern));
|
119
119
|
},
|
120
120
|
|
121
121
|
checkboxChanged(pattern) {
|
@@ -1,21 +1,21 @@
|
|
1
1
|
import Component from "@ember/component";
|
2
2
|
import { formatTime } from "client-app/lib/utilities";
|
3
|
+
import { later } from "@ember/runloop";
|
3
4
|
|
4
5
|
export default Component.extend({
|
5
6
|
didInsertElement() {
|
6
|
-
|
7
|
-
|
8
|
-
const timestamp = parseInt(this.getAttribute("data-timestamp"), 10);
|
9
|
-
const elem = this;
|
10
|
-
const text = formatTime(timestamp);
|
7
|
+
later(this, this.updateTimes, 60000);
|
8
|
+
},
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
updateTimes() {
|
11
|
+
Array.from(document.querySelectorAll(".auto-update-time")).forEach(node => {
|
12
|
+
const timestamp = parseInt(node.dataset.timestamp);
|
13
|
+
if (!timestamp) return;
|
14
|
+
const formatted = formatTime(timestamp);
|
15
|
+
if (formatted !== node.innerText) {
|
16
|
+
node.innerText = formatted;
|
17
|
+
}
|
18
|
+
});
|
19
|
+
later(this, this.updateTimes, 60000);
|
20
20
|
}
|
21
21
|
});
|
@@ -18,8 +18,10 @@ export default Controller.extend({
|
|
18
18
|
}),
|
19
19
|
|
20
20
|
resizePanels(amount) {
|
21
|
-
|
22
|
-
|
21
|
+
const bottomPanel = document.getElementById("bottom-panel");
|
22
|
+
const topPanel = document.getElementById("top-panel");
|
23
|
+
bottomPanel.style.height = `${amount - 13}px`;
|
24
|
+
topPanel.style.bottom = `${amount + 12}px`;
|
23
25
|
},
|
24
26
|
|
25
27
|
actionsInMenu: computed(function() {
|
data/client-app/app/index.html
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
<title>ClientApp</title>
|
7
7
|
<meta name="description" content="">
|
8
8
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=yes">
|
9
|
-
<meta id="preloaded-data" data-root-path="/logs" data-preloaded="{"env_expandable_keys":[],"patterns_enabled":true}">
|
9
|
+
<meta id="preloaded-data" data-root-path="/logs" data-preloaded="{"env_expandable_keys":[],"patterns_enabled":true,"gems_dir":"/home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/","backtrace_links_enabled":true,"gems_data":[],"directories":[{"path":"/home/sam/Source/discourse","url":"https://github.com/discourse/discourse","main_app":true}],"application_version":"b329e23f8511b7248c0e4aee370a9f8a249e1b84"}">
|
10
10
|
|
11
11
|
{{content-for "head"}}
|
12
12
|
|
@@ -56,7 +56,7 @@ export function initialize(app) {
|
|
56
56
|
const isMobile =
|
57
57
|
/mobile/i.test(navigator.userAgent) && !/iPad/.test(navigator.userAgent);
|
58
58
|
if (isMobile) {
|
59
|
-
|
59
|
+
document.body.classList.add("mobile");
|
60
60
|
}
|
61
61
|
app.register("site:main", { isMobile }, { instantiate: false });
|
62
62
|
app.inject("controller", "site", "site:main");
|
@@ -4,9 +4,8 @@ let isInitialized = false;
|
|
4
4
|
// exported so that it can be used in tests
|
5
5
|
export function init() {
|
6
6
|
const dataset = document.getElementById("preloaded-data").dataset;
|
7
|
-
CONTAINER =
|
8
|
-
|
9
|
-
});
|
7
|
+
CONTAINER = JSON.parse(dataset.preloaded);
|
8
|
+
CONTAINER.rootPath = dataset.rootPath;
|
10
9
|
isInitialized = true;
|
11
10
|
}
|
12
11
|
|
@@ -18,3 +17,15 @@ export default {
|
|
18
17
|
return Em.get(CONTAINER, key);
|
19
18
|
}
|
20
19
|
};
|
20
|
+
|
21
|
+
// used in tests
|
22
|
+
export function mutatePreload(key, value) {
|
23
|
+
if (!isInitialized) {
|
24
|
+
init();
|
25
|
+
}
|
26
|
+
Em.set(CONTAINER, key, value);
|
27
|
+
}
|
28
|
+
|
29
|
+
export function uninitialize() {
|
30
|
+
isInitialized = false;
|
31
|
+
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import Preload from "client-app/lib/preload";
|
2
|
+
import { Promise, resolve } from "rsvp";
|
2
3
|
|
3
4
|
const entityMap = {
|
4
5
|
"&": "&",
|
@@ -14,16 +15,47 @@ export function escapeHtml(string) {
|
|
14
15
|
}
|
15
16
|
|
16
17
|
export function ajax(url, settings) {
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
return new Promise((resolve, reject) => {
|
19
|
+
settings = settings || {};
|
20
|
+
const xhr = new XMLHttpRequest();
|
21
|
+
url = Preload.get("rootPath") + url;
|
22
|
+
if (settings.data) {
|
23
|
+
for (let param in settings.data) {
|
24
|
+
const prefix = url.indexOf("?") === -1 ? "?" : "&";
|
25
|
+
url += prefix;
|
26
|
+
url += `${param}=${encodeURIComponent(settings.data[param])}`;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
xhr.open(settings.method || settings.type || "GET", url);
|
30
|
+
xhr.setRequestHeader("X-SILENCE-LOGGER", true);
|
31
|
+
if (settings.headers) {
|
32
|
+
for (let header in settings.headers) {
|
33
|
+
xhr.setRequestHeader(header, settings.headers[header]);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
xhr.onreadystatechange = () => {
|
37
|
+
if (xhr.readyState === 4) {
|
38
|
+
let status = xhr.status;
|
39
|
+
if ((status >= 200 && status < 300) || status === 304) {
|
40
|
+
const type = xhr.getResponseHeader("Content-Type");
|
41
|
+
let data = xhr.responseText;
|
42
|
+
if (/\bjson\b/.test(type)) {
|
43
|
+
data = JSON.parse(data);
|
44
|
+
}
|
45
|
+
resolve(data);
|
46
|
+
} else {
|
47
|
+
reject(xhr);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
};
|
51
|
+
xhr.send();
|
52
|
+
});
|
21
53
|
}
|
22
54
|
|
23
55
|
export function preloadOrAjax(url, settings) {
|
24
56
|
const preloaded = Preload.get(url.replace(".json", ""));
|
25
57
|
if (preloaded) {
|
26
|
-
return
|
58
|
+
return resolve(preloaded);
|
27
59
|
} else {
|
28
60
|
return ajax(url, settings);
|
29
61
|
}
|
@@ -96,32 +128,33 @@ export function buildArrayString(array) {
|
|
96
128
|
return "[" + buffer.join(", ") + "]";
|
97
129
|
}
|
98
130
|
|
99
|
-
export function buildHashString(hash, recurse, expanded = []) {
|
131
|
+
export function buildHashString(hash, recurse, expanded = [], lists = {}) {
|
100
132
|
if (!hash) return "";
|
101
133
|
|
102
134
|
const buffer = [];
|
103
135
|
const hashes = [];
|
104
136
|
const expandableKeys = Preload.get("env_expandable_keys") || [];
|
105
|
-
|
137
|
+
Object.keys(hash).forEach(k => {
|
138
|
+
const v = hash[k];
|
106
139
|
if (v === null) {
|
107
140
|
buffer.push("null");
|
108
|
-
} else if (
|
141
|
+
} else if (expandableKeys.indexOf(k) !== -1 && !recurse) {
|
109
142
|
let valueHtml = "";
|
110
|
-
if (
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
) {
|
116
|
-
valueHtml = `${escapeHtml(
|
117
|
-
v[0]
|
118
|
-
)}, <a class="expand-list" data-key=${k}>${v.length - 1} more</a>`;
|
143
|
+
if (expanded.indexOf(k) !== -1 || (lists[k] && lists[k].length < 3)) {
|
144
|
+
valueHtml =
|
145
|
+
lists[k] && lists[k].length === 1
|
146
|
+
? escapeHtml(lists[k][0])
|
147
|
+
: buildArrayString(lists[k]);
|
119
148
|
} else {
|
120
|
-
valueHtml =
|
149
|
+
valueHtml = `${escapeHtml(
|
150
|
+
lists[k][0]
|
151
|
+
)}, <a class="expand-list" data-key=${k}>${lists[k].length -
|
152
|
+
1} more</a>`;
|
121
153
|
}
|
122
|
-
buffer.push(
|
123
|
-
|
124
|
-
);
|
154
|
+
buffer.push(`<tr><td>${escapeHtml(k)}</td><td>${valueHtml}</td></tr>`);
|
155
|
+
} else if (Object.prototype.toString.call(v) === "[object Array]") {
|
156
|
+
const valueHtml = buildArrayString(v);
|
157
|
+
buffer.push(`<tr><td>${escapeHtml(k)}</td><td>${valueHtml}</td></tr>`);
|
125
158
|
} else if (typeof v === "object") {
|
126
159
|
hashes.push(k);
|
127
160
|
} else {
|
@@ -137,20 +170,14 @@ export function buildHashString(hash, recurse, expanded = []) {
|
|
137
170
|
}
|
138
171
|
});
|
139
172
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
buildHashString(v, true) +
|
149
|
-
"</td>"
|
150
|
-
);
|
151
|
-
buffer.push("</table></td></tr>");
|
152
|
-
});
|
153
|
-
}
|
173
|
+
hashes.forEach(k1 => {
|
174
|
+
const v = hash[k1];
|
175
|
+
buffer.push("<tr><td></td><td><table>");
|
176
|
+
buffer.push(
|
177
|
+
`<td>${escapeHtml(k1)}</td><td>${buildHashString(v, true)}</td>`
|
178
|
+
);
|
179
|
+
buffer.push("</table></td></tr>");
|
180
|
+
});
|
154
181
|
const className = recurse ? "" : "env-table";
|
155
|
-
return
|
182
|
+
return `<table class='${className}'>${buffer.join("\n")}</table>`;
|
156
183
|
}
|