posthog-ruby 3.16.0 → 3.18.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: 1091ed7a7995e58f6abbf25b2a45dba44b9c2f15ff152b5a5bf4044856269161
4
- data.tar.gz: f5f5542aa6c66d00bb22c7362ff4e1613eee4ae20b94cf3fe04933ad798b83b5
3
+ metadata.gz: 75e17034bb60a9af9539cfe28137a91a71d83305e4d3dc7d1db3844870efac38
4
+ data.tar.gz: b511f71723032dff61fc998ae145d1e650eb6d90bdb4df84024e9335613aed71
5
5
  SHA512:
6
- metadata.gz: 8f9bc8bf1c98bce5faf4856db35779fd7436df5d538eeb5ae09c3715feb765ade37d22e903056a974f8942f1e207da6de0cba20cd1c08ee601db56a260b33ee3
7
- data.tar.gz: 201ed92c1b736ba97e9d76e3221f2297d9f49de0bdee9adc331991d4316db22bf6d014305ef182df6afcc7b1760b2eb7d3026d4337de81c9ce5dbec225239826
6
+ metadata.gz: d27932884831bba0abb8b90315c1c8c684ae821aa85a5f6df8b486339d53097022ee2d58303b913a46cb05cf576230bf1f45b2ffdbdf465c79fce4875239cfb9
7
+ data.tar.gz: 043c0f02fb14f744898e7910623757931021d03cb0fbcacc46a305ec2f4d753df81a1ed8482b72f6e9c7e8bc1c826c78b86e889491ceb4c112dfa1844b1cf82a
@@ -53,7 +53,11 @@ module PostHog
53
53
 
54
54
  # @param opts [Hash] Client configuration.
55
55
  # @option opts [String, nil] :api_key Your project's API key. Missing or blank values disable the client.
56
- # @option opts [String, nil] :personal_api_key Your personal API key. Required for local feature flag evaluation.
56
+ # @option opts [String, nil] :secret_key The credential used for local feature flag evaluation and remote
57
+ # config. Accepts either a Personal API Key (`phx_...`) or a Project Secret API Key (`phs_...`). Required
58
+ # for local feature flag evaluation.
59
+ # @option opts [String, nil] :personal_api_key
60
+ # @deprecated Use +:secret_key+ instead. Retained as an alias; when both are supplied, +:secret_key+ wins.
57
61
  # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`.
58
62
  # @option opts [Integer] :max_queue_size Maximum number of calls to remain queued. Defaults to 10_000.
59
63
  # @option opts [Integer] :batch_size Maximum number of events to send in one async batch.
@@ -88,9 +92,18 @@ module PostHog
88
92
  symbolize_keys!(opts)
89
93
 
90
94
  opts[:api_key] = normalize_string_option(opts[:api_key])
95
+ opts[:secret_key] = normalize_string_option(opts[:secret_key], blank_as_nil: true)
91
96
  opts[:personal_api_key] = normalize_string_option(opts[:personal_api_key], blank_as_nil: true)
92
97
  opts[:host] = normalize_host_option(opts[:host])
93
98
 
99
+ if opts[:secret_key].nil? && !opts[:personal_api_key].nil?
100
+ logger.warn(
101
+ 'The :personal_api_key option is deprecated; use :secret_key instead. It accepts either a ' \
102
+ 'Personal API Key (phx_...) or a Project Secret API Key (phs_...).'
103
+ )
104
+ end
105
+ secret_key = opts[:secret_key] || opts[:personal_api_key]
106
+
94
107
  @queue = Queue.new
95
108
  @queue_mutex = Mutex.new
96
109
  @api_key = opts[:api_key]
@@ -119,7 +132,8 @@ module PostHog
119
132
  end
120
133
  @worker_thread = nil
121
134
  @feature_flags_poller = nil
122
- @personal_api_key = opts[:personal_api_key]
135
+ @secret_key = secret_key
136
+ @personal_api_key = secret_key
123
137
 
124
138
  if @disabled && !opts[:silence_disabled_client_error]
125
139
  logger.error('api_key is missing or empty after trimming whitespace; check your project API key')
@@ -142,7 +156,7 @@ module PostHog
142
156
  @feature_flags_poller =
