exis_ray 0.5.1 → 0.5.3

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: de4cf609747379030a7d49bf06aea93efbe9d60490eb3d9bfe4af3fec0fe1bd4
4
- data.tar.gz: 0f3cbba6bf49ffec549c1cc7a306b9c018c12196f4a60afd62769268c5f964bf
3
+ metadata.gz: d8bf4b504bfe6112b50328a358879a63b5ff19832e4724711a4650a110c94aaf
4
+ data.tar.gz: fd3332690b0ff0b3ca95ccf4dddbddcb99322fd01481f9da4e11a12d3f0bf801
5
5
  SHA512:
6
- metadata.gz: 5e9532985f27d6ccb4efe76be22b3ff580eb60169e7f7c4a6a9db4ae19ac05a41489d43635f754deb6effd7732c97203ab0e28c19ae845f41f1a284ed79231ec
7
- data.tar.gz: 40796e0043612a9636e212ae4ced8e096a984144e35f4d1e4baa9a1216514f8916ad9f3b2c054852861157015b4d37e9987da5e6b335a34254d0e5b5b8327468
6
+ metadata.gz: 639eb1ab36f013dbf034dd7be88e5c13ab77779ed6a130d0f28cb75ace6baf36b0bb7b387f7ec77cb9a37236cb5ac703daa9bfa7116160aeee59ccc18e43b9be
7
+ data.tar.gz: '0882e98c833ebc8774e47f2ddca1d33ce73fd9b7d8a5427b9851eafcab4323c41b0253d512e2ec9b0fd2391120f6390eeb1be2995d924a3674e155cda7c36dcb'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## [0.5.3] - 2026-03-24
2
+
3
+ ### Changed
4
+ - **OpenTelemetry Alignment:** Free-text log lines now emit their content under the `body` key instead of `message`, following the OpenTelemetry Log Data Model specification.
5
+
6
+ ### Fixed
7
+ - **Robust KV Parsing:** Added support for single-quoted values (`' '`) in the KV parser. This fixes cases where human-readable durations or other strings used single quotes.
8
+ - **Quote Sanitization:** Values extracted from the KV parser now have surrounding quotes (single or double) removed before JSON emission.
9
+
10
+ ## [0.5.2] - 2026-03-24
11
+
12
+ ### Added
13
+ - **Official Documentation:** Introduced `MANIFEST.md` as the source of truth for observability standards.
14
+ - **Universal Type-Casting:** Improved `JsonFormatter` to apply automatic numeric casting (Integer/Float) to direct `Hash` inputs, not just KV strings.
15
+ - **Security Hardening:** Documented explicit bans on PII in logs and established valid `source` entrypoint values.
16
+
1
17
  ## [0.5.1] - 2026-03-24
2
18
 
3
19
  ### Fixed
data/CLAUDE.md CHANGED
@@ -4,11 +4,18 @@
4
4
  - `ExisRay::Tracer`: Distributed tracing (AWS X-Ray). Uses `CurrentAttributes` for thread-safety.
5
5
  - `ExisRay::Current`: Business context (User, ISP). Abstract base class for host app subclassing.
6
6
  - `ExisRay::Reporter`: Sentry wrapper. Abstract base class for host app subclassing.
7
- - `ExisRay::JsonFormatter`: Core engine. Handles Hash, KV strings, and free-text with automatic masking.
7
+ - `ExisRay::JsonFormatter`: Core engine. Handles Hash, KV strings, and free-text with automatic masking and **Type Casting**.
8
8
  - `ExisRay::LogSubscriber`: Native HTTP logger since v0.4.0. Replaces Lograge.
9
+ - `ExisRay::TaskMonitor`: Lifecycle manager for non-HTTP processes.
9
10
 
10
11
  ## Technical Knowledge & Compatibility
11
12
 
