logster 2.5.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +9 -0
  4. data/README.md +15 -1
  5. data/Rakefile +1 -0
  6. data/assets/javascript/client-app.js +204 -168
  7. data/assets/javascript/vendor.js +5132 -5833
  8. data/assets/stylesheets/client-app.css +1 -1
  9. data/client-app/.eslintrc.js +17 -5
  10. data/client-app/.travis.yml +4 -3
  11. data/client-app/app/app.js +5 -7
  12. data/client-app/app/components/actions-menu.js +24 -17
  13. data/client-app/app/components/back-trace.js +148 -0
  14. data/client-app/app/components/env-tab.js +16 -12
  15. data/client-app/app/components/message-info.js +84 -7
  16. data/client-app/app/components/message-row.js +13 -15
  17. data/client-app/app/components/panel-resizer.js +63 -45
  18. data/client-app/app/components/patterns-list.js +6 -6
  19. data/client-app/app/components/update-time.js +13 -13
  20. data/client-app/app/controllers/index.js +4 -2
  21. data/client-app/app/index.html +1 -1
  22. data/client-app/app/initializers/app-init.js +1 -1
  23. data/client-app/app/lib/decorators.js +11 -0
  24. data/client-app/app/lib/preload.js +14 -3
  25. data/client-app/app/lib/utilities.js +63 -36
  26. data/client-app/app/models/group.js +6 -1
  27. data/client-app/app/models/message-collection.js +9 -7
  28. data/client-app/app/models/message.js +25 -20
  29. data/client-app/app/router.js +4 -6
  30. data/client-app/app/styles/app.css +18 -4
  31. data/client-app/app/templates/components/actions-menu.hbs +6 -2
  32. data/client-app/app/templates/components/back-trace.hbs +8 -0
  33. data/client-app/app/templates/components/message-info.hbs +7 -2
  34. data/client-app/app/templates/index.hbs +4 -1
  35. data/client-app/config/environment.js +1 -1
  36. data/client-app/config/optional-features.json +4 -1
  37. data/client-app/ember-cli-build.js +2 -3
  38. data/client-app/package-lock.json +9712 -2884
  39. data/client-app/package.json +25 -22
  40. data/client-app/preload-json-manager.rb +62 -0
  41. data/client-app/testem.js +0 -1
  42. data/client-app/tests/index.html +1 -1
  43. data/client-app/tests/integration/components/back-trace-test.js +109 -0
  44. data/client-app/tests/integration/components/message-info-test.js +4 -3
  45. data/client-app/tests/integration/components/patterns-list-test.js +7 -2
  46. data/lib/logster.rb +1 -0
  47. data/lib/logster/base_store.rb +16 -9
  48. data/lib/logster/configuration.rb +12 -2
  49. data/lib/logster/defer_logger.rb +1 -1
  50. data/lib/logster/logger.rb +12 -0
  51. data/lib/logster/message.rb +89 -30
  52. data/lib/logster/middleware/viewer.rb +44 -8
  53. data/lib/logster/redis_store.rb +69 -51
  54. data/lib/logster/suppression_pattern.rb +1 -1
  55. data/lib/logster/version.rb +1 -1
  56. data/logster.gemspec +1 -1
  57. data/test/logster/middleware/test_viewer.rb +100 -0
  58. data/test/logster/test_base_store.rb +16 -0
  59. data/test/logster/test_defer_logger.rb +1 -1
  60. data/test/logster/test_message.rb +142 -54
  61. data/test/logster/test_redis_store.rb +99 -39
  62. metadata +11 -6