143
157
  FeatureFlagsPoller.new(
144
158
  opts[:feature_flags_polling_interval],
145
- opts[:personal_api_key],
159
+ secret_key,
146
160
  @api_key,
147
161
  opts[:host],
148
162
  opts[:feature_flag_request_timeout_seconds] || Defaults::FeatureFlags::FLAG_REQUEST_TIMEOUT_SECONDS,
@@ -761,9 +775,9 @@ module PostHog
761
775
  def reload_feature_flags
762
776
  return if @disabled
763
777
 
764
- unless @personal_api_key
778
+ unless @secret_key
765
779
  logger.error(
766
- 'You need to specify a personal_api_key to locally evaluate feature flags'
780
+ 'You need to specify a secret_key to locally evaluate feature flags'
767
781
  )
768
782
  return
769
783
  end
@@ -97,8 +97,10 @@ module PostHog
97
97
  def self.build_stacktrace(backtrace)
98
98
  return nil unless backtrace && !backtrace.empty?
99
99
 
100
+ root = project_root
101
+ roots = dependency_roots(root)
100
102
  frames = backtrace.first(50).map do |line|
101
- parse_backtrace_line(line)
103
+ parse_backtrace_line(line, project_root: root, dependency_roots: roots)
102
104
  end.compact.reverse
103
105
 
104
106
  {
@@ -108,8 +110,10 @@ module PostHog
108
110
  end
109
111
 
110
112
  # @param line [String]
113
+ # @param project_root [String, nil] Project root used to derive project-relative filenames.
114
+ # @param dependency_roots [Array<String>, nil] Cached gem and stdlib roots.
111
115
  # @return [Hash, nil]
112
- def self.parse_backtrace_line(line)
116
+ def self.parse_backtrace_line(line, project_root: self.project_root, dependency_roots: nil)
113
117
  match = line.match(RUBY_INPUT_FORMAT)
114
118
  return nil unless match
115
119
 
@@ -118,11 +122,11 @@ module PostHog
118
122
  method_name = match[5]
119
123
 
120
124
  frame = {
121
- 'filename' => File.basename(file),
125
+ 'filename' => frame_filename(file, project_root),
122
126
  'abs_path' => file,
123
127
  'lineno' => lineno,
124
128
  'function' => method_name,
125
- 'in_app' => !gem_path?(file),
129
+ 'in_app' => !gem_path?(file, dependency_roots || self.dependency_roots(project_root)),
126
130
  'platform' => 'ruby'
127
131
  }
128
132
 
@@ -131,13 +135,71 @@ module PostHog
131
135
  frame
132
136
  end
133
137
 
138
+ # Root directory the application runs from, used to derive stable
139
+ # project-relative filenames from per-host deploy paths
140
+ # (e.g. `/app/releases/20240101/...`).
141
+ #
142
+ # @return [String]
143
+ def self.project_root
144
+ if defined?(::Rails) && ::Rails.respond_to?(:root) && ::Rails.root
145
+ ::Rails.root.to_s
146
+ else
147
+ Dir.pwd
148
+ end
149
+ rescue StandardError
150
+ Dir.pwd
151
+ end
152
+
153
+ # Stable filename used for fingerprinting: the project-relative path when
154
+ # the file lives inside the project root, its basename otherwise. The raw
155
+ # absolute path is kept separately in `abs_path`.
156
+ #
157
+ # @param path [String]
158
+ # @param project_root [String, nil]
159
+ # @return [String]
160
+ def self.frame_filename(path, project_root)
161
+ if project_root && !project_root.empty? && path_within?(path, project_root)
162
+ relative = path[project_root.length..]
163
+ relative = relative[1..] while relative.start_with?(File::SEPARATOR)
164
+ return relative unless relative.empty?
165
+ end
166
+
167
+ File.basename(path)
168
+ end
169
+
170
+ # Whether the path belongs to an installed gem or the Ruby standard library,
171
+ # based on gem install locations rather than path substrings.
172
+ #
173
+ # @param path [String]
174
+ # @param dependency_roots [Array<String>]
175
+ # @return [Boolean]
176
+ def self.gem_path?(path, dependency_roots = self.dependency_roots)
177
+ dependency_roots.any? { |root| path_within?(path, root) }
178
+ end
179
+
180
+ # @param project_root [String]
181
+ # @return [Array<String>] Directories containing installed gems and the Ruby stdlib.
182
+ def self.dependency_roots(project_root = self.project_root)
183
+ roots = []
184
+ if defined?(Gem)
185
+ roots.concat(Gem.path) if Gem.respond_to?(:path)
186
+ roots << Gem.default_dir if Gem.respond_to?(:default_dir)
187
+ roots.concat(Gem.loaded_specs.each_value.map(&:full_gem_path)) if Gem.respond_to?(:loaded_specs)
188
+ end
189
+ roots << RbConfig::CONFIG['rubylibprefix'] if defined?(RbConfig)
190
+ # The project itself can be a loaded spec (e.g. a gem developed in place,
191
+ # or a Rails engine); its frames are still application code.
192
+ roots.compact.reject(&:empty?).uniq - [project_root]
193
+ rescue StandardError
194
+ []
195
+ end
196
+
134
197
  # @param path [String]
198
+ # @param root [String]
135
199
  # @return [Boolean]
136
- def self.gem_path?(path)
137
- path.include?('/gems/') ||
138
- path.include?('/ruby/') ||
139
- path.include?('/.rbenv/') ||
140
- path.include?('/.rvm/')
200
+ def self.path_within?(path, root)
201
+ root = root.chomp(File::SEPARATOR)
202
+ path == root || path.start_with?("#{root}#{File::SEPARATOR}")
141
203
  end
142
204
 
143
205
  # @param frame [Hash]
@@ -30,7 +30,8 @@ module PostHog
30
30
  include PostHog::Utils
31
31
 
32
32
  # @param polling_interval [Integer, nil] Seconds between local feature flag definition polls.
33
- # @param personal_api_key [String, nil] Personal API key used to fetch local evaluation definitions.
33
+ # @param secret_key [String, nil] Credential used to fetch local evaluation definitions. Accepts either a
34
+ # Personal API Key (`phx_...`) or a Project Secret API Key (`phs_...`).
34
35
  # @param project_api_key [String] Project API key.
35
36
  # @param host [String] PostHog API host URL.
36
37
  # @param feature_flag_request_timeout_seconds [Integer] Timeout for feature flag requests.
@@ -41,7 +42,7 @@ module PostHog
41
42
  # Set to 0 to disable retrying.
42
43
  def initialize(
43
44
  polling_interval,
44
- personal_api_key,
45
+ secret_key,
45
46
  project_api_key,
46
47
  host,
47
48
  feature_flag_request_timeout_seconds,
@@ -50,7 +51,7 @@ module PostHog
50
51
  feature_flag_request_max_retries: nil
51
52
  )
52
53
  @polling_interval = polling_interval || 30
53
- @personal_api_key = personal_api_key
54
+ @secret_key = secret_key
54
55
  @project_api_key = project_api_key
55
56
  @host = host
56
57
  @feature_flags = Concurrent::Array.new
@@ -74,9 +75,9 @@ module PostHog
74
75
  execution_interval: polling_interval
75
76
  ) { _load_feature_flags }
76
77
 
77
- # If no personal API key, disable local evaluation & thus polling for definitions
78
- if @personal_api_key.nil?
79
- logger.info 'No personal API key provided, disabling local evaluation'
78
+ # If no secret_key, disable local evaluation & thus polling for definitions
79
+ if @secret_key.nil?
80
+ logger.info 'No secret_key provided, disabling local evaluation'
80
81
  @loaded_flags_successfully_once.make_true
81
82
  else
82
83
  # load once before timer
@@ -1227,7 +1228,7 @@ module PostHog
1227
1228
  uri = URI("#{@host}/flags/definitions")
1228
1229
  uri.query = URI.encode_www_form([['token', @project_api_key], %w[send_cohorts true]])
1229
1230
  req = Net::HTTP::Get.new(uri)
1230
- req['Authorization'] = "Bearer #{@personal_api_key}"
1231
+ req['Authorization'] = "Bearer #{@secret_key}"
1231
1232
  req['If-None-Match'] = etag if etag
1232
1233
 
1233
1234
  _request(uri, req, nil, include_etag: true)
@@ -1253,7 +1254,7 @@ module PostHog
1253
1254
  uri.query = URI.encode_www_form([['token', @project_api_key]])
1254
1255
  req = Net::HTTP::Get.new(uri)
1255
1256
  req['Content-Type'] = 'application/json'
1256
- req['Authorization'] = "Bearer #{@personal_api_key}"
1257
+ req['Authorization'] = "Bearer #{@secret_key}"
1257
1258
 
1258
1259
  _request(uri, req, @feature_flag_request_timeout_seconds)
1259
1260
  end
@@ -52,7 +52,7 @@ module PostHog
52
52
  # cache = RedisFlagCache.new(redis, service_key: 'my-service')
53
53
  # client = PostHog::Client.new(
54
54
  # api_key: '<project_api_key>',
55
- # personal_api_key: '<personal_api_key>',
55
+ # secret_key: '<secret_key>',
56
56
  # flag_definition_cache_provider: cache
57
57
  # )
58
58
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
- VERSION = '3.16.0'
4
+ VERSION = '3.18.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.16.0
4
+ version: 3.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''