13
+ ### Wispro Observability Spec (v1)
14
+ - **Manifest:** All logging MUST follow the rules defined in `MANIFEST.md`.
15
+ - **Metrics:** Always use `_s` suffix for durations (Float) and `count` for volumes (Integer). Never include units in values.
16
+ - **Type-Awareness:** `JsonFormatter` automatically casts numeric KV values to Float/Integer. Emit raw numbers in KV strings.
17
+ - **Automatic Fields:** Do not manually log `time`, `level`, `service`, `source`, `root_id`, `correlation_id`, `sidekiq_job` or `task`. These are handled by the library.
18
+
12
19
  ### Rails Compatibility (6, 7, 8)
13
20
  - **Reloading:** Use `cache_classes?` helper (checks `respond_to?(:enable_reloading)`) to avoid deprecation warnings in Rails 7.1+.
14
21
  - **Notifications:** Rails 7.1+ uses `all_listeners_for`, while 6/7.0 uses `listeners_for`. Always use `respond_to?` guards when manipulating subscribers.
@@ -17,11 +24,14 @@
17
24
  - **Propagation:** Use `propagation_trace_header` (standard HTTP format) for outgoing requests.
18
25
  - **Parsing:** `trace_header` (Rack format) is ONLY for incoming request parsing.
19
26
 
20
- ### Security & Privacy
21
- - **Sentry Context:** Default `sentry_user_context` and `sentry_isp_context` to `{ id: }` only. Never send full objects to avoid leaking sensitive fields (PII/Tokens).
22
- - **Masking:** `JsonFormatter` automatically filters `password`, `token`, `api_key`, `auth`, `secret`.
27
+ ## Security & Privacy
28
+ - **Sensitive Key Filtering:** `JsonFormatter` auto-filters keys matching `password|pass|passwd|secret|token|api_key|auth` replaced with `[FILTERED]`.
29
+ - **Header Injection Prevention:** `ExisRay::Current` sanitizes values via `sanitize_header_value` before storing user/ISP identity.
30
+ - **No PII in Logs:** Never log raw user data. Only IDs (`user_id`, `isp_id`) are permitted.
23
31
 
24
32
  ## Execution Rules
25
33
  - **No Lograge:** Do not suggest or re-add the Lograge dependency.
