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
@@ -18,40 +18,43 @@
18
18
  "test": "ember test"
19
19
  },
20
20
  "devDependencies": {
21
- "@ember/optional-features": "^0.6.3",
22
- "broccoli-asset-rev": "^2.7.0",
23
- "ember-ajax": "^5.0.0",
24
- "ember-cli": "^3.8.3",
21
+ "@ember/optional-features": "^1.1.0",
22
+ "@glimmer/component": "^1.0.0",
23
+ "babel-eslint": "^10.0.3",
24
+ "broccoli-asset-rev": "^3.0.0",
25
+ "ember-auto-import": "^1.5.3",
26
+ "ember-cli": "^3.15.0",
25
27
  "ember-cli-app-version": "^3.2.0",
26
- "ember-cli-babel": "^7.12.0",
28
+ "ember-cli-babel": "^7.13.0",
27
29
  "ember-cli-dependency-checker": "^3.2.0",
28
- "ember-cli-eslint": "^4.2.3",
29
- "ember-cli-htmlbars": "^3.1.0",
30
- "ember-cli-htmlbars-inline-precompile": "^1.0.3",
31
- "ember-cli-inject-live-reload": "^1.8.2",
30
+ "ember-cli-eslint": "^5.1.0",
31
+ "ember-cli-htmlbars": "^4.2.0",
32
+ "ember-cli-inject-live-reload": "^2.0.1",
32
33
  "ember-cli-sri": "^2.1.1",
33
34
  "ember-cli-template-lint": "^1.0.0-beta.3",
34
- "ember-cli-uglify": "^2.1.0",
35
- "ember-data": "~3.8.0",
36
- "ember-export-application-global": "^2.0.0",
35
+ "ember-cli-uglify": "^3.0.0",
36
+ "ember-data": "~3.15.0",
37
+ "ember-export-application-global": "^2.0.1",
38
+ "ember-fetch": "^7.0.0",
37
39
  "ember-font-awesome": "^4.0.0-rc.4",
38
- "ember-load-initializers": "^1.1.0",
40
+ "ember-load-initializers": "^2.1.1",
39
41
  "ember-maybe-import-regenerator": "^0.1.6",
40
- "ember-qunit": "^3.4.1",
41
- "ember-resolver": "^5.3.0",
42
- "ember-source": "^3.8.3",
43
- "ember-welcome-page": "^3.2.0",
44
- "eslint-plugin-ember": "^5.4.0",
45
- "jquery": "3.4.1",
42
+ "ember-qunit": "^4.6.0",
43
+ "ember-resolver": "^7.0.0",
44
+ "ember-source": "^3.15.0",
45
+ "eslint-plugin-ember": "^7.7.1",
46
+ "eslint-plugin-node": "^10.0.0",
46
47
  "loader.js": "^4.7.0",
47
48
  "prettier": "^1.19.1",
48
- "qunit-dom": "^0.8.5"
49
+ "qunit-dom": "^0.9.2"
49
50
  },
50
51
  "engines": {
51
- "node": "6.* || 8.* || >= 10.*"
52
+ "node": "8.* || >= 10.*"
53
+ },
54
+ "ember": {
55
+ "edition": "octane"
52
56
  },
53
57
  "dependencies": {
54
- "lodash": "^4.17.15",
55
58
  "moment": "~2.22.2"
56
59
  }
