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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/.rubocop.yml +1 -2
  4. data/CHANGELOG.md +5 -11
  5. data/README.md +7 -2
  6. data/lib/scout_apm/logging/config.rb +16 -27
  7. data/lib/scout_apm/logging/context.rb +0 -8
  8. data/lib/scout_apm/logging/loggers/capture.rb +1 -1
  9. data/lib/scout_apm/logging/loggers/formatter.rb +17 -12
  10. data/lib/scout_apm/logging/loggers/logger.rb +1 -127
  11. data/lib/scout_apm/logging/loggers/opentelemetry/log_record_patch.rb +38 -0
  12. data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +8 -31
  13. data/lib/scout_apm/logging/version.rb +1 -1
  14. data/lib/scout_apm_logging.rb +3 -1
  15. data/scout_apm_logging.gemspec +4 -5
  16. data/spec/integration/rails/lifecycle_spec.rb +6 -50
  17. data/spec/rails/app.rb +2 -37
  18. metadata +36 -47
  19. data/lib/scout_apm/logging/loggers/opentelemetry/LICENSE +0 -201
  20. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/log_record.rb +0 -18
  21. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger.rb +0 -64
  22. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/logger_provider.rb +0 -31
  23. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/severity_number.rb +0 -43
  24. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs/version.rb +0 -18
  25. data/lib/scout_apm/logging/loggers/opentelemetry/api/logs.rb +0 -28
  26. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/logs_exporter.rb +0 -398
  27. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/exporter/otlp/version.rb +0 -20
  28. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/collector/logs/v1/logs_service_pb.rb +0 -43
  29. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/common/v1/common_pb.rb +0 -58
  30. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/logs/v1/logs_pb.rb +0 -91
  31. data/lib/scout_apm/logging/loggers/opentelemetry/exporter/proto/resource/v1/resource_pb.rb +0 -33
  32. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/batch_log_record_processor.rb +0 -223
  33. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export/log_record_exporter.rb +0 -64
  34. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/export.rb +0 -34
  35. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record.rb +0 -170
  36. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_data.rb +0 -31
  37. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_limits.rb +0 -49
  38. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/log_record_processor.rb +0 -52
  39. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger.rb +0 -98
  40. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/logger_provider.rb +0 -170
  41. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs/version.rb +0 -20
  42. data/lib/scout_apm/logging/loggers/opentelemetry/sdk/logs.rb +0 -29
  43. data/spec/unit/loggers/logger_spec.rb +0 -95
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83a18019240ac4086ab5fbbc2c729c334123830425a569442629783bb0e20a65
4
- data.tar.gz: 9f4e105cf2d85cc9352434c7749fc61bbc3545ee2b5baee56c86a0f0a47b14a8
3
+ metadata.gz: c7064d80f9892529f81a77b0e46f9e517e8ea96f8e9e53880ee2eefd6c5abbf2
4
+ data.tar.gz: 3a3ec083c05435e99d23b719bbc31f38d7ce4a07ddcf25ef47fed544147ea21c
5
5
  SHA512:
6
- metadata.gz: 426922ace12c94d8e84c9840971884b55ca9ac2328c15f76553f3259987448ea264bed665b85fb3bfa34d14230a556ababd1b7810fa339174c31bc851210a33d
7
- data.tar.gz: 72503cc9faaf7130028e466f71ea785ec3ece65a82cc5a88d318e68f4123ba89464961a9a6c50c2e22061b0c39cdce0d8af045b4e0c3577e2f4b86709ae527d7
6
+ metadata.gz: d7ea291371355544a94b69d3ab7ad75ee7407c84d450bb3e9a44ce6aabfa6bb2c27ce5813276111fb6584268eaba59875d346bd6b36b83c4db0d153478f7ba9f
7
+ data.tar.gz: ff9ee96494248654b146c2cc9d45cc7db960196781661cc94e3f79ebbabde83930b4c264e67c0270cd24e1510027242b62e307cda061b26e1b2789d1af5f7941
@@ -12,7 +12,7 @@ jobs:
12
12
  env:
13
13
  DOCKER_RUBY_VERSION: ${{ matrix.ruby }}
14
14
  BUNDLE_GEMFILE: gems/rails.gemfile
15
- runs-on: ubuntu-latest
15
+ runs-on: ubuntu-20.04
16
16
  steps:
17
17
  - uses: actions/checkout@v2
18
18
  - name: Extract Library Version
data/.rubocop.yml CHANGED
@@ -8,11 +8,10 @@ AllCops:
8
8
  - "gems/*"
9
9
  - "Rakefile"
10
10
  NewCops: disable
11
- TargetRubyVersion: 2.6
11
+ TargetRubyVersion: 3.1
12
12
 
13
13
  Metrics/BlockLength:
14
14
  Exclude:
15
- - "scout_apm_logging.gemspec"
16
15
  - "spec/**/*"
17
16
 
18
17
  Metrics/ClassLength:
data/CHANGELOG.md CHANGED
@@ -1,14 +1,8 @@
1
- ## 1.2.0
2
- * Add ability to capture log line with logs.
3
- * `logs_capture_log_line: true`
4
- * Add ability to capture stack trace with logs.
5
- * `logs_capture_call_stack: true`
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
  [![Build Status](https://github.com/scoutapp/scout_apm_ruby_logging/actions/workflows/test.yml/badge.svg)](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.3 bundle exec rake test
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
- # logs_capture_call_stack - true or false. If true, capture the call stack for each log message
23
- # logs_capture_log_line - true or false. If true, capture the log line for each log message
24
- # logs_call_stack_search_depth - the number of frames to search in the call stack
25
- # logs_call_stack_capture_depth - the number of frames to capture in the call stack
26
- # logs_method_missing_warning - true or false. If true, log a warning when method_missing is called
27
- # logs_method_missing_call_stack - true or false. If true, capture the call stack when method_missing is called
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
- 'logs_capture_call_stack' => BooleanCoercion.new,
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: key, value: value(key).inspect, source: o.name }
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: 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
- emit_log(msg, severity, time, attributes_to_log)
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
- return unless ScoutApm::Logging::Context.instance.config.value('logs_method_missing_warning')
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
- require_relative 'api/logs'
7
- require_relative 'sdk/logs'
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
- @logger = context.logger
47
-
48
- exporter = OpenTelemetry::Exporter::OTLP::LogsExporter.new(endpoint: context.config.value('logs_reporting_endpoint_http'))
49
- processor = OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(exporter)
50
- ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider = OpenTelemetry::SDK::Logs::LoggerProvider.new(resource: scout_resource(context))
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ScoutApm
4
4
  module Logging
5
- VERSION = '1.2.0'
5
+ VERSION = '2.0.0'
6
6
  end
7
7
  end
@@ -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 = ScoutApm::Logging::Context.instance
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
@@ -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 = '>= 2.6'
18
+ s.required_ruby_version = '>= 3.1'
19
19
 
20
20
  s.add_dependency 'googleapis-common-protos-types'
21
- s.add_dependency 'google-protobuf', '~> 3.0'
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
- expect(local_messages.count('Warn level log')).to eq(1)
99
- expect(local_messages.count('Error level log')).to eq(1)
100
- expect(local_messages.count('Fatal level log')).to eq(1)
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 = ScoutApm::Logging::Loggers::Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode(uncompressed)
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|