appsignal 2.8.0.alpha.1 → 2.8.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.
@@ -0,0 +1,91 @@
1
+ module Appsignal
2
+ class CLI
3
+ class Diagnose
4
+ class Paths
5
+ BYTES_TO_READ_FOR_FILES = 2 * 1024 * 1024 # 2 Mebibytes
6
+
7
+ def report
8
+ {}.tap do |hash|
9
+ paths.each do |filename, config|
10
+ hash[filename] = path_stat(config[:path])
11
+ end
12
+ end
13
+ end
14
+
15
+ def paths
16
+ @paths ||=
17
+ begin
18
+ config = Appsignal.config
19
+ log_file_path = config.log_file_path
20
+ install_log_path = File.join("ext", "install.log")
21
+ makefile_log_path = File.join("ext", "mkmf.log")
22
+ {
23
+ :package_install_path => {
24
+ :label => "AppSignal gem path",
25
+ :path => gem_path
26
+ },
27
+ :working_dir => {
28
+ :label => "Current working directory",
29
+ :path => Dir.pwd
30
+ },
31
+ :root_path => {
32
+ :label => "Root path",
33
+ :path => config.root_path
34
+ },
35
+ :log_dir_path => {
36
+ :label => "Log directory",
37
+ :path => log_file_path ? File.dirname(log_file_path) : ""
38
+ },
39
+ install_log_path => {
40
+ :label => "Extension install log",
41
+ :path => File.join(gem_path, install_log_path)
42
+ },
43
+ makefile_log_path => {
44
+ :label => "Makefile install log",
45
+ :path => File.join(gem_path, makefile_log_path)
46
+ },
47
+ "appsignal.log" => {
48
+ :label => "AppSignal log",
49
+ :path => log_file_path
50
+ }
51
+ }
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def path_stat(path)
58
+ {
59
+ :path => path,
60
+ :exists => File.exist?(path)
61
+ }.tap do |info|
62
+ next unless info[:exists]
63
+ stat = File.stat(path)
64
+ info[:type] = stat.directory? ? "directory" : "file"
65
+ info[:mode] = format("%o", stat.mode)
66
+ info[:writable] = stat.writable?
67
+ path_uid = stat.uid
68
+ path_gid = stat.gid
69
+ info[:ownership] = {
70
+ :uid => path_uid,
71
+ :user => Utils.username_for_uid(path_uid),
72
+ :gid => path_gid,
73
+ :group => Utils.group_for_gid(path_gid)
74
+ }
75
+ if info[:type] == "file"
76
+ info[:content] = Utils.read_file_content(
77
+ path,
78
+ BYTES_TO_READ_FOR_FILES
79
+ ).split("\n")
80
+ end
81
+ end
82
+ end
83
+
84
+ def gem_path
85
+ @gem_path ||= \
86
+ Bundler::CLI::Common.select_spec("appsignal").full_gem_path.strip
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,36 @@
1
+ module Appsignal
2
+ class CLI
3
+ class Diagnose
4
+ class Utils
5
+ def self.username_for_uid(uid)
6
+ passwd_struct = Etc.getpwuid(uid)
7
+ return unless passwd_struct
8
+ passwd_struct.name
9
+ end
10
+
11
+ def self.group_for_gid(gid)
12
+ passwd_struct = Etc.getgrgid(gid)
13
+ return unless passwd_struct
14
+ passwd_struct.name
15
+ end
16
+
17
+ def self.read_file_content(path, bytes_to_read)
18
+ file_size = File.size(path)
19
+ if bytes_to_read > file_size
20
+ # When the file is smaller than the bytes_to_read
21
+ # Read the whole file
22
+ offset = 0
23
+ length = file_size
24
+ else
25
+ # When the file is smaller than the bytes_to_read
26
+ # Read the last X bytes_to_read
27
+ length = bytes_to_read
28
+ offset = file_size - bytes_to_read
29
+ end
30
+
31
+ IO.binread(path, length, offset)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -73,10 +73,10 @@ module Appsignal
73
73
  end
74
74
 
75
75
  def install_for_rails(config)
76
- require File.expand_path(File.join(Dir.pwd, "config/application.rb"))
77
-
78
76
  puts "Installing for Ruby on Rails"
79
77
 
78
+ require File.expand_path(File.join(Dir.pwd, "config/application.rb"))
79
+
80
80
  config[:name] = Rails.application.class.parent_name
81
81
 