57
60
  }
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This script takes care of updating the content of the preloaded json in app/index.html and tests/index.html
4
+ # All you need to do is update the ruby hash of the corresponding file you want to update and run the script
5
+
6
+ require 'bundler/inline'
7
+ require 'json'
8
+ require 'cgi'
9
+
10
+ gemfile do
11
+ source 'https://rubygems.org'
12
+ gem 'nokogiri'
13
+ end
14
+
15
+ tests_index_html = {
16
+ env_expandable_keys: [],
17
+ gems_dir: "/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/",
18
+ backtrace_links_enabled: true,
19
+ gems_data: [
20
+ {
21
+ name: "activerecord",
22
+ url: "https://github.com/rails/rails/tree/v6.0.1/activerecord"
23
+ }
24
+ ],
25
+ directories: [
26
+ {
27
+ path: "/var/www/discourse",
28
+ url: "https://github.com/discourse/discourse",
29
+ main_app: true
30
+ },
31
+ {
32
+ path: "/var/www/discourse/plugins/discourse-prometheus",
33
+ url: "https://github.com/discourse/discourse-prometheus"
34
+ }
35
+ ],
36
+ application_version: "ce512452b512b909c38e9c63f2a0e1f8c17a2399"
37
+ }
38
+
39
+ app_index_html = {
40
+ env_expandable_keys: [],
41
+ patterns_enabled: true,
42
+ gems_dir: "/home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/",
43
+ backtrace_links_enabled: true,
44
+ gems_data: [],
45
+ directories: [
46
+ {
47
+ path: "/home/sam/Source/discourse",
48
+ url: "https://github.com/discourse/discourse",
49
+ main_app: true
50
+ }
51
+ ],
52
+ application_version: "b329e23f8511b7248c0e4aee370a9f8a249e1b84"
53
+ }
54
+
55
+ types = { app: app_index_html, tests: tests_index_html }
56
+
57
+ %i{app tests}.each do |type|
58
+ content = File.read("#{type}/index.html")
59
+ json = CGI.escapeHTML(JSON.generate(types[type]))
60
+ content.sub!(/data-preloaded=".*">$/, "data-preloaded=\"#{json}\">")
61
+ File.write("#{type}/index.html", content)
62
+ end
@@ -13,7 +13,6 @@ module.exports = {
13
13
  // --no-sandbox is needed when running Chrome inside a container
14
14
  process.env.CI ? '--no-sandbox' : null,
15
15
  '--headless',
16
- '--disable-gpu',
17
16
  '--disable-dev-shm-usage',
18
17
  '--disable-software-rasterizer',
19
18
  '--mute-audio',
@@ -6,7 +6,7 @@
6
6
  <title>ClientApp Tests</title>
7
7
  <meta name="description" content="">
8
8
  <meta name="viewport" content="width=device-width, initial-scale=1">
9
- <meta id="preloaded-data" data-root-path="/logs" data-preloaded="{&quot;env_expandable_keys&quot;:[]}">
9
+ <meta id="preloaded-data" data-root-path="/logs" data-preloaded="{&quot;env_expandable_keys&quot;:[],&quot;gems_dir&quot;:&quot;/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/&quot;,&quot;backtrace_links_enabled&quot;:true,&quot;gems_data&quot;:[{&quot;name&quot;:&quot;activerecord&quot;,&quot;url&quot;:&quot;https://github.com/rails/rails/tree/v6.0.1/activerecord&quot;}],&quot;directories&quot;:[{&quot;path&quot;:&quot;/var/www/discourse&quot;,&quot;url&quot;:&quot;https://github.com/discourse/discourse&quot;,&quot;main_app&quot;:true},{&quot;path&quot;:&quot;/var/www/discourse/plugins/discourse-prometheus&quot;,&quot;url&quot;:&quot;https://github.com/discourse/discourse-prometheus&quot;}],&quot;application_version&quot;:&quot;ce512452b512b909c38e9c63f2a0e1f8c17a2399&quot;}">
10
10
 
11
11
  {{content-for "head"}}
12
12
  {{content-for "test-head"}}
@@ -0,0 +1,109 @@
1
+ import { module, test } from "qunit";
2
+ import { setupRenderingTest } from "ember-qunit";
3
+ import { render, find, findAll } from "@ember/test-helpers";
4
+ import hbs from "htmlbars-inline-precompile";
5
+ import { uninitialize, mutatePreload } from "client-app/lib/preload";
6
+
7
+ module("Integration | Component | back-trace", function(hooks) {
8
+ setupRenderingTest(hooks);
9
+
10
+ hooks.beforeEach(function() {
11
+ uninitialize();
12
+ });
13
+
14
+ hooks.afterEach(function() {
15
+ uninitialize();
16
+ });
17
+
18
+ test("backtrace lines display and work correctly", async function(assert) {
19
+ const backtrace = `/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/relation/finder_methods.rb:317:in \`exists?'
20
+ /var/www/discourse/lib/permalink_constraint.rb:6:in \`matches?'
21
+ /var/www/discourse/plugins/discourse-prometheus/lib/middleware/metrics.rb:17:in \`call'`;
22
+ this.set("backtrace", backtrace);
23
+ await render(hbs`{{back-trace backtrace=backtrace}}`);
24
+
25
+ const [gem, app, plugin] = findAll("a");
26
+ assert.equal(
27
+ gem.href,
28
+ "https://github.com/rails/rails/tree/v6.0.1/activerecord/lib/active_record/relation/finder_methods.rb#L317"
29
+ );
30
+
31
+ assert.equal(
32
+ app.href,
33
+ "https://github.com/discourse/discourse/blob/ce512452b512b909c38e9c63f2a0e1f8c17a2399/lib/permalink_constraint.rb#L6"
34
+ );
35
+
36
+ assert.equal(
37
+ plugin.href,
38
+ "https://github.com/discourse/discourse-prometheus/blob/master/lib/middleware/metrics.rb#L17"
39
+ );
40
+
41
+ let gemLine = find("div.backtrace-line");
42
+ assert.equal(
43
+ gemLine.textContent,
44
+ "activerecord-6.0.1/lib/active_record/relation/finder_methods.rb:317:in `exists?'",
45
+ "gem lines are truncated"
46
+ );
47
+ });
48
+
49
+ test("non-ruby backtraces don't break things", async function(assert) {
50
+ this.set(
51
+ "backtrace",
52
+ `m/<@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27448
53
+ m@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27560
54
+ string@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27869`
55
+ );
56
+ await render(hbs`{{back-trace backtrace=backtrace}}`);
57
+ const lines = this.backtrace.split("\n");
58
+ findAll("div.backtrace-line").forEach((node, index) => {
59
+ assert.equal(node.textContent.trim(), lines[index]);
60
+ });
61
+ });
62
+
63
+ test("Github links use commit sha", async function(assert) {
64
+ const backtrace = `/var/www/discourse/lib/permalink_constraint.rb:6:in \`matches?'`;
65
+ let env = [
66
+ { application_version: "123abc" },
67
+ { application_version: "abc123" }
68
+ ];
69
+ this.setProperties({
70
+ backtrace,
71
+ env
72
+ });
73
+ await render(hbs`{{back-trace backtrace=backtrace env=env}}`);
74
+ let href = find("a").href;
75
+ assert.equal(
76
+ href,
77
+ "https://github.com/discourse/discourse/blob/123abc/lib/permalink_constraint.rb#L6",
78
+ "uses the first application_version if there are multiple versions"
79
+ );
80
+
81
+ env = { application_version: "567def" };
82
+ this.set("env", env);
83
+ await render(hbs`{{back-trace backtrace=backtrace env=env}}`);
84
+ href = find("a").href;
85
+ assert.equal(
86
+ href,
87
+ "https://github.com/discourse/discourse/blob/567def/lib/permalink_constraint.rb#L6",
88
+ "uses application_version when env is only a hash"
89
+ );
90
+
91
+ this.set("env", null);
92
+ await render(hbs`{{back-trace backtrace=backtrace env=env}}`);
93
+ href = find("a").href;
94
+ assert.equal(
95
+ href,
96
+ "https://github.com/discourse/discourse/blob/ce512452b512b909c38e9c63f2a0e1f8c17a2399/lib/permalink_constraint.rb#L6",
97
+ "falls back to preload if env doesn't contain application_version"
98
+ );
99
+
100
+ mutatePreload("application_version", null);
101
+ await render(hbs`{{back-trace backtrace=backtrace}}`);
102
+ href = find("a").href;
103
+ assert.equal(
104
+ href,
105
+ "https://github.com/discourse/discourse/blob/master/lib/permalink_constraint.rb#L6",
106
+ "falls back to master branch when neither preload nor application_version in env are available"
107
+ );
108
+ });
109
+ });
@@ -32,11 +32,12 @@ module("Integration | Component | message-info", function(hooks) {
32
32
  showTitle=showTitle
33
33
  currentEnvPosition=envPosition
34
34
  envChangedAction=callback
35
+ showShare=true
35
36
  actionsInMenu=actionsInMenu}}`
36
37
  );
37
38
  let activeTab = find(".message-info .content.active pre");
38
39
  assert.equal(
39
- activeTab.textContent,
40
+ activeTab.textContent.trim(),
40
41
  backtrace,
41
42
  "default active tab is backtrace"
42
43
  );
@@ -60,7 +61,7 @@ module("Integration | Component | message-info", function(hooks) {
60
61
  await click(find(".message-actions button.expand.no-text"));
61
62
  assert.equal(
62
63
  findAll(".actions-menu button").length,
63
- 2,
64
+ 3,
64
65
  "extra buttons shown inside a menu"
65
66
  );
66
67
  assert
@@ -85,7 +86,7 @@ module("Integration | Component | message-info", function(hooks) {
85
86
  .doesNotExist("menu expand button is not shown");
86
87
  assert.equal(
87
88
  findAll(".message-actions button").length,
88
- 3,
89
+ 4,
89
90
  "all actions buttons are shown inline when `actionsInMenu` is false"
90
91
  );
91
92
 
@@ -12,7 +12,9 @@ module("Integration | Component | patterns-list", function(hooks) {
12
12
  mutable: true,
13
13
  patterns: []
14
14
  });
15
- await render(hbs`{{patterns-list patterns=patterns mutable=mutable}}`);
15
+ await render(
16
+ hbs`{{patterns-list patterns=patterns mutable=mutable showCounter=true}}`
17
+ );
16
18
  assert
17
19
  .dom(".pattern-input")
18
20
  .exists("It shows an input when patterns are emtpy");
@@ -33,7 +35,10 @@ module("Integration | Component | patterns-list", function(hooks) {
33
35
  .doesNotExist("No save buttons are shown when there is 0 buffer");
34
36
  const counters = findAll("input.count");
35
37
  assert.equal(counters.length, 3, "counters shown for all patterns");
36
- assert.ok(counters.every(c => c.disabled), "counters are disabled");
38
+ assert.ok(
39
+ counters.every(c => c.disabled),
40
+ "counters are disabled"
41
+ );
37
42
 
38
43
  pattern1.set("count", 6);
39
44
  this.set("patterns", [pattern1, pattern2]);
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logster/version'
3
4
  require 'logster/logger'
4
5
  require 'logster/message'
5
6
  require 'logster/configuration'
@@ -18,7 +18,7 @@ module Logster
18
18
  end
19
19
 
20
20
  # Modify the saved message to the given one (identified by message.key) and bump it to the top of the latest list
21
- def replace_and_bump(message, save_env: true)
21
+ def replace_and_bump(message)
22
22
  not_implemented
23
23
  end
24
24
 
@@ -195,26 +195,33 @@ module Logster
195
195
  similar = nil
196
196
 
197
197
  if Logster.config.allow_grouping
198
+ message.apply_message_size_limit(
199
+ Logster.config.maximum_message_size_bytes,
200
+ gems_dir: Logster.config.gems_dir
201
+ )
198
202
  key = self.similar_key(message)
199
203
  similar = get(key, load_env: false) if key
200
204
  end
201
205
 
206
+ message.drop_redundant_envs(Logster.config.max_env_count_per_message)
207
+ message.apply_env_size_limit(Logster.config.max_env_bytes)
208
+ saved = true
202
209
  if similar
203
- if similar.count < Logster::MAX_GROUPING_LENGTH
204
- similar.env = get_env(similar.key) || {}
205
- end
206
- save_env = similar.merge_similar_message(message)
207
-
208
- replace_and_bump(similar, save_env: save_env)
210
+ similar.merge_similar_message(message)
211
+ replace_and_bump(similar)
209
212
  similar
210
213
  else
211
- save message
214
+ message.apply_message_size_limit(
215
+ Logster.config.maximum_message_size_bytes,
216
+ gems_dir: Logster.config.gems_dir
217
+ )
218
+ saved = save(message)
212
219
  message
213
220
  end
214
221
 
215
222
  message = similar || message
216
223
 
217
- if Logster.config.enable_custom_patterns_via_ui || allow_custom_patterns
224
+ if (Logster.config.enable_custom_patterns_via_ui || allow_custom_patterns) && saved
218
225
  grouping_patterns = @patterns_cache.fetch(Logster::GroupingPattern::CACHE_KEY) do
219
226
  Logster::GroupingPattern.find_all(store: self)
220
227
  end
@@ -12,7 +12,12 @@ module Logster
12
12
  :environments,
13
13
  :rate_limit_error_reporting,
14
14
  :web_title,
15
- :maximum_message_size_bytes
15
+ :maximum_message_size_bytes,
16
+ :project_directories,
17
+ :enable_backtrace_links,
18
+ :gems_dir,
19
+ :max_env_bytes,
20
+ :max_env_count_per_message
16
21
  )
17
22
 
18
23
  attr_writer :subdirectory
@@ -26,7 +31,12 @@ module Logster
26
31
  @enable_custom_patterns_via_ui = false
27
32
  @rate_limit_error_reporting = true
28
33
  @enable_js_error_reporting = true
29
- @maximum_message_size_bytes = 60_000
34
+ @maximum_message_size_bytes = 10_000
35
+ @max_env_bytes = 1000
36
+ @max_env_count_per_message = 50
37
+ @project_directories = []
38
+ @enable_backtrace_links = true
39
+ @gems_dir = Gem.dir + "/gems/"
30
40
 
31
41
  @allow_grouping = false
32
42
 
@@ -7,7 +7,7 @@ module Logster
7
7
  private
8
8
 
9
9
  def report_to_store(severity, progname, message, opts = {})
10
- opts[:backtrace] ||= caller
10
+ opts[:backtrace] ||= caller.join("\n")
11
11
  Logster::Scheduler.schedule do
12
12
  super(severity, progname, message, opts)
13
13
  end
@@ -70,6 +70,18 @@ module Logster
70
70
  message = message.scrub
71
71
  end
72
72
 
73
+ # we want to get the backtrace as early as possible so that logster's
74
+ # own methods don't show up as the first few frames in the backtrace
75
+ if !opts || !opts.key?(:backtrace)
76
+ opts ||= {}
77
+ backtrace = caller_locations
78
+ while backtrace.first.path.end_with?("/logger.rb")
79
+ backtrace.shift
80
+ end
81
+ backtrace = backtrace.join("\n")
82
+ opts[:backtrace] = backtrace
83
+ end
84
+
73
85
  if @chained
74
86
  i = 0
75
87
  # micro optimise for logging
@@ -4,8 +4,6 @@ require 'digest/sha1'
4
4
  require 'securerandom'
5
5
 
6
6
  module Logster
7
-
8
- MAX_GROUPING_LENGTH = 50
9
7
  MAX_MESSAGE_LENGTH = 600
10
8
 
11
9
  class Message
@@ -22,9 +20,10 @@ module Logster
22
20
  hostname
23
21
  process_id
24
22
  application_version
23
+ time
25
24
  }
26
25
 
27
- attr_accessor :timestamp, :severity, :progname, :key, :backtrace, :count, :protected, :first_timestamp
26
+ attr_accessor :timestamp, :severity, :progname, :key, :backtrace, :count, :protected, :first_timestamp, :env_buffer
28
27
  attr_reader :message, :env
29
28
 
30
29
  def initialize(severity, progname, message, timestamp = nil, key = nil, count: 1)
@@ -37,6 +36,7 @@ module Logster
37
36
  @count = count || 1
38
37
  @protected = false
39
38
  @first_timestamp = nil
39
+ @env_buffer = []
40
40
  end
41
41
 
42
42
  def to_h(exclude_env: false)
@@ -82,7 +82,6 @@ module Logster
82
82
  end
83
83
 
84
84
  def env=(env)
85
- @env_json = nil
86
85
  @env = self.class.scrub_params(env)
87
86
  end
88
87
 
@@ -94,12 +93,18 @@ module Logster
94
93
  env ||= {}
95
94
  if Array === env
96
95
  env = env.map do |single_env|
97
- self.class.default_env.merge(single_env)
96
+ single_env = self.class.default_env.merge(single_env)
97
+ if !single_env.key?("time") && !single_env.key?(:time)
98
+ single_env["time"] = @timestamp || get_timestamp
99
+ end
100
+ single_env
98
101
  end
99
102
  else
100
103
  env = self.class.default_env.merge(env)
104
+ if !env.key?("time") && !env.key?(:time)
105
+ env["time"] = @timestamp || get_timestamp
106
+ end
101
107
  end
102
- @env_json = nil
103
108
  @env = Message.populate_from_env(env)
104
109
  end
105
110
 
@@ -127,12 +132,11 @@ module Logster
127
132
  if Array === env
128
133
  versions = env.map { |single_env| single_env["application_version"] }
129
134
  else
130
- versions = env["application_version"]
135
+ versions = [env["application_version"]]
131
136
  end
137
+ versions.compact!
132
138
 
133
- if versions && backtrace && backtrace.length > 0
134
- versions = [versions] if String === versions
135
-
139
+ if backtrace && backtrace.length > 0
136
140
  versions.map do |version|
137
141
  Digest::SHA1.hexdigest "#{version} #{backtrace}"
138
142
  end
@@ -146,31 +150,24 @@ module Logster
146
150
  def merge_similar_message(other)
147
151
  self.first_timestamp ||= self.timestamp
148
152
  self.timestamp = [self.timestamp, other.timestamp].max
149
-
150
153
  self.count += other.count || 1
151
- return false if self.count > Logster::MAX_GROUPING_LENGTH
152
154
 
153
- size = self.to_json(exclude_env: true).bytesize + self.env_json.bytesize
154
- extra_env_size = other.env_json.bytesize
155
- return false if size + extra_env_size > Logster.config.maximum_message_size_bytes
156
-
157
- other_env = JSON.load JSON.fast_generate other.env
158
- if Hash === other_env && !other_env.key?("time")
159
- other_env["time"] = other.timestamp
160
- end
161
- if Hash === self.env && !self.env.key?("time")
162
- self.env["time"] = self.first_timestamp
155
+ if Hash === other.env && !other.env.key?("time") && !other.env.key?(:time)
156
+ other.env["time"] = other.timestamp
163
157
  end
164
158
 
165
- if Array === self.env
166
- Array === other_env ? self.env.concat(other_env) : self.env << other_env
159
+ if Array === other.env
160
+ env_buffer.unshift(*other.env)
167
161
  else
168
- Array === other_env ? self.env = [self.env, *other_env] : self.env = [self.env, other_env]
162
+ env_buffer.unshift(other.env)
169
163
  end
170
- @env_json = nil
171
164
  true
172
165
  end
173
166
 
167
+ def has_env_buffer?
168
+ env_buffer.size > 0
169
+ end
170
+
174
171
  def self.populate_from_env(env)
175
172
  if Array === env
176
173
  env.map do |single_env|
@@ -229,10 +226,6 @@ module Logster
229
226
  end
230
227
  end
231
228
 
232
- def env_json
233
- @env_json ||= (self.env || {}).to_json
234
- end
235
-
236
229
  def self.scrub_params(params)
237
230
  if Array === params
238
231
  params.map! { |p| scrub_params(p) }
@@ -250,8 +243,74 @@ module Logster
250
243
  end
251
244
  end
252
245
 
246
+ def drop_redundant_envs(limit)
247
+ if Array === env
248
+ env.slice!(limit..-1)
249
+ end
250
+ end
251
+
252
+ def apply_env_size_limit(size_limit)
253
+ if Array === env
254
+ env.each { |e| truncate_env(e, size_limit) }
255
+ elsif Hash === env
256
+ truncate_env(env, size_limit)
257
+ end
258
+ end
259
+
260
+ def apply_message_size_limit(limit, gems_dir: nil)
261
+ size = self.to_json(exclude_env: true).bytesize
262
+ if size > limit && @backtrace
263
+ @backtrace.gsub!(gems_dir, "") if gems_dir
264
+ @backtrace.strip!
265
+ size = self.to_json(exclude_env: true).bytesize
266
+ backtrace_limit = limit - (size - @backtrace.bytesize)
267
+ return if backtrace_limit <= 0 || size <= limit
268
+ truncate_backtrace(backtrace_limit)
269
+ end
270
+ end
271
+
272
+ def truncate_backtrace(bytes_limit)
273
+ @backtrace = @backtrace.byteslice(0...bytes_limit)
274
+ while !@backtrace[-1].valid_encoding? && @backtrace.size > 1
275
+ @backtrace.slice!(-1)
276
+ end
277
+ end
278
+
253
279
  protected
254
280
 
281
+ def truncate_env(env, limit)
282
+ if JSON.fast_generate(env).bytesize > limit
283
+ sizes = {}
284
+ braces = '{}'.bytesize
285
+ env.each do |k, v|
286
+ sizes[k] = JSON.fast_generate(k => v).bytesize - braces
287
+ end
288
+ sorted = env.keys.sort { |a, b| sizes[a] <=> sizes[b] }
289
+
290
+ kept_keys = []
291
+ if env.key?(:time)
292
+ kept_keys << :time
293
+ elsif env.key?("time")
294
+ kept_keys << "time"
295
+ end
296
+
297
+ sum = braces
298
+ if time_key = kept_keys.first
299
+ sum += sizes[time_key]
300
+ sorted.delete(time_key)
301
+ end
302
+ comma = ','.bytesize
303
+
304
+ sorted.each do |k|
305
+ extra = kept_keys.size == 0 ? 0 : comma
306
+ break if sum + sizes[k] + extra > limit
307
+ kept_keys << k
308
+ sum += sizes[k] + extra
309
+ end
310
+ env.select! { |k| kept_keys.include?(k) }
311
+ end
312
+ end
313
+
255
314
  def truncate_message(msg)
256
315
  return msg unless msg
257
316
  msg = msg.inspect unless String === msg