logster 2.5.1 → 2.6.0

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.
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
  }