82
82
  name_overwritten = yes_or_no(" Your app's name is: '#{config[:name]}' \n Do you want to change how this is displayed in AppSignal? (y/n): ")
@@ -8,6 +8,8 @@ require "tmpdir"
8
8
 
9
9
  module Appsignal
10
10
  class Config
11
+ include Appsignal::Utils::DeprecationMessage
12
+
11
13
  DEFAULT_CONFIG = {
12
14
  :debug => false,
13
15
  :log => "file",
@@ -82,27 +84,69 @@ module Appsignal
82
84
  :ignore_exceptions => :ignore_errors
83
85
  }.freeze
84
86
 
85
- attr_reader :root_path, :env, :initial_config, :config_hash
87
+ # @attribute [r] system_config
88
+ # Config detected on the system level.
89
+ # Used in diagnose report.
90
+ # @api private
91
+ # @return [Hash]
92
+ # @!attribute [r] initial_config
93
+ # Config detected on the system level.
94
+ # Used in diagnose report.
95
+ # @api private
96
+ # @return [Hash]
97
+ # @!attribute [r] file_config
98
+ # Config loaded from `config/appsignal.yml` config file.
99
+ # Used in diagnose report.
100
+ # @api private
101
+ # @return [Hash]
102
+ # @!attribute [r] env_config
103
+ # Config loaded from the system environment.
104
+ # Used in diagnose report.
105
+ # @api private
106
+ # @return [Hash]
107
+ # @!attribute [r] config_hash
108
+ # Config used by the AppSignal gem.
109
+ # Combined Hash of the {system_config}, {initial_config}, {file_config},
110
+ # {env_config} attributes.
111
+ # @see #[]
112
+ # @see #[]=
113
+ # @api private
114
+ # @return [Hash]
115
+
116
+ attr_reader :root_path, :env, :config_hash, :system_config,
117
+ :initial_config, :file_config, :env_config
86
118
  attr_accessor :logger
87
119
 
88
120
  def initialize(root_path, env, initial_config = {}, logger = Appsignal.logger)
89
121
  @root_path = root_path
90
- @env = ENV.fetch("APPSIGNAL_APP_ENV".freeze, env.to_s)
91
- @initial_config = initial_config
92
122
  @logger = logger
93
123
  @valid = false
94
124
  @config_hash = Hash[DEFAULT_CONFIG]
125
+ env_loaded_from_initial = env.to_s
126
+ @env =
127
+ if ENV.key?("APPSIGNAL_APP_ENV".freeze)
128
+ env_loaded_from_env = ENV["APPSIGNAL_APP_ENV".freeze]
129
+ else
130
+ env_loaded_from_initial
131
+ end
95
132
 
96
133
  # Set config based on the system
97
- detect_from_system
134
+ @system_config = detect_from_system
135
+ merge(system_config)
98
136
  # Initial config
99
- merge(@config_hash, initial_config)
137
+ @initial_config = initial_config
138
+ merge(initial_config)
100
139
  # Load the config file if it exists
101
- load_from_disk
140
+ @file_config = load_from_disk || {}
141
+ merge(file_config)
102
142
  # Load config from environment variables
103
- load_from_environment
143
+ @env_config = load_from_environment
144
+ merge(env_config)
104
145
  # Validate that we have a correct config
105
146
  validate
147
+ # Track origin of env
148
+ @initial_config[:env] = env_loaded_from_initial if env_loaded_from_initial
149
+ @env_config[:env] = env_loaded_from_env if env_loaded_from_env
106
150
  end
107
151
 
108
152
  # @api private
@@ -166,7 +210,6 @@ module Appsignal
166
210
  ENV["_APPSIGNAL_IGNORE_ACTIONS"] = config_hash[:ignore_actions].join(",")
167
211
  ENV["_APPSIGNAL_IGNORE_ERRORS"] = config_hash[:ignore_errors].join(",")
168
212
  ENV["_APPSIGNAL_IGNORE_NAMESPACES"] = config_hash[:ignore_namespaces].join(",")
169
- ENV["_APPSIGNAL_SEND_PARAMS"] = config_hash[:send_params].to_s
170
213
  ENV["_APPSIGNAL_RUNNING_IN_CONTAINER"] = config_hash[:running_in_container].to_s
171
214
  ENV["_APPSIGNAL_WORKING_DIR_PATH"] = config_hash[:working_dir_path] if config_hash[:working_dir_path]