@@ -21,25 +21,23 @@ export default Component.extend({
21
21
  return;
22
22
  }
23
23
 
24
- const $topPanel = Em.$("#top-panel");
24
+ const topPanel = document.getElementById("top-panel");
25
+ if (!topPanel) return;
25
26
 
26
- const scrollTop = $topPanel.scrollTop();
27
- const height = $topPanel.height();
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 $topPanel = Em.$("#top-panel");
36
- Em.run.next(() => {
37
- CHECKED_BOTTOM = false;
38
-
39
- if (STICK_TO_BOTTOM) {
40
- STICK_TO_BOTTOM = false;
41
- $topPanel.scrollTop($topPanel[0].scrollHeight - $topPanel.height());
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, win) {
10
- const $win = win || Em.$(window);
11
- const height = $win.height();
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.css("bottom", fromBottom - 5);
21
+ this.divider.style.bottom = `${fromBottom - 5}px`;
19
22
  this.events.trigger("panelResized", fromBottom);
20
23
  },
21
24
 
22
- didInsertElement() {
23
- // inspired by http://plugins.jquery.com/misc/textarea.js
24
- this.divider = Em.$(".divider");
25
+ @bound
26
+ performDrag(e) {
27
+ throttle(this, this.throttledPerformDrag, e, 25);
28
+ },
25
29
 
26
- const $win = Em.$(window);
27
- let resizing = false;
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
- const performDrag = e => {
30
- if (resizing) {
31
- this.divideView(
32
- e.clientY || (e.touches && e.touches[0] && e.touches[0].clientY),
33
- $win
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
- const endDrag = () => {
39
- Em.$("#overlay").remove();
40
- resizing = false;
46
+ if (localStorage) {
47
+ localStorage.logster_divider_bottom = parseInt(
48
+ this.divider.style.bottom,
49
+ 10
50
+ );
51
+ }
41
52
 
42
- if (localStorage) {
43
- localStorage.logster_divider_bottom = parseInt(
44
- this.divider.css("bottom"),
45
- 10
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
- const $document = Em.$(document);
50
- MOVE_EVENTS.forEach(e => $document.unbind(e, performDrag));
51
- UP_EVENTS.forEach(e => $document.unbind(e, endDrag));
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
- this.divider.on(DOWN_EVENTS.join(" "), e => {
55
- e.preventDefault(); // for disabling pull-down-to-refresh on mobile
56
- Em.$("<div id='overlay'></div>").appendTo(Em.$("body"));
57
- resizing = true;
58
- Em.$(document)
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
- Em.run.next(() => {
64
- const amount =
65
- (localStorage && localStorage.logster_divider_bottom) || 300;
66
- const fromTop = $win.height() - parseInt(amount, 10);
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
- Em.$(".divider").off(DOWN_EVENTS.join(" "));
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
- alwaysBlock(pattern) {
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
- .always(() => this.alwaysBlock(pattern));
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.get("valueBuffer"),
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
- .always(() => this.alwaysBlock(pattern));
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
- .always(() => this.alwaysBlock(pattern));
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
- const updateTimes = () => {
7
- Em.$(".auto-update-time").each(function() {
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
- if (text !== elem.innerText) {
13
- elem.innerText = text;
14
- }
15
- });
16
- Em.run.later(updateTimes, 60000);
17
- };
18
-
19
- Em.run.later(updateTimes, 60000);
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
- Em.$("#bottom-panel").css("height", amount - 13);
22
- Em.$("#top-panel").css("bottom", amount + 12);
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() {
@@ -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="{&quot;env_expandable_keys&quot;:[],&quot;patterns_enabled&quot;:true}">
9
+ <meta id="preloaded-data" data-root-path="/logs" data-preloaded="{&quot;env_expandable_keys&quot;:[],&quot;patterns_enabled&quot;:true,&quot;gems_dir&quot;:&quot;/home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/&quot;,&quot;backtrace_links_enabled&quot;:true,&quot;gems_data&quot;:[],&quot;directories&quot;:[{&quot;path&quot;:&quot;/home/sam/Source/discourse&quot;,&quot;url&quot;:&quot;https://github.com/discourse/discourse&quot;,&quot;main_app&quot;:true}],&quot;application_version&quot;:&quot;b329e23f8511b7248c0e4aee370a9f8a249e1b84&quot;}">
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
- Em.$("body").addClass("mobile");
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");
@@ -0,0 +1,11 @@
1
+ export function bound(target, key, desc) {
2
+ const orig = desc.value;
3
+ const boundKey = `_${key}Bound`;
4
+ return {
5
+ get() {
6
+ if (this[boundKey]) return this[boundKey];
7
+ this.set(boundKey, orig.bind(this));
8
+ return this[boundKey];
9
+ }
10
+ };
11
+ }
@@ -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 = Em.$.extend(JSON.parse(dataset.preloaded), {
8
- rootPath: dataset.rootPath
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
  "&": "&amp;",
@@ -14,16 +15,47 @@ export function escapeHtml(string) {
14
15
  }
15
16
 
16
17
  export function ajax(url, settings) {
17
- settings = settings || {};
18
- settings.headers = settings.headers || {};
19
- settings.headers["X-SILENCE-LOGGER"] = true;
20
- return Em.$.ajax(Preload.get("rootPath") + url, settings);
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 Em.RSVP.resolve(preloaded);
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
- _.each(hash, (v, k) => {
137
+ Object.keys(hash).forEach(k => {
138
+ const v = hash[k];
106
139
  if (v === null) {
107
140
  buffer.push("null");
108
- } else if (Object.prototype.toString.call(v) === "[object Array]") {
141
+ } else if (expandableKeys.indexOf(k) !== -1 && !recurse) {
109
142
  let valueHtml = "";
110
- if (
111
- expandableKeys.indexOf(k) !== -1 &&
112
- !recurse &&
113
- expanded.indexOf(k) === -1 &&
114
- v.length > 3
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 = buildArrayString(v);
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
- "<tr><td>" + escapeHtml(k) + "</td><td>" + valueHtml + "</td></tr>"
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
- if (_.size(hashes) > 0) {
141
- _.each(hashes, function(k1) {
142
- const v = hash[k1];
143
- buffer.push("<tr><td></td><td><table>");
144
- buffer.push(
145
- "<td>" +
146
- escapeHtml(k1) +
147
- "</td><td>" +
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 "<table class='" + className + "'>" + buffer.join("\n") + "</table>";
182
+ return `<table class='${className}'>${buffer.join("\n")}</table>`;
156
183
  }