scout_apm_logging 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a472edfe7271717a5eb8b5c76f1dda7a84dc1b8ad2f0da4c6223173f87c44db
4
- data.tar.gz: 1eaf6f5de049078460287b0b02e09583cad2db3fc31c3b54a7d83930d5fca787
3
+ metadata.gz: e3e6660d99d9d31d6e78ba6ef801432d582a5a9fe78917d8b027a8cfae85e834
4
+ data.tar.gz: 37fd12719798598626ac9d97bfd3c13d186be2d54a0e6ac86b572ecc246c31b1
5
5
  SHA512:
6
- metadata.gz: f74a53847327e6068ad671b8f2363516971b6ee04f9aea1f2c47d5ea069eae3ba70ea5b36be17c3bd57e2d378cd3c3835c6a229d1ea825793c8456585630c4a1
7
- data.tar.gz: b3de4ed980c0129c615574b6e55e1bb245df9f85d515a3dba0fad8ebc2fe692cd99c9aeff2b96a47b4efb24fbe34fbbc540d78fcc96a048b25fab84ef6363b29
6
+ metadata.gz: 3ef2d0cb82f91cdfb640b4eed871e1b3505af4eead81317182bb53e53858ce009dccdc693227cafdc7ceffca21d9be3f95f089e34f4d1945a48599ca568e64bc
7
+ data.tar.gz: eadfd1cbd5ab6f204fbc5d8b087164c01ed7f532a33d5841b658143aeb0abda19c20d22dce4a2e827ebc4caca53c9c1830c83923185d8b103edc463805162cd3
@@ -0,0 +1,30 @@
1
+ name: Release Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ # Directly from https://github.com/rubygems/release-gem/tree/v1
9
+ # Predicated on Tag Ruleset preventing arbitrary tag creation and
10
+ # requiring checks to pass for commit before tag creation
11
+ jobs:
12
+ push:
13
+ name: Push gem to RubyGems.org
14
+ runs-on: ubuntu-latest
15
+
16
+ permissions:
17
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
18
+ contents: write # IMPORTANT: this permission is required for `rake release` to push the release tag
19
+
20
+ steps:
21
+ # Set up
22
+ - uses: actions/checkout@v4
23
+ - name: Set up Ruby
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ bundler-cache: true
27
+ ruby-version: ruby
28
+
29
+ # Release
30
+ - uses: rubygems/release-gem@v1
@@ -8,7 +8,7 @@ jobs:
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4']
11
+ ruby: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
12
12
  env:
13
13
  DOCKER_RUBY_VERSION: ${{ matrix.ruby }}
14
14
  BUNDLE_GEMFILE: gems/rails.gemfile
@@ -21,21 +21,26 @@ jobs:
21
21
  echo "LIBRARY_VERSION=$VERSION" >> $GITHUB_ENV
22
22
  - name: Set up Ruby
23
23
  uses: ruby/setup-ruby@v1
24
- if: ${{ (env.LIBRARY_VERSION == '1' && !contains(fromJson('["3.4"]'), matrix.ruby)) ||
25
- (env.LIBRARY_VERSION == '2' && !contains(fromJson('["2.6", "2.7", "3.0"]'), matrix.ruby)) }}
24
+ if: ${{ (env.LIBRARY_VERSION == '1' && !startsWith(matrix.ruby, '3.4') && !startsWith(matrix.ruby, '4.')) || (env.LIBRARY_VERSION == '2' && !startsWith(matrix.ruby, '2.6') && !startsWith(matrix.ruby, '2.7') && !startsWith(matrix.ruby, '3.0')) }}
26
25
  with:
27
26
  ruby-version: ${{ matrix.ruby }}
28
27
  bundler-cache: true
29
28
  - name: Run RSpec
30
29
  run: |