26
- - **Pure Data Logging:** Gem internal logs must use KV strings (`component=exis_ray event=...`).
27
- - **Resilience:** All logging operations must be wrapped in `rescue StandardError` to ensure the host application never crashes due to a telemetry failure.
34
+ - **Pure Data Logging:** Internal logs use KV strings (`component=exis_ray event=...`).
35
+ - **Resilience:** All logging operations must be wrapped in `rescue StandardError`.
36
+ - **Automatic Fields:** NEVER manually log `time`, `level`, `service`, `source`, `root_id`, `correlation_id`, `sidekiq_job` or `task`. These are handled by the library.
37
+ - **Source Values:** Valid values for `source` field: `http`, `sidekiq`, `task`, `system`.
data/MANIFEST.md ADDED
@@ -0,0 +1,98 @@
1
+ # Observability & Telemetry Manifest
2
+
3
+ Este documento define el estándar obligatorio de telemetría y logging estructurado para todo nuestro ecosistema de software. El objetivo es garantizar que cualquier sistema, independientemente de su propósito, emita datos que sean **analizables, consistentes y escalables**.
4
+
5
+ ## Objetivo
6
+ Transformar el logging tradicional en un **flujo de eventos de datos**. Esto permite que herramientas de análisis puedan generar dashboards de rendimiento, alertas inteligentes y rastreo de errores sin necesidad de procesamiento manual de texto o transformaciones complejas.
7
+
8
+ ## Reglas Fundamentales
9
+
10
+ ### 1. Unidad en la Key, Número en el Valor (Data First)
11
+ La regla de oro para que las métricas sean operables es separar la unidad del dato numérico. **Nunca** incluir unidades dentro de los valores de los logs.
12
+ * Incorrecto: `duration="0.5s"`, `memory="128MB"`.
13
+ * Correcto: `duration_s=0.5`, `memory_mb=128`.
14
+
15
+ **Sufijos de unidad recomendados:**
16
+ - `_s`: Segundos (Float). Estándar para duraciones y latencias visibles al negocio.
17
+ - `_ms`: Milisegundos (Integer). Para precisión técnica interna de alta frecuencia.
18
+ - `_count`: Cantidades o volúmenes (Integer). Ej: `record_count`, `retry_count`.
19
+ - `_bytes` / `_kb` / `_mb`: Unidades de almacenamiento o memoria.
20
+ - `_human`: (Opcional) Texto explicativo legible para humanos (ej: `duration_human="2 hours"`).
21
+
22
+ ### 2. Contexto de Identidad
23
+ Cada línea de log debe llevar los metadatos necesarios para identificar su origen técnico de forma inmediata:
24
+ * `component`: Nombre de la gema, librería o módulo responsable (ej: `exis_ray`, `storage_engine`).
25
+ * `event`: Nombre de la acción puntual o hito alcanzado (ej: `http_request`, `engine.complete`).
26
+ * `source`: El punto de entrada de la ejecución (`http`, `sidekiq`, `task`, `system`).
27
+
28
+ ### 3. Convenciones de Naming & Tipos
29
+ - **Naming:** Las llaves (keys) deben usar siempre `snake_case`.
30
+ - **Valores Numéricos:** Deben emitirse como números reales (sin sufijos de texto) para permitir que el motor de logs realice el casting automático.
31
+ - **Formato:** Pares `key=value` en una sola línea estructurada.
32
+
33
+ ## 🏗 Infraestructura de Datos (Automática)
34
+
35
+ Para evitar logs redundantes y pesados, **NUNCA** incluyas manualmente las siguientes llaves en tus mensajes de log. La capa de infraestructura (`exis_ray`) las inyecta automáticamente en el nivel raíz del JSON:
36
+
37
+ | Llave | Descripción | Por qué no incluirla |
38
+ | :--- | :--- | :--- |
39
+ | `time` | Timestamp ISO8601 | Lo añade el Logger base. |
40
+ | `level` | INFO, ERROR, etc. | Lo añade el Logger base. |
41
+ | `service` | Nombre de la App | Se obtiene de la configuración global. |
42
+ | `source` | Entrypoint (http, task) | Lo inyecta el middleware/monitor correspondiente. |
43
+ | `root_id` | Trace ID (AWS X-Ray) | Se gestiona a nivel de hilo/petición. |
44
+ | `correlation_id`| ID de rastreo cruzado | Se genera automáticamente al inicio de la ejecución. |
45
+ | `user_id` / `isp_id`| Contexto de negocio | Se extrae del estado global de la petición. |
46
+ | `sidekiq_job` | Clase del Worker | Inyectado automáticamente en procesos Sidekiq. |
47
+ | `task` | Nombre de la tarea | Inyectado automáticamente por el TaskMonitor. |
48
+
49
+ > **Regla de Oro:** Tu log manual solo debe contener datos de **tu lógica de negocio**. La infraestructura ya sabe quién eres, de dónde vienes y cuál es tu ID de traza.
50
+
51
+ ---
52
+
53
+ ## 🛰 Ciclo de Vida del Evento
54
+
55
+ ## Ciclo de Vida del Evento
56
+
57
+ ### Procesos, Trabajos y Tareas (Jobs/Tasks)
58
+ Todo proceso aislado debe reportar su inicio y su finalización con una estructura consistente:
59
+ - `event`: Identificador de estado (ej: `task_started`, `task_finished`).
60
+ - `status`: Resultado final de la operación (`success`, `failed`, `aborted`). En contexto de tareas/jobs, siempre es un string semántico, no un código HTTP.
61
+ - `duration_s`: Tiempo total de ejecución (calculado con reloj monotónico).
62
+ - `error_class` / `error_message`: Obligatorios solo en caso de fallo.
63
+
64
+ ### Peticiones de Interfaz (APIs/HTTP)
65
+ Los logs de cierre de peticiones deben estandarizar el reporte de rendimiento para telemetría:
66
+ - `status`: Código de respuesta HTTP (Integer). En contexto HTTP siempre es el código numérico (ej: `200`, `404`, `500`).
67
+ - `duration_s`: Tiempo total de respuesta del servidor.
68
+ - `[subsystem]_runtime_s`: Desglose opcional de tiempos por capa (ej: `db_runtime_s`, `view_runtime_s`).
69
+
70
+ ---
71
+
72
+ ## Ejemplo de Implementación Estándar
73
+
74
+ **En el código fuente:**
75
+ ```ruby
76
+ # Reporte de éxito con métricas integradas
77
+ logger.info "component=data_processor event=sync_complete status=success duration_s=1.25 record_count=500"
78
+
79
+ # Reporte de error con contexto técnico
80
+ logger.error "component=data_processor event=sync_complete status=failed error_class=Timeout error_message=\"Server reset\""
81
+ ```
82
+
83
+ **Resultado JSON unificado:**
84
+ ```json
85
+ {
86
+ "time": "2026-03-24T14:00:00Z",
87
+ "level": "INFO",
88
+ "service": "my_application",
89
+ "component": "data_processor",
90
+ "event": "sync_complete",
91
+ "status": "success",
92
+ "duration_s": 1.25,
93
+ "record_count": 500,
94
+ "source": "task",
95
+ "root_id": "Root=1-...",
96
+ "correlation_id": "my_application;Root=..."
97
+ }
98
+ ```
data/README.md CHANGED
@@ -148,14 +148,14 @@ end
148
148
 