172
215
  ENV["_APPSIGNAL_WORKING_DIRECTORY_PATH"] = config_hash[:working_directory_path] if config_hash[:working_directory_path]
@@ -208,10 +251,12 @@ module Appsignal
208
251
  end
209
252
 
210
253
  def detect_from_system
211
- config_hash[:log] = "stdout" if Appsignal::System.heroku?
254
+ {}.tap do |hash|
255
+ hash[:log] = "stdout" if Appsignal::System.heroku?
212
256
 
213
- # Make active by default if APPSIGNAL_PUSH_API_KEY is present
214
- config_hash[:active] = true if ENV["APPSIGNAL_PUSH_API_KEY"]
257
+ # Make active by default if APPSIGNAL_PUSH_API_KEY is present
258
+ hash[:active] = true if ENV["APPSIGNAL_PUSH_API_KEY"]
259
+ end
215
260
  end
216
261
 
217
262
  def load_from_disk
@@ -225,11 +270,10 @@ module Appsignal
225
270
  hash[key.to_sym] = value # convert keys to symbols
226
271
  end
227
272
 
228
- config_for_this_env = maintain_backwards_compatibility(config_for_this_env)
229
-
230
- merge(@config_hash, config_for_this_env)
273
+ maintain_backwards_compatibility(config_for_this_env)
231
274
  else
232
275
  @logger.error "Not loading from config file: config for '#{env}' not found"
276
+ nil
233
277
  end
234
278
  end
235
279
 
@@ -242,17 +286,21 @@ module Appsignal
242
286
  DEPRECATED_CONFIG_KEY_MAPPING.each do |old_key, new_key|
243
287
  old_config_value = config.delete(old_key)
244
288
  next unless old_config_value
245
- logger.warn "Old configuration key found. Please update the "\
246
- "'#{old_key}' to '#{new_key}'."
289
+ deprecation_message \
290
+ "Old configuration key found. Please update the "\
291
+ "'#{old_key}' to '#{new_key}'.",
292
+ logger
247
293
 
248
294
  next if config[new_key] # Skip if new key is already in use
249
295
  config[new_key] = old_config_value
250
296
  end
251
297
 
252
298
  if config.include?(:working_dir_path)
253
- logger.warn "'working_dir_path' is deprecated, please use " \
254
- "'working_directory_path' instead and specify the " \
255
- "full path to the working directory"
299
+ deprecation_message \
300
+ "'working_dir_path' is deprecated, please use " \
301
+ "'working_directory_path' instead and specify the " \
302
+ "full path to the working directory",
303
+ logger
256
304
  end
257
305
  end
258
306
  end
@@ -292,15 +340,15 @@ module Appsignal
292
340
  config[ENV_TO_KEY_MAPPING[var]] = env_var.split(",")
293
341
  end
294
342
 
295
- merge(@config_hash, config)
343
+ config
296
344
  end
297
345
 
298
- def merge(original_config, new_config)
346
+ def merge(new_config)
299
347
  new_config.each do |key, value|
300
- unless original_config[key].nil?
348
+ unless config_hash[key].nil?
301
349
  @logger.debug("Config key '#{key}' is being overwritten")
302
350
  end
303
- original_config[key] = value
351
+ config_hash[key] = value
304
352
  end
305
353
  end
306
354
  end
@@ -14,6 +14,8 @@ module Appsignal
14
14
  # @api private
15
15
  class EventFormatter
16
16
  class << self
17
+ include Appsignal::Utils::DeprecationMessage
18
+
17
19
  def formatters
18
20
  @@formatters ||= {}
19
21
  end
@@ -33,7 +35,7 @@ module Appsignal
33
35
  end
34
36
 
35
37
  if registered?(name, formatter)
