prefab-cloud-ruby 1.3.1 → 1.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ac25de1650da9f74095f10f535d3d82d7b865c8e3f2c56d621703ebe5a5c15f9
4
- data.tar.gz: a628c38bba37dbb0f49fcb9ed6beeb6c58363a73a656a24d853d87ea9d835d92
3
+ metadata.gz: a71229211822c51b1c42109d9554b59508abe12226af8f1f4c0c5717426309ad
4
+ data.tar.gz: 643af23ac3361bbc7a1ea6d2d45bcb84cf15788bc82e2823705997e458fca5c6
5
5
  SHA512:
6
- metadata.gz: 1ea0aef2dd291d5263b825ad7ccb0565feb05acba45c0e142872f22b1111b93c3fadb431a03540230d97f48b20c13772821d523e41beb9b940bcbeec25e074bf
7
- data.tar.gz: 9a70210438aeff434d33adfd6119ea279d4b27ff0c87cc604970ed3e48c77651d8064b761b06cc376039f29cab2519f72c494579472de87bdfadd7fdf02c4467
6
+ metadata.gz: cc81730e0dc3282aa5519c7fe5aa36282fece615252869c5464c4ad0c35eb0ec53ae1507a1b9478aea56486429ff1cb9c4f241166b942dc2ff81955eeb6bd054
7
+ data.tar.gz: bf81a80940acec11928e5445c024aee0b17c74da03a343c2d90ce9b216db121762767c2c3d2e28836efded14d77af90077f095f1e472339f1940fa5abad4863c
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
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)
8
+
9
+ ## 1.3.2 - 2023-11-15
10
+ - Send back cloud.prefab logging telemetry (#160)
4
11
 
5
12
  ## 1.3.1 - 2023-11-14
6
13
  - Improve path of rails.controller logging & fix strong param include (#159)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.1
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,48 +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
- @log_path_aggregator&.push(path_loc, severity)
60
-
61
63
  log(message, path, progname, severity, log_context, &block)
62
64
  end
63
65
 
64
- def log_internal(severity, message, path, log_context={}, &block)
66
+ def log_internal(severity, message, path, log_context = {}, &block)
65
67
  return if @recurse_check[local_log_id]
66
68
  @recurse_check[local_log_id] = true
67
69
  begin
@@ -71,8 +73,10 @@ module Prefab
71
73
  end
72
74
  end
73
75
 
74
- def log(message, path, progname, severity, log_context={})
76
+ def log(message, path, progname, severity, log_context = {})
75
77
  severity ||= ::Logger::UNKNOWN
78
+ @log_path_aggregator&.push(path, severity)
79
+
76
80
  return true if @logdev.nil? || severity < level_of(path) || @silences[local_log_id]
77
81
 
78
82
  progname = @progname if progname.nil?
@@ -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.1 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.1"
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-14"
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' })
@@ -34,22 +34,19 @@ class TestLogPathAggregator < Minitest::Test
34
34
  client.log_path_aggregator.send(:sync)
35
35
  end
36
36
 
37
- assert_equal [[
38
- '/api/v1/known-loggers',
39
- PrefabProto::Loggers.new(
40
- loggers: [PrefabProto::Logger.new(logger_name: 'test.test_log_path_aggregator.test_sync',
41
- infos: 2, errors: 3)],
42
- start_at: Prefab::TimeHelpers.now_in_ms,
43
- end_at: Prefab::TimeHelpers.now_in_ms,
44
- instance_hash: client.instance_hash,
45
- namespace: 'this.is.a.namespace'
46
- )
47
- ]], requests
37
+ assert_equal '/api/v1/known-loggers', requests[0][0]
38
+ sent_logger = requests[0][1]
39
+ assert_equal 'this.is.a.namespace', sent_logger.namespace
40
+ assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.start_at
41
+ assert_equal Prefab::TimeHelpers.now_in_ms, sent_logger.end_at
42
+ assert_equal client.instance_hash, sent_logger.instance_hash
43
+ assert_includes sent_logger.loggers, PrefabProto::Logger.new(logger_name: 'test.test_log_path_aggregator.test_sync', infos: 2, errors: 3)
44
+ assert_includes sent_logger.loggers, PrefabProto::Logger.new(logger_name: 'cloud.prefab.client.client', debugs: 1) # spot test that internal logging is here too
48
45
 
49
46
  assert_logged [
50
- 'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints',
51
- 'ERROR 2023-08-09 15:18:12 -0400: test.test_log_path_aggregator.test_sync here is a message'
52
- ]
47
+ 'WARN 2023-08-09 15:18:12 -0400: cloud.prefab.client.configclient No success loading checkpoints',
48
+ 'ERROR 2023-08-09 15:18:12 -0400: test.test_log_path_aggregator.test_sync here is a message'
49
+ ]
53
50
  end
54
51
  end
55
52
 
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.1
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-14 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