149
149
  When `config.log_format = :json` is enabled, ExisRay transforms all your application outputs into single-line, context-rich JSON objects.
150
150
 
151
- **HTTP Requests (via Lograge):**
151
+ **HTTP Requests:**
152
152
  ```json
153
153
  {"time":"2026-03-12T14:30:00Z","level":"INFO","service":"App-HTTP","root_id":"Root=1-65a...bc","trace_id":"Root=1-65a...bc;Self=...","request_id":"9876-abcd-...","user_id":42,"isp_id":10,"method":"GET","path":"/api/v1/users","format":"html","controller":"UsersController","action":"index","status":200,"duration":45.2,"view":20.1,"db":15.0}
154
154
  ```
155
155
 
156
156
  **Sidekiq Jobs & Rake Tasks:**
157
157
  ```json
158
- {"time":"2026-03-12T14:31:00Z","level":"INFO","service":"Sidekiq-HardWorker","root_id":"Root=1-65a...bc","user_id":42,"message":"[ExisRay] Processing payment..."}
158
+ {"time":"2026-03-12T14:31:00Z","level":"INFO","service":"Sidekiq-HardWorker","root_id":"Root=1-65a...bc","user_id":42,"body":"[ExisRay] Processing payment..."}
159
159
  ```
160
160
 
161
161
  ### B. Automatic Sidekiq Integration
@@ -22,8 +22,8 @@ module ExisRay
22
22
  # Detecta si un string comienza con al menos un par key=value.
23
23
  KV_DETECT_RE = /\A\w+=/
24
24
 
