exis_ray 0.2.0 → 0.3.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 +7 -1
- data/README.md +1 -0
- data/Rakefile +3 -1
- data/lib/exis_ray/json_formatter.rb +28 -1
- data/lib/exis_ray/version.rb +1 -1
- data/spec/exis_ray/json_formatter_spec.rb +159 -0
- data/spec/spec_helper.rb +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37c492f253a0a6899c05e68bb34b2d3f2f2bb90a4ee94a205e602fd821aa7a65
|
|
4
|
+
data.tar.gz: dac186ccd577e7171655656029db9ab1d3dfe52bc6b5b005f61821f50a99259d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54fcb9e9ffe4172373089e229942d960bf60d9a64b36c34ed5080acac7458fb6013f8d647eac6de4f2c702eca45491e905cf8728bd6982483ecb419a62745df7
|
|
7
|
+
data.tar.gz: d2921731f064a4d1216a402de59bc915cfec98eeea8671d17a4ce39340b1f1180d2795d75e165827072cc16b2d363f9437826626a2f14e4b79316bd7ea202c12
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
## [
|
|
1
|
+
## [0.3.0] - 2026-03-23
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- **KV String Parser in JsonFormatter:** The `JsonFormatter` now automatically detects and parses messages in `key=value` format.
|
|
5
|
+
- Extracted pairs from strings are elevated to the JSON root, allowing structured logging from plain string messages.
|
|
6
|
+
- Support for quoted values with spaces (e.g., `message="some text"`) and escaped characters within logs.
|
|
7
|
+
- Added comprehensive RSpec suite for `ExisRay::JsonFormatter` to verify message processing logic.
|
|
2
8
|
|
|
3
9
|
## [0.2.0] - 2026-03-12
|
|
4
10
|
|
data/README.md
CHANGED
|
@@ -250,6 +250,7 @@ end
|
|
|
250
250
|
* **`ExisRay::Current`**: The business layer. Manages domain identity (`User`, `ISP`).
|
|
251
251
|
* **`ExisRay::Reporter`**: The observability layer. Bridges the gap between your app and Sentry.
|
|
252
252
|
* **`ExisRay::JsonFormatter`**: The central logging engine. Intercepts HTTP, Sidekiq, and Tasks to output clean JSON.
|
|
253
|
+
* **KV String Parser:** It automatically detects if a log message (String) uses `key=value` format. If so, it parses the pairs and elevates them to the root of the JSON. For example, `Rails.logger.info "event=boot status=ok"` becomes `{"event":"boot","status":"ok",...}`. It supports quoted values with spaces: `message="something went wrong"`.
|
|
253
254
|
* **`ExisRay::TaskMonitor`**: The entry point for non-HTTP processes.
|
|
254
255
|
|
|
255
256
|
## License
|
data/Rakefile
CHANGED
|
@@ -84,7 +84,10 @@ module ExisRay
|
|
|
84
84
|
# Procesa el cuerpo del mensaje recibido y lo fusiona con el payload.
|
|
85
85
|
#
|
|
86
86
|
# Si el mensaje es un `Hash` (como el que nos pasará Lograge para peticiones HTTP),
|
|
87
|
-
# se hace un merge directo. Si es
|
|
87
|
+
# se hace un merge directo. Si es un String con formato key=value (ej: "event=foo bar=baz"),
|
|
88
|
+
# se parsea y los campos se elevan al nivel raíz del JSON. Valores con espacios deben estar
|
|
89
|
+
# entre comillas (ej: message="algo salió mal"). Si el String no sigue ese formato, se asigna
|
|
90
|
+
# a la clave `:message`.
|
|
88
91
|
#
|
|
89
92
|
# @param payload [Hash] El diccionario base del log.
|
|
90
93
|
# @param msg [String, Hash, Object] El mensaje original recibido por el logger.
|
|
@@ -92,9 +95,33 @@ module ExisRay
|
|
|
92
95
|
def process_message(payload, msg)
|
|
93
96
|
if msg.is_a?(Hash)
|
|
94
97
|
payload.merge!(msg)
|
|
98
|
+
elsif msg.is_a?(String) && kv_string?(msg)
|
|
99
|
+
payload.merge!(parse_kv_string(msg))
|
|
95
100
|
else
|
|
96
101
|
payload[:message] = msg.to_s
|
|
97
102
|
end
|
|
98
103
|
end
|
|
104
|
+
|
|
105
|
+
# Determina si un string tiene formato key=value.
|
|
106
|
+
# Considera que hay formato kv si el string comienza con `word=`.
|
|
107
|
+
#
|
|
108
|
+
# @param str [String]
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
def kv_string?(str)
|
|
111
|
+
str.match?(/\A\w+=/)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Parsea un string con formato key=value y retorna un Hash.
|
|
115
|
+
# Soporta valores con espacios si están entre comillas dobles (ej: message="algo salió mal").
|
|
116
|
+
#
|
|
117
|
+
# @param str [String]
|
|
118
|
+
# @return [Hash]
|
|
119
|
+
def parse_kv_string(str)
|
|
120
|
+
result = {}
|
|
121
|
+
str.scan(/(\w+)=("(?:[^"\\]|\\.)*"|\S+)/) do |key, value|
|
|
122
|
+
result[key.to_sym] = value.start_with?('"') ? value[1..-2].gsub('\\"', '"') : value
|
|
123
|
+
end
|
|
124
|
+
result
|
|
125
|
+
end
|
|
99
126
|
end
|
|
100
127
|
end
|
data/lib/exis_ray/version.rb
CHANGED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe ExisRay::JsonFormatter do
|
|
6
|
+
subject(:formatter) { described_class.new }
|
|
7
|
+
|
|
8
|
+
let(:severity) { "INFO" }
|
|
9
|
+
let(:timestamp) { Time.utc(2025, 9, 1, 12, 0, 0) }
|
|
10
|
+
let(:progname) { nil }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
# Stub Tracer y Current para aislar el formatter de dependencias externas
|
|
14
|
+
stub_const("ExisRay::Tracer", Module.new do
|
|
15
|
+
def self.service_name = "test-service"
|
|
16
|
+
def self.root_id = nil
|
|
17
|
+
def self.trace_id = nil
|
|
18
|
+
end)
|
|
19
|
+
|
|
20
|
+
allow(ExisRay).to receive(:current_class).and_return(nil)
|
|
21
|
+
|
|
22
|
+
# current_tags es un método de ActiveSupport::TaggedLogging::Formatter que depende
|
|
23
|
+
# de IsolatedExecutionState (thread-local). Lo stubeamos para entornos sin Rails.
|
|
24
|
+
allow(formatter).to receive(:current_tags).and_return([])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def call(msg)
|
|
28
|
+
JSON.parse(formatter.call(severity, timestamp, progname, msg))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#call" do
|
|
32
|
+
context "cuando el mensaje es un Hash" do
|
|
33
|
+
it "eleva los campos al nivel raíz del JSON" do
|
|
34
|
+
result = call({ event: "archive_lookup", cutoff: "2025-09-01" })
|
|
35
|
+
|
|
36
|
+
expect(result).to include(
|
|
37
|
+
"event" => "archive_lookup",
|
|
38
|
+
"cutoff" => "2025-09-01",
|
|
39
|
+
"level" => "INFO",
|
|
40
|
+
"service" => "test-service"
|
|
41
|
+
)
|
|
42
|
+
expect(result).not_to have_key("message")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "convierte claves symbol y string indistintamente" do
|
|
46
|
+
result = call({ "event" => "foo", bar: "baz" })
|
|
47
|
+
|
|
48
|
+
expect(result).to include("event" => "foo", "bar" => "baz")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "cuando el mensaje es un String con formato key=value" do
|
|
53
|
+
it "parsea los pares y los eleva al nivel raíz" do
|
|
54
|
+
result = call("event=archive_lookup cutoff=2025-09-01")
|
|
55
|
+
|
|
56
|
+
expect(result).to include(
|
|
57
|
+
"event" => "archive_lookup",
|
|
58
|
+
"cutoff" => "2025-09-01",
|
|
59
|
+
"level" => "INFO",
|
|
60
|
+
"service" => "test-service"
|
|
61
|
+
)
|
|
62
|
+
expect(result).not_to have_key("message")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "soporta un único par key=value" do
|
|
66
|
+
result = call("event=boot")
|
|
67
|
+
|
|
68
|
+
expect(result["event"]).to eq("event=boot".split("=").last)
|
|
69
|
+
expect(result).not_to have_key("message")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "soporta valores con espacios entre comillas dobles" do
|
|
73
|
+
result = call('event=error message="algo salió mal"')
|
|
74
|
+
|
|
75
|
+
expect(result["event"]).to eq("error")
|
|
76
|
+
expect(result["message"]).to eq("algo salió mal")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "soporta comillas escapadas dentro del valor" do
|
|
80
|
+
result = call('msg="dijo \"hola\""')
|
|
81
|
+
|
|
82
|
+
expect(result["msg"]).to eq('dijo "hola"')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "soporta múltiples pares con tipos de valor variados" do
|
|
86
|
+
result = call("component=orders event=invoice_generated duration_ms=42.5 retries=0")
|
|
87
|
+
|
|
88
|
+
expect(result).to include(
|
|
89
|
+
"component" => "orders",
|
|
90
|
+
"event" => "invoice_generated",
|
|
91
|
+
"duration_ms" => "42.5",
|
|
92
|
+
"retries" => "0"
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context "cuando el mensaje es un String libre (sin formato key=value)" do
|
|
98
|
+
it "asigna el string completo al campo message" do
|
|
99
|
+
result = call("algo salió mal")
|
|
100
|
+
|
|
101
|
+
expect(result["message"]).to eq("algo salió mal")
|
|
102
|
+
expect(result).to include("level" => "INFO", "service" => "test-service")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "asigna un string vacío al campo message" do
|
|
106
|
+
result = call("")
|
|
107
|
+
|
|
108
|
+
expect(result["message"]).to eq("")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "convierte objetos arbitrarios a string via to_s" do
|
|
112
|
+
result = call(42)
|
|
113
|
+
|
|
114
|
+
expect(result["message"]).to eq("42")
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe "campos base" do
|
|
119
|
+
it "incluye time en formato ISO8601 UTC" do
|
|
120
|
+
result = call("event=boot")
|
|
121
|
+
|
|
122
|
+
expect(result["time"]).to eq("2025-09-01T12:00:00Z")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "incluye level y service" do
|
|
126
|
+
result = call("event=boot")
|
|
127
|
+
|
|
128
|
+
expect(result["level"]).to eq("INFO")
|
|
129
|
+
expect(result["service"]).to eq("test-service")
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe "#kv_string? (privado)" do
|
|
135
|
+
it "retorna true si el string empieza con word=" do
|
|
136
|
+
expect(formatter.send(:kv_string?, "event=foo")).to be true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "retorna false para strings libres" do
|
|
140
|
+
expect(formatter.send(:kv_string?, "algo salió mal")).to be false
|
|
141
|
+
expect(formatter.send(:kv_string?, "")).to be false
|
|
142
|
+
expect(formatter.send(:kv_string?, "=sinkey")).to be false
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe "#parse_kv_string (privado)" do
|
|
147
|
+
it "retorna un hash con claves symbol" do
|
|
148
|
+
result = formatter.send(:parse_kv_string, "a=1 b=2")
|
|
149
|
+
|
|
150
|
+
expect(result).to eq({ a: "1", b: "2" })
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "desenvuelve las comillas dobles de los valores" do
|
|
154
|
+
result = formatter.send(:parse_kv_string, 'msg="hello world"')
|
|
155
|
+
|
|
156
|
+
expect(result).to eq({ msg: "hello world" })
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: exis_ray
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel Edera
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -78,6 +78,8 @@ files:
|
|
|
78
78
|
- lib/exis_ray/tracer.rb
|
|
79
79
|
- lib/exis_ray/version.rb
|
|
80
80
|
- sig/exis_ray.rbs
|
|
81
|
+
- spec/exis_ray/json_formatter_spec.rb
|
|
82
|
+
- spec/spec_helper.rb
|
|
81
83
|
homepage: https://github.com/gedera/exis_ray
|
|
82
84
|
licenses:
|
|
83
85
|
- MIT
|