scout_apm_logging 1.2.0 → 2.0.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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/.rubocop.yml +1 -2
- data/CHANGELOG.md +5 -11
- data/README.md +7 -2
- data/lib/scout_apm/logging/config.rb +16 -27
- data/lib/scout_apm/logging/context.rb +0 -8
- data/lib/scout_apm/logging/loggers/capture.rb +1 -1
- data/lib/scout_apm/logging/loggers/formatter.rb +17 -12
- data/lib/scout_apm/logging/loggers/logger.rb +1 -127
- data/lib/scout_apm/logging/loggers/opentelemetry/log_record_patch.rb +38 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +8 -31
- data/lib/scout_apm/logging/version.rb +1 -1
- data/lib/scout_apm_logging.rb +3 -1
- data/scout_apm_logging.gemspec +4 -5
- data/spec/integration/rails/lifecycle_spec.rb +6 -50
- data/spec/rails/app.rb +2 -37
- metadata +36 -47
- data/lib/scout_apm/logging/loggers/opentelemetry/LICENSE +0 -201
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/log_record.rb +0 -18
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger.rb +0 -64
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger_provider.rb +0 -31
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/severity_number.rb +0 -43
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/version.rb +0 -18
- data/lib/scout_apm/logging/loggers/opentelemetry/api/logs.rb +0 -28
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +0 -398
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/version.rb +0 -20
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb +0 -43
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/common/v1/common_pb.rb +0 -58
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/logs/v1/logs_pb.rb +0 -91
- data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/resource/v1/resource_pb.rb +0 -33
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +0 -223
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb +0 -64
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export.rb +0 -34
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +0 -170
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +0 -31
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb +0 -49
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +0 -52
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +0 -98
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +0 -170
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +0 -20
- data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +0 -29
- data/spec/unit/loggers/logger_spec.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c7064d80f9892529f81a77b0e46f9e517e8ea96f8e9e53880ee2eefd6c5abbf2
|
4
|
+
data.tar.gz: 3a3ec083c05435e99d23b719bbc31f38d7ce4a07ddcf25ef47fed544147ea21c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7ea291371355544a94b69d3ab7ad75ee7407c84d450bb3e9a44ce6aabfa6bb2c27ce5813276111fb6584268eaba59875d346bd6b36b83c4db0d153478f7ba9f
|
7
|
+
data.tar.gz: ff9ee96494248654b146c2cc9d45cc7db960196781661cc94e3f79ebbabde83930b4c264e67c0270cd24e1510027242b62e307cda061b26e1b2789d1af5f7941
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,8 @@
|
|
1
|
-
##
|
2
|
-
*
|
3
|
-
|
4
|
-
*
|
5
|
-
*
|
6
|
-
* See [#98](https://github.com/scoutapp/scout_apm_ruby_logging/pull/98) for benchmarks.
|
7
|
-
* Add ability to disable warn message on method_missing.
|
8
|
-
* `logs_method_missing_warning: false`
|
9
|
-
* Add ability to log stack trace on method_missing.
|
10
|
-
* `logs_method_missing_call_stack: true`
|
11
|
-
* Add tests for ActionCable.
|
1
|
+
## 2.0.0
|
2
|
+
* Remove vendored opentelmetry SDK.
|
3
|
+
* Add support for Ruby 3.4.
|
4
|
+
* Breaking: Drop support for Ruby 2.6, 2.7, 3.0.
|
5
|
+
* Note: The 1.x release is still maintained.
|
12
6
|
|
13
7
|
## 1.1.0
|
14
8
|
* Bump vendored SDK version to 0.2.0.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[](https://github.com/scoutapp/scout_apm_ruby_logging/actions)
|
4
4
|
|
5
|
-
A Ruby gem for detailed, easy to navigate, managed log monitoring.
|
5
|
+
A Ruby gem for detailed, easy to navigate, managed log monitoring. Supports Ruby 2.6+.
|
6
6
|
|
7
7
|
Sign up for an account at https://www.scoutapm.com to start monitoring your logs and application performance in minutes.
|
8
8
|
|
@@ -32,6 +32,11 @@ Update your [RAILS_ROOT/config/scout_apm.yml](https://scoutapm.com/apps/new_ruby
|
|
32
32
|
|
33
33
|
Deploy :rocket:
|
34
34
|
|
35
|
+
## Versions
|
36
|
+
We currently maintain two versions: 2.x (Ruby 3.1+) and 1.x (2.6-3.3). These versions/branches have feature parity and a similar release cadence.
|
37
|
+
|
38
|
+
The `main` branch contains the 2.x version, while the `1.x` branch contains the 1.x version.
|
39
|
+
|
35
40
|
## Testing
|
36
41
|
To run the entire test suite:
|
37
42
|
```ruby
|
@@ -45,7 +50,7 @@ bundle exec rake test file=/path/to/spec/_spec.rb
|
|
45
50
|
|
46
51
|
To run test(s) against a specific Ruby version:
|
47
52
|
```ruby
|
48
|
-
DOCKER_RUBY_VERSION=3.
|
53
|
+
DOCKER_RUBY_VERSION=3.4 bundle exec rake test
|
49
54
|
```
|
50
55
|
|
51
56
|
## Local
|
@@ -19,12 +19,19 @@
|
|
19
19
|
# logs_config - a hash of configuration options for merging into the collector's config
|
20
20
|
# logs_reporting_endpoint - the endpoint to send logs to
|
21
21
|
# logs_proxy_log_dir - the directory to store logs in for monitoring
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
22
|
+
# manager_lock_file - the location for obtaining an exclusive lock for running monitor manager
|
23
|
+
# monitor_pid_file - the location of the pid file for the monitor
|
24
|
+
# monitor_state_file - the location of the state file for the monitor
|
25
|
+
# monitor_interval - the interval to check the collector healtcheck and for new state logs
|
26
|
+
# monitor_interval_delay - the delay to wait before running the first monitor interval
|
27
|
+
# collector_log_level - the log level for the collector
|
28
|
+
# collector_sending_queue_storage_dir - the directory to store queue files
|
29
|
+
# collector_offset_storage_dir - the directory to store offset files
|
30
|
+
# collector_pid_file - the location of the pid file for the collector
|
31
|
+
# collector_download_dir - the directory to store downloaded collector files
|
32
|
+
# collector_config_file - the location of the config file for the collector
|
33
|
+
# collector_version - the version of the collector to download
|
34
|
+
# health_check_port - the port to use for the collector health check. Default is dynamically derived based on port availability
|
28
35
|
#
|
29
36
|
# Any of these config settings can be set with an environment variable prefixed
|
30
37
|
# by SCOUT_ and uppercasing the key: SCOUT_LOG_LEVEL for instance.
|
@@ -48,23 +55,11 @@ module ScoutApm
|
|
48
55
|
logs_reporting_endpoint_http
|
49
56
|
logs_proxy_log_dir
|
50
57
|
logs_log_file_size
|
51
|
-
logs_capture_call_stack
|
52
|
-
logs_capture_log_line
|
53
|
-
logs_call_stack_search_depth
|
54
|
-
logs_call_stack_capture_depth
|
55
|
-
logs_method_missing_warning
|
56
|
-
logs_method_missing_call_stack
|
57
58
|
].freeze
|
58
59
|
|
59
60
|
SETTING_COERCIONS = {
|
60
61
|
'logs_monitor' => BooleanCoercion.new,
|
61
|
-
'
|
62
|
-
'logs_capture_log_line' => BooleanCoercion.new,
|
63
|
-
'logs_call_stack_search_depth' => IntegerCoercion.new,
|
64
|
-
'logs_call_stack_capture_depth' => IntegerCoercion.new,
|
65
|
-
'logs_log_file_size' => IntegerCoercion.new,
|
66
|
-
'logs_method_missing_warning' => BooleanCoercion.new,
|
67
|
-
'logs_method_missing_call_stack' => BooleanCoercion.new
|
62
|
+
'logs_log_file_size' => IntegerCoercion.new
|
68
63
|
}.freeze
|
69
64
|
|
70
65
|
# The bootstrapped, and initial config that we attach to the context. Will be swapped out by
|
@@ -109,7 +104,7 @@ module ScoutApm
|
|
109
104
|
def all_settings
|
110
105
|
KNOWN_CONFIG_OPTIONS.inject([]) do |memo, key|
|
111
106
|
o = overlay_for_key(key)
|
112
|
-
memo << { key
|
107
|
+
memo << { key:, value: value(key).inspect, source: o.name }
|
113
108
|
end
|
114
109
|
end
|
115
110
|
|
@@ -122,13 +117,7 @@ module ScoutApm
|
|
122
117
|
'logs_reporting_endpoint' => 'https://otlp.scoutotel.com:4317',
|
123
118
|
'logs_reporting_endpoint_http' => 'https://otlp.scoutotel.com:4318/v1/logs',
|
124
119
|
'logs_proxy_log_dir' => '/tmp/scout_apm/logs/',
|
125
|
-
'logs_log_file_size' => 1024 * 1024 * 10
|
126
|
-
'logs_capture_call_stack' => false,
|
127
|
-
'logs_capture_log_line' => false,
|
128
|
-
'logs_call_stack_search_depth' => 15,
|
129
|
-
'logs_call_stack_capture_depth' => 2,
|
130
|
-
'logs_method_missing_warning' => true,
|
131
|
-
'logs_method_missing_call_stack' => false
|
120
|
+
'logs_log_file_size' => 1024 * 1024 * 10
|
132
121
|
}.freeze
|
133
122
|
|
134
123
|
def value(key)
|
@@ -7,14 +7,6 @@ module ScoutApm
|
|
7
7
|
# The root of the application.
|
8
8
|
attr_accessor :application_root
|
9
9
|
|
10
|
-
# Use this as the entrypoint.
|
11
|
-
def self.instance
|
12
|
-
@@instance ||= new.tap do |instance|
|
13
|
-
instance.config = ScoutApm::Logging::Config.with_file(instance, instance.config.value('config_file'))
|
14
|
-
instance.config.log_settings(instance.logger)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
10
|
# Initially start up without attempting to load a configuration file. We
|
19
11
|
# need to be able to lookup configuration options like "application_root"
|
20
12
|
# which would then in turn influence where the yaml configuration file is
|
@@ -56,7 +56,7 @@ module ScoutApm
|
|
56
56
|
# Re-extend TaggedLogging to verify the patch is be applied.
|
57
57
|
# This appears to be an issue in Ruby 2.7 with the broadcast logger.
|
58
58
|
ruby_version = Gem::Version.new(RUBY_VERSION)
|
59
|
-
isruby27 = ruby_version >= Gem::Version.new('2.7') && ruby_version < Gem::Version.new('3.0')
|
59
|
+
isruby27 = (ruby_version >= Gem::Version.new('2.7') && ruby_version < Gem::Version.new('3.0'))
|
60
60
|
return unless isruby27 && ::Rails.logger.respond_to?(:broadcasts)
|
61
61
|
|
62
62
|
::Rails.logger.broadcasts.each do |logger|
|
@@ -12,31 +12,24 @@ module ScoutApm
|
|
12
12
|
class Formatter < ::Logger::Formatter
|
13
13
|
DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%LZ'
|
14
14
|
|
15
|
-
def call(severity, time, progname, msg)
|
15
|
+
def call(severity, time, progname, msg) # rubocop:disable Metrics/AbcSize
|
16
16
|
attributes_to_log = {
|
17
|
-
severity
|
17
|
+
severity:,
|
18
18
|
time: format_datetime(time),
|
19
19
|
msg: msg2str(msg)
|
20
20
|
}
|
21
21
|
|
22
|
-
log_location = Thread.current[:scout_log_location]
|
23
|
-
|
24
22
|
attributes_to_log[:progname] = progname if progname
|
25
23
|
attributes_to_log['service.name'] = service_name
|
26
|
-
attributes_to_log['log_location'] = log_location if log_location
|
27
24
|
|
28
25
|
attributes_to_log.merge!(scout_transaction_id)
|
29
26
|
attributes_to_log.merge!(scout_layer)
|
30
27
|
attributes_to_log.merge!(scout_context)
|
28
|
+
# Naive local benchmarks show this takes around 200 microseconds. As such, we only apply it to WARN and above.
|
29
|
+
attributes_to_log.merge!(local_log_location) if ::Logger::Severity.const_get(severity) >= ::Logger::Severity::WARN
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
"#{attributes_to_log.to_json}\n"
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
31
|
+
message = "#{attributes_to_log.to_json}\n"
|
38
32
|
|
39
|
-
def emit_log(msg, severity, time, attributes_to_log)
|
40
33
|
ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider.logger(
|
41
34
|
name: 'scout_apm',
|
42
35
|
version: '0.1.0'
|
@@ -48,8 +41,11 @@ module ScoutApm
|
|
48
41
|
body: msg,
|
49
42
|
context: ::OpenTelemetry::Context.current
|
50
43
|
)
|
44
|
+
message
|
51
45
|
end
|
52
46
|
|
47
|
+
private
|
48
|
+
|
53
49
|
def format_datetime(time)
|
54
50
|
time.utc.strftime(DATETIME_FORMAT)
|
55
51
|
end
|
@@ -107,6 +103,15 @@ module ScoutApm
|
|
107
103
|
{ "scout_transaction_id": ScoutApm::RequestManager.lookup.transaction_id }
|
108
104
|
end
|
109
105
|
|
106
|
+
def local_log_location
|
107
|
+
# Should give us the last local stack which called the log within just the last couple frames.
|
108
|
+
last_local_location = caller[0..15].find { |path| path.include?(Rails.root.to_s) }
|
109
|
+
|
110
|
+
return {} unless last_local_location
|
111
|
+
|
112
|
+
{ 'log_location' => last_local_location }
|
113
|
+
end
|
114
|
+
|
110
115
|
def context
|
111
116
|
ScoutApm::Logging::Context.new.tap do |context|
|
112
117
|
context.config = ScoutApm::Logging::Config.with_file(context, context.config.value('config_file'))
|
@@ -7,70 +7,6 @@ module ScoutApm
|
|
7
7
|
class FileLogger < ::Logger
|
8
8
|
include ::ActiveSupport::LoggerSilence if const_defined?('::ActiveSupport::LoggerSilence')
|
9
9
|
|
10
|
-
def initialize(*args, **kwargs, &block)
|
11
|
-
if ScoutApm::Logging::Context.instance.config.value('logs_capture_log_line')
|
12
|
-
self.class.send(:alias_method, :debug, :debug_patched)
|
13
|
-
self.class.send(:alias_method, :info, :info_patched)
|
14
|
-
self.class.send(:alias_method, :warn, :warn_patched)
|
15
|
-
self.class.send(:alias_method, :error, :error_patched)
|
16
|
-
self.class.send(:alias_method, :fatal, :fatal_patched)
|
17
|
-
self.class.send(:alias_method, :unknown, :unknown_patched)
|
18
|
-
end
|
19
|
-
|
20
|
-
super(*args, **kwargs, &block)
|
21
|
-
end
|
22
|
-
|
23
|
-
# Taken from ::Logger. Progname becomes message if no block is given.
|
24
|
-
def debug_patched(progname = nil, &block)
|
25
|
-
return true if level > DEBUG
|
26
|
-
|
27
|
-
# short circuit the block to update the message. #add would eventually call it.
|
28
|
-
# https://github.com/ruby/logger/blob/v1.7.0/lib/logger.rb#L675
|
29
|
-
progname = yield if block_given?
|
30
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
31
|
-
add(DEBUG, progname, nil, &block)
|
32
|
-
end
|
33
|
-
|
34
|
-
def info_patched(progname = nil, &block)
|
35
|
-
return true if level > INFO
|
36
|
-
|
37
|
-
progname = yield if block_given?
|
38
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
39
|
-
add(INFO, progname, nil, &block)
|
40
|
-
end
|
41
|
-
|
42
|
-
def warn_patched(progname = nil, &block)
|
43
|
-
return true if level > WARN
|
44
|
-
|
45
|
-
progname = yield if block_given?
|
46
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
47
|
-
add(WARN, progname, nil, &block)
|
48
|
-
end
|
49
|
-
|
50
|
-
def error_patched(progname = nil, &block)
|
51
|
-
return true if level > ERROR
|
52
|
-
|
53
|
-
progname = yield if block_given?
|
54
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
55
|
-
add(ERROR, progname, nil, &block)
|
56
|
-
end
|
57
|
-
|
58
|
-
def fatal_patched(progname = nil, &block)
|
59
|
-
return true if level > FATAL
|
60
|
-
|
61
|
-
progname = yield if block_given?
|
62
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
63
|
-
add(FATAL, progname, nil, &block)
|
64
|
-
end
|
65
|
-
|
66
|
-
def unknown_patched(progname = nil, &block)
|
67
|
-
return true if level > UNKNOWN
|
68
|
-
|
69
|
-
progname = yield if block_given?
|
70
|
-
progname = add_log_file_and_line_to_message(progname) if progname
|
71
|
-
add(UNKNOWN, progname, nil, &block)
|
72
|
-
end
|
73
|
-
|
74
10
|
# Other loggers may be extended with additional methods that have not been applied to this file logger.
|
75
11
|
# Most likely, these methods will still utilize the exiting logging methods to write to the IO device,
|
76
12
|
# however, if this is not the case we may miss logs. With that being said, we shouldn't impact the original
|
@@ -78,75 +14,13 @@ module ScoutApm
|
|
78
14
|
def method_missing(name, *_args)
|
79
15
|
return unless defined?(::Rails)
|
80
16
|
|
81
|
-
|
82
|
-
|
83
|
-
if ScoutApm::Logging::Context.instance.config.value('logs_method_missing_call_stack')
|
84
|
-
cs = caller_locations(0, 20)
|
85
|
-
::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined.")
|
86
|
-
::Rails.logger.warn("Call stack: #{cs}")
|
87
|
-
else
|
88
|
-
::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined. Try setting 'logs_method_missing_call_stack' to true in your Scout configuration to see the call stack.") # rubocop:disable Layout/LineLength
|
89
|
-
end
|
17
|
+
::Rails.logger.warn("Method #{name} called on ScoutApm::Logging::Loggers::FileLogger, but it is not defined.")
|
90
18
|
end
|
91
19
|
|
92
20
|
# More impactful for the broadcast logger.
|
93
21
|
def respond_to_missing?(name, *_args)
|
94
22
|
super
|
95
23
|
end
|
96
|
-
|
97
|
-
private
|
98
|
-
|
99
|
-
# Useful for testing.
|
100
|
-
def filter_log_location(the_call_stack = caller_locations)
|
101
|
-
the_call_stack.find { |loc| loc.path.include?(Rails.root.to_s) }
|
102
|
-
end
|
103
|
-
|
104
|
-
# Cache call stack to reduce performance impact.
|
105
|
-
def call_stack
|
106
|
-
@call_stack ||= caller_locations(4, ScoutApm::Logging::Context.instance.config.value('logs_call_stack_search_depth'))
|
107
|
-
end
|
108
|
-
|
109
|
-
def find_log_location
|
110
|
-
filter_log_location(call_stack)
|
111
|
-
end
|
112
|
-
|
113
|
-
def get_call_stack_for_attribute
|
114
|
-
call_stack
|
115
|
-
.select { |loc| loc.path.include?(Rails.root.to_s) }
|
116
|
-
.map(&:to_s)
|
117
|
-
.slice(0, ScoutApm::Logging::Context.instance.config.value('logs_call_stack_capture_depth'))
|
118
|
-
.join("\n")
|
119
|
-
end
|
120
|
-
|
121
|
-
# Ideally, we would pass an additional argument to the formatter, but
|
122
|
-
# we run into issues with how tagged logging is implemented. As such,
|
123
|
-
# this is a work around for tagged logging and incorrect passed arguments.
|
124
|
-
# May need to move to fiber at some point.
|
125
|
-
def format_message(severity, datetime, progname, msg)
|
126
|
-
if ScoutApm::Logging::Context.instance.config.value('logs_capture_call_stack')
|
127
|
-
Thread.current[:scout_log_location] =
|
128
|
-
get_call_stack_for_attribute
|
129
|
-
end
|
130
|
-
|
131
|
-
super(severity, datetime, progname, msg)
|
132
|
-
|
133
|
-
# Reset for next logger call.
|
134
|
-
ensure
|
135
|
-
@call_stack = nil
|
136
|
-
Thread.current[:scout_log_location] = nil
|
137
|
-
end
|
138
|
-
|
139
|
-
def add_log_file_and_line_to_message(message)
|
140
|
-
return message unless message.is_a?(String)
|
141
|
-
|
142
|
-
file = find_log_location
|
143
|
-
return message unless file
|
144
|
-
|
145
|
-
file_path = file.path.split('/').last
|
146
|
-
line_number = file.lineno
|
147
|
-
|
148
|
-
"[#{file_path}:#{line_number}] #{message}"
|
149
|
-
end
|
150
24
|
end
|
151
25
|
|
152
26
|
# The newly created logger which we can configure, and will log to a filepath.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module OpenTelemetry
|
2
|
+
module Exporter
|
3
|
+
module OTLP
|
4
|
+
module Logs
|
5
|
+
# Patch the log record creation to add back severity number.
|
6
|
+
class LogsExporter
|
7
|
+
def as_otlp_log_record(log_record_data)
|
8
|
+
Opentelemetry::Proto::Logs::V1::LogRecord.new(
|
9
|
+
time_unix_nano: log_record_data.timestamp,
|
10
|
+
observed_time_unix_nano: log_record_data.observed_timestamp,
|
11
|
+
severity_number: as_otlp_severity_number(log_record_data.severity_number),
|
12
|
+
severity_text: log_record_data.severity_text,
|
13
|
+
body: as_otlp_any_value(log_record_data.body),
|
14
|
+
attributes: log_record_data.attributes&.map { |k, v| as_otlp_key_value(k, v) },
|
15
|
+
dropped_attributes_count: log_record_data.total_recorded_attributes - log_record_data.attributes&.size.to_i,
|
16
|
+
flags: log_record_data.trace_flags.instance_variable_get(:@flags),
|
17
|
+
trace_id: log_record_data.trace_id,
|
18
|
+
span_id: log_record_data.span_id
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# TODO: This has been removed in newer versions of the OpenTelemetry Ruby Logs SDK, however
|
23
|
+
# it appears that we are missing the severity number. Investigate this.
|
24
|
+
def as_otlp_severity_number(severity_number)
|
25
|
+
case severity_number
|
26
|
+
when 0 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_DEBUG
|
27
|
+
when 1 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_INFO
|
28
|
+
when 2 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_WARN
|
29
|
+
when 3 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_ERROR
|
30
|
+
when 4 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_FATAL
|
31
|
+
when 5 then Opentelemetry::Proto::Logs::V1::SeverityNumber::SEVERITY_NUMBER_UNSPECIFIED
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -2,37 +2,15 @@
|
|
2
2
|
require 'logger'
|
3
3
|
require 'opentelemetry'
|
4
4
|
require 'opentelemetry/sdk'
|
5
|
-
|
6
|
-
|
7
|
-
require_relative '
|
8
|
-
require_relative 'exporter/exporter/otlp/version'
|
9
|
-
require_relative 'exporter/exporter/otlp/logs_exporter'
|
5
|
+
require 'opentelemetry-logs-sdk'
|
6
|
+
require 'opentelemetry/exporter/otlp_logs'
|
7
|
+
require_relative './log_record_patch'
|
10
8
|
|
11
9
|
module ScoutApm
|
12
10
|
module Logging
|
13
11
|
module Loggers
|
14
12
|
module OpenTelemetry
|
15
13
|
class << self
|
16
|
-
# Overwritten on setup to be the internal logger.
|
17
|
-
# @return [Object, Logger] configured Logger or a default STDOUT Logger.
|
18
|
-
def logger
|
19
|
-
@logger ||= ::Logger.new($stdout, level: ENV['OTEL_LOG_LEVEL'] || ::Logger::INFO)
|
20
|
-
end
|
21
|
-
|
22
|
-
# @return [Callable] configured error handler or a default that logs the
|
23
|
-
# exception and message at ERROR level.
|
24
|
-
def error_handler
|
25
|
-
@error_handler ||= ->(exception: nil, message: nil) { logger.error("OpenTelemetry error: #{[message, exception&.message, exception&.backtrace&.first].compact.join(' - ')}") }
|
26
|
-
end
|
27
|
-
|
28
|
-
# Handles an error by calling the configured error_handler.
|
29
|
-
#
|
30
|
-
# @param [optional Exception] exception The exception to be handled
|
31
|
-
# @param [optional String] message An error message.
|
32
|
-
def handle_error(exception: nil, message: nil)
|
33
|
-
error_handler.call(exception: exception, message: message)
|
34
|
-
end
|
35
|
-
|
36
14
|
def logger_provider=(logger_provider)
|
37
15
|
@logger_provider = logger_provider
|
38
16
|
end
|
@@ -43,12 +21,11 @@ module ScoutApm
|
|
43
21
|
end
|
44
22
|
|
45
23
|
def self.setup(context)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider.add_log_record_processor(processor)
|
24
|
+
exporter = ::OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new(endpoint: context.config.value('logs_reporting_endpoint_http'))
|
25
|
+
processor = ::OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(exporter)
|
26
|
+
OpenTelemetry.logger_provider = ::OpenTelemetry::SDK::Logs::LoggerProvider.new(resource: scout_resource(context))
|
27
|
+
OpenTelemetry.logger_provider.add_log_record_processor(processor)
|
28
|
+
::OpenTelemetry.logger = context.logger || ::Logger.new($stdout, level: ENV['OTEL_LOG_LEVEL'] || ::Logger::INFO)
|
52
29
|
end
|
53
30
|
|
54
31
|
def self.scout_resource(context)
|
data/lib/scout_apm_logging.rb
CHANGED
@@ -15,7 +15,9 @@ module ScoutApm
|
|
15
15
|
# If we are in a Rails environment, setup the monitor daemon manager.
|
16
16
|
class RailTie < ::Rails::Railtie
|
17
17
|
initializer 'scout_apm_logging.monitor', after: :initialize_logger, before: :initialize_cache do
|
18
|
-
context =
|
18
|
+
context = Context.new
|
19
|
+
context.config = Config.with_file(context, context.config.value('config_file'))
|
20
|
+
context.config.log_settings(context.logger)
|
19
21
|
|
20
22
|
Loggers::Capture.new(context).setup!
|
21
23
|
end
|
data/scout_apm_logging.gemspec
CHANGED
@@ -15,13 +15,15 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
16
|
s.require_paths = ['lib']
|
17
17
|
|
18
|
-
s.required_ruby_version = '>=
|
18
|
+
s.required_ruby_version = '>= 3.1'
|
19
19
|
|
20
20
|
s.add_dependency 'googleapis-common-protos-types'
|
21
|
-
s.add_dependency 'google-protobuf', '
|
21
|
+
s.add_dependency 'google-protobuf', '>= 3.18'
|
22
22
|
s.add_dependency 'opentelemetry-api'
|
23
23
|
s.add_dependency 'opentelemetry-common'
|
24
|
+
s.add_dependency 'opentelemetry-exporter-otlp-logs', '>= 0.2.0'
|
24
25
|
s.add_dependency 'opentelemetry-instrumentation-base'
|
26
|
+
s.add_dependency 'opentelemetry-logs-sdk', '>= 0.2.0'
|
25
27
|
s.add_dependency 'opentelemetry-sdk', '>= 1.2'
|
26
28
|
s.add_dependency 'scout_apm'
|
27
29
|
|
@@ -29,7 +31,4 @@ Gem::Specification.new do |s|
|
|
29
31
|
s.add_development_dependency 'rubocop', '1.50.2'
|
30
32
|
s.add_development_dependency 'rubocop-ast', '1.30.0'
|
31
33
|
s.add_development_dependency 'webmock'
|
32
|
-
# Old, but works. It is a small wrapper library around websocket-eventmachine
|
33
|
-
# for ActionCable protocols.
|
34
|
-
s.add_development_dependency 'action_cable_client'
|
35
34
|
end
|
@@ -1,20 +1,10 @@
|
|
1
1
|
require 'webmock/rspec'
|
2
2
|
|
3
|
-
# Old, but still works.
|
4
|
-
require 'action_cable_client'
|
5
|
-
require 'eventmachine'
|
6
3
|
require 'spec_helper'
|
7
4
|
require 'zlib'
|
8
5
|
require 'stringio'
|
9
|
-
require 'securerandom'
|
10
6
|
require_relative '../../rails/app'
|
11
7
|
|
12
|
-
ScoutApm::Logging::Loggers::FileLogger.class_exec do
|
13
|
-
define_method(:filter_log_location) do |locations|
|
14
|
-
locations.find { |loc| loc.path.include?(Rails.root.to_s) && !loc.path.include?('scout_apm/logging') }
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
8
|
describe ScoutApm::Logging do
|
19
9
|
before do
|
20
10
|
@file_path = '/app/response_body.txt'
|
@@ -48,27 +38,6 @@ describe ScoutApm::Logging do
|
|
48
38
|
# Call the app to generate the logs
|
49
39
|
`curl localhost:9292`
|
50
40
|
|
51
|
-
channel_client = fork do
|
52
|
-
url = 'ws://localhost:9292/cable'
|
53
|
-
channel_name = 'TestChannel'
|
54
|
-
# Loops.
|
55
|
-
EventMachine.run do
|
56
|
-
client = ActionCableClient.new(url, channel_name)
|
57
|
-
# called whenever a welcome message is received from the server
|
58
|
-
client.connected { puts 'successfully connected.' }
|
59
|
-
|
60
|
-
client.subscribed do
|
61
|
-
puts 'client subscribed to the channel.'
|
62
|
-
client.perform('ding', { message: 'hello from client' })
|
63
|
-
end
|
64
|
-
|
65
|
-
# called whenever a message is received from the server
|
66
|
-
client.received do |message|
|
67
|
-
puts message
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
41
|
sleep 5
|
73
42
|
|
74
43
|
proxy_dir = context.config.value('logs_proxy_log_dir')
|
@@ -85,19 +54,18 @@ describe ScoutApm::Logging do
|
|
85
54
|
end
|
86
55
|
end
|
87
56
|
|
88
|
-
puts lines
|
89
|
-
|
90
57
|
local_messages = lines.map { |item| item['msg'] }
|
91
|
-
puts local_messages
|
92
58
|
|
93
59
|
# Verify we have all the logs in the local log file
|
94
60
|
expect(local_messages.count('[TEST] Some log')).to eq(1)
|
95
61
|
expect(local_messages.count('[YIELD] Yield Test')).to eq(1)
|
96
62
|
expect(local_messages.count('Another Log')).to eq(1)
|
97
63
|
expect(local_messages.count('Should not be captured')).to eq(0)
|
98
|
-
|
99
|
-
|
100
|
-
|
64
|
+
|
65
|
+
log_locations = lines.map { |item| item['log_location'] }.compact
|
66
|
+
|
67
|
+
# Verify that log attributes aren't persisted
|
68
|
+
expect(log_locations.size).to eq(1)
|
101
69
|
|
102
70
|
# Verify the logs are sent to the receiver
|
103
71
|
receiver_contents = File.readlines(@file_path, chomp: true)
|
@@ -105,21 +73,9 @@ describe ScoutApm::Logging do
|
|
105
73
|
expect(receiver_contents.count('[YIELD] Yield Test')).to eq(1)
|
106
74
|
expect(receiver_contents.count('Another Log')).to eq(1)
|
107
75
|
expect(receiver_contents.count('Should not be captured')).to eq(0)
|
108
|
-
expect(local_messages.count('Warn level log')).to eq(1)
|
109
|
-
expect(local_messages.count('Error level log')).to eq(1)
|
110
|
-
expect(local_messages.count('Fatal level log')).to eq(1)
|
111
|
-
|
112
|
-
# Verify we recorded server ActionCable messages
|
113
|
-
expect(receiver_contents.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
|
114
|
-
expect(receiver_contents.count('Subscribed to test_channel')).to eq(1)
|
115
|
-
expect(receiver_contents.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
|
116
|
-
expect(local_messages.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
|
117
|
-
expect(local_messages.count('Subscribed to test_channel')).to eq(1)
|
118
|
-
expect(local_messages.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
|
119
76
|
|
120
77
|
# Kill the rails process. We use kill as using any other signal throws a long log line.
|
121
78
|
Process.kill('KILL', rails_pid)
|
122
|
-
Process.kill('KILL', channel_client)
|
123
79
|
end
|
124
80
|
|
125
81
|
private
|
@@ -128,7 +84,7 @@ describe ScoutApm::Logging do
|
|
128
84
|
gz = Zlib::GzipReader.new(StringIO.new(body))
|
129
85
|
uncompressed = gz.read
|
130
86
|
|
131
|
-
value =
|
87
|
+
value = ::Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode(uncompressed)
|
132
88
|
value_hash = value.to_h
|
133
89
|
|
134
90
|
value_hash[:resource_logs].map do |item|
|