prefab-cloud-ruby 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51803ee8ef4be86a0dbe2a7cbf678ea24c91eb5f023c37cc66ed9477d0132ae7
4
- data.tar.gz: 43106c26d3dcb9c3736ef5d6b1ff69e90525cc34098d74406e183eff8c536222
3
+ metadata.gz: a71229211822c51b1c42109d9554b59508abe12226af8f1f4c0c5717426309ad
4
+ data.tar.gz: 643af23ac3361bbc7a1ea6d2d45bcb84cf15788bc82e2823705997e458fca5c6
5
5
  SHA512:
6
- metadata.gz: 3051706d0c390715de78a1fb2e8c02eb030801bb8923498a4fb12e6b3ec47c3a18b67fabae5bc3d189ed6fd3f7cd5d4589f5159f1474af61ce2b38444e524ac3
7
- data.tar.gz: 7398e9212e21f4a10d5093b7b5c572e1f7f938b3a805bb41c694c38246123a1d464f67cbe86e04ea0581c8c830e1100429378c6d7c24351618a5e4b97c063e3c
6
+ metadata.gz: cc81730e0dc3282aa5519c7fe5aa36282fece615252869c5464c4ad0c35eb0ec53ae1507a1b9478aea56486429ff1cb9c4f241166b942dc2ff81955eeb6bd054
7
+ data.tar.gz: bf81a80940acec11928e5445c024aee0b17c74da03a343c2d90ce9b216db121762767c2c3d2e28836efded14d77af90077f095f1e472339f1940fa5abad4863c
data/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 1.4.0 - 2023-11-28
4
+ - ActiveJob tagged logger issue (#164)
5
+ - Compact Log Format (#163)
6
+ - Tagged Logging (#161)
7
+ - ContextKey logging thread safety (#162)
4
8
 
5
9
  ## 1.3.2 - 2023-11-15
6
10
  - Send back cloud.prefab logging telemetry (#160)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.2
1
+ 1.4.0
@@ -60,6 +60,14 @@ module Prefab
60
60
  Thread.current[THREAD_KEY] = old_context
61
61
  end
62
62
 
63
+ def with_merged_context(context)
64
+ old_context = Thread.current[THREAD_KEY]
65
+ Thread.current[THREAD_KEY] = merge_with_current(context)
66
+ yield
67
+ ensure
68
+ Thread.current[THREAD_KEY] = old_context
69
+ end
70
+
63
71
  def clear_current
64
72
  Thread.current[THREAD_KEY] = nil
65
73
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ module Errors
5
+ class UninitializedError < Prefab::Error
6
+ def initialize(key=nil)
7
+ message = "Use Prefab.initialize before calling Prefab.get #{key}"
8
+
9
+ super(message)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -5,6 +5,8 @@ module Prefab
5
5
  SEP = '.'
6
6
  BASE_KEY = 'log-level'
7
7
  UNKNOWN_PATH = 'unknown.'
8
+ LOG_TAGS = 'log.tags'
9
+ REQ_TAGS = 'req.tags'
8
10
 
9
11
  LOG_LEVEL_LOOKUPS = {
10
12
  PrefabProto::LogLevel::NOT_SET_LOG_LEVEL => ::Logger::DEBUG,
@@ -20,46 +22,48 @@ module Prefab
20
22
  @@shared_instance ||= LoggerClient.new($stdout)
21
23
  end
22
24
 
23
- attr_reader :context_keys
24
-
25
25
  def initialize(logdev, log_path_aggregator: nil, formatter: Options::DEFAULT_LOG_FORMATTER, prefix: nil)
26
26
  super(logdev)
27
- self.formatter = formatter
27
+ self.formatter = Prefab::Logging::FormatterBase.new(formatter_proc: formatter, logger_client: self)
28
28
  @config_client = BootstrappingConfigClient.new
29
29
  @silences = Concurrent::Map.new(initial_capacity: 2)
30
30
  @recurse_check = Concurrent::Map.new(initial_capacity: 2)
31
31
  @prefix = "#{prefix}#{prefix && '.'}"
32
32
 
33
- @context_keys = Concurrent::Set.new
33
+ @context_keys_map = Concurrent::Map.new(initial_capacity: 4)
34
34
 
35
35
  @log_path_aggregator = log_path_aggregator
36
36
  @@shared_instance = self
37
37
  end
38
38
 
39
39
  def add_context_keys(*keys)
40
- @context_keys += keys
40
+ context_keys.merge(keys)
41
41
  end
42
42
 
43
43
  def with_context_keys(*keys)
44
- @context_keys += keys
44
+ context_keys.merge(keys)
45
45
  yield
46
46
  ensure
47
- @context_keys -= keys
47
+ context_keys.subtract(keys)
48
48
  end
49
49
 
50
- def internal_logger(path=nil)
50
+ def internal_logger(path = nil)
51
51
  InternalLogger.new(path, self)
52
52
  end
53
-
53
+
54
+ def context_keys
55
+ @context_keys_map.fetch_or_store(local_log_id, Concurrent::Set.new)
56
+ end
57
+
54
58
  # InternalLoggers Will Call This
55
- def add_internal(severity, message, progname, loc, log_context={}, &block)
59
+ def add_internal(severity, message, progname, loc, log_context = {}, &block)
56
60
  path_loc = get_loc_path(loc)
57
61
  path = @prefix + path_loc
58
62
 
59
63
  log(message, path, progname, severity, log_context, &block)
60
64
  end
61
65
 
62
- def log_internal(severity, message, path, log_context={}, &block)
66
+ def log_internal(severity, message, path, log_context = {}, &block)
63
67
  return if @recurse_check[local_log_id]
64
68
  @recurse_check[local_log_id] = true
65
69
  begin
@@ -69,7 +73,7 @@ module Prefab
69
73
  end
70
74
  end
71
75
 
72
- def log(message, path, progname, severity, log_context={})
76
+ def log(message, path, progname, severity, log_context = {})
73
77
  severity ||= ::Logger::UNKNOWN
74
78
  @log_path_aggregator&.push(path, severity)
75
79
 
@@ -136,6 +140,34 @@ module Prefab
136
140
  DEBUG
137
141
  end
138
142
 
143
+ def tagged(*tags)
144
+ to_add = tags.flatten.compact
145
+ if block_given?
146
+ new_log_tags = current_tags
147
+ new_log_tags += to_add unless to_add.empty?
148
+ Prefab::Context.with_merged_context({ "log" => { "tags" => new_log_tags } }) do
149
+ with_context_keys LOG_TAGS do
150
+ yield self
151
+ end
152
+ end
153
+ else
154
+ new_log_tags = Prefab::Context.current.get(REQ_TAGS) || []
155
+ new_log_tags += to_add unless to_add.empty?
156
+ add_context_keys REQ_TAGS
157
+ Prefab::Context.current.set("req", {"tags": new_log_tags})
158
+ self
159
+ end
160
+ end
161
+
162
+ def current_tags
163
+ Prefab::Context.current.get(LOG_TAGS) || []
164
+ end
165
+
166
+ def flush
167
+ Prefab::Context.current.set("req", {"tags": nil})
168
+ super if defined?(super)
169
+ end
170
+
139
171
  def config_client=(config_client)
140
172
  @config_client = config_client
141
173
  end
@@ -161,7 +193,7 @@ module Prefab
161
193
 
162
194
  def fetch_context_for_context_keys
163
195
  context = Prefab::Context.current.to_h
164
- Hash[@context_keys.map do |key|
196
+ Hash[context_keys.map do |key|
165
197
  [key, context.dig(*key.split("."))]
166
198
  end]
167
199
  end
@@ -203,16 +235,16 @@ module Prefab
203
235
  path
204
236
  end
205
237
 
206
- def format_message(severity, datetime, progname, msg, path = nil, log_context={})
238
+ def format_message(severity, datetime, progname, msg, path = nil, log_context = {})
207
239
  formatter = (@formatter || @default_formatter)
208
-
209
- formatter.call(
240
+ compact_context = log_context.reject{ |_, v| v.nil? || ((v.is_a? Array) && v.empty?) }
241
+ @formatter.call_proc(
210
242
  severity: severity,
211
243
  datetime: datetime,
212
244
  progname: progname,
213
245
  path: path,
214
246
  message: msg,
215
- log_context: log_context
247
+ log_context: compact_context
216
248
  )
217
249
  end
218
250
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Prefab
4
+ module Logging
5
+ # Shim class to quack like a Rails TaggedLogger
6
+ class FormatterBase < ::Logger
7
+ def initialize(formatter_proc:, logger_client:)
8
+ @formatter_proc = formatter_proc
9
+ @logger_client = logger_client
10
+ end
11
+
12
+ def call_proc(data)
13
+ @formatter_proc.call(data)
14
+ end
15
+
16
+ def current_tags
17
+ @logger_client.current_tags
18
+ end
19
+ end
20
+ end
21
+ end
@@ -32,8 +32,8 @@ module Prefab
32
32
  progname = (progname.nil? || progname.empty?) ? path : "#{progname}: #{path}"
33
33
 
34
34
  formatted_log_context = log_context.sort.map do |k, v|
35
- v.nil? ? nil : "#{k}=#{v}"
36
- end.compact.join(" ")
35
+ "#{k}=#{v}"
36
+ end.join(" ")
37
37
  "#{severity.ljust(5)} #{datetime}:#{' ' if progname}#{progname} #{msg}#{log_context.any? ? " " + formatted_log_context : ""}\n"
38
38
  }
39
39
 
@@ -42,6 +42,18 @@ module Prefab
42
42
  data.merge(log_context).compact.to_json << "\n"
43
43
  }
44
44
 
45
+ COMPACT_LOG_FORMATTER = proc { |data|
46
+ severity = data[:severity]
47
+ msg = data[:message]
48
+ log_context = data[:log_context]
49
+ log_context["path"] = data[:path] || ""
50
+
51
+ formatted_log_context = log_context.sort.map do |k, v|
52
+ "#{k}=#{v}"
53
+ end.join(" ")
54
+ "#{severity.ljust(5)} #{msg&.strip} #{formatted_log_context}\n"
55
+ }
56
+
45
57
  module ON_INITIALIZATION_FAILURE
46
58
  RAISE = :raise
47
59
  RETURN = :return
data/lib/prefab/prefab.rb CHANGED
@@ -27,12 +27,12 @@ module Prefab
27
27
  end
28
28
 
29
29
  def self.get(key, properties = NO_DEFAULT_PROVIDED)
30
- ensure_initialized
30
+ ensure_initialized key
31
31
  @singleton.get(key, properties)
32
32
  end
33
33
 
34
34
  def self.enabled?(feature_name, jit_context = NO_DEFAULT_PROVIDED)
35
- ensure_initialized
35
+ ensure_initialized feature_name
36
36
  @singleton.enabled?(feature_name, jit_context)
37
37
  end
38
38
 
@@ -48,9 +48,9 @@ module Prefab
48
48
 
49
49
  private
50
50
 
51
- def self.ensure_initialized
51
+ def self.ensure_initialized(key = nil)
52
52
  if not defined? @singleton or @singleton.nil?
53
- raise "Use Prefab.initialize before calling Prefab.get"
53
+ raise Prefab::Errors::UninitializedError.new(key)
54
54
  end
55
55
  end
56
56
  end
@@ -20,6 +20,7 @@ require 'prefab/errors/initialization_timeout_error'
20
20
  require 'prefab/errors/invalid_api_key_error'
21
21
  require 'prefab/errors/missing_default_error'
22
22
  require 'prefab/errors/env_var_parse_error'
23
+ require 'prefab/errors/uninitialized_error'
23
24
  require 'prefab/options'
24
25
  require 'prefab/static_logger'
25
26
  require 'prefab/internal_logger'
@@ -45,6 +46,7 @@ require 'prefab/logger_client'
45
46
  require 'active_support/deprecation'
46
47
  require 'active_support'
47
48
  require 'action_controller/metal/strong_parameters'
49
+ require 'prefab/logging/formatter_base'
48
50
  require 'prefab/log_subscribers/action_controller_subscriber'
49
51
  require 'prefab/client'
50
52
  require 'prefab/config_client_presenter'
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: prefab-cloud-ruby 1.3.2 ruby lib
5
+ # stub: prefab-cloud-ruby 1.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "prefab-cloud-ruby".freeze
9
- s.version = "1.3.2"
9
+ s.version = "1.4.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Jeff Dwyer".freeze]
14
- s.date = "2023-11-15"
14
+ s.date = "2023-11-28"
15
15
  s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
16
16
  s.email = "jdwyer@prefab.cloud".freeze
17
17
  s.executables = ["console".freeze]
@@ -54,6 +54,7 @@ Gem::Specification.new do |s|
54
54
  "lib/prefab/errors/initialization_timeout_error.rb",
55
55
  "lib/prefab/errors/invalid_api_key_error.rb",
56
56
  "lib/prefab/errors/missing_default_error.rb",
57
+ "lib/prefab/errors/uninitialized_error.rb",
57
58
  "lib/prefab/evaluation.rb",
58
59
  "lib/prefab/evaluation_summary_aggregator.rb",
59
60
  "lib/prefab/example_contexts_aggregator.rb",
@@ -65,6 +66,7 @@ Gem::Specification.new do |s|
65
66
  "lib/prefab/log_path_aggregator.rb",
66
67
  "lib/prefab/log_subscribers/action_controller_subscriber.rb",
67
68
  "lib/prefab/logger_client.rb",
69
+ "lib/prefab/logging/formatter_base.rb",
68
70
  "lib/prefab/murmer3.rb",
69
71
  "lib/prefab/options.rb",
70
72
  "lib/prefab/periodic_sync.rb",
data/test/test_context.rb CHANGED
@@ -103,6 +103,18 @@ class TestContext < Minitest::Test
103
103
  end
104
104
  end
105
105
 
106
+ def test_with_context_merge_nesting
107
+ Prefab::Context.with_context(EXAMPLE_PROPERTIES) do
108
+ Prefab::Context.with_merged_context({ user: { key: 'abc', other: 'different' } }) do
109
+ context = Prefab::Context.current
110
+ assert_equal({ 'user' => { 'key' => 'abc', 'other' => 'different' }, "team"=>{"key"=>"abc", "plan"=>"pro"} }, context.to_h)
111
+ end
112
+
113
+ context = Prefab::Context.current
114
+ assert_equal(stringify(EXAMPLE_PROPERTIES), context.to_h)
115
+ end
116
+ end
117
+
106
118
  def test_setting
107
119
  context = Prefab::Context.new({})
108
120
  context.set('user', { key: 'value' })
data/test/test_logger.rb CHANGED
@@ -542,6 +542,55 @@ class TestLogger < Minitest::Test
542
542
  end
543
543
  end
544
544
 
545
+ def test_context_key_threads
546
+ prefab, io = captured_logger
547
+
548
+ threads = []
549
+ 1000.times.map do |i|
550
+ threads << Thread.new do
551
+ prefab.with_context({test: {"thread_#{i}": "thread_#{i}"}}) do
552
+ prefab.log.add_context_keys "test.thread_#{i}"
553
+ prefab.log.error "UH OH"
554
+ assert_logged io, 'ERROR', 'test.test_logger.test_context_key_threads',
555
+ "UH OH test.thread_#{i}=thread_#{i}"
556
+ end
557
+ end
558
+ end
559
+ threads.each { |thr| thr.join }
560
+ end
561
+
562
+ def test_tagged
563
+ prefab, io = captured_logger
564
+
565
+ prefab.log.tagged([[]]) do # rails sends some of these
566
+ prefab.log.tagged("outside") do
567
+ prefab.log.tagged("nested", "tag2") do
568
+ prefab.log.error "msg"
569
+ assert_logged io, 'ERROR', 'test.test_logger.test_tagged',
570
+ 'msg log.tags=\["outside", "nested", "tag2"\]'
571
+ end
572
+ end
573
+ end
574
+ end
575
+
576
+ def test_req_tagged
577
+ prefab, io = captured_logger
578
+ prefab.log.tagged("tag-1").error "first"
579
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
580
+ 'first req.tags=\["tag-1"\]'
581
+ reset_io(io)
582
+
583
+ prefab.log.tagged("tag-2").error "2nd"
584
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
585
+ '2nd req.tags=\["tag-1", "tag-2"\]'
586
+ prefab.log.flush
587
+
588
+ prefab.log.tagged("tag-3").error "3rd"
589
+ assert_logged io, 'ERROR', 'test.test_logger.test_req_tagged',
590
+ '3rd req.tags=\["tag-3"\]'
591
+ prefab.log.flush
592
+ end
593
+
545
594
  private
546
595
 
547
596
  def assert_logged(logged_io, level, path, message)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prefab-cloud-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Dwyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-15 00:00:00.000000000 Z
11
+ date: 2023-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -241,6 +241,7 @@ files:
241
241
  - lib/prefab/errors/initialization_timeout_error.rb
242
242
  - lib/prefab/errors/invalid_api_key_error.rb
243
243
  - lib/prefab/errors/missing_default_error.rb
244
+ - lib/prefab/errors/uninitialized_error.rb
244
245
  - lib/prefab/evaluation.rb
245
246
  - lib/prefab/evaluation_summary_aggregator.rb
246
247
  - lib/prefab/example_contexts_aggregator.rb
@@ -252,6 +253,7 @@ files:
252
253
  - lib/prefab/log_path_aggregator.rb
253
254
  - lib/prefab/log_subscribers/action_controller_subscriber.rb
254
255
  - lib/prefab/logger_client.rb
256
+ - lib/prefab/logging/formatter_base.rb
255
257
  - lib/prefab/murmer3.rb
256
258
  - lib/prefab/options.rb
257
259
  - lib/prefab/periodic_sync.rb