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 +4 -4
- data/.github/workflows/release.yml +30 -0
- data/.github/workflows/test.yml +12 -7
- data/CHANGELOG.md +16 -0
- data/README.md +5 -1
- data/lib/scout_apm/logging/loggers/formatter.rb +26 -2
- data/lib/scout_apm/logging/loggers/opentelemetry/log_record_data_compat.rb +34 -0
- data/lib/scout_apm/logging/loggers/opentelemetry/opentelemetry.rb +1 -1
- data/lib/scout_apm/logging/version.rb +1 -1
- data/scout_apm_logging.gemspec +1 -0
- data/spec/integration/rails/lifecycle_spec.rb +33 -15
- data/spec/unit/loggers/formatter_spec.rb +86 -0
- data/spec/unit/loggers/logger_spec.rb +4 -5
- data/spec/unit/loggers/opentelemetry/log_record_data_compat_spec.rb +44 -0
- metadata +22 -16
- data/lib/scout_apm/logging/loggers/opentelemetry/log_record_patch.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3e6660d99d9d31d6e78ba6ef801432d582a5a9fe78917d8b027a8cfae85e834
|
|
4
|
+
data.tar.gz: 37fd12719798598626ac9d97bfd3c13d186be2d54a0e6ac86b572ecc246c31b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/.github/workflows/test.yml
CHANGED
|
@@ -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' && !
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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=
|
|
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:
|
|
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
|
data/scout_apm_logging.gemspec
CHANGED
|
@@ -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
|
-
|
|
28
|
-
file.puts(
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
expect(
|
|
104
|
-
expect(
|
|
105
|
-
expect(
|
|
106
|
-
expect(
|
|
107
|
-
expect(
|
|
108
|
-
expect(
|
|
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(
|
|
112
|
-
expect(
|
|
113
|
-
expect(
|
|
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
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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/
|
|
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:
|
|
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
|