31
- if [[ "$LIBRARY_VERSION" == "1" && ("${{ matrix.ruby }}" == "3.4" ) ]]; then
32
- echo "Skipping Ruby ${{ matrix.ruby }} for Library v1.x"
30
+ RUBY="${{ matrix.ruby }}"
31
+
32
+ # Library v1: block Ruby >= 3.4 (3.4, 4.x, 5.x, ...)
33
+ if [[ "$LIBRARY_VERSION" == "1" && ( "$RUBY" == 3.4* || "$RUBY" == 4.* || "$RUBY" == 5.* ) ]]; then
34
+ echo "Skipping Ruby $RUBY for Library v1.x (>= 3.4 unsupported)"
33
35
  exit 0
34
36
  fi
35
- if [[ "$LIBRARY_VERSION" == "2" && ( "${{ matrix.ruby }}" == "2.6" || "${{ matrix.ruby }}" == "2.7" || "${{ matrix.ruby }}" == "3.0" ) ]]; then
36
- echo "Skipping Ruby ${{ matrix.ruby }} for Library v2.x"
37
+
38
+ # Library v2: block specific old Rubies
39
+ if [[ "$LIBRARY_VERSION" == "2" && ( "$RUBY" == 2.6* || "$RUBY" == 2.7* || "$RUBY" == 3.0* ) ]]; then
40
+ echo "Skipping Ruby $RUBY for Library v2.x"
37
41
  exit 0
38
42
  fi
43
+
39
44
  bundle exec rake test
40
45
 
41
46
  rubocop:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 2.2.0