36
- Appsignal.logger.warn(
38
+ logger.warn(
37
39
  "Formatter for '#{name}' already registered, not registering "\
38
40
  "'#{formatter.name}'"
39
41
  )
@@ -82,19 +84,23 @@ module Appsignal
82
84
  rescue => ex
83
85
  formatter_classes.delete(name)
84
86
  formatters.delete(name)
85
- Appsignal.logger.warn("'#{ex.message}' when initializing #{name} event formatter")
87
+ logger.warn("'#{ex.message}' when initializing #{name} event formatter")
86
88
  end
87
89
 
88
90
  def register_deprecated_formatter(name)
89
- Appsignal.logger.warn(
91
+ deprecation_message \
90
92
  "Formatter for '#{name}' is using a deprecated registration " \
91
93
  "method. This event formatter will not be loaded. " \
92
94
  "Please update the formatter according to the documentation at: " \
93
- "https://docs.appsignal.com/ruby/instrumentation/event-formatters.html"
94
- )
95
+ "https://docs.appsignal.com/ruby/instrumentation/event-formatters.html",
96
+ logger
95
97
 
96
98
  deprecated_formatter_classes[name] = self
97
99
  end
100
+
101
+ def logger
102
+ Appsignal.logger
103
+ end
98
104
  end
99
105
 
100
106
  # @api public
@@ -47,4 +47,15 @@ module Appsignal
47
47
  self.class.lock
48
48
  end
49
49
  end
50
+
51
+ # {Appsignal::NilGarbageCollectionProfiler} is a dummy profiler
52
+ # that always returns 0 as the total time.
53
+ # Used when we don't want any profile information
54
+ #
55
+ # @api private
56
+ class NilGarbageCollectionProfiler
57
+ def total_time
58
+ 0
59
+ end
60
+ end
50
61
  end
@@ -7,7 +7,9 @@ module Appsignal
7
7
  #
8
8
  # @api private
9
9
  module System
10
+ LINUX_TARGET = "linux".freeze
10
11
  MUSL_TARGET = "linux-musl".freeze
12
+ FREEBSD_TARGET = "freebsd".freeze
11
13
  GEM_EXT_PATH = File.expand_path("../../../ext", __FILE__).freeze
12
14
 
13
15
  def self.heroku?
@@ -45,12 +47,12 @@ module Appsignal
45
47
  host_os = RbConfig::CONFIG["host_os"].downcase
46
48
  local_os =
47
49
  case host_os
48
- when /linux/
49
- "linux"
50
+ when /#{LINUX_TARGET}/
51
+ LINUX_TARGET
50
52
  when /darwin/
51
53
  "darwin"
52
- when /freebsd/
53
- "freebsd"
54
+ when /#{FREEBSD_TARGET}/
55
+ FREEBSD_TARGET
54
56
  else
55
57
  host_os
56
58
  end
@@ -53,7 +53,8 @@ module Appsignal
53
53
  end
54
54
 
55
55
  def garbage_collection_profiler
56
- @garbage_collection_profiler ||= Appsignal::GarbageCollectionProfiler.new
56
+ @garbage_collection_profiler ||=
57
+ Appsignal.config[:enable_gc_instrumentation] ? Appsignal::GarbageCollectionProfiler.new : NilGarbageCollectionProfiler.new
57
58
  end
58
59
  end
59
60
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "appsignal/utils/deprecation_message"
3
4
  require "appsignal/utils/data"
4
5
  require "appsignal/utils/hash_sanitizer"
5
6
  require "appsignal/utils/json"
@@ -0,0 +1,10 @@
1
+ module Appsignal
2
+ module Utils
3
+ module DeprecationMessage
4
+ def deprecation_message(message, logger)
5
+ $stdout.puts "appsignal WARNING: #{message}"
6
+ logger.warn message
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.8.0.alpha.1".freeze
4
+ VERSION = "2.8.0".freeze
5
5
  end
@@ -0,0 +1,37 @@
1
+ require "appsignal/cli/diagnose/utils"
2
+
3
+ describe Appsignal::CLI::Diagnose::Utils do
4
+ describe ".read_file_content" do
5
+ let(:path) { File.join(spec_system_tmp_dir, "test_file.txt") }
6
+ let(:bytes_to_read) { 100 }
7
+ subject { described_class.read_file_content(path, bytes_to_read) }
8
+ before do
9
+ File.open path, "w+" do |f|
10
+ f.write file_contents
11
+ end
12
+ end
13
+
14
+ context "when file is bigger than read size" do
15
+ let(:file_contents) do
16
+ "".tap do |s|
17
+ 100.times do |i|
18
+ s << "line #{i}\n"
19
+ end
20
+ end
21
+ end
22
+
23
+ it "returns the last X bytes" do
24
+ is_expected
25
+ .to eq(file_contents[(file_contents.length - bytes_to_read)..file_contents.length])
26
+ end
27
+ end
28
+
29
+ context "when file is smaller than read size" do
30
+ let(:file_contents) { "line 1\n" }
31
+
32
+ it "returns the whole file content" do
33
+ is_expected.to eq(file_contents)
34
+ end
35
+ end
36
+ end
37
+ end