julewire-gcp 1.0.0 → 1.1.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/CHANGELOG.md +12 -0
- data/julewire-gcp.gemspec +1 -1
- data/lib/julewire/gcp/destination.rb +1 -1
- data/lib/julewire/gcp/formatter.rb +12 -17
- data/lib/julewire/gcp/formatter_options.rb +3 -5
- data/lib/julewire/gcp/label_formatter.rb +10 -11
- data/lib/julewire/gcp/log_decoder.rb +2 -2
- data/lib/julewire/gcp/source_location.rb +42 -7
- data/lib/julewire/gcp/source_location_options.rb +4 -6
- data/lib/julewire/gcp/trace_context.rb +1 -7
- data/lib/julewire/gcp/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bb2a1472182d07e7099ea1d254a8f56d6fcad94527eb5c9959974191d3b4bc5
|
|
4
|
+
data.tar.gz: 6a2ce085d54af374465210210496dc5bcf14692710f44c09717d500b9ad8dc5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 383c1c579384c7ffeeb2b95c3b6ad7ec12f75ab3625955e7e11c62c6fbbdea907e8e53a8405b206a1f3e1be92f4d9fe5723e03241b4ba5f39151947645fa8735
|
|
7
|
+
data.tar.gz: 832fdcf7596afe7de1113ca3bc7599414a8ff87c76f969b0ca09452bcd06f088a11952dd3632ec0c5ad0af9d8ee91d8b680927eb018316e99ddf5e2995d92f6f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
|
+
## 1.1.0 - 2026-07-20
|
|
4
|
+
|
|
5
|
+
- Keep Cloud Logging formatting and decoding aligned with the core record field
|
|
6
|
+
taxonomy and deterministic trace/source parsing.
|
|
7
|
+
- Require julewire-core 1.1.0.
|
|
8
|
+
|
|
9
|
+
## 1.0.1 - 2026-06-25
|
|
10
|
+
|
|
11
|
+
- Replace the fallback backtrace regex with deterministic parsing so GCP source
|
|
12
|
+
location inference stays boring under CodeQL.
|
|
13
|
+
- Require julewire-core 1.0.1.
|
|
14
|
+
|
|
3
15
|
## 1.0.0 - 2026-06-21
|
|
4
16
|
|
|
5
17
|
- Initial release: Google Cloud Logging formatter, trace fields, source
|
data/julewire-gcp.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module GCP
|
|
5
5
|
class Destination < Julewire::Core::Destinations::Destination
|
|
6
|
-
def initialize(output:, name: :gcp, formatter: nil, encoder:
|
|
6
|
+
def initialize(output:, name: :gcp, formatter: nil, encoder: JsonEncoder.new,
|
|
7
7
|
max_record_bytes: DEFAULT_MAX_RECORD_BYTES, close_output: false, on_drop: nil,
|
|
8
8
|
on_failure: nil)
|
|
9
9
|
super(
|
|
@@ -41,11 +41,11 @@ module Julewire
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def call(record)
|
|
44
|
-
|
|
44
|
+
Record.validate_normalized!(record)
|
|
45
45
|
|
|
46
46
|
error = record.fetch(:error)
|
|
47
47
|
operation_options = operation_options(record)
|
|
48
|
-
neutral_attributes =
|
|
48
|
+
neutral_attributes = record.fetch(:neutral)
|
|
49
49
|
source_location_options = SourceLocationOptions.call(record, neutral_attributes)
|
|
50
50
|
entry = {
|
|
51
51
|
"severity" => severity(record.fetch(:severity)),
|
|
@@ -67,7 +67,7 @@ module Julewire
|
|
|
67
67
|
|
|
68
68
|
private
|
|
69
69
|
|
|
70
|
-
def frozen_service_context(value) =
|
|
70
|
+
def frozen_service_context(value) = Core::Fields::FieldSet.frozen_copy(value)
|
|
71
71
|
|
|
72
72
|
def append_special_fields(entry, record, error:, operation_options:, source_location_options:,
|
|
73
73
|
neutral_attributes:)
|
|
@@ -110,7 +110,6 @@ module Julewire
|
|
|
110
110
|
return if (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
|
|
111
111
|
|
|
112
112
|
entry[key] = value
|
|
113
|
-
nil
|
|
114
113
|
end
|
|
115
114
|
|
|
116
115
|
def severity(value) = SEVERITIES.fetch(value)
|
|
@@ -134,17 +133,19 @@ module Julewire
|
|
|
134
133
|
end
|
|
135
134
|
|
|
136
135
|
def operation_options(record)
|
|
137
|
-
|
|
136
|
+
control = record.fetch(:payload)[:gcp]
|
|
137
|
+
return EMPTY_HASH unless control.is_a?(Hash)
|
|
138
|
+
|
|
139
|
+
options = control[:operation]
|
|
138
140
|
options.is_a?(Hash) ? options : EMPTY_HASH
|
|
139
141
|
end
|
|
140
142
|
|
|
141
143
|
def operation_producer(record, options)
|
|
142
|
-
options[:producer] || @operation_producer || record
|
|
144
|
+
options[:producer] || @operation_producer || record.fetch(:source) || record.fetch(:logger)
|
|
143
145
|
end
|
|
144
146
|
|
|
145
147
|
def source_location(error, options)
|
|
146
148
|
return SourceLocation.call(options) unless options.empty?
|
|
147
|
-
return unless error.is_a?(Hash) && !error.empty?
|
|
148
149
|
|
|
149
150
|
SourceLocation.from_error(error)
|
|
150
151
|
end
|
|
@@ -183,7 +184,7 @@ module Julewire
|
|
|
183
184
|
end
|
|
184
185
|
|
|
185
186
|
def remove_gcp_control_payload(payload, control)
|
|
186
|
-
return payload unless control.is_a?(Hash)
|
|
187
|
+
return payload unless control.is_a?(Hash)
|
|
187
188
|
|
|
188
189
|
cleaned_payload = payload.dup
|
|
189
190
|
cleaned_control = control.dup
|
|
@@ -197,11 +198,7 @@ module Julewire
|
|
|
197
198
|
cleaned_payload
|
|
198
199
|
end
|
|
199
200
|
|
|
200
|
-
def
|
|
201
|
-
control.key?(:operation) || control.key?(:source_location)
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def julewire_payload(record, error:, operation_options:, stack_trace: nil)
|
|
201
|
+
def julewire_payload(record, error:, operation_options:, stack_trace:)
|
|
205
202
|
{}.tap do |payload|
|
|
206
203
|
append_log_field(payload, :kind, record.fetch(:kind))
|
|
207
204
|
append_log_field(payload, :event, record.fetch(:event))
|
|
@@ -214,19 +211,17 @@ module Julewire
|
|
|
214
211
|
)
|
|
215
212
|
append_log_field(payload, :context, record.fetch(:context))
|
|
216
213
|
append_log_field(payload, :error, julewire_error(error, stack_trace: stack_trace))
|
|
217
|
-
append_log_field(payload, :metrics, record
|
|
214
|
+
append_log_field(payload, :metrics, record.fetch(:metrics))
|
|
218
215
|
end
|
|
219
216
|
end
|
|
220
217
|
|
|
221
218
|
def julewire_error(error, stack_trace:)
|
|
222
|
-
return error unless
|
|
219
|
+
return error unless stack_trace
|
|
223
220
|
|
|
224
221
|
StackTrace.remove_backtraces(error)
|
|
225
222
|
end
|
|
226
223
|
|
|
227
224
|
def stack_trace(error)
|
|
228
|
-
return unless error.is_a?(Hash) && !error.empty?
|
|
229
|
-
|
|
230
225
|
StackTrace.call(error)
|
|
231
226
|
end
|
|
232
227
|
|
|
@@ -20,7 +20,7 @@ module Julewire
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def trace_headers_paths(paths)
|
|
23
|
-
Array(paths).filter_map { normalize_path(it, min_length: 1) }
|
|
23
|
+
Array(paths).filter_map { normalize_path(it, min_length: 1) }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def trace_value_path(path)
|
|
@@ -34,7 +34,7 @@ module Julewire
|
|
|
34
34
|
def label_options(options)
|
|
35
35
|
label_options = options.fetch(:label_options, {}).dup
|
|
36
36
|
%i[max_labels max_label_key_bytes max_label_value_bytes].each do |key|
|
|
37
|
-
label_options[key] = options
|
|
37
|
+
label_options[key] = options.fetch(key) if options.key?(key)
|
|
38
38
|
end
|
|
39
39
|
label_options
|
|
40
40
|
end
|
|
@@ -42,12 +42,10 @@ module Julewire
|
|
|
42
42
|
private
|
|
43
43
|
|
|
44
44
|
def normalize_path(path, min_length:)
|
|
45
|
-
return if path.nil?
|
|
46
|
-
|
|
47
45
|
normalized = Array(path).map { normalize_path_segment(it) }
|
|
48
46
|
return if normalized.length < min_length
|
|
49
47
|
|
|
50
|
-
normalized
|
|
48
|
+
normalized
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
def normalize_path_segment(segment)
|
|
@@ -3,19 +3,17 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module GCP
|
|
5
5
|
class LabelFormatter
|
|
6
|
-
def initialize(max_labels:
|
|
7
|
-
max_label_key_bytes:
|
|
8
|
-
max_label_value_bytes:
|
|
6
|
+
def initialize(max_labels: DEFAULT_MAX_LABELS,
|
|
7
|
+
max_label_key_bytes: DEFAULT_MAX_LABEL_KEY_BYTES,
|
|
8
|
+
max_label_value_bytes: DEFAULT_MAX_LABEL_VALUE_BYTES)
|
|
9
9
|
@max_labels = validate_count_limit(max_labels, name: :max_labels)
|
|
10
10
|
@max_label_key_bytes = validate_byte_limit(max_label_key_bytes, name: :max_label_key_bytes)
|
|
11
11
|
@max_label_value_bytes = validate_byte_limit(max_label_value_bytes, name: :max_label_value_bytes)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call(labels)
|
|
15
|
-
return if labels.empty?
|
|
16
|
-
|
|
17
15
|
labels.each_with_object({}) do |(key, value), result|
|
|
18
|
-
break result if
|
|
16
|
+
break result if result.size == @max_labels
|
|
19
17
|
|
|
20
18
|
label_key = label_key(key)
|
|
21
19
|
next unless label_key
|
|
@@ -28,18 +26,20 @@ module Julewire
|
|
|
28
26
|
|
|
29
27
|
def label_key(value)
|
|
30
28
|
key = label_string(value)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
nil
|
|
29
|
+
key unless @max_label_key_bytes && key.bytesize > @max_label_key_bytes
|
|
34
30
|
end
|
|
35
31
|
|
|
36
32
|
def bounded_label_string(value, max_bytes)
|
|
37
33
|
string = label_string(value)
|
|
38
|
-
return string unless
|
|
34
|
+
return string unless label_too_long?(string, max_bytes)
|
|
39
35
|
|
|
40
36
|
label_string(string.byteslice(0, max_bytes))
|
|
41
37
|
end
|
|
42
38
|
|
|
39
|
+
def label_too_long?(string, max_bytes)
|
|
40
|
+
!max_bytes.nil? && string.bytesize > max_bytes
|
|
41
|
+
end
|
|
42
|
+
|
|
43
43
|
def label_string(value)
|
|
44
44
|
Core::Serialization::EncodingSanitizer.call(value.to_s)
|
|
45
45
|
end
|
|
@@ -52,7 +52,6 @@ module Julewire
|
|
|
52
52
|
|
|
53
53
|
def validate_byte_limit(value, name:)
|
|
54
54
|
Core::Validation.validate_byte_limit!(value, name: name)
|
|
55
|
-
value
|
|
56
55
|
end
|
|
57
56
|
end
|
|
58
57
|
end
|
|
@@ -36,7 +36,7 @@ module Julewire
|
|
|
36
36
|
def record_base(payload, julewire)
|
|
37
37
|
{
|
|
38
38
|
timestamp: payload["time"] || payload["timestamp"],
|
|
39
|
-
severity:
|
|
39
|
+
severity: Core::Records::Severity.normalize(payload["severity"] || :info),
|
|
40
40
|
kind: RecordDecoder.kind(julewire["kind"] || :point),
|
|
41
41
|
event: julewire["event"],
|
|
42
42
|
message: payload["message"],
|
|
@@ -46,7 +46,7 @@ module Julewire
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def record_sections(payload, julewire)
|
|
49
|
-
RecordDecoder.sections(
|
|
49
|
+
RecordDecoder.sections(nil) do |section, _source|
|
|
50
50
|
gcp_section_value(section, payload, julewire)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module GCP
|
|
5
5
|
module SourceLocation
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
FUNCTION_SEPARATOR = ":in "
|
|
7
|
+
QUOTE_BYTES = [39, 96].freeze
|
|
8
|
+
private_constant :FUNCTION_SEPARATOR, :QUOTE_BYTES
|
|
8
9
|
|
|
9
10
|
class << self
|
|
10
11
|
def call(options)
|
|
@@ -27,13 +28,22 @@ module Julewire
|
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def from_backtrace_line(line)
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
line = line.to_s
|
|
32
|
+
index = line.rindex(FUNCTION_SEPARATOR)
|
|
33
|
+
if index
|
|
34
|
+
location = line[0, index]
|
|
35
|
+
function = line[(index + FUNCTION_SEPARATOR.length)..]
|
|
36
|
+
else
|
|
37
|
+
location = line
|
|
38
|
+
function = nil
|
|
39
|
+
end
|
|
40
|
+
file, line_number = split_file_and_line(location)
|
|
41
|
+
return unless line_number
|
|
32
42
|
|
|
33
43
|
call(
|
|
34
|
-
file:
|
|
35
|
-
line:
|
|
36
|
-
function:
|
|
44
|
+
file: file,
|
|
45
|
+
line: line_number,
|
|
46
|
+
function: normalize_function(function)
|
|
37
47
|
)
|
|
38
48
|
end
|
|
39
49
|
|
|
@@ -47,6 +57,31 @@ module Julewire
|
|
|
47
57
|
string = value.to_s
|
|
48
58
|
string if string.match?(/\A\d+\z/)
|
|
49
59
|
end
|
|
60
|
+
|
|
61
|
+
def split_file_and_line(location)
|
|
62
|
+
index = location.rindex(":")
|
|
63
|
+
return unless index
|
|
64
|
+
|
|
65
|
+
file = location[0, index]
|
|
66
|
+
line = location[(index + 1)..]
|
|
67
|
+
return if file.empty? || !line.match?(/\A\d+\z/)
|
|
68
|
+
|
|
69
|
+
[file, line]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def normalize_function(function)
|
|
73
|
+
return unless function
|
|
74
|
+
|
|
75
|
+
if function.length >= 2 && quoted?(function.getbyte(0)) && quoted?(function.getbyte(-1))
|
|
76
|
+
function[1...-1]
|
|
77
|
+
else
|
|
78
|
+
function
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def quoted?(byte)
|
|
83
|
+
QUOTE_BYTES.include?(byte)
|
|
84
|
+
end
|
|
50
85
|
end
|
|
51
86
|
end
|
|
52
87
|
end
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module GCP
|
|
5
5
|
module SourceLocationOptions
|
|
6
|
-
EMPTY_HASH = {}.freeze
|
|
7
|
-
private_constant :EMPTY_HASH
|
|
8
|
-
|
|
9
6
|
class << self
|
|
10
7
|
def call(record, neutral_attributes)
|
|
11
|
-
|
|
8
|
+
control = record.fetch(:payload)[:gcp]
|
|
9
|
+
return from_neutral_attributes(neutral_attributes) unless control.is_a?(Hash)
|
|
10
|
+
|
|
11
|
+
options = control[:source_location]
|
|
12
12
|
return options if options.is_a?(Hash)
|
|
13
13
|
|
|
14
14
|
from_neutral_attributes(neutral_attributes)
|
|
@@ -18,8 +18,6 @@ module Julewire
|
|
|
18
18
|
file = neutral_attributes[Core::Fields::AttributeKeys::CODE_FILE_PATH]
|
|
19
19
|
line = neutral_attributes[Core::Fields::AttributeKeys::CODE_LINE_NUMBER]
|
|
20
20
|
function = neutral_attributes[Core::Fields::AttributeKeys::CODE_FUNCTION_NAME]
|
|
21
|
-
return EMPTY_HASH if file.nil? && line.nil? && function.nil?
|
|
22
|
-
|
|
23
21
|
values = Core::Integration::Values::Shape
|
|
24
22
|
options = {}
|
|
25
23
|
values.append_field(options, :file, file)
|
|
@@ -25,13 +25,7 @@ module Julewire
|
|
|
25
25
|
module Hex
|
|
26
26
|
class << self
|
|
27
27
|
def zero?(value)
|
|
28
|
-
|
|
29
|
-
while offset < value.bytesize
|
|
30
|
-
return false unless value.getbyte(offset) == 48
|
|
31
|
-
|
|
32
|
-
offset += 1
|
|
33
|
-
end
|
|
34
|
-
true
|
|
28
|
+
value.each_byte.all? { it == 48 }
|
|
35
29
|
end
|
|
36
30
|
end
|
|
37
31
|
end
|
data/lib/julewire/gcp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: julewire-gcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Grebennik
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 1.1.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 1.1.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: zeitwerk
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -93,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
93
93
|
- !ruby/object:Gem::Version
|
|
94
94
|
version: '0'
|
|
95
95
|
requirements: []
|
|
96
|
-
rubygems_version: 4.0.
|
|
96
|
+
rubygems_version: 4.0.16
|
|
97
97
|
specification_version: 4
|
|
98
98
|
summary: Google Cloud Logging formatter for Julewire.
|
|
99
99
|
test_files: []
|