25
- # Extrae pares key=value de un string. Soporta valores sin espacios o entre comillas dobles.
26
- KV_PARSE_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/
25
+ # Extrae pares key=value de un string. Soporta valores sin espacios o entre comillas dobles/simples.
26
+ KV_PARSE_RE = /(\w+)=("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)/
27
27
 
28
28
  # Claves sensibles que deben filtrarse automáticamente según el estándar de Gabriel.
29
29
  SENSITIVE_KEYS = /password|pass|passwd|secret|token|api_key|auth/i
@@ -99,7 +99,7 @@ module ExisRay
99
99
  # se hace un merge directo. Si es un String con formato key=value (ej: "event=foo bar=baz"),
100
100
  # se parsea y los campos se elevan al nivel raíz del JSON. Valores con espacios deben estar
101
101
  # entre comillas (ej: message="algo salió mal"). Si el String no sigue ese formato, se asigna
102
- # a la clave `:message`.
102
+ # a la clave `:body` (OpenTelemetry log body).
103
103
  #
104
104
  # @param payload [Hash] El diccionario base del log.
105
105
  # @param msg [String, Hash, Object] El mensaje original recibido por el logger.
@@ -110,9 +110,9 @@ module ExisRay
110
110
  payload.merge!(filter_sensitive_hash(msg))
111
111
  elsif msg.is_a?(String) && kv_string?(msg)
112
112
  parsed = parse_kv_string(msg)
113
- parsed.empty? ? payload[:message] = msg : payload.merge!(parsed)
113
+ parsed.empty? ? payload[:body] = msg : payload.merge!(parsed)
114
114
  else
115
- payload[:message] = msg.to_s
115
+ payload[:body] = msg.to_s
116
116
  end
117
117
  end
118
118
 
@@ -126,7 +126,7 @@ module ExisRay
126
126
  end
127
127
 
128
128
  # Parsea un string con formato key=value y retorna un Hash.
129
- # Soporta valores con espacios si están entre comillas dobles (ej: message="algo salió mal").
129
+ # Soporta valores con espacios si están entre comillas dobles o simples.
130
130
  # Intenta convertir valores numéricos a Float o Integer automáticamente.
131
131
  #
132
132
  # @param str [String]
@@ -134,7 +134,13 @@ module ExisRay
134
134
  def parse_kv_string(str)
135
135
  result = {}
136
136
  str.scan(KV_PARSE_RE) do |key, value|
137
- val = value.start_with?('"') ? (value[1..-2] || "").gsub('\\"', '"') : value
137
+ # Eliminamos comillas envolventes si existen (dobles o simples)
138
+ val = if value.start_with?('"', "'")
139
+ value[1..-2].to_s.gsub("\\#{value[0]}", value[0])
140
+ else
141
+ value
142
+ end
143
+
138
144
  result[key.to_sym] = cast_value(key, val)
139
145
  end
140
146
  result
@@ -165,7 +171,7 @@ module ExisRay
165
171
  result[k] = if v.is_a?(Hash)
166
172
  filter_sensitive_hash(v)
167
173
  else
168
- filter_sensitive_value(k, v)
174
+ cast_value(k, v)
169
175
  end
170
176
  end
171
177
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ExisRay
4
4
  # Versión actual de la gema.
5
- VERSION = "0.5.1"
5
+ VERSION = "0.5.3"
6
6
  end
@@ -98,32 +98,32 @@ RSpec.describe ExisRay::JsonFormatter do
98
98
  end
99
99
  end
100
100
 
101
- it "cae a message si el string parece kv pero no produce ningún par" do
101
+ it "cae a body si el string parece kv pero no produce ningún par" do
102
102
  result = call("key=")
103
103
 
104
- expect(result["message"]).to eq("key=")
104
+ expect(result["body"]).to eq("key=")
105
105
  expect(result).not_to have_key("key")
106
106
  end
107
107
  end
108
108
 
109
109
  context "cuando el mensaje es un String libre (sin formato key=value)" do
110
- it "asigna el string completo al campo message" do
110
+ it "asigna el string completo al campo body" do
111
111
  result = call("algo salió mal")
112
112
 
113
- expect(result["message"]).to eq("algo salió mal")
113
+ expect(result["body"]).to eq("algo salió mal")
114
114
  expect(result).to include("level" => "INFO", "service" => "test-service")
115
115
  end
116
116
 
117
- it "asigna un string vacío al campo message" do
117
+ it "asigna un string vacío al campo body" do
118
118
  result = call("")
119
119
 
120
- expect(result["message"]).to eq("")
120
+ expect(result["body"]).to eq("")
121
121
  end
122
122
 
123
123
  it "convierte objetos arbitrarios a string via to_s" do
124
124
  result = call(42)
125
125
 
126
- expect(result["message"]).to eq("42")
126
+ expect(result["body"]).to eq("42")
127
127
  end
128
128
  end
129
129
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: exis_ray
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Edera
@@ -48,6 +48,7 @@ files:
48
48
  - CHANGELOG.md
49
49
  - CLAUDE.md
50
50
  - LICENSE.txt
51
+ - MANIFEST.md
51
52
  - README.md
52
53
  - Rakefile
53
54
  - lib/exis_ray.rb