2
+ * Fix severity numbers sent with `opentelemetry-exporter-otlp-logs` >= 0.7.0 (Ruby levels were sent instead of OpenTelemetry levels).
3
+ * See [#119](https://github.com/scoutapp/scout_apm_ruby_logging/pull/119).
4
+ * Remove the OTLP exporter monkey patch; severity is now mapped when the log is emitted.
5
+ * Replace the [#114](https://github.com/scoutapp/scout_apm_ruby_logging/pull/114) guard with a small `LogRecordData` shim, so mismatched `opentelemetry-exporter-otlp-logs` / `opentelemetry-logs-sdk` versions no longer drop logs.
6
+ * Declare `opentelemetry-logs-api` as a direct dependency.
7
+ * Fix `logger.unknown` raising `NameError`; it is now sent as FATAL.
8
+ * Fix logging text that isn't valid UTF-8 raising an error; invalid bytes are now replaced. Thanks @rpearce!
9
+ * See [#120](https://github.com/scoutapp/scout_apm_ruby_logging/pull/120).
10
+ * Add release workflow for publishing to RubyGems with trusted publishing.
11
+
12
+ ## 2.1.1
13
+ * Fix all logs being dropped with `opentelemetry-logs-sdk` >= 0.7.0. Thanks @rpearce!
14
+ * See [#114](https://github.com/scoutapp/scout_apm_ruby_logging/pull/114).
15
+ * Add Ruby 4.0 to test matrix.
16
+
1
17
  ## 2.1.0
2
18
  * Add ability to capture log line with logs.
3
19
  * `logs_capture_log_line: true`
data/README.md CHANGED
@@ -28,10 +28,14 @@ Update your [RAILS_ROOT/config/scout_apm.yml](https://scoutapm.com/apps/new_ruby
28
28
 
29
29
  logs_monitor: true
30
30
  logs_ingest_key: ...
31
+ logs_capture_level: debug
31
32
  ```
32
33
 
33
34
  Deploy :rocket:
34
35
 
36
+ ## Docs
37
+ For a complete list of configurations as well as additional information, visit our [docs](https://scoutapm.com/docs/ruby/log-management).
38
+
35
39
  ## Versions
36
40
  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
41
 
@@ -50,7 +54,7 @@ bundle exec rake test file=/path/to/spec/_spec.rb
50
54
 
51
55
  To run test(s) against a specific Ruby version:
52
56
  ```ruby
53
- DOCKER_RUBY_VERSION=3.4 bundle exec rake test
57
+ DOCKER_RUBY_VERSION=4.0 bundle exec rake test
54
58
  ```
55
59
 
56
60
  ## Local
@@ -3,6 +3,7 @@
3
3
  require 'json'
4
4
  require 'logger'
5
5
 
6
+ require 'opentelemetry-logs-api'
6
7
  require 'scout_apm'
7
8
 
8
9
  module ScoutApm
@@ -12,6 +13,19 @@ module ScoutApm
12
13
  class Formatter < ::Logger::Formatter
13
14
  DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%LZ'
14
15
 
16
+ # Ruby's Logger::Severity integers (0..5) do not match OpenTelemetry's
17
+ # SeverityNumber scale. Emit the OTel value directly so the exporter can
18
+ # pass it straight through without a patch. Logger labels UNKNOWN as
19
+ # 'ANY'; it ranks above FATAL, so keep it at the top of the scale.
20
+ SEVERITY_NUMBER_MAP = {
21
+ 'DEBUG' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_DEBUG,
22
+ 'INFO' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_INFO,
23
+ 'WARN' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_WARN,
24
+ 'ERROR' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_ERROR,
25
+ 'FATAL' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_FATAL,
26
+ 'ANY' => ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_FATAL
27
+ }.freeze
28
+
15
29
  def call(severity, time, progname, msg)
16
30
  attributes_to_log = {
17
31
  severity:,
@@ -36,16 +50,26 @@ module ScoutApm
36
50
 
37
51
  private
38
52
 
53
+ # to_json raises on binary or invalid text, and the raise escapes the log call.
54
+ def msg2str(msg)
55
+ text = super
56
+ return text if text.valid_encoding? && text.encoding != Encoding::BINARY
57
+
58
+ text.dup.force_encoding(Encoding::UTF_8).scrub
59
+ end
60
+
39
61
  def emit_log(msg, severity, time, attributes_to_log)
40
62
  ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider.logger(
41
63
  name: 'scout_apm',
42
64
  version: '0.1.0'
43
65
  ).on_emit(
44
66
  severity_text: severity,
45
- severity_number: ::Logger::Severity.const_get(severity),
67
+ severity_number: SEVERITY_NUMBER_MAP.fetch(
68
+ severity, ::OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_UNSPECIFIED
69
+ ),
46
70
  attributes: attributes_to_log.transform_keys(&:to_s),
47
71
  timestamp: time,
48
- body: msg,
72
+ body: msg.is_a?(String) ? attributes_to_log[:msg] : msg,
49
73
  context: ::OpenTelemetry::Context.current
50
74
  )
51
75
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenTelemetry
4
+ module SDK
5
+ module Logs
6
+ # The OTLP logs exporter and the logs SDK are versioned independently, and every exporter
7
+ # release only requires `opentelemetry-logs-sdk ~> 0.1`, so Bundler can resolve pairs that
8
+ # disagree on LogRecordData's fields:
9
+ # * exporter <= 0.5.1 reads `total_recorded_attributes`, which logs-sdk >= 0.7.0 replaced
10
+ # with `dropped_attributes_count` (read by exporter >= 0.6.0). See #114.
11
+ # * exporter >= 0.5.0 reads `event_name`, which logs-sdk < 0.6.0 does not have.
12
+ # Define whichever of these is missing so any resolved pair can export.
13
+ class LogRecordData
14
+ unless method_defined?(:total_recorded_attributes)
15
+ def total_recorded_attributes
16
+ attributes&.size.to_i + dropped_attributes_count.to_i
17
+ end
18
+ end
19
+
20
+ unless method_defined?(:dropped_attributes_count)
21
+ def dropped_attributes_count
22
+ [total_recorded_attributes.to_i - attributes&.size.to_i, 0].max
23
+ end
24
+ end
25
+
26
+ unless method_defined?(:event_name)
27
+ def event_name
28
+ nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -4,7 +4,7 @@ require 'opentelemetry'
4
4
  require 'opentelemetry/sdk'
5
5
  require 'opentelemetry-logs-sdk'
6
6
  require 'opentelemetry/exporter/otlp_logs'
7
- require_relative './log_record_patch'
7
+ require_relative './log_record_data_compat'
8
8
 
9
9
  module ScoutApm
10
10
  module Logging
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ScoutApm
4
4
  module Logging
5
- VERSION = '2.1.0'
5
+ VERSION = '2.2.0'
6
6
  end
7
7
  end
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency 'opentelemetry-common'
24
24
  s.add_dependency 'opentelemetry-exporter-otlp-logs', '>= 0.2.0'
25
25
  s.add_dependency 'opentelemetry-instrumentation-base'
26
+ s.add_dependency 'opentelemetry-logs-api'
26
27
  s.add_dependency 'opentelemetry-logs-sdk', '>= 0.2.0'
27
28
  s.add_dependency 'opentelemetry-sdk', '>= 1.2'
28
29
  s.add_dependency 'scout_apm'
@@ -24,8 +24,8 @@ describe ScoutApm::Logging do
24
24
  # We have to write to the file, as we are applying the patch in the fork, and
25
25
  # need to run the expectations in the main process.
26
26
  File.open(@file_path, 'a+') do |file|
27
- msgs = transform_body_to_msgs(request.body)
28
- file.puts(msgs)
27
+ records = transform_body_to_records(request.body)
28
+ records.each { |record| file.puts(record.to_json) }
29
29
  end
30
30
  { body: '', headers: {}, status: 200 }
31
31
  end
@@ -98,19 +98,28 @@ describe ScoutApm::Logging do
98
98
  expect(local_messages.count('Fatal level log')).to eq(1)
99
99
 
100
100
  # Verify the logs are sent to the receiver
101
- receiver_contents = File.readlines(@file_path, chomp: true)
102
- expect(receiver_contents.count('[TEST] Some log')).to eq(1)
103
- expect(receiver_contents.count('[YIELD] Yield Test')).to eq(1)
104
- expect(receiver_contents.count('Another Log')).to eq(1)
105
- expect(receiver_contents.count('Should not be captured')).to eq(0)
106
- expect(local_messages.count('Warn level log')).to eq(1)
107
- expect(local_messages.count('Error level log')).to eq(1)
108
- expect(local_messages.count('Fatal level log')).to eq(1)
101
+ receiver_records = File.readlines(@file_path, chomp: true).map { |line| JSON.parse(line) }
102
+ receiver_messages = receiver_records.map { |record| record['body'] }
103
+ expect(receiver_messages.count('[TEST] Some log')).to eq(1)
104
+ expect(receiver_messages.count('[YIELD] Yield Test')).to eq(1)
105
+ expect(receiver_messages.count('Another Log')).to eq(1)
106
+ expect(receiver_messages.count('Should not be captured')).to eq(0)
107
+ expect(receiver_messages.count('Warn level log')).to eq(1)
108
+ expect(receiver_messages.count('Error level log')).to eq(1)
109
+ expect(receiver_messages.count('Fatal level log')).to eq(1)
110
+
111
+ # Verify the severity number sent on the wire uses the OpenTelemetry scale,
112
+ # not Ruby's Logger::Severity scale. Guards against upstream exporter drift
113
+ # now that the severity-remapping patch has been removed.
114
+ expect(severity_for(receiver_records, 'Warn level log')).to eq(:SEVERITY_NUMBER_WARN)
115
+ expect(severity_for(receiver_records, 'Error level log')).to eq(:SEVERITY_NUMBER_ERROR)
116
+ expect(severity_for(receiver_records, 'Fatal level log')).to eq(:SEVERITY_NUMBER_FATAL)
117
+ expect(severity_for(receiver_records, '[TEST] Some log')).to eq(:SEVERITY_NUMBER_INFO)
109
118
 
110
119
  # Verify we recorded server ActionCable messages
111
- expect(receiver_contents.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
112
- expect(receiver_contents.count('Subscribed to test_channel')).to eq(1)
113
- expect(receiver_contents.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
120
+ expect(receiver_messages.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
121
+ expect(receiver_messages.count('Subscribed to test_channel')).to eq(1)
122
+ expect(receiver_messages.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
114
123
  expect(local_messages.count { |msg| msg.include?('ActionCable Connected:') }).to eq(1)
115
124
  expect(local_messages.count('Subscribed to test_channel')).to eq(1)
116
125
  expect(local_messages.count { |msg| msg.include?('Ding received with data: {"message"') }).to eq(1)
@@ -122,7 +131,7 @@ describe ScoutApm::Logging do
122
131
 
123
132
  private
124
133
 
125
- def transform_body_to_msgs(body)
134
+ def transform_body_to_records(body) # rubocop:disable Metrics/AbcSize
126
135
  gz = Zlib::GzipReader.new(StringIO.new(body))
127
136
  uncompressed = gz.read
128
137
 
@@ -132,9 +141,18 @@ describe ScoutApm::Logging do
132
141
  value_hash[:resource_logs].map do |item|
133
142
  item[:scope_logs].map do |sl|
134
143
  sl[:log_records].map do |log|
135
- log[:body][:string_value]
144
+ {
145
+ 'body' => log[:body][:string_value],
146
+ 'severity_number' => log[:severity_number],
147
+ 'severity_text' => log[:severity_text]
148
+ }
136
149
  end
137
150
  end
138
151
  end.flatten
139
152
  end
153
+
154
+ def severity_for(records, body)
155
+ record = records.find { |item| item['body'] == body }
156
+ record && record['severity_number']&.to_sym
157
+ end
140
158
  end
@@ -0,0 +1,86 @@
1
+ require 'json'
2
+ require 'logger'
3
+ require 'stringio'
4
+
5
+ require 'spec_helper'
6
+
7
+ require 'scout_apm_logging'
8
+
9
+ describe ScoutApm::Logging::Loggers::Formatter do
10
+ context 'severity' do
11
+ let(:exporter) { OpenTelemetry::SDK::Logs::Export::InMemoryLogRecordExporter.new }
12
+
13
+ before do
14
+ ScoutApm::Logging::Context.instance
15
+
16
+ provider = OpenTelemetry::SDK::Logs::LoggerProvider.new
17
+ provider.add_log_record_processor(OpenTelemetry::SDK::Logs::Export::SimpleLogRecordProcessor.new(exporter))
18
+ ScoutApm::Logging::Loggers::OpenTelemetry.logger_provider = provider
19
+ end
20
+
21
+ {
22
+ 'DEBUG' => :SEVERITY_NUMBER_DEBUG,
23
+ 'INFO' => :SEVERITY_NUMBER_INFO,
24
+ 'WARN' => :SEVERITY_NUMBER_WARN,
25
+ 'ERROR' => :SEVERITY_NUMBER_ERROR,
26
+ 'FATAL' => :SEVERITY_NUMBER_FATAL,
27
+ 'ANY' => :SEVERITY_NUMBER_FATAL,
28
+ 'SOMETHING_ELSE' => :SEVERITY_NUMBER_UNSPECIFIED
29
+ }.each do |label, expected|
30
+ it "emits #{expected} for #{label}" do
31
+ described_class.new.call(label, Time.now, nil, 'msg')
32
+
33
+ record = exporter.emitted_log_records.last
34
+ expect(record.severity_text).to eq(label)
35
+ expect(record.severity_number).to eq(OpenTelemetry::Logs::SeverityNumber.const_get(expected))
36
+ end
37
+ end
38
+
39
+ it 'maps Logger#unknown to FATAL' do
40
+ logger = ::Logger.new(StringIO.new)
41
+ logger.formatter = described_class.new
42
+ logger.unknown('gone')
43
+
44
+ expect(exporter.emitted_log_records.last.severity_number).to eq(OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_FATAL)
45
+ end
46
+ end
47
+
48
+ context 'encoding' do
49
+ let(:formatter) { ScoutApm::Logging::Loggers::Formatter.new }
50
+ let(:otel_logger) { double(on_emit: nil) }
51
+
52
+ before do
53
+ allow(ScoutApm::Logging::Loggers::OpenTelemetry).to receive(:logger_provider).and_return(double(logger: otel_logger))
54
+ end
55
+
56
+ it 'should replace bytes that are not valid UTF-8' do
57
+ line = formatter.call('INFO', Time.now, nil, "bad \xFF byte")
58
+
59
+ expect(JSON.parse(line)['msg']).to eq("bad \u{FFFD} byte")
60
+ end
61
+
62
+ it 'should read binary text, as a request path is logged, as UTF-8' do
63
+ line = formatter.call('INFO', Time.now, nil, 'café /x?%FF'.b + "\xFF".b)
64
+
65
+ expect(JSON.parse(line)['msg']).to eq("café /x?%FF\u{FFFD}")
66
+ end
67
+
68
+ it 'should keep valid text in another encoding' do
69
+ line = formatter.call('INFO', Time.now, nil, 'café'.encode('UTF-16LE'))
70
+
71
+ expect(JSON.parse(line)['msg']).to eq('café')
72
+ end
73
+
74
+ it 'should replace bad bytes in an exception message' do
75
+ line = formatter.call('ERROR', Time.now, nil, RuntimeError.new("bad \xFF byte".b))
76
+
77
+ expect(JSON.parse(line)['msg']).to eq("bad \u{FFFD} byte (RuntimeError)\n")
78
+ end
79
+
80
+ it 'should emit the replaced text as the OpenTelemetry body' do
81
+ formatter.call('INFO', Time.now, nil, "bad \xFF byte".b)
82
+
83
+ expect(otel_logger).to have_received(:on_emit).with(hash_including(body: "bad \u{FFFD} byte"))
84
+ end
85
+ end
86
+ end
@@ -5,11 +5,6 @@ require 'spec_helper'
5
5
 
6
6
  require 'scout_apm_logging'
7
7
 
8
- ScoutApm::Logging::Loggers::Formatter.class_exec do
9
- define_method(:emit_log) do |msg, severity, time, attributes_to_log|
10
- end
11
- end
12
-
13
8
  def capture_stdout
14
9
  old_stdout = $stdout
15
10
  $stdout = StringIO.new
@@ -20,6 +15,10 @@ ensure
20
15
  end
21
16
 
22
17
  describe ScoutApm::Logging::Loggers::Logger do
18
+ before do
19
+ allow_any_instance_of(ScoutApm::Logging::Loggers::Formatter).to receive(:emit_log)
20
+ end
21
+
23
22
  it 'should not capture call stack or log line' do
24
23
  ScoutApm::Logging::Context.instance
25
24
 
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../../lib/scout_apm/logging/loggers/opentelemetry/opentelemetry'
4
+
5
+ describe OpenTelemetry::SDK::Logs::LogRecordData do
6
+ let(:exporter) { OpenTelemetry::SDK::Logs::Export::InMemoryLogRecordExporter.new }
7
+
8
+ let(:log_record_data) do
9
+ provider = OpenTelemetry::SDK::Logs::LoggerProvider.new
10
+ provider.add_log_record_processor(OpenTelemetry::SDK::Logs::Export::SimpleLogRecordProcessor.new(exporter))
11
+
12
+ provider.logger(name: 'scout_apm', version: '0.1.0').on_emit(
13
+ severity_text: 'INFO',
14
+ severity_number: OpenTelemetry::Logs::SeverityNumber::SEVERITY_NUMBER_INFO,
15
+ attributes: { 'controller' => 'UsersController' },
16
+ timestamp: Time.now,
17
+ body: 'Processing by UsersController#index'
18
+ )
19
+
20
+ exporter.emitted_log_records.first
21
+ end
22
+
23
+ it 'responds to the fields read by every supported exporter version' do
24
+ expect(log_record_data.total_recorded_attributes).to eq(1)
25
+ expect(log_record_data.dropped_attributes_count).to eq(0)
26
+ expect(log_record_data).to respond_to(:event_name)
27
+ end
28
+
29
+ it 'exports through the installed OTLP exporter without raising' do
30
+ otlp_exporter = OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new
31
+ # Exporter >= 0.7.0 moved encoding into opentelemetry-exporter-otlp-common.
32
+ encoded = if otlp_exporter.respond_to?(:encode, true)
33
+ otlp_exporter.send(:encode, [log_record_data])
34
+ else
35
+ OpenTelemetry::Exporter::OTLP::Common.as_encoded_elsr([log_record_data])
36
+ end
37
+ request = Opentelemetry::Proto::Collector::Logs::V1::ExportLogsServiceRequest.decode(encoded)
38
+ record = request.resource_logs.first.scope_logs.first.log_records.first
39
+
40
+ expect(record.body.string_value).to eq('Processing by UsersController#index')
41
+ expect(record.severity_number).to eq(:SEVERITY_NUMBER_INFO)
42
+ expect(record.dropped_attributes_count).to eq(0)
43
+ end
44
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm_logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scout APM
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-06-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: googleapis-common-protos-types
@@ -94,6 +93,20 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: opentelemetry-logs-api
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: opentelemetry-logs-sdk
99
112
  requirement: !ruby/object:Gem::Requirement
@@ -213,6 +226,7 @@ executables: []
213
226
  extensions: []
214
227
  extra_rdoc_files: []
215
228
  files:
229
+ - ".github/workflows/release.yml"
216
230
  - ".github/workflows/test.yml"
217
231
  - ".gitignore"
218
232
  - ".rubocop.yml"
@@ -230,7 +244,7 @@ files:
230
244
  - lib/scout_apm/logging/loggers/formatter.rb
231
245
  - lib/scout_apm/logging/loggers/logger.rb
232
246
  - lib/scout_apm/logging/loggers/opentelemetry/NOTICE
233
- - lib/scout_apm/logging/loggers/opentelemetry/log_record_patch.rb
247
+ - lib/scout_apm/logging/loggers/opentelemetry/log_record_data_compat.rb
234
248
  - lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb
235
249
  - lib/scout_apm/logging/loggers/patches/rails_logger.rb
236
250
  - lib/scout_apm/logging/loggers/patches/tagged_logging.rb
@@ -249,12 +263,13 @@ files:
249
263
  - spec/spec_helper.rb
250
264
  - spec/unit/config_spec.rb
251
265
  - spec/unit/loggers/capture_spec.rb
266
+ - spec/unit/loggers/formatter_spec.rb
252
267
  - spec/unit/loggers/logger_spec.rb
268
+ - spec/unit/loggers/opentelemetry/log_record_data_compat_spec.rb
253
269
  homepage: https://github.com/scoutapp/scout_apm_ruby_logging
254
270
  licenses:
255
271
  - MIT
256
272
  metadata: {}
257
- post_install_message:
258
273
  rdoc_options: []
259
274
  require_paths:
260
275
  - lib
@@ -269,16 +284,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
284
  - !ruby/object:Gem::Version
270
285
  version: '0'
271
286
  requirements: []
272
- rubygems_version: 3.3.26
273
- signing_key:
287
+ rubygems_version: 4.0.20
274
288
  specification_version: 4
275
289
  summary: Managed log monitoring for Ruby applications.
276
- test_files:
277
- - spec/data/config_test_1.yml
278
- - spec/data/mock_config.yml
279
- - spec/integration/rails/lifecycle_spec.rb
280
- - spec/rails/app.rb
281
- - spec/spec_helper.rb
282
- - spec/unit/config_spec.rb
283
- - spec/unit/loggers/capture_spec.rb
284
- - spec/unit/loggers/logger_spec.rb
290
+ test_files: []
@@ -1,38 +0,0 @@
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