atatus 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +57 -0
- data/LICENSE +65 -0
- data/LICENSE-THIRD-PARTY +205 -0
- data/README.md +13 -0
- data/Rakefile +19 -0
- data/atatus.gemspec +36 -0
- data/atatus.yml +2 -0
- data/bench/.gitignore +2 -0
- data/bench/app.rb +53 -0
- data/bench/benchmark.rb +36 -0
- data/bench/report.rb +55 -0
- data/bench/rubyprof.rb +39 -0
- data/bench/stackprof.rb +23 -0
- data/bin/build_docs +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/lib/atatus.rb +325 -0
- data/lib/atatus/agent.rb +260 -0
- data/lib/atatus/central_config.rb +141 -0
- data/lib/atatus/central_config/cache_control.rb +34 -0
- data/lib/atatus/collector/base.rb +329 -0
- data/lib/atatus/collector/builder.rb +317 -0
- data/lib/atatus/collector/transport.rb +72 -0
- data/lib/atatus/config.rb +248 -0
- data/lib/atatus/config/bytes.rb +25 -0
- data/lib/atatus/config/duration.rb +23 -0
- data/lib/atatus/config/options.rb +134 -0
- data/lib/atatus/config/regexp_list.rb +13 -0
- data/lib/atatus/context.rb +33 -0
- data/lib/atatus/context/request.rb +11 -0
- data/lib/atatus/context/request/socket.rb +19 -0
- data/lib/atatus/context/request/url.rb +42 -0
- data/lib/atatus/context/response.rb +22 -0
- data/lib/atatus/context/user.rb +42 -0
- data/lib/atatus/context_builder.rb +97 -0
- data/lib/atatus/deprecations.rb +22 -0
- data/lib/atatus/error.rb +22 -0
- data/lib/atatus/error/exception.rb +46 -0
- data/lib/atatus/error/log.rb +24 -0
- data/lib/atatus/error_builder.rb +76 -0
- data/lib/atatus/instrumenter.rb +224 -0
- data/lib/atatus/internal_error.rb +6 -0
- data/lib/atatus/logging.rb +55 -0
- data/lib/atatus/metadata.rb +19 -0
- data/lib/atatus/metadata/process_info.rb +18 -0
- data/lib/atatus/metadata/service_info.rb +61 -0
- data/lib/atatus/metadata/system_info.rb +35 -0
- data/lib/atatus/metadata/system_info/container_info.rb +121 -0
- data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
- data/lib/atatus/metadata/system_info/os_info.rb +31 -0
- data/lib/atatus/metrics.rb +98 -0
- data/lib/atatus/metrics/cpu_mem.rb +240 -0
- data/lib/atatus/metrics/vm.rb +60 -0
- data/lib/atatus/metricset.rb +19 -0
- data/lib/atatus/middleware.rb +76 -0
- data/lib/atatus/naively_hashable.rb +21 -0
- data/lib/atatus/normalizers.rb +68 -0
- data/lib/atatus/normalizers/action_controller.rb +27 -0
- data/lib/atatus/normalizers/action_mailer.rb +26 -0
- data/lib/atatus/normalizers/action_view.rb +77 -0
- data/lib/atatus/normalizers/active_record.rb +45 -0
- data/lib/atatus/opentracing.rb +346 -0
- data/lib/atatus/rails.rb +61 -0
- data/lib/atatus/railtie.rb +30 -0
- data/lib/atatus/span.rb +125 -0
- data/lib/atatus/span/context.rb +40 -0
- data/lib/atatus/span_helpers.rb +44 -0
- data/lib/atatus/spies.rb +86 -0
- data/lib/atatus/spies/action_dispatch.rb +28 -0
- data/lib/atatus/spies/delayed_job.rb +68 -0
- data/lib/atatus/spies/elasticsearch.rb +36 -0
- data/lib/atatus/spies/faraday.rb +70 -0
- data/lib/atatus/spies/http.rb +44 -0
- data/lib/atatus/spies/json.rb +22 -0
- data/lib/atatus/spies/mongo.rb +87 -0
- data/lib/atatus/spies/net_http.rb +70 -0
- data/lib/atatus/spies/rake.rb +45 -0
- data/lib/atatus/spies/redis.rb +27 -0
- data/lib/atatus/spies/sequel.rb +47 -0
- data/lib/atatus/spies/sidekiq.rb +89 -0
- data/lib/atatus/spies/sinatra.rb +41 -0
- data/lib/atatus/spies/tilt.rb +27 -0
- data/lib/atatus/sql_summarizer.rb +35 -0
- data/lib/atatus/stacktrace.rb +16 -0
- data/lib/atatus/stacktrace/frame.rb +52 -0
- data/lib/atatus/stacktrace_builder.rb +104 -0
- data/lib/atatus/subscriber.rb +77 -0
- data/lib/atatus/trace_context.rb +85 -0
- data/lib/atatus/transaction.rb +100 -0
- data/lib/atatus/transport/base.rb +174 -0
- data/lib/atatus/transport/connection.rb +156 -0
- data/lib/atatus/transport/connection/http.rb +116 -0
- data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
- data/lib/atatus/transport/filters.rb +43 -0
- data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
- data/lib/atatus/transport/serializers.rb +93 -0
- data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
- data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
- data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
- data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
- data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
- data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
- data/lib/atatus/transport/worker.rb +73 -0
- data/lib/atatus/util.rb +42 -0
- data/lib/atatus/util/inflector.rb +93 -0
- data/lib/atatus/util/lru_cache.rb +48 -0
- data/lib/atatus/util/prefixed_logger.rb +18 -0
- data/lib/atatus/util/throttle.rb +35 -0
- data/lib/atatus/version.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +190 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class ErrorSerializer < Serializer
|
8
|
+
def context_serializer
|
9
|
+
@context_serializer ||= ContextSerializer.new(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
13
|
+
def build(error)
|
14
|
+
base = {
|
15
|
+
id: error.id,
|
16
|
+
transaction_id: error.transaction_id,
|
17
|
+
transaction: build_transaction(error.transaction),
|
18
|
+
trace_id: error.trace_id,
|
19
|
+
parent_id: error.parent_id,
|
20
|
+
|
21
|
+
culprit: keyword_field(error.culprit),
|
22
|
+
timestamp: error.timestamp
|
23
|
+
}
|
24
|
+
|
25
|
+
if (context = context_serializer.build(error.context))
|
26
|
+
base[:context] = context
|
27
|
+
end
|
28
|
+
|
29
|
+
if (exception = error.exception)
|
30
|
+
base[:exception] = build_exception exception
|
31
|
+
end
|
32
|
+
|
33
|
+
if (log = error.log)
|
34
|
+
base[:log] = build_log log
|
35
|
+
end
|
36
|
+
|
37
|
+
{ error: base }
|
38
|
+
end
|
39
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_exception(exception)
|
44
|
+
{
|
45
|
+
message: exception.message,
|
46
|
+
type: keyword_field(exception.type),
|
47
|
+
module: keyword_field(exception.module),
|
48
|
+
code: keyword_field(exception.code),
|
49
|
+
attributes: exception.attributes,
|
50
|
+
stacktrace: exception.stacktrace.to_a,
|
51
|
+
handled: exception.handled,
|
52
|
+
cause: exception.cause && [build_exception(exception.cause)]
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_log(log)
|
57
|
+
{
|
58
|
+
message: log.message,
|
59
|
+
level: keyword_field(log.level),
|
60
|
+
logger_name: keyword_field(log.logger_name),
|
61
|
+
param_message: keyword_field(log.param_message),
|
62
|
+
stacktrace: log.stacktrace.to_a
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_transaction(transaction)
|
67
|
+
return unless transaction
|
68
|
+
|
69
|
+
{
|
70
|
+
sampled: transaction[:sampled],
|
71
|
+
type: keyword_field(transaction[:type])
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class MetadataSerializer < Serializer
|
8
|
+
def build(metadata)
|
9
|
+
{
|
10
|
+
metadata: {
|
11
|
+
service: build_service(metadata.service),
|
12
|
+
process: build_process(metadata.process),
|
13
|
+
system: build_system(metadata.system),
|
14
|
+
labels: build_labels(metadata.labels)
|
15
|
+
}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
22
|
+
def build_service(service)
|
23
|
+
{
|
24
|
+
name: keyword_field(service.name),
|
25
|
+
environment: keyword_field(service.environment),
|
26
|
+
version: keyword_field(service.version),
|
27
|
+
agent: {
|
28
|
+
name: keyword_field(service.agent.name),
|
29
|
+
version: keyword_field(service.agent.version)
|
30
|
+
},
|
31
|
+
framework: {
|
32
|
+
name: keyword_field(service.framework.name),
|
33
|
+
version: keyword_field(service.framework.version)
|
34
|
+
},
|
35
|
+
language: {
|
36
|
+
name: keyword_field(service.language.name),
|
37
|
+
version: keyword_field(service.language.version)
|
38
|
+
},
|
39
|
+
runtime: {
|
40
|
+
name: keyword_field(service.runtime.name),
|
41
|
+
version: keyword_field(service.runtime.version)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
46
|
+
|
47
|
+
def build_process(process)
|
48
|
+
{
|
49
|
+
pid: process.pid,
|
50
|
+
title: keyword_field(process.title),
|
51
|
+
argv: process.argv
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_system(system)
|
56
|
+
{
|
57
|
+
hostname: keyword_field(system.hostname),
|
58
|
+
architecture: keyword_field(system.architecture),
|
59
|
+
platform: keyword_field(system.platform),
|
60
|
+
kubernetes: keyword_object(system.kubernetes)
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_labels(labels)
|
65
|
+
keyword_object(labels)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class MetricsetSerializer < Serializer
|
8
|
+
def build(metricset)
|
9
|
+
{
|
10
|
+
metricset: {
|
11
|
+
timestamp: metricset.timestamp.to_i,
|
12
|
+
tags: keyword_object(metricset.labels),
|
13
|
+
samples: build_samples(metricset.samples)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build_samples(samples)
|
21
|
+
samples.each_with_object({}) do |(key, value), hsh|
|
22
|
+
hsh[key] = { value: value }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class SpanSerializer < Serializer
|
8
|
+
def initialize(config)
|
9
|
+
super
|
10
|
+
|
11
|
+
@context_serializer = ContextSerializer.new(config)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :context_serializer
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
def build(span)
|
18
|
+
{
|
19
|
+
span: {
|
20
|
+
id: span.id,
|
21
|
+
transaction_id: span.transaction_id,
|
22
|
+
parent_id: span.parent_id,
|
23
|
+
name: keyword_field(span.name),
|
24
|
+
type: join_type(span),
|
25
|
+
duration: ms(span.duration),
|
26
|
+
context: context_serializer.build(span.context),
|
27
|
+
stacktrace: span.stacktrace.to_a,
|
28
|
+
timestamp: span.timestamp,
|
29
|
+
trace_id: span.trace_id
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
# rubocop:enable Metrics/MethodLength
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
class ContextSerializer < Serializer
|
37
|
+
def build(context)
|
38
|
+
return unless context
|
39
|
+
|
40
|
+
{ sync: context.sync }.tap do |base|
|
41
|
+
base[:db] = build_db(context.db) if context.db
|
42
|
+
base[:http] = build_http(context.http) if context.http
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def build_db(db)
|
49
|
+
return unless db
|
50
|
+
|
51
|
+
{
|
52
|
+
instance: db.instance,
|
53
|
+
statement: Util.truncate(db.statement, max_length: 10_000),
|
54
|
+
type: db.type,
|
55
|
+
user: db.user
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_http(http)
|
60
|
+
return unless http
|
61
|
+
|
62
|
+
{
|
63
|
+
url: http.url,
|
64
|
+
status_code: http.status_code.to_i,
|
65
|
+
method: keyword_field(http.method)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def join_type(span)
|
73
|
+
combined = [span.type, span.subtype, span.action]
|
74
|
+
combined.compact!
|
75
|
+
combined.join '.'
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
module Serializers
|
6
|
+
# @api private
|
7
|
+
class TransactionSerializer < Serializer
|
8
|
+
def context_serializer
|
9
|
+
@context_serializer ||= ContextSerializer.new(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
13
|
+
def build(transaction)
|
14
|
+
{
|
15
|
+
transaction: {
|
16
|
+
id: transaction.id,
|
17
|
+
trace_id: transaction.trace_id,
|
18
|
+
parent_id: transaction.parent_id,
|
19
|
+
name: keyword_field(transaction.name),
|
20
|
+
type: keyword_field(transaction.type),
|
21
|
+
result: keyword_field(transaction.result.to_s),
|
22
|
+
duration: ms(transaction.duration),
|
23
|
+
timestamp: transaction.timestamp,
|
24
|
+
sampled: transaction.sampled?,
|
25
|
+
context: context_serializer.build(transaction.context),
|
26
|
+
span_count: {
|
27
|
+
started: transaction.started_spans,
|
28
|
+
dropped: transaction.dropped_spans
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
module Transport
|
5
|
+
# @api private
|
6
|
+
class Worker
|
7
|
+
include Logging
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
class StopMessage; end
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
class FlushMessage; end
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
config,
|
17
|
+
queue,
|
18
|
+
serializers:,
|
19
|
+
filters:,
|
20
|
+
conn_adapter: Connection
|
21
|
+
)
|
22
|
+
@config = config
|
23
|
+
@queue = queue
|
24
|
+
|
25
|
+
@serializers = serializers
|
26
|
+
@filters = filters
|
27
|
+
|
28
|
+
metadata = serializers.serialize(Metadata.new(config))
|
29
|
+
@connection = conn_adapter.new(config, metadata)
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :queue, :filters, :name, :connection, :serializers
|
33
|
+
|
34
|
+
# rubocop:disable Metrics/MethodLength
|
35
|
+
def work_forever
|
36
|
+
while (msg = queue.pop)
|
37
|
+
case msg
|
38
|
+
when StopMessage
|
39
|
+
debug 'Stopping worker -- %s', self
|
40
|
+
connection.flush(:halt)
|
41
|
+
break
|
42
|
+
else
|
43
|
+
process msg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue Exception => e
|
47
|
+
warn 'Worker died with exception: %s', e.inspect
|
48
|
+
debug e.backtrace.join("\n")
|
49
|
+
end
|
50
|
+
# rubocop:enable Metrics/MethodLength
|
51
|
+
|
52
|
+
def process(resource)
|
53
|
+
return unless (json = serialize_and_filter(resource))
|
54
|
+
connection.write(json)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def serialize_and_filter(resource)
|
60
|
+
serialized = serializers.serialize(resource)
|
61
|
+
|
62
|
+
# if a filter returns nil, it means skip the event
|
63
|
+
return nil if @filters.apply!(serialized) == Filters::SKIP
|
64
|
+
|
65
|
+
JSON.fast_generate(serialized)
|
66
|
+
rescue Exception
|
67
|
+
error format('Failed converting event to JSON: %s', resource.inspect)
|
68
|
+
error serialized.inspect
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/atatus/util.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
# @api private
|
5
|
+
module Util
|
6
|
+
|
7
|
+
def self.ms(micros)
|
8
|
+
micros.to_f / 1_000
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.micros(target = Time.now)
|
12
|
+
utc = target.utc
|
13
|
+
utc.to_i * 1_000_000 + utc.usec
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.monotonic_micros
|
17
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :microsecond)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.git_sha
|
21
|
+
sha = `git rev-parse --verify HEAD 2>&1`.chomp
|
22
|
+
$? && $?.success? ? sha : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.hex_to_bits(str)
|
26
|
+
str.hex.to_s(2).rjust(str.size * 4, '0')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.reverse_merge!(first, *others)
|
30
|
+
others.reduce(first) do |curr, other|
|
31
|
+
curr.merge!(other) { |_, _, new| new }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.truncate(value, max_length: 1024)
|
36
|
+
return unless value
|
37
|
+
return value if value.length <= max_length
|
38
|
+
|
39
|
+
value[0...(max_length - 1)] + '…'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Atatus
|
4
|
+
# rubocop:disable all
|
5
|
+
module Util
|
6
|
+
# From https://github.com/rails/rails/blob/v5.2.0/activesupport/lib/active_support/inflector/methods.rb#L254-L332
|
7
|
+
module Inflector
|
8
|
+
extend self
|
9
|
+
|
10
|
+
#
|
11
|
+
# Tries to find a constant with the name specified in the argument string.
|
12
|
+
#
|
13
|
+
# constantize('Module') # => Module
|
14
|
+
# constantize('Foo::Bar') # => Foo::Bar
|
15
|
+
#
|
16
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
17
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
18
|
+
# account:
|
19
|
+
#
|
20
|
+
# C = 'outside'
|
21
|
+
# module M
|
22
|
+
# C = 'inside'
|
23
|
+
# C # => 'inside'
|
24
|
+
# constantize('C') # => 'outside', same as ::C
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# NameError is raised when the name is not in CamelCase or the constant is
|
28
|
+
# unknown.
|
29
|
+
def constantize(camel_cased_word)
|
30
|
+
names = camel_cased_word.split("::".freeze)
|
31
|
+
|
32
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
33
|
+
Object.const_get(camel_cased_word) if names.empty?
|
34
|
+
|
35
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
36
|
+
names.shift if names.size > 1 && names.first.empty?
|
37
|
+
|
38
|
+
names.inject(Object) do |constant, name|
|
39
|
+
if constant == Object
|
40
|
+
constant.const_get(name)
|
41
|
+
else
|
42
|
+
candidate = constant.const_get(name)
|
43
|
+
next candidate if constant.const_defined?(name, false)
|
44
|
+
next candidate unless Object.const_defined?(name)
|
45
|
+
|
46
|
+
# Go down the ancestors to check if it is owned directly. The check
|
47
|
+
# stops when we reach Object or the end of ancestors tree.
|
48
|
+
constant = constant.ancestors.inject(constant) do |const, ancestor|
|
49
|
+
break const if ancestor == Object
|
50
|
+
break ancestor if ancestor.const_defined?(name, false)
|
51
|
+
const
|
52
|
+
end
|
53
|
+
|
54
|
+
# owner is in Object, so raise
|
55
|
+
constant.const_get(name, false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Tries to find a constant with the name specified in the argument string.
|
61
|
+
#
|
62
|
+
# safe_constantize('Module') # => Module
|
63
|
+
# safe_constantize('Foo::Bar') # => Foo::Bar
|
64
|
+
#
|
65
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
66
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
67
|
+
# account:
|
68
|
+
#
|
69
|
+
# C = 'outside'
|
70
|
+
# module M
|
71
|
+
# C = 'inside'
|
72
|
+
# C # => 'inside'
|
73
|
+
# safe_constantize('C') # => 'outside', same as ::C
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# +nil+ is returned when the name is not in CamelCase or the constant (or
|
77
|
+
# part of it) is unknown.
|
78
|
+
#
|
79
|
+
# safe_constantize('blargle') # => nil
|
80
|
+
# safe_constantize('UnknownModule') # => nil
|
81
|
+
# safe_constantize('UnknownModule::Foo::Bar') # => nil
|
82
|
+
def safe_constantize(camel_cased_word)
|
83
|
+
constantize(camel_cased_word)
|
84
|
+
rescue NameError => e
|
85
|
+
raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
|
86
|
+
e.name.to_s == camel_cased_word.to_s)
|
87
|
+
rescue ArgumentError => e
|
88
|
+
raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match(e.message)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
# rubocop:enable all
|
93
|
+
end
|